react-native-theoplayer
Version:
A THEOplayer video component for react-native.
453 lines (383 loc) • 13.7 kB
text/typescript
import { DefaultEventDispatcher } from './event/DefaultEventDispatcher';
import {
AdsAPI,
AspectRatio,
BackgroundAudioConfiguration,
CastAPI,
CmcdConfiguration,
CmcdTransmissionMode,
EventBroadcastAPI,
MediaTrack,
NativeHandleType,
PlayerConfiguration,
PlayerEventMap,
PlayerEventType,
PlayerVersion,
PreloadType,
PresentationMode,
TextTrack,
TextTrackStyle,
TheoAdsAPI,
TheoLiveAPI,
THEOplayer,
} from 'react-native-theoplayer';
import { THEOplayerWebAdsAdapter } from './ads/THEOplayerWebAdsAdapter';
import { THEOplayerWebCastAdapter } from './cast/THEOplayerWebCastAdapter';
import type { DimensionChangeEvent, MediaTrack as NativeMediaTrack, TextTrack as NativeTextTrack } from 'theoplayer';
import { ChromelessPlayer as NativeChromelessPlayer, SourceDescription as NativeSourceDescription, version as nativeVersion } from 'theoplayer';
import { findNativeQualitiesByUid, fromNativeMediaTrackList } from './web/TrackUtils';
import type { ABRConfiguration, SourceDescription } from 'src/api/barrel';
import { WebEventForwarder } from './WebEventForwarder';
import type { PiPConfiguration } from 'src/api/pip/PiPConfiguration';
import { WebPresentationModeManager } from './web/WebPresentationModeManager';
import { WebMediaSession } from './web/WebMediaSession';
import { BaseEvent } from './event/BaseEvent';
import { EventBroadcastAdapter } from './broadcast/EventBroadcastAdapter';
import { TextTrackState } from './NativePlayerState';
import { DefaultTextTrackState } from './DefaultTextTrackState';
import { THEOAdsWebAdapter } from './theoads/THEOAdsWebAdapter';
import { CMCDConnector, Configuration, createCMCDConnector, TransmissionMode } from '@theoplayer/cmcd-connector-web';
import { TheoLiveWebAdapter } from './theolive/TheoLiveWebAdapter';
const defaultBackgroundAudioConfiguration: BackgroundAudioConfiguration = {
enabled: false,
};
const defaultPipConfiguration: PiPConfiguration = {
startsAutomatically: false,
};
export class THEOplayerWebAdapter extends DefaultEventDispatcher<PlayerEventMap> implements THEOplayer {
private readonly _adsAdapter: THEOplayerWebAdsAdapter;
private readonly _castAdapter: THEOplayerWebCastAdapter;
private readonly _theoAdsAdapter: THEOAdsWebAdapter;
private readonly _textTrackState: TextTrackState;
private readonly _presentationModeManager: WebPresentationModeManager;
private readonly _theoliveAdapter: TheoLiveWebAdapter;
private _player: NativeChromelessPlayer | undefined;
private _eventForwarder: WebEventForwarder | undefined;
private _mediaSession: WebMediaSession | undefined = undefined;
private _targetVideoQuality: number | number[] | undefined = undefined;
private _backgroundAudioConfiguration: BackgroundAudioConfiguration = defaultBackgroundAudioConfiguration;
private _pipConfiguration: PiPConfiguration = defaultPipConfiguration;
private _externalEventRouter: EventBroadcastAPI | undefined = undefined;
private _cmcdConnector: CMCDConnector | undefined = undefined;
private _width: number | undefined = undefined;
private _height: number | undefined = undefined;
constructor(player: NativeChromelessPlayer, config?: PlayerConfiguration) {
super();
this._player = player;
this._adsAdapter = new THEOplayerWebAdsAdapter(this._player);
this._castAdapter = new THEOplayerWebCastAdapter(this._player);
this._theoAdsAdapter = new THEOAdsWebAdapter(this._player);
this._theoliveAdapter = new TheoLiveWebAdapter(this._player);
this._textTrackState = new DefaultTextTrackState(this);
this._eventForwarder = new WebEventForwarder(this._player, this);
this._presentationModeManager = new WebPresentationModeManager(this._player, this);
document.addEventListener('visibilitychange', this.onVisibilityChange);
this._player.addEventListener('dimensionchange', this.onPlayerDimensionChange);
// Optionally create a media session connector
if (config?.mediaControl?.mediaSessionEnabled !== false) {
this._mediaSession = new WebMediaSession(this, player, config?.mediaControl);
}
}
get abr(): ABRConfiguration | undefined {
return this._player?.abr as ABRConfiguration | undefined;
}
get source(): SourceDescription | undefined {
return this._player?.source as SourceDescription;
}
set source(source: SourceDescription | undefined) {
this._targetVideoQuality = undefined;
if (this._player) {
this._player.source = source as NativeSourceDescription;
if (source?.cmcd && this._cmcdConnector === undefined) {
this._cmcdConnector = createCMCDConnector(this._player);
}
this._cmcdConnector?.reconfigure(this.toWebCmcdConfiguration(source?.cmcd));
}
}
private toWebCmcdConfiguration(cmcdConfiguration?: CmcdConfiguration): Configuration | undefined {
if (!cmcdConfiguration) {
return undefined;
}
let transmissionMode = TransmissionMode.QUERY_ARGUMENT;
switch (cmcdConfiguration.transmissionMode) {
case CmcdTransmissionMode.HTTP_HEADER:
transmissionMode = TransmissionMode.HTTP_HEADER;
break;
case CmcdTransmissionMode.JSON_OBJECT:
transmissionMode = TransmissionMode.JSON_OBJECT;
break;
case CmcdTransmissionMode.QUERY_ARGUMENT:
case CmcdTransmissionMode.SDK_DEFAULT:
default:
transmissionMode = TransmissionMode.QUERY_ARGUMENT;
}
return {
...cmcdConfiguration,
transmissionMode: transmissionMode,
};
}
play(): void {
this._player?.play();
}
pause(): void {
this._player?.pause();
}
get paused(): boolean {
return this._player ? this._player.paused : true;
}
get autoplay(): boolean {
return this._player ? this._player.autoplay : false;
}
set autoplay(autoplay: boolean) {
if (this._player) {
this._player.autoplay = autoplay;
}
}
set preload(type: PreloadType) {
if (this._player) {
this._player.preload = type;
}
}
get preload(): PreloadType {
return this._player?.preload || 'none';
}
get seekable() {
if (!this._player) {
return [];
}
const nativeRange = this._player.seekable;
return [...Array(nativeRange.length)].map((_, index) => ({ start: 1e3 * nativeRange.start(index), end: 1e3 * nativeRange.end(index) }));
}
get buffered() {
if (!this._player) {
return [];
}
const nativeRange = this._player.buffered;
return [...Array(nativeRange.length)].map((_, index) => ({ start: 1e3 * nativeRange.start(index), end: 1e3 * nativeRange.end(index) }));
}
get playbackRate(): number {
return this._player ? this._player.playbackRate : 1;
}
set playbackRate(playbackRate: number) {
if (this._player) {
this._player.playbackRate = playbackRate;
}
}
get pipConfiguration(): PiPConfiguration {
return this._pipConfiguration;
}
set pipConfiguration(config: PiPConfiguration) {
this._pipConfiguration = config;
}
get backgroundAudioConfiguration(): BackgroundAudioConfiguration {
return this._backgroundAudioConfiguration;
}
set backgroundAudioConfiguration(config: BackgroundAudioConfiguration) {
this._backgroundAudioConfiguration = config;
// Notify media session
this._mediaSession?.updateMediaSession();
}
get volume(): number {
return this._player ? this._player.volume : 1;
}
set volume(volume: number) {
if (this._player) {
this._player.volume = volume;
}
}
get muted(): boolean {
return this._player ? this._player.muted : false;
}
set muted(muted: boolean) {
if (this._player) {
this._player.muted = muted;
}
}
get seeking(): boolean {
return this._player ? this._player.seeking : false;
}
get presentationMode(): PresentationMode {
return this._presentationModeManager.presentationMode;
}
set presentationMode(presentationMode: PresentationMode) {
this._presentationModeManager.presentationMode = presentationMode;
}
get audioTracks(): MediaTrack[] {
return this._player ? fromNativeMediaTrackList(this._player.audioTracks) : [];
}
get videoTracks(): MediaTrack[] {
return this._player ? fromNativeMediaTrackList(this._player.videoTracks) : [];
}
get textTracks(): TextTrack[] {
return this._textTrackState.textTracks;
}
get selectedTextTrack(): number | undefined {
return this._player ? this._textTrackState.selectedTextTrack : undefined;
}
set selectedTextTrack(trackUid: number | undefined) {
if (this._player) {
this._textTrackState.selectedTextTrack = trackUid;
// Apply native selection
this._player.textTracks.forEach((textTrack: NativeTextTrack) => {
if (textTrack.uid === trackUid) {
textTrack.mode = 'showing';
} else if (textTrack.mode === 'showing') {
textTrack.mode = 'disabled';
}
});
}
}
get textTrackStyle(): TextTrackStyle {
return this._player?.textTrackStyle as unknown as TextTrackStyle;
}
get selectedVideoTrack(): number | undefined {
if (this._player) {
return this._player.videoTracks.find((videoTrack: NativeMediaTrack) => videoTrack.enabled)?.uid;
}
return undefined;
}
set selectedVideoTrack(selectedVideoTrack: number | undefined) {
if (this._player) {
this._targetVideoQuality = undefined;
this._player.videoTracks.forEach((videoTrack: NativeMediaTrack) => {
videoTrack.enabled = videoTrack.uid === selectedVideoTrack;
});
}
}
get selectedAudioTrack(): number | undefined {
if (this._player) {
return this._player.audioTracks.find((audioTrack: NativeMediaTrack) => {
return audioTrack.enabled;
})?.uid;
}
return undefined;
}
set selectedAudioTrack(selectedAudioTrack: number | undefined) {
if (this._player) {
this._player.audioTracks.forEach((audioTrack: NativeMediaTrack) => {
audioTrack.enabled = audioTrack.uid === selectedAudioTrack;
});
}
}
get targetVideoQuality(): number | number[] | undefined {
if (this._player) {
return this._targetVideoQuality;
}
return undefined;
}
set targetVideoQuality(targetVideoQuality: number | number[] | undefined) {
if (this._player) {
const enabledVideoTrack = this._player.videoTracks.find((videoTrack: NativeMediaTrack) => videoTrack.enabled);
if (enabledVideoTrack) {
enabledVideoTrack.targetQuality = findNativeQualitiesByUid(enabledVideoTrack, targetVideoQuality);
}
this._targetVideoQuality = targetVideoQuality;
}
}
get currentTime(): number {
return this._player ? 1e3 * this._player.currentTime : NaN;
}
set currentTime(currentTime: number) {
if (isNaN(currentTime)) {
return;
}
if (this._player) {
this._player.currentTime = currentTime / 1e3;
}
}
get currentProgramDateTime(): number | undefined {
return this._player?.currentProgramDateTime?.getTime();
}
get aspectRatio(): AspectRatio {
return AspectRatio.FIT;
}
set aspectRatio(_ratio: AspectRatio) {
// unused
}
get keepScreenOn(): boolean {
return false;
}
set keepScreenOn(_screenOn: boolean) {
// unused
}
get duration(): number {
return this._player ? this._player.duration * 1e3 : NaN;
}
public get ads(): AdsAPI {
return this._adsAdapter;
}
public get theoads(): TheoAdsAPI {
return this._theoAdsAdapter;
}
public get cast(): CastAPI {
return this._castAdapter;
}
public get theoLive(): TheoLiveAPI {
return this._theoliveAdapter;
}
public get theolive(): TheoLiveAPI {
return this._theoliveAdapter;
}
public get version(): PlayerVersion {
return {
version: nativeVersion,
playerSuiteVersion: '', // deprecated
};
}
destroy(): void {
this.dispatchEvent(new BaseEvent(PlayerEventType.DESTROY));
this._eventForwarder?.unload();
this._mediaSession?.destroy();
document.removeEventListener('visibilitychange', this.onVisibilityChange);
this._eventForwarder = undefined;
this._mediaSession = undefined;
this._cmcdConnector?.destroy();
this._cmcdConnector = undefined;
this._player?.removeEventListener('dimensionchange', this.onPlayerDimensionChange);
this._player?.destroy();
this._player = undefined;
}
private readonly onVisibilityChange = () => {
if (!this._player) {
return;
}
if (document.visibilityState !== 'visible') {
if (this.presentationMode !== PresentationMode.pip) {
// Apply background configuration: by default, pause when going to background, unless in pip
if (!this.backgroundAudioConfiguration.enabled) {
this._player.pause();
}
if (this.backgroundAudioConfiguration.stopOnBackground) {
this._player.stop();
}
}
}
// Apply media session controls
this._mediaSession?.updateMediaSession();
};
private readonly onPlayerDimensionChange = (event: DimensionChangeEvent) => {
if (!this._player) {
return;
}
this._width = event.width;
this._height = event.height;
};
get nativeHandle(): NativeHandleType {
return this._player;
}
get broadcast(): EventBroadcastAPI {
return this._externalEventRouter ?? (this._externalEventRouter = new EventBroadcastAdapter(this));
}
get width(): number | undefined {
return this._width;
}
get height(): number | undefined {
return this._height;
}
get videoWidth(): number | undefined {
return this._player?.videoWidth;
}
get videoHeight(): number | undefined {
return this._player?.videoHeight;
}
}