@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.
Files changed (59) hide show
  1. package/README.md +11 -9
  2. package/dist/player.css +182 -42
  3. package/package.json +1 -1
  4. package/src/core/ABRController.ts +38 -36
  5. package/src/core/CodecUtils.ts +49 -46
  6. package/src/core/Disposable.ts +4 -4
  7. package/src/core/EventEmitter.ts +1 -1
  8. package/src/core/GatewayClient.ts +41 -39
  9. package/src/core/InteractionController.ts +89 -82
  10. package/src/core/LiveDurationProxy.ts +14 -15
  11. package/src/core/MetaTrackManager.ts +73 -65
  12. package/src/core/MistReporter.ts +72 -45
  13. package/src/core/MistSignaling.ts +59 -56
  14. package/src/core/PlayerController.ts +527 -384
  15. package/src/core/PlayerInterface.ts +83 -59
  16. package/src/core/PlayerManager.ts +79 -133
  17. package/src/core/PlayerRegistry.ts +59 -42
  18. package/src/core/QualityMonitor.ts +38 -31
  19. package/src/core/ScreenWakeLockManager.ts +8 -9
  20. package/src/core/SeekingUtils.ts +31 -22
  21. package/src/core/StreamStateClient.ts +74 -68
  22. package/src/core/SubtitleManager.ts +24 -22
  23. package/src/core/TelemetryReporter.ts +34 -31
  24. package/src/core/TimeFormat.ts +13 -17
  25. package/src/core/TimerManager.ts +24 -8
  26. package/src/core/UrlUtils.ts +20 -17
  27. package/src/core/detector.ts +44 -44
  28. package/src/core/index.ts +57 -48
  29. package/src/core/scorer.ts +136 -141
  30. package/src/core/selector.ts +2 -6
  31. package/src/global.d.ts +1 -1
  32. package/src/index.ts +46 -35
  33. package/src/players/DashJsPlayer.ts +164 -115
  34. package/src/players/HlsJsPlayer.ts +132 -78
  35. package/src/players/MewsWsPlayer/SourceBufferManager.ts +41 -36
  36. package/src/players/MewsWsPlayer/WebSocketManager.ts +9 -9
  37. package/src/players/MewsWsPlayer/index.ts +192 -152
  38. package/src/players/MewsWsPlayer/types.ts +21 -21
  39. package/src/players/MistPlayer.ts +45 -26
  40. package/src/players/MistWebRTCPlayer/index.ts +175 -129
  41. package/src/players/NativePlayer.ts +203 -143
  42. package/src/players/VideoJsPlayer.ts +170 -118
  43. package/src/players/WebCodecsPlayer/JitterBuffer.ts +6 -7
  44. package/src/players/WebCodecsPlayer/LatencyProfiles.ts +43 -43
  45. package/src/players/WebCodecsPlayer/RawChunkParser.ts +10 -10
  46. package/src/players/WebCodecsPlayer/SyncController.ts +45 -53
  47. package/src/players/WebCodecsPlayer/WebSocketController.ts +66 -68
  48. package/src/players/WebCodecsPlayer/index.ts +263 -221
  49. package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +12 -17
  50. package/src/players/WebCodecsPlayer/types.ts +56 -56
  51. package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +238 -182
  52. package/src/players/WebCodecsPlayer/worker/types.ts +31 -31
  53. package/src/players/index.ts +8 -8
  54. package/src/styles/animations.css +2 -1
  55. package/src/styles/player.css +182 -42
  56. package/src/styles/tailwind.css +473 -159
  57. package/src/types.ts +43 -43
  58. package/src/vanilla/FrameWorksPlayer.ts +29 -14
  59. 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: 'video' | 'audio' | 'meta';
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?: 'live' | 'vod';
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<{ id: string; label: string; bitrate?: number; width?: number; height?: number; isAuto?: boolean; active?: boolean }>;
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(mimetype: string, source: StreamSource, streamInfo: StreamInfo): boolean | string[];
225
- abstract initialize(container: HTMLElement, source: StreamSource, options: PlayerOptions, streamInfo?: StreamInfo): Promise<HTMLVideoElement>;
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('play', () => options.onPlay?.());
270
- handleEvent('pause', () => options.onPause?.());
271
- handleEvent('ended', () => options.onEnded?.());
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('waiting', () => options.onWaiting?.());
275
- video.addEventListener('playing', () => options.onPlaying?.());
276
- video.addEventListener('canplay', () => options.onCanPlay?.());
291
+ video.addEventListener("waiting", () => options.onWaiting?.());
292
+ video.addEventListener("playing", () => options.onPlaying?.());
293
+ video.addEventListener("canplay", () => options.onCanPlay?.());
277
294
 
278
- video.addEventListener('durationchange', () => {
295
+ video.addEventListener("durationchange", () => {
279
296
  options.onDurationChange?.(video.duration);
280
297
  });
281
298
 
282
- video.addEventListener('timeupdate', () => {
299
+ video.addEventListener("timeupdate", () => {
283
300
  const currentTime = video.currentTime;
284
301
  options.onTimeUpdate?.(currentTime);
285
- this.emit('timeupdate', currentTime);
302
+ this.emit("timeupdate", currentTime);
286
303
  });
287
304
 
288
- video.addEventListener('error', () => {
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('error', error);
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('ready', video);
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({ id: String(i), label: tt.label || `CC ${i+1}`, lang: (tt as any).language, active: tt.mode === 'showing' });
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 = 'showing';
402
+ tt.mode = "showing";
383
403
  } else {
384
- tt.mode = 'disabled';
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 { v.currentTime = seekable.end(seekable.length - 1); } catch {}
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 { await (document as any).exitPictureInPicture?.(); } catch {}
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('picture-in-picture');
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`;