@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.
Files changed (120) hide show
  1. package/dist/cjs/index.js +19493 -0
  2. package/dist/cjs/index.js.map +1 -0
  3. package/dist/esm/index.js +19398 -0
  4. package/dist/esm/index.js.map +1 -0
  5. package/dist/player.css +2140 -0
  6. package/dist/types/core/ABRController.d.ts +164 -0
  7. package/dist/types/core/CodecUtils.d.ts +54 -0
  8. package/dist/types/core/Disposable.d.ts +61 -0
  9. package/dist/types/core/EventEmitter.d.ts +73 -0
  10. package/dist/types/core/GatewayClient.d.ts +144 -0
  11. package/dist/types/core/InteractionController.d.ts +121 -0
  12. package/dist/types/core/LiveDurationProxy.d.ts +102 -0
  13. package/dist/types/core/MetaTrackManager.d.ts +220 -0
  14. package/dist/types/core/MistReporter.d.ts +163 -0
  15. package/dist/types/core/MistSignaling.d.ts +148 -0
  16. package/dist/types/core/PlayerController.d.ts +665 -0
  17. package/dist/types/core/PlayerInterface.d.ts +230 -0
  18. package/dist/types/core/PlayerManager.d.ts +182 -0
  19. package/dist/types/core/PlayerRegistry.d.ts +27 -0
  20. package/dist/types/core/QualityMonitor.d.ts +184 -0
  21. package/dist/types/core/ScreenWakeLockManager.d.ts +70 -0
  22. package/dist/types/core/SeekingUtils.d.ts +142 -0
  23. package/dist/types/core/StreamStateClient.d.ts +108 -0
  24. package/dist/types/core/SubtitleManager.d.ts +111 -0
  25. package/dist/types/core/TelemetryReporter.d.ts +79 -0
  26. package/dist/types/core/TimeFormat.d.ts +97 -0
  27. package/dist/types/core/TimerManager.d.ts +83 -0
  28. package/dist/types/core/UrlUtils.d.ts +81 -0
  29. package/dist/types/core/detector.d.ts +149 -0
  30. package/dist/types/core/index.d.ts +49 -0
  31. package/dist/types/core/scorer.d.ts +167 -0
  32. package/dist/types/core/selector.d.ts +9 -0
  33. package/dist/types/index.d.ts +45 -0
  34. package/dist/types/lib/utils.d.ts +2 -0
  35. package/dist/types/players/DashJsPlayer.d.ts +102 -0
  36. package/dist/types/players/HlsJsPlayer.d.ts +70 -0
  37. package/dist/types/players/MewsWsPlayer/SourceBufferManager.d.ts +119 -0
  38. package/dist/types/players/MewsWsPlayer/WebSocketManager.d.ts +60 -0
  39. package/dist/types/players/MewsWsPlayer/index.d.ts +220 -0
  40. package/dist/types/players/MewsWsPlayer/types.d.ts +89 -0
  41. package/dist/types/players/MistPlayer.d.ts +25 -0
  42. package/dist/types/players/MistWebRTCPlayer/index.d.ts +133 -0
  43. package/dist/types/players/NativePlayer.d.ts +143 -0
  44. package/dist/types/players/VideoJsPlayer.d.ts +59 -0
  45. package/dist/types/players/WebCodecsPlayer/JitterBuffer.d.ts +118 -0
  46. package/dist/types/players/WebCodecsPlayer/LatencyProfiles.d.ts +64 -0
  47. package/dist/types/players/WebCodecsPlayer/RawChunkParser.d.ts +63 -0
  48. package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +174 -0
  49. package/dist/types/players/WebCodecsPlayer/WebSocketController.d.ts +164 -0
  50. package/dist/types/players/WebCodecsPlayer/index.d.ts +149 -0
  51. package/dist/types/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.d.ts +105 -0
  52. package/dist/types/players/WebCodecsPlayer/types.d.ts +395 -0
  53. package/dist/types/players/WebCodecsPlayer/worker/decoder.worker.d.ts +13 -0
  54. package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +197 -0
  55. package/dist/types/players/index.d.ts +14 -0
  56. package/dist/types/styles/index.d.ts +11 -0
  57. package/dist/types/types.d.ts +363 -0
  58. package/dist/types/vanilla/FrameWorksPlayer.d.ts +143 -0
  59. package/dist/types/vanilla/index.d.ts +19 -0
  60. package/dist/workers/decoder.worker.js +989 -0
  61. package/dist/workers/decoder.worker.js.map +1 -0
  62. package/package.json +80 -0
  63. package/src/core/ABRController.ts +550 -0
  64. package/src/core/CodecUtils.ts +257 -0
  65. package/src/core/Disposable.ts +120 -0
  66. package/src/core/EventEmitter.ts +113 -0
  67. package/src/core/GatewayClient.ts +439 -0
  68. package/src/core/InteractionController.ts +712 -0
  69. package/src/core/LiveDurationProxy.ts +270 -0
  70. package/src/core/MetaTrackManager.ts +753 -0
  71. package/src/core/MistReporter.ts +543 -0
  72. package/src/core/MistSignaling.ts +346 -0
  73. package/src/core/PlayerController.ts +2829 -0
  74. package/src/core/PlayerInterface.ts +432 -0
  75. package/src/core/PlayerManager.ts +900 -0
  76. package/src/core/PlayerRegistry.ts +149 -0
  77. package/src/core/QualityMonitor.ts +597 -0
  78. package/src/core/ScreenWakeLockManager.ts +163 -0
  79. package/src/core/SeekingUtils.ts +364 -0
  80. package/src/core/StreamStateClient.ts +457 -0
  81. package/src/core/SubtitleManager.ts +297 -0
  82. package/src/core/TelemetryReporter.ts +308 -0
  83. package/src/core/TimeFormat.ts +205 -0
  84. package/src/core/TimerManager.ts +209 -0
  85. package/src/core/UrlUtils.ts +179 -0
  86. package/src/core/detector.ts +382 -0
  87. package/src/core/index.ts +140 -0
  88. package/src/core/scorer.ts +553 -0
  89. package/src/core/selector.ts +16 -0
  90. package/src/global.d.ts +11 -0
  91. package/src/index.ts +75 -0
  92. package/src/lib/utils.ts +6 -0
  93. package/src/players/DashJsPlayer.ts +642 -0
  94. package/src/players/HlsJsPlayer.ts +483 -0
  95. package/src/players/MewsWsPlayer/SourceBufferManager.ts +572 -0
  96. package/src/players/MewsWsPlayer/WebSocketManager.ts +241 -0
  97. package/src/players/MewsWsPlayer/index.ts +1065 -0
  98. package/src/players/MewsWsPlayer/types.ts +106 -0
  99. package/src/players/MistPlayer.ts +188 -0
  100. package/src/players/MistWebRTCPlayer/index.ts +703 -0
  101. package/src/players/NativePlayer.ts +820 -0
  102. package/src/players/VideoJsPlayer.ts +643 -0
  103. package/src/players/WebCodecsPlayer/JitterBuffer.ts +299 -0
  104. package/src/players/WebCodecsPlayer/LatencyProfiles.ts +151 -0
  105. package/src/players/WebCodecsPlayer/RawChunkParser.ts +151 -0
  106. package/src/players/WebCodecsPlayer/SyncController.ts +456 -0
  107. package/src/players/WebCodecsPlayer/WebSocketController.ts +564 -0
  108. package/src/players/WebCodecsPlayer/index.ts +1650 -0
  109. package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +379 -0
  110. package/src/players/WebCodecsPlayer/types.ts +542 -0
  111. package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +1360 -0
  112. package/src/players/WebCodecsPlayer/worker/types.ts +276 -0
  113. package/src/players/index.ts +22 -0
  114. package/src/styles/animations.css +21 -0
  115. package/src/styles/index.ts +52 -0
  116. package/src/styles/player.css +2126 -0
  117. package/src/styles/tailwind.css +1015 -0
  118. package/src/types.ts +421 -0
  119. package/src/vanilla/FrameWorksPlayer.ts +367 -0
  120. package/src/vanilla/index.ts +22 -0
