playable
Version:
Video player based on HTML5Video
137 lines (112 loc) • 3.47 kB
text/typescript
import { EngineState, VideoEvent, LiveState, UIEvent } from '../../constants';
import { IEventEmitter, IEventMap } from '../event-emitter/types';
import { ILiveStateEngineDependencies, IPlaybackEngine } from './types';
const SEEK_BY_UI_EVENTS = [
UIEvent.GO_FORWARD_WITH_KEYBOARD,
UIEvent.GO_BACKWARD_WITH_KEYBOARD,
UIEvent.PROGRESS_CHANGE,
];
class LiveStateEngine {
static moduleName = 'liveStateEngine';
static dependencies = ['eventEmitter', 'engine'];
private _eventEmitter: IEventEmitter;
private _engine: IPlaybackEngine;
private _state: LiveState;
private _isSeekedByUIWhilePlaying: boolean;
private _unbindEvents: () => void;
constructor({ eventEmitter, engine }: ILiveStateEngineDependencies) {
this._eventEmitter = eventEmitter;
this._engine = engine;
this._state = LiveState.NONE;
this._isSeekedByUIWhilePlaying = null;
this._bindEvents();
}
get state(): LiveState {
return this._state;
}
private _bindEvents() {
this._unbindEvents = this._eventEmitter.bindEvents(
[
[VideoEvent.STATE_CHANGED, this._processStateChange],
...SEEK_BY_UI_EVENTS.map(
eventName => [eventName, this._processSeekByUI] as IEventMap,
),
[VideoEvent.DYNAMIC_CONTENT_ENDED, this._onDynamicContentEnded],
],
this,
);
}
private _processStateChange({
prevState,
nextState,
}: {
prevState: EngineState;
nextState: EngineState;
}) {
if (nextState === EngineState.SRC_SET) {
this._setState(LiveState.NONE);
return;
}
if (!this._engine.isDynamicContent || this._engine.isDynamicContentEnded) {
return;
}
switch (nextState) {
case EngineState.METADATA_LOADED:
this._setState(LiveState.INITIAL);
break;
case EngineState.PLAY_REQUESTED:
if (this._state === LiveState.INITIAL) {
this._engine.syncWithLive();
}
break;
case EngineState.PLAYING:
// NOTE: skip PLAYING event after events like `WAITING` and other not important events.
if (
this._state === LiveState.INITIAL ||
this._state === LiveState.NOT_SYNC ||
this._isSeekedByUIWhilePlaying
) {
this._setState(
this._engine.isSyncWithLive ? LiveState.SYNC : LiveState.NOT_SYNC,
);
this._isSeekedByUIWhilePlaying = false;
}
break;
case EngineState.PAUSED:
// NOTE: process `PAUSED` event only `PLAYING`, to be sure its not related with `WAITING` events
if (prevState === EngineState.PLAYING) {
this._setState(LiveState.NOT_SYNC);
}
break;
default:
break;
}
}
private _processSeekByUI() {
if (
this._engine.isDynamicContent &&
this._engine.getCurrentState() === EngineState.PLAYING
) {
// NOTE: flag should be handled on `PLAYING` state in `_processStateChange`
this._isSeekedByUIWhilePlaying = true;
}
}
private _onDynamicContentEnded() {
this._setState(LiveState.ENDED);
}
private _setState(state: LiveState) {
if (this._state !== state) {
const prevState = this._state;
const nextState = state;
this._state = state;
this._eventEmitter.emitAsync(VideoEvent.LIVE_STATE_CHANGED, {
prevState,
nextState,
});
}
}
destroy() {
this._unbindEvents();
}
}
export default LiveStateEngine;