@jwplayer/jwplayer-react-native
Version:
React-native Android/iOS plugin for JWPlayer SDK (https://www.jwplayer.com/)
755 lines (693 loc) • 19.3 kB
JavaScript
import React, { Component } from 'react';
import {
requireNativeComponent,
NativeModules,
Platform,
findNodeHandle,
} from 'react-native';
import PropTypes from 'prop-types';
import isEqualWith from 'lodash.isequalwith';
const RNJWPlayerManager =
Platform.OS === 'ios'
? NativeModules.RNJWPlayerViewManager
: NativeModules.RNJWPlayerModule;
let playerId = 0;
const RCT_RNJWPLAYER_REF = 'RNJWPlayerKey';
const RNJWPlayer = requireNativeComponent('RNJWPlayerView');
const JWPlayerStateIOS = {
JWPlayerStateUnknown: 0,
JWPlayerStateIdle: 1,
JWPlayerStateBuffering: 2,
JWPlayerStatePlaying: 3,
JWPlayerStatePaused: 4,
JWPlayerStateComplete: 5,
JWPlayerStateError: 6,
};
const JWPlayerStateAndroid = {
JWPlayerStateIdle: 0,
JWPlayerStateBuffering: 1,
JWPlayerStatePlaying: 2,
JWPlayerStatePaused: 3,
JWPlayerStateComplete: 4,
JWPlayerStateError: null,
};
export const JWPlayerAdEvents = {
/// This event is reported when the ad break has come to an end.
JWAdEventTypeAdBreakEnd: 0,
/// This event is reported when the ad break has begun.
JWAdEventTypeAdBreakStart: 1,
/// This event is reported when the user taps the ad.
JWAdEventTypeClicked: 2,
/// This event is reported when the ad is done playing.
JWAdEventTypeComplete: 3,
/// This event is used to report the ad impression, supplying additional detailed information about the ad.
JWAdEventTypeImpression: 4,
/// This event reports meta data information associated with the ad.
JWAdEventTypeMeta: 5,
/// The event is reported when the ad pauses.
JWAdEventTypePause: 6,
/// This event is reported when the ad begins playing, even in the middle of the stream after it was paused.
JWAdEventTypePlay: 7,
/// The event reports data about the ad request, when the ad is about to be loaded.
JWAdEventTypeRequest: 8,
/// This event reports the schedule of ads across the currently playing content.
JWAdEventTypeSchedule: 9,
/// This event is reported when the user skips the ad.
JWAdEventTypeSkipped: 10,
/// This event is reported when the ad begins.
JWAdEventTypeStarted: 11,
/// This event relays information about ad companions.
JWAdEventTypeCompanion: 12,
}
export const JWPlayerState =
Platform.OS === 'ios' ? JWPlayerStateIOS : JWPlayerStateAndroid;
export const JWPlayerAdClients = {
JWAdClientJWPlayer: 0,
JWAdClientGoogleIMA: 1,
JWAdClientGoogleIMADAI: 2,
JWAdClientUnknown: 3,
};
// Common PropTypes for imaSettings and adRules
const imaSettingsPropTypes = PropTypes.shape({
locale: PropTypes.string,
ppid: PropTypes.string,
maxRedirects: PropTypes.number,
sessionID: PropTypes.string,
debugMode: PropTypes.bool,
});
const adRulesPropTypes = PropTypes.shape({
startOn: PropTypes.number,
frequency: PropTypes.number,
timeBetweenAds: PropTypes.number,
startOnSeek: PropTypes.oneOf(['none', 'pre']),
});
const adSettingsPropTypes = PropTypes.shape({
allowsBackgroundPlayback: PropTypes.bool,
// Include other ad settings properties here
});
const adSchedulePropTypes = PropTypes.arrayOf(
PropTypes.shape({
tag: PropTypes.string,
offset: PropTypes.string,
})
);
// Define PropTypes for each ad client type
const vastAdvertisingPropTypes = {
adClient: PropTypes.oneOf(['vast']),
adSchedule: adSchedulePropTypes,
adVmap: PropTypes.string,
tag: PropTypes.string,
openBrowserOnAdClick: PropTypes.bool,
adRules: adRulesPropTypes,
adSettings: adSettingsPropTypes,
// Add other VAST-specific properties here
};
const imaAdvertisingPropTypes = {
adClient: PropTypes.oneOf(['ima']),
adSchedule: adSchedulePropTypes,
adVmap: PropTypes.string,
tag: PropTypes.string,
imaSettings: imaSettingsPropTypes,
adRules: adRulesPropTypes,
// companionAdSlots: PropTypes.arrayOf(
// PropTypes.shape({
// viewId: PropTypes.string,
// size: PropTypes.shape({
// width: PropTypes.number,
// height: PropTypes.number,
// }),
// })
// ),
// friendlyObstructions: PropTypes.arrayOf(
// PropTypes.shape({
// viewId: PropTypes.string,
// purpose: PropTypes.oneOf(['mediaControls', 'closeAd', 'notVisible', 'other']),
// reason: PropTypes.string,
// })
// ),
// Add other IMA-specific properties here
};
const imaDaiAdvertisingPropTypes = {
adClient: PropTypes.oneOf(['ima_dai']),
imaSettings: imaSettingsPropTypes,
googleDAIStream: PropTypes.shape({
videoID: PropTypes.string,
cmsID: PropTypes.string,
assetKey: PropTypes.string,
apiKey: PropTypes.string,
adTagParameters: PropTypes.object,
}),
// friendlyObstructions: PropTypes.arrayOf(
// PropTypes.shape({
// viewId: PropTypes.string,
// purpose: PropTypes.oneOf(['mediaControls', 'closeAd', 'notVisible', 'other']),
// reason: PropTypes.string,
// })
// ),
// Add other IMA DAI-specific properties here
};
const advertisingPropTypes = PropTypes.oneOfType([
PropTypes.shape(vastAdvertisingPropTypes),
PropTypes.shape(imaAdvertisingPropTypes),
PropTypes.shape(imaDaiAdvertisingPropTypes),
]);
export default class JWPlayer extends Component {
// [TODO] -- Match the JwConfig type from index.d.ts as oneOfType
static propTypes = {
config: PropTypes.shape({
license: PropTypes.string.isRequired,
forceLegacyConfig: PropTypes.bool,
/**
* Only enable if you are prepared to implement `onBeforeNextPlaylistItem` on the JS side
* And resolve the promise with `resolveNextPlaylistItem` inside the callback
* Otherwise, the player will not proceed to the next item
*/
playlistItemCallbackEnabled: PropTypes.bool,
backgroundAudioEnabled: PropTypes.bool,
category: PropTypes.oneOf([
'Ambient',
'SoloAmbient',
'Playback',
'Record',
'PlayAndRecord',
'MultiRoute',
]),
categoryOptions: PropTypes.arrayOf(
PropTypes.oneOf([
'MixWithOthers',
'DuckOthers',
'AllowBluetooth',
'DefaultToSpeaker',
'InterruptSpokenAudioAndMix',
'AllowBluetoothA2DP',
'AllowAirPlay',
'OverrideMutedMicrophone',
])
),
mode: PropTypes.oneOf([
'Default',
'VoiceChat',
'VideoChat',
'GameChat',
'VideoRecording',
'Measurement',
'MoviePlayback',
'SpokenAudio',
'VoicePrompt',
]),
pipEnabled: PropTypes.bool,
viewOnly: PropTypes.bool,
autostart: PropTypes.bool,
controls: PropTypes.bool,
repeat: PropTypes.bool,
preload: PropTypes.oneOf(['auto', 'none']),
playlist: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(
PropTypes.shape({
file: PropTypes.string,
sources: PropTypes.arrayOf(
PropTypes.shape({
file: PropTypes.string,
label: PropTypes.string,
default: PropTypes.bool,
})
),
image: PropTypes.string,
title: PropTypes.string,
desc: PropTypes.string,
mediaId: PropTypes.string,
autostart: PropTypes.bool,
recommendations: PropTypes.string,
tracks: PropTypes.arrayOf(
PropTypes.shape({
file: PropTypes.string,
label: PropTypes.string,
kind: PropTypes.oneOf(['captions', 'thumbnails', 'chapters']),
default: PropTypes.bool
})
),
adSchedule: PropTypes.arrayOf(
PropTypes.shape({
tag: PropTypes.string,
offset: PropTypes.string,
})
),
adVmap: PropTypes.string,
startTime: PropTypes.number,
})
)]),
advertising: advertisingPropTypes,
// controller only
interfaceBehavior: PropTypes.oneOf([
'normal',
'hidden',
'onscreen',
]),
// All `Styling` is only intended to be used with iOS. Android requires overloading
// of the JWP IDs seen here: https://docs.jwplayer.com/players/docs/android-styling-guide
styling: PropTypes.shape({
colors: PropTypes.shape({
buttons: PropTypes.string,
backgroundColor: PropTypes.string,
fontColor: PropTypes.string,
timeslider: PropTypes.shape({
thumb: PropTypes.string,
rail: PropTypes.string,
slider: PropTypes.string,
}),
font: PropTypes.shape({
name: PropTypes.string,
size: PropTypes.number,
}),
captionsStyle: PropTypes.shape({
font: PropTypes.shape({
name: PropTypes.string,
size: PropTypes.number,
}),
backgroundColor: PropTypes.string,
fontColor: PropTypes.string,
highlightColor: PropTypes.string,
edgeStyle: PropTypes.oneOf([
'none',
'dropshadow',
'raised',
'depressed',
'uniform',
]),
}),
menuStyle: PropTypes.shape({
font: PropTypes.shape({
name: PropTypes.string,
size: PropTypes.number,
}),
backgroundColor: PropTypes.string,
fontColor: PropTypes.string,
}),
}),
showTitle: PropTypes.bool,
showDesc: PropTypes.bool,
}),
nextUpStyle: PropTypes.shape({
offsetSeconds: PropTypes.number,
offsetPercentage: PropTypes.number,
}),
offlineMessage: PropTypes.string,
offlineImage: PropTypes.string,
forceFullScreenOnLandscape: PropTypes.bool,
forceLandscapeOnFullScreen: PropTypes.bool,
playerInModal: PropTypes.bool,
enableLockScreenControls: PropTypes.bool,
stretching: PropTypes.oneOf([
'uniform',
'exactFit',
'fill',
'none',
]),
processSpcUrl: PropTypes.string,
fairplayCertUrl: PropTypes.string,
contentUUID: PropTypes.string,
}),
onPlayerReady: PropTypes.func,
onPlaylist: PropTypes.func,
onLoaded: PropTypes.func,
changePlaylist: PropTypes.func,
play: PropTypes.func,
pause: PropTypes.func,
setVolume: PropTypes.func,
toggleSpeed: PropTypes.func,
setSpeed: PropTypes.func,
setCurrentQuality: PropTypes.func,
currentQuality: PropTypes.func,
getQualityLevels: PropTypes.func,
setPlaylistIndex: PropTypes.func,
setControls: PropTypes.func,
setVisibility: PropTypes.func,
setLockScreenControls: PropTypes.func,
setFullscreen: PropTypes.func,
setUpCastController: PropTypes.func,
presentCastDialog: PropTypes.func,
connectedDevice: PropTypes.func,
availableDevices: PropTypes.func,
castState: PropTypes.func,
seekTo: PropTypes.func,
loadPlaylist: PropTypes.func,
onBeforePlay: PropTypes.func,
onBeforeComplete: PropTypes.func,
onPlay: PropTypes.func,
onPause: PropTypes.func,
onSetupPlayerError: PropTypes.func,
onPlayerError: PropTypes.func,
onPlayerWarning: PropTypes.func,
onPlayerAdError: PropTypes.func,
onPlayerAdWarning: PropTypes.func,
onAdEvent: PropTypes.func,
onAdTime: PropTypes.func,
onBuffer: PropTypes.func,
onTime: PropTypes.func,
onComplete: PropTypes.func,
onFullScreenRequested: PropTypes.func,
onFullScreen: PropTypes.func,
onFullScreenExitRequested: PropTypes.func,
onFullScreenExit: PropTypes.func,
onSeek: PropTypes.func,
onSeeked: PropTypes.func,
onRateChanged: PropTypes.func,
onPlaylistItem: PropTypes.func,
onControlBarVisible: PropTypes.func,
onPlaylistComplete: PropTypes.func,
getAudioTracks: PropTypes.func,
getCurrentAudioTrack: PropTypes.func,
setCurrentAudioTrack: PropTypes.func,
setCurrentCaptions: PropTypes.func,
getCurrentCaptions: PropTypes.func,
onCaptionsChanged: PropTypes.func,
onCaptionsList: PropTypes.func,
onAudioTracks: PropTypes.func,
/**
* Callback that is fired when the player is about to play the next playlist item.
* Indented to be paired with `playlistItemCallbackEnabled` prop
*
* Android will only fire after index 0 (starting at index 1)
*
* iOS will fire all playlist items (including index 0)
*/
onBeforeNextPlaylistItem: PropTypes.func,
resolveNextPlaylistItem: PropTypes.func
};
constructor(props) {
super(props);
this._playerId = playerId++;
this.ref_key = `${RCT_RNJWPLAYER_REF}-${this._playerId}`;
}
shouldComponentUpdate(nextProps, nextState) {
var { shouldComponentUpdate } = this.props;
if (shouldComponentUpdate) {
return shouldComponentUpdate(nextProps, nextState);
}
var { config, controls } = nextProps;
var thisConfig = this.props.config || {};
var result = !isEqualWith(
config,
thisConfig,
(value1, value2, key) => {
return key === 'startTime' ? true : undefined;
}
);
return result || controls !== this.props.controls;
}
componentWillUnmount() {
this.pause();
this.stop();
}
quite() {
if (RNJWPlayerManager && Platform.OS === 'ios')
RNJWPlayerManager.quite();
}
pause() {
if (RNJWPlayerManager)
RNJWPlayerManager.pause(this.getRNJWPlayerBridgeHandle());
}
changePlaylist(fileUrl) {
if (RNJWPlayerManager)
RNJWPlayerManager.changePlaylist(
this.getRNJWPlayerBridgeHandle(),
fileUrl
);
}
play() {
if (RNJWPlayerManager)
RNJWPlayerManager.play(this.getRNJWPlayerBridgeHandle());
}
stop() {
if (RNJWPlayerManager)
RNJWPlayerManager.stop(this.getRNJWPlayerBridgeHandle());
}
toggleSpeed() {
if (RNJWPlayerManager)
RNJWPlayerManager.toggleSpeed(this.getRNJWPlayerBridgeHandle());
}
setSpeed(speed) {
if (RNJWPlayerManager)
RNJWPlayerManager.setSpeed(this.getRNJWPlayerBridgeHandle(), speed);
}
setCurrentQuality(index) {
if (RNJWPlayerManager && Platform.OS === "android")
RNJWPlayerManager.setCurrentQuality(
this.getRNJWPlayerBridgeHandle(),
index
);
}
currentQuality() {
if (RNJWPlayerManager && Platform.OS === "android")
return RNJWPlayerManager.getCurrentQuality(
this.getRNJWPlayerBridgeHandle()
);
}
async getQualityLevels() {
if (RNJWPlayerManager && Platform.OS === "android") {
try {
var qualityLevels = await RNJWPlayerManager.getQualityLevels(
this.getRNJWPlayerBridgeHandle()
);
return qualityLevels;
} catch (e) {
console.error(e);
return null;
}
}
}
setPlaylistIndex(index) {
if (RNJWPlayerManager)
RNJWPlayerManager.setPlaylistIndex(
this.getRNJWPlayerBridgeHandle(),
index
);
}
setControls(show) {
if (RNJWPlayerManager)
RNJWPlayerManager.setControls(
this.getRNJWPlayerBridgeHandle(),
show
);
}
setVisibility(visibility, controls) {
if (RNJWPlayerManager && Platform.OS === 'ios')
RNJWPlayerManager.setVisibility(
this.getRNJWPlayerBridgeHandle(),
visibility,
controls
);
}
setLockScreenControls(show) {
if (RNJWPlayerManager && Platform.OS === 'ios')
RNJWPlayerManager.setLockScreenControls(
this.getRNJWPlayerBridgeHandle(),
show
);
}
seekTo(time) {
if (RNJWPlayerManager)
RNJWPlayerManager.seekTo(this.getRNJWPlayerBridgeHandle(), time);
}
loadPlaylist(playlistItems) {
if (RNJWPlayerManager)
RNJWPlayerManager.loadPlaylist(this.getRNJWPlayerBridgeHandle(), playlistItems);
}
setFullscreen(fullscreen) {
if (RNJWPlayerManager)
RNJWPlayerManager.setFullscreen(
this.getRNJWPlayerBridgeHandle(),
fullscreen
);
}
setVolume(value) {
if (RNJWPlayerManager) {
RNJWPlayerManager.setVolume(
this.getRNJWPlayerBridgeHandle(),
value
);
}
}
async time() {
if (RNJWPlayerManager) {
try {
var time = await RNJWPlayerManager.time(
this.getRNJWPlayerBridgeHandle()
);
return time;
} catch (e) {
console.error(e);
return null;
}
}
}
async position() {
if (RNJWPlayerManager) {
try {
var position = await RNJWPlayerManager.position(
this.getRNJWPlayerBridgeHandle()
);
return position;
} catch (e) {
console.error(e);
return null;
}
}
}
togglePIP() {
if (RNJWPlayerManager)
RNJWPlayerManager.togglePIP(this.getRNJWPlayerBridgeHandle());
}
setUpCastController() {
if (RNJWPlayerManager && Platform.OS === 'ios')
RNJWPlayerManager.setUpCastController(
this.getRNJWPlayerBridgeHandle()
);
}
presentCastDialog() {
if (RNJWPlayerManager && Platform.OS === 'ios')
RNJWPlayerManager.presentCastDialog(
this.getRNJWPlayerBridgeHandle()
);
}
async connectedDevice() {
if (RNJWPlayerManager && Platform.OS === 'ios') {
try {
var connectedDevice = await RNJWPlayerManager.connectedDevice(
this.getRNJWPlayerBridgeHandle()
);
return connectedDevice;
} catch (e) {
console.error(e);
return null;
}
}
}
async availableDevices() {
if (RNJWPlayerManager && Platform.OS === 'ios') {
try {
var availableDevices = await RNJWPlayerManager.availableDevices(
this.getRNJWPlayerBridgeHandle()
);
return availableDevices;
} catch (e) {
console.error(e);
return null;
}
}
}
async castState() {
if (RNJWPlayerManager && Platform.OS === 'ios') {
try {
var castState = await RNJWPlayerManager.castState(
this.getRNJWPlayerBridgeHandle()
);
return castState;
} catch (e) {
console.error(e);
return null;
}
}
}
async playerState() {
if (RNJWPlayerManager) {
try {
var state = await RNJWPlayerManager.state(
this.getRNJWPlayerBridgeHandle()
);
return state;
} catch (e) {
console.error(e);
return null;
}
}
}
async getAudioTracks() {
if (RNJWPlayerManager) {
try {
var audioTracks = await RNJWPlayerManager.getAudioTracks(
this.getRNJWPlayerBridgeHandle()
);
// iOS sends autoSelect as 0 or 1 instead of a boolean
// couldn't figure out how to send autoSelect as a boolean from Objective C
return audioTracks.map((audioTrack) => {
audioTrack.autoSelect = !!audioTrack.autoSelect;
return audioTrack;
});
} catch (e) {
console.error(e);
return null;
}
}
}
async getCurrentAudioTrack() {
if (RNJWPlayerManager) {
try {
var currentAudioTrack =
await RNJWPlayerManager.getCurrentAudioTrack(
this.getRNJWPlayerBridgeHandle()
);
return currentAudioTrack;
} catch (e) {
console.error(e);
return null;
}
}
}
setCurrentAudioTrack(index) {
if (RNJWPlayerManager) {
RNJWPlayerManager.setCurrentAudioTrack(
this.getRNJWPlayerBridgeHandle(),
index
);
}
}
setCurrentCaptions(index) {
if (RNJWPlayerManager) {
RNJWPlayerManager.setCurrentCaptions(
this.getRNJWPlayerBridgeHandle(),
index
);
}
}
async getCurrentCaptions() {
if (RNJWPlayerManager) {
try {
var currentCaptionTrack =
await RNJWPlayerManager.getCurrentCaptions(
this.getRNJWPlayerBridgeHandle()
);
return currentCaptionTrack;
} catch (e) {
console.error(e);
return null;
}
}
}
/**
* Only to be called in the onBeforeNextPlaylistItem callback.
* If called outside of the callback, it will not have any effect.
*
* Can only be called once inside the onBeforeNextPlaylistItem callback.
* @param {PlaylistItem | JwPlaylistItem} playlistItem
*/
resolveNextPlaylistItem(playlistItem) {
if (RNJWPlayerManager) {
RNJWPlayerManager.resolveNextPlaylistItem(
this.getRNJWPlayerBridgeHandle(),
playlistItem
);
}
}
getRNJWPlayerBridgeHandle() {
return findNodeHandle(this[this.ref_key]);
}
render() {
return (
<RNJWPlayer
ref={(player) => (this[this.ref_key] = player)}
key={this.ref_key}
{...this.props}
/>
);
}
}