Skip to main content

Quickstart

Broadcast and view your first livestream

In this tutorial you install the Native Frame client and build two small apps: a broadcaster that streams your camera, and a viewer that watches it. By the end you will have a livestream playing in your own page. Pick React or vanilla JavaScript with the tabs in each step and follow the same three steps either way.

Prerequisites

Before you start, you need a Native Frame account and three values from it:

CredentialWhat it looks likeFrom
Authentication token (JWT)A signed token string.Generate an API token
Stream keyIdentifies the stream you publish to.Get your RTMP URL and stream key
Backend endpointhttps://your-subdomain.nativeframe.comCopy your backend endpoint

If you do not have these yet, follow Create an account and get credentials first, then come back here.

Want to see a stream working first?

You do not need any of this to watch a stream play. Create an account and get credentials opens with a no-code path: create a stream in the dashboard, publish to it from software like OBS, and play it back — no token, no install. Come here when you want to build it into your own app.

You paste those values in by hand below, which is fine for a local run. Before you ship anything, read Token setup: in production your backend mints a short-lived token per session instead.

Steps

Step 1: Install the client

Install the Native Frame client if you have not already — Installation covers npm, yarn, and the CDN, and is the one place install is taught:

npm install @video/video-client-react

Building the vanilla JavaScript version instead? Installation's CDN tab has the import map to use; there is nothing to install for that path.

Step 2: Build your first broadcaster

Create a broadcaster that streams your camera to viewers. In order, the code below authenticates with your token, initialises the camera and shows a local preview, configures the call with your stream key and backend endpoint, and wires up the start and stop controls.

Do not ship a token in client-side code

The token literals in the code below are for a local tutorial run only. Anything you put in front-end JavaScript is readable by anyone who loads the page. In production your backend mints a short-lived token per session — see Token setup.

import React, { useMemo } from 'react';
import {
usePreviewPlayer,
useAuthClient,
useCreateCall,
useBroadcast
} from '@video/video-client-react/hooks';
import {
MediaStreamControllerAPIProvider,
PlayerAPIProvider
} from '@video/video-client-react/context';
import { Video } from '@video/video-client-react/components';

function Broadcaster() {
// Set up camera and preview
const { mediaStreamController, previewPlayer } = usePreviewPlayer();

// Set up authentication. useAuthClient takes the token itself, and an
// optional refresh callback returning a fresh one — see Token setup.
const authClient = useAuthClient('your-broadcaster-token');

// MEMOISE THESE. useCreateCall and useBroadcast pin their inputs at first
// render on purpose: if you pass a fresh object literal every render, later
// changes to it are SILENTLY IGNORED rather than recreating the call. A new
// object each render also re-triggers the pin warning in development.
const callOptions = useMemo(() => ({
streamKey: 'your-stream-key',
backendEndpoints: ['https://your-subdomain.nativeframe.com'],
auth: authClient,
user: { userId: 'user-1', displayName: 'Broadcaster' }
}), [authClient]);

const broadcastOptions = useMemo(() => ({ streamName: 'default' }), []);

// Create the call, then broadcast the camera into it. Passing null while auth
// is still resolving is the supported idle path — the hook stays idle and does
// not warn.
const { call, status, error } = useCreateCall(authClient == null ? null : callOptions);
useBroadcast(call, mediaStreamController, broadcastOptions);

if (status === 'error') {
return <div>Could not start the call: {String(error)}</div>;
}

return (
<MediaStreamControllerAPIProvider mediaStreamController={mediaStreamController}>
<PlayerAPIProvider player={previewPlayer}>
<div>
<h1>My First Livestream</h1>
<Video /> {/* Shows your camera preview */}
{/* Give this to your viewer. It is null until the call connects. */}
{call != null && <p>Call ID: <code>{call.id}</code></p>}
</div>
</PlayerAPIProvider>
</MediaStreamControllerAPIProvider>
);
}

export default Broadcaster;
What happened to useCallControls?

Earlier versions of this guide imported a useCallControls hook from the SDK. The SDK does not export one — it is a helper defined inside the demo app, so that import could never resolve. Start and stop are driven by useCreateCall and useBroadcast instead, as above. There are no ready-made buttons: build your own from the lifecycle functions those two hooks return — start and dispose from useCreateCall, start and stop from useBroadcast.

status is one of idle, connecting, connected, error, or disposed. Note that it reports call setup only — if a call fails after it has connected, status stays connected. To react to mid-session trouble, listen on the call itself with call.on('error', …) and read call.state.

You now have a broadcaster with a camera preview and the call ID to hand to your viewer in Step 3. Unlike the vanilla tab below, this example renders no start/stop buttons — see the note above for the functions to wire them to.

Step 3: Build your first viewer

Create a viewer that plays the broadcast. The viewer connects by call ID — the ID of the call your broadcaster created in Step 2 — so get that value first. Both tabs take the same one.

Verify the loop end to end: run your broadcaster, take the call ID it gives you (the React version renders it on the page, the vanilla version logs it to the console), and pass it to the viewer as callId. When the viewer shows your camera, the broadcast-to-playback loop works.

import React, { useState } from 'react';
import { useAuthClient } from '@video/video-client-react/hooks';
import { PlayerAPIProvider } from '@video/video-client-react/context';
import { Video } from '@video/video-client-react/components';
import { createPlayer } from '@video/video-client-react';

function Viewer({ callId }) {
const [player, setPlayer] = useState(null);

const authClient = useAuthClient('your-viewer-token');

React.useEffect(() => {
let created;
let cancelled = false;

createPlayer({
callId: callId,
streamName: 'default',
backendEndpoints: ['https://your-subdomain.nativeframe.com'],
auth: authClient
}).then((next) => {
created = next;
// If the component unmounted while createPlayer was still resolving,
// dispose immediately rather than storing a player nobody will clean up.
if (cancelled) next.dispose();
else setPlayer(next);
});

return () => {
cancelled = true;
created?.dispose();
};
}, [callId, authClient]);

if (!player) return <div>Loading stream...</div>;

return (
<PlayerAPIProvider player={player}>
<div>
<h1>Watching Livestream</h1>
<Video />
</div>
</PlayerAPIProvider>
);
}

export default Viewer;

// Render it with the call ID your broadcaster printed on the page in Step 2.
// Hard-coding it is fine while you are following this guide; in a real app the
// broadcaster would hand this to the viewer over your own backend.
//
// <Viewer callId="call-id-from-broadcaster" />

Open the broadcaster and click Start Broadcasting, then load the viewer with that callId. Your camera should appear in the viewer's player.

Next steps

Now that the basics work, go deeper: