@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.
Files changed (59) hide show
  1. package/README.md +11 -9
  2. package/dist/player.css +182 -42
  3. package/package.json +1 -1
  4. package/src/core/ABRController.ts +38 -36
  5. package/src/core/CodecUtils.ts +49 -46
  6. package/src/core/Disposable.ts +4 -4
  7. package/src/core/EventEmitter.ts +1 -1
  8. package/src/core/GatewayClient.ts +41 -39
  9. package/src/core/InteractionController.ts +89 -82
  10. package/src/core/LiveDurationProxy.ts +14 -15
  11. package/src/core/MetaTrackManager.ts +73 -65
  12. package/src/core/MistReporter.ts +72 -45
  13. package/src/core/MistSignaling.ts +59 -56
  14. package/src/core/PlayerController.ts +527 -384
  15. package/src/core/PlayerInterface.ts +83 -59
  16. package/src/core/PlayerManager.ts +79 -133
  17. package/src/core/PlayerRegistry.ts +59 -42
  18. package/src/core/QualityMonitor.ts +38 -31
  19. package/src/core/ScreenWakeLockManager.ts +8 -9
  20. package/src/core/SeekingUtils.ts +31 -22
  21. package/src/core/StreamStateClient.ts +74 -68
  22. package/src/core/SubtitleManager.ts +24 -22
  23. package/src/core/TelemetryReporter.ts +34 -31
  24. package/src/core/TimeFormat.ts +13 -17
  25. package/src/core/TimerManager.ts +24 -8
  26. package/src/core/UrlUtils.ts +20 -17
  27. package/src/core/detector.ts +44 -44
  28. package/src/core/index.ts +57 -48
  29. package/src/core/scorer.ts +136 -141
  30. package/src/core/selector.ts +2 -6
  31. package/src/global.d.ts +1 -1
  32. package/src/index.ts +46 -35
  33. package/src/players/DashJsPlayer.ts +164 -115
  34. package/src/players/HlsJsPlayer.ts +132 -78
  35. package/src/players/MewsWsPlayer/SourceBufferManager.ts +41 -36
  36. package/src/players/MewsWsPlayer/WebSocketManager.ts +9 -9
  37. package/src/players/MewsWsPlayer/index.ts +192 -152
  38. package/src/players/MewsWsPlayer/types.ts +21 -21
  39. package/src/players/MistPlayer.ts +45 -26
  40. package/src/players/MistWebRTCPlayer/index.ts +175 -129
  41. package/src/players/NativePlayer.ts +203 -143
  42. package/src/players/VideoJsPlayer.ts +170 -118
  43. package/src/players/WebCodecsPlayer/JitterBuffer.ts +6 -7
  44. package/src/players/WebCodecsPlayer/LatencyProfiles.ts +43 -43
  45. package/src/players/WebCodecsPlayer/RawChunkParser.ts +10 -10
  46. package/src/players/WebCodecsPlayer/SyncController.ts +45 -53
  47. package/src/players/WebCodecsPlayer/WebSocketController.ts +66 -68
  48. package/src/players/WebCodecsPlayer/index.ts +263 -221
  49. package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +12 -17
  50. package/src/players/WebCodecsPlayer/types.ts +56 -56
  51. package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +238 -182
  52. package/src/players/WebCodecsPlayer/worker/types.ts +31 -31
  53. package/src/players/index.ts +8 -8
  54. package/src/styles/animations.css +2 -1
  55. package/src/styles/player.css +182 -42
  56. package/src/styles/tailwind.css +473 -159
  57. package/src/types.ts +43 -43
  58. package/src/vanilla/FrameWorksPlayer.ts +29 -14
  59. package/src/vanilla/index.ts +4 -4
