@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
@@ -10,17 +10,22 @@
10
10
  * - DataChannel for timed metadata
11
11
  */
12
12
 
13
- import { BasePlayer } from '../../core/PlayerInterface';
14
- import type { StreamSource, StreamInfo, PlayerOptions, PlayerCapability } from '../../core/PlayerInterface';
15
- import { MistSignaling, type MistTimeUpdate } from '../../core/MistSignaling';
16
- import { checkWebRTCCodecCompatibility } from '../../core/detector';
13
+ import { BasePlayer } from "../../core/PlayerInterface";
14
+ import type {
15
+ StreamSource,
16
+ StreamInfo,
17
+ PlayerOptions,
18
+ PlayerCapability,
19
+ } from "../../core/PlayerInterface";
20
+ import { MistSignaling, type MistTimeUpdate } from "../../core/MistSignaling";
21
+ import { checkWebRTCCodecCompatibility } from "../../core/detector";
17
22
 
18
23
  export class MistWebRTCPlayerImpl extends BasePlayer {
19
24
  readonly capability: PlayerCapability = {
20
25
  name: "MistServer WebRTC",
21
26
  shortname: "mist-webrtc",
22
27
  priority: 2, // After direct (WHEP=1), before HLS.js (3)
23
- mimes: ["webrtc", "mist/webrtc"]
28
+ mimes: ["webrtc", "mist/webrtc"],
24
29
  };
25
30
 
26
31
  private signaling: MistSignaling | null = null;
@@ -33,7 +38,7 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
33
38
  private seekOffset = 0;
34
39
  private durationMs = 0;
35
40
  private isLiveStream = true;
36
- private playRate: number | 'auto' = 'auto';
41
+ private playRate: number | "auto" = "auto";
37
42
 
38
43
  // Buffer window tracking (P2)
39
44
  private bufferWindow = 0;
@@ -46,7 +51,11 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
46
51
  private currentOptions: PlayerOptions | null = null;
47
52
 
48
53
  // Stats tracking
49
- private lastInboundStats: { video?: { bytesReceived: number }; audio?: { bytesReceived: number }; timestamp: number } | null = null;
54
+ private lastInboundStats: {
55
+ video?: { bytesReceived: number };
56
+ audio?: { bytesReceived: number };
57
+ timestamp: number;
58
+ } | null = null;
50
59
 
51
60
  /**
52
61
  * Chrome on Android has a bug where H264 is not available immediately
@@ -56,16 +65,16 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
56
65
  private async checkH264Available(retries = 5): Promise<boolean> {
57
66
  for (let i = 0; i < retries; i++) {
58
67
  try {
59
- const caps = RTCRtpReceiver.getCapabilities?.('video');
60
- if (caps?.codecs.some(c => c.mimeType === 'video/H264')) {
68
+ const caps = RTCRtpReceiver.getCapabilities?.("video");
69
+ if (caps?.codecs.some((c) => c.mimeType === "video/H264")) {
61
70
  return true;
62
71
  }
63
72
  } catch {}
64
73
  if (i < retries - 1) {
65
- await new Promise(r => setTimeout(r, 100));
74
+ await new Promise((r) => setTimeout(r, 100));
66
75
  }
67
76
  }
68
- console.warn('[MistWebRTC] H264 not available after retries');
77
+ console.warn("[MistWebRTC] H264 not available after retries");
69
78
  return false;
70
79
  }
71
80
 
@@ -77,14 +86,14 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
77
86
  if ((window as any).WebRTCBrowserEqualizerLoaded) return;
78
87
 
79
88
  return new Promise((resolve) => {
80
- const script = document.createElement('script');
89
+ const script = document.createElement("script");
81
90
  script.src = `${host}/webrtc.js`;
82
91
  script.onload = () => {
83
- console.debug('[MistWebRTC] Browser equalizer loaded');
92
+ console.debug("[MistWebRTC] Browser equalizer loaded");
84
93
  resolve();
85
94
  };
86
95
  script.onerror = () => {
87
- console.warn('[MistWebRTC] Failed to load browser equalizer');
96
+ console.warn("[MistWebRTC] Failed to load browser equalizer");
88
97
  resolve(); // Non-fatal
89
98
  };
90
99
  document.head.appendChild(script);
@@ -105,35 +114,46 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
105
114
  return this.capability.mimes.includes(mimetype);
106
115
  }
107
116
 
108
- isBrowserSupported(mimetype: string, source: StreamSource, streamInfo: StreamInfo): boolean | string[] {
117
+ isBrowserSupported(
118
+ mimetype: string,
119
+ source: StreamSource,
120
+ streamInfo: StreamInfo
121
+ ): boolean | string[] {
109
122
  // Check basic WebRTC support
110
- if (!('RTCPeerConnection' in window) || !('WebSocket' in window)) return false;
123
+ if (!("RTCPeerConnection" in window) || !("WebSocket" in window)) return false;
111
124
 
112
125
  // Check codec compatibility
113
126
  const codecCompat = checkWebRTCCodecCompatibility(streamInfo.meta.tracks);
114
127
  if (!codecCompat.compatible) {
115
- console.debug('[MistWebRTC] Skipping - incompatible codecs:', codecCompat.incompatibleCodecs.join(', '));
128
+ console.debug(
129
+ "[MistWebRTC] Skipping - incompatible codecs:",
130
+ codecCompat.incompatibleCodecs.join(", ")
131
+ );
116
132
  return false;
117
133
  }
118
134
 
119
135
  // Return which track types we can play
120
136
  const playable: string[] = [];
121
137
  if (codecCompat.details.compatibleVideoCodecs.length > 0) {
122
- playable.push('video');
138
+ playable.push("video");
123
139
  }
124
140
  if (codecCompat.details.compatibleAudioCodecs.length > 0) {
125
- playable.push('audio');
141
+ playable.push("audio");
126
142
  }
127
143
 
128
144
  return playable.length > 0 ? playable : false;
129
145
  }
130
146
 
131
- async initialize(container: HTMLElement, source: StreamSource, options: PlayerOptions): Promise<HTMLVideoElement> {
147
+ async initialize(
148
+ container: HTMLElement,
149
+ source: StreamSource,
150
+ options: PlayerOptions
151
+ ): Promise<HTMLVideoElement> {
132
152
  this.destroyed = false;
133
153
  this.container = container;
134
154
  this.currentSource = source;
135
155
  this.currentOptions = options;
136
- container.classList.add('fw-player-container');
156
+ container.classList.add("fw-player-container");
137
157
 
138
158
  // Load browser equalizer script (P0) - extract host from source URL
139
159
  try {
@@ -146,10 +166,10 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
146
166
  await this.checkH264Available();
147
167
 
148
168
  // Create video element
149
- const video = document.createElement('video');
150
- video.classList.add('fw-player-video');
151
- video.setAttribute('playsinline', '');
152
- video.setAttribute('crossorigin', 'anonymous');
169
+ const video = document.createElement("video");
170
+ video.classList.add("fw-player-video");
171
+ video.setAttribute("playsinline", "");
172
+ video.setAttribute("crossorigin", "anonymous");
153
173
 
154
174
  if (options.autoplay) video.autoplay = true;
155
175
  if (options.muted) video.muted = true;
@@ -165,7 +185,7 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
165
185
  await this.setupWebRTC(video, source, options);
166
186
  return video;
167
187
  } catch (error: any) {
168
- this.emit('error', error.message || String(error));
188
+ this.emit("error", error.message || String(error));
169
189
  throw error;
170
190
  }
171
191
  }
@@ -184,23 +204,31 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
184
204
 
185
205
  // Close data channel
186
206
  if (this.dataChannel) {
187
- try { this.dataChannel.close(); } catch {}
207
+ try {
208
+ this.dataChannel.close();
209
+ } catch {}
188
210
  this.dataChannel = null;
189
211
  }
190
212
 
191
213
  // Close peer connection
192
214
  if (this.peerConnection) {
193
- try { this.peerConnection.close(); } catch {}
215
+ try {
216
+ this.peerConnection.close();
217
+ } catch {}
194
218
  this.peerConnection = null;
195
219
  }
196
220
 
197
221
  // Clean up video element
198
222
  if (this.videoElement) {
199
- try { (this.videoElement as any).srcObject = null; } catch {}
223
+ try {
224
+ (this.videoElement as any).srcObject = null;
225
+ } catch {}
200
226
  this.videoElement.pause();
201
227
 
202
228
  if (this.container) {
203
- try { this.container.removeChild(this.videoElement); } catch {}
229
+ try {
230
+ this.container.removeChild(this.videoElement);
231
+ } catch {}
204
232
  }
205
233
  }
206
234
 
@@ -216,7 +244,7 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
216
244
  this.videoElement.pause();
217
245
  this.seekOffset = time - this.videoElement.currentTime;
218
246
  this.signaling.seek(time).catch((e) => {
219
- console.warn('[MistWebRTC] Seek failed:', e);
247
+ console.warn("[MistWebRTC] Seek failed:", e);
220
248
  });
221
249
  }
222
250
 
@@ -232,8 +260,8 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
232
260
 
233
261
  this.videoElement.pause();
234
262
  this.seekOffset = 0;
235
- this.signaling.seek('live').catch((e) => {
236
- console.warn('[MistWebRTC] Jump to live failed:', e);
263
+ this.signaling.seek("live").catch((e) => {
264
+ console.warn("[MistWebRTC] Jump to live failed:", e);
237
265
  });
238
266
  }
239
267
 
@@ -260,7 +288,7 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
260
288
  getQualities(): Array<{ id: string; label: string; isAuto?: boolean; active?: boolean }> {
261
289
  // Always offer auto as first option
262
290
  const qualities: Array<{ id: string; label: string; isAuto?: boolean; active?: boolean }> = [
263
- { id: 'auto', label: 'Auto', isAuto: true, active: this.playRate === 'auto' }
291
+ { id: "auto", label: "Auto", isAuto: true, active: this.playRate === "auto" },
264
292
  ];
265
293
 
266
294
  // If we have track info from signaling, add quality options
@@ -274,8 +302,8 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
274
302
  selectQuality(id: string): void {
275
303
  if (!this.signaling?.isConnected) return;
276
304
 
277
- if (id === 'auto') {
278
- this.signaling.setSpeed('auto');
305
+ if (id === "auto") {
306
+ this.signaling.setSpeed("auto");
279
307
  } else {
280
308
  // Track selection: ~widthxheight or |bitrate
281
309
  this.signaling.setTracks({ video: id });
@@ -287,63 +315,67 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
287
315
  if (!this.signaling?.isConnected) return;
288
316
 
289
317
  if (id === null) {
290
- this.signaling.setTracks({ video: 'none' });
318
+ this.signaling.setTracks({ video: "none" });
291
319
  } else {
292
320
  this.signaling.setTracks({ video: id });
293
321
  }
294
322
  }
295
323
 
296
- async getStats(): Promise<{
297
- type: 'webrtc';
298
- video?: {
299
- bytesReceived: number;
300
- packetsReceived: number;
301
- packetsLost: number;
302
- packetLossRate: number;
303
- jitter: number;
304
- framesDecoded: number;
305
- framesDropped: number;
306
- frameDropRate: number;
307
- frameWidth: number;
308
- frameHeight: number;
309
- framesPerSecond: number;
310
- bitrate: number;
311
- jitterBufferDelay: number;
312
- };
313
- audio?: {
314
- bytesReceived: number;
315
- packetsReceived: number;
316
- packetsLost: number;
317
- packetLossRate: number;
318
- jitter: number;
319
- bitrate: number;
320
- };
321
- network?: {
322
- rtt: number;
323
- availableOutgoingBitrate: number;
324
- availableIncomingBitrate: number;
325
- bytesSent: number;
326
- bytesReceived: number;
327
- };
328
- timestamp: number;
329
- } | undefined> {
324
+ async getStats(): Promise<
325
+ | {
326
+ type: "webrtc";
327
+ video?: {
328
+ bytesReceived: number;
329
+ packetsReceived: number;
330
+ packetsLost: number;
331
+ packetLossRate: number;
332
+ jitter: number;
333
+ framesDecoded: number;
334
+ framesDropped: number;
335
+ frameDropRate: number;
336
+ frameWidth: number;
337
+ frameHeight: number;
338
+ framesPerSecond: number;
339
+ bitrate: number;
340
+ jitterBufferDelay: number;
341
+ };
342
+ audio?: {
343
+ bytesReceived: number;
344
+ packetsReceived: number;
345
+ packetsLost: number;
346
+ packetLossRate: number;
347
+ jitter: number;
348
+ bitrate: number;
349
+ };
350
+ network?: {
351
+ rtt: number;
352
+ availableOutgoingBitrate: number;
353
+ availableIncomingBitrate: number;
354
+ bytesSent: number;
355
+ bytesReceived: number;
356
+ };
357
+ timestamp: number;
358
+ }
359
+ | undefined
360
+ > {
330
361
  if (!this.peerConnection) return undefined;
331
362
 
332
363
  try {
333
364
  const stats = await this.peerConnection.getStats();
334
365
  const now = Date.now();
335
- const result: any = { type: 'webrtc', timestamp: now };
366
+ const result: any = { type: "webrtc", timestamp: now };
336
367
 
337
368
  stats.forEach((report: any) => {
338
- if (report.type === 'inbound-rtp') {
339
- const packetLossRate = report.packetsReceived > 0
340
- ? (report.packetsLost / (report.packetsReceived + report.packetsLost)) * 100
341
- : 0;
369
+ if (report.type === "inbound-rtp") {
370
+ const packetLossRate =
371
+ report.packetsReceived > 0
372
+ ? (report.packetsLost / (report.packetsReceived + report.packetsLost)) * 100
373
+ : 0;
342
374
 
343
375
  // Calculate bitrate from previous sample
344
376
  let bitrate = 0;
345
- if (this.lastInboundStats && this.lastInboundStats[report.kind as 'video' | 'audio']) {
346
- const prev = this.lastInboundStats[report.kind as 'video' | 'audio'];
377
+ if (this.lastInboundStats && this.lastInboundStats[report.kind as "video" | "audio"]) {
378
+ const prev = this.lastInboundStats[report.kind as "video" | "audio"];
347
379
  const timeDelta = (now - this.lastInboundStats.timestamp) / 1000;
348
380
  if (timeDelta > 0 && prev) {
349
381
  const bytesDelta = report.bytesReceived - prev.bytesReceived;
@@ -351,10 +383,11 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
351
383
  }
352
384
  }
353
385
 
354
- if (report.kind === 'video') {
355
- const frameDropRate = report.framesDecoded > 0
356
- ? (report.framesDropped / (report.framesDecoded + report.framesDropped)) * 100
357
- : 0;
386
+ if (report.kind === "video") {
387
+ const frameDropRate =
388
+ report.framesDecoded > 0
389
+ ? (report.framesDropped / (report.framesDecoded + report.framesDropped)) * 100
390
+ : 0;
358
391
 
359
392
  result.video = {
360
393
  bytesReceived: report.bytesReceived || 0,
@@ -369,12 +402,13 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
369
402
  frameHeight: report.frameHeight || 0,
370
403
  framesPerSecond: report.framesPerSecond || 0,
371
404
  bitrate,
372
- jitterBufferDelay: report.jitterBufferDelay && report.jitterBufferEmittedCount
373
- ? (report.jitterBufferDelay / report.jitterBufferEmittedCount) * 1000
374
- : 0,
405
+ jitterBufferDelay:
406
+ report.jitterBufferDelay && report.jitterBufferEmittedCount
407
+ ? (report.jitterBufferDelay / report.jitterBufferEmittedCount) * 1000
408
+ : 0,
375
409
  };
376
410
  }
377
- if (report.kind === 'audio') {
411
+ if (report.kind === "audio") {
378
412
  result.audio = {
379
413
  bytesReceived: report.bytesReceived || 0,
380
414
  packetsReceived: report.packetsReceived || 0,
@@ -385,7 +419,7 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
385
419
  };
386
420
  }
387
421
  }
388
- if (report.type === 'candidate-pair' && report.nominated) {
422
+ if (report.type === "candidate-pair" && report.nominated) {
389
423
  result.network = {
390
424
  rtt: report.currentRoundTripTime ? report.currentRoundTripTime * 1000 : 0,
391
425
  availableOutgoingBitrate: report.availableOutgoingBitrate || 0,
@@ -409,7 +443,9 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
409
443
  }
410
444
  }
411
445
 
412
- async getLatency(): Promise<{ estimatedMs: number; jitterBufferMs: number; rttMs: number } | undefined> {
446
+ async getLatency(): Promise<
447
+ { estimatedMs: number; jitterBufferMs: number; rttMs: number } | undefined
448
+ > {
413
449
  const s = await this.getStats();
414
450
  if (!s) return undefined;
415
451
 
@@ -460,15 +496,15 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
460
496
  const pauseOnFirstPlay = () => {
461
497
  video.pause();
462
498
  this.signaling?.pause();
463
- video.removeEventListener('play', pauseOnFirstPlay);
499
+ video.removeEventListener("play", pauseOnFirstPlay);
464
500
  };
465
- video.addEventListener('play', pauseOnFirstPlay);
501
+ video.addEventListener("play", pauseOnFirstPlay);
466
502
  }
467
503
 
468
504
  // Loop reconnect for VoD content (P1)
469
- video.addEventListener('ended', async () => {
505
+ video.addEventListener("ended", async () => {
470
506
  if (video.loop && !this.isLiveStream && this.currentSource && this.currentOptions) {
471
- console.debug('[MistWebRTC] VoD ended with loop enabled, reconnecting...');
507
+ console.debug("[MistWebRTC] VoD ended with loop enabled, reconnecting...");
472
508
  try {
473
509
  // Partial cleanup - keep container and video element
474
510
  if (this.signaling) {
@@ -479,19 +515,23 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
479
515
  this.signaling = null;
480
516
  }
481
517
  if (this.dataChannel) {
482
- try { this.dataChannel.close(); } catch {}
518
+ try {
519
+ this.dataChannel.close();
520
+ } catch {}
483
521
  this.dataChannel = null;
484
522
  }
485
523
  if (this.peerConnection) {
486
- try { this.peerConnection.close(); } catch {}
524
+ try {
525
+ this.peerConnection.close();
526
+ } catch {}
487
527
  this.peerConnection = null;
488
528
  }
489
529
 
490
530
  // Reconnect WebRTC
491
531
  await this.setupWebRTC(video, this.currentSource, this.currentOptions);
492
532
  } catch (e) {
493
- console.error('[MistWebRTC] Failed to reconnect for loop:', e);
494
- this.emit('error', 'Failed to reconnect for loop');
533
+ console.error("[MistWebRTC] Failed to reconnect for loop:", e);
534
+ this.emit("error", "Failed to reconnect for loop");
495
535
  }
496
536
  }
497
537
  });
@@ -499,7 +539,11 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
499
539
 
500
540
  // Private methods
501
541
 
502
- private async setupWebRTC(video: HTMLVideoElement, source: StreamSource, _options: PlayerOptions): Promise<void> {
542
+ private async setupWebRTC(
543
+ video: HTMLVideoElement,
544
+ source: StreamSource,
545
+ _options: PlayerOptions
546
+ ): Promise<void> {
503
547
  const sourceAny = source as any;
504
548
  const iceServers: RTCIceServer[] = sourceAny?.iceServers || [];
505
549
 
@@ -515,10 +559,10 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
515
559
  this.peerConnection = pc;
516
560
 
517
561
  // Create data channel for metadata
518
- this.dataChannel = pc.createDataChannel('*', { protocol: 'JSON' });
562
+ this.dataChannel = pc.createDataChannel("*", { protocol: "JSON" });
519
563
  this.dataChannel.onmessage = (event) => {
520
564
  if (this.destroyed) return;
521
- console.debug('[MistWebRTC] DataChannel message:', event.data);
565
+ console.debug("[MistWebRTC] DataChannel message:", event.data);
522
566
  // Handle timed metadata here if needed
523
567
  };
524
568
 
@@ -536,8 +580,8 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
536
580
  const state = pc.connectionState;
537
581
  console.debug(`[MistWebRTC] Connection state: ${state}`);
538
582
 
539
- if (state === 'failed') {
540
- this.emit('error', 'WebRTC connection failed (firewall?)');
583
+ if (state === "failed") {
584
+ this.emit("error", "WebRTC connection failed (firewall?)");
541
585
  }
542
586
  };
543
587
 
@@ -547,8 +591,8 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
547
591
  const state = pc.iceConnectionState;
548
592
  console.debug(`[MistWebRTC] ICE state: ${state}`);
549
593
 
550
- if (state === 'failed') {
551
- this.emit('error', 'ICE connection failed');
594
+ if (state === "failed") {
595
+ this.emit("error", "ICE connection failed");
552
596
  }
553
597
  };
554
598
 
@@ -561,15 +605,15 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
561
605
  // Wait for signaling to connect
562
606
  await new Promise<void>((resolve, reject) => {
563
607
  const timeout = setTimeout(() => {
564
- reject(new Error('Signaling connection timeout'));
608
+ reject(new Error("Signaling connection timeout"));
565
609
  }, 10000);
566
610
 
567
- this.signaling!.once('connected', () => {
611
+ this.signaling!.once("connected", () => {
568
612
  clearTimeout(timeout);
569
613
  resolve();
570
614
  });
571
615
 
572
- this.signaling!.once('error', ({ message }) => {
616
+ this.signaling!.once("error", ({ message }) => {
573
617
  clearTimeout(timeout);
574
618
  reject(new Error(message));
575
619
  });
@@ -581,18 +625,18 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
581
625
  // Wait for answer
582
626
  await new Promise<void>((resolve, reject) => {
583
627
  const timeout = setTimeout(() => {
584
- reject(new Error('SDP answer timeout'));
628
+ reject(new Error("SDP answer timeout"));
585
629
  }, 10000);
586
630
 
587
- this.signaling!.once('answer_sdp', async ({ result, answer_sdp }) => {
631
+ this.signaling!.once("answer_sdp", async ({ result, answer_sdp }) => {
588
632
  clearTimeout(timeout);
589
633
  if (!result) {
590
- reject(new Error('Failed to get SDP answer'));
634
+ reject(new Error("Failed to get SDP answer"));
591
635
  return;
592
636
  }
593
637
 
594
638
  try {
595
- await pc.setRemoteDescription({ type: 'answer', sdp: answer_sdp });
639
+ await pc.setRemoteDescription({ type: "answer", sdp: answer_sdp });
596
640
  resolve();
597
641
  } catch (err) {
598
642
  reject(err);
@@ -605,49 +649,49 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
605
649
  if (!this.signaling) return;
606
650
 
607
651
  // Dispatch webrtc_connected event (P2)
608
- this.signaling.on('connected', () => {
652
+ this.signaling.on("connected", () => {
609
653
  if (this.destroyed) return;
610
- video.dispatchEvent(new Event('webrtc_connected'));
654
+ video.dispatchEvent(new Event("webrtc_connected"));
611
655
  });
612
656
 
613
- this.signaling.on('time_update', (update: MistTimeUpdate) => {
657
+ this.signaling.on("time_update", (update: MistTimeUpdate) => {
614
658
  if (this.destroyed) return;
615
659
  this.handleTimeUpdate(update, video);
616
660
  });
617
661
 
618
- this.signaling.on('seeked', ({ live_point }) => {
662
+ this.signaling.on("seeked", ({ live_point }) => {
619
663
  if (this.destroyed) return;
620
664
  // Dispatch seeked event
621
- video.dispatchEvent(new CustomEvent('seeked', { detail: { seekOffset: this.seekOffset } }));
665
+ video.dispatchEvent(new CustomEvent("seeked", { detail: { seekOffset: this.seekOffset } }));
622
666
  // Set playback rate to auto if seeked to live point
623
667
  if (live_point && this.signaling) {
624
- this.signaling.setSpeed('auto');
668
+ this.signaling.setSpeed("auto");
625
669
  }
626
670
  video.play().catch(() => {});
627
671
  });
628
672
 
629
- this.signaling.on('speed_changed', ({ play_rate_curr }) => {
673
+ this.signaling.on("speed_changed", ({ play_rate_curr }) => {
630
674
  if (this.destroyed) return;
631
675
  this.playRate = play_rate_curr;
632
- video.dispatchEvent(new CustomEvent('ratechange', { detail: { play_rate_curr } }));
676
+ video.dispatchEvent(new CustomEvent("ratechange", { detail: { play_rate_curr } }));
633
677
  });
634
678
 
635
- this.signaling.on('stopped', () => {
679
+ this.signaling.on("stopped", () => {
636
680
  if (this.destroyed) return;
637
681
  this.isLiveStream = false;
638
682
  video.pause();
639
- this.emit('ended', undefined);
683
+ this.emit("ended", undefined);
640
684
  });
641
685
 
642
- this.signaling.on('error', ({ message }) => {
686
+ this.signaling.on("error", ({ message }) => {
643
687
  if (this.destroyed) return;
644
- this.emit('error', message);
688
+ this.emit("error", message);
645
689
  });
646
690
 
647
691
  // Dispatch webrtc_disconnected event (P2)
648
- this.signaling.on('disconnected', () => {
692
+ this.signaling.on("disconnected", () => {
649
693
  if (this.destroyed) return;
650
- video.dispatchEvent(new Event('webrtc_disconnected'));
694
+ video.dispatchEvent(new Event("webrtc_disconnected"));
651
695
  video.pause();
652
696
  });
653
697
  }
@@ -668,9 +712,11 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
668
712
  if (update.tracks && !this.arraysEqual(update.tracks, this.currentTracks)) {
669
713
  for (const trackId of update.tracks) {
670
714
  if (!this.currentTracks.includes(trackId)) {
671
- video.dispatchEvent(new CustomEvent('playerUpdate_trackChanged', {
672
- detail: { trackId }
673
- }));
715
+ video.dispatchEvent(
716
+ new CustomEvent("playerUpdate_trackChanged", {
717
+ detail: { trackId },
718
+ })
719
+ );
674
720
  }
675
721
  }
676
722
  this.currentTracks = [...update.tracks];
@@ -686,8 +732,8 @@ export class MistWebRTCPlayerImpl extends BasePlayer {
686
732
  if (!this.signaling) return;
687
733
 
688
734
  // Add transceivers for receiving
689
- pc.addTransceiver('video', { direction: 'recvonly' });
690
- pc.addTransceiver('audio', { direction: 'recvonly' });
735
+ pc.addTransceiver("video", { direction: "recvonly" });
736
+ pc.addTransceiver("audio", { direction: "recvonly" });
691
737
 
692
738
  const offer = await pc.createOffer({
693
739
  offerToReceiveAudio: true,