Skip to main content
Version: 1.2.x

Customized Live Stream - Flutter

VideoSDK is a platform that offers a range of video streaming tools and solutions for content creators, publishers, and developers.

Custom Template

  • Custom template is template for live stream, which allows users to add real-time graphics to their streams.
  • With custom templates, users can create unique and engaging video experiences by overlaying graphics, text, images, and animations onto their live streams. These graphics can be customized to match the branding.
  • Custom templates enable users to create engaging video content with real-time graphics, with live scoreboards, social media feeds, and other customizations, users can easily create unique and visually appealing streams that stands out from the crowd.
note

Custom templates can be used with recordings and RTMP service provided by VideoSDK as well.

What you can do with Custom Template

Using a custom template, you may create a variety of various modes. Here are a few of the more well-known modes that you can create.

  • PK Host: Host can organise player vs player battle. Below image is example of gaming battle.

  • Watermark: Host can add & update watermark anywhere in the template. In below image we have added VideoSDK watermark on top right side of the screen.

  • News Mode: Host can add dynamic text in lower third banner. in below image we have added some sample text in bottom left of the screen.

Mobile Custom Template

Custom template with VideoSDK

In this section, we will discuss how Custom Templates work with VideoSDK.

  • Host: The host is responsible for starting the live streaming by passing the templateURL. The templateURL is the URL of the hosted template webpage. The host is also responsible for managing the template, such as changing text, logos, and switching template layout, among other things.

  • VideoSDK Template Engine : The VideoSDK Template Engine accepts and opens the templateURL in the browser. It listens to all the events performed by the Host and enables customization of the template according to the Host's preferences.

  • Viewer: The viewer can stream the content. They can watch the live stream with the added real-time graphics, which makes for a unique and engaging viewing experience.

custom template

Understanding Template URL

The template URL is the webpage that VideoSDK Template Engine will open while composing the live stream.

The template URL will appear as shown below.

template url

The Template URL consists of two parts:

  • Your actual page URL, which will look something like https://example.com/videosdk-template.
  • Query parameters, which will allow the VideoSDK Template Engine to join the meeting when the URL is opened. There are a total of three query parameters:
    • token: This will be your token, which will be used to join the meeting.
    • meetingId: This will be the meeting ID that will be joined by the VideoSDK Template Engine.
    • participantId: This will be the participant ID of the VideoSDK Template Engine, which should be passed while joining the template engine in your template so that the tempalte engine participant is not visible to other participants. This parameter will be added by the VideoSDK.
info

Above mentioned query parameters are mandatory. Apart from these parameters, you can pass any other extra parameters which are required according to your use-case.

Creating Template

Step 1: Create a new React App using the below command

npx create-react-app videosdk-custom-template
note

You can use VideoSDK's React or JavaScript SDK to create custom template. Following is the example of building custom template with React SDK.

Step 2: Install the VideoSDK using the below-mentioned npm command.

Make sure you are in your react app directory before you run this command.

npm install "@videosdk.live/react-sdk"

//For the Participants Video
npm install "react-player"
App Architecture

template architechture

Structure of the Project
Project Structure
   root
├── node_modules
├── public
├── src
│ ├── components
│ ├── MeetingContainer.js
│ ├── ParticipantsAudioPlayer.js
│ ├── ParticipantsView.js
│ ├── Notification.js
│ ├── icons
│ ├── App.js
│ ├── index.js
├── package.json
.

Step 3: Next we will fetch the query parameters, from the URL which we will later use to initialize the meeting

function App() {
const { meetingId, token, participantId } = useMemo(() => {
const location = window.location;

const urlParams = new URLSearchParams(location.search);

const paramKeys = {
meetingId: "meetingId",
token: "token",
participantId: "participantId",
};

Object.keys(paramKeys).forEach((key) => {
paramKeys[key] = urlParams.get(key)
? decodeURIComponent(urlParams.get(key))
: null;
});

return paramKeys;
}, []);
}

