@livepeer-frameworks/player-core 0.0.4 → 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 +21 -6
- package/dist/cjs/index.js +792 -146
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +792 -146
- package/dist/esm/index.js.map +1 -1
- package/dist/player.css +185 -373
- package/dist/types/core/GatewayClient.d.ts +3 -4
- package/dist/types/core/InteractionController.d.ts +12 -0
- package/dist/types/core/MetaTrackManager.d.ts +1 -1
- package/dist/types/core/PlayerController.d.ts +18 -2
- package/dist/types/core/PlayerInterface.d.ts +10 -0
- package/dist/types/core/SeekingUtils.d.ts +3 -1
- package/dist/types/core/StreamStateClient.d.ts +1 -1
- package/dist/types/players/HlsJsPlayer.d.ts +8 -0
- package/dist/types/players/MewsWsPlayer/index.d.ts +1 -1
- package/dist/types/players/VideoJsPlayer.d.ts +12 -4
- package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +1 -1
- package/dist/types/players/WebCodecsPlayer/index.d.ts +11 -0
- package/dist/types/players/WebCodecsPlayer/types.d.ts +25 -3
- package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +20 -2
- package/dist/types/types.d.ts +32 -1
- package/dist/types/vanilla/FrameWorksPlayer.d.ts +5 -5
- package/dist/types/vanilla/index.d.ts +3 -3
- package/dist/workers/decoder.worker.js +183 -6
- package/dist/workers/decoder.worker.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ABRController.ts +38 -36
- package/src/core/CodecUtils.ts +50 -47
- package/src/core/Disposable.ts +4 -4
- package/src/core/EventEmitter.ts +1 -1
- package/src/core/GatewayClient.ts +48 -48
- package/src/core/InteractionController.ts +89 -82
- package/src/core/LiveDurationProxy.ts +14 -16
- package/src/core/MetaTrackManager.ts +74 -66
- package/src/core/MistReporter.ts +72 -45
- package/src/core/MistSignaling.ts +59 -56
- package/src/core/PlayerController.ts +724 -375
- package/src/core/PlayerInterface.ts +89 -59
- package/src/core/PlayerManager.ts +118 -123
- 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 +75 -69
- package/src/core/SubtitleManager.ts +25 -23
- package/src/core/TelemetryReporter.ts +34 -31
- package/src/core/TimeFormat.ts +13 -17
- package/src/core/TimerManager.ts +25 -9
- 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 +137 -138
- 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 +175 -114
- package/src/players/HlsJsPlayer.ts +154 -76
- package/src/players/MewsWsPlayer/SourceBufferManager.ts +44 -39
- package/src/players/MewsWsPlayer/WebSocketManager.ts +9 -10
- package/src/players/MewsWsPlayer/index.ts +196 -154
- package/src/players/MewsWsPlayer/types.ts +21 -21
- package/src/players/MistPlayer.ts +46 -27
- package/src/players/MistWebRTCPlayer/index.ts +175 -129
- package/src/players/NativePlayer.ts +203 -143
- package/src/players/VideoJsPlayer.ts +200 -146
- 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 +46 -55
- package/src/players/WebCodecsPlayer/WebSocketController.ts +67 -69
- package/src/players/WebCodecsPlayer/index.ts +280 -220
- package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +12 -17
- package/src/players/WebCodecsPlayer/types.ts +81 -53
- package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +255 -192
- package/src/players/WebCodecsPlayer/worker/types.ts +33 -29
- package/src/players/index.ts +8 -8
- package/src/styles/animations.css +2 -1
- package/src/styles/player.css +182 -356
- package/src/styles/tailwind.css +473 -159
- package/src/types.ts +75 -33
- package/src/vanilla/FrameWorksPlayer.ts +34 -19
- package/src/vanilla/index.ts +7 -7
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { BasePlayer } from
|
|
2
|
-
import { checkProtocolMismatch, getBrowserInfo, isFileProtocol } from
|
|
3
|
-
import { translateCodec } from
|
|
4
|
-
import type {
|
|
1
|
+
import { BasePlayer } from "../core/PlayerInterface";
|
|
2
|
+
import { checkProtocolMismatch, getBrowserInfo, isFileProtocol } from "../core/detector";
|
|
3
|
+
import { translateCodec } from "../core/CodecUtils";
|
|
4
|
+
import type {
|
|
5
|
+
StreamSource,
|
|
6
|
+
StreamInfo,
|
|
7
|
+
PlayerOptions,
|
|
8
|
+
PlayerCapability,
|
|
9
|
+
} from "../core/PlayerInterface";
|
|
5
10
|
|
|
6
11
|
// Player implementation class
|
|
7
12
|
export class DashJsPlayerImpl extends BasePlayer {
|
|
@@ -9,7 +14,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
9
14
|
name: "Dash.js Player",
|
|
10
15
|
shortname: "dashjs",
|
|
11
16
|
priority: 100, // Below legacy (99) - DASH support is experimental
|
|
12
|
-
mimes: ["dash/video/mp4"]
|
|
17
|
+
mimes: ["dash/video/mp4"],
|
|
13
18
|
};
|
|
14
19
|
|
|
15
20
|
private dashPlayer: any = null;
|
|
@@ -20,7 +25,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
20
25
|
// Live duration proxy state (ported from reference dashjs.js:81-122)
|
|
21
26
|
private lastProgress = Date.now();
|
|
22
27
|
private videoProxy: HTMLVideoElement | null = null;
|
|
23
|
-
private streamType:
|
|
28
|
+
private streamType: "live" | "vod" | "unknown" = "unknown";
|
|
24
29
|
|
|
25
30
|
// Subtitle deferred loading (ported from reference dashjs.js:173-197)
|
|
26
31
|
private subsLoaded = false;
|
|
@@ -30,7 +35,11 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
30
35
|
return this.capability.mimes.includes(mimetype);
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
isBrowserSupported(
|
|
38
|
+
isBrowserSupported(
|
|
39
|
+
mimetype: string,
|
|
40
|
+
source: StreamSource,
|
|
41
|
+
streamInfo: StreamInfo
|
|
42
|
+
): boolean | string[] {
|
|
34
43
|
// Check protocol mismatch
|
|
35
44
|
if (checkProtocolMismatch(source.url)) {
|
|
36
45
|
return false;
|
|
@@ -54,12 +63,12 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
54
63
|
|
|
55
64
|
// Group tracks by type
|
|
56
65
|
for (const track of streamInfo.meta.tracks) {
|
|
57
|
-
if (track.type ===
|
|
58
|
-
if (track.codec ===
|
|
66
|
+
if (track.type === "meta") {
|
|
67
|
+
if (track.codec === "subtitle") {
|
|
59
68
|
// Check for WebVTT subtitle support
|
|
60
69
|
for (const src of streamInfo.source) {
|
|
61
|
-
if (src.type ===
|
|
62
|
-
playableTracks.push(
|
|
70
|
+
if (src.type === "html5/text/vtt") {
|
|
71
|
+
playableTracks.push("subtitle");
|
|
63
72
|
break;
|
|
64
73
|
}
|
|
65
74
|
}
|
|
@@ -75,7 +84,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
75
84
|
|
|
76
85
|
// DASH-incompatible audio codecs for fMP4 segments (even if browser MSE supports them)
|
|
77
86
|
// Standard DASH audio: AAC, MP3, AC-3/E-AC-3. OPUS only works in WebM DASH (not fMP4)
|
|
78
|
-
const DASH_INCOMPATIBLE_AUDIO = [
|
|
87
|
+
const DASH_INCOMPATIBLE_AUDIO = ["OPUS", "Opus", "opus", "VORBIS", "Vorbis"];
|
|
79
88
|
|
|
80
89
|
// Test codec support for video/audio tracks
|
|
81
90
|
for (const [trackType, tracks] of Object.entries(tracksByType)) {
|
|
@@ -83,14 +92,14 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
83
92
|
|
|
84
93
|
for (const track of tracks) {
|
|
85
94
|
// Explicit DASH codec filtering - OPUS in fMP4 DASH doesn't work reliably
|
|
86
|
-
if (trackType ===
|
|
95
|
+
if (trackType === "audio" && DASH_INCOMPATIBLE_AUDIO.includes(track.codec)) {
|
|
87
96
|
console.debug(`[DashJS] Codec incompatible with DASH fMP4: ${track.codec}`);
|
|
88
97
|
continue;
|
|
89
98
|
}
|
|
90
99
|
|
|
91
100
|
const codecString = translateCodec(track);
|
|
92
101
|
// Use correct container type for audio vs video tracks
|
|
93
|
-
const container = trackType ===
|
|
102
|
+
const container = trackType === "audio" ? "audio/mp4" : "video/mp4";
|
|
94
103
|
const mimeType = `${container};codecs="${codecString}"`;
|
|
95
104
|
|
|
96
105
|
if (MediaSource.isTypeSupported && MediaSource.isTypeSupported(mimeType)) {
|
|
@@ -114,8 +123,8 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
114
123
|
* Ported from reference dashjs.js live detection.
|
|
115
124
|
*/
|
|
116
125
|
private isLiveStream(): boolean {
|
|
117
|
-
if (this.streamType ===
|
|
118
|
-
if (this.streamType ===
|
|
126
|
+
if (this.streamType === "live") return true;
|
|
127
|
+
if (this.streamType === "vod") return false;
|
|
119
128
|
// Fallback: check video duration
|
|
120
129
|
const v = this.videoElement;
|
|
121
130
|
if (v && (v.duration === Infinity || !isFinite(v.duration))) {
|
|
@@ -132,13 +141,13 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
132
141
|
* This makes the seek bar usable for live content.
|
|
133
142
|
*/
|
|
134
143
|
private createVideoProxy(video: HTMLVideoElement): HTMLVideoElement {
|
|
135
|
-
if (!(
|
|
144
|
+
if (!("Proxy" in window)) {
|
|
136
145
|
// Fallback for older browsers
|
|
137
146
|
return video;
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
// Track buffer progress for duration extrapolation
|
|
141
|
-
video.addEventListener(
|
|
150
|
+
video.addEventListener("progress", () => {
|
|
142
151
|
this.lastProgress = Date.now();
|
|
143
152
|
});
|
|
144
153
|
|
|
@@ -146,7 +155,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
146
155
|
return new Proxy(video, {
|
|
147
156
|
get(target, key, receiver) {
|
|
148
157
|
// Override duration for live streams (reference dashjs.js:108-116)
|
|
149
|
-
if (key ===
|
|
158
|
+
if (key === "duration" && self.isLiveStream()) {
|
|
150
159
|
const buffered = target.buffered;
|
|
151
160
|
if (buffered.length > 0) {
|
|
152
161
|
const bufferEnd = buffered.end(buffered.length - 1);
|
|
@@ -156,14 +165,14 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
156
165
|
}
|
|
157
166
|
const value = Reflect.get(target, key, receiver);
|
|
158
167
|
// Bind functions to the original target
|
|
159
|
-
if (typeof value ===
|
|
168
|
+
if (typeof value === "function") {
|
|
160
169
|
return value.bind(target);
|
|
161
170
|
}
|
|
162
171
|
return value;
|
|
163
172
|
},
|
|
164
173
|
set(target, key, value) {
|
|
165
174
|
return Reflect.set(target, key, value);
|
|
166
|
-
}
|
|
175
|
+
},
|
|
167
176
|
}) as HTMLVideoElement;
|
|
168
177
|
}
|
|
169
178
|
|
|
@@ -173,9 +182,15 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
173
182
|
*/
|
|
174
183
|
private setupEventLogging(dashjs: any): void {
|
|
175
184
|
const skipEvents = [
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
185
|
+
"METRIC_ADDED",
|
|
186
|
+
"METRIC_UPDATED",
|
|
187
|
+
"METRIC_CHANGED",
|
|
188
|
+
"METRICS_CHANGED",
|
|
189
|
+
"FRAGMENT_LOADING_STARTED",
|
|
190
|
+
"FRAGMENT_LOADING_COMPLETED",
|
|
191
|
+
"LOG",
|
|
192
|
+
"PLAYBACK_TIME_UPDATED",
|
|
193
|
+
"PLAYBACK_PROGRESS",
|
|
179
194
|
];
|
|
180
195
|
|
|
181
196
|
const events = dashjs.MediaPlayer?.events || {};
|
|
@@ -184,7 +199,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
184
199
|
this.dashPlayer.on(events[eventKey], (e: any) => {
|
|
185
200
|
if (this.destroyed) return;
|
|
186
201
|
if (this.debugging) {
|
|
187
|
-
console.log(
|
|
202
|
+
console.log("DASH event:", e.type);
|
|
188
203
|
}
|
|
189
204
|
});
|
|
190
205
|
}
|
|
@@ -196,7 +211,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
196
211
|
* Ported from reference dashjs.js:173-197.
|
|
197
212
|
*/
|
|
198
213
|
private setupSubtitleHandling(): void {
|
|
199
|
-
this.dashPlayer.on(
|
|
214
|
+
this.dashPlayer.on("allTextTracksAdded", () => {
|
|
200
215
|
if (this.destroyed) return;
|
|
201
216
|
this.subsLoaded = true;
|
|
202
217
|
if (this.pendingSubtitleId !== null) {
|
|
@@ -211,41 +226,45 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
211
226
|
* Ported from reference dashjs.js:207-211.
|
|
212
227
|
*/
|
|
213
228
|
private setupStalledHandling(): void {
|
|
214
|
-
this.videoElement?.addEventListener(
|
|
229
|
+
this.videoElement?.addEventListener("progress", () => {
|
|
215
230
|
// Clear any stalled state when buffer advances
|
|
216
231
|
// This integrates with the loading indicator system
|
|
217
232
|
});
|
|
218
233
|
}
|
|
219
234
|
|
|
220
|
-
async initialize(
|
|
235
|
+
async initialize(
|
|
236
|
+
container: HTMLElement,
|
|
237
|
+
source: StreamSource,
|
|
238
|
+
options: PlayerOptions
|
|
239
|
+
): Promise<HTMLVideoElement> {
|
|
221
240
|
this.destroyed = false;
|
|
222
241
|
this.container = container;
|
|
223
242
|
this.subsLoaded = false;
|
|
224
243
|
this.pendingSubtitleId = null;
|
|
225
|
-
container.classList.add(
|
|
244
|
+
container.classList.add("fw-player-container");
|
|
226
245
|
|
|
227
246
|
// Detect stream type from source if available (reference dashjs.js live detection)
|
|
228
247
|
const sourceType = (source as any).type;
|
|
229
|
-
if (sourceType ===
|
|
230
|
-
this.streamType =
|
|
231
|
-
} else if (sourceType ===
|
|
232
|
-
this.streamType =
|
|
248
|
+
if (sourceType === "live") {
|
|
249
|
+
this.streamType = "live";
|
|
250
|
+
} else if (sourceType === "vod") {
|
|
251
|
+
this.streamType = "vod";
|
|
233
252
|
} else {
|
|
234
|
-
this.streamType =
|
|
253
|
+
this.streamType = "unknown";
|
|
235
254
|
}
|
|
236
255
|
|
|
237
256
|
// Create video element
|
|
238
|
-
const video = document.createElement(
|
|
239
|
-
video.classList.add(
|
|
240
|
-
video.setAttribute(
|
|
241
|
-
video.setAttribute(
|
|
257
|
+
const video = document.createElement("video");
|
|
258
|
+
video.classList.add("fw-player-video");
|
|
259
|
+
video.setAttribute("playsinline", "");
|
|
260
|
+
video.setAttribute("crossorigin", "anonymous");
|
|
242
261
|
|
|
243
262
|
// Apply options (ported from reference dashjs.js:129-142)
|
|
244
263
|
if (options.autoplay) video.autoplay = true;
|
|
245
264
|
if (options.muted) video.muted = true;
|
|
246
265
|
video.controls = options.controls === true;
|
|
247
266
|
// Loop only for VoD (reference dashjs.js: live streams don't loop)
|
|
248
|
-
if (options.loop && this.streamType !==
|
|
267
|
+
if (options.loop && this.streamType !== "live") video.loop = true;
|
|
249
268
|
if (options.poster) video.poster = options.poster;
|
|
250
269
|
|
|
251
270
|
// Create proxy for live duration handling (reference dashjs.js:81-122)
|
|
@@ -259,13 +278,13 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
259
278
|
|
|
260
279
|
try {
|
|
261
280
|
// Dynamic import of DASH.js
|
|
262
|
-
console.debug(
|
|
263
|
-
const mod = await import(
|
|
281
|
+
console.debug("[DashJS] Importing dashjs module...");
|
|
282
|
+
const mod = await import("dashjs");
|
|
264
283
|
const dashjs = (mod as any).default || (mod as any);
|
|
265
|
-
console.debug(
|
|
284
|
+
console.debug("[DashJS] Module imported:", dashjs);
|
|
266
285
|
|
|
267
286
|
this.dashPlayer = dashjs.MediaPlayer().create();
|
|
268
|
-
console.debug(
|
|
287
|
+
console.debug("[DashJS] MediaPlayer created");
|
|
269
288
|
|
|
270
289
|
// Set up event logging (reference dashjs.js:152-160)
|
|
271
290
|
this.setupEventLogging(dashjs);
|
|
@@ -273,39 +292,40 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
273
292
|
// Set up subtitle handling (reference dashjs.js:173-197)
|
|
274
293
|
this.setupSubtitleHandling();
|
|
275
294
|
|
|
276
|
-
this.dashPlayer.on(
|
|
295
|
+
this.dashPlayer.on("error", (e: any) => {
|
|
277
296
|
if (this.destroyed) return;
|
|
278
|
-
const error = `DASH error: ${e?.event?.message || e?.message ||
|
|
279
|
-
console.error(
|
|
280
|
-
this.emit(
|
|
297
|
+
const error = `DASH error: ${e?.event?.message || e?.message || "unknown"}`;
|
|
298
|
+
console.error("[DashJS] Error event:", e);
|
|
299
|
+
this.emit("error", error);
|
|
281
300
|
});
|
|
282
301
|
|
|
283
302
|
// Log key dashjs events for debugging
|
|
284
|
-
this.dashPlayer.on(
|
|
285
|
-
console.debug(
|
|
303
|
+
this.dashPlayer.on("manifestLoaded", (e: any) => {
|
|
304
|
+
console.debug("[DashJS] manifestLoaded:", e);
|
|
286
305
|
});
|
|
287
|
-
this.dashPlayer.on(
|
|
288
|
-
console.debug(
|
|
306
|
+
this.dashPlayer.on("canPlay", () => {
|
|
307
|
+
console.debug("[DashJS] canPlay event");
|
|
289
308
|
});
|
|
290
309
|
|
|
291
310
|
// Log stream initialization for debugging
|
|
292
|
-
this.dashPlayer.on(
|
|
311
|
+
this.dashPlayer.on("streamInitialized", () => {
|
|
293
312
|
if (this.destroyed) return;
|
|
294
313
|
const isDynamic = this.dashPlayer.isDynamic?.() ?? false;
|
|
295
|
-
console.debug(
|
|
314
|
+
console.debug("[DashJS v5] streamInitialized - isDynamic:", isDynamic);
|
|
296
315
|
});
|
|
297
316
|
|
|
298
317
|
// Configure dashjs v5 streaming settings BEFORE initialization
|
|
318
|
+
// AGGRESSIVE settings for fastest startup and low latency
|
|
299
319
|
this.dashPlayer.updateSettings({
|
|
300
320
|
streaming: {
|
|
301
|
-
//
|
|
321
|
+
// AGGRESSIVE: Minimal buffers for fastest startup
|
|
302
322
|
buffer: {
|
|
303
323
|
fastSwitchEnabled: true,
|
|
304
|
-
stableBufferTime: 16
|
|
305
|
-
bufferTimeAtTopQuality: 30
|
|
306
|
-
bufferTimeAtTopQualityLongForm: 60
|
|
307
|
-
bufferToKeep: 30
|
|
308
|
-
bufferPruningInterval: 30
|
|
324
|
+
stableBufferTime: 4, // Reduced from 16 (aggressive!)
|
|
325
|
+
bufferTimeAtTopQuality: 8, // Reduced from 30
|
|
326
|
+
bufferTimeAtTopQualityLongForm: 15, // Reduced from 60
|
|
327
|
+
bufferToKeep: 10, // Reduced from 30
|
|
328
|
+
bufferPruningInterval: 10, // Reduced from 30
|
|
309
329
|
},
|
|
310
330
|
// Gaps/stall handling
|
|
311
331
|
gaps: {
|
|
@@ -314,12 +334,23 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
314
334
|
smallGapLimit: 1.5,
|
|
315
335
|
threshold: 0.3,
|
|
316
336
|
},
|
|
317
|
-
// ABR
|
|
337
|
+
// AGGRESSIVE: ABR with high initial bitrate estimate
|
|
318
338
|
abr: {
|
|
319
339
|
autoSwitchBitrate: { video: true, audio: true },
|
|
320
340
|
limitBitrateByPortal: false,
|
|
321
341
|
useDefaultABRRules: true,
|
|
322
|
-
initialBitrate: { video:
|
|
342
|
+
initialBitrate: { video: 5_000_000, audio: 128_000 }, // 5Mbps initial (was -1)
|
|
343
|
+
},
|
|
344
|
+
// LIVE CATCHUP - critical for maintaining live edge (was missing!)
|
|
345
|
+
liveCatchup: {
|
|
346
|
+
enabled: true,
|
|
347
|
+
maxDrift: 1.5, // Seek to live if drift > 1.5s
|
|
348
|
+
playbackRate: {
|
|
349
|
+
max: 0.15, // Speed up by max 15%
|
|
350
|
+
min: -0.15, // Slow down by max 15%
|
|
351
|
+
},
|
|
352
|
+
playbackBufferMin: 0.3, // Min buffer before catchup
|
|
353
|
+
mode: "liveCatchupModeDefault",
|
|
323
354
|
},
|
|
324
355
|
// Retry settings - more aggressive
|
|
325
356
|
retryAttempts: {
|
|
@@ -343,7 +374,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
343
374
|
// Timeout settings - faster abandonment of slow segments
|
|
344
375
|
timeoutAttempts: {
|
|
345
376
|
MPD: 2,
|
|
346
|
-
MediaSegment: 2,
|
|
377
|
+
MediaSegment: 2, // Abandon after 2 timeout attempts
|
|
347
378
|
InitializationSegment: 2,
|
|
348
379
|
BitstreamSwitchingSegment: 2,
|
|
349
380
|
IndexSegment: 2,
|
|
@@ -351,47 +382,51 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
351
382
|
other: 1,
|
|
352
383
|
},
|
|
353
384
|
// Abandon slow segment downloads more quickly
|
|
354
|
-
abandonLoadTimeout: 5000,
|
|
385
|
+
abandonLoadTimeout: 5000, // 5 seconds instead of default 10
|
|
355
386
|
xhrWithCredentials: false,
|
|
356
387
|
text: { defaultEnabled: false },
|
|
357
|
-
//
|
|
388
|
+
// AGGRESSIVE: Tighter live delay
|
|
358
389
|
delay: {
|
|
359
|
-
liveDelay:
|
|
390
|
+
liveDelay: 2, // Reduced from 4 (2s behind live edge)
|
|
360
391
|
liveDelayFragmentCount: null,
|
|
361
|
-
useSuggestedPresentationDelay:
|
|
392
|
+
useSuggestedPresentationDelay: false, // Ignore manifest suggestions
|
|
362
393
|
},
|
|
363
394
|
},
|
|
364
395
|
debug: {
|
|
365
|
-
logLevel: 4,
|
|
396
|
+
logLevel: 4, // Always debug for now to see what's happening
|
|
366
397
|
},
|
|
367
398
|
});
|
|
368
399
|
|
|
369
400
|
// Add fragment loading event listeners to debug the pending issue
|
|
370
|
-
this.dashPlayer.on(
|
|
371
|
-
console.debug(
|
|
401
|
+
this.dashPlayer.on("fragmentLoadingStarted", (e: any) => {
|
|
402
|
+
console.debug("[DashJS] Fragment loading started:", e.request?.url?.split("/").pop());
|
|
372
403
|
});
|
|
373
|
-
this.dashPlayer.on(
|
|
374
|
-
console.debug(
|
|
404
|
+
this.dashPlayer.on("fragmentLoadingCompleted", (e: any) => {
|
|
405
|
+
console.debug("[DashJS] Fragment loading completed:", e.request?.url?.split("/").pop());
|
|
375
406
|
});
|
|
376
|
-
this.dashPlayer.on(
|
|
377
|
-
console.warn(
|
|
407
|
+
this.dashPlayer.on("fragmentLoadingAbandoned", (e: any) => {
|
|
408
|
+
console.warn("[DashJS] Fragment loading ABANDONED:", e.request?.url?.split("/").pop(), e);
|
|
378
409
|
});
|
|
379
|
-
this.dashPlayer.on(
|
|
380
|
-
console.error(
|
|
410
|
+
this.dashPlayer.on("fragmentLoadingFailed", (e: any) => {
|
|
411
|
+
console.error("[DashJS] Fragment loading FAILED:", e.request?.url?.split("/").pop(), e);
|
|
381
412
|
});
|
|
382
413
|
|
|
383
414
|
// dashjs v5: Initialize with URL
|
|
384
|
-
console.debug(
|
|
415
|
+
console.debug("[DashJS v5] Initializing with URL:", source.url);
|
|
385
416
|
this.dashPlayer.initialize(video, source.url, options.autoplay ?? false);
|
|
386
|
-
console.debug(
|
|
417
|
+
console.debug("[DashJS v5] Initialize called");
|
|
387
418
|
|
|
388
419
|
// Optional subtitle tracks helper from source extras (external tracks)
|
|
389
420
|
try {
|
|
390
|
-
const subs = (source as any).subtitles as Array<{
|
|
421
|
+
const subs = (source as any).subtitles as Array<{
|
|
422
|
+
label: string;
|
|
423
|
+
lang: string;
|
|
424
|
+
src: string;
|
|
425
|
+
}>;
|
|
391
426
|
if (Array.isArray(subs)) {
|
|
392
427
|
subs.forEach((s, idx) => {
|
|
393
|
-
const track = document.createElement(
|
|
394
|
-
track.kind =
|
|
428
|
+
const track = document.createElement("track");
|
|
429
|
+
track.kind = "subtitles";
|
|
395
430
|
track.label = s.label;
|
|
396
431
|
track.srclang = s.lang;
|
|
397
432
|
track.src = s.src;
|
|
@@ -402,9 +437,8 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
402
437
|
} catch {}
|
|
403
438
|
|
|
404
439
|
return video;
|
|
405
|
-
|
|
406
440
|
} catch (error: any) {
|
|
407
|
-
this.emit(
|
|
441
|
+
this.emit("error", error.message || String(error));
|
|
408
442
|
throw error;
|
|
409
443
|
}
|
|
410
444
|
}
|
|
@@ -413,32 +447,37 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
413
447
|
* Get DASH.js-specific stats for ABR and playback monitoring
|
|
414
448
|
* Updated for dashjs v5 API
|
|
415
449
|
*/
|
|
416
|
-
async getStats(): Promise<
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
450
|
+
async getStats(): Promise<
|
|
451
|
+
| {
|
|
452
|
+
type: "dash";
|
|
453
|
+
currentQuality: number;
|
|
454
|
+
bufferLevel: number;
|
|
455
|
+
bitrateInfoList: Array<{ bitrate: number; width: number; height: number }>;
|
|
456
|
+
currentBitrate: number;
|
|
457
|
+
playbackRate: number;
|
|
458
|
+
}
|
|
459
|
+
| undefined
|
|
460
|
+
> {
|
|
424
461
|
if (!this.dashPlayer || !this.videoElement) return undefined;
|
|
425
462
|
|
|
426
463
|
try {
|
|
427
464
|
// dashjs v5: getCurrentRepresentationForType returns Representation object
|
|
428
|
-
const currentRep = this.dashPlayer.getCurrentRepresentationForType?.(
|
|
465
|
+
const currentRep = this.dashPlayer.getCurrentRepresentationForType?.("video");
|
|
429
466
|
// dashjs v5: getRepresentationsByType returns Representation[] (bandwidth instead of bitrate)
|
|
430
|
-
const representations = this.dashPlayer.getRepresentationsByType?.(
|
|
431
|
-
const bufferLevel = this.dashPlayer.getBufferLength(
|
|
467
|
+
const representations = this.dashPlayer.getRepresentationsByType?.("video") || [];
|
|
468
|
+
const bufferLevel = this.dashPlayer.getBufferLength("video") || 0;
|
|
432
469
|
|
|
433
470
|
// Find current quality index
|
|
434
|
-
const currentIndex = currentRep
|
|
471
|
+
const currentIndex = currentRep
|
|
472
|
+
? representations.findIndex((r: any) => r.id === currentRep.id)
|
|
473
|
+
: 0;
|
|
435
474
|
|
|
436
475
|
return {
|
|
437
|
-
type:
|
|
476
|
+
type: "dash",
|
|
438
477
|
currentQuality: currentIndex >= 0 ? currentIndex : 0,
|
|
439
478
|
bufferLevel,
|
|
440
479
|
bitrateInfoList: representations.map((r: any) => ({
|
|
441
|
-
bitrate: r.bandwidth || 0,
|
|
480
|
+
bitrate: r.bandwidth || 0, // v5 uses 'bandwidth' not 'bitrate'
|
|
442
481
|
width: r.width || 0,
|
|
443
482
|
height: r.height || 0,
|
|
444
483
|
})),
|
|
@@ -490,8 +529,8 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
490
529
|
if (!video || !this.isLiveStream()) return;
|
|
491
530
|
|
|
492
531
|
// DASH.js has a seekToLive method for live streams
|
|
493
|
-
if (this.dashPlayer && typeof this.dashPlayer.seekToLive ===
|
|
494
|
-
console.debug(
|
|
532
|
+
if (this.dashPlayer && typeof this.dashPlayer.seekToLive === "function") {
|
|
533
|
+
console.debug("[DashJS] jumpToLive using seekToLive()");
|
|
495
534
|
this.dashPlayer.seekToLive();
|
|
496
535
|
return;
|
|
497
536
|
}
|
|
@@ -500,7 +539,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
500
539
|
if (video.seekable && video.seekable.length > 0) {
|
|
501
540
|
const liveEdge = video.seekable.end(video.seekable.length - 1);
|
|
502
541
|
if (isFinite(liveEdge) && liveEdge > 0) {
|
|
503
|
-
console.debug(
|
|
542
|
+
console.debug("[DashJS] jumpToLive using seekable.end:", liveEdge);
|
|
504
543
|
video.currentTime = liveEdge;
|
|
505
544
|
}
|
|
506
545
|
}
|
|
@@ -514,7 +553,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
514
553
|
if (!video || !this.isLiveStream()) return 0;
|
|
515
554
|
|
|
516
555
|
// DASH.js provides live delay metrics
|
|
517
|
-
if (this.dashPlayer && typeof this.dashPlayer.getCurrentLiveLatency ===
|
|
556
|
+
if (this.dashPlayer && typeof this.dashPlayer.getCurrentLiveLatency === "function") {
|
|
518
557
|
return this.dashPlayer.getCurrentLiveLatency() * 1000;
|
|
519
558
|
}
|
|
520
559
|
|
|
@@ -539,13 +578,15 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
539
578
|
try {
|
|
540
579
|
this.dashPlayer.reset();
|
|
541
580
|
} catch (e) {
|
|
542
|
-
console.warn(
|
|
581
|
+
console.warn("Error destroying DASH.js:", e);
|
|
543
582
|
}
|
|
544
583
|
this.dashPlayer = null;
|
|
545
584
|
}
|
|
546
585
|
|
|
547
586
|
if (this.videoElement && this.container) {
|
|
548
|
-
try {
|
|
587
|
+
try {
|
|
588
|
+
this.container.removeChild(this.videoElement);
|
|
589
|
+
} catch {}
|
|
549
590
|
}
|
|
550
591
|
|
|
551
592
|
this.videoElement = null;
|
|
@@ -553,24 +594,32 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
553
594
|
this.listeners.clear();
|
|
554
595
|
}
|
|
555
596
|
|
|
556
|
-
getQualities(): Array<{
|
|
597
|
+
getQualities(): Array<{
|
|
598
|
+
id: string;
|
|
599
|
+
label: string;
|
|
600
|
+
bitrate?: number;
|
|
601
|
+
width?: number;
|
|
602
|
+
height?: number;
|
|
603
|
+
isAuto?: boolean;
|
|
604
|
+
active?: boolean;
|
|
605
|
+
}> {
|
|
557
606
|
const out: any[] = [];
|
|
558
607
|
const v = this.videoElement;
|
|
559
608
|
if (!this.dashPlayer || !v) return out;
|
|
560
609
|
try {
|
|
561
610
|
// dashjs v5: getRepresentationsByType returns Representation[] (bandwidth instead of bitrate)
|
|
562
|
-
const representations = this.dashPlayer.getRepresentationsByType?.(
|
|
611
|
+
const representations = this.dashPlayer.getRepresentationsByType?.("video") || [];
|
|
563
612
|
const settings = this.dashPlayer.getSettings?.();
|
|
564
613
|
const isAutoEnabled = settings?.streaming?.abr?.autoSwitchBitrate?.video !== false;
|
|
565
614
|
|
|
566
|
-
out.push({ id:
|
|
615
|
+
out.push({ id: "auto", label: "Auto", isAuto: true, active: isAutoEnabled });
|
|
567
616
|
representations.forEach((rep: any, i: number) => {
|
|
568
617
|
out.push({
|
|
569
618
|
id: String(i),
|
|
570
619
|
label: rep.height ? `${rep.height}p` : `${Math.round((rep.bandwidth || 0) / 1000)}kbps`,
|
|
571
|
-
bitrate: rep.bandwidth,
|
|
620
|
+
bitrate: rep.bandwidth, // v5 uses 'bandwidth'
|
|
572
621
|
width: rep.width,
|
|
573
|
-
height: rep.height
|
|
622
|
+
height: rep.height,
|
|
574
623
|
});
|
|
575
624
|
});
|
|
576
625
|
} catch {}
|
|
@@ -579,15 +628,21 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
579
628
|
|
|
580
629
|
selectQuality(id: string): void {
|
|
581
630
|
if (!this.dashPlayer) return;
|
|
582
|
-
if (id ===
|
|
583
|
-
this.dashPlayer.updateSettings({
|
|
631
|
+
if (id === "auto") {
|
|
632
|
+
this.dashPlayer.updateSettings({
|
|
633
|
+
streaming: { abr: { autoSwitchBitrate: { video: true } } },
|
|
634
|
+
});
|
|
584
635
|
return;
|
|
585
636
|
}
|
|
586
637
|
const idx = parseInt(id, 10);
|
|
587
638
|
if (!isNaN(idx)) {
|
|
588
|
-
this.dashPlayer.updateSettings({
|
|
639
|
+
this.dashPlayer.updateSettings({
|
|
640
|
+
streaming: { abr: { autoSwitchBitrate: { video: false } } },
|
|
641
|
+
});
|
|
589
642
|
// dashjs v5: setRepresentationForTypeByIndex instead of setQualityFor
|
|
590
|
-
try {
|
|
643
|
+
try {
|
|
644
|
+
this.dashPlayer.setRepresentationForTypeByIndex?.("video", idx);
|
|
645
|
+
} catch {}
|
|
591
646
|
}
|
|
592
647
|
}
|
|
593
648
|
|
|
@@ -600,7 +655,12 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
600
655
|
const textTracks = (v.textTracks || []) as any;
|
|
601
656
|
for (let i = 0; i < textTracks.length; i++) {
|
|
602
657
|
const tt = textTracks[i];
|
|
603
|
-
out.push({
|
|
658
|
+
out.push({
|
|
659
|
+
id: String(i),
|
|
660
|
+
label: tt.label || `CC ${i + 1}`,
|
|
661
|
+
lang: (tt as any).language,
|
|
662
|
+
active: tt.mode === "showing",
|
|
663
|
+
});
|
|
604
664
|
}
|
|
605
665
|
} catch {}
|
|
606
666
|
return out;
|
|
@@ -618,7 +678,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
618
678
|
|
|
619
679
|
// Try dash.js API first (reference dashjs.js:193-197)
|
|
620
680
|
try {
|
|
621
|
-
const dashTracks = this.dashPlayer.getTracksFor(
|
|
681
|
+
const dashTracks = this.dashPlayer.getTracksFor("text");
|
|
622
682
|
if (dashTracks && dashTracks.length > 0) {
|
|
623
683
|
const idx = id === null ? -1 : parseInt(id, 10);
|
|
624
684
|
if (idx >= 0 && idx < dashTracks.length) {
|
|
@@ -636,7 +696,8 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
636
696
|
const list = v.textTracks as TextTrackList;
|
|
637
697
|
for (let i = 0; i < list.length; i++) {
|
|
638
698
|
const tt = list[i];
|
|
639
|
-
if (id !== null && String(i) === id) tt.mode =
|
|
699
|
+
if (id !== null && String(i) === id) tt.mode = "showing";
|
|
700
|
+
else tt.mode = "disabled";
|
|
640
701
|
}
|
|
641
702
|
}
|
|
642
703
|
}
|