React Native Integration Guide
Integration guide for the StreamLayer SDK in bare React Native projects using the old architecture (RN 0.72+). Covers installation, native iOS and Android configuration, THEOplayer setup, SDK initialization, and the StreamLayerView component.
React Native Integration Guide (Old Architecture)
Note: This documentation is for the old React Native architecture. For the new architecture, see React Native New Architecture Integration Guide.
This documentation is for the old architecture of React Native.
Background
The React Native SDK should be integrated if the client host app is built using React. Please ensure you are familiar with the Architecture Overview before proceeding with this integration.
Prerequisites
System Requirements
iOS Requirements
- Xcode 15.3
- Swift 5.9
- iOS 15+
Android Requirements
- Android Studio 4.0+
- Kotlin 1.7+ or Java 8+
- Minimum SDK version 21
Obtain an SDK API Key
You will need an SDK API Key to integrate StreamLayer into your application. If you do not already have one, please follow the instructions below.
Provide us with your email address to create a new organization for you on our platform, including setting up a personal dashboard for your account. You will then receive an invitation email with a link to the StreamLayer Studio and your authentication credentials. After gaining access to the StreamLayer Studio, you can generate an API key in the development section of the StreamLayer Studio. This API key authenticates your application with our servers and is required for all interactions.
Integration Example
You can find a complete StreamLayer SDK integration example in our repo.
Or you can follow our video guide:
Installation
Install the necessary packages with the following command:
npm
npm install react-native-streamlayer react-native-safe-area-contextyarn
yarn add react-native-streamlayer react-native-safe-area-contextNative iOS Configuration
Update Cocoapods Dependencies
Navigate to the ios directory and install the dependencies:
cd ios && pod install && cd ..Upgrade minimum Ios Target
Update the minimum iOS version in the Podfile.
platform :ios, '15.0'Install TheoPlayer
Install packages
npm
npm install [email protected] [email protected] @theoplayer/[email protected] @react-native-community/[email protected] [email protected] yarn
yarn add [email protected] [email protected] @theoplayer/[email protected] @react-native-community/[email protected] [email protected] Navigate to the ios/ directory and install the dependencies:
cd ios/ && pod install && cd ../Add dependencies
Add dependencies inandroid/app/build.gradle
dependencies {
...your dependencies
implementation 'com.google.android.gms:play-services-cast-framework:21.4.0'
implementation "com.google.android.gms:play-services-ads-identifier:18.0.1"
}Add metadata
Add metadata to AndroidManifest.xml
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" android:value="com.googlecastoptionprovider.CastOptionsProvider" />Android Configuration
Create CastOptionsProvider.kt
CastOptionsProvider.ktCreate a file named CastOptionsProvider.kt in android/app/src/main/java/com/sl/expo/app/ with the following content:
package com.googlecastoptionprovider
import android.content.Context
import com.google.android.gms.cast.framework.CastOptions
import com.google.android.gms.cast.framework.OptionsProvider
import com.google.android.gms.cast.framework.SessionProvider
import com.google.android.gms.cast.framework.CastOptions.Builder
class CastOptionsProvider : OptionsProvider {
override fun getCastOptions(context: Context): CastOptions {
return Builder()
.setReceiverApplicationId("CC1AD845")
.build()
}
override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
return null
}
}StreamLayer SDK Setup for React Native
Add API Keys to .env
.env- Install
react-native-config:
npm install react-native-configyarn add react-native-config- In the
iosdirectory, run:
pod install- Add the following line to
android/app/build.gradle:
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"- Create a
.envfile in your project’s root directory and add your API key:
SL_SDK_API_KEY="your_api_key_here"Implement <SafeAreaProvider> at the top of your app.
- For example I gonna implement this at index.js/index.tsx
import { AppRegistry } from 'react-native';
import App from './App';
import { name as appName } from './app.json';
import { SafeAreaProvider } from 'react-native-safe-area-context';
const AppWithProvider = () => (
<SafeAreaProvider>
<App />
</SafeAreaProvider>
);
AppRegistry.registerComponent(appName, () => AppWithProvider);
Add imports
import React, { useState, useEffect, useRef, ReactElement } from 'react';
import {
StyleSheet,
View,
ScrollView,
Dimensions,
Text,
Image,
Pressable,
} from 'react-native';
import {
StreamLayer,
StreamLayerView,
StreamLayerViewPlayer,
StreamLayerViewOverlayLandscapeMode,
StreamLayerViewConfiguration,
StreamLayerViewNotificationFeature,
StreamLayerDemoEvent,
StreamLayerTheme
} from 'react-native-streamlayer';
import {
PlayerConfiguration,
SourceDescription,
PlayerEventType,
THEOplayer,
THEOplayerView
} from 'react-native-theoplayer';
import { UiContainer,
CenteredControlBar,
SkipButton,
PlayButton,
DEFAULT_THEOPLAYER_THEME
} from '@theoplayer/react-native-ui';
import {
SafeAreaView,
useSafeAreaInsets,
} from 'react-native-safe-area-context';
Add variables
class LBarState {
slideX: number;
slideY: number;
constructor(slideX: number, slideY: number) {
this.slideX = slideX
this.slideY = slideY
}
}
const playerConfig: PlayerConfiguration = {
license: undefined,
};
const source: SourceDescription = {
sources: [
{
src: "https://cdn.theoplayer.com/video/elephants-dream/playlist-single-audio.m3u8",
type: "application/x-mpegurl"
},
],
};
const insets = useSafeAreaInsets()
const playerHeight = isScreenPortrait() ? 300 : Dimensions.get('screen').height;
const streamLayerViewPlayer: StreamLayerViewPlayer = {
get volume() {
return 0.0
},
set volume(value) {
},
}
const viewConfig = getViewConfig()
const [volumeBeforeDucking, setVolumeBeforeDucking] = useState<number | undefined>(undefined)
const [isPortrait, setPortrait] = useState<boolean>();
const [player, setPlayer] = useState<THEOplayer | undefined>(undefined);
const [lbarState, setLbarState] = useState(new LBarState(0, 0));
const [events, setEvents] = useState<Array<StreamLayerDemoEvent>>()
const [currentEventId, setCurrentEventId] = useState<String>()
const [isInitialized, setInitialized] = useState(false);
const viewRef = useRef<StreamLayerView>(null);
var scrollItems = new Array<ReactElement>();
if (events !== undefined) {
events.forEach((event) => {
scrollItems.push(
<Pressable key={event.id} onPress={() => createEventSession(event.id)}>
<View style={styles.eventRow}>
{event.previewUrl !== undefined && (
<Image source={{ uri: event.previewUrl }}
style={styles.eventRowImage} />
)}
<Text style={styles.eventRowTitle} numberOfLines={1} ellipsizeMode='tail'>{event.title}</Text>
</View>
</Pressable>
)
})
}
var currentEvent: StreamLayerDemoEvent | undefined;
if (events !== undefined && currentEventId !== undefined) {
currentEvent = events.find((event) => {
return event.id == currentEventId
})
}
Add Functions
useEffect(() => {
setPortrait(isScreenPortrait())
const initialize = async () => {
try {
checkInitialized();
} catch (error) {
console.error("Error initializing:", error);
}
};
initialize();
const subscription = Dimensions.addEventListener('change', ({ window, screen }) => {
setPortrait(window.height > window.width)
});
return () => subscription?.remove()
}, []);
function getViewConfig(): StreamLayerViewConfiguration {
return {
viewNotificationFeatures: new Array(
StreamLayerViewNotificationFeature.Games,
StreamLayerViewNotificationFeature.Chat,
StreamLayerViewNotificationFeature.WatchParty,
StreamLayerViewNotificationFeature.Twitter
),
isGamesPointsEnabled: true,
isGamesPointsStartSide: false,
isLaunchButtonEnabled: true,
isMenuAlwaysOpened: false,
isMenuLabelsVisible: true,
isMenuProfileEnabled: true,
isTooltipsEnabled: true,
isWatchPartyReturnButtonEnabled: true,
isWhoIsWatchingViewEnabled: true,
isOverlayExpandable: true,
overlayHeightSpace: 300,
overlayWidth: 0,
overlayLandscapeMode: StreamLayerViewOverlayLandscapeMode.Start
}
}
function isScreenPortrait(): boolean {
return Dimensions.get('window').height > Dimensions.get('window').width
}
const onReady = (player: THEOplayer) => {
setPlayer(player);
player.autoplay = true
player.source = source;
player.addEventListener(PlayerEventType.ERROR, console.log);
}
const checkInitialized = async () => {
try {
await StreamLayer.initSdk({
isLoggingEnabled: true,
theme: StreamLayerTheme.Green,
sdkKey: "YOUR SDK KEY",
},false)
checkAuth()
loadDemoEvents()
const inited = await StreamLayer.isInitialized()
setInitialized(inited)
} catch (e) {
console.error(e);
}
}
const loadDemoEvents = async () => {
try {
const events = await StreamLayer.getDemoEvents("2022-01-01")
setEvents(events)
if (events !== undefined && events !== null && events.length > 0) {
createEventSession(events[0].id)
}
} catch (e) {
console.error("PlayerScreen loadDemoEvents error", e);
}
}
const checkAuth = async () => {
try {
const isUserAuthorized = await StreamLayer.isUserAuthorized()
if (!isUserAuthorized) {
await StreamLayer.useAnonymousAuth();
}
} catch (e) {
console.error(e);
}
}
const createEventSession = async (id: string) => {
try {
await StreamLayer.createEventSession(id);
console.log(`Created a new event with id ${id}`);
setCurrentEventId(id)
} catch (e) {
console.error(e);
}
};Add functions for view event listener
const onRequestStream = (id: string) => {
console.log("onRequestStream id=" + id)
createEventSession(id)
}
const onLBarStateChanged = (slideX: number, slideY: number) => {
console.log("onLBarStateChanged slideX=" + slideX + " slideY=" + slideY)
setLbarState(new LBarState(slideX, slideY));
}
const onRequestAudioDucking = (level: number) => {
console.log("onRequestAudioDucking level=" + level)
}
const onDisableAudioDucking = () => {
console.log("onDisableAudioDucking")
}Add Views for StreamLayer and StreamLayer Events
return (
<SafeAreaView style={{...styles.container, marginTop: insets.top }} edges={['top']}>
{(isPortrait) &&
<View style={{ flex: 1, marginTop: playerHeight - insets.top }}>
{currentEvent !== undefined && (
<Text style={styles.eventTitle}>{currentEvent.title}</Text>
)}
<ScrollView style={{ flex: 1 }}>
{scrollItems}
</ScrollView>
</View>
}
{isInitialized &&
<StreamLayerView
style={StyleSheet.absoluteFillObject}
ref={viewRef}
config={viewConfig}
applyWindowInsets={false}
onRequestStream={onRequestStream}
onLBarStateChanged={onLBarStateChanged}
onRequestAudioDucking={onRequestAudioDucking}
onDisableAudioDucking={onDisableAudioDucking}
player={streamLayerViewPlayer}
playerView={
<THEOplayerView config={playerConfig} onPlayerReady={onReady}
style={{
width: Dimensions.get('screen').width - lbarState.slideX,
height: playerHeight-lbarState.slideY,
paddingTop: 0,
}}>
{player !== undefined && (
<UiContainer
theme={DEFAULT_THEOPLAYER_THEME}
player={player}
center={
<CenteredControlBar
left={<SkipButton skip={-10} />}
middle={<PlayButton />}
right={<SkipButton skip={10} />}
/>
}
/>
)}
</THEOplayerView>
}
/>
}
</SafeAreaView>
)
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'gray',
},
eventTitle: {
color: 'white',
fontSize: 24,
margin: 4
},
eventRowTitle: {
flex: 1,
color: 'white',
fontSize: 16,
margin: 4
},
eventRow: {
flexDirection: 'row',
margin: 4,
padding: 4,
height: 58,
justifyContent: 'flex-start',
alignItems: 'center',
borderWidth: 1,
borderColor: 'black',
},
eventRowImage: {
width: 100,
height: 50
},
overlay: {
paddingTop: 500,
backgroundColor: '#00000000',
flex: 1,
}
});
Updated 2 months ago
