Tổng quan về Streaming
Streaming là gì?
Streaming hoạt động như thế nào?
Streaming sử dụng User Datagram Protocol (UDP) hay Transmission Control Protocol (TCP)?
Một số phương thức Streaming sử dụng UDP, trong khi các phương thức khác sử dụng TCP. UDP và TCP là các giao thức truyền tải, được sử dụng để truyền các gói dữ liệu qua mạng. Cả hai đều hoạt động cùng với Internet Protocol (IP).TCP thiết lập một kết nối riêng trước khi truyền dữ liệu và đảm bảo các gói dữ liệu được gửi đến theo đúng thứ tự. Trong khi đó, UDP không thực hiện các cơ chế này. Do đó, TCP có độ tin cậy cao hơn, nhưng truyền dữ liệu qua UDP thường nhanh hơn TCP, mặc dù một số gói dữ liệu có thể bị mất trong quá trình truyền.
Buffering là gì?
Các trình phát Streaming thường tải trước một vài giây nội dung để video hoặc âm thanh vẫn có thể tiếp tục phát nếu kết nối bị gián đoạn trong thời gian ngắn. Quá trình này được gọi là Buffering. Buffering giúp video phát mượt mà và liên tục. Tuy nhiên, với kết nối quá chậm hoặc mạng có độ trễ cao, video có thể mất nhiều thời gian để Buffering.
Xây dựng ứng dụng Streaming với React Native sử dụng GetStream
Trong hướng dẫn này, chúng ta sẽ bắt đầu trực tiếp với việc xây dựng ứng dụng Streaming mà không đi sâu vào các bước thiết lập React Native bổ sung.
Để xây dựng một hệ thống Streaming hoàn chỉnh, thông thường chúng ta cần phát triển Backend, CMS và ứng dụng di động. Tuy nhiên, trong hướng dẫn này, chúng ta sẽ tập trung vào việc xây dựng ứng dụng Streaming chỉ với React Native. Để thực hiện, chúng ta sẽ sử dụng thư viện bên thứ ba có tên GetStream. GetStream.
Cài đặt (Xem chi tiết tại đây)
Để cài đặt Stream Video React Native SDK, hãy chạy lệnh sau trong Terminal mà bạn sử dụng:
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
Cài đặt dành riêng cho Android
buildscript {
ext {
...
minSdkVersion = 24
}
...
}
Bật hỗ trợ Java 8
Trong android/app/build.gradle, thêm đoạn sau vào phần android:
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_11
} Tùy chọn: Hỗ trợ R8/ProGuard
Nếu cần hỗ trợ R8/ProGuard, trong file android/app/proguard-rules.pro, thêm dòng sau:
-keep class org.webrtc.** { *; }
Khai báo quyền
iOS
Tối thiểu, thêm các key và value sau vào file Info.plist:
Privacy – Camera Usage Description: “ requires camera access to capture and transmit video”- Privacy – Microphone Usage Description: “ requires microphone access to capture and transmit audio”
Android
Trong 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" />
Tiếp theo, chúng ta sẽ xây dựng hai màn hình: màn hình Host và màn hình Viewer.
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,
];
Kết luận
Bằng cách làm theo hướng dẫn này, bạn có thể xây dựng một ứng dụng Streaming cơ bản với React Native. Nếu muốn phát triển một ứng dụng Streaming toàn diện và chuyên nghiệp hơn, bạn có thể lựa chọn hợp tác với một đơn vị gia công phần mềm (Software Outsourcing) như SparkMinds để được tư vấn và hỗ trợ triển khai giải pháp phù hợp. hãy liên hệHãy chia sẻ thông tin chi tiết về dự án và nhận báo giá từ chúng tôi.
Chúng tôi luôn sẵn sàng ký kết một NDA để bảo mật thông tin của bạn!















