@applicaster/zapp-react-native-utils
Version:
Applicaster Zapp React Native utilities package
259 lines (201 loc) • 6.76 kB
text/typescript
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);
}
}