@@ -0,0 +1,456 @@
1
+ /**
2
+ * SyncController - Buffer Management & Playback Timing
3
+ *
4
+ * Orchestrates:
5
+ * - Buffer level monitoring
6
+ * - Adaptive playback speed (catchup/slowdown)
7
+ * - Jitter tracking integration
8
+ * - Server delay estimation
9
+ * - Seek coordination
10
+ *
11
+ * Based on legacy rawws.js FrameTiming + buffer management with improvements:
12
+ * - Post-decode drift correction
13
+ * - Better seek cancellation
14
+ * - TypeScript types
15
+ */
16
+
17
+ import type {
18
+ LatencyProfile,
19
+ BufferState,
20
+ SyncState,
21
+ TrackInfo,
22
+ } from './types';
23
+ import { MultiTrackJitterTracker } from './JitterBuffer';
24
+ import { getLatencyProfile } from './LatencyProfiles';
25
+
26
+ /** Events emitted by SyncController */
27
+ export interface SyncControllerEvents {
28
+ speedchange: { speed: number; reason: 'catchup' | 'slowdown' | 'normal' };
29
+ bufferlow: { current: number; desired: number };
30
+ bufferhigh: { current: number; desired: number };
31
+ underrun: void;
32
+ livecatchup: { fastForwardMs: number };
33
+ seekstart: { seekId: number; time: number };
34
+ seekcomplete: { seekId: number };
35
+ }
36
+
37
+ type EventListener<K extends keyof SyncControllerEvents> = (
38
+ data: SyncControllerEvents[K]
39
+ ) => void;
40
+
41
+ /** Seek state tracking */
42
+ interface SeekState {
43
+ active: boolean;
44
+ id: number;
45
+ targetTime: number;
46
+ startedAt: number;
47
+ }
48
+
49
+ /**
50
+ * SyncController - Manages playback synchronization
51
+ */
52
+ export class SyncController {
53
+ private profile: LatencyProfile;
54
+ private jitterTracker: MultiTrackJitterTracker;
55
+ private listeners = new Map<keyof SyncControllerEvents, Set<Function>>();
56
+
57
+ // Playback speed state
58
+ private mainSpeed = 1;
59
+ private tweakSpeed = 1;
60
+
61
+ // Buffer state
62
+ private lastBufferCheck = 0;
63
+ private bufferCheckInterval = 100; // ms
64
+
65
+ // Server delay tracking
66
+ private serverDelays: number[] = [];
67
+ private maxServerDelaysSamples = 3;
68
+
69
+ // Live catchup tracking
70
+ // Reference: rawws.js:489-503 - proactively request 5s fast forward
71
+ private lastLiveCatchup = 0;
72
+ private liveCatchupCooldown = 2000; // Minimum ms between catchup requests
73
+ private liveCatchupThresholdMs = 5000; // Request catchup when within 5s of live
74
+ private liveCatchupRequestMs = 5000; // Request 5 seconds of fast forward
75
+
76
+ // Time tracking
77
+ private serverTime = 0;
78
+ private localTimeAtServerUpdate = 0;
79
+
80
+ // Seek state
81
+ private seekState: SeekState = {
82
+ active: false,
83
+ id: 0,
84
+ targetTime: 0,
85
+ startedAt: 0,
86
+ };
87
+
88
+ // Stream info
89
+ private isLive = true;
90
+
91
+ // Callbacks for external control
92
+ private onSpeedChange?: (main: number, tweak: number) => void;
93
+ private onFastForwardRequest?: (ms: number) => void;
94
+
95
+ constructor(options: {
96
+ profile?: LatencyProfile;
97
+ isLive?: boolean;
98
+ onSpeedChange?: (main: number, tweak: number) => void;
99
+ onFastForwardRequest?: (ms: number) => void;
100
+ } = {}) {
101
+ this.profile = options.profile ?? getLatencyProfile('low');
102
+ this.isLive = options.isLive ?? true;
103
+ this.onSpeedChange = options.onSpeedChange;
104
+ this.onFastForwardRequest = options.onFastForwardRequest;
105
+
106
+ this.jitterTracker = new MultiTrackJitterTracker({
107
+ initialJitter: 120,
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Update latency profile
113
+ */
114
+ setProfile(profile: LatencyProfile): void {
115
+ this.profile = profile;
116
+ }
117
+
118
+ /**
119
+ * Update stream type (live vs VOD)
120
+ */
121
+ setLive(isLive: boolean): void {
122
+ this.isLive = isLive;
123
+ }
124
+
125
+ /**
126
+ * Record a chunk arrival for jitter tracking
127
+ */
128
+ recordChunkArrival(trackIndex: number, mediaTimeMs: number): void {
129
+ this.jitterTracker.addChunk(trackIndex, mediaTimeMs);
130
+ }
131
+
132
+ /**
133
+ * Update server time from on_time message
134
+ */
135
+ updateServerTime(currentTime: number): void {
136
+ this.serverTime = currentTime;
137
+ this.localTimeAtServerUpdate = performance.now();
138
+ }
139
+
140
+ /**
141
+ * Record server delay measurement
142
+ */
143
+ recordServerDelay(delayMs: number): void {
144
+ this.serverDelays.push(delayMs);
145
+ if (this.serverDelays.length > this.maxServerDelaysSamples) {
146
+ this.serverDelays.shift();
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Get current server delay estimate
152
+ */
153
+ getServerDelay(): number {
154
+ if (this.serverDelays.length === 0) return 0;
155
+ return this.serverDelays.reduce((sum, d) => sum + d, 0) / this.serverDelays.length;
156
+ }
157
+
158
+ /**
159
+ * Get estimated current server time (interpolated)
160
+ */
161
+ getEstimatedServerTime(): number {
162
+ const elapsed = (performance.now() - this.localTimeAtServerUpdate) / 1000;
163
+ return this.serverTime + elapsed * this.getCombinedSpeed();
164
+ }
165
+
166
+ /**
167
+ * Calculate desired buffer size based on profile + jitter + server delay
168
+ * Matches MistServer mews.js pattern with Chrome-specific handling
169
+ */
170
+ getDesiredBuffer(): number {
171
+ // Chrome needs larger base buffer (per mews.js:482)
172
+ const isChrome = typeof navigator !== 'undefined' &&
173
+ /Chrome/.test(navigator.userAgent) && !/Edge|Edg/.test(navigator.userAgent);
174
+ const baseBuffer = isChrome ? 1000 : 100;
175
+
176
+ const serverDelay = this.getServerDelay();
177
+ const jitter = this.jitterTracker.getMax() * this.profile.jitterMultiplier;
178
+
179
+ // Match mews.js formula: Math.max(baseBuffer + serverDelay, serverDelay * 2) + jitter
180
+ const liveBuffer = Math.max(baseBuffer + serverDelay, serverDelay * 2) + jitter;
181
+
182
+ // VoD gets extra buffer (mews.js:480)
183
+ return this.isLive ? liveBuffer : liveBuffer + 2000;
184
+ }
185
+
186
+ /**
187
+ * Evaluate buffer state and adjust playback speed if needed
188
+ *
189
+ * @param currentBufferMs - Current buffer level in milliseconds
190
+ * @returns Updated sync state
191
+ */
192
+ evaluateBuffer(currentBufferMs: number): SyncState {
193
+ const now = performance.now();
194
+
195
+ // Rate limit buffer checks
196
+ if (now - this.lastBufferCheck < this.bufferCheckInterval) {
197
+ return this.getState(currentBufferMs);
198
+ }
199
+ this.lastBufferCheck = now;
200
+
201
+ const desired = this.getDesiredBuffer();
202
+ const ratio = currentBufferMs / desired;
203
+ const jitterMs = this.jitterTracker.getMax();
204
+
205
+ // Proactive live catchup logic
206
+ // Reference: rawws.js:489-503 - request 5s fast forward when close to live
207
+ // distanceToLive = how much buffer we have (close to live = small buffer)
208
+ // Conditions:
209
+ // - Within liveCatchupThreshold of live edge (small buffer)
210
+ // - Buffer is more than jitter + safety margin (not stalled)
211
+ // - Buffer is less than 1s above desired
212
+ // - Cooldown period has passed
213
+ if (this.isLive &&
214
+ currentBufferMs < this.liveCatchupThresholdMs &&
215
+ currentBufferMs > Math.max(jitterMs * 1.1, jitterMs + 250) &&
216
+ (currentBufferMs - desired) < 1000 &&
217
+ (now - this.lastLiveCatchup) > this.liveCatchupCooldown) {
218
+ this.lastLiveCatchup = now;
219
+ this.requestFastForward(this.liveCatchupRequestMs);
220
+ this.emit('livecatchup', { fastForwardMs: this.liveCatchupRequestMs });
221
+ }
222
+
223
+ // Determine if speed adjustment needed
224
+ if (ratio > this.profile.speedUpThreshold && this.isLive) {
225
+ // Buffer too high - speed up to catch up to live edge
226
+ this.setTweakSpeed(this.profile.maxSpeedUp, 'catchup');
227
+ } else if (ratio < this.profile.speedDownThreshold) {
228
+ // Buffer too low - slow down to build buffer
229
+ this.setTweakSpeed(this.profile.minSpeedDown, 'slowdown');
230
+
231
+ // Request additional data if critically low
232
+ if (ratio < 0.3 && this.isLive) {
233
+ this.requestFastForward(desired - currentBufferMs);
234
+ this.emit('underrun', undefined);
235
+ }
236
+
237
+ this.emit('bufferlow', { current: currentBufferMs, desired });
238
+ } else {
239
+ // Buffer in acceptable range - return to normal speed
240
+ if (this.tweakSpeed !== 1) {
241
+ this.setTweakSpeed(1, 'normal');
242
+ }
243
+ }
244
+
245
+ return this.getState(currentBufferMs);
246
+ }
247
+
248
+ /**
249
+ * Get current sync state
250
+ */
251
+ getState(currentBufferMs?: number): SyncState {
252
+ const buffer = currentBufferMs ?? 0;
253
+ const desired = this.getDesiredBuffer();
254
+
255
+ return {
256
+ buffer: {
257
+ current: buffer,
258
+ desired,
259
+ ratio: desired > 0 ? buffer / desired : 1,
260
+ },
261
+ jitter: this.jitterTracker.getMax() > 0
262
+ ? {
263
+ current: 0, // Would need per-frame tracking
264
+ peak: 0,
265
+ weighted: this.jitterTracker.getMax(),
266
+ }
267
+ : { current: 0, peak: 0, weighted: 0 },
268
+ playbackSpeed: this.getCombinedSpeed(),
269
+ serverTime: this.serverTime,
270
+ serverDelay: this.getServerDelay(),
271
+ avOffset: 0, // Server handles A/V sync via timestamp+offset
272
+ };
273
+ }
274
+
275
+ /**
276
+ * Get combined playback speed (main * tweak)
277
+ */
278
+ getCombinedSpeed(): number {
279
+ return this.mainSpeed * this.tweakSpeed;
280
+ }
281
+
282
+ /**
283
+ * Set main playback speed (user-controlled)
284
+ */
285
+ setMainSpeed(speed: number): void {
286
+ this.mainSpeed = speed;
287
+ this.jitterTracker.setSpeed(speed);
288
+ this.notifySpeedChange();
289
+ }
290
+
291
+ /**
292
+ * Set tweak speed (automatic adjustment)
293
+ */
294
+ private setTweakSpeed(
295
+ speed: number,
296
+ reason: 'catchup' | 'slowdown' | 'normal'
297
+ ): void {
298
+ if (this.tweakSpeed !== speed) {
299
+ this.tweakSpeed = speed;
300
+ this.notifySpeedChange();
301
+ this.emit('speedchange', { speed: this.getCombinedSpeed(), reason });
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Notify external listeners of speed change
307
+ */
308
+ private notifySpeedChange(): void {
309
+ this.onSpeedChange?.(this.mainSpeed, this.tweakSpeed);
310
+ }
311
+
312
+ /**
313
+ * Request additional data from server
314
+ */
315
+ private requestFastForward(ms: number): void {
316
+ this.onFastForwardRequest?.(Math.max(0, ms));
317
+ }
318
+
319
+ // ============================================================================
320
+ // Seek Management
321
+ // ============================================================================
322
+
323
+ /**
324
+ * Start a seek operation
325
+ * Returns a seek ID that can be used to check if seek is still active
326
+ */
327
+ startSeek(targetTimeMs: number): number {
328
+ // Cancel any existing seek
329
+ this.seekState.id++;
330
+
331
+ this.seekState = {
332
+ active: true,
333
+ id: this.seekState.id,
334
+ targetTime: targetTimeMs,
335
+ startedAt: performance.now(),
336
+ };
337
+
338
+ // Reset jitter tracking on seek
339
+ this.jitterTracker.reset();
340
+
341
+ this.emit('seekstart', { seekId: this.seekState.id, time: targetTimeMs });
342
+
343
+ return this.seekState.id;
344
+ }
345
+
346
+ /**
347
+ * Check if a seek is still the active one
348
+ */
349
+ isSeekActive(seekId: number): boolean {
350
+ return this.seekState.active && this.seekState.id === seekId;
351
+ }
352
+
353
+ /**
354
+ * Complete a seek operation
355
+ */
356
+ completeSeek(seekId: number): void {
357
+ if (this.seekState.id === seekId) {
358
+ this.seekState.active = false;
359
+ this.emit('seekcomplete', { seekId });
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Cancel any active seek
365
+ */
366
+ cancelSeek(): void {
367
+ if (this.seekState.active) {
368
+ this.seekState.active = false;
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Check if currently seeking
374
+ */
375
+ isSeeking(): boolean {
376
+ return this.seekState.active;
377
+ }
378
+
379
+ // ============================================================================
380
+ // Track Management
381
+ // ============================================================================
382
+
383
+ /**
384
+ * Register a new track
385
+ */
386
+ addTrack(trackIndex: number, track: TrackInfo): void {
387
+ // Jitter tracking will be initialized on first chunk
388
+ }
389
+
390
+ /**
391
+ * Remove a track
392
+ */
393
+ removeTrack(trackIndex: number): void {
394
+ this.jitterTracker.removeTrack(trackIndex);
395
+ }
396
+
397
+ // ============================================================================
398
+ // Reset
399
+ // ============================================================================
400
+
401
+ /**
402
+ * Reset all state
403
+ */
404
+ reset(): void {
405
+ this.mainSpeed = 1;
406
+ this.tweakSpeed = 1;
407
+ this.serverDelays = [];
408
+ this.serverTime = 0;
409
+ this.localTimeAtServerUpdate = 0;
410
+ this.lastLiveCatchup = 0;
411
+ this.jitterTracker.reset();
412
+ this.seekState = {
413
+ active: false,
414
+ id: 0,
415
+ targetTime: 0,
416
+ startedAt: 0,
417
+ };
418
+ }
419
+
420
+ // ============================================================================
421
+ // Event Emitter
422
+ // ============================================================================
423
+
424
+ on<K extends keyof SyncControllerEvents>(
425
+ event: K,
426
+ listener: EventListener<K>
427
+ ): void {
428
+ if (!this.listeners.has(event)) {
429
+ this.listeners.set(event, new Set());
430
+ }
431
+ this.listeners.get(event)!.add(listener);
432
+ }
433
+
434
+ off<K extends keyof SyncControllerEvents>(
435
+ event: K,
436
+ listener: EventListener<K>
437
+ ): void {
438
+ this.listeners.get(event)?.delete(listener);
439
+ }
440
+
441
+ private emit<K extends keyof SyncControllerEvents>(
442
+ event: K,
443
+ data: SyncControllerEvents[K]
444
+ ): void {
445
+ const eventListeners = this.listeners.get(event);
446
+ if (eventListeners) {
447
+ for (const listener of eventListeners) {
448
+ try {
449
+ listener(data);
450
+ } catch (err) {
451
+ console.error(`Error in ${event} listener:`, err);
452
+ }
453
+ }
454
+ }
455
+ }
456
+ }