In the fourth industrial revolution, streaming became more popular in multiple sectors, such as entertainment, retail, and consulting services. If you want to create a streaming app to help your business boom, you are in the right place. This article will help you understand streaming and how to develop a streaming app with React Native and GetStream.

Streaming Overview

What Is Streaming?

Streaming is the continuous transmission of audio or video files from a server to a client. In simpler terms, streaming is what happens when audiences watch TV or listen to podcasts on Internet-connected devices. With streaming, the media file being played on the client device is stored remotely and transmitted a few seconds at a time over the Internet.

How does streaming work?

Just like other data sent over the Internet, audio and video data are broken down into data packets. Each packet contains a small piece of the file, and an audio or video player in the browser on the client device receives the flow of data packets and interprets them as video or audio.

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.

If TCP is like a package delivery service that requires the recipient to sign for the package, then UDP is like a delivery service that leaves packages on the front porch without knocking on the door to get a signature. The TCP delivery service loses fewer packages, but the UDP delivery service is faster, because packages can get dropped off even if no one is home to sign for them.
For streaming, in some cases, speed is far more important than reliability. For instance, if someone is in a video conference, they would prefer to interact with the other conference attendees in real time than to sit and wait for every bit of data to be delivered. Therefore, a few lost data packets are not a huge concern, and UDP should be used.
In other cases, reliability is more important for streaming. For example, both HTTP live streaming (HLS) and MPEG-DASH are streaming protocols that use TCP for transport. Many video-on-demand services use TCP.

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
Stream Video React Native SDK requires installing some peer dependencies to provide you with a great calling experience. You can run the following command to install them:
yarn add @stream-io/react-native-webrtc \\
   react-native-incall-manager react-native-svg \\
   @react-native-community/netinfo
npx pod-install

Android Specific installation

In android/build.gradle add the following inside the buildscript section:
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

Making video or audio calls requires the usage of the device’s camera and microphone accordingly. In both platforms, we must declare the 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" />
If you plan to also support Bluetooth devices then add the following command.
<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.

StreamingHostScreen.tsx
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>
  );
};
ViewerLivestreamView.tsx
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>
  );
};
ViewerLivestreamTopView.tsx
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,
];
ViewerLivestreamControls.tsx
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!