unified-video-framework
Version:
Cross-platform video player framework supporting iOS, Android, Web, Smart TVs (Samsung/LG), Roku, and more
251 lines (204 loc) • 6.2 kB
text/typescript
/**
* Abstract base player class that provides common functionality
*/
import {
IVideoPlayer,
VideoSource,
PlayerConfig,
PlayerState,
PlayerError,
Quality,
SubtitleTrack
} from './interfaces/IVideoPlayer';
import { PlayerEvent } from './interfaces';
import { EventEmitter } from './utils/EventEmitter';
export abstract class BasePlayer implements IVideoPlayer {
protected container: HTMLElement | null = null;
protected config: PlayerConfig;
protected events: EventEmitter;
protected state: PlayerState;
protected source: VideoSource | null = null;
protected subtitles: SubtitleTrack[] = [];
protected currentSubtitleIndex: number = -1;
constructor() {
this.config = this.getDefaultConfig();
this.events = new EventEmitter();
this.state = this.getDefaultState();
}
protected getDefaultConfig(): PlayerConfig {
return {
autoPlay: false,
muted: false,
volume: 1.0,
controls: true,
loop: false,
preload: 'metadata',
playsInline: true,
enableAdaptiveBitrate: true,
debug: false,
freeDuration: 0
};
}
protected getDefaultState(): PlayerState {
return {
isPlaying: false,
isPaused: true,
isBuffering: false,
isEnded: false,
isError: false,
currentTime: 0,
duration: 0,
bufferedPercentage: 0,
volume: 1.0,
isMuted: false,
playbackRate: 1.0,
availableQualities: []
};
}
async initialize(container: HTMLElement | string, config?: PlayerConfig): Promise<void> {
if (typeof container === 'string') {
const element = document.querySelector(container) as HTMLElement;
if (!element) {
throw new Error(`Container element not found: ${container}`);
}
this.container = element;
} else {
this.container = container;
}
this.config = { ...this.getDefaultConfig(), ...config };
this.state.volume = this.config.volume || 1.0;
this.state.isMuted = this.config.muted || false;
await this.setupPlayer();
}
protected abstract setupPlayer(): Promise<void>;
abstract destroy(): Promise<void>;
abstract load(source: VideoSource): Promise<void>;
async play(): Promise<void> {
this.state.isPlaying = true;
this.state.isPaused = false;
this.emit('onPlay');
}
pause(): void {
this.state.isPlaying = false;
this.state.isPaused = true;
this.emit('onPause');
}
stop(): void {
this.pause();
this.seek(0);
this.state.isEnded = true;
}
abstract seek(time: number): void;
setVolume(level: number): void {
const volume = Math.max(0, Math.min(1, level));
this.state.volume = volume;
this.emit('onVolumeChanged', volume);
}
mute(): void {
this.state.isMuted = true;
this.emit('onVolumeChanged', 0);
}
unmute(): void {
this.state.isMuted = false;
this.emit('onVolumeChanged', this.state.volume);
}
toggleMute(): void {
if (this.state.isMuted) {
this.unmute();
} else {
this.mute();
}
}
abstract getQualities(): Quality[];
abstract getCurrentQuality(): Quality | null;
abstract setQuality(index: number): void;
abstract setAutoQuality(enabled: boolean): void;
setPlaybackRate(rate: number): void {
this.state.playbackRate = rate;
}
getPlaybackRate(): number {
return this.state.playbackRate;
}
getCurrentTime(): number {
return this.state.currentTime;
}
getDuration(): number {
return this.state.duration;
}
getBufferedPercentage(): number {
return this.state.bufferedPercentage;
}
getState(): PlayerState {
return { ...this.state };
}
isPlaying(): boolean {
return this.state.isPlaying;
}
isPaused(): boolean {
return this.state.isPaused;
}
isEnded(): boolean {
return this.state.isEnded;
}
abstract enterFullscreen(): Promise<void>;
abstract exitFullscreen(): Promise<void>;
async toggleFullscreen(): Promise<void> {
if (document.fullscreenElement) {
await this.exitFullscreen();
} else {
await this.enterFullscreen();
}
}
abstract enterPictureInPicture(): Promise<void>;
abstract exitPictureInPicture(): Promise<void>;
on(event: string, handler: Function): void {
this.events.on(event, handler as any);
}
off(event: string, handler?: Function): void {
this.events.off(event, handler as any);
}
once(event: string, handler: Function): void {
this.events.once(event, handler as any);
}
protected emit(event: PlayerEvent, ...args: any[]): void {
this.events.emit(event, ...args);
}
// Optional runtime free preview controls (no-op base; platform implementations may override)
setFreeDuration?(seconds: number): void;
resetFreePreviewGate?(): void;
getSubtitles(): SubtitleTrack[] {
return this.subtitles;
}
setSubtitleTrack(index: number): void {
if (index >= 0 && index < this.subtitles.length) {
this.currentSubtitleIndex = index;
this.applySubtitleTrack(this.subtitles[index]);
}
}
disableSubtitles(): void {
this.currentSubtitleIndex = -1;
this.removeSubtitles();
}
protected abstract applySubtitleTrack(track: SubtitleTrack): void;
protected abstract removeSubtitles(): void;
protected handleError(error: PlayerError): void {
this.state.isError = true;
this.state.isPlaying = false;
this.emit('onError', error);
if (this.config.debug) {
console.error('[VideoPlayer Error]', error);
}
}
protected updateTime(time: number): void {
this.state.currentTime = time;
this.emit('onTimeUpdate', time);
}
protected updateBuffered(percentage: number): void {
this.state.bufferedPercentage = percentage;
this.emit('onProgress', percentage);
}
protected setBuffering(isBuffering: boolean): void {
this.state.isBuffering = isBuffering;
this.emit('onBuffering', isBuffering);
}
}