React Native Implementation Guide
This comprehensive guide covers everything you need to integrate social.plus Video SDK into your React Native application, including live streaming, video playback, and real-time notifications.Prerequisites
- React Native 0.64 or higher
- Node.js 14 or higher
- iOS 12+ (for iOS apps)
- Android API level 21+ (for Android apps)
- Valid social.plus API credentials
Installation & Setup
Package Installation
Install the required peer dependencies:- npm
- yarn
npm install \
@amityco/ts-sdk-react-native \
@amityco/video-broadcaster-react-native \
@amityco/video-player-react-native \
@api.video/react-native-livestream \
react-native-video \
react-native-vlc-media-player \
react-native-get-random-values \
react-native-rsa-native
yarn add \
@amityco/ts-sdk-react-native \
@amityco/video-broadcaster-react-native \
@amityco/video-player-react-native \
@api.video/react-native-livestream \
react-native-video \
react-native-vlc-media-player \
react-native-get-random-values \
react-native-rsa-native
Platform Configuration
- iOS
- Android
1. Install iOS Dependencies
cd ios && pod install
2. Configure Permissions
Add the following permissions to yourInfo.plist file:<key>NSCameraUsageDescription</key>
<string>This app needs camera access to broadcast live streams</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app needs microphone access for live streaming audio</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs photo library access to share images and videos</string>
3. Configure Build Settings
In yourios/Podfile, ensure minimum iOS version:platform :ios, '12.0'
1. Configure Build Settings
Updateandroid/build.gradle to include required versions:buildscript {
ext {
compileSdkVersion = 34
targetSdkVersion = 34
minSdkVersion = 21
kotlinVersion = "1.9.22"
// ... other configurations
}
}
2. Configure Permissions
Add permissions toandroid/app/src/main/AndroidManifest.xml:<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Camera and Audio Permissions -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<!-- Network Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Storage Permissions -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Hardware Features -->
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.microphone" android:required="true" />
</manifest>
3. ProGuard Configuration
If using ProGuard, add these rules toandroid/app/proguard-rules.pro:-keep class com.amityco.** { *; }
-dontwarn com.amityco.**
SDK Initialization
Basic Setup
import { Client } from '@amityco/ts-sdk-react-native';
import { setupAmityVideoPlayer } from '@amityco/video-player-react-native';
const initializeSocialPlus = async () => {
try {
// Initialize the SDK
const loginParams = {
userId: 'your-user-id',
displayName: 'User Display Name',
// Add other required parameters
};
const sessionHandler = {
sessionWillRenewAccessToken: (renewal) => {
// Handle session renewal
renewal.renew();
},
};
// Login to social.plus
const response = await Client.login(loginParams, sessionHandler);
// Setup video player after successful login
setupAmityVideoPlayer();
console.log('social.plus SDK initialized successfully');
} catch (error) {
console.error('Failed to initialize social.plus SDK:', error);
}
};
Live Streaming Implementation
Broadcasting Live Streams
- Basic Broadcasting
- Advanced Broadcasting
import React, { useRef, useState } from 'react';
import { View, Button, Alert } from 'react-native';
import { AmityVideoBroadcaster } from '@amityco/video-broadcaster-react-native';
import { StreamRepository } from '@amityco/ts-sdk-react-native';
const LiveBroadcastScreen = () => {
const broadcasterRef = useRef(null);
const [isStreaming, setIsStreaming] = useState(false);
const [streamId, setStreamId] = useState(null);
const startBroadcast = async () => {
try {
// Create a new stream
const streamParams = {
title: 'My Live Stream',
description: 'Broadcasting live from React Native',
thumbnailFileId: 'optional-thumbnail-file-id',
isSecure: false,
};
const { data: newStream } = await StreamRepository.createStream(streamParams);
setStreamId(newStream.streamId);
// Start broadcasting
broadcasterRef.current?.startPublish(newStream.streamId);
setIsStreaming(true);
} catch (error) {
Alert.alert('Error', 'Failed to start broadcast');
console.error('Broadcast error:', error);
}
};
const stopBroadcast = () => {
broadcasterRef.current?.stopPublish();
setIsStreaming(false);
setStreamId(null);
};
const switchCamera = () => {
broadcasterRef.current?.switchCamera();
};
const onBroadcastStateChange = (state) => {
console.log('Broadcast state changed:', state);
// Handle state changes (connecting, connected, disconnected, etc.)
};
return (
<View style={{ flex: 1 }}>
<AmityVideoBroadcaster
ref={broadcasterRef}
style={{ flex: 1 }}
onBroadcastStateChange={onBroadcastStateChange}
// Optional: Configure video settings
resolution={{
width: 1280,
height: 720,
}}
bitrate={2000000} // 2 Mbps
/>
<View style={{ padding: 20 }}>
<Button
title={isStreaming ? "Stop Broadcast" : "Start Broadcast"}
onPress={isStreaming ? stopBroadcast : startBroadcast}
/>
{isStreaming && (
<Button
title="Switch Camera"
onPress={switchCamera}
/>
)}
</View>
</View>
);
};
export default LiveBroadcastScreen;
import React, { useRef, useState, useEffect } from 'react';
import { View, Button, Alert, Text } from 'react-native';
import { AmityVideoBroadcaster, AmityStreamBroadcasterState } from '@amityco/video-broadcaster-react-native';
import { StreamRepository } from '@amityco/ts-sdk-react-native';
const AdvancedBroadcastScreen = () => {
const broadcasterRef = useRef(null);
const [broadcastState, setBroadcastState] = useState(AmityStreamBroadcasterState.IDLE);
const [streamData, setStreamData] = useState(null);
const [connectionQuality, setConnectionQuality] = useState('unknown');
const createAndStartStream = async () => {
try {
const streamParams = {
title: 'Advanced Live Stream',
description: 'High-quality broadcast with monitoring',
tags: ['live', 'react-native'],
isSecure: false,
metadata: {
quality: 'HD',
source: 'react-native-app'
}
};
const { data: newStream } = await StreamRepository.createStream(streamParams);
setStreamData(newStream);
// Configure advanced settings
const broadcastConfig = {
resolution: {
width: 1920,
height: 1080,
},
bitrate: 4000000, // 4 Mbps for HD
frameRate: 30,
audioConfig: {
bitrate: 128000,
sampleRate: 44100
}
};
// Start broadcast with configuration
broadcasterRef.current?.startPublish(newStream.streamId, broadcastConfig);
} catch (error) {
Alert.alert('Error', 'Failed to create stream');
console.error('Stream creation error:', error);
}
};
const handleBroadcastStateChange = (state) => {
setBroadcastState(state);
switch (state) {
case AmityStreamBroadcasterState.CONNECTING:
console.log('Connecting to stream...');
break;
case AmityStreamBroadcasterState.CONNECTED:
console.log('Stream connected successfully');
break;
case AmityStreamBroadcasterState.DISCONNECTED:
console.log('Stream disconnected');
handleStreamEnd();
break;
case AmityStreamBroadcasterState.ERROR:
Alert.alert('Stream Error', 'Broadcasting encountered an error');
break;
}
};
const handleStreamEnd = async () => {
if (streamData) {
try {
// End the stream properly
await StreamRepository.endStream(streamData.streamId);
setStreamData(null);
} catch (error) {
console.error('Error ending stream:', error);
}
}
};
const monitorConnectionQuality = (quality) => {
setConnectionQuality(quality);
// Adjust bitrate based on connection quality
if (quality === 'poor' && broadcasterRef.current) {
broadcasterRef.current.adjustBitrate(1000000); // Lower to 1 Mbps
} else if (quality === 'excellent' && broadcasterRef.current) {
broadcasterRef.current.adjustBitrate(4000000); // Increase to 4 Mbps
}
};
return (
<View style={{ flex: 1 }}>
<AmityVideoBroadcaster
ref={broadcasterRef}
style={{ flex: 1 }}
onBroadcastStateChange={handleBroadcastStateChange}
onConnectionQualityChange={monitorConnectionQuality}
/>
<View style={{ padding: 20, backgroundColor: 'rgba(0,0,0,0.7)' }}>
<Text style={{ color: 'white', marginBottom: 10 }}>
Status: {broadcastState}
</Text>
<Text style={{ color: 'white', marginBottom: 10 }}>
Connection: {connectionQuality}
</Text>
<Button
title="Start Advanced Broadcast"
onPress={createAndStartStream}
disabled={broadcastState !== AmityStreamBroadcasterState.IDLE}
/>
</View>
</View>
);
};
export default AdvancedBroadcastScreen;
Video Playback
- Basic Playback
- Advanced Playback
import React, { useRef, useState, useEffect } from 'react';
import { View, Button, Text } from 'react-native';
import { AmityStreamPlayer } from '@amityco/video-player-react-native';
import { StreamRepository } from '@amityco/ts-sdk-react-native';
const VideoPlayerScreen = ({ streamId }) => {
const playerRef = useRef(null);
const [stream, setStream] = useState(null);
const [isPlaying, setIsPlaying] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (streamId) {
loadStream(streamId);
}
}, [streamId]);
const loadStream = (id) => {
const unsubscriber = StreamRepository.getStreamById(id, ({ data, loading, error }) => {
setLoading(loading);
if (data) {
setStream(data);
console.log('Stream loaded:', data.streamId, 'Status:', data.status);
}
if (error) {
console.error('Error loading stream:', error);
}
});
// Cleanup subscription
return () => unsubscriber();
};
const playStream = () => {
playerRef.current?.play();
setIsPlaying(true);
};
const pauseStream = () => {
playerRef.current?.pause();
setIsPlaying(false);
};
const togglePlayback = () => {
if (isPlaying) {
pauseStream();
} else {
playStream();
}
};
if (loading) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Loading stream...</Text>
</View>
);
}
if (!stream) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Stream not found</Text>
</View>
);
}
return (
<View style={{ flex: 1 }}>
<AmityStreamPlayer
ref={playerRef}
stream={stream}
status={stream.status}
style={{ flex: 1 }}
onPlaybackStatusUpdate={(status) => {
console.log('Playback status:', status);
}}
/>
<View style={{ padding: 20 }}>
<Text style={{ marginBottom: 10 }}>
{stream.title} - Status: {stream.status}
</Text>
<Button
title={isPlaying ? "Pause" : "Play"}
onPress={togglePlayback}
/>
</View>
</View>
);
};
export default VideoPlayerScreen;
import React, { useRef, useState, useEffect } from 'react';
import { View, Button, Text, Slider, Alert } from 'react-native';
import { AmityStreamPlayer } from '@amityco/video-player-react-native';
import { StreamRepository } from '@amityco/ts-sdk-react-native';
const AdvancedVideoPlayerScreen = ({ streamId }) => {
const playerRef = useRef(null);
const [stream, setStream] = useState(null);
const [playbackState, setPlaybackState] = useState({
isPlaying: false,
currentTime: 0,
duration: 0,
isBuffering: false,
volume: 1.0,
playbackRate: 1.0
});
useEffect(() => {
if (streamId) {
loadStreamWithRetry(streamId);
}
}, [streamId]);
const loadStreamWithRetry = async (id, retryCount = 0) => {
const maxRetries = 3;
try {
const unsubscriber = StreamRepository.getStreamById(id, ({ data, loading, error }) => {
if (data) {
setStream(data);
console.log('Stream loaded successfully');
}
if (error && retryCount < maxRetries) {
console.log(`Retrying stream load (${retryCount + 1}/${maxRetries})`);
setTimeout(() => loadStreamWithRetry(id, retryCount + 1), 2000);
} else if (error) {
Alert.alert('Error', 'Failed to load stream after multiple attempts');
}
});
return unsubscriber;
} catch (error) {
console.error('Stream loading error:', error);
}
};
const handlePlaybackStatusUpdate = (status) => {
setPlaybackState(prev => ({
...prev,
isPlaying: status.isPlaying,
currentTime: status.positionMillis || 0,
duration: status.durationMillis || 0,
isBuffering: status.isBuffering || false
}));
};
const handlePlaybackError = (error) => {
console.error('Playback error:', error);
Alert.alert('Playback Error', 'Unable to play the stream');
};
const seekTo = (timeMillis) => {
playerRef.current?.seekTo(timeMillis);
};
const setVolume = (volume) => {
playerRef.current?.setVolume(volume);
setPlaybackState(prev => ({ ...prev, volume }));
};
const setPlaybackRate = (rate) => {
playerRef.current?.setRate(rate);
setPlaybackState(prev => ({ ...prev, playbackRate: rate }));
};
const formatTime = (millis) => {
const minutes = Math.floor(millis / 60000);
const seconds = Math.floor((millis % 60000) / 1000);
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
};
if (!stream) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Loading stream...</Text>
</View>
);
}
return (
<View style={{ flex: 1 }}>
<AmityStreamPlayer
ref={playerRef}
stream={stream}
status={stream.status}
style={{ flex: 1 }}
onPlaybackStatusUpdate={handlePlaybackStatusUpdate}
onError={handlePlaybackError}
resizeMode="contain"
shouldPlay={false}
volume={playbackState.volume}
rate={playbackState.playbackRate}
/>
{/* Custom Controls */}
<View style={{ padding: 20, backgroundColor: 'rgba(0,0,0,0.8)' }}>
<Text style={{ color: 'white', textAlign: 'center', marginBottom: 10 }}>
{stream.title}
</Text>
{/* Progress Bar */}
{playbackState.duration > 0 && (
<View style={{ marginBottom: 15 }}>
<Slider
style={{ width: '100%' }}
minimumValue={0}
maximumValue={playbackState.duration}
value={playbackState.currentTime}
onSlidingComplete={seekTo}
minimumTrackTintColor="#ffffff"
maximumTrackTintColor="#cccccc"
thumbStyle={{ backgroundColor: '#ffffff' }}
/>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text style={{ color: 'white' }}>
{formatTime(playbackState.currentTime)}
</Text>
<Text style={{ color: 'white' }}>
{formatTime(playbackState.duration)}
</Text>
</View>
</View>
)}
{/* Playback Controls */}
<View style={{ flexDirection: 'row', justifyContent: 'space-around', marginBottom: 15 }}>
<Button
title={playbackState.isPlaying ? "Pause" : "Play"}
onPress={() => {
if (playbackState.isPlaying) {
playerRef.current?.pause();
} else {
playerRef.current?.play();
}
}}
/>
<Button
title="0.5x"
onPress={() => setPlaybackRate(0.5)}
/>
<Button
title="1x"
onPress={() => setPlaybackRate(1.0)}
/>
<Button
title="2x"
onPress={() => setPlaybackRate(2.0)}
/>
</View>
{/* Volume Control */}
<View style={{ marginBottom: 10 }}>
<Text style={{ color: 'white', marginBottom: 5 }}>
Volume: {Math.round(playbackState.volume * 100)}%
</Text>
<Slider
style={{ width: '100%' }}
minimumValue={0}
maximumValue={1}
value={playbackState.volume}
onValueChange={setVolume}
minimumTrackTintColor="#ffffff"
maximumTrackTintColor="#cccccc"
/>
</View>
{playbackState.isBuffering && (
<Text style={{ color: 'yellow', textAlign: 'center' }}>
Buffering...
</Text>
)}
</View>
</View>
);
};
export default AdvancedVideoPlayerScreen;
Push Notifications
For comprehensive push notification setup and handling, refer to our Push Notifications Guide.Basic Notification Setup
import { StreamRepository } from '@amityco/ts-sdk-react-native';
import PushNotification from 'react-native-push-notification';
// Configure push notifications
const configurePushNotifications = () => {
PushNotification.configure({
onNotification: function(notification) {
console.log('Notification received:', notification);
// Handle stream-related notifications
if (notification.data?.streamId) {
// Navigate to stream or handle accordingly
handleStreamNotification(notification.data);
}
},
requestPermissions: Platform.OS === 'ios',
});
};
const handleStreamNotification = (data) => {
const { streamId, type } = data;
switch (type) {
case 'stream_started':
// Handle stream started notification
break;
case 'stream_ended':
// Handle stream ended notification
break;
default:
console.log('Unknown notification type:', type);
}
};
Error Handling & Troubleshooting
Common Issues and Solutions
- Installation Issues
- Runtime Errors
// Common installation troubleshooting
// 1. Metro bundler issues with native dependencies
// Add to metro.config.js:
module.exports = {
resolver: {
assetExts: ['bin', 'txt', 'jpg', 'png', 'json', 'mp4', 'mov'],
},
};
// 2. Android build issues
// Ensure proper Kotlin version in android/build.gradle:
buildscript {
ext {
kotlinVersion = "1.9.22" // Use this exact version
}
}
// 3. iOS linking issues
// Run these commands:
// cd ios && pod deintegrate && pod install
// 4. Clear React Native cache
// npx react-native start --reset-cache
// Error handling utilities
export class VideoSDKErrorHandler {
static handleStreamError(error) {
console.error('Stream error:', error);
switch (error.code) {
case 'NETWORK_ERROR':
return 'Check your internet connection and try again';
case 'PERMISSION_DENIED':
return 'Camera and microphone permissions are required';
case 'INVALID_STREAM_ID':
return 'The stream you\'re trying to access is invalid';
case 'STREAM_NOT_FOUND':
return 'Stream not found or no longer available';
default:
return 'An unexpected error occurred';
}
}
static async retryOperation(operation, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, delay));
delay *= 2; // Exponential backoff
}
}
}
}
// Usage example:
const createStreamWithRetry = async (params) => {
try {
return await VideoSDKErrorHandler.retryOperation(
() => StreamRepository.createStream(params)
);
} catch (error) {
const message = VideoSDKErrorHandler.handleStreamError(error);
Alert.alert('Error', message);
}
};
Performance Optimization
Best Practices
// Performance optimization tips
// 1. Optimize video quality based on device capabilities
const getOptimalVideoConfig = () => {
const { width, height } = Dimensions.get('window');
const isHighEnd = DeviceInfo.getTotalMemory() > 4000000000; // 4GB+ RAM
if (isHighEnd && width > 1080) {
return {
resolution: { width: 1920, height: 1080 },
bitrate: 4000000,
frameRate: 30
};
} else if (width > 720) {
return {
resolution: { width: 1280, height: 720 },
bitrate: 2000000,
frameRate: 30
};
} else {
return {
resolution: { width: 854, height: 480 },
bitrate: 1000000,
frameRate: 24
};
}
};
// 2. Memory management for video components
const VideoComponent = React.memo(({ streamId }) => {
useEffect(() => {
return () => {
// Cleanup video resources when component unmounts
if (playerRef.current) {
playerRef.current.cleanup?.();
}
};
}, []);
// Component implementation...
});
// 3. Optimize stream loading
const useStreamLoader = (streamId) => {
const [stream, setStream] = useState(null);
const [loading, setLoading] = useState(false);
const cache = useRef(new Map());
const loadStream = useCallback(async (id) => {
// Check cache first
if (cache.current.has(id)) {
setStream(cache.current.get(id));
return;
}
setLoading(true);
try {
const streamData = await StreamRepository.getStreamById(id);
cache.current.set(id, streamData);
setStream(streamData);
} catch (error) {
console.error('Failed to load stream:', error);
} finally {
setLoading(false);
}
}, []);
return { stream, loading, loadStream };
};
Testing
Unit Testing Example
// __tests__/VideoSDK.test.ts
import { StreamRepository } from '@amityco/ts-sdk-react-native';
import { VideoSDKErrorHandler } from '../src/utils/VideoSDKErrorHandler';
// Mock the SDK
jest.mock('@amityco/ts-sdk-react-native');
describe('Video SDK Integration', () => {
beforeEach(() => {
jest.clearAllMocks();
});
test('should create stream successfully', async () => {
const mockStream = {
streamId: 'test-stream-id',
title: 'Test Stream',
status: 'idle'
};
StreamRepository.createStream.mockResolvedValue({ data: mockStream });
const params = {
title: 'Test Stream',
description: 'Test Description'
};
const result = await StreamRepository.createStream(params);
expect(result.data).toEqual(mockStream);
expect(StreamRepository.createStream).toHaveBeenCalledWith(params);
});
test('should handle stream creation error', async () => {
const mockError = new Error('Network error');
StreamRepository.createStream.mockRejectedValue(mockError);
const errorMessage = VideoSDKErrorHandler.handleStreamError(mockError);
expect(errorMessage).toBeDefined();
});
});
Integration Testing
// __tests__/VideoPlayerIntegration.test.tsx
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import VideoPlayerScreen from '../src/screens/VideoPlayerScreen';
describe('Video Player Integration', () => {
test('should load and play stream', async () => {
const mockStreamId = 'test-stream-id';
const { getByText, getByTestId } = render(
<VideoPlayerScreen streamId={mockStreamId} />
);
// Wait for stream to load
await waitFor(() => {
expect(getByText('Test Stream')).toBeTruthy();
});
// Test play button
const playButton = getByText('Play');
fireEvent.press(playButton);
await waitFor(() => {
expect(getByText('Pause')).toBeTruthy();
});
});
});
Advanced Features
Custom UI Components
// Custom broadcaster component with advanced features
const CustomBroadcaster = ({ onStreamStart, onStreamEnd }) => {
const [filters, setFilters] = useState([]);
const [overlays, setOverlays] = useState([]);
const addFilter = (filterType) => {
setFilters(prev => [...prev, filterType]);
};
const addOverlay = (overlayData) => {
setOverlays(prev => [...prev, overlayData]);
};
return (
<View style={{ flex: 1 }}>
<AmityVideoBroadcaster
ref={broadcasterRef}
style={{ flex: 1 }}
filters={filters}
overlays={overlays}
onStreamStart={onStreamStart}
onStreamEnd={onStreamEnd}
/>
{/* Custom filter controls */}
<FilterControls onFilterChange={addFilter} />
{/* Custom overlay controls */}
<OverlayControls onOverlayAdd={addOverlay} />
</View>
);
};
Troubleshooting
For detailed troubleshooting guides, see:Common React Native Issues
- Metro bundler conflicts: Clear cache with
npx react-native start --reset-cache - iOS build failures: Run
cd ios && pod deintegrate && pod install - Android permission issues: Ensure all required permissions are declared in AndroidManifest.xml
- Video playback issues: Check device compatibility and network connectivity
Next Steps
- Explore Core Concepts for advanced SDK usage
- Learn about Broadcasting Features
- Implement Real-time Notifications
- Review API Reference for complete SDK documentation