UNPKG

@applicaster/zapp-react-native-utils

Version:

Applicaster Zapp React Native utilities package

259 lines (201 loc) • 6.76 kB
import { BehaviorSubject } from "rxjs"; import { TapSeekController } from "./TapSeekController"; import { LongPressSeekController } from "./LongPressSeekController"; import { SEEK_TYPE } from "../const"; import { createLogger } from "../../../logger"; import { Player } from "../../../appUtils/playerManager/player"; import { SeekKeyHandlerListener } from "../../../appUtils/keyInputHandler/KeyInputHandler"; import { ISeekController, ISeekControllerDelegate, SeekIncrementalBaseStrategy, } from "./ISeekController"; export const { log_warning, log_debug, log_info, log_error, log_verbose } = createLogger({ category: "General", subsystem: "TVSeekController", }); export enum SEEK_STRATEGY { TAP_SEEK = "TAP_SEEK", AUTO_SEEK = "AUTO_SEEK", } const CONFIG_TAP_SEEK = { CLICKS_BEFORE_PAUSE: 0, }; const CONFIG_LONG_PRESS = { TIMER_INTERVAL_MS: 200, }; const LONG_PRESS_THRESHOLD = 500; const SEEK_END_TIMEOUT_DURATION = 700; // prettier-ignore export class TVSeekController implements SeekKeyHandlerListener, ISeekControllerDelegate { public delayedSeekPosition: number | null = null; private readonly playerController: Player; private delayedSeekProgressSubject = new BehaviorSubject<number | null>(null); isPlayerInitiallyPaused: null | boolean = null; currentSeekType: SEEK_TYPE | null = null; private currentSeekController: ISeekController; private readonly tapSeekController: TapSeekController; private readonly longPressSeekController: LongPressSeekController; private longPressThresholdTimeout: any | null = null; private seekEndTimeout: any | null = null; constructor(playerController: Player) { this.playerController = playerController; this.tapSeekController = new TapSeekController( this, new SeekIncrementalBaseStrategy(), CONFIG_TAP_SEEK ); this.longPressSeekController = new LongPressSeekController( this, new SeekIncrementalBaseStrategy(), CONFIG_LONG_PRESS ); this.currentSeekController = this.tapSeekController; } getDelayedSeekProgressObservable = () => { return this.delayedSeekProgressSubject; }; setStrategy = (seekStrategy: SEEK_STRATEGY) => { switch (seekStrategy) { case SEEK_STRATEGY.TAP_SEEK: this.currentSeekController = this.tapSeekController; log_debug("TVSeekController: setStrategy - TAP_SEEK"); break; case SEEK_STRATEGY.AUTO_SEEK: this.currentSeekController = this.longPressSeekController; log_debug("TVSeekController: setStrategy - AUTO_SEEK"); break; default: } }; handleStartSeek = (seekType: SEEK_TYPE) => { this.currentSeekType = seekType; if (this.isPlayerInitiallyPaused === null) { this.isPlayerInitiallyPaused = this.playerController.isPaused(); } this.clearLongPressThresholdTimeout(); this.clearSeekCompleteTimeout(); this.setStrategy(SEEK_STRATEGY.TAP_SEEK); this.currentSeekController.onStartSeek(seekType); this.longPressThresholdTimeout = setTimeout(() => { this.setStrategy(SEEK_STRATEGY.AUTO_SEEK); this.currentSeekController.onStartSeek(seekType); }, LONG_PRESS_THRESHOLD); }; onForwardPress = (): boolean => { this.handleStartSeek(SEEK_TYPE.FORWARD); return true; }; onRewindPress = (): boolean => { this.handleStartSeek(SEEK_TYPE.REWIND); return true; }; onSeekEnd = () => { this.clearLongPressThresholdTimeout(); this.currentSeekController.onSeekEnd(); }; onRewindPressOut = () => { this.onSeekEnd(); }; onForwardPressOut = () => { this.onSeekEnd(); }; onVideoProgress = (position: number) => { if (this.delayedSeekPosition !== null) { return; } this.delayedSeekProgressSubject.next(position); }; handleDelayedSeek = (offset: number = 0) => { this.handlePlayerPause(); const currentPos = this.delayedSeekPosition === null ? this.playerController.getPosition() : this.delayedSeekPosition; let targetPos = currentPos; if (this.currentSeekType === SEEK_TYPE.FORWARD) { targetPos = Math.min( currentPos + offset, this.playerController.getSeekableDuration() ); } else if (this.currentSeekType === SEEK_TYPE.REWIND) { targetPos = Math.max(0, currentPos - offset); } else { log_warning( `TVSeekController: handleDelayedSeek - invalid seek type: ${this.currentSeekType}` ); } log_debug( `TVSeekController: handleDelayedSeek - targetPos: ${targetPos}, skipTime: ${offset}` ); const prevValue = this.delayedSeekProgressSubject.getValue(); if (prevValue !== null) { const delta = Math.abs(prevValue - targetPos); if (delta < 1) { return; } } this.delayedSeekPosition = targetPos; this.delayedSeekProgressSubject.next(targetPos); }; handleSeekTo = (newPosition: number) => { this.playerController.seekTo(newPosition); if (!this.isPlayerInitiallyPaused) { this.playerController.play(); } }; handleSeek = (offset: number) => { this.playerController[this.currentSeekType](offset); if (!this.isPlayerInitiallyPaused) { this.playerController.play(); } }; handleSeekComplete = () => { this.clearSeekCompleteTimeout(); this.seekEndTimeout = setTimeout(() => { if (this.delayedSeekPosition !== null) { this.handleSeekTo(this.delayedSeekPosition); } log_debug( `handleSeekComplete: Seek finished, type: ${this.currentSeekController.title}, seekPosition: ${this.delayedSeekPosition}. Reset all states and seek position.` ); this.resetAll(); }, SEEK_END_TIMEOUT_DURATION); }; isSeeking = (): boolean => { return this.delayedSeekPosition !== null; }; handlePlayerPause = () => { if (this.playerController.isPaused()) { return; } this.playerController.pause(); }; clearSeekCompleteTimeout = () => { if (this.seekEndTimeout === null) { return; } clearTimeout(this.seekEndTimeout); this.seekEndTimeout = null; }; clearLongPressThresholdTimeout = () => { if (this.longPressThresholdTimeout === null) { return; } clearTimeout(this.longPressThresholdTimeout); this.longPressThresholdTimeout = null; }; resetAll() { log_debug("TVSeekController: Reset all states"); this.currentSeekType = null; this.delayedSeekPosition = null; this.isPlayerInitiallyPaused = null; this.clearSeekCompleteTimeout(); this.clearLongPressThresholdTimeout(); this.tapSeekController.reset(); this.longPressSeekController.reset(); this.setStrategy(SEEK_STRATEGY.TAP_SEEK); } }