@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.
Files changed (82) hide show
  1. package/README.md +21 -6
  2. package/dist/cjs/index.js +792 -146
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/esm/index.js +792 -146
  5. package/dist/esm/index.js.map +1 -1
  6. package/dist/player.css +185 -373
  7. package/dist/types/core/GatewayClient.d.ts +3 -4
  8. package/dist/types/core/InteractionController.d.ts +12 -0
  9. package/dist/types/core/MetaTrackManager.d.ts +1 -1
  10. package/dist/types/core/PlayerController.d.ts +18 -2
  11. package/dist/types/core/PlayerInterface.d.ts +10 -0
  12. package/dist/types/core/SeekingUtils.d.ts +3 -1
  13. package/dist/types/core/StreamStateClient.d.ts +1 -1
  14. package/dist/types/players/HlsJsPlayer.d.ts +8 -0
  15. package/dist/types/players/MewsWsPlayer/index.d.ts +1 -1
  16. package/dist/types/players/VideoJsPlayer.d.ts +12 -4
  17. package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +1 -1
  18. package/dist/types/players/WebCodecsPlayer/index.d.ts +11 -0
  19. package/dist/types/players/WebCodecsPlayer/types.d.ts +25 -3
  20. package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +20 -2
  21. package/dist/types/types.d.ts +32 -1
  22. package/dist/types/vanilla/FrameWorksPlayer.d.ts +5 -5
  23. package/dist/types/vanilla/index.d.ts +3 -3
  24. package/dist/workers/decoder.worker.js +183 -6
  25. package/dist/workers/decoder.worker.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/core/ABRController.ts +38 -36
  28. package/src/core/CodecUtils.ts +50 -47
  29. package/src/core/Disposable.ts +4 -4
  30. package/src/core/EventEmitter.ts +1 -1
  31. package/src/core/GatewayClient.ts +48 -48
  32. package/src/core/InteractionController.ts +89 -82
  33. package/src/core/LiveDurationProxy.ts +14 -16
  34. package/src/core/MetaTrackManager.ts +74 -66
  35. package/src/core/MistReporter.ts +72 -45
  36. package/src/core/MistSignaling.ts +59 -56
  37. package/src/core/PlayerController.ts +724 -375
  38. package/src/core/PlayerInterface.ts +89 -59
  39. package/src/core/PlayerManager.ts +118 -123
  40. package/src/core/PlayerRegistry.ts +59 -42
  41. package/src/core/QualityMonitor.ts +38 -31
  42. package/src/core/ScreenWakeLockManager.ts +8 -9
  43. package/src/core/SeekingUtils.ts +31 -22
  44. package/src/core/StreamStateClient.ts +75 -69
  45. package/src/core/SubtitleManager.ts +25 -23
  46. package/src/core/TelemetryReporter.ts +34 -31
  47. package/src/core/TimeFormat.ts +13 -17
  48. package/src/core/TimerManager.ts +25 -9
  49. package/src/core/UrlUtils.ts +20 -17
  50. package/src/core/detector.ts +44 -44
  51. package/src/core/index.ts +57 -48
  52. package/src/core/scorer.ts +137 -138
  53. package/src/core/selector.ts +2 -6
  54. package/src/global.d.ts +1 -1
  55. package/src/index.ts +46 -35
  56. package/src/players/DashJsPlayer.ts +175 -114
  57. package/src/players/HlsJsPlayer.ts +154 -76
  58. package/src/players/MewsWsPlayer/SourceBufferManager.ts +44 -39
  59. package/src/players/MewsWsPlayer/WebSocketManager.ts +9 -10
  60. package/src/players/MewsWsPlayer/index.ts +196 -154
  61. package/src/players/MewsWsPlayer/types.ts +21 -21
  62. package/src/players/MistPlayer.ts +46 -27
  63. package/src/players/MistWebRTCPlayer/index.ts +175 -129
  64. package/src/players/NativePlayer.ts +203 -143
  65. package/src/players/VideoJsPlayer.ts +200 -146
  66. package/src/players/WebCodecsPlayer/JitterBuffer.ts +6 -7
  67. package/src/players/WebCodecsPlayer/LatencyProfiles.ts +43 -43
  68. package/src/players/WebCodecsPlayer/RawChunkParser.ts +10 -10
  69. package/src/players/WebCodecsPlayer/SyncController.ts +46 -55
  70. package/src/players/WebCodecsPlayer/WebSocketController.ts +67 -69
  71. package/src/players/WebCodecsPlayer/index.ts +280 -220
  72. package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +12 -17
  73. package/src/players/WebCodecsPlayer/types.ts +81 -53
  74. package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +255 -192
  75. package/src/players/WebCodecsPlayer/worker/types.ts +33 -29
  76. package/src/players/index.ts +8 -8
  77. package/src/styles/animations.css +2 -1
  78. package/src/styles/player.css +182 -356
  79. package/src/styles/tailwind.css +473 -159
  80. package/src/types.ts +75 -33
  81. package/src/vanilla/FrameWorksPlayer.ts +34 -19
  82. package/src/vanilla/index.ts +7 -7
