@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.
- package/dist/cjs/index.js +19493 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.js +19398 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/player.css +2140 -0
- package/dist/types/core/ABRController.d.ts +164 -0
- package/dist/types/core/CodecUtils.d.ts +54 -0
- package/dist/types/core/Disposable.d.ts +61 -0
- package/dist/types/core/EventEmitter.d.ts +73 -0
- package/dist/types/core/GatewayClient.d.ts +144 -0
- package/dist/types/core/InteractionController.d.ts +121 -0
- package/dist/types/core/LiveDurationProxy.d.ts +102 -0
- package/dist/types/core/MetaTrackManager.d.ts +220 -0
- package/dist/types/core/MistReporter.d.ts +163 -0
- package/dist/types/core/MistSignaling.d.ts +148 -0
- package/dist/types/core/PlayerController.d.ts +665 -0
- package/dist/types/core/PlayerInterface.d.ts +230 -0
- package/dist/types/core/PlayerManager.d.ts +182 -0
- package/dist/types/core/PlayerRegistry.d.ts +27 -0
- package/dist/types/core/QualityMonitor.d.ts +184 -0
- package/dist/types/core/ScreenWakeLockManager.d.ts +70 -0
- package/dist/types/core/SeekingUtils.d.ts +142 -0
- package/dist/types/core/StreamStateClient.d.ts +108 -0
- package/dist/types/core/SubtitleManager.d.ts +111 -0
- package/dist/types/core/TelemetryReporter.d.ts +79 -0
- package/dist/types/core/TimeFormat.d.ts +97 -0
- package/dist/types/core/TimerManager.d.ts +83 -0
- package/dist/types/core/UrlUtils.d.ts +81 -0
- package/dist/types/core/detector.d.ts +149 -0
- package/dist/types/core/index.d.ts +49 -0
- package/dist/types/core/scorer.d.ts +167 -0
- package/dist/types/core/selector.d.ts +9 -0
- package/dist/types/index.d.ts +45 -0
- package/dist/types/lib/utils.d.ts +2 -0
- package/dist/types/players/DashJsPlayer.d.ts +102 -0
- package/dist/types/players/HlsJsPlayer.d.ts +70 -0
- package/dist/types/players/MewsWsPlayer/SourceBufferManager.d.ts +119 -0
- package/dist/types/players/MewsWsPlayer/WebSocketManager.d.ts +60 -0
- package/dist/types/players/MewsWsPlayer/index.d.ts +220 -0
- package/dist/types/players/MewsWsPlayer/types.d.ts +89 -0
- package/dist/types/players/MistPlayer.d.ts +25 -0
- package/dist/types/players/MistWebRTCPlayer/index.d.ts +133 -0
- package/dist/types/players/NativePlayer.d.ts +143 -0
- package/dist/types/players/VideoJsPlayer.d.ts +59 -0
- package/dist/types/players/WebCodecsPlayer/JitterBuffer.d.ts +118 -0
- package/dist/types/players/WebCodecsPlayer/LatencyProfiles.d.ts +64 -0
- package/dist/types/players/WebCodecsPlayer/RawChunkParser.d.ts +63 -0
- package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +174 -0
- package/dist/types/players/WebCodecsPlayer/WebSocketController.d.ts +164 -0
- package/dist/types/players/WebCodecsPlayer/index.d.ts +149 -0
- package/dist/types/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.d.ts +105 -0
- package/dist/types/players/WebCodecsPlayer/types.d.ts +395 -0
- package/dist/types/players/WebCodecsPlayer/worker/decoder.worker.d.ts +13 -0
- package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +197 -0
- package/dist/types/players/index.d.ts +14 -0
- package/dist/types/styles/index.d.ts +11 -0
- package/dist/types/types.d.ts +363 -0
- package/dist/types/vanilla/FrameWorksPlayer.d.ts +143 -0
- package/dist/types/vanilla/index.d.ts +19 -0
- package/dist/workers/decoder.worker.js +989 -0
- package/dist/workers/decoder.worker.js.map +1 -0
- package/package.json +80 -0
- package/src/core/ABRController.ts +550 -0
- package/src/core/CodecUtils.ts +257 -0
- package/src/core/Disposable.ts +120 -0
- package/src/core/EventEmitter.ts +113 -0
- package/src/core/GatewayClient.ts +439 -0
- package/src/core/InteractionController.ts +712 -0
- package/src/core/LiveDurationProxy.ts +270 -0
- package/src/core/MetaTrackManager.ts +753 -0
- package/src/core/MistReporter.ts +543 -0
- package/src/core/MistSignaling.ts +346 -0
- package/src/core/PlayerController.ts +2829 -0
- package/src/core/PlayerInterface.ts +432 -0
- package/src/core/PlayerManager.ts +900 -0
- package/src/core/PlayerRegistry.ts +149 -0
- package/src/core/QualityMonitor.ts +597 -0
- package/src/core/ScreenWakeLockManager.ts +163 -0
- package/src/core/SeekingUtils.ts +364 -0
- package/src/core/StreamStateClient.ts +457 -0
- package/src/core/SubtitleManager.ts +297 -0
- package/src/core/TelemetryReporter.ts +308 -0
- package/src/core/TimeFormat.ts +205 -0
- package/src/core/TimerManager.ts +209 -0
- package/src/core/UrlUtils.ts +179 -0
- package/src/core/detector.ts +382 -0
- package/src/core/index.ts +140 -0
- package/src/core/scorer.ts +553 -0
- package/src/core/selector.ts +16 -0
- package/src/global.d.ts +11 -0
- package/src/index.ts +75 -0
- package/src/lib/utils.ts +6 -0
- package/src/players/DashJsPlayer.ts +642 -0
- package/src/players/HlsJsPlayer.ts +483 -0
- package/src/players/MewsWsPlayer/SourceBufferManager.ts +572 -0
- package/src/players/MewsWsPlayer/WebSocketManager.ts +241 -0
- package/src/players/MewsWsPlayer/index.ts +1065 -0
- package/src/players/MewsWsPlayer/types.ts +106 -0
- package/src/players/MistPlayer.ts +188 -0
- package/src/players/MistWebRTCPlayer/index.ts +703 -0
- package/src/players/NativePlayer.ts +820 -0
- package/src/players/VideoJsPlayer.ts +643 -0
- package/src/players/WebCodecsPlayer/JitterBuffer.ts +299 -0
- package/src/players/WebCodecsPlayer/LatencyProfiles.ts +151 -0
- package/src/players/WebCodecsPlayer/RawChunkParser.ts +151 -0
- package/src/players/WebCodecsPlayer/SyncController.ts +456 -0
- package/src/players/WebCodecsPlayer/WebSocketController.ts +564 -0
- package/src/players/WebCodecsPlayer/index.ts +1650 -0
- package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +379 -0
- package/src/players/WebCodecsPlayer/types.ts +542 -0
- package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +1360 -0
- package/src/players/WebCodecsPlayer/worker/types.ts +276 -0
- package/src/players/index.ts +22 -0
- package/src/styles/animations.css +21 -0
- package/src/styles/index.ts +52 -0
- package/src/styles/player.css +2126 -0
- package/src/styles/tailwind.css +1015 -0
- package/src/types.ts +421 -0
- package/src/vanilla/FrameWorksPlayer.ts +367 -0
- 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
|
+
}
|