Documentation Index
Fetch the complete documentation index at: https://learn.social.plus/llms.txt
Use this file to discover all available pages before exploring further.
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