@livepeer-frameworks/player-core 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +19493 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.js +19398 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/player.css +2140 -0
- package/dist/types/core/ABRController.d.ts +164 -0
- package/dist/types/core/CodecUtils.d.ts +54 -0
- package/dist/types/core/Disposable.d.ts +61 -0
- package/dist/types/core/EventEmitter.d.ts +73 -0
- package/dist/types/core/GatewayClient.d.ts +144 -0
- package/dist/types/core/InteractionController.d.ts +121 -0
- package/dist/types/core/LiveDurationProxy.d.ts +102 -0
- package/dist/types/core/MetaTrackManager.d.ts +220 -0
- package/dist/types/core/MistReporter.d.ts +163 -0
- package/dist/types/core/MistSignaling.d.ts +148 -0
- package/dist/types/core/PlayerController.d.ts +665 -0
- package/dist/types/core/PlayerInterface.d.ts +230 -0
- package/dist/types/core/PlayerManager.d.ts +182 -0
- package/dist/types/core/PlayerRegistry.d.ts +27 -0
- package/dist/types/core/QualityMonitor.d.ts +184 -0
- package/dist/types/core/ScreenWakeLockManager.d.ts +70 -0
- package/dist/types/core/SeekingUtils.d.ts +142 -0
- package/dist/types/core/StreamStateClient.d.ts +108 -0
- package/dist/types/core/SubtitleManager.d.ts +111 -0
- package/dist/types/core/TelemetryReporter.d.ts +79 -0
- package/dist/types/core/TimeFormat.d.ts +97 -0
- package/dist/types/core/TimerManager.d.ts +83 -0
- package/dist/types/core/UrlUtils.d.ts +81 -0
- package/dist/types/core/detector.d.ts +149 -0
- package/dist/types/core/index.d.ts +49 -0
- package/dist/types/core/scorer.d.ts +167 -0
- package/dist/types/core/selector.d.ts +9 -0
- package/dist/types/index.d.ts +45 -0
- package/dist/types/lib/utils.d.ts +2 -0
- package/dist/types/players/DashJsPlayer.d.ts +102 -0
- package/dist/types/players/HlsJsPlayer.d.ts +70 -0
- package/dist/types/players/MewsWsPlayer/SourceBufferManager.d.ts +119 -0
- package/dist/types/players/MewsWsPlayer/WebSocketManager.d.ts +60 -0
- package/dist/types/players/MewsWsPlayer/index.d.ts +220 -0
- package/dist/types/players/MewsWsPlayer/types.d.ts +89 -0
- package/dist/types/players/MistPlayer.d.ts +25 -0
- package/dist/types/players/MistWebRTCPlayer/index.d.ts +133 -0
- package/dist/types/players/NativePlayer.d.ts +143 -0
- package/dist/types/players/VideoJsPlayer.d.ts +59 -0
- package/dist/types/players/WebCodecsPlayer/JitterBuffer.d.ts +118 -0
- package/dist/types/players/WebCodecsPlayer/LatencyProfiles.d.ts +64 -0
- package/dist/types/players/WebCodecsPlayer/RawChunkParser.d.ts +63 -0
- package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +174 -0
- package/dist/types/players/WebCodecsPlayer/WebSocketController.d.ts +164 -0
- package/dist/types/players/WebCodecsPlayer/index.d.ts +149 -0
- package/dist/types/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.d.ts +105 -0
- package/dist/types/players/WebCodecsPlayer/types.d.ts +395 -0
- package/dist/types/players/WebCodecsPlayer/worker/decoder.worker.d.ts +13 -0
- package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +197 -0
- package/dist/types/players/index.d.ts +14 -0
- package/dist/types/styles/index.d.ts +11 -0
- package/dist/types/types.d.ts +363 -0
- package/dist/types/vanilla/FrameWorksPlayer.d.ts +143 -0
- package/dist/types/vanilla/index.d.ts +19 -0
- package/dist/workers/decoder.worker.js +989 -0
- package/dist/workers/decoder.worker.js.map +1 -0
- package/package.json +80 -0
- package/src/core/ABRController.ts +550 -0
- package/src/core/CodecUtils.ts +257 -0
- package/src/core/Disposable.ts +120 -0
- package/src/core/EventEmitter.ts +113 -0
- package/src/core/GatewayClient.ts +439 -0
- package/src/core/InteractionController.ts +712 -0
- package/src/core/LiveDurationProxy.ts +270 -0
- package/src/core/MetaTrackManager.ts +753 -0
- package/src/core/MistReporter.ts +543 -0
- package/src/core/MistSignaling.ts +346 -0
- package/src/core/PlayerController.ts +2829 -0
- package/src/core/PlayerInterface.ts +432 -0
- package/src/core/PlayerManager.ts +900 -0
- package/src/core/PlayerRegistry.ts +149 -0
- package/src/core/QualityMonitor.ts +597 -0
- package/src/core/ScreenWakeLockManager.ts +163 -0
- package/src/core/SeekingUtils.ts +364 -0
- package/src/core/StreamStateClient.ts +457 -0
- package/src/core/SubtitleManager.ts +297 -0
- package/src/core/TelemetryReporter.ts +308 -0
- package/src/core/TimeFormat.ts +205 -0
- package/src/core/TimerManager.ts +209 -0
- package/src/core/UrlUtils.ts +179 -0
- package/src/core/detector.ts +382 -0
- package/src/core/index.ts +140 -0
- package/src/core/scorer.ts +553 -0
- package/src/core/selector.ts +16 -0
- package/src/global.d.ts +11 -0
- package/src/index.ts +75 -0
- package/src/lib/utils.ts +6 -0
- package/src/players/DashJsPlayer.ts +642 -0
- package/src/players/HlsJsPlayer.ts +483 -0
- package/src/players/MewsWsPlayer/SourceBufferManager.ts +572 -0
- package/src/players/MewsWsPlayer/WebSocketManager.ts +241 -0
- package/src/players/MewsWsPlayer/index.ts +1065 -0
- package/src/players/MewsWsPlayer/types.ts +106 -0
- package/src/players/MistPlayer.ts +188 -0
- package/src/players/MistWebRTCPlayer/index.ts +703 -0
- package/src/players/NativePlayer.ts +820 -0
- package/src/players/VideoJsPlayer.ts +643 -0
- package/src/players/WebCodecsPlayer/JitterBuffer.ts +299 -0
- package/src/players/WebCodecsPlayer/LatencyProfiles.ts +151 -0
- package/src/players/WebCodecsPlayer/RawChunkParser.ts +151 -0
- package/src/players/WebCodecsPlayer/SyncController.ts +456 -0
- package/src/players/WebCodecsPlayer/WebSocketController.ts +564 -0
- package/src/players/WebCodecsPlayer/index.ts +1650 -0
- package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +379 -0
- package/src/players/WebCodecsPlayer/types.ts +542 -0
- package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +1360 -0
- package/src/players/WebCodecsPlayer/worker/types.ts +276 -0
- package/src/players/index.ts +22 -0
- package/src/styles/animations.css +21 -0
- package/src/styles/index.ts +52 -0
- package/src/styles/player.css +2126 -0
- package/src/styles/tailwind.css +1015 -0
- package/src/types.ts +421 -0
- package/src/vanilla/FrameWorksPlayer.ts +367 -0
- package/src/vanilla/index.ts +22 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common Player Interface
|
|
3
|
+
*
|
|
4
|
+
* All player implementations must implement this interface to ensure
|
|
5
|
+
* consistent behavior and enable the PlayerManager selection system
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface StreamSource {
|
|
9
|
+
url: string;
|
|
10
|
+
type: string;
|
|
11
|
+
index?: number;
|
|
12
|
+
streamName?: string;
|
|
13
|
+
mistPlayerUrl?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface StreamTrack {
|
|
17
|
+
type: 'video' | 'audio' | 'meta';
|
|
18
|
+
codec: string;
|
|
19
|
+
codecstring?: string;
|
|
20
|
+
init?: string;
|
|
21
|
+
/** Track index from MistServer (used for binary chunk routing) */
|
|
22
|
+
idx?: number;
|
|
23
|
+
// Video-specific
|
|
24
|
+
width?: number;
|
|
25
|
+
height?: number;
|
|
26
|
+
fpks?: number; // frames per kilosecond
|
|
27
|
+
// Audio-specific
|
|
28
|
+
channels?: number;
|
|
29
|
+
rate?: number; // sample rate
|
|
30
|
+
size?: number; // bits per sample
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface StreamInfo {
|
|
34
|
+
source: StreamSource[];
|
|
35
|
+
meta: {
|
|
36
|
+
tracks: StreamTrack[];
|
|
37
|
+
};
|
|
38
|
+
type?: 'live' | 'vod';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PlayerOptions {
|
|
42
|
+
autoplay?: boolean;
|
|
43
|
+
muted?: boolean;
|
|
44
|
+
controls?: boolean;
|
|
45
|
+
loop?: boolean;
|
|
46
|
+
poster?: string;
|
|
47
|
+
width?: number;
|
|
48
|
+
height?: number;
|
|
49
|
+
/** Enable dev mode - for Legacy player, uses MistServer's dev skin with source selection */
|
|
50
|
+
devMode?: boolean;
|
|
51
|
+
/** Enable debug logging in player implementations */
|
|
52
|
+
debug?: boolean;
|
|
53
|
+
onReady?: (element: HTMLVideoElement) => void;
|
|
54
|
+
onError?: (error: string | Error) => void;
|
|
55
|
+
onPlay?: () => void;
|
|
56
|
+
onPause?: () => void;
|
|
57
|
+
onEnded?: () => void;
|
|
58
|
+
onTimeUpdate?: (currentTime: number) => void;
|
|
59
|
+
// New callbacks for buffering/state management
|
|
60
|
+
onWaiting?: () => void;
|
|
61
|
+
onPlaying?: () => void;
|
|
62
|
+
onCanPlay?: () => void;
|
|
63
|
+
onDurationChange?: (duration: number) => void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface PlayerCapability {
|
|
67
|
+
/** Player name for display */
|
|
68
|
+
name: string;
|
|
69
|
+
/** Unique identifier */
|
|
70
|
+
shortname: string;
|
|
71
|
+
/** Priority (lower number = higher priority) */
|
|
72
|
+
priority: number;
|
|
73
|
+
/** MIME types this player can handle */
|
|
74
|
+
mimes: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface PlayerEvents {
|
|
78
|
+
ready: HTMLVideoElement;
|
|
79
|
+
error: string | Error;
|
|
80
|
+
play: void;
|
|
81
|
+
pause: void;
|
|
82
|
+
ended: void;
|
|
83
|
+
timeupdate: number;
|
|
84
|
+
/** Request to reload the player (e.g., Firefox segment error recovery) */
|
|
85
|
+
reloadrequested: { reason: string };
|
|
86
|
+
/** Seekable range changed */
|
|
87
|
+
seekablechange: { start: number; end: number; bufferWindow: number };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Base interface all players must implement
|
|
92
|
+
*/
|
|
93
|
+
export interface IPlayer {
|
|
94
|
+
/** Player metadata */
|
|
95
|
+
readonly capability: PlayerCapability;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if this player supports the given MIME type
|
|
99
|
+
*/
|
|
100
|
+
isMimeSupported(mimetype: string): boolean;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Check if this player can play in the current browser environment
|
|
104
|
+
* @param mimetype - MIME type to test
|
|
105
|
+
* @param source - Source information
|
|
106
|
+
* @param streamInfo - Stream metadata
|
|
107
|
+
* @returns false if not supported, true if supported (no track info),
|
|
108
|
+
* or array of supported track types
|
|
109
|
+
*/
|
|
110
|
+
isBrowserSupported(
|
|
111
|
+
mimetype: string,
|
|
112
|
+
source: StreamSource,
|
|
113
|
+
streamInfo: StreamInfo
|
|
114
|
+
): boolean | string[];
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Initialize the player with given source and options
|
|
118
|
+
* @param container - Container element to render in
|
|
119
|
+
* @param source - Source to play
|
|
120
|
+
* @param options - Player options
|
|
121
|
+
* @param streamInfo - Full stream metadata (optional, for players that need track details)
|
|
122
|
+
* @returns Promise resolving to video element
|
|
123
|
+
*/
|
|
124
|
+
initialize(
|
|
125
|
+
container: HTMLElement,
|
|
126
|
+
source: StreamSource,
|
|
127
|
+
options: PlayerOptions,
|
|
128
|
+
streamInfo?: StreamInfo
|
|
129
|
+
): Promise<HTMLVideoElement>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Clean up and destroy the player.
|
|
133
|
+
* May be async if cleanup requires network requests (e.g., WHEP session DELETE).
|
|
134
|
+
*/
|
|
135
|
+
destroy(): void | Promise<void>;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the underlying video element (if available)
|
|
139
|
+
*/
|
|
140
|
+
getVideoElement(): HTMLVideoElement | null;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Set video size
|
|
144
|
+
*/
|
|
145
|
+
setSize?(width: number, height: number): void;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Add event listener
|
|
149
|
+
*/
|
|
150
|
+
on<K extends keyof PlayerEvents>(event: K, listener: (data: PlayerEvents[K]) => void): void;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Remove event listener
|
|
154
|
+
*/
|
|
155
|
+
off<K extends keyof PlayerEvents>(event: K, listener: (data: PlayerEvents[K]) => void): void;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get current playback state
|
|
159
|
+
*/
|
|
160
|
+
getCurrentTime?(): number;
|
|
161
|
+
getDuration?(): number;
|
|
162
|
+
isPaused?(): boolean;
|
|
163
|
+
isMuted?(): boolean;
|
|
164
|
+
/** Optional: provide an override seekable range (seconds) */
|
|
165
|
+
getSeekableRange?(): { start: number; end: number } | null;
|
|
166
|
+
/** Optional: provide buffered ranges override */
|
|
167
|
+
getBufferedRanges?(): TimeRanges | null;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Control playback
|
|
171
|
+
*/
|
|
172
|
+
play?(): Promise<void>;
|
|
173
|
+
pause?(): void;
|
|
174
|
+
seek?(time: number): void;
|
|
175
|
+
setVolume?(volume: number): void;
|
|
176
|
+
setMuted?(muted: boolean): void;
|
|
177
|
+
setPlaybackRate?(rate: number): void;
|
|
178
|
+
|
|
179
|
+
// Optional: captions/text tracks
|
|
180
|
+
getTextTracks?(): Array<{ id: string; label: string; lang?: string; active: boolean }>;
|
|
181
|
+
selectTextTrack?(id: string | null): void;
|
|
182
|
+
|
|
183
|
+
// Optional: quality/level selection
|
|
184
|
+
getQualities?(): Array<{ id: string; label: string; bitrate?: number; width?: number; height?: number; isAuto?: boolean; active?: boolean }>;
|
|
185
|
+
selectQuality?(id: string): void; // use 'auto' to enable ABR
|
|
186
|
+
getCurrentQuality?(): string | null;
|
|
187
|
+
|
|
188
|
+
// Optional: live edge helpers
|
|
189
|
+
isLive?(): boolean;
|
|
190
|
+
jumpToLive?(): void;
|
|
191
|
+
/** Optional: frame step (direction -1/1, optional step seconds) */
|
|
192
|
+
frameStep?(direction: -1 | 1, seconds?: number): void;
|
|
193
|
+
|
|
194
|
+
// Optional: PiP
|
|
195
|
+
requestPiP?(): Promise<void>;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Optional: Retrieve player-specific stats (e.g., WebRTC inbound-rtp)
|
|
199
|
+
*/
|
|
200
|
+
getStats?(): Promise<any>;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Optional: Retrieve approximate playback latency stats
|
|
204
|
+
*/
|
|
205
|
+
getLatency?(): Promise<any>;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Base class providing common functionality
|
|
210
|
+
*/
|
|
211
|
+
export abstract class BasePlayer implements IPlayer {
|
|
212
|
+
abstract readonly capability: PlayerCapability;
|
|
213
|
+
|
|
214
|
+
protected listeners: Map<string, Set<Function>> = new Map();
|
|
215
|
+
protected videoElement: HTMLVideoElement | null = null;
|
|
216
|
+
|
|
217
|
+
abstract isMimeSupported(mimetype: string): boolean;
|
|
218
|
+
abstract isBrowserSupported(mimetype: string, source: StreamSource, streamInfo: StreamInfo): boolean | string[];
|
|
219
|
+
abstract initialize(container: HTMLElement, source: StreamSource, options: PlayerOptions, streamInfo?: StreamInfo): Promise<HTMLVideoElement>;
|
|
220
|
+
abstract destroy(): void | Promise<void>;
|
|
221
|
+
|
|
222
|
+
getVideoElement(): HTMLVideoElement | null {
|
|
223
|
+
return this.videoElement;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
on<K extends keyof PlayerEvents>(event: K, listener: (data: PlayerEvents[K]) => void): void {
|
|
227
|
+
if (!this.listeners.has(event)) {
|
|
228
|
+
this.listeners.set(event, new Set());
|
|
229
|
+
}
|
|
230
|
+
this.listeners.get(event)!.add(listener);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
off<K extends keyof PlayerEvents>(event: K, listener: (data: PlayerEvents[K]) => void): void {
|
|
234
|
+
const eventListeners = this.listeners.get(event);
|
|
235
|
+
if (eventListeners) {
|
|
236
|
+
eventListeners.delete(listener);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
protected emit<K extends keyof PlayerEvents>(event: K, data: PlayerEvents[K]): void {
|
|
241
|
+
const eventListeners = this.listeners.get(event);
|
|
242
|
+
if (eventListeners) {
|
|
243
|
+
eventListeners.forEach(listener => {
|
|
244
|
+
try {
|
|
245
|
+
listener(data);
|
|
246
|
+
} catch (e) {
|
|
247
|
+
console.error(`Error in ${event} listener:`, e);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
protected setupVideoEventListeners(video: HTMLVideoElement, options: PlayerOptions): void {
|
|
254
|
+
const handleEvent = (eventName: keyof PlayerEvents, handler: () => void) => {
|
|
255
|
+
const listener = () => {
|
|
256
|
+
handler();
|
|
257
|
+
this.emit(eventName as any, undefined as any);
|
|
258
|
+
};
|
|
259
|
+
video.addEventListener(eventName, listener);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// Core playback events
|
|
263
|
+
handleEvent('play', () => options.onPlay?.());
|
|
264
|
+
handleEvent('pause', () => options.onPause?.());
|
|
265
|
+
handleEvent('ended', () => options.onEnded?.());
|
|
266
|
+
|
|
267
|
+
// Buffering/state events (previously duplicated in Player.tsx onReady)
|
|
268
|
+
video.addEventListener('waiting', () => options.onWaiting?.());
|
|
269
|
+
video.addEventListener('playing', () => options.onPlaying?.());
|
|
270
|
+
video.addEventListener('canplay', () => options.onCanPlay?.());
|
|
271
|
+
|
|
272
|
+
video.addEventListener('durationchange', () => {
|
|
273
|
+
options.onDurationChange?.(video.duration);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
video.addEventListener('timeupdate', () => {
|
|
277
|
+
const currentTime = video.currentTime;
|
|
278
|
+
options.onTimeUpdate?.(currentTime);
|
|
279
|
+
this.emit('timeupdate', currentTime);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
video.addEventListener('error', () => {
|
|
283
|
+
const error = video.error ?
|
|
284
|
+
`Video error: ${video.error.message}` :
|
|
285
|
+
'Unknown video error';
|
|
286
|
+
options.onError?.(error);
|
|
287
|
+
this.emit('error', error);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Call onReady LAST - after all listeners are attached
|
|
291
|
+
// This prevents race conditions where events fire before handlers exist
|
|
292
|
+
this.emit('ready', video);
|
|
293
|
+
if (options.onReady) {
|
|
294
|
+
options.onReady(video);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Default implementations for optional methods
|
|
299
|
+
getCurrentTime(): number {
|
|
300
|
+
return this.videoElement?.currentTime || 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
getDuration(): number {
|
|
304
|
+
return this.videoElement?.duration || 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
getSeekableRange(): { start: number; end: number } | null {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
getBufferedRanges(): TimeRanges | null {
|
|
312
|
+
return this.videoElement?.buffered ?? null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
isPaused(): boolean {
|
|
316
|
+
return this.videoElement?.paused ?? true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
isMuted(): boolean {
|
|
320
|
+
return this.videoElement?.muted ?? false;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async play(): Promise<void> {
|
|
324
|
+
if (this.videoElement) {
|
|
325
|
+
return this.videoElement.play();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
pause(): void {
|
|
330
|
+
this.videoElement?.pause();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
seek(time: number): void {
|
|
334
|
+
if (this.videoElement) {
|
|
335
|
+
this.videoElement.currentTime = time;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
setVolume(volume: number): void {
|
|
340
|
+
if (this.videoElement) {
|
|
341
|
+
this.videoElement.volume = Math.max(0, Math.min(1, volume));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
setMuted(muted: boolean): void {
|
|
346
|
+
if (this.videoElement) {
|
|
347
|
+
this.videoElement.muted = muted;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
setPlaybackRate(rate: number): void {
|
|
351
|
+
if (this.videoElement) {
|
|
352
|
+
this.videoElement.playbackRate = rate;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Default captions/text tracks using native TextTrack API
|
|
357
|
+
getTextTracks(): Array<{ id: string; label: string; lang?: string; active: boolean }> {
|
|
358
|
+
const video = this.videoElement;
|
|
359
|
+
if (!video || !video.textTracks) return [];
|
|
360
|
+
const out: Array<{ id: string; label: string; lang?: string; active: boolean }> = [];
|
|
361
|
+
const list = video.textTracks as any as TextTrackList;
|
|
362
|
+
for (let i = 0; i < list.length; i++) {
|
|
363
|
+
const tt = list[i];
|
|
364
|
+
out.push({ id: String(i), label: tt.label || `CC ${i+1}`, lang: (tt as any).language, active: tt.mode === 'showing' });
|
|
365
|
+
}
|
|
366
|
+
return out;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
selectTextTrack(id: string | null): void {
|
|
370
|
+
const video = this.videoElement;
|
|
371
|
+
if (!video || !video.textTracks) return;
|
|
372
|
+
const list = video.textTracks as any as TextTrackList;
|
|
373
|
+
for (let i = 0; i < list.length; i++) {
|
|
374
|
+
const tt = list[i];
|
|
375
|
+
if (id !== null && String(i) === id) {
|
|
376
|
+
tt.mode = 'showing';
|
|
377
|
+
} else {
|
|
378
|
+
tt.mode = 'disabled';
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Default live helpers
|
|
384
|
+
isLive(): boolean {
|
|
385
|
+
const v = this.videoElement;
|
|
386
|
+
if (!v) return false;
|
|
387
|
+
return !isFinite(v.duration) || v.duration === Infinity;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
jumpToLive(): void {
|
|
391
|
+
const v = this.videoElement;
|
|
392
|
+
if (!v) return;
|
|
393
|
+
const seekable = v.seekable;
|
|
394
|
+
if (seekable && seekable.length > 0) {
|
|
395
|
+
try { v.currentTime = seekable.end(seekable.length - 1); } catch {}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Default PiP helper
|
|
400
|
+
async requestPiP(): Promise<void> {
|
|
401
|
+
const v: any = this.videoElement as any;
|
|
402
|
+
if (!v) return;
|
|
403
|
+
// Exit if already in PiP
|
|
404
|
+
if (document.pictureInPictureElement === v) {
|
|
405
|
+
try { await (document as any).exitPictureInPicture?.(); } catch {}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
if (v.requestPictureInPicture) {
|
|
410
|
+
await v.requestPictureInPicture();
|
|
411
|
+
} else if (v.webkitSetPresentationMode) {
|
|
412
|
+
v.webkitSetPresentationMode('picture-in-picture');
|
|
413
|
+
}
|
|
414
|
+
} catch {}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
setSize(width: number, height: number): void {
|
|
418
|
+
if (this.videoElement) {
|
|
419
|
+
this.videoElement.style.width = `${width}px`;
|
|
420
|
+
this.videoElement.style.height = `${height}px`;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Default optional stats methods
|
|
425
|
+
async getStats(): Promise<any> {
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async getLatency(): Promise<any> {
|
|
430
|
+
return undefined;
|
|
431
|
+
}
|
|
432
|
+
}
|