@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
295 lines (228 loc) • 7.97 kB
text/typescript
import * as R from "ramda";
import { isEmptyOrNil } from "@applicaster/zapp-react-native-utils/cellUtils";
import { createLogger, utilsLogger } from "../../logger";
import { Player } from "./player";
import { findNodeHandle } from "react-native";
import { PlayerRole } from "./conts";
import { toBooleanWithDefaultFalse } from "../../booleanUtils";
import { RNPlayerManager } from "./nativePlayerManager";
const logger = createLogger({
category: "PlayerNativeController",
subsystem: "General",
parent: utilsLogger,
});
const { log_warning } = logger;
type PlayerPlugnId = "QuickBrickPlayerPlugin" | "KalturaPlayerPlugin";
type Props = {
player: PlayerRef;
listener?: QuickBrickPlayer.SharedPlayerCallBacks | null;
config?: Record<string, any>;
playerId: string;
autoplay?: boolean;
entry: ZappEntry;
playerPluginId?: PlayerPlugnId;
};
const PlayerModuleFuncNames = {
seekTo: "seekTo",
forward: "forward",
rewind: "rewind",
play: "play",
pause: "pause",
startSleepTimer: "startSleepTimer",
cancelSleepTimer: "cancelSleepTimer",
getSleepTimerEnd: "getSleepTimerEnd",
};
export class PlayerNative extends Player {
private readonly playerComponent?: PlayerRef | null;
constructor(props: Props) {
super(props);
this.entry = props.entry;
this.playerComponent = props.player;
this.playerPluginId = props.playerPluginId;
this.startPosition = this.getContinueWatchingOffset({
entry: this.getEntry() || (props.listener as any)?.entry,
ignoreContinueWatching: this.playerRole === PlayerRole.Cell,
});
}
getContentPosition = () => {
return this.playerState.contentPosition;
};
currentPlayerComponent = (): ReactComponent<PlayerProps> =>
this.playerComponent?.current;
getState = () => this.playerState;
// TODO: Try to add module interface
invokeNativeFunction = (funcName: string, ...params: any[]): boolean => {
const module = this.getPlayerModule();
const node = this.getComponentNode();
const func = module?.[funcName];
if (!(node && func)) {
return false;
}
func(node, ...params);
return true;
};
getPlayerModule = () => {
return RNPlayerManager?.[this.playerPluginId];
};
getComponentNode = () => {
const component =
(this.playerComponent?.current as any)?._root ||
(this.playerComponent?.current as any);
return findNodeHandle(component);
};
getInstance = () => {
return this.playerComponent;
};
// Methods to call actions on the player
seekTo = (position: number) => {
if (!this.playerState.seekableDuration) {
log_warning("seekTo: Can not seek, video is not seekable");
return;
}
const newPosition = R.clamp(0, this.playerState.seekableDuration, position);
if (
!this.invokeNativeFunction(
PlayerModuleFuncNames.seekTo,
newPosition,
1000
)
) {
if (isEmptyOrNil(this.playerState.seekPosition)) {
this.playerState.seekPosition = this.playerState.contentPosition;
}
this.playerState.seekPosition = newPosition;
this.currentPlayerComponent()?.["seeking"](this.playerState.seekPosition);
}
this.logState(PlayerModuleFuncNames.seekTo, { position });
};
forward = (deltaTime: number) => {
if (isEmptyOrNil(this.playerState.contentPosition)) {
return;
}
if (!this.invokeNativeFunction(PlayerModuleFuncNames.forward, deltaTime)) {
// Kaltura does not have yet this implementation, use legacy code
this.currentPlayerComponent()?.["forward"](deltaTime);
}
this.notifyPlayHeadPositionUpdate();
this.logState(PlayerModuleFuncNames.forward, { deltaTime });
};
rewind = (deltaTime: number) => {
if (isEmptyOrNil(this.playerState.contentPosition)) {
return;
}
if (!this.invokeNativeFunction(PlayerModuleFuncNames.rewind, deltaTime)) {
// Kaltura does not have yet this implementation, use legacy code
this.currentPlayerComponent()?.["rewind"](deltaTime);
}
this.notifyPlayHeadPositionUpdate();
this.logState(PlayerModuleFuncNames.rewind, { deltaTime });
};
play = () => {
if (!this.invokeNativeFunction(PlayerModuleFuncNames.play)) {
this.currentPlayerComponent()?.[PlayerModuleFuncNames.play]?.();
}
};
pause = () => {
if (!this.invokeNativeFunction(PlayerModuleFuncNames.pause)) {
this.currentPlayerComponent()?.[PlayerModuleFuncNames.pause]?.();
}
};
startSleepTimer = (sleepTimestamp: number) => {
if (
!this.invokeNativeFunction(
PlayerModuleFuncNames.startSleepTimer,
sleepTimestamp
)
) {
this.currentPlayerComponent()?.["startSleepTimer"](sleepTimestamp);
}
};
cancelSleepTimer = () => {
if (!this.invokeNativeFunction(PlayerModuleFuncNames.cancelSleepTimer)) {
this.currentPlayerComponent()?.[
PlayerModuleFuncNames.cancelSleepTimer
]?.();
}
};
disableBufferAnimation = (): boolean => false;
selectTrack = (
selected: QuickBrickPlayer.TextTrack | QuickBrickPlayer.AudioTrack
) => {
this.currentPlayerComponent()?.["selectTrack"]?.(selected);
};
setPlaybackRate = (rate: number) => {
this.currentPlayerComponent()?.["setPlaybackRate"]?.(rate);
};
getPluginConfiguration = () => {
return (this.currentPlayerComponent() as any)?.props?.pluginConfiguration;
};
getProps = () => (this.currentPlayerComponent() as any)?.props;
appStateChange = (appState, previousAppState) => {
this.currentPlayerComponent()?.["appStateChange"]?.(
appState,
previousAppState
);
};
closeNativePlayer = () => {
// TODO: Delete does not work
this.currentPlayerComponent()?.["closeNativePlayer"]?.();
};
togglePlayPause = () => {
this.currentPlayerComponent()?.["togglePlayPause"]?.();
};
onVideoLoad = (event) => {
if (this.startPosition) {
super.onVideoLoad({ ...event, ignoreContinueWatching: true });
this.seekTo(this.startPosition);
this.startPosition = null;
} else {
super.onVideoLoad(event);
}
};
// This function preserves current chromecast content position,
// So we can resume from the same position if chromecast session is closed by user
// Because, current position is lost when view is recreated.
onVideoProgress = (event) => {
super.onVideoProgress(event);
// TODO: Use native player view reference
const nativePlayerModule = (this.playerComponent?.current as any)?._root;
if (!nativePlayerModule) {
if (this.playerRole === PlayerRole.Cell) {
return;
}
this.startPosition = event.currentTime;
}
};
// This function is called when native player used directly without wrapped component,
// Since event from native comes with wrapped in NativeEvent property
getNativeViewListener = (): QuickBrickPlayer.SharedPlayerCallBacks => {
const listeners = this.getListener();
const wrapNativeEvent = (event, callerFunc) => {
if (!event) {
callerFunc();
}
callerFunc(event?.nativeEvent || event);
};
return R.map((f) => {
return (event) => wrapNativeEvent(event, f);
}, listeners);
};
isAudioItem = () =>
this.getEntry()?.content?.type?.includes?.("audio") ||
this.getEntry()?.type?.value === "audio";
isFullScreenSupported = (): boolean => {
const config = this.getConfig();
const disableFullScreen = config?.["disable_fullscreen"];
if (disableFullScreen) {
return false;
}
const isFullScreenAudioPlayer = config?.["full_screen_audio_player"];
return !(isFullScreenAudioPlayer && this.isAudioItem());
};
supportsNativeControls = (): boolean =>
toBooleanWithDefaultFalse(this.getConfig()?.["supports_native_controls"]);
supportNativeCast = (): boolean =>
toBooleanWithDefaultFalse(
this.playerPluginId === "quick-brick-player-brightcove"
);
}