Step 4: Now we will initialize the meeting with the parameters we extracted from the URL. Make sure joinWithoutUserInteraction is specified, so that the template engine is able to join directly into the meeting, on the page load.

function App(){
...

return meetingId && token && participantId ? (
<div>
<MeetingProvider
config={{
meetingId,
micEnabled: false,
webcamEnabled: false,
name: "recorder",
participantId,
}}
token={token}
joinWithoutUserInteraction
>
{/* We will create this in upcoming steps */}
<MeetingContainer />
</MeetingProvider>
</div>
) : null;

}

Step 5: Let us create the MeetingContainer which will render the meeting view for us.

  • It will also listen to the PubSub messages from the CHANGE_BACKGROUND topic, which will change the background color of the meeting.
  • It will have Notification component which will show any messages share by Host
note

We will be using the PubSub mechanism to communicate with the template. You can learn more about PubSub from here.

import { Constants, useMeeting, usePubSub } from "@videosdk.live/react-sdk";
import { Notification } from "./Notification";
import { ParticipantsAudioPlayer } from "./ParticipantsAudioPlayer";
import { ParticipantView } from "./ParticipantView";

export const MeetingContainer = () => {
const { isMeetingJoined, participants, localParticipant } = useMeeting();
const { messages } = usePubSub("CHANGE_BACKGROUND");

const remoteSpeakers = [...participants.values()].filter((participant) => {
return participant.mode == Constants.modes.CONFERENCE && !participant.local;
});

return isMeetingJoined ? (
<div
style={{
display: "flex",
flexDirection: "column",
height: "100vh",
backgroundColor:
messages.length > 0
? messages.at(messages.length - 1).message
: "#fff",
}}
>
<ParticipantsAudioPlayer />
<div
style={{
display: "grid",
gridTemplateColumns: remoteSpeakers?.length > 1 ? "1fr 1fr" : "1fr",
flex: 1,
maxHeight: `100vh`,
overflowY: "auto",
gap: "20px",
padding: "20px",
alignItems: "center",
justifyItems: "center",
}}
>
{[...remoteSpeakers].map((participant) => {
return (
<ParticipantView
key={participant.id}
participantId={participant.id}
/>
);
})}
</div>
<Notification />
</div>
) : (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100vh",
}}
>
<div class="loader"></div>
</div>
);
};

Step 6: Let us create the ParticipantView and ParticipantsAudioPlayer which will render the video and audio of the participants respectively.

import { useParticipant } from "@videosdk.live/react-sdk";
import { useMemo } from "react";
import ReactPlayer from "react-player";
import MicOffIcon from "../icons/MicOffIcon";

export const ParticipantView = (props) => {
const { webcamStream, webcamOn, displayName, micOn } = useParticipant(
props.participantId
);

const videoStream = useMemo(() => {
if (webcamOn && webcamStream) {
const mediaStream = new MediaStream();
mediaStream.addTrack(webcamStream.track);
return mediaStream;
}
}, [webcamStream, webcamOn]);

return (
<div
style={{
width: "100%",
height: "400px",
maxWidth: "600px",
display: "flex",
flex: 1,
alignItems: "center",
justifyContent: "center",
position: "relative",
backgroundColor: "#1A1C22",
borderRadius: "10px",
overflow: "hidden",
}}
class="video-cover"
>
{webcamOn && webcamStream ? (
<ReactPlayer
//
playsinline // extremely crucial prop
pip={false}
light={false}
controls={false}
muted={true}
playing={true}
//
url={videoStream}
//
height={"100%"}
width={"100%"}
onError={(err) => {
console.log(err, "participant video error");
}}
/>
) : (
<div
style={{
fontSize: "50px",
color: "#fff",
}}
>
{String(displayName).charAt(0).toUpperCase()}
</div>
)}
<div
style={{
position: "absolute",
left: "10px",
bottom: "10px",
backgroundColor: "#050A0E",
color: "#fff",
padding: "4px",
borderRadius: "4px",
alignItems: "center",
justifyItems: "center",
display: "flex",
}}
>
{displayName}{" "}
{!micOn && <MicOffIcon fillcolor="#fff" height="18" width="18" />}
</div>
</div>
);
};
import { useMeeting, useParticipant } from "@videosdk.live/react-sdk";
import { useEffect, useRef } from "react";

