Build multi-modal AI applications using our new open-source Vision AI SDK.

React Native Livestreaming Tutorial

Build a low-latency livestreaming app for iOS and Android with Stream's React Native Video SDK. Watch an RTMP broadcast, build a custom player, then go live from the phone itself over WebRTC.

Prefer to skip the setup? Add the Stream skill and let your AI agent build your React Native livestream.

Livestream Quickstart

This tutorial builds a low-latency in-app livestreaming experience, broadcast over Stream's global edge network.

It comes in two parts:

Part 1 - Watch a livestream in a React Native app

  • Create a livestream in the Stream dashboard
  • Push video to it over RTMP with OBS
  • Play it in a React Native app with LivestreamPlayer
  • Replace that with a custom player you control

Part 2 - Broadcast from the phone

  • Publish a livestream from a mobile device over WebRTC
  • Use backstage and go-live to control when viewers can watch

This page works for both humans and AI coding agents: every step is a file operation or a shell command, every step has a verification checkpoint, and the few steps that need a human are explicitly marked.

Before you start. Part 1 needs OBS, a free desktop broadcaster, and runs fine in an emulator since the phone is only watching. Part 2 publishes camera video, so it needs a physical device. Want to skip OBS? Do Steps 3.1 and 3.2 to create the project and install the SDK, then jump straight to Step 4 and broadcast from the phone.

Choose your path

This tutorial has two paths. In Path A you let your AI agent build the app for you. In Path B you build it yourself, step by step. Both paths end with the same working livestream, so pick the one you prefer instead of working through both.

Install the Stream CLI once, then add the skills. This gives Claude Code, Cursor, or Codex the React Native integration patterns and current SDK APIs, so it builds against real docs instead of stale training data.

Terminal (bash)
1
2
3
4
5
6
7
# Install the getstream CLI curl -fsSL https://getstream.io/cli.sh | bash # Install the skills (router, docs, and builder). The React Native pack # installs on demand the first time it's needed, or add it explicitly: getstream skills getstream skills stream-react-native

Then ask your agent:

Prompt (markdown)
1
2
/stream-react-native Build a React Native livestreaming app that plays a livestream and can also broadcast from the device camera with a go-live button.

Where you come in. The agent handles credentials and code. It stops three times: getstream init opening your browser to sign in and pick an app (new accounts get an organization in that flow), the build to a device, and the camera and microphone prompt.

Want to get the sign-in out of the way first? Run getstream init before you prompt the agent - it picks up the initialized project from there.

Human checkpoint: build the app to a device if the agent hasn't, then allow camera and microphone access. Tapping Go Live should start a stream you can watch from a browser.

Skim Important Building Blocks if you want to understand what the agent wrote before you extend it.

Path B - Build it manually

Choose this path if you'd rather write the code yourself, or you want to understand what the SDK is doing before you hand it to an agent. Follow the steps below.

For AI assistants reading this page: append .md to any Stream docs URL for a clean Markdown version. A condensed index for the React Native Video SDK is at https://getstream.io/video/docs/react-native/llms.txt. Prefer these over parsing HTML.

Important Building Blocks

Four pieces, one mental model:

  • StreamVideoClient - the low-level client. Holds the API key, the user, the token and the websocket connection to the Stream Video service.
  • Call - one livestream, created with client.call("livestream", id). The same Call object powers video calling and audio rooms; the call type is what changes the defaults.
  • LivestreamPlayer - the drop-in viewer. Give it a call type and call id and it plays the stream.
  • Call state hooks - useCallStateHooks() hands you the hooks (useParticipantCount, useIsCallLive, useLocalParticipant, and the rest) that drive a custom UI.

Backstage controls when viewers get in. A livestream call starts backstage so you can set up your camera before an audience arrives; call.goLive() opens it. Streams created from the dashboard go live immediately by default, and you can change either behavior per call type in the dashboard.

Step 1 - Create a livestream in the dashboard

First, let's create our livestream using the dashboard. To do this, open the dashboard and select "Video & Audio" -> "Overview".

In that screen, you will see three buttons that allow you to create different types of calls, as shown in the image below.

Screenshot shows dashboard overview

Click on the third one, the "Create Livestream" option. After you do this, you will be shown the following screen, which contains information about the livestream:

Screenshot shows dashboard livestream credentials

You will need the RTMP URL and RTMP Stream Key from this page, which are needed to setup the livestream in OBS software.

