Skip to main content

Direct Streaming: Viewer

Introduction

Direct Streaming facilitates personalized, real-time video interactions between a broadcaster and select viewers. This guide provides a comprehensive walkthrough for integrating the viewer component of Direct Streaming into your application using Native Frame's SDK.

Key Concepts

  • Direct Streaming: A private broadcast where selected viewers can join and broadcast themselves simultaneously.
  • Encoder: Handles video capture and broadcasting for the viewer.
  • Manifest Player: Displays the broadcaster's stream to the viewer.
  • VideoClient: Manages the connection and streaming process.

Prerequisites

Before you begin, ensure you have the following:

  • Native Frame SDK installed in your project
  • Basic understanding of React and React Hooks
  • Familiarity with TypeScript (optional but recommended)
  • A valid callId from an active Direct Streaming broadcast

Implementation Steps

Step 1: Set Up the VideoClient

First, create a custom hook to manage the VideoClient instance:

import { useEffect, useState } from 'react';
import { types, VideoClient, CallState } from "@video/video-client-web";

export function useVideoClient() {
const [vc, setVc] = useState<types.VideoClient | null>(null);
const [callState, setCallState] = useState<CallState | null>(null);

useEffect(() => {
if (!vc) {
const vcViewerOptions: types.VideoClientOptions = {
backendEndpoints: [backendEndpoint],
token: tokenRefresher({
backendEndpoint,
authUrl: `${backendEndpoint}/api/demo/v1/access-token`,
streamKey,
scope: "private-viewer",
clientReferrer,
}),
};

const newVc = new VideoClient(vcViewerOptions);
setVc(newVc);
setCallState(new CallState());
}

return () => {
if (vc) {
vc.dispose();
setVc(null);
}
};
}, [backendEndpoint, streamKey, clientReferrer]);

return { vc, callState };
}

Step 2: Create the Encoder UI State

Set up the EncoderUiState for the viewer's broadcast:

import { EncoderUiState, mediaController } from "@video/video-client-web";

export function useEncoderUi() {
const [encoderUi, setEncoderUi] = useState<EncoderUiState | null>(null);

useEffect(() => {
if (!encoderUi) {
(async () => {
await mediaController.init();
const mediaStreamController = await mediaController.requestController();
setEncoderUi(new EncoderUiState(mediaStreamController));
})();
}
return () => {
if (encoderUi) {
encoderUi.dispose();
setEncoderUi(null);
}
};
}, [encoderUi]);

return encoderUi;
}

Step 3: Create the Encoder Component

Implement the Encoder component for the viewer's broadcast:

import React from 'react';
import {
CameraButton,
ControlBar,
EncoderVideo,
JoinBroadcastButton,
MediaContainer,
MicrophoneButton,
VideoClientContext,
EncoderUiContext,
CallContext,
} from "@video/video-client-web";

const Encoder: React.FC<{
encoderUi: EncoderUiState;
videoclient: types.VideoClient | null;
callId: string;
callState: CallState | null;
}> = ({ encoderUi, videoclient, callId, callState }) => {
return (
<VideoClientContext.Provider value={videoclient}>
<CallContext.Provider value={callState}>
<EncoderUiContext.Provider value={encoderUi}>
<MediaContainer>
<EncoderVideo />
<ControlBar variant="encoder">
<JoinBroadcastButton
callId={callId}
broadcastOptions={{
streamName: "demo",
}}
/>
<CameraButton />
<MicrophoneButton />
</ControlBar>
</MediaContainer>
</EncoderUiContext.Provider>
</CallContext.Provider>
</VideoClientContext.Provider>
);
};

export default Encoder;

Step 4: Create the Custom Manifest Player Component

Implement the CustomManifest component to display the broadcaster's stream:

import React, { useEffect, useState } from 'react';
import {
ControlBar,
PlayerUiContext,
MediaContainer,
PlayerAudioButton,
PlayerGetSoundButton,
PlayerPlayButton,
PlayerUiState,
PlayerVideo,
PlayerVolumeRange,
types,
} from "@video/video-client-web";

const CustomManifest: React.FC<{
vc: types.VideoClient | null,
manifestUrl: string
}> = ({ vc, manifestUrl }) => {
const [playerUi, setPlayerUi] = useState<PlayerUiState | null>(null);

useEffect(() => {
if (manifestUrl && !playerUi && vc) {
const player = vc.requestPlayer(manifestUrl);
setPlayerUi(new PlayerUiState(player));
}

return () => {
if (playerUi) {
playerUi.dispose();
setPlayerUi(null);
}
};
}, [manifestUrl, playerUi, vc]);

if (!playerUi) return null;

return (
<PlayerUiContext.Provider value={playerUi}>
<MediaContainer>
<PlayerGetSoundButton />
<PlayerVideo />
<ControlBar variant="player">
<PlayerVolumeRange />
<PlayerAudioButton />
<PlayerPlayButton />
</ControlBar>
</MediaContainer>
</PlayerUiContext.Provider>
);
};

export default CustomManifest;

Step 5: Implement the Broadcast Viewer Component

Create a BroadcastViewer component that utilizes the hooks we've created:

import React from 'react';
import { PlayerUiState } from "@video/video-client-web";
import Encoder from './Encoder';
import CustomManifest from './CustomManifest';

const BroadcastViewer: React.FC<{ callId: string, manifestUrl: string }> = ({ callId, manifestUrl }) => {
const { vc, callState } = useVideoClient();
const encoderUi = useEncoderUi();

return (
<>
{encoderUi && (
<Encoder
callId={callId}
encoderUi={encoderUi}
videoclient={vc}
callState={callState}
/>
)}
<CustomManifest vc={vc} manifestUrl={manifestUrl} />
</>
);
};

export default BroadcastViewer;

Usage

To use the Direct Streaming Viewer in your application:

  1. Import the BroadcastViewer component into your main application file.
  2. Render the BroadcastViewer component where you want the viewing interface to appear, providing the necessary callId and manifestUrl.
import React from 'react';
import BroadcastViewer from './BroadcastViewer';

const App: React.FC = () => {
const callId = "your-call-id";
const manifestUrl = "your-manifest-url";

return (
<div className="App">
<h1>Direct Streaming Viewer</h1>
<BroadcastViewer callId={callId} manifestUrl={manifestUrl} />
</div>
);
};

export default App;