@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
@@ -45,11 +45,11 @@ export interface InteractionState {
45
45
  }
46
46
 
47
47
  // Timing constants
48
- const HOLD_THRESHOLD_MS = 200; // Time before keydown becomes "hold" vs "tap"
48
+ const HOLD_THRESHOLD_MS = 200; // Time before keydown becomes "hold" vs "tap"
49
49
  const LONG_PRESS_THRESHOLD_MS = 300; // Time for touch/click to become "hold"
50
- const DOUBLE_TAP_WINDOW_MS = 300; // Window for detecting double-tap
51
- const SKIP_AMOUNT_SECONDS = 10; // Skip forward/backward amount
52
- const VOLUME_STEP = 0.1; // Volume change per arrow press (10%)
50
+ const DOUBLE_TAP_WINDOW_MS = 300; // Window for detecting double-tap
51
+ const SKIP_AMOUNT_SECONDS = 10; // Skip forward/backward amount
52
+ const VOLUME_STEP = 0.1; // Volume change per arrow press (10%)
53
53
  const DEFAULT_IDLE_TIMEOUT_MS = 5000; // Default idle timeout (5 seconds)
54
54
 
55
55
  export class InteractionController {
@@ -117,30 +117,30 @@ export class InteractionController {
117
117
  const { container } = this.config;
118
118
 
119
119
  // Make container focusable for keyboard events
120
- if (!container.hasAttribute('tabindex')) {
121
- container.setAttribute('tabindex', '0');
120
+ if (!container.hasAttribute("tabindex")) {
121
+ container.setAttribute("tabindex", "0");
122
122
  }
123
123
 
124
124
  // Keyboard events
125
- container.addEventListener('keydown', this.boundKeyDown);
126
- container.addEventListener('keyup', this.boundKeyUp);
127
- document.addEventListener('keydown', this.boundDocumentKeyDown);
128
- document.addEventListener('keyup', this.boundDocumentKeyUp);
125
+ container.addEventListener("keydown", this.boundKeyDown);
126
+ container.addEventListener("keyup", this.boundKeyUp);
127
+ document.addEventListener("keydown", this.boundDocumentKeyDown);
128
+ document.addEventListener("keyup", this.boundDocumentKeyUp);
129
129
 
130
130
  // Pointer events (unified mouse + touch)
131
- container.addEventListener('pointerdown', this.boundPointerDown);
132
- container.addEventListener('pointerup', this.boundPointerUp);
133
- container.addEventListener('pointercancel', this.boundPointerCancel);
134
- container.addEventListener('pointerleave', this.boundPointerCancel);
131
+ container.addEventListener("pointerdown", this.boundPointerDown);
132
+ container.addEventListener("pointerup", this.boundPointerUp);
133
+ container.addEventListener("pointercancel", this.boundPointerCancel);
134
+ container.addEventListener("pointerleave", this.boundPointerCancel);
135
135
 
136
136
  // Mouse move for idle detection
137
- container.addEventListener('mousemove', this.boundMouseMove);
137
+ container.addEventListener("mousemove", this.boundMouseMove);
138
138
 
139
139
  // Double click for fullscreen (desktop)
140
- container.addEventListener('dblclick', this.boundDoubleClick);
140
+ container.addEventListener("dblclick", this.boundDoubleClick);
141
141
 
142
142
  // Prevent context menu on long press
143
- container.addEventListener('contextmenu', this.boundContextMenu);
143
+ container.addEventListener("contextmenu", this.boundContextMenu);
144
144
 
145
145
  // Start idle tracking
146
146
  this.resetIdleTimer();
@@ -156,17 +156,17 @@ export class InteractionController {
156
156
 
157
157
  const { container } = this.config;
158
158
 
159
- container.removeEventListener('keydown', this.boundKeyDown);
160
- container.removeEventListener('keyup', this.boundKeyUp);
161
- document.removeEventListener('keydown', this.boundDocumentKeyDown);
162
- document.removeEventListener('keyup', this.boundDocumentKeyUp);
163
- container.removeEventListener('pointerdown', this.boundPointerDown);
164
- container.removeEventListener('pointerup', this.boundPointerUp);
165
- container.removeEventListener('pointercancel', this.boundPointerCancel);
166
- container.removeEventListener('pointerleave', this.boundPointerCancel);
167
- container.removeEventListener('mousemove', this.boundMouseMove);
168
- container.removeEventListener('dblclick', this.boundDoubleClick);
169
- container.removeEventListener('contextmenu', this.boundContextMenu);
159
+ container.removeEventListener("keydown", this.boundKeyDown);
160
+ container.removeEventListener("keyup", this.boundKeyUp);
161
+ document.removeEventListener("keydown", this.boundDocumentKeyDown);
162
+ document.removeEventListener("keyup", this.boundDocumentKeyUp);
163
+ container.removeEventListener("pointerdown", this.boundPointerDown);
164
+ container.removeEventListener("pointerup", this.boundPointerUp);
165
+ container.removeEventListener("pointercancel", this.boundPointerCancel);
166
+ container.removeEventListener("pointerleave", this.boundPointerCancel);
167
+ container.removeEventListener("mousemove", this.boundMouseMove);
168
+ container.removeEventListener("dblclick", this.boundDoubleClick);
169
+ container.removeEventListener("contextmenu", this.boundContextMenu);
170
170
 
171
171
  // Clear any pending timeouts
172
172
  if (this.holdCheckTimeout) {
@@ -244,66 +244,66 @@ export class InteractionController {
244
244
  const isPaused = this.config.isPaused?.() ?? this.config.videoElement?.paused ?? false;
245
245
 
246
246
  switch (e.key) {
247
- case ' ':
248
- case 'Spacebar':
247
+ case " ":
248
+ case "Spacebar":
249
249
  e.preventDefault();
250
250
  this.handleSpaceDown();
251
251
  break;
252
252
 
253
- case 'ArrowLeft':
254
- case 'j':
255
- case 'J':
253
+ case "ArrowLeft":
254
+ case "j":
255
+ case "J":
256
256
  e.preventDefault();
257
257
  if (!isLive) {
258
258
  this.config.onSeek(-SKIP_AMOUNT_SECONDS);
259
259
  }
260
260
  break;
261
261
 
262
- case 'ArrowRight':
263
- case 'l':
264
- case 'L':
262
+ case "ArrowRight":
263
+ case "l":
264
+ case "L":
265
265
  e.preventDefault();
266
266
  if (!isLive) {
267
267
  this.config.onSeek(SKIP_AMOUNT_SECONDS);
268
268
  }
269
269
  break;
270
270
 
271
- case 'ArrowUp':
271
+ case "ArrowUp":
272
272
  e.preventDefault();
273
273
  this.config.onVolumeChange(VOLUME_STEP);
274
274
  break;
275
275
 
276
- case 'ArrowDown':
276
+ case "ArrowDown":
277
277
  e.preventDefault();
278
278
  this.config.onVolumeChange(-VOLUME_STEP);
279
279
  break;
280
280
 
281
- case 'm':
282
- case 'M':
281
+ case "m":
282
+ case "M":
283
283
  e.preventDefault();
284
284
  this.config.onMuteToggle();
285
285
  break;
286
286
 
287
- case 'f':
288
- case 'F':
287
+ case "f":
288
+ case "F":
289
289
  e.preventDefault();
290
290
  this.config.onFullscreenToggle();
291
291
  break;
292
292
 
293
- case 'c':
294
- case 'C':
293
+ case "c":
294
+ case "C":
295
295
  e.preventDefault();
296
296
  this.config.onCaptionsToggle?.();
297
297
  break;
298
298
 
299
- case 'k':
300
- case 'K':
299
+ case "k":
300
+ case "K":
301
301
  // YouTube-style: K = play/pause (no hold behavior)
302
302
  e.preventDefault();
303
303
  this.config.onPlayPause();
304
304
  break;
305
305
 
306
- case '<':
306
+ case "<":
307
307
  // Decrease speed (shift+, = <)
308
308
  e.preventDefault();
309
309
  if (!isLive) {
@@ -311,7 +311,7 @@ export class InteractionController {
311
311
  }
312
312
  break;
313
313
 
314
- case '>':
314
+ case ">":
315
315
  // Increase speed (shift+. = >)
316
316
  e.preventDefault();
317
317
  if (!isLive) {
@@ -319,7 +319,7 @@ export class InteractionController {
319
319
  }
320
320
  break;
321
321
 
322
- case ',':
322
+ case ",":
323
323
  // Previous frame when paused
324
324
  if (this.config.onFrameStep || (!isLive && isPaused)) {
325
325
  e.preventDefault();
@@ -327,7 +327,7 @@ export class InteractionController {
327
327
  }
328
328
  break;
329
329
 
330
- case '.':
330
+ case ".":
331
331
  // Next frame when paused
332
332
  if (this.config.onFrameStep || (!isLive && isPaused)) {
333
333
  e.preventDefault();
@@ -336,16 +336,16 @@ export class InteractionController {
336
336
  break;
337
337
 
338
338
  // Number keys for seeking to percentage
339
- case '0':
340
- case '1':
341
- case '2':
342
- case '3':
343
- case '4':
344
- case '5':
345
- case '6':
346
- case '7':
347
- case '8':
348
- case '9':
339
+ case "0":
340
+ case "1":
341
+ case "2":
342
+ case "3":
343
+ case "4":
344
+ case "5":
345
+ case "6":
346
+ case "7":
347
+ case "8":
348
+ case "9":
349
349
  e.preventDefault();
350
350
  if (!isLive && this.config.onSeekPercent) {
351
351
  const percent = parseInt(e.key, 10) / 10;
@@ -360,7 +360,7 @@ export class InteractionController {
360
360
  if (e.defaultPrevented) return;
361
361
  if (!this.shouldHandleKeyboard(e)) return;
362
362
 
363
- if (e.key === ' ' || e.key === 'Spacebar') {
363
+ if (e.key === " " || e.key === "Spacebar") {
364
364
  e.preventDefault();
365
365
  this.handleSpaceUp();
366
366
  }
@@ -373,8 +373,8 @@ export class InteractionController {
373
373
  const active = document.activeElement as HTMLElement | null;
374
374
  if (active && this.config.container.contains(active)) return true;
375
375
  try {
376
- if (this.config.container.matches(':focus-within')) return true;
377
- if (this.config.container.matches(':hover')) return true;
376
+ if (this.config.container.matches(":focus-within")) return true;
377
+ if (this.config.container.matches(":hover")) return true;
378
378
  } catch {}
379
379
  const now = Date.now();
380
380
  if (now - this.lastInteractionTime < DEFAULT_IDLE_TIMEOUT_MS) return true;
@@ -432,7 +432,7 @@ export class InteractionController {
432
432
  const video = this.config.videoElement;
433
433
  if (!video) return;
434
434
 
435
- const target = video.currentTime + (direction * step);
435
+ const target = video.currentTime + direction * step;
436
436
  if (!Number.isFinite(target)) return;
437
437
 
438
438
  // Only step within already-buffered ranges to avoid network seeks
@@ -442,7 +442,9 @@ export class InteractionController {
442
442
  const start = buffered.start(i);
443
443
  const end = buffered.end(i);
444
444
  if (target >= start && target <= end) {
445
- try { video.currentTime = target; } catch {}
445
+ try {
446
+ video.currentTime = target;
447
+ } catch {}
446
448
  return;
447
449
  }
448
450
  }
@@ -467,7 +469,7 @@ export class InteractionController {
467
469
  const now = Date.now();
468
470
  const rect = this.config.container.getBoundingClientRect();
469
471
  const relativeX = (e.clientX - rect.left) / rect.width;
470
- const isMouse = e.pointerType === 'mouse';
472
+ const isMouse = e.pointerType === "mouse";
471
473
 
472
474
  // Check for double-tap
473
475
  if (now - this.lastTapTime < DOUBLE_TAP_WINDOW_MS) {
@@ -673,7 +675,12 @@ export class InteractionController {
673
675
  private isInputElement(target: EventTarget | null): boolean {
674
676
  if (!target || !(target instanceof HTMLElement)) return false;
675
677
  const tagName = target.tagName.toLowerCase();
676
- return tagName === 'input' || tagName === 'textarea' || tagName === 'select' || target.isContentEditable;
678
+ return (
679
+ tagName === "input" ||
680
+ tagName === "textarea" ||
681
+ tagName === "select" ||
682
+ target.isContentEditable
683
+ );
677
684
  }
678
685
 
679
686
  private isControlElement(target: EventTarget | null): boolean {
@@ -681,25 +688,25 @@ export class InteractionController {
681
688
 
682
689
  // Check if clicking on player controls (buttons, sliders, etc.)
683
690
  const controlSelectors = [
684
- 'button',
691
+ "button",
685
692
  '[role="button"]',
686
693
  '[role="slider"]',
687
- 'input',
688
- 'select',
689
- '.fw-player-controls',
690
- '[data-player-controls]',
691
- '.fw-controls-wrapper',
692
- '.fw-control-bar',
693
- '.fw-settings-menu',
694
- '.fw-context-menu',
695
- '.fw-stats-panel',
696
- '.fw-dev-panel',
697
- '.fw-error-overlay',
698
- '.fw-error-popup',
699
- '.fw-player-error',
694
+ "input",
695
+ "select",
696
+ ".fw-player-controls",
697
+ "[data-player-controls]",
698
+ ".fw-controls-wrapper",
699
+ ".fw-control-bar",
700
+ ".fw-settings-menu",
701
+ ".fw-context-menu",
702
+ ".fw-stats-panel",
703
+ ".fw-dev-panel",
704
+ ".fw-error-overlay",
705
+ ".fw-error-popup",
706
+ ".fw-player-error",
700
707
  ];
701
708
 
702
- return controlSelectors.some(selector => {
709
+ return controlSelectors.some((selector) => {
703
710
  return target.matches(selector) || target.closest(selector) !== null;
704
711
  });
705
712
  }
@@ -170,9 +170,8 @@ export class LiveDurationProxy {
170
170
 
171
171
  const bufferEnd = this.getBufferEnd();
172
172
  const now = Date.now();
173
- const elapsedSinceProgress = this.lastProgressTime > 0
174
- ? (now - this.lastProgressTime) / 1000
175
- : 0;
173
+ const elapsedSinceProgress =
174
+ this.lastProgressTime > 0 ? (now - this.lastProgressTime) / 1000 : 0;
176
175
 
177
176
  // MistPlayer formula: buffer_end + elapsed_since_last_progress
178
177
  const newDuration = bufferEnd + elapsedSinceProgress;
@@ -207,16 +206,16 @@ export class LiveDurationProxy {
207
206
  this.updateDuration();
208
207
  };
209
208
 
210
- this.video.addEventListener('progress', onProgress);
211
- this.video.addEventListener('timeupdate', onTimeUpdate);
212
- this.video.addEventListener('durationchange', onDurationChange);
213
- this.video.addEventListener('loadedmetadata', onLoadedMetadata);
209
+ this.video.addEventListener("progress", onProgress);
210
+ this.video.addEventListener("timeupdate", onTimeUpdate);
211
+ this.video.addEventListener("durationchange", onDurationChange);
212
+ this.video.addEventListener("loadedmetadata", onLoadedMetadata);
214
213
 
215
214
  this.listeners = [
216
- () => this.video.removeEventListener('progress', onProgress),
217
- () => this.video.removeEventListener('timeupdate', onTimeUpdate),
218
- () => this.video.removeEventListener('durationchange', onDurationChange),
219
- () => this.video.removeEventListener('loadedmetadata', onLoadedMetadata),
215
+ () => this.video.removeEventListener("progress", onProgress),
216
+ () => this.video.removeEventListener("timeupdate", onTimeUpdate),
217
+ () => this.video.removeEventListener("durationchange", onDurationChange),
218
+ () => this.video.removeEventListener("loadedmetadata", onLoadedMetadata),
220
219
  ];
221
220
  }
222
221
 
@@ -224,7 +223,7 @@ export class LiveDurationProxy {
224
223
  * Cleanup
225
224
  */
226
225
  destroy(): void {
227
- this.listeners.forEach(cleanup => cleanup());
226
+ this.listeners.forEach((cleanup) => cleanup());
228
227
  this.listeners = [];
229
228
  }
230
229
  }
@@ -243,19 +242,19 @@ export function createLiveVideoProxy(
243
242
 
244
243
  const proxy = new Proxy(video, {
245
244
  get(target, prop, receiver) {
246
- if (prop === 'duration') {
245
+ if (prop === "duration") {
247
246
  return controller.getDuration();
248
247
  }
249
248
 
250
249
  const value = Reflect.get(target, prop, receiver);
251
- if (typeof value === 'function') {
250
+ if (typeof value === "function") {
252
251
  return value.bind(target);
253
252
  }
254
253
  return value;
255
254
  },
256
255
 
257
256
  set(target, prop, value, receiver) {
258
- if (prop === 'currentTime' && controller.isLive()) {
257
+ if (prop === "currentTime" && controller.isLive()) {
259
258
  controller.seek(value as number);
260
259
  return true;
261
260
  }