@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
|
@@ -45,11 +45,11 @@ export interface InteractionState {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// Timing constants
|
|
48
|
-
const HOLD_THRESHOLD_MS = 200;
|
|
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;
|
|
51
|
-
const SKIP_AMOUNT_SECONDS = 10;
|
|
52
|
-
const VOLUME_STEP = 0.1;
|
|
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(
|
|
121
|
-
container.setAttribute(
|
|
120
|
+
if (!container.hasAttribute("tabindex")) {
|
|
121
|
+
container.setAttribute("tabindex", "0");
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
// Keyboard events
|
|
125
|
-
container.addEventListener(
|
|
126
|
-
container.addEventListener(
|
|
127
|
-
document.addEventListener(
|
|
128
|
-
document.addEventListener(
|
|
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(
|
|
132
|
-
container.addEventListener(
|
|
133
|
-
container.addEventListener(
|
|
134
|
-
container.addEventListener(
|
|
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(
|
|
137
|
+
container.addEventListener("mousemove", this.boundMouseMove);
|
|
138
138
|
|
|
139
139
|
// Double click for fullscreen (desktop)
|
|
140
|
-
container.addEventListener(
|
|
140
|
+
container.addEventListener("dblclick", this.boundDoubleClick);
|
|
141
141
|
|
|
142
142
|
// Prevent context menu on long press
|
|
143
|
-
container.addEventListener(
|
|
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(
|
|
160
|
-
container.removeEventListener(
|
|
161
|
-
document.removeEventListener(
|
|
162
|
-
document.removeEventListener(
|
|
163
|
-
container.removeEventListener(
|
|
164
|
-
container.removeEventListener(
|
|
165
|
-
container.removeEventListener(
|
|
166
|
-
container.removeEventListener(
|
|
167
|
-
container.removeEventListener(
|
|
168
|
-
container.removeEventListener(
|
|
169
|
-
container.removeEventListener(
|
|
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
|
|
247
|
+
case " ":
|
|
248
|
+
case "Spacebar":
|
|
249
249
|
e.preventDefault();
|
|
250
250
|
this.handleSpaceDown();
|
|
251
251
|
break;
|
|
252
252
|
|
|
253
|
-
case
|
|
254
|
-
case
|
|
255
|
-
case
|
|
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
|
|
263
|
-
case
|
|
264
|
-
case
|
|
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
|
|
271
|
+
case "ArrowUp":
|
|
272
272
|
e.preventDefault();
|
|
273
273
|
this.config.onVolumeChange(VOLUME_STEP);
|
|
274
274
|
break;
|
|
275
275
|
|
|
276
|
-
case
|
|
276
|
+
case "ArrowDown":
|
|
277
277
|
e.preventDefault();
|
|
278
278
|
this.config.onVolumeChange(-VOLUME_STEP);
|
|
279
279
|
break;
|
|
280
280
|
|
|
281
|
-
case
|
|
282
|
-
case
|
|
281
|
+
case "m":
|
|
282
|
+
case "M":
|
|
283
283
|
e.preventDefault();
|
|
284
284
|
this.config.onMuteToggle();
|
|
285
285
|
break;
|
|
286
286
|
|
|
287
|
-
case
|
|
288
|
-
case
|
|
287
|
+
case "f":
|
|
288
|
+
case "F":
|
|
289
289
|
e.preventDefault();
|
|
290
290
|
this.config.onFullscreenToggle();
|
|
291
291
|
break;
|
|
292
292
|
|
|
293
|
-
case
|
|
294
|
-
case
|
|
293
|
+
case "c":
|
|
294
|
+
case "C":
|
|
295
295
|
e.preventDefault();
|
|
296
296
|
this.config.onCaptionsToggle?.();
|
|
297
297
|
break;
|
|
298
298
|
|
|
299
|
-
case
|
|
300
|
-
case
|
|
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
|
|
340
|
-
case
|
|
341
|
-
case
|
|
342
|
-
case
|
|
343
|
-
case
|
|
344
|
-
case
|
|
345
|
-
case
|
|
346
|
-
case
|
|
347
|
-
case
|
|
348
|
-
case
|
|
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 ===
|
|
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(
|
|
377
|
-
if (this.config.container.matches(
|
|
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 +
|
|
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 {
|
|
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 ===
|
|
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
|
|
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
|
-
|
|
691
|
+
"button",
|
|
685
692
|
'[role="button"]',
|
|
686
693
|
'[role="slider"]',
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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 =
|
|
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(
|
|
211
|
-
this.video.addEventListener(
|
|
212
|
-
this.video.addEventListener(
|
|
213
|
-
this.video.addEventListener(
|
|
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(
|
|
217
|
-
() => this.video.removeEventListener(
|
|
218
|
-
() => this.video.removeEventListener(
|
|
219
|
-
() => this.video.removeEventListener(
|
|
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 ===
|
|
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 ===
|
|
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 ===
|
|
257
|
+
if (prop === "currentTime" && controller.isLive()) {
|
|
259
258
|
controller.seek(value as number);
|
|
260
259
|
return true;
|
|
261
260
|
}
|