Copy these values for now, and we will get back to the dashboard a bit later.

Step 2 - Setup the livestream in OBS

OBS is one of the most popular livestreaming software packages and we'll use it to explain how to publish video with RTMP.

After you download and install the software using the instructions provided on the link, you should setup the capturing device and the livestream data.

First, let's setup the capturing device, which can be found in the sources section:

Screenshot shows capturing device setup

Select the "Video Capture Device" option to stream from your computer's camera. Alternatively, you can choose other options, such as "macOS Screen Capture," to stream your screen.

Next, we need to provide the livestream credentials from our dashboard to OBS. To do this, click on the "Settings" button located in the "Controls" section at the bottom right corner of OBS.

Screenshot shows OBS settings

This will open a popup. Select the second option, "Stream". For the "Service" option, choose "Custom". In the "Server" and "Stream Key" fields, enter the values you copied from the dashboard in Step 1.

Screenshot shows OBS settings setup

With that, our livestream setup is complete. Before returning to the dashboard, press the "Start Streaming" button in the "Controls" section.

Now, let's go back to the dashboard. If everything is set up correctly, you should see the OBS livestream in the dashboard, as shown in this screenshot:

Screenshot shows livestream that is live

By default the dashboard starts the livestream immediately. To change that, enable or disable backstage for the livestream call type.

Checkpoint: the dashboard shows your OBS video playing under the livestream.

Step 3 - Show the livestream in a React Native app

The livestream is running. Now build a React Native app to watch it.

Step 3.1 - Create a new React Native app

First, set up your React Native environment. Then generate a project called "LivestreamExample" with the React Native Community CLI:

Terminal (bash)
1
2
npx @react-native-community/cli@latest init LivestreamExample cd LivestreamExample

If you are having trouble with iOS, try to reinstall the dependencies by running:

  1. cd ios to navigate to the ios folder
  2. bundle install to install Bundler
  3. bundle exec pod install to install the iOS dependencies managed by CocoaPods

Checkpoint: you have a LivestreamExample directory and yarn ios or yarn android builds the React Native starter screen.

Step 3.2 - Install the SDK and its dependencies

To install the Stream Video React Native SDK, run the following command in your terminal of choice:

Terminal (bash)
1
yarn add @stream-io/video-react-native-sdk @stream-io/react-native-webrtc

The SDK requires installing some peer dependencies. You can run the following command to install them:

Terminal (bash)
1
2
3
4
5
6
yarn add react-native-svg yarn add @react-native-community/netinfo yarn add react-native-safe-area-context # Install pods for iOS npx pod-install

Android specific: update buildscript with required SDK versions

In android/build.gradle add the following inside the buildscript section:

java
1
2
3
4
5
6
7
buildscript { ext { ... minSdkVersion = 24 } ... }

Step 3.3 - View a livestream on a React Native app

Now build the viewer that plays the stream you started above.

Open App.tsx and replace its contents with this code:

