@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,572 @@
1
+ /**
2
+ * SourceBuffer Manager for MEWS Player
3
+ *
4
+ * Handles MediaSource Extension (MSE) buffer operations.
5
+ * Ported from reference: mews.js:206-384
6
+ *
7
+ * Key features:
8
+ * - Buffer creation and codec configuration
9
+ * - Append queue management with _append/_do/_doNext pattern
10
+ * - QuotaExceededError handling
11
+ * - Buffer cleanup (_clean)
12
+ * - Track switch msgqueue handling
13
+ */
14
+
15
+ import type { SourceBufferManagerOptions } from './types';
16
+
17
+ export class SourceBufferManager {
18
+ private mediaSource: MediaSource;
19
+ private videoElement: HTMLVideoElement;
20
+ private sourceBuffer: SourceBuffer | null = null;
21
+ private onError: (message: string) => void;
22
+
23
+ // Append queue for when buffer is busy (ported from mews.js:218)
24
+ private queue: Uint8Array[] = [];
25
+
26
+ // Busy flag to prevent concurrent appends (ported from mews.js:296, 266, 301)
27
+ private _busy = false;
28
+
29
+ // Operations to run after updateend (ported from mews.js:219, 247-262)
30
+ private do_on_updateend: Array<(remaining?: Array<() => void>) => void> = [];
31
+
32
+ // Message queue for track switches (ported from mews.js:452, 689-693)
33
+ // Array of arrays to handle rapid switches. Binary data goes to latest array.
34
+ // On reinit, oldest array is drained first to preserve order.
35
+ private msgqueue: Uint8Array[][] | false = false;
36
+
37
+ // Track current codecs to skip unnecessary reinits
38
+ private _codecs: string[] = [];
39
+
40
+ // Fragment counter for proactive buffer cleaning (ported from mews.js:223, 237-245)
41
+ private fragmentCount = 0;
42
+
43
+ // Paused flag for browser pause detection (ported from mews.js:506, 791)
44
+ public paused = false;
45
+
46
+ // Debugging mode
47
+ private debugging = false;
48
+
49
+ constructor(options: SourceBufferManagerOptions) {
50
+ this.mediaSource = options.mediaSource;
51
+ this.videoElement = options.videoElement;
52
+ this.onError = options.onError;
53
+ }
54
+
55
+ /**
56
+ * Initialize the SourceBuffer with the given codecs.
57
+ * Ported from mews.js:206-384 (sbinit function)
58
+ */
59
+ initWithCodecs(codecs: string[]): boolean {
60
+ if (this.sourceBuffer) {
61
+ // Already initialized
62
+ return true;
63
+ }
64
+ if (!codecs || !codecs.length) {
65
+ this.onError('No codecs provided');
66
+ return false;
67
+ }
68
+
69
+ const container = 'mp4'; // Could be 'webm' for WebM container
70
+ const mime = `video/${container};codecs="${codecs.join(',')}"`;
71
+
72
+ if (!MediaSource.isTypeSupported(mime)) {
73
+ this.onError(`Unsupported MSE codec: ${mime}`);
74
+ return false;
75
+ }
76
+
77
+ try {
78
+ // Create SourceBuffer (mews.js:211)
79
+ this.sourceBuffer = this.mediaSource.addSourceBuffer(mime);
80
+ // Use segments mode - fragments will be put at correct time (mews.js:212)
81
+ this.sourceBuffer.mode = 'segments';
82
+ // Save current codecs (mews.js:215)
83
+ this._codecs = codecs.slice();
84
+
85
+ this.installEventHandlers();
86
+
87
+ // Drain any pre-buffered messages from track switch queue (mews.js:341-367)
88
+ this.drainMessageQueue();
89
+
90
+ // Flush any data that was queued before sourceBuffer was ready
91
+ this.flushQueue();
92
+
93
+ return true;
94
+ } catch (e: any) {
95
+ this.onError(e?.message || 'Failed to create SourceBuffer');
96
+ return false;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Get current codecs
102
+ */
103
+ getCodecs(): string[] {
104
+ return this._codecs;
105
+ }
106
+
107
+ /**
108
+ * Queue data for appending to the buffer.
109
+ * If a track switch is in progress (msgqueue active), data goes to the latest queue.
110
+ * Ported from mews.js:809-824
111
+ */
112
+ append(data: Uint8Array): void {
113
+ if (!data || !data.byteLength) return;
114
+
115
+ // If track switch in progress, queue to msgqueue instead (mews.js:818-824)
116
+ if (this.msgqueue) {
117
+ this.msgqueue[this.msgqueue.length - 1].push(data);
118
+ return;
119
+ }
120
+
121
+ // No sourceBuffer yet, queue for later (mews.js:809-816)
122
+ if (!this.sourceBuffer) {
123
+ this.queue.push(data);
124
+ return;
125
+ }
126
+
127
+ // Check if we can append now
128
+ if (this.sourceBuffer.updating || this.queue.length || this._busy) {
129
+ this.queue.push(data);
130
+ return;
131
+ }
132
+
133
+ // Append directly
134
+ this._append(data);
135
+ }
136
+
137
+ /**
138
+ * Internal append with error handling.
139
+ * Ported from mews.js:292-339
140
+ */
141
+ private _append(data: Uint8Array): void {
142
+ if (!data || !data.buffer) return;
143
+ if (!this.sourceBuffer) return;
144
+
145
+ if (this._busy) {
146
+ // Still busy, put back in queue (mews.js:296-300)
147
+ if (this.debugging) console.warn('MEWS: wanted to append but busy, requeuing');
148
+ this.queue.unshift(data);
149
+ return;
150
+ }
151
+
152
+ this._busy = true;
153
+
154
+ try {
155
+ // Handle SharedArrayBuffer edge case (mews.js doesn't have this, but we need it)
156
+ if (data.buffer instanceof SharedArrayBuffer) {
157
+ const buffer = new ArrayBuffer(data.byteLength);
158
+ new Uint8Array(buffer).set(data);
159
+ this.sourceBuffer.appendBuffer(buffer);
160
+ } else {
161
+ this.sourceBuffer.appendBuffer(
162
+ data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength)
163
+ );
164
+ }
165
+ } catch (e: any) {
166
+ // Error handling (mews.js:306-338)
167
+ switch (e?.name) {
168
+ case 'QuotaExceededError': {
169
+ // Buffer is full - clean up and retry (mews.js:308-324)
170
+ const buffered = this.videoElement.buffered;
171
+ if (buffered.length && this.videoElement.currentTime - buffered.start(0) > 1) {
172
+ // Clear as much from buffer as we can
173
+ if (this.debugging) {
174
+ console.log('MEWS: QuotaExceededError, cleaning buffer');
175
+ }
176
+ this._clean(1); // Keep 1 second
177
+ this._busy = false;
178
+ this._append(data); // Retry
179
+ return;
180
+ } else if (buffered.length) {
181
+ // Can't clean more, skip ahead (mews.js:316-319)
182
+ const bufferEnd = buffered.end(buffered.length - 1);
183
+ if (this.debugging) {
184
+ console.log('MEWS: QuotaExceededError, skipping ahead');
185
+ }
186
+ this.videoElement.currentTime = bufferEnd;
187
+ this._busy = false;
188
+ this._append(data);
189
+ return;
190
+ }
191
+ break;
192
+ }
193
+ case 'InvalidStateError': {
194
+ // Playback is borked (mews.js:326-334)
195
+ if (this.videoElement.error) {
196
+ // Video element error will handle this
197
+ this._busy = false;
198
+ return;
199
+ }
200
+ break;
201
+ }
202
+ }
203
+ this.onError(e?.message || 'Append buffer failed');
204
+ this._busy = false;
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Schedule an operation to run on next updateend.
210
+ * Ported from mews.js:281-283
211
+ */
212
+ _doNext(func: () => void): void {
213
+ this.do_on_updateend.push(func);
214
+ }
215
+
216
+ /**
217
+ * Run operation now if not busy, otherwise schedule for updateend.
218
+ * Ported from mews.js:284-291
219
+ */
220
+ _do(func: (remaining?: Array<() => void>) => void): void {
221
+ if (!this.sourceBuffer) {
222
+ this._doNext(func);
223
+ return;
224
+ }
225
+ if (this.sourceBuffer.updating || this._busy) {
226
+ this._doNext(func);
227
+ } else {
228
+ func();
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Schedule an operation to run after the current SourceBuffer update completes.
234
+ * Public API for external callers.
235
+ */
236
+ scheduleAfterUpdate(fn: () => void): void {
237
+ this._doNext(fn);
238
+ }
239
+
240
+ /**
241
+ * Change codecs mid-stream (track switch).
242
+ * Uses message queue to prevent data loss during rapid switches.
243
+ * Ported from mews.js:623-788
244
+ */
245
+ changeCodecs(codecs: string[], switchPointMs?: number): void {
246
+ // Skip reinit if codecs are identical (mews.js:676)
247
+ if (this.codecsEqual(this._codecs, codecs)) {
248
+ if (this.debugging) console.log('MEWS: keeping source buffer, codecs same');
249
+ return;
250
+ }
251
+
252
+ const container = 'mp4';
253
+ const mime = `video/${container};codecs="${codecs.join(',')}"`;
254
+ if (!MediaSource.isTypeSupported(mime)) {
255
+ this.onError(`Unsupported codec for switch: ${mime}`);
256
+ return;
257
+ }
258
+
259
+ // Start message queue for track switch (mews.js:689-693)
260
+ if (this.msgqueue) {
261
+ this.msgqueue.push([]); // Add new queue for rapid switch
262
+ } else {
263
+ this.msgqueue = [[]];
264
+ }
265
+
266
+ const pendingCodecs = codecs.slice();
267
+
268
+ if (typeof switchPointMs === 'number' && switchPointMs > 0) {
269
+ // Wait for playback to reach switching point (mews.js:751-785)
270
+ this.awaitSwitchingPoint(mime, switchPointMs, pendingCodecs);
271
+ } else {
272
+ // Clear and reinit immediately
273
+ this.clearAndReinit(mime, pendingCodecs);
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Check if two codec arrays are equivalent (order-independent)
279
+ */
280
+ private codecsEqual(arr1: string[], arr2: string[]): boolean {
281
+ if (arr1.length !== arr2.length) return false;
282
+ for (const codec of arr1) {
283
+ if (!arr2.includes(codec)) return false;
284
+ }
285
+ return true;
286
+ }
287
+
288
+ /**
289
+ * Find which buffer range contains the given position.
290
+ * Ported from mews.js:947-956
291
+ */
292
+ findBufferIndex(position: number): number | false {
293
+ const buffered = this.videoElement.buffered;
294
+ for (let i = 0; i < buffered.length; i++) {
295
+ if (buffered.start(i) <= position && buffered.end(i) >= position) {
296
+ return i;
297
+ }
298
+ }
299
+ return false;
300
+ }
301
+
302
+ /**
303
+ * Remove buffer content before keepaway seconds from current position.
304
+ * Ported from mews.js:370-379
305
+ */
306
+ _clean(keepaway: number = 180): void {
307
+ if (!this.sourceBuffer) return;
308
+
309
+ const currentTime = this.videoElement.currentTime;
310
+ if (currentTime <= keepaway) return;
311
+
312
+ this._do(() => {
313
+ if (!this.sourceBuffer) return;
314
+ try {
315
+ // Make sure end time is never 0 (mews.js:376)
316
+ this.sourceBuffer.remove(0, Math.max(0.1, currentTime - keepaway));
317
+ } catch (e) {
318
+ // Ignore errors during cleanup
319
+ }
320
+ });
321
+ }
322
+
323
+ /**
324
+ * Clear buffer and reinitialize with new codecs.
325
+ * Ported from mews.js:696-730
326
+ */
327
+ private clearAndReinit(mime: string, newCodecs: string[]): void {
328
+ this._do((remaining_do_on_updateend) => {
329
+ if (!this.sourceBuffer) {
330
+ // No sourceBuffer to clear, just reinit
331
+ this.reinitBuffer(mime, newCodecs, remaining_do_on_updateend);
332
+ return;
333
+ }
334
+
335
+ if (this.sourceBuffer.updating) {
336
+ // Still updating, schedule for later
337
+ this._doNext(() => this.clearAndReinit(mime, newCodecs));
338
+ return;
339
+ }
340
+
341
+ try {
342
+ // Clear buffer (mews.js:701)
343
+ if (!isNaN(this.mediaSource.duration)) {
344
+ this.sourceBuffer.remove(0, Infinity);
345
+ }
346
+ } catch (e) {
347
+ // Ignore
348
+ }
349
+
350
+ // Wait for remove to complete, then reinit
351
+ this._doNext(() => {
352
+ this.reinitBuffer(mime, newCodecs, remaining_do_on_updateend);
353
+ });
354
+ });
355
+ }
356
+
357
+ /**
358
+ * Reinitialize buffer with new codecs.
359
+ * Ported from mews.js:703-724
360
+ */
361
+ private reinitBuffer(
362
+ mime: string,
363
+ newCodecs: string[],
364
+ remaining_do_on_updateend?: Array<() => void>
365
+ ): void {
366
+ // Save queue
367
+ const remaining = this.queue.slice();
368
+ this.queue = [];
369
+
370
+ // Remove old sourceBuffer
371
+ if (this.sourceBuffer && this.mediaSource.readyState === 'open') {
372
+ try {
373
+ this.mediaSource.removeSourceBuffer(this.sourceBuffer);
374
+ } catch (e) {
375
+ // Ignore
376
+ }
377
+ }
378
+ this.sourceBuffer = null;
379
+ this._busy = false;
380
+
381
+ try {
382
+ // Create new sourceBuffer (mews.js:713-715)
383
+ this.sourceBuffer = this.mediaSource.addSourceBuffer(mime);
384
+ this.sourceBuffer.mode = 'segments';
385
+ this._codecs = newCodecs;
386
+
387
+ this.installEventHandlers();
388
+
389
+ // Restore any remaining do_on_updateend functions (mews.js:715)
390
+ if (remaining_do_on_updateend?.length) {
391
+ this.do_on_updateend = remaining_do_on_updateend;
392
+ }
393
+
394
+ // Drain message queue (mews.js:341-367)
395
+ this.drainMessageQueue();
396
+
397
+ // Restore queued data
398
+ for (const frag of remaining) {
399
+ this.append(frag);
400
+ }
401
+ } catch (e: any) {
402
+ this.onError(e?.message || 'Failed to reinit SourceBuffer');
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Wait for playback to reach switch point, then clear and reinit.
408
+ * Ported from mews.js:751-785
409
+ */
410
+ private awaitSwitchingPoint(mime: string, switchPointMs: number, newCodecs: string[]): void {
411
+ const tSec = switchPointMs / 1000;
412
+
413
+ const clearAndReinit = () => {
414
+ this.clearAndReinit(mime, newCodecs);
415
+ };
416
+
417
+ // Wait for video.currentTime to reach switch point
418
+ const onTimeUpdate = () => {
419
+ if (this.videoElement.currentTime >= tSec) {
420
+ this.videoElement.removeEventListener('timeupdate', onTimeUpdate);
421
+ this.videoElement.removeEventListener('waiting', onWaiting);
422
+ clearAndReinit();
423
+ }
424
+ };
425
+
426
+ // Or if video is waiting (buffer empty before switch point)
427
+ const onWaiting = () => {
428
+ this.videoElement.removeEventListener('timeupdate', onTimeUpdate);
429
+ this.videoElement.removeEventListener('waiting', onWaiting);
430
+ clearAndReinit();
431
+ };
432
+
433
+ this.videoElement.addEventListener('timeupdate', onTimeUpdate);
434
+ this.videoElement.addEventListener('waiting', onWaiting);
435
+ }
436
+
437
+ /**
438
+ * Drain the oldest message queue and append its data.
439
+ * Called after source buffer reinit to flush queued track switch data.
440
+ * Ported from mews.js:341-367
441
+ */
442
+ private drainMessageQueue(): void {
443
+ if (!this.msgqueue || this.msgqueue.length === 0) return;
444
+
445
+ // Get oldest queue
446
+ const oldest = this.msgqueue[0];
447
+
448
+ let do_do = false; // If no messages, trigger updateend manually (mews.js:357-358)
449
+
450
+ if (oldest.length) {
451
+ // Append all data from oldest queue (mews.js:346-355)
452
+ for (const frag of oldest) {
453
+ if (this.sourceBuffer && (this.sourceBuffer.updating || this.queue.length || this._busy)) {
454
+ this.queue.push(frag);
455
+ } else {
456
+ this._append(frag);
457
+ }
458
+ }
459
+ } else {
460
+ do_do = true;
461
+ }
462
+
463
+ // Remove oldest queue (mews.js:360)
464
+ this.msgqueue.shift();
465
+ if (this.msgqueue.length === 0) {
466
+ this.msgqueue = false;
467
+ }
468
+
469
+ if (this.debugging) {
470
+ console.log(
471
+ 'MEWS: drained msgqueue',
472
+ this.msgqueue ? `${this.msgqueue.length} more queue(s) remain` : ''
473
+ );
474
+ }
475
+
476
+ // Manually trigger updateend if queue was empty (mews.js:363-366)
477
+ if (do_do && this.sourceBuffer) {
478
+ this.sourceBuffer.dispatchEvent(new Event('updateend'));
479
+ }
480
+ }
481
+
482
+ /**
483
+ * Install event handlers on the sourceBuffer.
484
+ * Ported from mews.js:224-279
485
+ */
486
+ private installEventHandlers(): void {
487
+ if (!this.sourceBuffer) return;
488
+
489
+ this.sourceBuffer.addEventListener('updateend', () => {
490
+ if (!this.sourceBuffer) {
491
+ if (this.debugging) console.log('MEWS: updateend but sourceBuffer is null');
492
+ return;
493
+ }
494
+
495
+ // Every 500 fragments, clean the buffer (mews.js:237-245)
496
+ if (this.fragmentCount >= 500) {
497
+ this.fragmentCount = 0;
498
+ this._clean(10); // Keep 10 seconds
499
+ } else {
500
+ this.fragmentCount++;
501
+ }
502
+
503
+ // Execute queued operations (mews.js:247-262)
504
+ const do_funcs = this.do_on_updateend.slice();
505
+ this.do_on_updateend = [];
506
+
507
+ for (let i = 0; i < do_funcs.length; i++) {
508
+ if (!this.sourceBuffer) {
509
+ if (this.debugging) console.warn('MEWS: doing updateend but sb was reset');
510
+ break;
511
+ }
512
+ if (this.sourceBuffer.updating) {
513
+ // Still updating, requeue remaining functions (mews.js:255-259)
514
+ this.do_on_updateend = this.do_on_updateend.concat(do_funcs.slice(i));
515
+ if (this.debugging) console.warn('MEWS: doing updateend but was interrupted');
516
+ break;
517
+ }
518
+ try {
519
+ // Pass remaining functions as argument (mews.js:261)
520
+ do_funcs[i](i < do_funcs.length - 1 ? do_funcs.slice(i + 1) : []);
521
+ } catch (e) {
522
+ console.error('MEWS: error in do_on_updateend:', e);
523
+ }
524
+ }
525
+
526
+ this._busy = false;
527
+
528
+ // Process queued data (mews.js:269-272)
529
+ if (this.sourceBuffer && this.queue.length > 0 && !this.sourceBuffer.updating && !this.videoElement.error) {
530
+ this._append(this.queue.shift()!);
531
+ }
532
+ });
533
+
534
+ this.sourceBuffer.addEventListener('error', () => {
535
+ this.onError('SourceBuffer error');
536
+ });
537
+ }
538
+
539
+ /**
540
+ * Flush any pending queue data.
541
+ */
542
+ private flushQueue(): void {
543
+ if (!this.sourceBuffer) return;
544
+
545
+ const pending = this.queue.slice();
546
+ this.queue = [];
547
+ for (const frag of pending) {
548
+ this.append(frag);
549
+ }
550
+ }
551
+
552
+ /**
553
+ * Check if there's an active message queue (track switch in progress)
554
+ */
555
+ hasActiveMessageQueue(): boolean {
556
+ return this.msgqueue !== false;
557
+ }
558
+
559
+ destroy(): void {
560
+ if (this.sourceBuffer) {
561
+ try {
562
+ this.sourceBuffer.abort();
563
+ } catch {}
564
+ }
565
+ this.sourceBuffer = null;
566
+ this.queue = [];
567
+ this._busy = false;
568
+ this.do_on_updateend = [];
569
+ this.msgqueue = false;
570
+ this.paused = false;
571
+ }
572
+ }