unified-video-framework
Version:
Cross-platform video player framework supporting iOS, Android, Web, Smart TVs (Samsung/LG), Roku, and more
434 lines (386 loc) • 14.8 kB
text/typescript
/**
* Core video player interface that all platform implementations must follow
*/
export interface FallbackSource {
url: string;
type?: 'mp4' | 'hls' | 'dash' | 'webm' | 'auto';
priority?: number; // Lower number = higher priority (default: index order)
}
export interface VideoSource {
url: string;
type?: 'mp4' | 'hls' | 'dash' | 'webm' | 'auto';
drm?: DRMConfig;
subtitles?: SubtitleTrack[];
metadata?: VideoMetadata;
// Fallback configuration
fallbackSources?: FallbackSource[]; // Alternative video URLs to try on failure
fallbackPoster?: string; // Static image to show when all video sources fail
fallbackShowErrorMessage?: boolean; // Show error message overlay on fallback poster (default: true)
fallbackRetryDelay?: number; // Delay in ms before trying next fallback (default: 1000)
fallbackRetryAttempts?: number; // Number of retry attempts per source (default: 1)
onAllSourcesFailed?: (errors: Array<{ url: string; error: any }>) => void; // Callback when all sources fail
}
export interface DRMConfig {
licenseUrl: string;
certificateUrl?: string;
headers?: Record<string, string>;
type: 'widevine' | 'playready' | 'fairplay' | 'clearkey';
}
export interface SubtitleTrack {
url: string;
language: string;
label: string;
kind: 'subtitles' | 'captions' | 'descriptions';
default?: boolean;
}
export interface VideoMetadata {
id?: string;
videoId?: string;
title?: string;
description?: string;
duration?: number;
thumbnailUrl?: string;
posterUrl?: string;
}
export interface Quality {
height: number;
width: number;
bitrate: number;
label: string;
index: number;
}
export interface PlayerState {
isPlaying: boolean;
isPaused: boolean;
isBuffering: boolean;
isEnded: boolean;
isError: boolean;
currentTime: number;
duration: number;
bufferedPercentage: number;
volume: number;
isMuted: boolean;
playbackRate: number;
currentQuality?: Quality;
availableQualities: Quality[];
}
export interface PlayerEvents {
onReady?: () => void;
onPlay?: () => void;
onPause?: () => void;
onEnded?: () => void;
onTimeUpdate?: (time: number) => void;
onBuffering?: (isBuffering: boolean) => void;
onError?: (error: PlayerError) => void;
onQualityChanged?: (quality: Quality) => void;
onVolumeChanged?: (volume: number) => void;
onFullscreenChanged?: (isFullscreen: boolean) => void;
onProgress?: (buffered: number) => void;
onSeeking?: () => void;
onSeeked?: () => void;
onLoadedMetadata?: (metadata: VideoMetadata) => void;
// Fired exactly once when free preview duration is reached and playback is blocked
onFreePreviewEnded?: () => void;
// EPG (Electronic Program Guide) events
epgToggle?: (data?: any) => void;
epgDataSet?: (data?: any) => void;
// Framework branding events
frameworkBrandingClick?: (data: { timestamp: number; url: string; userAgent: string }) => void;
// Chapter events
chapterchange?: (chapter: any) => void;
segmententered?: (segment: any) => void;
segmentexited?: (segment: any) => void;
segmentskipped?: (segment: any) => void;
chapterSegmentEntered?: (data: any) => void;
chapterSegmentSkipped?: (data: any) => void;
chapterSkipButtonShown?: (data: any) => void;
chapterSkipButtonHidden?: (data: any) => void;
chaptersLoaded?: (data: any) => void;
chaptersLoadError?: (data: any) => void;
// Navigation events
navigationBackClicked?: () => void;
navigationCloseClicked?: () => void;
// Live stream waiting events
onLiveStreamWaiting?: () => void;
onLiveStreamReady?: () => void;
// Bandwidth detection events
onBandwidthDetected?: (data: { bandwidth: number | null; tier: string; method: string }) => void;
onBandwidthTierChanged?: (data: { tier: string; bandwidth: number | null }) => void;
}
export interface PlayerError {
code: string;
message: string;
type: 'network' | 'media' | 'drm' | 'unknown';
fatal: boolean;
details?: any;
}
export interface PaywallConfig {
enabled: boolean;
apiBase: string; // e.g., http://localhost:3100
userId: string;
videoId: string;
gateways: Array<'stripe' | 'cashfree'>;
branding?: { title?: string; description?: string; logoUrl?: string; theme?: any };
popup?: { width?: number; height?: number };
// Email OTP Authentication (optional - if not provided, assumes user is already authenticated)
emailAuth?: {
enabled: boolean; // Enable email authentication flow
skipIfAuthenticated?: boolean; // Skip email auth if user already has valid session (default: true)
sessionStorage?: {
tokenKey?: string; // Key for storing session token (default: 'uvf_session_token')
refreshTokenKey?: string; // Key for storing refresh token (default: 'uvf_refresh_token')
userIdKey?: string; // Key for storing user ID (default: 'uvf_user_id')
};
api?: {
requestOtp: string; // POST /auth/request-otp endpoint
verifyOtp: string; // POST /auth/verify-otp endpoint
refreshToken?: string; // POST /auth/refresh-token endpoint
logout?: string; // POST /auth/logout endpoint
};
ui?: {
title?: string; // Modal title (default: "Sign in to continue")
description?: string; // Modal description
emailPlaceholder?: string; // Email input placeholder
otpPlaceholder?: string; // OTP input placeholder
submitButtonText?: string; // Submit button text
resendButtonText?: string; // Resend OTP button text
resendCooldown?: number; // Resend cooldown in seconds (default: 30)
};
validation?: {
otpLength?: number; // Expected OTP length (default: 6)
otpTimeout?: number; // OTP validity timeout in seconds (default: 300)
rateLimiting?: {
maxAttempts?: number; // Max OTP requests per hour (default: 5)
windowMinutes?: number; // Rate limiting window (default: 60)
};
};
};
}
export interface ShareConfig {
enabled?: boolean;
url?: string;
title?: string;
text?: string;
generateUrl?: (videoData: { videoId?: string; metadata?: any }) => string;
}
/**
* Configuration for individual player control visibility
* All controls default to true (visible) unless explicitly set to false
*/
export interface ControlsVisibilityConfig {
/** Playback control buttons */
playback?: {
/** Large center play button overlay (default: true) */
centerPlayButton?: boolean;
/** Bottom bar play/pause button (default: true) */
playPauseButton?: boolean;
/** Skip forward/backward 10s buttons (default: true) */
skipButtons?: boolean;
/** Previous track button (default: auto based on playlist config) */
previousButton?: boolean;
/** Next track button (default: auto based on playlist config) */
nextButton?: boolean;
};
/** Audio control buttons */
audio?: {
/** Volume mute/unmute button (default: true) */
volumeButton?: boolean;
/** Volume slider panel (default: true) */
volumeSlider?: boolean;
};
/** Progress and time controls */
progress?: {
/** Video seekbar/progress bar (default: true) */
progressBar?: boolean;
/** Current time / duration display (default: true) */
timeDisplay?: boolean;
};
/** Quality and settings controls */
quality?: {
/** Quality badge indicator (HD, 4K, etc.) (default: true) */
badge?: boolean;
/** Settings menu button (default: true) */
settingsButton?: boolean;
};
/** Display mode controls */
display?: {
/** Fullscreen toggle button (default: true) */
fullscreenButton?: boolean;
/** Picture-in-picture button (default: true on supported browsers) */
pipButton?: boolean;
};
/** Advanced feature controls */
features?: {
/** Electronic Program Guide button (default: true when EPG data available) */
epgButton?: boolean;
/** Playlist panel toggle button (default: true when callback provided) */
playlistButton?: boolean;
/** Cast to TV button (default: true) */
castButton?: boolean;
/** Share video button (default: true) */
shareButton?: boolean;
// Note: Stop casting button is automatically shown/hidden based on active casting state
};
/** Navigation and branding chrome */
chrome?: {
/** Back/close navigation buttons (default: auto based on navigation config) */
navigationButtons?: boolean;
/** Framework watermark/branding (default: true) */
frameworkBranding?: boolean;
};
}
export interface PlayerConfig {
autoPlay?: boolean;
muted?: boolean;
volume?: number;
controls?: boolean;
loop?: boolean;
preload?: 'none' | 'metadata' | 'auto';
crossOrigin?: 'anonymous' | 'use-credentials';
playsInline?: boolean;
defaultQuality?: number;
enableAdaptiveBitrate?: boolean;
debug?: boolean;
// Playback start time
startTime?: number; // Start playback from this time in seconds (e.g., 125 for 2:05)
// Free preview
freeDuration?: number; // seconds of free playback before paywall
// Live stream indicator
isLive?: boolean; // Force LIVE indicator display (for live streams)
// Live stream waiting configuration
liveWaitingMessages?: {
waitingForStream?: string; // Default: "Waiting for Stream"
loading?: string; // Default: "Loading"
comingBack?: string; // Default: "Coming back"
};
liveMessageRotationInterval?: number; // Default: 2500ms (2.5s per message)
liveRetryInterval?: number; // Default: 5000ms (stream retry interval)
liveMaxRetryAttempts?: number; // Default: undefined (infinite)
// Live stream countdown configuration - shows timer when program hasn't started
liveCountdown?: {
/** UTC timestamp (milliseconds) when next program starts - will be converted to local time */
nextProgramStartTime?: number;
/** Message shown above countdown (default: "There is no active program in this channel.") */
noProgramMessage?: string;
/** Countdown prefix message (default: "Next program will start in:") */
countdownMessage?: string;
/** Color for the countdown timer text (default: uses theme accent color or #00d4ff) */
timerColor?: string;
/** Callback when countdown reaches zero */
onCountdownComplete?: () => void;
/** Auto-reload stream when timer ends (default: true) */
autoReloadOnComplete?: boolean;
/** Update interval in milliseconds (default: 1000) */
updateInterval?: number;
};
// Optional paywall for dynamic rental flow
paywall?: PaywallConfig;
// Share configuration
share?: ShareConfig;
// Adaptive bitrate starting configuration
adaptiveBitrate?: {
/** Starting bitrate in bps (e.g. 2_000_000 for 2 Mbps) */
startBitrate?: number;
};
// Playlist navigation controls
playlist?: {
showPrevNext?: boolean; // show ⏮/⏭ standalone buttons (deprecated — use replaceSkipWithPrevNext)
onPreviousTrack?: () => void; // fired when previous track is triggered
onNextTrack?: () => void; // fired when next track is triggered
replaceSkipWithPrevNext?: boolean; // repurpose skip-back/forward buttons as prev/next track
onPlaylistToggle?: () => void; // fired when playlist icon button in controls is clicked
};
// Fine-grained control over individual player control visibility
/**
* Fine-grained control over individual player control visibility
* All controls default to true unless explicitly set to false
*/
controlsVisibility?: ControlsVisibilityConfig;
// Settings menu configuration
/**
* Settings menu configuration
* @deprecated Use controlsVisibility.quality.settingsButton to hide/show the settings button
*/
settings?: {
enabled?: boolean; // Show/hide settings button (deprecated - use controlsVisibility.quality.settingsButton)
speed?: boolean; // Enable playback speed control
quality?: boolean; // Enable quality selection
subtitles?: boolean; // Enable subtitle selection
};
// Navigation configuration (back/close buttons)
/**
* Navigation button configuration (back/close buttons in top bar)
*/
navigation?: {
onBack?: () => void; // Callback when back button is clicked
onClose?: () => void; // Callback when close button is clicked
};
// Framework branding display
/**
* Show/hide framework branding watermark
* @deprecated Use controlsVisibility.chrome.frameworkBranding instead
*/
showFrameworkBranding?: boolean;
// Custom element to use for requestFullscreen() — defaults to the internal player wrapper
fullscreenElement?: HTMLElement;
}
/**
* Main video player interface
*/
export interface IVideoPlayer {
// Lifecycle methods
initialize(container: HTMLElement | string, config?: PlayerConfig): Promise<void>;
destroy(): Promise<void>;
// Media control
load(source: VideoSource): Promise<void>;
play(): Promise<void>;
pause(): void;
stop(): void;
seek(time: number): void;
// Volume control
setVolume(level: number): void;
mute(): void;
unmute(): void;
toggleMute(): void;
// Quality control
getQualities(): Quality[];
getCurrentQuality(): Quality | null;
setQuality(index: number): void;
setAutoQuality(enabled: boolean): void;
// Playback control
setPlaybackRate(rate: number): void;
getPlaybackRate(): number;
// State queries
getCurrentTime(): number;
getDuration(): number;
getBufferedPercentage(): number;
getState(): PlayerState;
isPlaying(): boolean;
isPaused(): boolean;
isEnded(): boolean;
// Display control
enterFullscreen(): Promise<void>;
exitFullscreen(): Promise<void>;
toggleFullscreen(): Promise<void>;
enterPictureInPicture(): Promise<void>;
exitPictureInPicture(): Promise<void>;
// Event handling
on(event: string, handler: Function): void;
off(event: string, handler?: Function): void;
once(event: string, handler: Function): void;
// Subtitle control
getSubtitles(): SubtitleTrack[];
setSubtitleTrack(index: number): void;
disableSubtitles(): void;
// Advanced features
setAudioTrack?(index: number): void;
getAudioTracks?(): any[];
getThumbnail?(time: number): string;
getStats?(): any;
// Free preview runtime controls (optional)
setFreeDuration?(seconds: number): void;
resetFreePreviewGate?(): void;
// Bandwidth detection controls (optional)
getBandwidthInfo?(): { estimated: number | null; tier: string | null; method: string | null };
redetectBandwidth?(): Promise<void>;
}