@livepeer-frameworks/player-core 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/dist/player.css +182 -42
- package/package.json +1 -1
- package/src/core/ABRController.ts +38 -36
- package/src/core/CodecUtils.ts +49 -46
- package/src/core/Disposable.ts +4 -4
- package/src/core/EventEmitter.ts +1 -1
- package/src/core/GatewayClient.ts +41 -39
- package/src/core/InteractionController.ts +89 -82
- package/src/core/LiveDurationProxy.ts +14 -15
- package/src/core/MetaTrackManager.ts +73 -65
- package/src/core/MistReporter.ts +72 -45
- package/src/core/MistSignaling.ts +59 -56
- package/src/core/PlayerController.ts +527 -384
- package/src/core/PlayerInterface.ts +83 -59
- package/src/core/PlayerManager.ts +79 -133
- package/src/core/PlayerRegistry.ts +59 -42
- package/src/core/QualityMonitor.ts +38 -31
- package/src/core/ScreenWakeLockManager.ts +8 -9
- package/src/core/SeekingUtils.ts +31 -22
- package/src/core/StreamStateClient.ts +74 -68
- package/src/core/SubtitleManager.ts +24 -22
- package/src/core/TelemetryReporter.ts +34 -31
- package/src/core/TimeFormat.ts +13 -17
- package/src/core/TimerManager.ts +24 -8
- package/src/core/UrlUtils.ts +20 -17
- package/src/core/detector.ts +44 -44
- package/src/core/index.ts +57 -48
- package/src/core/scorer.ts +136 -141
- package/src/core/selector.ts +2 -6
- package/src/global.d.ts +1 -1
- package/src/index.ts +46 -35
- package/src/players/DashJsPlayer.ts +164 -115
- package/src/players/HlsJsPlayer.ts +132 -78
- package/src/players/MewsWsPlayer/SourceBufferManager.ts +41 -36
- package/src/players/MewsWsPlayer/WebSocketManager.ts +9 -9
- package/src/players/MewsWsPlayer/index.ts +192 -152
- package/src/players/MewsWsPlayer/types.ts +21 -21
- package/src/players/MistPlayer.ts +45 -26
- package/src/players/MistWebRTCPlayer/index.ts +175 -129
- package/src/players/NativePlayer.ts +203 -143
- package/src/players/VideoJsPlayer.ts +170 -118
- package/src/players/WebCodecsPlayer/JitterBuffer.ts +6 -7
- package/src/players/WebCodecsPlayer/LatencyProfiles.ts +43 -43
- package/src/players/WebCodecsPlayer/RawChunkParser.ts +10 -10
- package/src/players/WebCodecsPlayer/SyncController.ts +45 -53
- package/src/players/WebCodecsPlayer/WebSocketController.ts +66 -68
- package/src/players/WebCodecsPlayer/index.ts +263 -221
- package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +12 -17
- package/src/players/WebCodecsPlayer/types.ts +56 -56
- package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +238 -182
- package/src/players/WebCodecsPlayer/worker/types.ts +31 -31
- package/src/players/index.ts +8 -8
- package/src/styles/animations.css +2 -1
- package/src/styles/player.css +182 -42
- package/src/styles/tailwind.css +473 -159
- package/src/types.ts +43 -43
- package/src/vanilla/FrameWorksPlayer.ts +29 -14
- package/src/vanilla/index.ts +4 -4
|
@@ -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,26 +292,26 @@ 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
|
|
@@ -302,11 +321,11 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
302
321
|
// AGGRESSIVE: Minimal buffers for fastest startup
|
|
303
322
|
buffer: {
|
|
304
323
|
fastSwitchEnabled: true,
|
|
305
|
-
stableBufferTime: 4,
|
|
306
|
-
bufferTimeAtTopQuality: 8,
|
|
324
|
+
stableBufferTime: 4, // Reduced from 16 (aggressive!)
|
|
325
|
+
bufferTimeAtTopQuality: 8, // Reduced from 30
|
|
307
326
|
bufferTimeAtTopQualityLongForm: 15, // Reduced from 60
|
|
308
|
-
bufferToKeep: 10,
|
|
309
|
-
bufferPruningInterval: 10,
|
|
327
|
+
bufferToKeep: 10, // Reduced from 30
|
|
328
|
+
bufferPruningInterval: 10, // Reduced from 30
|
|
310
329
|
},
|
|
311
330
|
// Gaps/stall handling
|
|
312
331
|
gaps: {
|
|
@@ -320,18 +339,18 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
320
339
|
autoSwitchBitrate: { video: true, audio: true },
|
|
321
340
|
limitBitrateByPortal: false,
|
|
322
341
|
useDefaultABRRules: true,
|
|
323
|
-
initialBitrate: { video: 5_000_000, audio: 128_000 },
|
|
342
|
+
initialBitrate: { video: 5_000_000, audio: 128_000 }, // 5Mbps initial (was -1)
|
|
324
343
|
},
|
|
325
344
|
// LIVE CATCHUP - critical for maintaining live edge (was missing!)
|
|
326
345
|
liveCatchup: {
|
|
327
346
|
enabled: true,
|
|
328
|
-
maxDrift: 1.5,
|
|
347
|
+
maxDrift: 1.5, // Seek to live if drift > 1.5s
|
|
329
348
|
playbackRate: {
|
|
330
|
-
max: 0.15,
|
|
331
|
-
min: -0.15,
|
|
349
|
+
max: 0.15, // Speed up by max 15%
|
|
350
|
+
min: -0.15, // Slow down by max 15%
|
|
332
351
|
},
|
|
333
|
-
playbackBufferMin: 0.3,
|
|
334
|
-
mode:
|
|
352
|
+
playbackBufferMin: 0.3, // Min buffer before catchup
|
|
353
|
+
mode: "liveCatchupModeDefault",
|
|
335
354
|
},
|
|
336
355
|
// Retry settings - more aggressive
|
|
337
356
|
retryAttempts: {
|
|
@@ -355,7 +374,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
355
374
|
// Timeout settings - faster abandonment of slow segments
|
|
356
375
|
timeoutAttempts: {
|
|
357
376
|
MPD: 2,
|
|
358
|
-
MediaSegment: 2,
|
|
377
|
+
MediaSegment: 2, // Abandon after 2 timeout attempts
|
|
359
378
|
InitializationSegment: 2,
|
|
360
379
|
BitstreamSwitchingSegment: 2,
|
|
361
380
|
IndexSegment: 2,
|
|
@@ -363,47 +382,51 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
363
382
|
other: 1,
|
|
364
383
|
},
|
|
365
384
|
// Abandon slow segment downloads more quickly
|
|
366
|
-
abandonLoadTimeout: 5000,
|
|
385
|
+
abandonLoadTimeout: 5000, // 5 seconds instead of default 10
|
|
367
386
|
xhrWithCredentials: false,
|
|
368
387
|
text: { defaultEnabled: false },
|
|
369
388
|
// AGGRESSIVE: Tighter live delay
|
|
370
389
|
delay: {
|
|
371
|
-
liveDelay: 2,
|
|
390
|
+
liveDelay: 2, // Reduced from 4 (2s behind live edge)
|
|
372
391
|
liveDelayFragmentCount: null,
|
|
373
|
-
useSuggestedPresentationDelay: false,
|
|
392
|
+
useSuggestedPresentationDelay: false, // Ignore manifest suggestions
|
|
374
393
|
},
|
|
375
394
|
},
|
|
376
395
|
debug: {
|
|
377
|
-
logLevel: 4,
|
|
396
|
+
logLevel: 4, // Always debug for now to see what's happening
|
|
378
397
|
},
|
|
379
398
|
});
|
|
380
399
|
|
|
381
400
|
// Add fragment loading event listeners to debug the pending issue
|
|
382
|
-
this.dashPlayer.on(
|
|
383
|
-
console.debug(
|
|
401
|
+
this.dashPlayer.on("fragmentLoadingStarted", (e: any) => {
|
|
402
|
+
console.debug("[DashJS] Fragment loading started:", e.request?.url?.split("/").pop());
|
|
384
403
|
});
|
|
385
|
-
this.dashPlayer.on(
|
|
386
|
-
console.debug(
|
|
404
|
+
this.dashPlayer.on("fragmentLoadingCompleted", (e: any) => {
|
|
405
|
+
console.debug("[DashJS] Fragment loading completed:", e.request?.url?.split("/").pop());
|
|
387
406
|
});
|
|
388
|
-
this.dashPlayer.on(
|
|
389
|
-
console.warn(
|
|
407
|
+
this.dashPlayer.on("fragmentLoadingAbandoned", (e: any) => {
|
|
408
|
+
console.warn("[DashJS] Fragment loading ABANDONED:", e.request?.url?.split("/").pop(), e);
|
|
390
409
|
});
|
|
391
|
-
this.dashPlayer.on(
|
|
392
|
-
console.error(
|
|
410
|
+
this.dashPlayer.on("fragmentLoadingFailed", (e: any) => {
|
|
411
|
+
console.error("[DashJS] Fragment loading FAILED:", e.request?.url?.split("/").pop(), e);
|
|
393
412
|
});
|
|
394
413
|
|
|
395
414
|
// dashjs v5: Initialize with URL
|
|
396
|
-
console.debug(
|
|
415
|
+
console.debug("[DashJS v5] Initializing with URL:", source.url);
|
|
397
416
|
this.dashPlayer.initialize(video, source.url, options.autoplay ?? false);
|
|
398
|
-
console.debug(
|
|
417
|
+
console.debug("[DashJS v5] Initialize called");
|
|
399
418
|
|
|
400
419
|
// Optional subtitle tracks helper from source extras (external tracks)
|
|
401
420
|
try {
|
|
402
|
-
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
|
+
}>;
|
|
403
426
|
if (Array.isArray(subs)) {
|
|
404
427
|
subs.forEach((s, idx) => {
|
|
405
|
-
const track = document.createElement(
|
|
406
|
-
track.kind =
|
|
428
|
+
const track = document.createElement("track");
|
|
429
|
+
track.kind = "subtitles";
|
|
407
430
|
track.label = s.label;
|
|
408
431
|
track.srclang = s.lang;
|
|
409
432
|
track.src = s.src;
|
|
@@ -414,9 +437,8 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
414
437
|
} catch {}
|
|
415
438
|
|
|
416
439
|
return video;
|
|
417
|
-
|
|
418
440
|
} catch (error: any) {
|
|
419
|
-
this.emit(
|
|
441
|
+
this.emit("error", error.message || String(error));
|
|
420
442
|
throw error;
|
|
421
443
|
}
|
|
422
444
|
}
|
|
@@ -425,32 +447,37 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
425
447
|
* Get DASH.js-specific stats for ABR and playback monitoring
|
|
426
448
|
* Updated for dashjs v5 API
|
|
427
449
|
*/
|
|
428
|
-
async getStats(): Promise<
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
+
> {
|
|
436
461
|
if (!this.dashPlayer || !this.videoElement) return undefined;
|
|
437
462
|
|
|
438
463
|
try {
|
|
439
464
|
// dashjs v5: getCurrentRepresentationForType returns Representation object
|
|
440
|
-
const currentRep = this.dashPlayer.getCurrentRepresentationForType?.(
|
|
465
|
+
const currentRep = this.dashPlayer.getCurrentRepresentationForType?.("video");
|
|
441
466
|
// dashjs v5: getRepresentationsByType returns Representation[] (bandwidth instead of bitrate)
|
|
442
|
-
const representations = this.dashPlayer.getRepresentationsByType?.(
|
|
443
|
-
const bufferLevel = this.dashPlayer.getBufferLength(
|
|
467
|
+
const representations = this.dashPlayer.getRepresentationsByType?.("video") || [];
|
|
468
|
+
const bufferLevel = this.dashPlayer.getBufferLength("video") || 0;
|
|
444
469
|
|
|
445
470
|
// Find current quality index
|
|
446
|
-
const currentIndex = currentRep
|
|
471
|
+
const currentIndex = currentRep
|
|
472
|
+
? representations.findIndex((r: any) => r.id === currentRep.id)
|
|
473
|
+
: 0;
|
|
447
474
|
|
|
448
475
|
return {
|
|
449
|
-
type:
|
|
476
|
+
type: "dash",
|
|
450
477
|
currentQuality: currentIndex >= 0 ? currentIndex : 0,
|
|
451
478
|
bufferLevel,
|
|
452
479
|
bitrateInfoList: representations.map((r: any) => ({
|
|
453
|
-
bitrate: r.bandwidth || 0,
|
|
480
|
+
bitrate: r.bandwidth || 0, // v5 uses 'bandwidth' not 'bitrate'
|
|
454
481
|
width: r.width || 0,
|
|
455
482
|
height: r.height || 0,
|
|
456
483
|
})),
|
|
@@ -502,8 +529,8 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
502
529
|
if (!video || !this.isLiveStream()) return;
|
|
503
530
|
|
|
504
531
|
// DASH.js has a seekToLive method for live streams
|
|
505
|
-
if (this.dashPlayer && typeof this.dashPlayer.seekToLive ===
|
|
506
|
-
console.debug(
|
|
532
|
+
if (this.dashPlayer && typeof this.dashPlayer.seekToLive === "function") {
|
|
533
|
+
console.debug("[DashJS] jumpToLive using seekToLive()");
|
|
507
534
|
this.dashPlayer.seekToLive();
|
|
508
535
|
return;
|
|
509
536
|
}
|
|
@@ -512,7 +539,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
512
539
|
if (video.seekable && video.seekable.length > 0) {
|
|
513
540
|
const liveEdge = video.seekable.end(video.seekable.length - 1);
|
|
514
541
|
if (isFinite(liveEdge) && liveEdge > 0) {
|
|
515
|
-
console.debug(
|
|
542
|
+
console.debug("[DashJS] jumpToLive using seekable.end:", liveEdge);
|
|
516
543
|
video.currentTime = liveEdge;
|
|
517
544
|
}
|
|
518
545
|
}
|
|
@@ -526,7 +553,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
526
553
|
if (!video || !this.isLiveStream()) return 0;
|
|
527
554
|
|
|
528
555
|
// DASH.js provides live delay metrics
|
|
529
|
-
if (this.dashPlayer && typeof this.dashPlayer.getCurrentLiveLatency ===
|
|
556
|
+
if (this.dashPlayer && typeof this.dashPlayer.getCurrentLiveLatency === "function") {
|
|
530
557
|
return this.dashPlayer.getCurrentLiveLatency() * 1000;
|
|
531
558
|
}
|
|
532
559
|
|
|
@@ -551,13 +578,15 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
551
578
|
try {
|
|
552
579
|
this.dashPlayer.reset();
|
|
553
580
|
} catch (e) {
|
|
554
|
-
console.warn(
|
|
581
|
+
console.warn("Error destroying DASH.js:", e);
|
|
555
582
|
}
|
|
556
583
|
this.dashPlayer = null;
|
|
557
584
|
}
|
|
558
585
|
|
|
559
586
|
if (this.videoElement && this.container) {
|
|
560
|
-
try {
|
|
587
|
+
try {
|
|
588
|
+
this.container.removeChild(this.videoElement);
|
|
589
|
+
} catch {}
|
|
561
590
|
}
|
|
562
591
|
|
|
563
592
|
this.videoElement = null;
|
|
@@ -565,24 +594,32 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
565
594
|
this.listeners.clear();
|
|
566
595
|
}
|
|
567
596
|
|
|
568
|
-
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
|
+
}> {
|
|
569
606
|
const out: any[] = [];
|
|
570
607
|
const v = this.videoElement;
|
|
571
608
|
if (!this.dashPlayer || !v) return out;
|
|
572
609
|
try {
|
|
573
610
|
// dashjs v5: getRepresentationsByType returns Representation[] (bandwidth instead of bitrate)
|
|
574
|
-
const representations = this.dashPlayer.getRepresentationsByType?.(
|
|
611
|
+
const representations = this.dashPlayer.getRepresentationsByType?.("video") || [];
|
|
575
612
|
const settings = this.dashPlayer.getSettings?.();
|
|
576
613
|
const isAutoEnabled = settings?.streaming?.abr?.autoSwitchBitrate?.video !== false;
|
|
577
614
|
|
|
578
|
-
out.push({ id:
|
|
615
|
+
out.push({ id: "auto", label: "Auto", isAuto: true, active: isAutoEnabled });
|
|
579
616
|
representations.forEach((rep: any, i: number) => {
|
|
580
617
|
out.push({
|
|
581
618
|
id: String(i),
|
|
582
619
|
label: rep.height ? `${rep.height}p` : `${Math.round((rep.bandwidth || 0) / 1000)}kbps`,
|
|
583
|
-
bitrate: rep.bandwidth,
|
|
620
|
+
bitrate: rep.bandwidth, // v5 uses 'bandwidth'
|
|
584
621
|
width: rep.width,
|
|
585
|
-
height: rep.height
|
|
622
|
+
height: rep.height,
|
|
586
623
|
});
|
|
587
624
|
});
|
|
588
625
|
} catch {}
|
|
@@ -591,15 +628,21 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
591
628
|
|
|
592
629
|
selectQuality(id: string): void {
|
|
593
630
|
if (!this.dashPlayer) return;
|
|
594
|
-
if (id ===
|
|
595
|
-
this.dashPlayer.updateSettings({
|
|
631
|
+
if (id === "auto") {
|
|
632
|
+
this.dashPlayer.updateSettings({
|
|
633
|
+
streaming: { abr: { autoSwitchBitrate: { video: true } } },
|
|
634
|
+
});
|
|
596
635
|
return;
|
|
597
636
|
}
|
|
598
637
|
const idx = parseInt(id, 10);
|
|
599
638
|
if (!isNaN(idx)) {
|
|
600
|
-
this.dashPlayer.updateSettings({
|
|
639
|
+
this.dashPlayer.updateSettings({
|
|
640
|
+
streaming: { abr: { autoSwitchBitrate: { video: false } } },
|
|
641
|
+
});
|
|
601
642
|
// dashjs v5: setRepresentationForTypeByIndex instead of setQualityFor
|
|
602
|
-
try {
|
|
643
|
+
try {
|
|
644
|
+
this.dashPlayer.setRepresentationForTypeByIndex?.("video", idx);
|
|
645
|
+
} catch {}
|
|
603
646
|
}
|
|
604
647
|
}
|
|
605
648
|
|
|
@@ -612,7 +655,12 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
612
655
|
const textTracks = (v.textTracks || []) as any;
|
|
613
656
|
for (let i = 0; i < textTracks.length; i++) {
|
|
614
657
|
const tt = textTracks[i];
|
|
615
|
-
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
|
+
});
|
|
616
664
|
}
|
|
617
665
|
} catch {}
|
|
618
666
|
return out;
|
|
@@ -630,7 +678,7 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
630
678
|
|
|
631
679
|
// Try dash.js API first (reference dashjs.js:193-197)
|
|
632
680
|
try {
|
|
633
|
-
const dashTracks = this.dashPlayer.getTracksFor(
|
|
681
|
+
const dashTracks = this.dashPlayer.getTracksFor("text");
|
|
634
682
|
if (dashTracks && dashTracks.length > 0) {
|
|
635
683
|
const idx = id === null ? -1 : parseInt(id, 10);
|
|
636
684
|
if (idx >= 0 && idx < dashTracks.length) {
|
|
@@ -648,7 +696,8 @@ export class DashJsPlayerImpl extends BasePlayer {
|
|
|
648
696
|
const list = v.textTracks as TextTrackList;
|
|
649
697
|
for (let i = 0; i < list.length; i++) {
|
|
650
698
|
const tt = list[i];
|
|
651
|
-
if (id !== null && String(i) === id) tt.mode =
|
|
699
|
+
if (id !== null && String(i) === id) tt.mode = "showing";
|
|
700
|
+
else tt.mode = "disabled";
|
|
652
701
|
}
|
|
653
702
|
}
|
|
654
703
|
}
|