@@ -1,7 +1,12 @@
1
- import { BasePlayer } from '../core/PlayerInterface';
2
- import { checkProtocolMismatch, getBrowserInfo, isFileProtocol } from '../core/detector';
3
- import { translateCodec } from '../core/CodecUtils';
4
- import type { StreamSource, StreamInfo, PlayerOptions, PlayerCapability } from '../core/PlayerInterface';
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: 'live' | 'vod' | 'unknown' = 'unknown';
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(mimetype: string, source: StreamSource, streamInfo: StreamInfo): boolean | string[] {
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 === 'meta') {
58
- if (track.codec === 'subtitle') {
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 === 'html5/text/vtt') {
62
- playableTracks.push('subtitle');
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 = ['OPUS', 'Opus', 'opus', 'VORBIS', 'Vorbis'];
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 === 'audio' && DASH_INCOMPATIBLE_AUDIO.includes(track.codec)) {
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 === 'audio' ? 'audio/mp4' : 'video/mp4';
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 === 'live') return true;
118
- if (this.streamType === 'vod') return false;
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 (!('Proxy' in window)) {
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('progress', () => {
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 === 'duration' && self.isLiveStream()) {
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 === 'function') {
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
- 'METRIC_ADDED', 'METRIC_UPDATED', 'METRIC_CHANGED', 'METRICS_CHANGED',
177
- 'FRAGMENT_LOADING_STARTED', 'FRAGMENT_LOADING_COMPLETED',
178
- 'LOG', 'PLAYBACK_TIME_UPDATED', 'PLAYBACK_PROGRESS'
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('DASH event:', e.type);
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('allTextTracksAdded', () => {
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('progress', () => {
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(container: HTMLElement, source: StreamSource, options: PlayerOptions): Promise<HTMLVideoElement> {
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('fw-player-container');
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 === 'live') {
230
- this.streamType = 'live';
231
- } else if (sourceType === 'vod') {
232
- this.streamType = 'vod';
248
+ if (sourceType === "live") {
249
+ this.streamType = "live";
250
+ } else if (sourceType === "vod") {
251
+ this.streamType = "vod";
233
252
  } else {
234
- this.streamType = 'unknown';
253
+ this.streamType = "unknown";
235
254
  }
236
255
 
237
256
  // Create video element
238
- const video = document.createElement('video');
239
- video.classList.add('fw-player-video');
240
- video.setAttribute('playsinline', '');
241
- video.setAttribute('crossorigin', 'anonymous');
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 !== 'live') video.loop = true;
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('[DashJS] Importing dashjs module...');
263
- const mod = await import('dashjs');
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('[DashJS] Module imported:', dashjs);
284
+ console.debug("[DashJS] Module imported:", dashjs);
266
285
 
267
286
  this.dashPlayer = dashjs.MediaPlayer().create();
268
- console.debug('[DashJS] MediaPlayer created');
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('error', (e: any) => {
295
+ this.dashPlayer.on("error", (e: any) => {
277
296
  if (this.destroyed) return;
278
- const error = `DASH error: ${e?.event?.message || e?.message || 'unknown'}`;
279
- console.error('[DashJS] Error event:', e);
280
- this.emit('error', error);
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('manifestLoaded', (e: any) => {
285
- console.debug('[DashJS] manifestLoaded:', e);
303
+ this.dashPlayer.on("manifestLoaded", (e: any) => {
304
+ console.debug("[DashJS] manifestLoaded:", e);
286
305
  });
287
- this.dashPlayer.on('canPlay', () => {
288
- console.debug('[DashJS] canPlay event');
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('streamInitialized', () => {
311
+ this.dashPlayer.on("streamInitialized", () => {
293
312
  if (this.destroyed) return;
294
313
  const isDynamic = this.dashPlayer.isDynamic?.() ?? false;
295
- console.debug('[DashJS v5] streamInitialized - isDynamic:', isDynamic);
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
- // Buffer settings
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 - try disabling to isolate the issue
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: -1, audio: -1 }, // Let dashjs choose
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, // Abandon after 2 timeout attempts
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, // 5 seconds instead of default 10
385
+ abandonLoadTimeout: 5000, // 5 seconds instead of default 10
355
386
  xhrWithCredentials: false,
356
387
  text: { defaultEnabled: false },
357
- // Live delay settings for live streams
388
+ // AGGRESSIVE: Tighter live delay
358
389
  delay: {
359
- liveDelay: 4, // Target 4 seconds behind live edge
390
+ liveDelay: 2, // Reduced from 4 (2s behind live edge)
360
391
  liveDelayFragmentCount: null,
361
- useSuggestedPresentationDelay: true,
392
+ useSuggestedPresentationDelay: false, // Ignore manifest suggestions
362
393
  },
363
394
  },
364
395
  debug: {
365
- logLevel: 4, // Always debug for now to see what's happening
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('fragmentLoadingStarted', (e: any) => {
371
- console.debug('[DashJS] Fragment loading started:', e.request?.url?.split('/').pop());
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('fragmentLoadingCompleted', (e: any) => {
374
- console.debug('[DashJS] Fragment loading completed:', e.request?.url?.split('/').pop());
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('fragmentLoadingAbandoned', (e: any) => {
377
- console.warn('[DashJS] Fragment loading ABANDONED:', e.request?.url?.split('/').pop(), e);
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('fragmentLoadingFailed', (e: any) => {
380
- console.error('[DashJS] Fragment loading FAILED:', e.request?.url?.split('/').pop(), e);
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('[DashJS v5] Initializing with URL:', source.url);
415
+ console.debug("[DashJS v5] Initializing with URL:", source.url);
385
416
  this.dashPlayer.initialize(video, source.url, options.autoplay ?? false);
386
- console.debug('[DashJS v5] Initialize called');
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<{ label: string; lang: string; src: string }>;
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('track');
394
- track.kind = 'subtitles';
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('error', error.message || String(error));
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
- type: 'dash';
418
- currentQuality: number;
419
- bufferLevel: number;
420
- bitrateInfoList: Array<{ bitrate: number; width: number; height: number }>;
421
- currentBitrate: number;
422
- playbackRate: number;
423
- } | undefined> {
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?.('video');
465
+ const currentRep = this.dashPlayer.getCurrentRepresentationForType?.("video");
429
466
  // dashjs v5: getRepresentationsByType returns Representation[] (bandwidth instead of bitrate)
430
- const representations = this.dashPlayer.getRepresentationsByType?.('video') || [];
431
- const bufferLevel = this.dashPlayer.getBufferLength('video') || 0;
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 ? representations.findIndex((r: any) => r.id === currentRep.id) : 0;
471
+ const currentIndex = currentRep
472
+ ? representations.findIndex((r: any) => r.id === currentRep.id)
473
+ : 0;
435
474
 
436
475
  return {
437
- type: 'dash',
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, // v5 uses 'bandwidth' not 'bitrate'
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 === 'function') {
494
- console.debug('[DashJS] jumpToLive using seekToLive()');
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('[DashJS] jumpToLive using seekable.end:', liveEdge);
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 === 'function') {
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('Error destroying DASH.js:', e);
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 { this.container.removeChild(this.videoElement); } catch {}
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<{ id: string; label: string; bitrate?: number; width?: number; height?: number; isAuto?: boolean; active?: boolean }> {
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?.('video') || [];
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: 'auto', label: 'Auto', isAuto: true, active: isAutoEnabled });
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, // v5 uses '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 === 'auto') {
583
- this.dashPlayer.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: true } } } });
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({ streaming: { abr: { autoSwitchBitrate: { video: false } } } });
639
+ this.dashPlayer.updateSettings({
640
+ streaming: { abr: { autoSwitchBitrate: { video: false } } },
641
+ });
589
642
  // dashjs v5: setRepresentationForTypeByIndex instead of setQualityFor
590
- try { this.dashPlayer.setRepresentationForTypeByIndex?.('video', idx); } catch {}
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({ id: String(i), label: tt.label || `CC ${i+1}`, lang: (tt as any).language, active: tt.mode === 'showing' });
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('text');
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 = 'showing'; else tt.mode = 'disabled';
699
+ if (id !== null && String(i) === id) tt.mode = "showing";
700
+ else tt.mode = "disabled";
640
701
  }
641
702
  }
642
703
  }