@livepeer-frameworks/player-core 0.1.0 → 0.1.1
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/README.md +11 -9
- package/dist/player.css +182 -42
- package/package.json +1 -1
- package/src/core/ABRController.ts +38 -36
- package/src/core/CodecUtils.ts +49 -46
- package/src/core/Disposable.ts +4 -4
- package/src/core/EventEmitter.ts +1 -1
- package/src/core/GatewayClient.ts +41 -39
- package/src/core/InteractionController.ts +89 -82
- package/src/core/LiveDurationProxy.ts +14 -15
- package/src/core/MetaTrackManager.ts +73 -65
- package/src/core/MistReporter.ts +72 -45
- package/src/core/MistSignaling.ts +59 -56
- package/src/core/PlayerController.ts +527 -384
- package/src/core/PlayerInterface.ts +83 -59
- package/src/core/PlayerManager.ts +79 -133
- package/src/core/PlayerRegistry.ts +59 -42
- package/src/core/QualityMonitor.ts +38 -31
- package/src/core/ScreenWakeLockManager.ts +8 -9
- package/src/core/SeekingUtils.ts +31 -22
- package/src/core/StreamStateClient.ts +74 -68
- package/src/core/SubtitleManager.ts +24 -22
- package/src/core/TelemetryReporter.ts +34 -31
- package/src/core/TimeFormat.ts +13 -17
- package/src/core/TimerManager.ts +24 -8
- package/src/core/UrlUtils.ts +20 -17
- package/src/core/detector.ts +44 -44
- package/src/core/index.ts +57 -48
- package/src/core/scorer.ts +136 -141
- package/src/core/selector.ts +2 -6
- package/src/global.d.ts +1 -1
- package/src/index.ts +46 -35
- package/src/players/DashJsPlayer.ts +164 -115
- package/src/players/HlsJsPlayer.ts +132 -78
- package/src/players/MewsWsPlayer/SourceBufferManager.ts +41 -36
- package/src/players/MewsWsPlayer/WebSocketManager.ts +9 -9
- package/src/players/MewsWsPlayer/index.ts +192 -152
- package/src/players/MewsWsPlayer/types.ts +21 -21
- package/src/players/MistPlayer.ts +45 -26
- package/src/players/MistWebRTCPlayer/index.ts +175 -129
- package/src/players/NativePlayer.ts +203 -143
- package/src/players/VideoJsPlayer.ts +170 -118
- package/src/players/WebCodecsPlayer/JitterBuffer.ts +6 -7
- package/src/players/WebCodecsPlayer/LatencyProfiles.ts +43 -43
- package/src/players/WebCodecsPlayer/RawChunkParser.ts +10 -10
- package/src/players/WebCodecsPlayer/SyncController.ts +45 -53
- package/src/players/WebCodecsPlayer/WebSocketController.ts +66 -68
- package/src/players/WebCodecsPlayer/index.ts +263 -221
- package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +12 -17
- package/src/players/WebCodecsPlayer/types.ts +56 -56
- package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +238 -182
- package/src/players/WebCodecsPlayer/worker/types.ts +31 -31
- package/src/players/index.ts +8 -8
- package/src/styles/animations.css +2 -1
- package/src/styles/player.css +182 -42
- package/src/styles/tailwind.css +473 -159
- package/src/types.ts +43 -43
- package/src/vanilla/FrameWorksPlayer.ts +29 -14
- package/src/vanilla/index.ts +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Common Player Interface
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* All player implementations must implement this interface to ensure
|
|
5
5
|
* consistent behavior and enable the PlayerManager selection system
|
|
6
6
|
*/
|
|
@@ -14,7 +14,7 @@ export interface StreamSource {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export interface StreamTrack {
|
|
17
|
-
type:
|
|
17
|
+
type: "video" | "audio" | "meta";
|
|
18
18
|
codec: string;
|
|
19
19
|
codecstring?: string;
|
|
20
20
|
init?: string;
|
|
@@ -35,7 +35,7 @@ export interface StreamInfo {
|
|
|
35
35
|
meta: {
|
|
36
36
|
tracks: StreamTrack[];
|
|
37
37
|
};
|
|
38
|
-
type?:
|
|
38
|
+
type?: "live" | "vod";
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
export interface PlayerOptions {
|
|
@@ -99,26 +99,26 @@ export interface PlayerEvents {
|
|
|
99
99
|
export interface IPlayer {
|
|
100
100
|
/** Player metadata */
|
|
101
101
|
readonly capability: PlayerCapability;
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
/**
|
|
104
104
|
* Check if this player supports the given MIME type
|
|
105
105
|
*/
|
|
106
106
|
isMimeSupported(mimetype: string): boolean;
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
/**
|
|
109
109
|
* Check if this player can play in the current browser environment
|
|
110
110
|
* @param mimetype - MIME type to test
|
|
111
111
|
* @param source - Source information
|
|
112
112
|
* @param streamInfo - Stream metadata
|
|
113
|
-
* @returns false if not supported, true if supported (no track info),
|
|
113
|
+
* @returns false if not supported, true if supported (no track info),
|
|
114
114
|
* or array of supported track types
|
|
115
115
|
*/
|
|
116
116
|
isBrowserSupported(
|
|
117
|
-
mimetype: string,
|
|
118
|
-
source: StreamSource,
|
|
117
|
+
mimetype: string,
|
|
118
|
+
source: StreamSource,
|
|
119
119
|
streamInfo: StreamInfo
|
|
120
120
|
): boolean | string[];
|
|
121
|
-
|
|
121
|
+
|
|
122
122
|
/**
|
|
123
123
|
* Initialize the player with given source and options
|
|
124
124
|
* @param container - Container element to render in
|
|
@@ -133,33 +133,33 @@ export interface IPlayer {
|
|
|
133
133
|
options: PlayerOptions,
|
|
134
134
|
streamInfo?: StreamInfo
|
|
135
135
|
): Promise<HTMLVideoElement>;
|
|
136
|
-
|
|
136
|
+
|
|
137
137
|
/**
|
|
138
138
|
* Clean up and destroy the player.
|
|
139
139
|
* May be async if cleanup requires network requests (e.g., WHEP session DELETE).
|
|
140
140
|
*/
|
|
141
141
|
destroy(): void | Promise<void>;
|
|
142
|
-
|
|
142
|
+
|
|
143
143
|
/**
|
|
144
144
|
* Get the underlying video element (if available)
|
|
145
145
|
*/
|
|
146
146
|
getVideoElement(): HTMLVideoElement | null;
|
|
147
|
-
|
|
147
|
+
|
|
148
148
|
/**
|
|
149
149
|
* Set video size
|
|
150
150
|
*/
|
|
151
151
|
setSize?(width: number, height: number): void;
|
|
152
|
-
|
|
152
|
+
|
|
153
153
|
/**
|
|
154
154
|
* Add event listener
|
|
155
155
|
*/
|
|
156
156
|
on<K extends keyof PlayerEvents>(event: K, listener: (data: PlayerEvents[K]) => void): void;
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
/**
|
|
159
159
|
* Remove event listener
|
|
160
160
|
*/
|
|
161
161
|
off<K extends keyof PlayerEvents>(event: K, listener: (data: PlayerEvents[K]) => void): void;
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
/**
|
|
164
164
|
* Get current playback state
|
|
165
165
|
*/
|
|
@@ -171,7 +171,7 @@ export interface IPlayer {
|
|
|
171
171
|
getSeekableRange?(): { start: number; end: number } | null;
|
|
172
172
|
/** Optional: provide buffered ranges override */
|
|
173
173
|
getBufferedRanges?(): TimeRanges | null;
|
|
174
|
-
|
|
174
|
+
|
|
175
175
|
/**
|
|
176
176
|
* Control playback
|
|
177
177
|
*/
|
|
@@ -187,7 +187,15 @@ export interface IPlayer {
|
|
|
187
187
|
selectTextTrack?(id: string | null): void;
|
|
188
188
|
|
|
189
189
|
// Optional: quality/level selection
|
|
190
|
-
getQualities?(): Array<{
|
|
190
|
+
getQualities?(): Array<{
|
|
191
|
+
id: string;
|
|
192
|
+
label: string;
|
|
193
|
+
bitrate?: number;
|
|
194
|
+
width?: number;
|
|
195
|
+
height?: number;
|
|
196
|
+
isAuto?: boolean;
|
|
197
|
+
active?: boolean;
|
|
198
|
+
}>;
|
|
191
199
|
selectQuality?(id: string): void; // use 'auto' to enable ABR
|
|
192
200
|
getCurrentQuality?(): string | null;
|
|
193
201
|
|
|
@@ -216,37 +224,46 @@ export interface IPlayer {
|
|
|
216
224
|
*/
|
|
217
225
|
export abstract class BasePlayer implements IPlayer {
|
|
218
226
|
abstract readonly capability: PlayerCapability;
|
|
219
|
-
|
|
227
|
+
|
|
220
228
|
protected listeners: Map<string, Set<Function>> = new Map();
|
|
221
229
|
protected videoElement: HTMLVideoElement | null = null;
|
|
222
|
-
|
|
230
|
+
|
|
223
231
|
abstract isMimeSupported(mimetype: string): boolean;
|
|
224
|
-
abstract isBrowserSupported(
|
|
225
|
-
|
|
232
|
+
abstract isBrowserSupported(
|
|
233
|
+
mimetype: string,
|
|
234
|
+
source: StreamSource,
|
|
235
|
+
streamInfo: StreamInfo
|
|
236
|
+
): boolean | string[];
|
|
237
|
+
abstract initialize(
|
|
238
|
+
container: HTMLElement,
|
|
239
|
+
source: StreamSource,
|
|
240
|
+
options: PlayerOptions,
|
|
241
|
+
streamInfo?: StreamInfo
|
|
242
|
+
): Promise<HTMLVideoElement>;
|
|
226
243
|
abstract destroy(): void | Promise<void>;
|
|
227
|
-
|
|
244
|
+
|
|
228
245
|
getVideoElement(): HTMLVideoElement | null {
|
|
229
246
|
return this.videoElement;
|
|
230
247
|
}
|
|
231
|
-
|
|
248
|
+
|
|
232
249
|
on<K extends keyof PlayerEvents>(event: K, listener: (data: PlayerEvents[K]) => void): void {
|
|
233
250
|
if (!this.listeners.has(event)) {
|
|
234
251
|
this.listeners.set(event, new Set());
|
|
235
252
|
}
|
|
236
253
|
this.listeners.get(event)!.add(listener);
|
|
237
254
|
}
|
|
238
|
-
|
|
255
|
+
|
|
239
256
|
off<K extends keyof PlayerEvents>(event: K, listener: (data: PlayerEvents[K]) => void): void {
|
|
240
257
|
const eventListeners = this.listeners.get(event);
|
|
241
258
|
if (eventListeners) {
|
|
242
259
|
eventListeners.delete(listener);
|
|
243
260
|
}
|
|
244
261
|
}
|
|
245
|
-
|
|
262
|
+
|
|
246
263
|
protected emit<K extends keyof PlayerEvents>(event: K, data: PlayerEvents[K]): void {
|
|
247
264
|
const eventListeners = this.listeners.get(event);
|
|
248
265
|
if (eventListeners) {
|
|
249
|
-
eventListeners.forEach(listener => {
|
|
266
|
+
eventListeners.forEach((listener) => {
|
|
250
267
|
try {
|
|
251
268
|
listener(data);
|
|
252
269
|
} catch (e) {
|
|
@@ -255,7 +272,7 @@ export abstract class BasePlayer implements IPlayer {
|
|
|
255
272
|
});
|
|
256
273
|
}
|
|
257
274
|
}
|
|
258
|
-
|
|
275
|
+
|
|
259
276
|
protected setupVideoEventListeners(video: HTMLVideoElement, options: PlayerOptions): void {
|
|
260
277
|
const handleEvent = (eventName: keyof PlayerEvents, handler: () => void) => {
|
|
261
278
|
const listener = () => {
|
|
@@ -266,46 +283,44 @@ export abstract class BasePlayer implements IPlayer {
|
|
|
266
283
|
};
|
|
267
284
|
|
|
268
285
|
// Core playback events
|
|
269
|
-
handleEvent(
|
|
270
|
-
handleEvent(
|
|
271
|
-
handleEvent(
|
|
286
|
+
handleEvent("play", () => options.onPlay?.());
|
|
287
|
+
handleEvent("pause", () => options.onPause?.());
|
|
288
|
+
handleEvent("ended", () => options.onEnded?.());
|
|
272
289
|
|
|
273
290
|
// Buffering/state events (previously duplicated in Player.tsx onReady)
|
|
274
|
-
video.addEventListener(
|
|
275
|
-
video.addEventListener(
|
|
276
|
-
video.addEventListener(
|
|
291
|
+
video.addEventListener("waiting", () => options.onWaiting?.());
|
|
292
|
+
video.addEventListener("playing", () => options.onPlaying?.());
|
|
293
|
+
video.addEventListener("canplay", () => options.onCanPlay?.());
|
|
277
294
|
|
|
278
|
-
video.addEventListener(
|
|
295
|
+
video.addEventListener("durationchange", () => {
|
|
279
296
|
options.onDurationChange?.(video.duration);
|
|
280
297
|
});
|
|
281
298
|
|
|
282
|
-
video.addEventListener(
|
|
299
|
+
video.addEventListener("timeupdate", () => {
|
|
283
300
|
const currentTime = video.currentTime;
|
|
284
301
|
options.onTimeUpdate?.(currentTime);
|
|
285
|
-
this.emit(
|
|
302
|
+
this.emit("timeupdate", currentTime);
|
|
286
303
|
});
|
|
287
304
|
|
|
288
|
-
video.addEventListener(
|
|
289
|
-
const error = video.error ?
|
|
290
|
-
`Video error: ${video.error.message}` :
|
|
291
|
-
'Unknown video error';
|
|
305
|
+
video.addEventListener("error", () => {
|
|
306
|
+
const error = video.error ? `Video error: ${video.error.message}` : "Unknown video error";
|
|
292
307
|
options.onError?.(error);
|
|
293
|
-
this.emit(
|
|
308
|
+
this.emit("error", error);
|
|
294
309
|
});
|
|
295
310
|
|
|
296
311
|
// Call onReady LAST - after all listeners are attached
|
|
297
312
|
// This prevents race conditions where events fire before handlers exist
|
|
298
|
-
this.emit(
|
|
313
|
+
this.emit("ready", video);
|
|
299
314
|
if (options.onReady) {
|
|
300
315
|
options.onReady(video);
|
|
301
316
|
}
|
|
302
317
|
}
|
|
303
|
-
|
|
318
|
+
|
|
304
319
|
// Default implementations for optional methods
|
|
305
320
|
getCurrentTime(): number {
|
|
306
321
|
return this.videoElement?.currentTime || 0;
|
|
307
322
|
}
|
|
308
|
-
|
|
323
|
+
|
|
309
324
|
getDuration(): number {
|
|
310
325
|
return this.videoElement?.duration || 0;
|
|
311
326
|
}
|
|
@@ -317,37 +332,37 @@ export abstract class BasePlayer implements IPlayer {
|
|
|
317
332
|
getBufferedRanges(): TimeRanges | null {
|
|
318
333
|
return this.videoElement?.buffered ?? null;
|
|
319
334
|
}
|
|
320
|
-
|
|
335
|
+
|
|
321
336
|
isPaused(): boolean {
|
|
322
337
|
return this.videoElement?.paused ?? true;
|
|
323
338
|
}
|
|
324
|
-
|
|
339
|
+
|
|
325
340
|
isMuted(): boolean {
|
|
326
341
|
return this.videoElement?.muted ?? false;
|
|
327
342
|
}
|
|
328
|
-
|
|
343
|
+
|
|
329
344
|
async play(): Promise<void> {
|
|
330
345
|
if (this.videoElement) {
|
|
331
346
|
return this.videoElement.play();
|
|
332
347
|
}
|
|
333
348
|
}
|
|
334
|
-
|
|
349
|
+
|
|
335
350
|
pause(): void {
|
|
336
351
|
this.videoElement?.pause();
|
|
337
352
|
}
|
|
338
|
-
|
|
353
|
+
|
|
339
354
|
seek(time: number): void {
|
|
340
355
|
if (this.videoElement) {
|
|
341
356
|
this.videoElement.currentTime = time;
|
|
342
357
|
}
|
|
343
358
|
}
|
|
344
|
-
|
|
359
|
+
|
|
345
360
|
setVolume(volume: number): void {
|
|
346
361
|
if (this.videoElement) {
|
|
347
362
|
this.videoElement.volume = Math.max(0, Math.min(1, volume));
|
|
348
363
|
}
|
|
349
364
|
}
|
|
350
|
-
|
|
365
|
+
|
|
351
366
|
setMuted(muted: boolean): void {
|
|
352
367
|
if (this.videoElement) {
|
|
353
368
|
this.videoElement.muted = muted;
|
|
@@ -358,7 +373,7 @@ export abstract class BasePlayer implements IPlayer {
|
|
|
358
373
|
this.videoElement.playbackRate = rate;
|
|
359
374
|
}
|
|
360
375
|
}
|
|
361
|
-
|
|
376
|
+
|
|
362
377
|
// Default captions/text tracks using native TextTrack API
|
|
363
378
|
getTextTracks(): Array<{ id: string; label: string; lang?: string; active: boolean }> {
|
|
364
379
|
const video = this.videoElement;
|
|
@@ -367,7 +382,12 @@ export abstract class BasePlayer implements IPlayer {
|
|
|
367
382
|
const list = video.textTracks as any as TextTrackList;
|
|
368
383
|
for (let i = 0; i < list.length; i++) {
|
|
369
384
|
const tt = list[i];
|
|
370
|
-
out.push({
|
|
385
|
+
out.push({
|
|
386
|
+
id: String(i),
|
|
387
|
+
label: tt.label || `CC ${i + 1}`,
|
|
388
|
+
lang: (tt as any).language,
|
|
389
|
+
active: tt.mode === "showing",
|
|
390
|
+
});
|
|
371
391
|
}
|
|
372
392
|
return out;
|
|
373
393
|
}
|
|
@@ -379,9 +399,9 @@ export abstract class BasePlayer implements IPlayer {
|
|
|
379
399
|
for (let i = 0; i < list.length; i++) {
|
|
380
400
|
const tt = list[i];
|
|
381
401
|
if (id !== null && String(i) === id) {
|
|
382
|
-
tt.mode =
|
|
402
|
+
tt.mode = "showing";
|
|
383
403
|
} else {
|
|
384
|
-
tt.mode =
|
|
404
|
+
tt.mode = "disabled";
|
|
385
405
|
}
|
|
386
406
|
}
|
|
387
407
|
}
|
|
@@ -398,7 +418,9 @@ export abstract class BasePlayer implements IPlayer {
|
|
|
398
418
|
if (!v) return;
|
|
399
419
|
const seekable = v.seekable;
|
|
400
420
|
if (seekable && seekable.length > 0) {
|
|
401
|
-
try {
|
|
421
|
+
try {
|
|
422
|
+
v.currentTime = seekable.end(seekable.length - 1);
|
|
423
|
+
} catch {}
|
|
402
424
|
}
|
|
403
425
|
}
|
|
404
426
|
|
|
@@ -408,18 +430,20 @@ export abstract class BasePlayer implements IPlayer {
|
|
|
408
430
|
if (!v) return;
|
|
409
431
|
// Exit if already in PiP
|
|
410
432
|
if (document.pictureInPictureElement === v) {
|
|
411
|
-
try {
|
|
433
|
+
try {
|
|
434
|
+
await (document as any).exitPictureInPicture?.();
|
|
435
|
+
} catch {}
|
|
412
436
|
return;
|
|
413
437
|
}
|
|
414
438
|
try {
|
|
415
439
|
if (v.requestPictureInPicture) {
|
|
416
440
|
await v.requestPictureInPicture();
|
|
417
441
|
} else if (v.webkitSetPresentationMode) {
|
|
418
|
-
v.webkitSetPresentationMode(
|
|
442
|
+
v.webkitSetPresentationMode("picture-in-picture");
|
|
419
443
|
}
|
|
420
444
|
} catch {}
|
|
421
445
|
}
|
|
422
|
-
|
|
446
|
+
|
|
423
447
|
setSize(width: number, height: number): void {
|
|
424
448
|
if (this.videoElement) {
|
|
425
449
|
this.videoElement.style.width = `${width}px`;
|