App.tsx (tsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { SafeAreaProvider } from "react-native-safe-area-context"; import { LivestreamPlayer, StreamVideo, StreamVideoClient, User, } from "@stream-io/video-react-native-sdk"; const apiKey = "REPLACE_WITH_API_KEY"; const callId = "REPLACE_WITH_CALL_ID"; // Anonymous viewers need no token - watching a public livestream requires no // identity. Anonymous users don't open a websocket, so they receive no events; // they can watch and join, which is all a viewer needs. const user: User = { type: "anonymous" }; const client = new StreamVideoClient({ apiKey, user }); export default function App() { return ( <SafeAreaProvider> <StreamVideo client={client}> <LivestreamPlayer callType="livestream" callId={callId} /> </StreamVideo> </SafeAreaProvider> ); }

Before running the app, replace the placeholders with values from the dashboard:

  • apiKey - the API Key on your livestream page
  • token - the Viewer Token
  • callId - the Livestream ID

Screenshot shows livestream call data

Why there is no token here. For anonymous users the token is optional, so a public viewer needs nothing but your API key and the call id. Don't pass the dashboard's Viewer Token to an anonymous user: that token is minted for a named user, and anonymous auth requires the user_id claim to be the literal !anon. Pairing the two fails at connect time with "anon auth token must have user_id claim equal to !anon".

If you do want to restrict which livestreams an anonymous viewer can watch, mint a call-scoped anonymous token with one of the server-side SDKs and pass it as token. It must carry call_cids:

json
1
2
3
4
5
6
7
{ "user_id": "!anon", "role": "viewer", "call_cids": ["livestream:<your-call-id>"], "iat": 1726406693, "exp": 1726493093 }

Neither the dashboard's Viewer Token nor getstream token produces this shape - getstream token '!anon' would get the user_id right but omits call_cids, which the API requires. See Client & Authentication for guest and authenticated viewers.

That's everything. LivestreamPlayer plays a livestream given a call id and call type; there's nothing else to wire up.

Prefer the CLI? curl -fsSL https://getstream.io/cli.sh | bash, then getstream init to sign in and pick your app, and getstream token <user-id> to mint a token against it. The dashboard values above are the quickest path for this step, since the livestream you created there already has them on screen.

Run the app:

bash
1
2
3
4
5
# run iOS app yarn ios # run Android app yarn android

An emulator is fine here, since the phone is only watching. Part 2 publishes camera video and does need a physical device.

Checkpoint: the app plays the video you're broadcasting from OBS.

The LivestreamPlayer docs cover its options.

Step 3.4 - Customizing the UI

LivestreamPlayer gets you a viewer in one line. When you need a different look, build your own UI on top of the same SDK components and state layer.

State & Participants

For anything beyond the default - filtering participants, sorting them your own way - read the call state via call.state or the Call State Hooks.

Filtering is the common case. To get every participant with the host role:

tsx
1
2
3
4
5
import { useCallStateHooks } from "@stream-io/video-react-native-sdk"; const { useParticipants } = useCallStateHooks(); const participants = useParticipants(); const hosts = participants.filter((p) => p.roles.includes("host"));

The participant state docs list every available field.

For sorting, combine the comparators the SDK ships with, or write your own. A livestream-shaped example:

tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { combineComparators, role, dominantSpeaker, speaking, publishingAudio, publishingVideo, } from "@stream-io/video-react-native-sdk"; const livestreamComparator = combineComparators( role("host", "speaker"), dominantSpeaker, speaking, publishingVideo, publishingAudio, );

That ordering puts hosts first, then whoever is speaking, then anyone publishing video and audio.

Apply it like this:

tsx
1
2
3
4
5
6
7
import { useCallStateHooks } from "@stream-io/video-react-native-sdk"; const { useParticipants } = useCallStateHooks(); const sortedParticipants = useParticipants({ sortBy: livestreamComparator }); // alternatively, you can apply the comparator on the whole call: call.setSortParticipantsBy(livestreamComparator);

The participant sorting docs go deeper.

Now build a custom player that shows the livestream plus a live viewer count. Create CustomLivestreamPlayer.tsx:

CustomLivestreamPlayer.tsx (tsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import React, { useEffect, useState } from "react"; import { View, Text, StyleSheet } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; import { Call, combineComparators, publishingVideo, role, StreamCall, callManager, useCallStateHooks, useStreamVideoClient, VideoRenderer, } from "@stream-io/video-react-native-sdk"; // Prioritize the host (and anyone publishing video) so the broadcaster's // stream is the participant we render, reusing the comparators shown above. const livestreamComparator = combineComparators( role("host", "speaker"), publishingVideo, ); export const CustomLivestreamPlayer = (props: { callType: string; callId: string; }) => { const { callType, callId } = props; const client = useStreamVideoClient(); const [call, setCall] = useState<Call>(); useEffect(() => { if (!client) return; const myCall = client.call(callType, callId); setCall(myCall); myCall.join().catch((e) => { console.error("Failed to join call", e); }); return () => { myCall.leave().catch((e) => { console.error("Failed to leave call", e); }); setCall(undefined); }; }, [callId, callType, client]); if (!call) return null; return ( <StreamCall call={call}> <CustomLivestreamLayout /> </StreamCall> ); }; const CustomLivestreamLayout = () => { const { useParticipants, useParticipantCount } = useCallStateHooks(); const participantCount = useParticipantCount(); const [firstParticipant] = useParticipants({ sortBy: livestreamComparator }); useEffect(() => { // Automatically route audio to speaker devices as relevant for watching videos callManager.start({ audioRole: "listener" }); return () => callManager.stop(); }, []); return ( <SafeAreaView style={styles.flexed}> <Text style={styles.text}>Live: {participantCount}</Text> <View style={styles.flexed}> {firstParticipant ? ( <VideoRenderer participant={firstParticipant} /> ) : ( <Text style={styles.text}>The host hasn't joined yet</Text> )} </View> </SafeAreaView> ); }; const styles = StyleSheet.create({ flexed: { flex: 1, backgroundColor: "white", }, text: { alignSelf: "center", color: "white", backgroundColor: "blue", padding: 6, margin: 4, }, });

The comparator picks the host out of the call state and VideoRenderer draws their stream, or a placeholder if nobody is publishing. useParticipantCount() drives the viewer label. The useEffect joins on mount and leaves on unmount - in a real app you'd hang those off explicit buttons.

Swap LivestreamPlayer for CustomLivestreamPlayer in App:

App.tsx (tsx)
1
2
3
4
5
6
7
8
9
10
11
12
// ... the rest of the code import { CustomLivestreamPlayer } from "./CustomLivestreamPlayer"; export default function App() { return ( <SafeAreaProvider> <StreamVideo client={client}> <CustomLivestreamPlayer callType="livestream" callId={callId} /> </StreamVideo> </SafeAreaProvider> ); }

Checkpoint: the custom player shows the OBS video with a Live: <n> label above it.

Part 2 - Build your own Youtube Live

Part 1 published over RTMP and authenticated through the dashboard. In a real application you'd mint tokens programmatically with a server-side SDK.

Part 2 drops OBS entirely and broadcasts from the phone.

Step 4 - Live streaming from a React Native app

Now send video straight from the app over WebRTC, with backstage controlling when viewers get in. This part publishes camera video, so it needs a real Android or iOS device.

Step 4.1 - Permissions setup

Publishing needs camera and microphone access, which starts with declaring the permissions in your app.

Android
iOS

In AndroidManifest.xml add the following permissions before the application section.

xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <uses-feature android:name="android.hardware.audio.output" /> <uses-feature android:name="android.hardware.microphone" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.INTERNET" /> ... <application ...> ... </application> </manifest>

Step 4.2 - Broadcasting a livestream

Replace the viewer code in App.tsx with the following (or start a fresh project using the same setup steps). This step gets the broadcast connected; Step 4.3 renders the video.

App.tsx (tsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import React from "react"; import { StreamVideoClient, StreamVideo, User, StreamCall, } from "@stream-io/video-react-native-sdk"; import { Text, StyleSheet } from "react-native"; import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; const apiKey = "REPLACE_WITH_API_KEY"; const token = "REPLACE_WITH_TOKEN"; const userId = "REPLACE_WITH_USER_ID"; const callId = "REPLACE_WITH_CALL_ID"; const user: User = { id: userId, name: "Tutorial" }; const client = new StreamVideoClient({ apiKey, user, token }); const call = client.call("livestream", callId); // The `livestream` call type ships with the mic off by default, so enable it // explicitly - otherwise you go live with video but no audio, and nothing // tells you. Enabling it is also what triggers the microphone permission // prompt on first run. call.join({ create: true }).then(() => { call.camera.enable(); call.microphone.enable(); }); export default function App() { return ( <SafeAreaProvider> <StreamVideo client={client} language="en"> <StreamCall call={call}> <SafeAreaView style={styles.container}> <LivestreamView /> </SafeAreaView> </StreamCall> </StreamVideo> </SafeAreaProvider> ); } const LivestreamView = () => ( <Text style={styles.text}>TODO: render video</Text> ); const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "white", }, text: { fontSize: 30, color: "black", }, });

Run the app and you'll see "TODO: render video" - a placeholder you replace in Step 4.3. It confirms the app builds and launches, but be aware it renders regardless of whether the call actually joined: LivestreamView reads no call state, and call.join() above is not awaited, so a failure surfaces only in the console.

Checkpoint: the app shows "TODO: render video", and the Metro console shows no error from join(). Watch specifically for a 403 - that means the call id already exists under a different owner, and you should pick a new one. In a release build the error would be invisible, so check it now.

Here's what that code did. First, the user:

ts
1
2
3
import type { User } from "@stream-io/video-react-native-sdk"; const user: User = { id: userId, name: "Tutorial" };

Then the client:

ts
1
2
3
import { StreamVideoClient } from "@stream-io/video-react-native-sdk"; const client = new StreamVideoClient({ apiKey, user, token });

Your backend normally generates that token when the user signs up or signs in.

The call is the part worth dwelling on. The SDK uses the same Call object for livestreaming, audio rooms and video calling:

ts
1
2
3
4
5
const call = client.call("livestream", callId); call.join({ create: true }).then(() => { call.camera.enable(); call.microphone.enable(); });

Pass the call type livestream and a callId. That call type ships with sensible livestream defaults, and you can retune its features, permissions and settings - or add new call types - in the dashboard.

call.join({ create: true }) does two things: it creates the call on our servers and opens the realtime transport for audio and video.

You can also add members to a call and give them roles; see the call creation docs.

Step 4.3 - Rendering the video

Now show your local video with a button to start the livestream.

Replace the placeholder LivestreamView in App.tsx with the implementation below, along with the imports it needs. Here's the complete App.tsx at this point:

App.tsx (tsx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import React, { useEffect } from "react"; import { StreamVideoClient, StreamVideo, User, StreamCall, callManager, useCall, useCallStateHooks, VideoRenderer, } from "@stream-io/video-react-native-sdk"; import { View, Button, Text, StyleSheet } from "react-native"; import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; const apiKey = "REPLACE_WITH_API_KEY"; const token = "REPLACE_WITH_TOKEN"; const userId = "REPLACE_WITH_USER_ID"; const callId = "REPLACE_WITH_CALL_ID"; const user: User = { id: userId, name: "Tutorial" }; const client = new StreamVideoClient({ apiKey, user, token }); const call = client.call("livestream", callId); call.join({ create: true }).then(() => { call.camera.enable(); call.microphone.enable(); }); export default function App() { return ( <SafeAreaProvider> <StreamVideo client={client} language="en"> <StreamCall call={call}> <SafeAreaView style={styles.container}> <LivestreamView /> </SafeAreaView> </StreamCall> </StreamVideo> </SafeAreaProvider> ); } const LivestreamView = () => { const { useParticipantCount, useLocalParticipant, useIsCallLive } = useCallStateHooks(); const call = useCall(); const totalParticipants = useParticipantCount(); const localParticipant = useLocalParticipant(); const isCallLive = useIsCallLive(); // Automatically route audio to speaker devices as relevant for watching videos. useEffect(() => { callManager.start({ audioRole: "communicator", deviceEndpointType: "speaker", }); return () => callManager.stop(); }, []); return ( <View style={styles.flexed}> <Text style={styles.text}>Live: {totalParticipants}</Text> <View style={styles.flexed}> {localParticipant && ( <VideoRenderer participant={localParticipant} trackType="videoTrack" /> )} </View> <View style={styles.bottomBar}> {isCallLive ? ( <Button onPress={() => call?.stopLive()} title="Stop Live" /> ) : ( <Button onPress={() => call?.goLive()} title="Go Live" /> )} </View> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "white", }, flexed: { flex: 1, }, text: { alignSelf: "center", color: "white", backgroundColor: "blue", padding: 6, margin: 4, }, bottomBar: { alignSelf: "center", margin: 4, }, });

Human checkpoint: the device asks for camera and microphone access the first time - allow both. Agents: build and launch, then ask the human to grant access and confirm their own camera preview renders.

Step 5 - Backstage and GoLive

Backstage lets you and your co-hosts set up camera and equipment before anyone can watch. Viewers can only join after you call call.goLive().

If you'd rather calls start the moment you join them, open the Stream dashboard, find the livestream call type and disable backstage.

Step 6 - Preview the livestream

The React Native docs cover running on a device. Press Go live in the app and you'll get this:

Screenshot shows livestream app preview

Click below to watch yourself as a viewer in the browser. The link points at the call id from the snippets above.

For testing you can join the call on our web-app:

Checkpoint: the button reads Stop Live, your camera preview is on the device, and the browser tab plays your stream a moment later.

This is the payoff: you're broadcasting live from a phone to anyone with the link. On the shared tutorial credentials? Create your own Stream app - the free maker plan covers hobby projects.

Verify the whole build

Build to your device and run the full loop: allow camera and microphone, see your own preview, tap Go Live, then open the browser viewer and watch your stream arrive. Tap Stop Live and confirm the viewer drops out.

Troubleshooting

  • token is invalid / auth error - the token was minted for a different app, or it expired. Re-mint with getstream token <user-id> and confirm the API key matches.
  • project credentials missing - the CLI has not been initialized here. Run getstream init in the project directory first.
  • Viewer shows a black screen, or "The host hasn't joined yet" - OBS stopped streaming, or the API key, viewer token and livestream id came from different dashboard pages.
  • OBS connects but the dashboard shows nothing - the Server and Stream Key must match the dashboard's RTMP URL and RTMP Stream Key exactly, and Service must be set to Custom.
  • No camera preview in Part 2 - you're on an emulator. Emulators have no camera; build to a physical device.
  • Camera and mic permission prompts never appear - the usage description keys are missing from Info.plist, or the permissions are missing from AndroidManifest.xml (Step 4.1).
  • goLive() does nothing - the user lacks permission on that call type, or the call was already live. Check the console.
  • Viewers can't join even though you're live - backstage is still on for the call type and goLive() never succeeded.
  • iOS build fails to find a pod - run bundle install then bundle exec pod install from the ios folder, or npx pod-install from the project root.
  • Android build fails on minSdkVersion - set minSdkVersion = 24 in the buildscript.ext block of android/build.gradle.

Advanced Features

This tutorial covered watching a livestream over RTMP-in with OBS, and publishing one from a device.

There are several advanced features that can improve the livestreaming experience:

  • Co-hosts You can add members to your livestream with elevated permissions. So you can have co-hosts, moderators etc. You can see how to render multiple video tracks in our video calling tutorial.
  • Permissions and Moderation You can set up different types of permissions for different types of users and a request-based approach for granting additional access.
  • Custom events You can use custom events on the call to share any additional data. Think about showing the score for a game, or any other realtime use case.
  • Reactions & Chat Users can react to the livestream, and you can add chat. This makes for a more engaging experience.
  • Notifications You can notify users via push notifications when the livestream starts
  • Recording The call recording functionality allows you to record the call with various options and layouts
  • HLS Another way to watch a livestream is using HLS. HLS tends to have a 10 to 20 seconds delay, while the WebRTC approach is realtime. The benefit that HLS offers is better buffering under poor network conditions.

Recap

It was fun to see just how quickly you can build in-app low latency livestreaming. Please do let us know if you run into any issues. Our team is also happy to review your UI designs and offer recommendations on how to achieve them with Stream Video SDKs.

To recap what we've learned:

  • WebRTC is optimal for latency, HLS is slower but buffers better for users with poor connections
  • You set up a call: const call = client.call("livestream", callId)
  • The call type livestream controls which features are enabled and how permissions are set up
  • When you join a call, realtime communication is set up for audio & video: call.join()
  • Call State Hooks make it easy to build your own UI
  • You can easily publish your own video and audio from a React Native app on a mobile device

Calls run on Stream's global-edge network of video servers. Being closer to your users improves the latency and reliability of calls.

The SDKs enable you to build livestreaming, audio rooms and video calling in days.

We hope you've enjoyed this tutorial and please do feel free to reach out if you have any suggestions or questions.

Next steps

Pricing is flexible with multiple tiers, including a free maker plan for hobby projects and small companies.

Final Thoughts

In this video app tutorial we built a fully functioning React Native video app with our React Native SDK component library. We also showed how easy it is to customize the behavior and the style of the React Native video app components with minimal code changes.

Both the video SDK for React Native and the API have plenty more features available to support more advanced use-cases.

Machine-readable resources

For AI agents and coding assistants working with this SDK:

  • CLI + skills: curl -fsSL https://getstream.io/cli.sh | bash, then getstream skills and getstream skills stream-react-native for the React Native pack. (Alternative: npx skills add GetStream/agent-skills -s stream.)
  • Provisioning: getstream init (auth + create/select org & app) -> getstream token <user-id> (mint a token). getstream env --target <expo|ios|android> writes keys into a project file where the platform has a convention for it.
  • Data & config from the CLI: getstream api <Endpoint> --request '{...}' for users, calls, and call members
  • React Native integration skill: invoke /stream-react-native in your agent for React Native and Expo setup patterns; /stream-docs searches live SDK docs with citations
  • Docs index for LLMs: https://getstream.io/video/docs/react-native/llms.txt
  • Markdown endpoints: append .md to any docs URL for a clean, token-efficient version
  • Source of truth for APIs: the SDK repository - check the installed version's source rather than assuming APIs from training data

Give us feedback!

Did you find this tutorial helpful in getting you up and running with your project? Either good or bad, we're looking for your honest feedback so we can improve.

Start coding for free

No credit card required.
If you're interested in a custom plan or have any questions, please contact us.