@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,8 +1,14 @@
1
- import { BasePlayer } from '../core/PlayerInterface';
2
- import { checkProtocolMismatch, getBrowserInfo } from '../core/detector';
3
- import { translateCodec } from '../core/CodecUtils';
4
- import { LiveDurationProxy } from '../core/LiveDurationProxy';
5
- import type { StreamSource, StreamInfo, PlayerOptions, PlayerCapability } from '../core/PlayerInterface';
1
+ import { BasePlayer } from "../core/PlayerInterface";
2
+ import { checkProtocolMismatch, getBrowserInfo } from "../core/detector";
3
+ import { translateCodec } from "../core/CodecUtils";
4
+ import { LiveDurationProxy } from "../core/LiveDurationProxy";
5
+ import type {
6
+ StreamSource,
7
+ StreamInfo,
8
+ PlayerOptions,
9
+ PlayerCapability,
10
+ } from "../core/PlayerInterface";
11
+ import type { HlsJsConfig } from "../types";
6
12
 
7
13
  // Player implementation class
8
14
  export class HlsJsPlayerImpl extends BasePlayer {
@@ -10,7 +16,7 @@ export class HlsJsPlayerImpl extends BasePlayer {
10
16
  name: "HLS.js Player",
11
17
  shortname: "hlsjs",
12
18
  priority: 3,
13
- mimes: ["html5/application/vnd.apple.mpegurl", "html5/application/vnd.apple.mpegurl;version=7"]
19
+ mimes: ["html5/application/vnd.apple.mpegurl", "html5/application/vnd.apple.mpegurl;version=7"],
14
20
  };
15
21
 
16
22
  private hls: any = null;
@@ -23,7 +29,11 @@ export class HlsJsPlayerImpl extends BasePlayer {
23
29
  return this.capability.mimes.includes(mimetype);
24
30
  }
25
31
 
26
- isBrowserSupported(mimetype: string, source: StreamSource, streamInfo: StreamInfo): boolean | string[] {
32
+ isBrowserSupported(
33
+ mimetype: string,
34
+ source: StreamSource,
35
+ streamInfo: StreamInfo
36
+ ): boolean | string[] {
27
37
  // Check protocol mismatch
28
38
  if (checkProtocolMismatch(source.url)) {
29
39
  return false;
@@ -41,9 +51,9 @@ export class HlsJsPlayerImpl extends BasePlayer {
41
51
  // Check MediaSource support (required for HLS.js)
42
52
  if (!browser.supportsMediaSource) {
43
53
  // Fall back to native if available
44
- const testVideo = document.createElement('video');
45
- if (testVideo.canPlayType('application/vnd.apple.mpegurl')) {
46
- return ['video', 'audio'];
54
+ const testVideo = document.createElement("video");
55
+ if (testVideo.canPlayType("application/vnd.apple.mpegurl")) {
56
+ return ["video", "audio"];
47
57
  }
48
58
  return false;
49
59
  }
@@ -55,17 +65,17 @@ export class HlsJsPlayerImpl extends BasePlayer {
55
65
  // If no track info available yet, assume compatible (like upstream does)
56
66
  // Track info comes async from MistServer - don't block on it
57
67
  if (!streamInfo.meta.tracks || streamInfo.meta.tracks.length === 0) {
58
- return ['video', 'audio']; // Assume standard tracks until we know better
68
+ return ["video", "audio"]; // Assume standard tracks until we know better
59
69
  }
60
70
 
61
71
  // Group tracks by type
62
72
  for (const track of streamInfo.meta.tracks) {
63
- if (track.type === 'meta') {
64
- if (track.codec === 'subtitle') {
73
+ if (track.type === "meta") {
74
+ if (track.codec === "subtitle") {
65
75
  // Check for WebVTT subtitle support
66
76
  for (const src of streamInfo.source) {
67
- if (src.type === 'html5/text/vtt') {
68
- playableTracks.push('subtitle');
77
+ if (src.type === "html5/text/vtt") {
78
+ playableTracks.push("subtitle");
69
79
  break;
70
80
  }
71
81
  }
@@ -81,7 +91,7 @@ export class HlsJsPlayerImpl extends BasePlayer {
81
91
 
82
92
  // HLS-incompatible audio codecs (even if browser MSE supports them in fMP4)
83
93
  // HLS standard only supports: AAC, MP3, AC-3/E-AC-3
84
- const HLS_INCOMPATIBLE_AUDIO = ['OPUS', 'Opus', 'opus', 'VORBIS', 'Vorbis', 'FLAC'];
94
+ const HLS_INCOMPATIBLE_AUDIO = ["OPUS", "Opus", "opus", "VORBIS", "Vorbis", "FLAC"];
85
95
 
86
96
  // Test codec support for video/audio tracks
87
97
  for (const [trackType, tracks] of Object.entries(tracksByType)) {
@@ -89,14 +99,14 @@ export class HlsJsPlayerImpl extends BasePlayer {
89
99
 
90
100
  for (const track of tracks) {
91
101
  // Explicit HLS codec filtering - OPUS doesn't work in HLS even if MSE supports it
92
- if (trackType === 'audio' && HLS_INCOMPATIBLE_AUDIO.includes(track.codec)) {
102
+ if (trackType === "audio" && HLS_INCOMPATIBLE_AUDIO.includes(track.codec)) {
93
103
  console.debug(`[HLS.js] Codec incompatible with HLS: ${track.codec}`);
94
104
  continue;
95
105
  }
96
106
 
97
107
  const codecString = translateCodec(track);
98
108
  // Use correct container type for audio vs video tracks
99
- const container = trackType === 'audio' ? 'audio/mp4' : 'video/mp4';
109
+ const container = trackType === "audio" ? "audio/mp4" : "video/mp4";
100
110
  const mimeType = `${container};codecs="${codecString}"`;
101
111
 
102
112
  if (MediaSource.isTypeSupported && MediaSource.isTypeSupported(mimeType)) {
@@ -115,17 +125,21 @@ export class HlsJsPlayerImpl extends BasePlayer {
115
125
  return playableTracks.length > 0 ? playableTracks : false;
116
126
  }
117
127
 
118
- async initialize(container: HTMLElement, source: StreamSource, options: PlayerOptions): Promise<HTMLVideoElement> {
119
- console.log('[HLS.js] initialize() starting for', source.url.slice(0, 60) + '...');
128
+ async initialize(
129
+ container: HTMLElement,
130
+ source: StreamSource,
131
+ options: PlayerOptions
132
+ ): Promise<HTMLVideoElement> {
133
+ console.log("[HLS.js] initialize() starting for", source.url.slice(0, 60) + "...");
120
134
  this.destroyed = false;
121
135
  this.container = container;
122
- container.classList.add('fw-player-container');
136
+ container.classList.add("fw-player-container");
123
137
 
124
138
  // Create video element
125
- const video = document.createElement('video');
126
- video.classList.add('fw-player-video');
127
- video.setAttribute('playsinline', '');
128
- video.setAttribute('crossorigin', 'anonymous');
139
+ const video = document.createElement("video");
140
+ video.classList.add("fw-player-video");
141
+ video.setAttribute("playsinline", "");
142
+ video.setAttribute("crossorigin", "anonymous");
129
143
 
130
144
  // Apply options
131
145
  if (options.autoplay) video.autoplay = true;
@@ -142,19 +156,42 @@ export class HlsJsPlayerImpl extends BasePlayer {
142
156
 
143
157
  try {
144
158
  // Dynamic import of HLS.js
145
- console.log('[HLS.js] Dynamically importing hls.js module...');
146
- const mod = await import('hls.js');
159
+ console.log("[HLS.js] Dynamically importing hls.js module...");
160
+ const mod = await import("hls.js");
147
161
  const Hls = (mod as any).default || (mod as any);
148
- console.log('[HLS.js] hls.js module imported, Hls.isSupported():', Hls.isSupported?.());
162
+ console.log("[HLS.js] hls.js module imported, Hls.isSupported():", Hls.isSupported?.());
149
163
 
150
164
  if (Hls.isSupported()) {
151
- this.hls = new Hls({
165
+ // Build optimized HLS.js config with user overrides
166
+ const hlsConfig: HlsJsConfig = {
167
+ // Worker disabled for lower latency (per HLS.js maintainer recommendation)
152
168
  enableWorker: false,
169
+
170
+ // LL-HLS support
153
171
  lowLatencyMode: true,
154
- maxBufferLength: 15,
155
- maxMaxBufferLength: 60,
156
- backBufferLength: 30 // Reduced from 90 to prevent memory issues on long streams
157
- });
172
+
173
+ // AGGRESSIVE: Assume 5 Mbps initially (not 500kbps default)
174
+ // This dramatically improves startup time by selecting appropriate quality faster
175
+ abrEwmaDefaultEstimate: 5_000_000,
176
+
177
+ // AGGRESSIVE: Minimal buffers for fastest startup
178
+ maxBufferLength: 6, // Reduced from 15 (just 2 segments @ 3s)
179
+ maxMaxBufferLength: 15, // Reduced from 60
180
+ backBufferLength: Infinity, // Let browser manage (per maintainer advice)
181
+
182
+ // Stay close to live edge but not too aggressive
183
+ liveSyncDuration: 4, // Target 4 seconds behind live edge
184
+ liveMaxLatencyDuration: 8, // Max 8 seconds before seeking to live
185
+
186
+ // Faster ABR adaptation for live
187
+ abrEwmaFastLive: 2.0, // Faster than default 3.0
188
+ abrEwmaSlowLive: 6.0, // Faster than default 9.0
189
+
190
+ // Allow user overrides
191
+ ...options.hlsConfig,
192
+ };
193
+
194
+ this.hls = new Hls(hlsConfig);
158
195
 
159
196
  this.hls.attachMedia(video);
160
197
 
@@ -167,10 +204,12 @@ export class HlsJsPlayerImpl extends BasePlayer {
167
204
  if (data?.fatal) {
168
205
  if (this.failureCount < 3) {
169
206
  this.failureCount++;
170
- try { this.hls.recoverMediaError(); } catch {}
207
+ try {
208
+ this.hls.recoverMediaError();
209
+ } catch {}
171
210
  } else {
172
- const error = `HLS fatal error: ${data?.type || 'unknown'}`;
173
- this.emit('error', error);
211
+ const error = `HLS fatal error: ${data?.type || "unknown"}`;
212
+ this.emit("error", error);
174
213
  }
175
214
  }
176
215
  });
@@ -186,31 +225,34 @@ export class HlsJsPlayerImpl extends BasePlayer {
186
225
  constrainSeek: true,
187
226
  liveOffset: 0,
188
227
  });
189
- console.debug('[HLS.js] LiveDurationProxy initialized for live stream');
228
+ console.debug("[HLS.js] LiveDurationProxy initialized for live stream");
190
229
  }
191
230
 
192
231
  if (options.autoplay) {
193
- video.play().catch(e => console.warn('HLS autoplay failed:', e));
232
+ video.play().catch((e) => console.warn("HLS autoplay failed:", e));
194
233
  }
195
234
  });
196
-
197
- } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
235
+ } else if (video.canPlayType("application/vnd.apple.mpegurl")) {
198
236
  // Use native HLS support
199
237
  video.src = source.url;
200
238
  if (options.autoplay) {
201
- video.play().catch(e => console.warn('Native HLS autoplay failed:', e));
239
+ video.play().catch((e) => console.warn("Native HLS autoplay failed:", e));
202
240
  }
203
241
  } else {
204
- throw new Error('HLS not supported in this browser');
242
+ throw new Error("HLS not supported in this browser");
205
243
  }
206
244
 
207
245
  // Optional subtitle tracks helper from source extras
208
246
  try {
209
- const subs = (source as any).subtitles as Array<{ label: string; lang: string; src: string }>;
247
+ const subs = (source as any).subtitles as Array<{
248
+ label: string;
249
+ lang: string;
250
+ src: string;
251
+ }>;
210
252
  if (Array.isArray(subs)) {
211
253
  subs.forEach((s, idx) => {
212
- const track = document.createElement('track');
213
- track.kind = 'subtitles';
254
+ const track = document.createElement("track");
255
+ track.kind = "subtitles";
214
256
  track.label = s.label;
215
257
  track.srclang = s.lang;
216
258
  track.src = s.src;
@@ -220,17 +262,16 @@ export class HlsJsPlayerImpl extends BasePlayer {
220
262
  }
221
263
  } catch {}
222
264
 
223
- console.log('[HLS.js] initialize() complete, returning video element');
265
+ console.log("[HLS.js] initialize() complete, returning video element");
224
266
  return video;
225
-
226
267
  } catch (error: any) {
227
- this.emit('error', error.message || String(error));
268
+ this.emit("error", error.message || String(error));
228
269
  throw error;
229
270
  }
230
271
  }
231
272
 
232
273
  async destroy(): Promise<void> {
233
- console.debug('[HLS.js] destroy() called');
274
+ console.debug("[HLS.js] destroy() called");
234
275
  this.destroyed = true;
235
276
 
236
277
  if (this.liveDurationProxy) {
@@ -241,15 +282,17 @@ export class HlsJsPlayerImpl extends BasePlayer {
241
282
  if (this.hls) {
242
283
  try {
243
284
  this.hls.destroy();
244
- console.debug('[HLS.js] hls.destroy() completed');
285
+ console.debug("[HLS.js] hls.destroy() completed");
245
286
  } catch (e) {
246
- console.warn('[HLS.js] Error destroying:', e);
287
+ console.warn("[HLS.js] Error destroying:", e);
247
288
  }
248
289
  this.hls = null;
249
290
  }
250
291
 
251
292
  if (this.videoElement && this.container) {
252
- try { this.container.removeChild(this.videoElement); } catch {}
293
+ try {
294
+ this.container.removeChild(this.videoElement);
295
+ } catch {}
253
296
  }
254
297
 
255
298
  this.videoElement = null;
@@ -310,15 +353,19 @@ export class HlsJsPlayerImpl extends BasePlayer {
310
353
  if (!video) return;
311
354
 
312
355
  // HLS.js provides liveSyncPosition for live streams - use that first
313
- if (this.hls && typeof this.hls.liveSyncPosition === 'number' && this.hls.liveSyncPosition > 0) {
314
- console.debug('[HLS.js] jumpToLive using liveSyncPosition:', this.hls.liveSyncPosition);
356
+ if (
357
+ this.hls &&
358
+ typeof this.hls.liveSyncPosition === "number" &&
359
+ this.hls.liveSyncPosition > 0
360
+ ) {
361
+ console.debug("[HLS.js] jumpToLive using liveSyncPosition:", this.hls.liveSyncPosition);
315
362
  video.currentTime = this.hls.liveSyncPosition;
316
363
  return;
317
364
  }
318
365
 
319
366
  // Fall back to LiveDurationProxy
320
367
  if (this.liveDurationProxy && this.liveDurationProxy.isLive()) {
321
- console.debug('[HLS.js] jumpToLive using LiveDurationProxy');
368
+ console.debug("[HLS.js] jumpToLive using LiveDurationProxy");
322
369
  this.liveDurationProxy.jumpToLive();
323
370
  return;
324
371
  }
@@ -327,7 +374,7 @@ export class HlsJsPlayerImpl extends BasePlayer {
327
374
  if (video.seekable && video.seekable.length > 0) {
328
375
  const liveEdge = video.seekable.end(video.seekable.length - 1);
329
376
  if (isFinite(liveEdge) && liveEdge > 0) {
330
- console.debug('[HLS.js] jumpToLive using seekable.end:', liveEdge);
377
+ console.debug("[HLS.js] jumpToLive using seekable.end:", liveEdge);
331
378
  video.currentTime = liveEdge;
332
379
  }
333
380
  }
@@ -343,7 +390,11 @@ export class HlsJsPlayerImpl extends BasePlayer {
343
390
  const start = video.seekable.start(0);
344
391
  let end = video.seekable.end(video.seekable.length - 1);
345
392
 
346
- if (this.liveDurationProxy?.isLive() && this.hls && typeof this.hls.liveSyncPosition === 'number') {
393
+ if (
394
+ this.liveDurationProxy?.isLive() &&
395
+ this.hls &&
396
+ typeof this.hls.liveSyncPosition === "number"
397
+ ) {
347
398
  const sync = this.hls.liveSyncPosition;
348
399
  if (Number.isFinite(sync) && sync > 0) {
349
400
  end = Math.min(end, sync);
@@ -362,7 +413,7 @@ export class HlsJsPlayerImpl extends BasePlayer {
362
413
  if (!video) return 0;
363
414
 
364
415
  // HLS.js provides liveSyncPosition
365
- if (this.hls && typeof this.hls.liveSyncPosition === 'number') {
416
+ if (this.hls && typeof this.hls.liveSyncPosition === "number") {
366
417
  return Math.max(0, (this.hls.liveSyncPosition - video.currentTime) * 1000);
367
418
  }
368
419
 
@@ -377,22 +428,37 @@ export class HlsJsPlayerImpl extends BasePlayer {
377
428
  // ============================================================================
378
429
  // Quality API (Auto + levels)
379
430
  // ============================================================================
380
- getQualities(): Array<{ id: string; label: string; bitrate?: number; width?: number; height?: number; isAuto?: boolean; active?: boolean }> {
431
+ getQualities(): Array<{
432
+ id: string;
433
+ label: string;
434
+ bitrate?: number;
435
+ width?: number;
436
+ height?: number;
437
+ isAuto?: boolean;
438
+ active?: boolean;
439
+ }> {
381
440
  const qualities: any[] = [];
382
441
  const video = this.videoElement;
383
442
  if (!this.hls || !video) return qualities;
384
443
  const levels = this.hls.levels || [];
385
- const auto = { id: 'auto', label: 'Auto', isAuto: true, active: this.hls.autoLevelEnabled };
444
+ const auto = { id: "auto", label: "Auto", isAuto: true, active: this.hls.autoLevelEnabled };
386
445
  qualities.push(auto);
387
446
  levels.forEach((lvl: any, idx: number) => {
388
- qualities.push({ id: String(idx), label: lvl.height ? `${lvl.height}p` : `${Math.round((lvl.bitrate||0)/1000)}kbps`, bitrate: lvl.bitrate, width: lvl.width, height: lvl.height, active: this.hls.currentLevel === idx });
447
+ qualities.push({
448
+ id: String(idx),
449
+ label: lvl.height ? `${lvl.height}p` : `${Math.round((lvl.bitrate || 0) / 1000)}kbps`,
450
+ bitrate: lvl.bitrate,
451
+ width: lvl.width,
452
+ height: lvl.height,
453
+ active: this.hls.currentLevel === idx,
454
+ });
389
455
  });
390
456
  return qualities;
391
457
  }
392
458
 
393
459
  selectQuality(id: string): void {
394
460
  if (!this.hls) return;
395
- if (id === 'auto') {
461
+ if (id === "auto") {
396
462
  this.hls.currentLevel = -1;
397
463
  this.hls.autoLevelEnabled = true;
398
464
  return;
@@ -412,7 +478,12 @@ export class HlsJsPlayerImpl extends BasePlayer {
412
478
  const out: any[] = [];
413
479
  for (let i = 0; i < list.length; i++) {
414
480
  const tt = list[i];
415
- out.push({ id: String(i), label: tt.label || `CC ${i+1}`, lang: (tt as any).language, active: tt.mode === 'showing' });
481
+ out.push({
482
+ id: String(i),
483
+ label: tt.label || `CC ${i + 1}`,
484
+ lang: (tt as any).language,
485
+ active: tt.mode === "showing",
486
+ });
416
487
  }
417
488
  return out;
418
489
  }
@@ -423,23 +494,27 @@ export class HlsJsPlayerImpl extends BasePlayer {
423
494
  const list = v.textTracks as TextTrackList;
424
495
  for (let i = 0; i < list.length; i++) {
425
496
  const tt = list[i];
426
- if (id !== null && String(i) === id) tt.mode = 'showing'; else tt.mode = 'disabled';
497
+ if (id !== null && String(i) === id) tt.mode = "showing";
498
+ else tt.mode = "disabled";
427
499
  }
428
500
  }
429
501
 
430
502
  /**
431
503
  * Get HLS.js-specific stats for accurate bitrate and bandwidth
432
504
  */
433
- async getStats(): Promise<{
434
- type: 'hls';
435
- bandwidthEstimate: number;
436
- currentLevel: number;
437
- currentBitrate: number;
438
- loadLevel: number;
439
- levels: Array<{ bitrate: number; width: number; height: number }>;
440
- buffered: number;
441
- latency?: number;
442
- } | undefined> {
505
+ async getStats(): Promise<
506
+ | {
507
+ type: "hls";
508
+ bandwidthEstimate: number;
509
+ currentLevel: number;
510
+ currentBitrate: number;
511
+ loadLevel: number;
512
+ levels: Array<{ bitrate: number; width: number; height: number }>;
513
+ buffered: number;
514
+ latency?: number;
515
+ }
516
+ | undefined
517
+ > {
443
518
  if (!this.hls) return undefined;
444
519
 
445
520
  const levels = (this.hls.levels || []).map((lvl: any) => ({
@@ -456,7 +531,10 @@ export class HlsJsPlayerImpl extends BasePlayer {
456
531
  const video = this.videoElement;
457
532
  if (video && video.buffered.length > 0) {
458
533
  for (let i = 0; i < video.buffered.length; i++) {
459
- if (video.buffered.start(i) <= video.currentTime && video.buffered.end(i) > video.currentTime) {
534
+ if (
535
+ video.buffered.start(i) <= video.currentTime &&
536
+ video.buffered.end(i) > video.currentTime
537
+ ) {
460
538
  buffered = video.buffered.end(i) - video.currentTime;
461
539
  break;
462
540
  }
@@ -470,7 +548,7 @@ export class HlsJsPlayerImpl extends BasePlayer {
470
548
  }
471
549
 
472
550
  return {
473
- type: 'hls',
551
+ type: "hls",
474
552
  bandwidthEstimate: this.hls.bandwidthEstimate || 0,
475
553
  currentLevel,
476
554
  currentBitrate: currentLevelData?.bitrate || 0,