Bitmovin Player React Native
Official React Native bindings for Bitmovin's mobile Player SDKs.
![Bitmovin Community](https://img.shields.io/discourse/users?label=community&server=https%3A%2F%2Fcommunity.bitmovin.com)
As the library is under active development, this means certain features from our native SDKs are not yet exposed through these React Native bindings.
See Feature Support for an overview of the supported features.
Not seeing the features you’re looking for?
We are accepting community pull requests to this open-source project so please feel free to contribute.
or let us know in our community what features we should work on next.
Platform Support
This library requires at least React Native 0.64+ and React 17+ to work properly. The currently supported platforms are:
- iOS 12.0+
- tvOS 12.0+
- Android API 16+
- Android TV API 17+
- Fire TV (just make sure the Android API level is at least 17+)
Please note that browsers and other browser-like environments such as webOS and Tizen are not supported.
Feature Support
Features of the native mobile Player SDKs are progressively being implemented in this React Native library. The table below summarizes the current state of the main Player SDK features.
Feature | State |
---|
Playback of DRM-protected assets | :white_check_mark: Available since v0.2.0 |
Subtitles & Captions | :white_check_mark: Available since v0.2.0 |
Advertising | :gear: In progress, Q4 2022 |
Playlist API | :x: Not available |
Offline Playback | :x: Not available |
Analytics | :x: Coming Q1 2023 |
Installation
Since Bitmovin's native SDKs are distributed through custom Cocoapods and Maven repositories, the installation cannot be managed by React Native's Autolink and requires some extra steps. Please refer to the installation instructions for each platform below. For more information on integrating the native SDKs, refer to the Getting Started guides.
Add package dependency
This library is available as an NPM package and may be added as a dependency to your project using any node-based package manager, e.g.
npm
npm install bitmovin-player-react-native --save
yarn
yarn add bitmovin-player-react-native
Setup iOS Player SDK
If you ran pod install
after installing the node package and received an error similar to the one below, it is because Bitmovin's custom cocoapods repository has not been added to the Podfile
and the iOS Player SDK
could not be resolved:
[!] Unable to find a specification for `BitmovinPlayer (= 3.xx.x)` depended upon by `RNBitmovinPlayer`
You have either:
* out-of-date source repos which you can update with `pod repo update` or with `pod install --repo-update`.
* mistyped the name or version.
* not added the source repo that hosts the Podspec to your Podfile.
To fix above error, open your ios/Podfile
and set up Bitmovin's pods source url:
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
source 'https://github.com/bitmovin/cocoapod-specs.git'
platform :ios, '12.4'
install! 'cocoapods', :deterministic_uuids => false
target 'MyApp' do
config = use_native_modules!
Now run pod install
again (try with --repo-update
if the error persists) - the error should now be resolved.
Setup Android Player SDK
The Android setup also needs an extra step in order to correctly resolve the Android Player SDK native dependency.
Just make sure to add Bitmovin's artifacts repository to the allprojects.repositories
section of your android/build.gradle
:
allprojects {
repositories {
maven { url("$rootDir/../node_modules/react-native/android") }
maven { url("$rootDir/../node_modules/jsc-android/dist") }
mavenCentral {
content {
excludeGroup "com.facebook.react"
}
}
google()
maven { url 'https://www.jitpack.io' }
// Add Bitmovin's artifacts repository url
maven { url 'https://artifacts.bitmovin.com/artifactory/public-releases' }
}
}
Getting Started
The following is the simplest working component one can create using this library:
import React, { useEffect, useCallback } from 'react';
import { View, Platform, StyleSheet } from 'react-native';
import {
usePlayer,
SourceType,
PlayerView,
} from 'bitmovin-player-react-native';
export default function PlayerSample() {
const player = usePlayer({
licenseKey: '<ENTER-YOUR-LICENSE-KEY>',
});
useEffect(() => {
player.load({
url:
Platform.OS === 'ios'
?
'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/m3u8s/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8'
:
'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/mpds/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.mpd',
type: Platform.OS === 'ios' ? SourceType.HLS : SourceType.DASH,
title: 'Art of Motion',
poster:
'https://bitmovin-a.akamaihd.net/content/MI201109210084_1/poster.jpg',
isPosterPersistent: false,
});
}, [player]);
const onReady = useCallback(
(event) => {
player.play();
console.log(event.timestamp);
},
[player]
);
return (
<View style={styles.flex1}>
<PlayerView style={styles.flex1} player={player} onReady={onReady} />
</View>
);
}
const styles = StyleSheet.create({
flex1: {
flex: 1,
},
});
If you're interested in a complete running example, head to example/
.
Setting up a license key
First of all, create a license key on the Dashboard and then make sure to associate your iOS app bundle id with it (see more here).
Then your license key can be either set from code or by configuring Info.plist
and AndroidManifest.xml
.
Through code
import { usePlayer } from 'bitmovin-player-react-native';
const player = usePlayer({
licenseKey: '<ENTER-YOUR-LICENSE-KEY>',
});
import { Player } from 'bitmovin-player-react-native';
const player = new Player({
licenseKey: '<ENTER-YOUR-LICENSE-KEY>',
});
Through Info.plist
Add the following lines to the <dict>
section of your ios/Info.plist
:
<key>BitmovinPlayerLicenseKey</key>
<string>ENTER-YOUR-LICENSE-KEY</string>
Through AndroidManifest.xml
Add the following line to the <application>
section of your android/app/src/main/AndroidManifest.xml
:
<meta-data android:name="BITMOVIN_PLAYER_LICENSE_KEY" android:value="ENTER-YOUR-LICENSE-KEY" />
Setting up the playback configuration
If needed, the default player behavior can be configured through the playbackConfig
key when initialized.
import { usePlayer } from 'bitmovin-player-react-native';
const player = usePlayer({
playbackConfig: {
isAutoplayEnabled: true,
isMuted: true,
isTimeShiftEnabled: true,
isBackgroundPlaybackEnabled: true,
isPictureInPictureEnabled: true,
},
});
import { Player } from 'bitmovin-player-react-native';
const player = new Player({
playbackConfig: {
isAutoplayEnabled: true,
isMuted: true,
isTimeShiftEnabled: true,
isBackgroundPlaybackEnabled: true,
isPictureInPictureEnabled: true,
},
});
Accessing native Player
instances
When you instantiate a player with usePlayer
or new Player()
from javascript, you're actually either creating a new Player
instance in the native side (see SDKs docs for more info) or referencing an existing one.
So it means that a player with the same nativeId
in two different parts of the code is referencing the same in-memory instance internally.
Example
Both components in the example below are referencing the same native Player
indexed as my-player
. And even though each <PlayerView />
creates a different View
internally, the Player
instance (which is a separate thing) remains the same. It just gets attached to a different view.
export const CompA = () => {
const player = usePlayer({
nativeId: 'my-player',
});
return <PlayerView player={player} />;
};
export const CompB = () => {
const player = useRef(
new Player({
nativeId: 'my-player',
})
);
return <PlayerView player={player.current} />;
};
Listening to events
Both player and source events can be registered from PlayerView
, but not all of them. For a complete list of the events currently available, checkout EventProps
and events.ts
.
To register an event callback, just pass its name prefixed with on
as a PlayerView
prop:
return (
<PlayerView
onReady={onReady}
onMuted={onMuted}
onPaused={onPaused}
onPlayerActive={onPlayerActive}
onSourceLoaded={onSourceLoaded}
onPlayerError={onPlayerError}
onSourceError={onSourceError}
onPlaybackFinished={onPlaybackFinished}
{...}
/>
);
Enabling DRM protection
⚠️ Beta Version: For now, only FairPlay
is supported on iOS and
only Widevine
is supported on Android. More DRM systems will be added in the future.
Simple streaming of protected assets can be enabled with just a little configuration on SourceConfig.drmConfig
:
import { Platform } from 'react-native';
import { SourceConfig, SourceType } from 'bitmovin-player-react-native';
const drmSource: SourceConfig = {
url:
Platform.OS === 'ios'
? 'https://fps.ezdrm.com/demo/video/ezdrm.m3u8'
: 'https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/mpds/11331.mpd',
type: Platform.OS === 'ios' ? SourceType.HLS : SourceType.DASH,
drmConfig: {
widevine: {
licenseUrl: 'https://cwip-shaka-proxy.appspot.com/no_auth',
},
fairplay: {
licenseUrl:
'https://fps.ezdrm.com/api/licenses/09cc0377-6dd4-40cb-b09d-b582236e70fe',
certificateUrl: 'https://fps.ezdrm.com/demo/video/eleisure.cer',
},
},
};
Prepare hooks
In the native SDKs, some DRM properties like message
and license
can have their value transformed before use in order
to enable some more complex use cases: such as extracting the license
from a JSON
, for example.
In order to handle such transformations, it's possible to hook methods onto SourceConfig.drmConfig
to proxy DRM values
and potentially alter them:
import { Platform } from 'react-native';
import { SourceConfig, SourceType } from 'bitmovin-player-react-native';
const drmSource: SourceConfig = {
url:
Platform.OS === 'ios'
? 'https://fps.ezdrm.com/demo/video/ezdrm.m3u8'
: 'https://bitmovin-a.akamaihd.net/content/art-of-motion_drm/mpds/11331.mpd',
type: Platform.OS === 'ios' ? SourceType.HLS : SourceType.DASH,
drmConfig: {
widevine: {
licenseUrl: 'https://cwip-shaka-proxy.appspot.com/no_auth',
prepareLicense: (license: string) => {
return license;
},
},
fairplay: {
licenseUrl:
'https://fps.ezdrm.com/api/licenses/09cc0377-6dd4-40cb-b09d-b582236e70fe',
certificateUrl: 'https://fps.ezdrm.com/demo/video/eleisure.cer',
prepareLicense: (license: string) => {
return license;
},
prepareMessage: (message: string, assetId: string) => {
return message;
},
},
},
};
The FairplayConfig
interface provides a bunch of hooks that can be used to fetch and transform different DRM related data. Check out the docs for a complete list and detailed information on them.
Also, don't forget to check out the example app for a complete iOS/Android DRM example.
Adding external subtitle tracks
Usually, subtitle tracks are provided in the manifest of your content (see Enconding Manifests API for more information). And if they are provided this way, the player already recognizes them and show them in the subtitles selection menu without any further configuration.
Otherwise, it's also possible to add external tracks via the subtitle API:
import { Platform } from 'react-native';
import {
SourceConfig,
SourceType,
SubtitleFormat,
} from 'bitmovin-player-react-native';
const config: SourceConfig = {
url:
Platform.OS === 'ios'
? 'https://bitmovin-a.akamaihd.net/content/sintel/hls/playlist.m3u8'
: 'https://bitmovin-a.akamaihd.net/content/sintel/sintel.mpd',
type: Platform.OS === 'ios' ? SourceType.HLS : SourceType.DASH,
poster: 'https://bitmovin-a.akamaihd.net/content/sintel/poster.png',
subtitleTracks: [
{
url: 'https://bitdash-a.akamaihd.net/content/sintel/subtitles/subtitles_en.vtt',
format: SubtitleFormat.VTT,
label: 'Custom English',
language: 'en',
identifier: 'sub1',
isDefault: false,
isForced: false,
},
],
};
The supported PlayerView
events for subtitles are:
onSubtitleAdded
onSubtitleRemoved
onSubtitleChanged
You might check out a complete subtitle example in the example/
app.
Enabling Picture in Picture mode
In order to make use of the Picture in Picture functionalities provided by the player, it's first necessary to configure your native application to properly support PiP.
The steps required for each platform are described below:
Android
Declare Picture in Picture support on AndroidManifest.xml
Open android/app/src/main/AndroidManifest.xml
and set android:supportsPictureInPicture
to true
on your main activity's manifest. Also, specify that your activity handles layout configuration changes
so that your activity doesn't relaunch when layout changes occur during PiP mode transitions:
<activity android:name=".MainActivity"
android:supportsPictureInPicture="true"
android:configChanges=
"screenSize|smallestScreenSize|screenLayout|orientation"
...
iOS
Set background modes capability
Make sure to add the UIBackgroundModes
key to the dict
section of your Info.plist
:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
This step can also be performed from Xcode.
Configure audio session on app startup
Configure your app's AudioSession
category to playback
during the main component's initialization:
import { AudioSession } from 'bitmovin-player-react-native';
const App = () => {
useEffect(() => {
AudioSession.setCategory('playback').catch((error) => {
handleError(error);
});
});
return ;
};
This step is required in order to properly enable background playback on iOS. Without it, the Picture in Picture option appears on the player UI but has no effect when used.
You can read more about it on Apple's docs.
Showing the Picture in Picture UI option
Now that your native application is properly configured to support PiP changes, the player instance
in your JS code can be configured to show the Picture in Picture option in the player UI.
Simply add isPictureInPictureEnabled: true
on your player's playbackConfig
option:
const player = usePlayer({
playbackConfig: {
isPictureInPictureEnabled: true,
},
});
Supported Picture in Picture events
The supported Picture in Picture events on PlayerView
are:
onPictureInPictureEnter
onPictureInPictureExit
iOS only
onPictureInPictureEntered
onPictureInPictureExited
Android only
onPictureInPictureAvailabilityChanged
Check events.ts
for more information about them.
Setting up ads
The Bitmovin Player SDKs are capable of displaying Ads out of the box and there are two ways they can be
configured with the player. One option is to use static configuration in the player config object,
and the other is to schedule them dynamically using Player.scheduleAd
.
Static ads configuration
The easiest way to configure Ads is by adding the advertisingConfig
property to the player configuration object.
All that needs to be provided is a URL pointing to a target Ad tag along with the type of the tag.
const player = usePlayer({
licenseKey: '<PLAYER_LICENSE_KEY>',
advertisingConfig: {
schedule: [
{
sources: [
{
type: AdSourceType.IMA,
tag: 'https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dskippablelinear&correlator=',
},
],
position: '20%',
},
],
},
});
The possible AdItem
position values are:
"pre"
: pre-roll ad (for VoD and Live streaming; appears before playback starts)"post"
: post-roll ad (for VoD streaming only; appears after playback finishes)- Fractional seconds:
"10"
, "12.5"
(mid-roll ad, for VoD and Live streaming) - Percentage of the entire video duration:
"25%"
, "50%"
(mid-roll ad, for VoD streaming only) - Timecode
hh:mm:ss.mmm
: "00:10:30.000"
, "01:00:00.000"
(mid-roll ad, for VoD streaming only)
Dynamic ads scheduling
To gain more flexibility, it is also possible to schedule an AdItem
dynamically in code using the
Player
instance. To do this, you need to call the scheduleAd
method.
player.scheduleAd({
sources: [
{
tag: '<AD-URL>',
type: AdSourceType.IMA,
},
],
});
An AdScheduledEvent
event is dispatched when the ad is successfully scheduled via scheduleAd
.
Also, during playback, it's also possible to check whether an ad is being played with player.isAd()
and skip the ad being currently played with player.skipAd()
(see AdSkippedEvent
).
Supported ads events
The supported PlayerView
events for ads are:
onAdBreakFinished
onAdBreakStarted
onAdClicked
onAdError
onAdFinished
onAdManifestLoad
onAdManifestLoaded
onAdQuartile
onAdScheduled
onAdSkipped
onAdStarted
You can check out a complete ads example in the example/
app.
Contributing
See the contributing guide to learn how to contribute to the repository and the development workflow.