January 19, 2023 (5 months ago)
What will the future of e-commerce look like? The see-now-want-now consumer mentality has created a clear path to live video shopping — and you don’t want to be left behind.
So, where does Mux fit in? You may remember when we launched Real-Time Video earlier this year. There are lots of ways Real-Time Video can take your app to the next level, and one of them happens to be building a live shopping experience.
For example, take our friends at Impekable — a 10-year-old, award-winning digital product studio born in the San Francisco Bay area. They’ve done some pretty cool things with live shopping experiences, including building and redesigning the website and mobile experience (which included live shopping!) for Thailand’s largest retailer, Central Department Store.
Needless to say, Impekable knows a thing or two about creating an interactive live shopping experience, so we were really excited to see what they built for live shopping with our Real-Time Video product and the Stripe payment processing platform. And now we’re even more excited to share it with you.
Here’s Pek, founder and CEO of Impekable, to discuss why live shopping matters and walk you through how his team built an interactive live shopping experience with Real-Time Video and Stripe.
I don’t think anybody doubts that online e-commerce is important in today’s age. Amazon and Shopify have made it clear you need an online presence or you will get left behind. If you want to get ahead of the game, it’s time to incorporate live shopping into your sales motion. But don’t just take my word for it:
Why is live shopping superior to plain e-commerce? In addition to all the benefits of online e-commerce — including buy anywhere, anytime — livestream shopping offers qualities associated with physical shopping, like human interaction.
In the same way Shopify made it easy for anyone to have an e-commerce store, we wanted to show that a simple live shopping experience is accessible and easy to build with Mux’s Real-Time Video and Stripe. Inspired by Instagram’s live shopping feature, we translated the mobile experience into a web desktop-based demo. Here’s a small video of what we came up with.
In this demo, the Presenter can stream their live video and toggle a product content overlay on top of the Viewer video player with a title, description, and shoppable link available via Stripe. Think of it as a QVC-like live stream: multiple presenters are able to connect and talk about the current product in a Zoom or Google Meet-like format, while viewers are able to purchase when the product link pops up. Viewers are also able to engage with the presenter through a live chat.
For this demo, we adapted the Mux Meet code repository, which is a video chat room similar to Zoom or Google Meet. From there, we created a presenter view that allows a user to create a viewable live stream by using Mux Spaces to broadcast camera-enabled web browsers. Viewers navigate to a link containing the Mux Space created by the presenter to tune in to the live stream and participate in a group chat.
From here, the presenter will stream their live video. They can toggle a product content overlay on top of the viewer video player with a title, description, and shoppable link available via Stripe. Think of it as a QVC-like live stream, with multiple presenters able to connect and talk about the current product while viewers are able to purchase when the product link pops up.
This demo is bootstrapped by Next.js with create-next-app.
Alright. Let's move on to the technical implementation.
npm install @mux/spaces-web @mux/mux-node @mux/mux-player-react -save
Clicking “Create Channel” will create a Mux Space and a Live Stream object. The user will be redirected to the presenter control screen, where they’ll be able to view their product and livestream toggles as well as a chat window or real-time conversation with prospective customers.
Creating a new Mux Space from this UI makes a request to our NextJS-provided API function with the provided topic and presenter name.
const createSpace = () =>
fetch("/api/spaces", {
headers: {
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({
topic: topicName,
host: participantName,
}),
})
.then((res) => res.json())
.then((data: Space) => {
router.push(`/p/${data.id}`);
})
);
With this, we'll have an endpoint to create a new Space.
import { NextApiRequest, NextApiResponse } from "next";
import { StatusCodes } from "http-status-codes";
import Mux from "@mux/mux-node";
const { Video } = new Mux();
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === "POST") {
const { topic } = req.body;
const liveStream = await Video.LiveStreams.create({
playback_policy: "public",
embedded_subtitles: [],
passthrough: host,
});
const space = await Video.Spaces.create({
type: "server",
passthrough: topic,
broadcasts: [
{
live_stream_id: liveStream.id || "",
},
],
});
res.status(StatusCodes.CREATED).json(space);
} else {
res.status(StatusCodes.METHOD_NOT_ALLOWED).end();
}
}
NextJS api endpoints export a default `handler` function that acts as the http route for incoming requests. In this example, we are checking if the request is a `POST` method; if not, we end the request.
Since we don't have existing streams or broadcasts, we're creating a new one every time a Space is made. This allows us to pass the live stream ID so the Space knows where to send its broadcasts when going live. We're also expecting `topic` from the incoming request so we can attach it to the Space via the `passthrough` key, which is an arbitrary title for the Space.
Here, the presenter can enable their video and audio and take note of the product control on the bottom left, as well as the “Start Live Stream” button. This button enables the previously created Space and Live Stream to start broadcasting, which essentially signals the video coming from the Space to send video data to the Live Stream via a Playback ID reference.
The viewer experience looks similar to the presenter view, but without the controls:
Whenever the presenter decides to start live streaming from their browser, we implement WebSockets, using Socket.io, to communicate events from our API server to all of the viewers, informing them when one of the following happens:
We initialize the socket on the server and register all of the events that can be sent to viewers.
const onConnection = (socket: Socket) => {
const { roomId } = socket.handshake.query;
socket.join(roomId as string);
socket.on(INCOMING_CHAT_MESSAGE_EVENT, (data) => {
io.in(roomId as string).emit(INCOMING_CHAT_MESSAGE_EVENT, data);
});
const toggleProductDisplay = (displayProduct: boolean) => {
socket.broadcast.emit(DISPLAY_PRODUCT, displayProduct);
};
socket.on(TOGGLE_PRODUCT, toggleProductDisplay);
socket.on("disconnect", () => {
socket.leave(roomId as string);
});
};
Those server-sent events have a corresponding receiver on the client side to inform the app what should be done with the information:
type SpaceData = {
status: "active" | "idle";
space_id: string;
live_stream_id: string;
layout: "gallery";
id: string;
};
export type WebhookObject = {
id: string;
type: string;
data: SpaceData;
};
export const enum MuxEventType {
SpaceBroadcastActive = "video.space.broadcast.active",
SpaceBroadcastIdle = "video.space.broadcast.idle",
LiveStreamActive = "video.live_stream.active",
LiveStreamIdle = "video.live_stream.idle",
}
export interface MuxWebhookConfig<P = any> {
action: (params: P, socket: Server) => Promise<void>;
}
export const MuxWebhookAction: Record<MuxEventType, MuxWebhookConfig> = {
[MuxEventType.SpaceBroadcastActive]: {
async action(_params: SpaceData, io: Server) {
io.emit(BROADCAST_START);
return Promise.resolve();
},
},
[MuxEventType.SpaceBroadcastIdle]: {
async action(_params: SpaceData, io: Server) {
io.emit(BROADCAST_STOP);
return Promise.resolve();
},
},
[MuxEventType.LiveStreamActive]: {
async action(params: SpaceData, io: Server) {
io.emit(LIVESTREAM_START);
return Promise.resolve();
},
},
[MuxEventType.LiveStreamIdle]: {
async action(params: SpaceData, io: Server) {
io.emit(LIVESTREAM_STOP);
return Promise.resolve();
},
},
};
The events that begin with “video.space.broadcast…” are built-in Mux events using the Spaces SDK. These helpers allow us to change UI states between the broadcast beginning and the live stream ending, so that we can give granular updates to the viewers, such as a loading spinner when the live streamer has begun the broadcast but the live stream has not yet reached the playback state.
When the presenter decides it’s time to start their live stream, the Space notifies all of the viewers that the broadcast is beginning:
const broadcast = space.broadcasts?.at(0);
if (!broadcast) {
return res.status(StatusCodes.NOT_FOUND).json({});
}
await Video.Spaces.Broadcasts.start(space.id, broadcast.id);
Finally, the viewer is informed the live stream will begin shortly. For simple playback, we use Mux Player with the live stream’s Playback ID.
<MuxPlayer
autoPlay={autoPlay || isLive}
streamType="live"
playbackId={data.playback?.id}
muted={isMuted}
volume={playerVolume}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
maxHeight: "580px",
}}
/>
Other socket events, such as when the presenter clicks the “Show Product” button, are displayed to both the presenter and the viewer:
Notice how the presenter controls are absent from the viewer’s side:
The product block that the presenter is pointing at contains an actual Stripe Checkout link connected to the presenter’s store. From there, the viewer can click the “Buy Now” link to go directly to the purchase page:
Finally, when the presenter decides to end the broadcast, the “Stop Live Stream” button is clicked, and the event is sent across all viewers’ browsers:
const initalizeSocket = useCallback(async () => {
socketRef.current = socketIOClient();
….
socketRef.current.on(BROADCAST_STOP, () => {
refetch();
setStatus("ended");
});
…
}, [refetch]);
Now that you’ve seen how easy it is to get up and running with live shopping, here are some resources to help you start creating your own experience:
If you need a hand with building your own live shopping experience, Mux partner Impekable can help you. Contact sales@impekable.com
1. https://fitsmallbusiness.com/livestream-shopping-statistics/↩
2. https://www.statista.com/statistics/1276120/livestream-e-commerce-sales-united-states/↩
3. https://emplifi.io/resources/blog/live-shopping-statistics↩
4. https://fitsmallbusiness.com/livestream-shopping-statistics/↩
5. https://emplifi.io/resources/blog/live-shopping-statistics↩
6. https://emplifi.io/resources/blog/live-shopping-statistics↩
No credit card to start. $20 in free credits when you're ready.
Mux is proud to partner with Contentful, the API-first content platform, to help you create, manage, transcode, publish, and stream video content on any digital channel. If you are new to the Mux Cont ...
By John and Blair
Why your brand needs videos outside of social platforms With the introduction of App Tracking Transparency in iOS 14.5, online brands now have a dramatically different relationship with social platfor ...
By John and Harris
As innovators in the streaming video industry, we’d like to see a robust video strategy that gives the new administration control not only of stories, but also of the distribution.
By Eric Elia