@@ -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,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('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
@@ -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, // Reduced from 16 (aggressive!)
306
- bufferTimeAtTopQuality: 8, // Reduced from 30
324
+ stableBufferTime: 4, // Reduced from 16 (aggressive!)
325
+ bufferTimeAtTopQuality: 8, // Reduced from 30
307
326
  bufferTimeAtTopQualityLongForm: 15, // Reduced from 60
308
- bufferToKeep: 10, // Reduced from 30
309
- bufferPruningInterval: 10, // Reduced from 30
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 }, // 5Mbps initial (was -1)
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, // Seek to live if drift > 1.5s
347
+ maxDrift: 1.5, // Seek to live if drift > 1.5s
329
348
  playbackRate: {
330
- max: 0.15, // Speed up by max 15%
331
- min: -0.15, // Slow down by max 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, // Min buffer before catchup
334
- mode: 'liveCatchupModeDefault',
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, // Abandon after 2 timeout attempts
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, // 5 seconds instead of default 10
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, // Reduced from 4 (2s behind live edge)
390
+ liveDelay: 2, // Reduced from 4 (2s behind live edge)
372
391
  liveDelayFragmentCount: null,
373
- useSuggestedPresentationDelay: false, // Ignore manifest suggestions
392
+ useSuggestedPresentationDelay: false, // Ignore manifest suggestions
374
393
  },
375
394
  },
376
395
  debug: {
377
- logLevel: 4, // Always debug for now to see what's happening
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('fragmentLoadingStarted', (e: any) => {
383
- 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());
384
403
  });
385
- this.dashPlayer.on('fragmentLoadingCompleted', (e: any) => {
386
- 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());
387
406
  });
388
- this.dashPlayer.on('fragmentLoadingAbandoned', (e: any) => {
389
- 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);
390
409
  });
391
- this.dashPlayer.on('fragmentLoadingFailed', (e: any) => {
392
- 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);
393
412
  });
394
413
 
395
414
  // dashjs v5: Initialize with URL
396
- console.debug('[DashJS v5] Initializing with URL:', source.url);
415
+ console.debug("[DashJS v5] Initializing with URL:", source.url);
397
416
  this.dashPlayer.initialize(video, source.url, options.autoplay ?? false);
398
- console.debug('[DashJS v5] Initialize called');
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<{ 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
+ }>;
403
426
  if (Array.isArray(subs)) {
404
427
  subs.forEach((s, idx) => {
405
- const track = document.createElement('track');
406
- track.kind = 'subtitles';
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('error', error.message || String(error));
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
- type: 'dash';
430
- currentQuality: number;
431
- bufferLevel: number;
432
- bitrateInfoList: Array<{ bitrate: number; width: number; height: number }>;
433
- currentBitrate: number;
434
- playbackRate: number;
435
- } | 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
+ > {
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?.('video');
465
+ const currentRep = this.dashPlayer.getCurrentRepresentationForType?.("video");
441
466
  // dashjs v5: getRepresentationsByType returns Representation[] (bandwidth instead of bitrate)
442
- const representations = this.dashPlayer.getRepresentationsByType?.('video') || [];
443
- const bufferLevel = this.dashPlayer.getBufferLength('video') || 0;
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 ? representations.findIndex((r: any) => r.id === currentRep.id) : 0;
471
+ const currentIndex = currentRep
472
+ ? representations.findIndex((r: any) => r.id === currentRep.id)
473
+ : 0;
447
474
 
448
475
  return {
449
- type: 'dash',
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, // v5 uses 'bandwidth' not 'bitrate'
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 === 'function') {
506
- console.debug('[DashJS] jumpToLive using seekToLive()');
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('[DashJS] jumpToLive using seekable.end:', liveEdge);
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 === 'function') {
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('Error destroying DASH.js:', e);
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 { this.container.removeChild(this.videoElement); } catch {}
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<{ 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
+ }> {
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?.('video') || [];
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: 'auto', label: 'Auto', isAuto: true, active: isAutoEnabled });
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, // v5 uses '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 === 'auto') {
595
- this.dashPlayer.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: true } } } });
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({ streaming: { abr: { autoSwitchBitrate: { video: false } } } });
639
+ this.dashPlayer.updateSettings({
640
+ streaming: { abr: { autoSwitchBitrate: { video: false } } },
641
+ });
601
642
  // dashjs v5: setRepresentationForTypeByIndex instead of setQualityFor
602
- try { this.dashPlayer.setRepresentationForTypeByIndex?.('video', idx); } catch {}
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({ 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
+ });
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('text');
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 = 'showing'; else tt.mode = 'disabled';
699
+ if (id !== null && String(i) === id) tt.mode = "showing";
700
+ else tt.mode = "disabled";
652
701
  }
653
702
  }
654
703
  }