Streaming Overview
What Is Streaming?
How does streaming work?
Does streaming use the User Datagram Protocol (UDP) or the Transmission Control Protocol (TCP)?
Some streaming methods use UDP, while others use TCP. UDP and TCP are transport protocols, meaning they are used for moving packets of data across networks. Both are used with the Internet Protocol (IP). TCP opens a dedicated connection before transmitting data, and it ensures all data packets arrive in order. Unlike TCP, UDP does neither of these things. As a result, TCP is more reliable, but transmitting data via UDP does not take as long as it does via TCP, although some packets are lost along the way.
What is buffering?
Streaming media players load a few seconds of the stream ahead of time so that the video or audio can continue playing if the connection is briefly interrupted. This is known as buffering. Buffering ensures that videos can play smoothly and continuously. However, over slow connections, or if a network has a great deal of latency, a video can take a long time to buffer.
Create a Streaming App with React Native Using GetStream
In this tutorial, we’ll jump straight into building a streaming app without covering additional React Native setup.
To create a complete streaming system, we would typically need to develop our own backend, CMS, and mobile app. But now we are focusing solely on building a streaming app using just React Native. To achieve this, we need to use a third-party library called GetStream.
Installation (Details here)
In order to install the Stream Video React Native SDK, run the following command in your terminal of choice:
yarn add @stream-io/video-react-native-sdk
yarn add @stream-io/react-native-webrtc \\
react-native-incall-manager react-native-svg \\
@react-native-community/netinfo
npx pod-install
Android Specific installation
buildscript {
ext {
...
minSdkVersion = 24
}
...
}
Enable Java 8 Support
In android/app/build.gradle add the following inside the android section:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_11
}
Optional: R8/ProGuard Support
If you require R8/ProGuard support then in android/app/proguard-rules.pro add the following on a new line:
-keep class org.webrtc.** { *; }
Declaring Permissions
iOS
Add the following keys and values to Info.plist file at a minimum:
Privacy – Camera Usage Description – “<Your_app_name> requires camera access to capture and transmit video” Privacy – Microphone Usage Description – ” <Your_app_name> requires microphone access to capture and transmit audio”
Android
In AndroidManifest.xml add the following permissions before the <application> section.
<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" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
Next, we will build two screens: the Host screen and the Viewer screen.
import React, { useEffect, useLayoutEffect } from "react";
import {
HostLivestream,
StreamCall,
StreamVideo,
StreamVideoClient,
User,
} from "@stream-io/video-react-native-sdk";
import { AppStackScreenProp } from "navigators";
import { Layout } from "components";
import { HostLivestreamControlsView } from "./HostLivestreamControlsView";
import { HostLivestreamTopView } from "./HostLivestreamTopView";
import IncallManager from "react-native-incall-manager";
const apiKey = "mmhfdzb5evj2"; // API key for Stream Video SDK
const token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3Byb250by5nZXRzdHJlYW0uaW8iLCJzdWIiOiJ1c2VyL0dlbmVyYWxfR3JpZXZvdXMiLCJ1c2VyX2lkIjoiR2VuZXJhbF9Hcmlldm91cyIsInZhbGlkaXR5X2luX3NlY29uZHMiOjYwNDgwMCwiaWF0IjoxNzI2MDQ3MzYyLCJleHAiOjE3MjY2NTIxNjJ9.GDMFP4wf3DifOw1ZmxO0KSy2HG_t4ByFv2kKwW5hVp4"; // JWT token for user authentication
const userId = "General_Grievous"; // User ID for the stream
const callId = "FMeNGvwQJY26"; // Call ID for the livestream session
// StreamingHostScreen functional component
export const StreamingHostScreen: React.FC<
AppStackScreenProp<"StreamingHost">
> = ({ navigation }) => {
const user: User = { id: userId, name: "SparkMinds Host" }; // User details
// Get or create an instance of the StreamVideoClient
const client = StreamVideoClient.getOrCreateInstance({ apiKey, user, token });
// Initialize a call of type 'livestream' with the given callId
const call = client.call("livestream", callId);
// useEffect to handle joining and ending the livestream
useEffect(() => {
call.join({ create: true }); // Join or create the livestream
IncallManager.start({ media: "video" }); // Start the call with video
return () => {
call.endCall(); // Clean up by ending the call
IncallManager.stop(); // Stop the call manager when component unmounts
};
}, []);
// useLayoutEffect to hide the default header
useLayoutEffect(() => {
navigation.setOptions({
headerShown: false, // Hide the navigation header
});
}, []);
// Main JSX layout for hosting the livestream
return (
<Layout>
{/* StreamVideo provider for handling video stream */}
<StreamVideo client={client}>
{/* StreamCall component wrapping the livestream */}
<StreamCall call={call}>
{/* HostLivestream component, providing custom top view and controls */}
<HostLivestream
HostLivestreamTopView={HostLivestreamTopView} // Top view for the host stream UI
HostLivestreamControls={HostLivestreamControlsView} // Controls for managing the stream
/>
</StreamCall>
</StreamVideo>
</Layout>
);
};
HostLivestreamTopView.tsx
import React from "react";
import {
DurationBadge,
FollowerCount,
HostLivestreamTopViewProps,
LiveIndicator,
} from "@stream-io/video-react-native-sdk";
import { SViewStyle } from "models";
import { View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { style } from "theme";
export const HostLivestreamTopView: React.FC<
HostLivestreamTopViewProps
> = () => {
const insets = useSafeAreaInsets(); // Get the safe area insets to handle devices with notches
return (
// Main container for the top view, with top padding based on safe area insets
<View style={[$topView, { paddingTop: insets.top }]}>
{/* Displays the stream duration for the host */}
<DurationBadge mode="host" />
{/* Spacer view to push other elements apart */}
<View style={style.flex_1} />
{/* Live indicator showing the stream is currently live */}
<LiveIndicator />
{/* Displays the number of followers watching the stream */}
<FollowerCount />
</View>
);
};
// Styling for the top view container, arranging elements in a row with padding
const $topView: SViewStyle = [style.row, style.px_md];
HostLivestreamControlsView.tsx
import React from "react";
import {
HostLivestreamControlsProps,
HostStartStreamButton,
LivestreamMediaControls,
} from "@stream-io/video-react-native-sdk";
import { SViewStyle } from "models";
import { View } from "react-native";
import { spacing, style } from "theme";
import { useSafeAreaInsets } from "react-native-safe-area-context";
type HostLivestreamControlsViewProps = {} & HostLivestreamControlsProps;
// Main functional component for controlling the host livestream
export const HostLivestreamControlsView: React.FC<
HostLivestreamControlsViewProps
> = ({
onEndStreamHandler, // Handler for ending the stream
onStartStreamHandler, // Handler for starting the stream
hls, // HLS (HTTP Live Streaming) protocol flag or configuration
disableStopPublishedStreamsOnEndStream, // Option to disable stopping published streams when ending
onLayout, // Layout handler, typically for UI measurement purposes
}) => {
const insets = useSafeAreaInsets(); // Get safe area insets (e.g., for iPhone with notches)
return (
// Root view containing controls with safe area padding and layout handling
<View
style={[$root, { paddingBottom: insets.bottom || spacing.md }]} // Adding bottom padding based on safe area insets
onLayout={onLayout} // Handling layout changes
>
{/* Start/End stream button, passing through event handlers and stream config */}
<HostStartStreamButton
onEndStreamHandler={onEndStreamHandler}
onStartStreamHandler={onStartStreamHandler}
hls={hls}
disableStopPublishedStreamsOnEndStream={
disableStopPublishedStreamsOnEndStream
}
/>
{/* Media controls for interacting with the livestream (play, pause, etc.) */}
<LivestreamMediaControls />
</View>
);
};
// Styles for the root view using project-defined styles
const $root: SViewStyle = [style.row_between, style.px_md, style.pt_md];
StreamingViewerScreen.tsx
import React, { useLayoutEffect } from "react";
import { Layout } from "components";
import { AppStackScreenProp } from "navigators";
import {
LivestreamPlayer,
StreamVideo,
StreamVideoClient,
User,
} from "@stream-io/video-react-native-sdk";
import { ViewerLivestreamView } from "./ViewerLivestreamView";
const apiKey = "mmhfdzb5evj2"; // API key for Stream Video SDK
const token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3Byb250by5nZXRzdHJlYW0uaW8iLCJzdWIiOiJ1c2VyL0dlbmVyYWxfR3JpZXZvdXMiLCJ1c2VyX2lkIjoiR2VuZXJhbF9Hcmlldm91cyIsInZhbGlkaXR5X2luX3NlY29uZHMiOjYwNDgwMCwiaWF0IjoxNzI2MDQ3MzYyLCJleHAiOjE3MjY2NTIxNjJ9.GDMFP4wf3DifOw1ZmxO0KSy2HG_t4ByFv2kKwW5hVp4"; // JWT token for user authentication
const userId = "General_Grievous"; // User ID for the viewer
const callId = "FMeNGvwQJY26"; // Call ID for the livestream session
const user: User = { id: userId, name: "SparkMinds Viewer" }; // Viewer user details
// StreamingViewerScreen functional component
export const StreamingViewerScreen: React.FC<
AppStackScreenProp<"StreamingViewer">
> = ({ navigation }) => {
// Create an instance of the StreamVideoClient with the given API key, user details, and token
const client = StreamVideoClient.getOrCreateInstance({ apiKey, user, token });
// useLayoutEffect to set up the navigation header
useLayoutEffect(() => {
navigation.setOptions({
headerShown: false, // Hide the navigation header while viewing the livestream
});
}, []);
// Main JSX layout for the livestream viewer
return (
<Layout>
{/* StreamVideo provider to handle video stream for the viewer */}
<StreamVideo client={client}>
{/* LivestreamPlayer to manage the livestream playback */}
<LivestreamPlayer
callType="livestream" // Specifies the type of stream (livestream)
callId={callId} // Passes the callId for the specific livestream session
ViewerLivestream={ViewerLivestreamView} // Custom viewer view for the livestream UI
/>
</StreamVideo>
</Layout>
);
};
import React, { useEffect } from "react";
import { StyleSheet } from "react-native";
import InCallManager from "react-native-incall-manager";
import { useCallStateHooks } from "@stream-io/video-react-bindings";
import { hasVideo } from "@stream-io/video-client";
import {
DurationBadge,
FloatingParticipantView,
FollowerCount,
LiveIndicator,
LivestreamLayout,
useTheme,
ViewerLivestreamProps,
ViewerLivestreamTopViewProps,
} from "@stream-io/video-react-native-sdk";
import { Layout } from "components";
import { ViewerLivestreamTopView } from "./ViewerLivestreamTopView";
import { ViewerLivestreamControls } from "./ViewerLivestreamControls";
type ViewerLivestreamViewProps = {} & ViewerLivestreamProps;
export const ViewerLivestreamView: React.FC<ViewerLivestreamViewProps> = ({
onLeaveStreamHandler,
}) => {
const {
theme: { viewerLivestream }, // Get the theme for viewer livestream
} = useTheme();
const { useHasOngoingScreenShare, useParticipants } = useCallStateHooks(); // Hooks for call state management
const hasOngoingScreenShare = useHasOngoingScreenShare(); // Check if there is an ongoing screen share
const [currentSpeaker] = useParticipants(); // Get the current speaker from participants
const floatingParticipant =
hasOngoingScreenShare &&
currentSpeaker &&
hasVideo(currentSpeaker) && // Check if the current speaker has video
currentSpeaker;
const [topViewHeight, setTopViewHeight] = React.useState<number>(); // State for the height of the top view
const [controlsHeight, setControlsHeight] = React.useState<number>(); // State for the height of the controls
// Automatically route audio to speaker devices as relevant for watching videos
useEffect(() => {
InCallManager.start({ media: "video" }); // Start the in-call manager for video
return () => InCallManager.stop(); // Stop the in-call manager when the component unmounts
}, []);
// Props for the top view of the livestream
const topViewProps: ViewerLivestreamTopViewProps = {
LiveIndicator,
FollowerCount,
DurationBadge,
onLayout: (event) => {
setTopViewHeight(event.nativeEvent.layout.height); // Set the height of the top view
},
};
return (
<Layout style={viewerLivestream.container}>
{" "}
{/* Main layout for the viewer livestream */}
<ViewerLivestreamTopView {...topViewProps} />{" "}
{/* Render the top view with props */}
{floatingParticipant && ( // Render floating participant if available
<FloatingParticipantView
participant={floatingParticipant} // Pass the floating participant
draggableContainerStyle={[
StyleSheet.absoluteFill, // Fill the container absolutely
{
top: topViewHeight, // Position based on top view height
bottom: controlsHeight, // Position based on controls height
},
]}
/>
)}
<LivestreamLayout /> {/* Render the main livestream layout */}
<ViewerLivestreamControls
onLeaveStreamHandler={onLeaveStreamHandler} // Pass handler for leaving the stream
onLayout={(event) => {
setControlsHeight(event.nativeEvent.layout.height); // Set the height of the controls
}}
/>
</Layout>
);
};
import React from "react";
import {
FollowerCount,
LiveIndicator,
ViewerLivestreamTopViewProps,
} from "@stream-io/video-react-native-sdk";
import { SViewStyle } from "models";
import { View } from "react-native";
import { style } from "theme";
import { useSafeAreaInsets } from "react-native-safe-area-context";
export const ViewerLivestreamTopView: React.FC<
ViewerLivestreamTopViewProps
> = ({ onLayout }) => {
const insets = useSafeAreaInsets(); // Get safe area insets for proper padding
return (
<View onLayout={onLayout} style={[$root, { paddingTop: insets.top }]}>
{/* Indicator showing the stream is live */}
<LiveIndicator />
{/* Displays the number of followers watching the stream */}
<FollowerCount />
</View>
);
};
const $root: SViewStyle = [
style.row,
style.abs,
style.px_md,
{ left: 0, top: 0, zIndex: 1 },
style.w_screenWidth,
];
import {
ViewerLeaveStreamButton,
ViewerLivestreamControlsProps,
} from "@stream-io/video-react-native-sdk";
import { SViewStyle } from "models";
import React from "react";
import { View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { spacing, style } from "theme";
export const ViewerLivestreamControls: React.FC<
ViewerLivestreamControlsProps
> = ({ onLeaveStreamHandler, onLayout }) => {
const insets = useSafeAreaInsets(); // Get safe area insets for bottom padding
return (
<View
onLayout={onLayout} // Handle layout changes
style={[$root, { paddingBottom: insets.bottom || spacing.md }]} // Apply styles and padding for safe area
>
{/* Button for viewers to leave the stream */}
<ViewerLeaveStreamButton onLeaveStreamHandler={onLeaveStreamHandler} />
</View>
);
};
const $root: SViewStyle = [
style.abs,
style.bottom_0,
style.left_0,
style.right_0,
style.row,
style.px_md,
style.py_lg,
];
Conclusion, by following this guide, you can create a basic steaming app with React Native. In case, you want to build a comprehensive streaming app, I recommend you to hire a software outsourcing agency as SparkMinds. You can contact us, share the project details and get the quotation.
We are always willing to sign an NDA to save your information!