const ParticipantAudio = ({ participantId }) => {
const { micOn, micStream, isLocal } = useParticipant(participantId);
const audioPlayer = useRef();

useEffect(() => {
if (!isLocal && audioPlayer.current && micOn && micStream) {
const mediaStream = new MediaStream();
mediaStream.addTrack(micStream.track);

audioPlayer.current.srcObject = mediaStream;
audioPlayer.current.play().catch((err) => {});
} else {
audioPlayer.current.srcObject = null;
}
}, [micStream, micOn, isLocal, participantId]);

return <audio autoPlay playsInline controls={false} ref={audioPlayer} />;
};

export const ParticipantsAudioPlayer = () => {
const mMeeting = useMeeting();

const participants = mMeeting?.participants;

return participants ? (
[...participants.keys()].map((participantId) => (
<ParticipantAudio
key={`participant_audio_${participantId}`}
participantId={participantId}
/>
))
) : (
<></>
);
};

Step 7: Let us create the Notification component which will listen to PubSub messages for the topic VIEWER_MESSAGE and show a notification on the lower left corner.

import { usePubSub } from "@videosdk.live/react-sdk";
import { useEffect, useRef, useState } from "react";

export const Notification = () => {
const timeoutRef = useRef(null);
const handleChatMessage = (msg) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
setMessage(msg);
timeoutRef.current = setTimeout(() => {
setMessage(null);
}, 3200);
};
const [message, setMessage] = useState(null);
const { publish } = usePubSub("VIEWER_MESSAGE", {
onMessageReceived: handleChatMessage,
});
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return message ? (
<div
style={{
backgroundColor: "#232830",
padding: "10px",
textAlign: "center",
color: "#fff",
position: "absolute",
bottom: "50px",
left: "30px",
borderRadius: "10px",
animation: "fadein 0.5s",
}}
>
<strong>
{message.senderName} says {message.message}
</strong>
</div>
) : null;
};
note

For the CSS stylings and animations used in the project, please refer to this file.

Step 9: Once tested, you can deploy it using your backend or host it on services like firebase or vercel.

tip

You can checkout the github repository for sample custom template we just created here.

Setup Livestream Using Custom Template

  • To use Custom Template, you will need Host and Viewer setup from where the host will start the livestream and viewer can watch them.

Flutter Custom Template Example

  • You can clone this example and run it on your system.
git clone https://github.com/videosdk-live/quickstart.git
  • The cloned example will have a directory flutter-custom-template-manager which has to runned.
cd flutter-custom-template-manager
flutter pub get
flutter run
  • To use the custom template which we just deployed, we will call the Start HLS API instead of the startHls method from the Room class. This code has already been added in the example you cloned.
void startHls() async {
//Update your Custom Template URL here if you have deployed your own, if not deployed you can use this template from our URL.
String templateUrl =
'https://lab.videosdk.live/react-custom-template-demo?meetingId=${widget.room.id}&token=${token}';
final body = utf8.encode(
json.encode(
<String, dynamic>{
"roomId": widget.room.id,
"templateUrl": templateUrl,
"config":{
"orientation":"portrait",
},
},
),
);
final http.Response httpResponse = await http.post(
Uri.parse('https://api.videosdk.live/v2/hls/start'),
headers: {
'Authorization': token,
'Content-Type': "application/json",
},
body: body,
);
log("response ${json.decode(httpResponse.body)}"); // result will have playbackHlsUrl and livestreamUrl
}

ElevatedButton(
onPressed: () => startHls(),
child: Text("Start HLS")
)
  • This example has two option on the Host side which will allow them to change the background and send the messages to the viewer.

  • Let's give this example a test run.

API Reference

The API references for all the methods utilized in this guide are provided below.

Got a Question? Ask us on discord