@livepeer-frameworks/player-svelte 0.1.0 → 0.1.2

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 (127) hide show
  1. package/README.md +4 -6
  2. package/dist/DevModePanel.svelte +266 -127
  3. package/dist/DevModePanel.svelte.d.ts +1 -1
  4. package/dist/DvdLogo.svelte +17 -21
  5. package/dist/Icons.svelte +5 -3
  6. package/dist/Icons.svelte.d.ts +6 -19
  7. package/dist/IdleScreen.svelte +277 -186
  8. package/dist/IdleScreen.svelte.d.ts +1 -1
  9. package/dist/LoadingScreen.svelte +190 -162
  10. package/dist/Player.svelte +244 -111
  11. package/dist/Player.svelte.d.ts +1 -1
  12. package/dist/PlayerControls.svelte +263 -168
  13. package/dist/PlayerControls.svelte.d.ts +1 -1
  14. package/dist/SeekBar.svelte +61 -35
  15. package/dist/SkipIndicator.svelte +4 -4
  16. package/dist/SkipIndicator.svelte.d.ts +1 -1
  17. package/dist/SpeedIndicator.svelte +1 -1
  18. package/dist/StatsPanel.svelte +76 -57
  19. package/dist/StatsPanel.svelte.d.ts +1 -1
  20. package/dist/StreamStateOverlay.svelte +143 -107
  21. package/dist/StreamStateOverlay.svelte.d.ts +1 -1
  22. package/dist/SubtitleRenderer.svelte +46 -43
  23. package/dist/ThumbnailOverlay.svelte +22 -19
  24. package/dist/TitleOverlay.svelte +6 -11
  25. package/dist/components/VolumeIcons.svelte +12 -6
  26. package/dist/global.d.ts +3 -3
  27. package/dist/icons/FullscreenExitIcon.svelte +1 -5
  28. package/dist/icons/FullscreenIcon.svelte +1 -5
  29. package/dist/icons/PauseIcon.svelte +1 -5
  30. package/dist/icons/PictureInPictureIcon.svelte +12 -6
  31. package/dist/icons/PlayIcon.svelte +1 -5
  32. package/dist/icons/SeekToLiveIcon.svelte +1 -5
  33. package/dist/icons/SettingsIcon.svelte +1 -5
  34. package/dist/icons/SkipBackIcon.svelte +1 -5
  35. package/dist/icons/SkipForwardIcon.svelte +1 -5
  36. package/dist/icons/StatsIcon.svelte +1 -5
  37. package/dist/icons/VolumeOffIcon.svelte +1 -5
  38. package/dist/icons/VolumeUpIcon.svelte +1 -5
  39. package/dist/icons/index.d.ts +12 -12
  40. package/dist/icons/index.js +12 -12
  41. package/dist/index.d.ts +24 -24
  42. package/dist/index.js +21 -21
  43. package/dist/stores/index.d.ts +6 -6
  44. package/dist/stores/index.js +6 -6
  45. package/dist/stores/playbackQuality.d.ts +2 -2
  46. package/dist/stores/playbackQuality.js +7 -7
  47. package/dist/stores/playerContext.d.ts +2 -2
  48. package/dist/stores/playerContext.js +17 -17
  49. package/dist/stores/playerController.d.ts +13 -4
  50. package/dist/stores/playerController.js +80 -56
  51. package/dist/stores/playerSelection.d.ts +2 -2
  52. package/dist/stores/playerSelection.js +7 -7
  53. package/dist/stores/streamState.d.ts +2 -2
  54. package/dist/stores/streamState.js +56 -56
  55. package/dist/stores/viewerEndpoints.d.ts +3 -3
  56. package/dist/stores/viewerEndpoints.js +21 -21
  57. package/dist/types.d.ts +1 -1
  58. package/dist/ui/Badge.svelte +9 -10
  59. package/dist/ui/Badge.svelte.d.ts +8 -29
  60. package/dist/ui/Button.svelte +16 -16
  61. package/dist/ui/Button.svelte.d.ts +8 -29
  62. package/dist/ui/Slider.svelte +21 -55
  63. package/dist/ui/badge.js +1 -1
  64. package/dist/ui/button.js +2 -2
  65. package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte +5 -7
  66. package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte.d.ts +6 -27
  67. package/dist/ui/context-menu/ContextMenuContent.svelte +2 -9
  68. package/dist/ui/context-menu/ContextMenuItem.svelte +1 -5
  69. package/dist/ui/context-menu/ContextMenuLabel.svelte +1 -5
  70. package/dist/ui/context-menu/ContextMenuRadioItem.svelte +5 -7
  71. package/dist/ui/context-menu/ContextMenuRadioItem.svelte.d.ts +6 -27
  72. package/dist/ui/context-menu/ContextMenuSeparator.svelte +2 -8
  73. package/dist/ui/context-menu/ContextMenuShortcut.svelte +2 -12
  74. package/dist/ui/context-menu/ContextMenuSubContent.svelte +1 -5
  75. package/package.json +15 -7
  76. package/src/DevModePanel.svelte +266 -127
  77. package/src/DvdLogo.svelte +17 -21
  78. package/src/Icons.svelte +5 -3
  79. package/src/IdleScreen.svelte +277 -186
  80. package/src/LoadingScreen.svelte +190 -162
  81. package/src/Player.svelte +244 -111
  82. package/src/PlayerControls.svelte +263 -168
  83. package/src/SeekBar.svelte +61 -35
  84. package/src/SkipIndicator.svelte +4 -4
  85. package/src/SpeedIndicator.svelte +1 -1
  86. package/src/StatsPanel.svelte +76 -57
  87. package/src/StreamStateOverlay.svelte +143 -107
  88. package/src/SubtitleRenderer.svelte +46 -43
  89. package/src/ThumbnailOverlay.svelte +22 -19
  90. package/src/TitleOverlay.svelte +6 -11
  91. package/src/components/VolumeIcons.svelte +12 -6
  92. package/src/global.d.ts +3 -3
  93. package/src/icons/FullscreenExitIcon.svelte +1 -5
  94. package/src/icons/FullscreenIcon.svelte +1 -5
  95. package/src/icons/PauseIcon.svelte +1 -5
  96. package/src/icons/PictureInPictureIcon.svelte +12 -6
  97. package/src/icons/PlayIcon.svelte +1 -5
  98. package/src/icons/SeekToLiveIcon.svelte +1 -5
  99. package/src/icons/SettingsIcon.svelte +1 -5
  100. package/src/icons/SkipBackIcon.svelte +1 -5
  101. package/src/icons/SkipForwardIcon.svelte +1 -5
  102. package/src/icons/StatsIcon.svelte +1 -5
  103. package/src/icons/VolumeOffIcon.svelte +1 -5
  104. package/src/icons/VolumeUpIcon.svelte +1 -5
  105. package/src/icons/index.ts +12 -12
  106. package/src/index.ts +31 -24
  107. package/src/stores/index.ts +6 -6
  108. package/src/stores/playbackQuality.ts +10 -8
  109. package/src/stores/playerContext.ts +21 -17
  110. package/src/stores/playerController.ts +196 -126
  111. package/src/stores/playerSelection.ts +23 -13
  112. package/src/stores/streamState.ts +51 -51
  113. package/src/stores/viewerEndpoints.ts +30 -26
  114. package/src/types.ts +1 -1
  115. package/src/ui/Badge.svelte +9 -10
  116. package/src/ui/Button.svelte +16 -16
  117. package/src/ui/Slider.svelte +21 -55
  118. package/src/ui/badge.ts +3 -2
  119. package/src/ui/button.ts +17 -5
  120. package/src/ui/context-menu/ContextMenuCheckboxItem.svelte +5 -7
  121. package/src/ui/context-menu/ContextMenuContent.svelte +2 -9
  122. package/src/ui/context-menu/ContextMenuItem.svelte +1 -5
  123. package/src/ui/context-menu/ContextMenuLabel.svelte +1 -5
  124. package/src/ui/context-menu/ContextMenuRadioItem.svelte +5 -7
  125. package/src/ui/context-menu/ContextMenuSeparator.svelte +2 -8
  126. package/src/ui/context-menu/ContextMenuShortcut.svelte +2 -12
  127. package/src/ui/context-menu/ContextMenuSubContent.svelte +1 -5
@@ -10,23 +10,27 @@
10
10
  type StreamInfo,
11
11
  type MistStreamInfo,
12
12
  type PlaybackMode,
13
- } from '@livepeer-frameworks/player-core';
13
+ } from "@livepeer-frameworks/player-core";
14
14
 
15
15
  /** Short labels for source types */
16
16
  const SOURCE_TYPE_LABELS: Record<string, string> = {
17
- 'html5/application/vnd.apple.mpegurl': 'HLS',
18
- 'dash/video/mp4': 'DASH',
19
- 'html5/video/mp4': 'MP4',
20
- 'html5/video/webm': 'WebM',
21
- 'whep': 'WHEP',
22
- 'mist/html': 'Mist',
23
- 'mist/legacy': 'Auto',
24
- 'ws/video/mp4': 'MEWS',
17
+ "html5/application/vnd.apple.mpegurl": "HLS",
18
+ "dash/video/mp4": "DASH",
19
+ "html5/video/mp4": "MP4",
20
+ "html5/video/webm": "WebM",
21
+ whep: "WHEP",
22
+ "mist/html": "Mist",
23
+ "mist/legacy": "Auto",
24
+ "ws/video/mp4": "MEWS",
25
25
  };
26
26
 
27
27
  interface Props {
28
28
  /** Callback when user selects a combo (one-shot selection) */
29
- onSettingsChange: (settings: { forcePlayer?: string; forceType?: string; forceSource?: number }) => void;
29
+ onSettingsChange: (settings: {
30
+ forcePlayer?: string;
31
+ forceType?: string;
32
+ forceSource?: number;
33
+ }) => void;
30
34
  playbackMode?: PlaybackMode;
31
35
  onModeChange?: (mode: PlaybackMode) => void;
32
36
  onReload?: () => void;
@@ -44,7 +48,7 @@
44
48
 
45
49
  let {
46
50
  onSettingsChange,
47
- playbackMode = 'auto',
51
+ playbackMode = "auto",
48
52
  onModeChange = undefined,
49
53
  onReload = undefined,
50
54
  streamInfo = null,
@@ -61,7 +65,7 @@
61
65
 
62
66
  // Internal state
63
67
  let internalIsOpen = $state(false);
64
- let activeTab = $state<'config' | 'stats'>('config');
68
+ let activeTab = $state<"config" | "stats">("config");
65
69
  let hoveredComboIndex = $state<number | null>(null);
66
70
  let tooltipAbove = $state(false);
67
71
  let showDisabledPlayers = $state(false);
@@ -112,20 +116,20 @@
112
116
  }
113
117
  });
114
118
 
115
- let combinations = $derived(allCombinations.filter(c => c.compatible));
119
+ let combinations = $derived(allCombinations.filter((c) => c.compatible));
116
120
 
117
121
  // Find active combo index
118
122
  let activeComboIndex = $derived.by(() => {
119
123
  if (!currentPlayer || !currentSource || allCombinations.length === 0) return -1;
120
124
  return allCombinations.findIndex(
121
- c => c.player === currentPlayer.shortname && c.sourceType === currentSource.type
125
+ (c) => c.player === currentPlayer.shortname && c.sourceType === currentSource.type
122
126
  );
123
127
  });
124
128
 
125
129
  let activeCompatibleIndex = $derived.by(() => {
126
130
  if (!currentPlayer || !currentSource || combinations.length === 0) return -1;
127
131
  return combinations.findIndex(
128
- c => c.player === currentPlayer.shortname && c.sourceType === currentSource.type
132
+ (c) => c.player === currentPlayer.shortname && c.sourceType === currentSource.type
129
133
  );
130
134
  });
131
135
 
@@ -195,7 +199,7 @@
195
199
 
196
200
  // Video stats polling
197
201
  $effect(() => {
198
- if (!isOpen || activeTab !== 'stats') return;
202
+ if (!isOpen || activeTab !== "stats") return;
199
203
 
200
204
  function updateStats() {
201
205
  const player = globalPlayerManager.getCurrentPlayer();
@@ -206,12 +210,13 @@
206
210
  }
207
211
  stats = {
208
212
  resolution: `${v.videoWidth}x${v.videoHeight}`,
209
- buffered: v.buffered.length > 0
210
- ? (v.buffered.end(v.buffered.length - 1) - v.currentTime).toFixed(1)
211
- : '0',
213
+ buffered:
214
+ v.buffered.length > 0
215
+ ? (v.buffered.end(v.buffered.length - 1) - v.currentTime).toFixed(1)
216
+ : "0",
212
217
  playbackRate: v.playbackRate.toFixed(2),
213
218
  currentTime: v.currentTime.toFixed(1),
214
- duration: isFinite(v.duration) ? v.duration.toFixed(1) : 'live',
219
+ duration: isFinite(v.duration) ? v.duration.toFixed(1) : "live",
215
220
  readyState: v.readyState,
216
221
  networkState: v.networkState,
217
222
  };
@@ -224,7 +229,7 @@
224
229
 
225
230
  // Poll player-specific stats when stats tab is open
226
231
  $effect(() => {
227
- if (!isOpen || activeTab !== 'stats') {
232
+ if (!isOpen || activeTab !== "stats") {
228
233
  playerStats = null;
229
234
  return;
230
235
  }
@@ -232,7 +237,7 @@
232
237
  async function pollStats() {
233
238
  try {
234
239
  const player = globalPlayerManager.getCurrentPlayer();
235
- if (player && typeof player.getStats === 'function') {
240
+ if (player && typeof player.getStats === "function") {
236
241
  const stats = await player.getStats();
237
242
  if (stats) {
238
243
  playerStats = stats;
@@ -262,15 +267,15 @@
262
267
  <div class="fw-dev-header">
263
268
  <button
264
269
  type="button"
265
- onclick={() => activeTab = 'config'}
266
- class={cn('fw-dev-tab', activeTab === 'config' && 'fw-dev-tab--active')}
270
+ onclick={() => (activeTab = "config")}
271
+ class={cn("fw-dev-tab", activeTab === "config" && "fw-dev-tab--active")}
267
272
  >
268
273
  Config
269
274
  </button>
270
275
  <button
271
276
  type="button"
272
- onclick={() => activeTab = 'stats'}
273
- class={cn('fw-dev-tab', activeTab === 'stats' && 'fw-dev-tab--active')}
277
+ onclick={() => (activeTab = "stats")}
278
+ class={cn("fw-dev-tab", activeTab === "stats" && "fw-dev-tab--active")}
274
279
  >
275
280
  Stats
276
281
  </button>
@@ -281,21 +286,28 @@
281
286
  class="fw-dev-close"
282
287
  aria-label="Close dev mode panel"
283
288
  >
284
- <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">
289
+ <svg
290
+ width="12"
291
+ height="12"
292
+ viewBox="0 0 12 12"
293
+ fill="none"
294
+ stroke="currentColor"
295
+ stroke-width="1.5"
296
+ >
285
297
  <path d="M2 2l8 8M10 2l-8 8" />
286
298
  </svg>
287
299
  </button>
288
300
  </div>
289
301
 
290
- {#if activeTab === 'config'}
302
+ {#if activeTab === "config"}
291
303
  <div bind:this={comboListRef} class="fw-dev-body">
292
304
  <!-- Current State -->
293
305
  <div class="fw-dev-section">
294
306
  <div class="fw-dev-label">Active</div>
295
307
  <div class="fw-dev-value">
296
- {currentPlayer?.name || 'None'}{' '}
297
- <span class="fw-dev-value-arrow">→</span>{' '}
298
- {SOURCE_TYPE_LABELS[currentSource?.type || ''] || currentSource?.type || ''}
308
+ {currentPlayer?.name || "None"}{" "}
309
+ <span class="fw-dev-value-arrow">→</span>{" "}
310
+ {SOURCE_TYPE_LABELS[currentSource?.type || ""] || currentSource?.type || ""}
299
311
  </div>
300
312
  {#if nodeId}
301
313
  <div class="fw-dev-value-muted">Node: {nodeId}</div>
@@ -306,28 +318,26 @@
306
318
  <div class="fw-dev-section">
307
319
  <div class="fw-dev-label">Playback Mode</div>
308
320
  <div class="fw-dev-mode-group">
309
- {#each ['auto', 'low-latency', 'quality'] as mode}
321
+ {#each ["auto", "low-latency", "quality"] as mode}
310
322
  <button
311
323
  type="button"
312
324
  onclick={() => onModeChange?.(mode as PlaybackMode)}
313
- class={cn('fw-dev-mode-btn', playbackMode === mode && 'fw-dev-mode-btn--active')}
325
+ class={cn("fw-dev-mode-btn", playbackMode === mode && "fw-dev-mode-btn--active")}
314
326
  >
315
- {mode === 'low-latency' ? 'Low Lat' : mode.charAt(0).toUpperCase() + mode.slice(1)}
327
+ {mode === "low-latency" ? "Low Lat" : mode.charAt(0).toUpperCase() + mode.slice(1)}
316
328
  </button>
317
329
  {/each}
318
330
  </div>
319
331
  <div class="fw-dev-mode-desc">
320
- {#if playbackMode === 'auto'}Balanced: MP4/WS → WHEP → HLS{/if}
321
- {#if playbackMode === 'low-latency'}WHEP/WebRTC first (sub-1s delay){/if}
322
- {#if playbackMode === 'quality'}MP4/WS first, HLS fallback{/if}
332
+ {#if playbackMode === "auto"}Balanced: MP4/WS → WHEP → HLS{/if}
333
+ {#if playbackMode === "low-latency"}WHEP/WebRTC first (sub-1s delay){/if}
334
+ {#if playbackMode === "quality"}MP4/WS first, HLS fallback{/if}
323
335
  </div>
324
336
  </div>
325
337
 
326
338
  <!-- Action buttons -->
327
339
  <div class="fw-dev-actions">
328
- <button type="button" onclick={handleReload} class="fw-dev-action-btn">
329
- Reload
330
- </button>
340
+ <button type="button" onclick={handleReload} class="fw-dev-action-btn"> Reload </button>
331
341
  <button type="button" onclick={handleNextCombo} class="fw-dev-action-btn">
332
342
  Next Option
333
343
  </button>
@@ -342,7 +352,7 @@
342
352
  {#if allCombinations.length > combinations.length}
343
353
  <button
344
354
  type="button"
345
- onclick={() => showDisabledPlayers = !showDisabledPlayers}
355
+ onclick={() => (showDisabledPlayers = !showDisabledPlayers)}
346
356
  class="fw-dev-list-toggle"
347
357
  >
348
358
  <svg
@@ -352,11 +362,12 @@
352
362
  fill="none"
353
363
  stroke="currentColor"
354
364
  stroke-width="2"
355
- class={cn('fw-dev-chevron', showDisabledPlayers && 'fw-dev-chevron--open')}
365
+ class={cn("fw-dev-chevron", showDisabledPlayers && "fw-dev-chevron--open")}
356
366
  >
357
367
  <path d="M6 9l6 6 6-6" />
358
368
  </svg>
359
- {showDisabledPlayers ? 'Hide' : 'Show'} disabled ({allCombinations.length - combinations.length})
369
+ {showDisabledPlayers ? "Hide" : "Show"} disabled ({allCombinations.length -
370
+ combinations.length})
360
371
  </button>
361
372
  {/if}
362
373
  </div>
@@ -368,78 +379,121 @@
368
379
  {@const isCodecIncompat = (combo as any).codecIncompatible === true}
369
380
  {@const shouldShow = combo.compatible || isCodecIncompat || showDisabledPlayers}
370
381
  {@const isActive = activeComboIndex === index}
371
- {@const typeLabel = SOURCE_TYPE_LABELS[combo.sourceType] || combo.sourceType.split('/').pop()}
382
+ {@const typeLabel =
383
+ SOURCE_TYPE_LABELS[combo.sourceType] || combo.sourceType.split("/").pop()}
372
384
 
373
385
  {#if shouldShow}
374
386
  <div
375
387
  class="fw-dev-combo"
388
+ role="listitem"
376
389
  onmouseenter={(e) => handleComboHover(index, e)}
377
- onmouseleave={() => hoveredComboIndex = null}
390
+ onmouseleave={() => (hoveredComboIndex = null)}
378
391
  >
379
392
  <button
380
393
  type="button"
381
394
  onclick={() => handleSelectCombo(index)}
382
395
  class={cn(
383
- 'fw-dev-combo-btn',
384
- isActive && 'fw-dev-combo-btn--active',
385
- !combo.compatible && !isCodecIncompat && 'fw-dev-combo-btn--disabled',
386
- isCodecIncompat && 'fw-dev-combo-btn--codec-warn'
396
+ "fw-dev-combo-btn",
397
+ isActive && "fw-dev-combo-btn--active",
398
+ !combo.compatible && !isCodecIncompat && "fw-dev-combo-btn--disabled",
399
+ isCodecIncompat && "fw-dev-combo-btn--codec-warn"
387
400
  )}
388
401
  >
389
402
  <!-- Rank -->
390
- <span class={cn(
391
- 'fw-dev-combo-rank',
392
- isActive ? 'fw-dev-combo-rank--active' :
393
- !combo.compatible && !isCodecIncompat ? 'fw-dev-combo-rank--disabled' :
394
- isCodecIncompat ? 'fw-dev-combo-rank--warn' : ''
395
- )}>
396
- {combo.compatible ? index + 1 : isCodecIncompat ? '⚠' : '—'}
403
+ <span
404
+ class={cn(
405
+ "fw-dev-combo-rank",
406
+ isActive
407
+ ? "fw-dev-combo-rank--active"
408
+ : !combo.compatible && !isCodecIncompat
409
+ ? "fw-dev-combo-rank--disabled"
410
+ : isCodecIncompat
411
+ ? "fw-dev-combo-rank--warn"
412
+ : ""
413
+ )}
414
+ >
415
+ {combo.compatible ? index + 1 : isCodecIncompat ? "⚠" : "—"}
397
416
  </span>
398
417
  <!-- Player + Protocol -->
399
418
  <span class="fw-dev-combo-name">
400
- {combo.playerName}{' '}
401
- <span class="fw-dev-combo-arrow">→</span>{' '}
402
- <span class={cn(
403
- 'fw-dev-combo-type',
404
- isCodecIncompat && 'fw-dev-combo-type--warn',
405
- !combo.compatible && !isCodecIncompat && 'fw-dev-combo-type--disabled'
406
- )}>{typeLabel}</span>
419
+ {combo.playerName}{" "}
420
+ <span class="fw-dev-combo-arrow">→</span>{" "}
421
+ <span
422
+ class={cn(
423
+ "fw-dev-combo-type",
424
+ isCodecIncompat && "fw-dev-combo-type--warn",
425
+ !combo.compatible && !isCodecIncompat && "fw-dev-combo-type--disabled"
426
+ )}>{typeLabel}</span
427
+ >
407
428
  </span>
408
429
  <!-- Score -->
409
- <span class={cn(
410
- 'fw-dev-combo-score',
411
- !combo.compatible && !isCodecIncompat ? 'fw-dev-combo-score--disabled' :
412
- isCodecIncompat ? 'fw-dev-combo-score--low' :
413
- combo.score >= 2 ? 'fw-dev-combo-score--high' :
414
- combo.score >= 1.5 ? 'fw-dev-combo-score--mid' :
415
- 'fw-dev-combo-score--low'
416
- )}>
430
+ <span
431
+ class={cn(
432
+ "fw-dev-combo-score",
433
+ !combo.compatible && !isCodecIncompat
434
+ ? "fw-dev-combo-score--disabled"
435
+ : isCodecIncompat
436
+ ? "fw-dev-combo-score--low"
437
+ : combo.score >= 2
438
+ ? "fw-dev-combo-score--high"
439
+ : combo.score >= 1.5
440
+ ? "fw-dev-combo-score--mid"
441
+ : "fw-dev-combo-score--low"
442
+ )}
443
+ >
417
444
  {combo.score.toFixed(2)}
418
445
  </span>
419
446
  </button>
420
447
 
421
448
  <!-- Tooltip -->
422
449
  {#if hoveredComboIndex === index}
423
- <div class={cn(
424
- 'fw-dev-tooltip',
425
- tooltipAbove ? 'fw-dev-tooltip--above' : 'fw-dev-tooltip--below'
426
- )}>
450
+ <div
451
+ class={cn(
452
+ "fw-dev-tooltip",
453
+ tooltipAbove ? "fw-dev-tooltip--above" : "fw-dev-tooltip--below"
454
+ )}
455
+ >
427
456
  <div class="fw-dev-tooltip-header">
428
457
  <div class="fw-dev-tooltip-title">{combo.playerName}</div>
429
458
  <div class="fw-dev-tooltip-subtitle">{combo.sourceType}</div>
430
459
  {#if combo.scoreBreakdown?.trackTypes?.length}
431
460
  <div class="fw-dev-tooltip-tracks">
432
- Tracks: <span class="fw-dev-tooltip-value">{combo.scoreBreakdown.trackTypes.join(', ')}</span>
461
+ Tracks: <span class="fw-dev-tooltip-value"
462
+ >{combo.scoreBreakdown.trackTypes.join(", ")}</span
463
+ >
433
464
  </div>
434
465
  {/if}
435
466
  </div>
436
467
  {#if combo.compatible && combo.scoreBreakdown}
437
468
  <div class="fw-dev-tooltip-score">Score: {combo.score.toFixed(2)}</div>
438
- <div class="fw-dev-tooltip-row">Tracks: <span class="fw-dev-tooltip-value">{combo.scoreBreakdown.trackScore.toFixed(2)}</span> <span class="fw-dev-tooltip-weight">x{combo.scoreBreakdown.weights.tracks}</span></div>
439
- <div class="fw-dev-tooltip-row">Priority: <span class="fw-dev-tooltip-value">{combo.scoreBreakdown.priorityScore.toFixed(2)}</span> <span class="fw-dev-tooltip-weight">x{combo.scoreBreakdown.weights.priority}</span></div>
440
- <div class="fw-dev-tooltip-row">Source: <span class="fw-dev-tooltip-value">{combo.scoreBreakdown.sourceScore.toFixed(2)}</span> <span class="fw-dev-tooltip-weight">x{combo.scoreBreakdown.weights.source}</span></div>
469
+ <div class="fw-dev-tooltip-row">
470
+ Tracks: <span class="fw-dev-tooltip-value"
471
+ >{combo.scoreBreakdown.trackScore.toFixed(2)}</span
472
+ >
473
+ <span class="fw-dev-tooltip-weight"
474
+ >x{combo.scoreBreakdown.weights.tracks}</span
475
+ >
476
+ </div>
477
+ <div class="fw-dev-tooltip-row">
478
+ Priority: <span class="fw-dev-tooltip-value"
479
+ >{combo.scoreBreakdown.priorityScore.toFixed(2)}</span
480
+ >
481
+ <span class="fw-dev-tooltip-weight"
482
+ >x{combo.scoreBreakdown.weights.priority}</span
483
+ >
484
+ </div>
485
+ <div class="fw-dev-tooltip-row">
486
+ Source: <span class="fw-dev-tooltip-value"
487
+ >{combo.scoreBreakdown.sourceScore.toFixed(2)}</span
488
+ >
489
+ <span class="fw-dev-tooltip-weight"
490
+ >x{combo.scoreBreakdown.weights.source}</span
491
+ >
492
+ </div>
441
493
  {:else}
442
- <div class="fw-dev-tooltip-error">{combo.incompatibleReason || 'Incompatible'}</div>
494
+ <div class="fw-dev-tooltip-error">
495
+ {combo.incompatibleReason || "Incompatible"}
496
+ </div>
443
497
  {/if}
444
498
  </div>
445
499
  {/if}
@@ -449,47 +503,94 @@
449
503
  {/if}
450
504
  </div>
451
505
  </div>
452
- {:else if activeTab === 'stats'}
506
+ {:else if activeTab === "stats"}
453
507
  <div class="fw-dev-body">
454
508
  <!-- Playback Rate -->
455
509
  <div class="fw-dev-section">
456
510
  <div class="fw-dev-label">Playback Rate</div>
457
511
  <div class="fw-dev-rate">
458
- <div class={cn(
459
- 'fw-dev-rate-value',
460
- playbackScore >= 0.95 && playbackScore <= 1.05 ? 'fw-dev-stat-value--good' :
461
- playbackScore > 1.05 ? 'fw-dev-stat-value--accent' :
462
- playbackScore >= 0.75 ? 'fw-dev-stat-value--warn' :
463
- 'fw-dev-stat-value--bad'
464
- )}>
512
+ <div
513
+ class={cn(
514
+ "fw-dev-rate-value",
515
+ playbackScore >= 0.95 && playbackScore <= 1.05
516
+ ? "fw-dev-stat-value--good"
517
+ : playbackScore > 1.05
518
+ ? "fw-dev-stat-value--accent"
519
+ : playbackScore >= 0.75
520
+ ? "fw-dev-stat-value--warn"
521
+ : "fw-dev-stat-value--bad"
522
+ )}
523
+ >
465
524
  {playbackScore.toFixed(2)}×
466
525
  </div>
467
526
  <div class="fw-dev-rate-status">
468
- {playbackScore >= 0.95 && playbackScore <= 1.05 ? 'realtime' :
469
- playbackScore > 1.05 ? 'catching up' :
470
- playbackScore >= 0.75 ? 'slightly slow' : 'stalling'}
527
+ {playbackScore >= 0.95 && playbackScore <= 1.05
528
+ ? "realtime"
529
+ : playbackScore > 1.05
530
+ ? "catching up"
531
+ : playbackScore >= 0.75
532
+ ? "slightly slow"
533
+ : "stalling"}
471
534
  </div>
472
535
  </div>
473
536
  <div class="fw-dev-rate-stats">
474
- <span class={qualityScore >= 75 ? 'fw-dev-stat-value--good' : 'fw-dev-stat-value--bad'}>Quality: {qualityScore}/100</span>
475
- <span class={stallCount === 0 ? 'fw-dev-stat-value--good' : 'fw-dev-stat-value--warn'}>Stalls: {stallCount}</span>
476
- <span class={frameDropRate < 1 ? 'fw-dev-stat-value--good' : 'fw-dev-stat-value--bad'}>Drops: {frameDropRate.toFixed(1)}%</span>
537
+ <span class={qualityScore >= 75 ? "fw-dev-stat-value--good" : "fw-dev-stat-value--bad"}
538
+ >Quality: {qualityScore}/100</span
539
+ >
540
+ <span class={stallCount === 0 ? "fw-dev-stat-value--good" : "fw-dev-stat-value--warn"}
541
+ >Stalls: {stallCount}</span
542
+ >
543
+ <span class={frameDropRate < 1 ? "fw-dev-stat-value--good" : "fw-dev-stat-value--bad"}
544
+ >Drops: {frameDropRate.toFixed(1)}%</span
545
+ >
477
546
  </div>
478
547
  </div>
479
548
 
480
549
  <!-- Video Stats -->
481
550
  {#if stats}
482
- <div class="fw-dev-stat"><span class="fw-dev-stat-label">Resolution</span><span class="fw-dev-stat-value">{stats.resolution}</span></div>
483
- <div class="fw-dev-stat"><span class="fw-dev-stat-label">Buffer</span><span class="fw-dev-stat-value">{stats.buffered}s</span></div>
484
- <div class="fw-dev-stat"><span class="fw-dev-stat-label">Playback Rate</span><span class="fw-dev-stat-value">{stats.playbackRate}x</span></div>
485
- <div class="fw-dev-stat"><span class="fw-dev-stat-label">Time</span><span class="fw-dev-stat-value">{stats.currentTime} / {stats.duration}</span></div>
486
- <div class="fw-dev-stat"><span class="fw-dev-stat-label">Ready State</span><span class="fw-dev-stat-value">{stats.readyState}</span></div>
487
- <div class="fw-dev-stat"><span class="fw-dev-stat-label">Network State</span><span class="fw-dev-stat-value">{stats.networkState}</span></div>
551
+ <div class="fw-dev-stat">
552
+ <span class="fw-dev-stat-label">Resolution</span><span class="fw-dev-stat-value"
553
+ >{stats.resolution}</span
554
+ >
555
+ </div>
556
+ <div class="fw-dev-stat">
557
+ <span class="fw-dev-stat-label">Buffer</span><span class="fw-dev-stat-value"
558
+ >{stats.buffered}s</span
559
+ >
560
+ </div>
561
+ <div class="fw-dev-stat">
562
+ <span class="fw-dev-stat-label">Playback Rate</span><span class="fw-dev-stat-value"
563
+ >{stats.playbackRate}x</span
564
+ >
565
+ </div>
566
+ <div class="fw-dev-stat">
567
+ <span class="fw-dev-stat-label">Time</span><span class="fw-dev-stat-value"
568
+ >{stats.currentTime} / {stats.duration}</span
569
+ >
570
+ </div>
571
+ <div class="fw-dev-stat">
572
+ <span class="fw-dev-stat-label">Ready State</span><span class="fw-dev-stat-value"
573
+ >{stats.readyState}</span
574
+ >
575
+ </div>
576
+ <div class="fw-dev-stat">
577
+ <span class="fw-dev-stat-label">Network State</span><span class="fw-dev-stat-value"
578
+ >{stats.networkState}</span
579
+ >
580
+ </div>
488
581
  {#if protocol}
489
- <div class="fw-dev-stat"><span class="fw-dev-stat-label">Protocol</span><span class="fw-dev-stat-value">{protocol}</span></div>
582
+ <div class="fw-dev-stat">
583
+ <span class="fw-dev-stat-label">Protocol</span><span class="fw-dev-stat-value"
584
+ >{protocol}</span
585
+ >
586
+ </div>
490
587
  {/if}
491
588
  {#if nodeId}
492
- <div class="fw-dev-stat"><span class="fw-dev-stat-label">Node ID</span><span class="fw-dev-stat-value">{nodeId}</span></div>
589
+ <div class="fw-dev-stat">
590
+ <span class="fw-dev-stat-label">Node ID</span><span class="fw-dev-stat-value"
591
+ >{nodeId}</span
592
+ >
593
+ </div>
493
594
  {/if}
494
595
  {:else}
495
596
  <div class="fw-dev-list-empty">No video element available</div>
@@ -499,34 +600,46 @@
499
600
  {#if playerStats}
500
601
  <div class="fw-dev-section fw-dev-section-header">
501
602
  <div class="fw-dev-label">
502
- {playerStats.type === 'hls' ? 'HLS.js Stats' :
503
- playerStats.type === 'webrtc' ? 'WebRTC Stats' : 'Player Stats'}
603
+ {playerStats.type === "hls"
604
+ ? "HLS.js Stats"
605
+ : playerStats.type === "webrtc"
606
+ ? "WebRTC Stats"
607
+ : "Player Stats"}
504
608
  </div>
505
609
  </div>
506
610
  <!-- HLS-specific stats -->
507
- {#if playerStats.type === 'hls'}
611
+ {#if playerStats.type === "hls"}
508
612
  <div class="fw-dev-stat">
509
613
  <span class="fw-dev-stat-label">Bitrate</span>
510
614
  <span class="fw-dev-stat-value fw-dev-stat-value--accent">
511
- {playerStats.currentBitrate > 0 ? `${Math.round(playerStats.currentBitrate / 1000)} kbps` : 'N/A'}
615
+ {playerStats.currentBitrate > 0
616
+ ? `${Math.round(playerStats.currentBitrate / 1000)} kbps`
617
+ : "N/A"}
512
618
  </span>
513
619
  </div>
514
620
  <div class="fw-dev-stat">
515
621
  <span class="fw-dev-stat-label">Bandwidth Est.</span>
516
622
  <span class="fw-dev-stat-value">
517
- {playerStats.bandwidthEstimate > 0 ? `${Math.round(playerStats.bandwidthEstimate / 1000)} kbps` : 'N/A'}
623
+ {playerStats.bandwidthEstimate > 0
624
+ ? `${Math.round(playerStats.bandwidthEstimate / 1000)} kbps`
625
+ : "N/A"}
518
626
  </span>
519
627
  </div>
520
628
  <div class="fw-dev-stat">
521
629
  <span class="fw-dev-stat-label">Level</span>
522
630
  <span class="fw-dev-stat-value">
523
- {playerStats.currentLevel >= 0 ? playerStats.currentLevel : 'Auto'} / {playerStats.levels?.length || 0}
631
+ {playerStats.currentLevel >= 0 ? playerStats.currentLevel : "Auto"} / {playerStats
632
+ .levels?.length || 0}
524
633
  </span>
525
634
  </div>
526
635
  {#if playerStats.latency !== undefined}
527
636
  <div class="fw-dev-stat">
528
637
  <span class="fw-dev-stat-label">Latency</span>
529
- <span class={playerStats.latency > 5000 ? 'fw-dev-stat-value fw-dev-stat-value--warn' : 'fw-dev-stat-value'}>
638
+ <span
639
+ class={playerStats.latency > 5000
640
+ ? "fw-dev-stat-value fw-dev-stat-value--warn"
641
+ : "fw-dev-stat-value"}
642
+ >
530
643
  {Math.round(playerStats.latency)} ms
531
644
  </span>
532
645
  </div>
@@ -534,48 +647,70 @@
534
647
  {/if}
535
648
 
536
649
  <!-- WebRTC-specific stats -->
537
- {#if playerStats.type === 'webrtc'}
650
+ {#if playerStats.type === "webrtc"}
538
651
  {#if playerStats.video}
539
652
  <div class="fw-dev-stat">
540
653
  <span class="fw-dev-stat-label">Video Bitrate</span>
541
654
  <span class="fw-dev-stat-value fw-dev-stat-value--accent">
542
- {playerStats.video.bitrate > 0 ? `${Math.round(playerStats.video.bitrate / 1000)} kbps` : 'N/A'}
655
+ {playerStats.video.bitrate > 0
656
+ ? `${Math.round(playerStats.video.bitrate / 1000)} kbps`
657
+ : "N/A"}
543
658
  </span>
544
659
  </div>
545
660
  <div class="fw-dev-stat">
546
661
  <span class="fw-dev-stat-label">FPS</span>
547
- <span class="fw-dev-stat-value">{Math.round(playerStats.video.framesPerSecond || 0)}</span>
662
+ <span class="fw-dev-stat-value"
663
+ >{Math.round(playerStats.video.framesPerSecond || 0)}</span
664
+ >
548
665
  </div>
549
666
  <div class="fw-dev-stat">
550
667
  <span class="fw-dev-stat-label">Frames</span>
551
668
  <span class="fw-dev-stat-value">
552
- {playerStats.video.framesDecoded} decoded,{' '}
553
- <span class={playerStats.video.frameDropRate > 1 ? 'fw-dev-stat-value--bad' : 'fw-dev-stat-value--good'}>
669
+ {playerStats.video.framesDecoded} decoded,{" "}
670
+ <span
671
+ class={playerStats.video.frameDropRate > 1
672
+ ? "fw-dev-stat-value--bad"
673
+ : "fw-dev-stat-value--good"}
674
+ >
554
675
  {playerStats.video.framesDropped} dropped
555
676
  </span>
556
677
  </span>
557
678
  </div>
558
679
  <div class="fw-dev-stat">
559
680
  <span class="fw-dev-stat-label">Packet Loss</span>
560
- <span class={playerStats.video.packetLossRate > 1 ? 'fw-dev-stat-value fw-dev-stat-value--bad' : 'fw-dev-stat-value fw-dev-stat-value--good'}>
681
+ <span
682
+ class={playerStats.video.packetLossRate > 1
683
+ ? "fw-dev-stat-value fw-dev-stat-value--bad"
684
+ : "fw-dev-stat-value fw-dev-stat-value--good"}
685
+ >
561
686
  {playerStats.video.packetLossRate?.toFixed(2) || 0}%
562
687
  </span>
563
688
  </div>
564
689
  <div class="fw-dev-stat">
565
690
  <span class="fw-dev-stat-label">Jitter</span>
566
- <span class={playerStats.video.jitter > 30 ? 'fw-dev-stat-value fw-dev-stat-value--warn' : 'fw-dev-stat-value'}>
691
+ <span
692
+ class={playerStats.video.jitter > 30
693
+ ? "fw-dev-stat-value fw-dev-stat-value--warn"
694
+ : "fw-dev-stat-value"}
695
+ >
567
696
  {playerStats.video.jitter?.toFixed(1) || 0} ms
568
697
  </span>
569
698
  </div>
570
699
  <div class="fw-dev-stat">
571
700
  <span class="fw-dev-stat-label">Jitter Buffer</span>
572
- <span class="fw-dev-stat-value">{playerStats.video.jitterBufferDelay?.toFixed(1) || 0} ms</span>
701
+ <span class="fw-dev-stat-value"
702
+ >{playerStats.video.jitterBufferDelay?.toFixed(1) || 0} ms</span
703
+ >
573
704
  </div>
574
705
  {/if}
575
706
  {#if playerStats.network}
576
707
  <div class="fw-dev-stat">
577
708
  <span class="fw-dev-stat-label">RTT</span>
578
- <span class={playerStats.network.rtt > 200 ? 'fw-dev-stat-value fw-dev-stat-value--warn' : 'fw-dev-stat-value'}>
709
+ <span
710
+ class={playerStats.network.rtt > 200
711
+ ? "fw-dev-stat-value fw-dev-stat-value--warn"
712
+ : "fw-dev-stat-value"}
713
+ >
579
714
  {Math.round(playerStats.network.rtt || 0)} ms
580
715
  </span>
581
716
  </div>
@@ -593,19 +728,23 @@
593
728
  {#each Object.entries(mistStreamInfo.meta.tracks) as [id, track]}
594
729
  <div class="fw-dev-track">
595
730
  <div class="fw-dev-track-header">
596
- <span class={cn(
597
- 'fw-dev-track-badge',
598
- track.type === 'video' ? 'fw-dev-track-badge--video' :
599
- track.type === 'audio' ? 'fw-dev-track-badge--audio' :
600
- 'fw-dev-track-badge--other'
601
- )}>
731
+ <span
732
+ class={cn(
733
+ "fw-dev-track-badge",
734
+ track.type === "video"
735
+ ? "fw-dev-track-badge--video"
736
+ : track.type === "audio"
737
+ ? "fw-dev-track-badge--audio"
738
+ : "fw-dev-track-badge--other"
739
+ )}
740
+ >
602
741
  {track.type}
603
742
  </span>
604
743
  <span class="fw-dev-track-codec">{track.codec}</span>
605
744
  <span class="fw-dev-track-id">#{id}</span>
606
745
  </div>
607
746
  <div class="fw-dev-track-meta">
608
- {#if track.type === 'video' && track.width && track.height}
747
+ {#if track.type === "video" && track.width && track.height}
609
748
  <span>{track.width}×{track.height}</span>
610
749
  {/if}
611
750
  {#if track.bps}
@@ -614,10 +753,10 @@
614
753
  {#if track.fpks}
615
754
  <span>{Math.round(track.fpks / 1000)} fps</span>
616
755
  {/if}
617
- {#if track.type === 'audio' && track.channels}
756
+ {#if track.type === "audio" && track.channels}
618
757
  <span>{track.channels}ch</span>
619
758
  {/if}
620
- {#if track.type === 'audio' && track.rate}
759
+ {#if track.type === "audio" && track.rate}
621
760
  <span>{track.rate} Hz</span>
622
761
  {/if}
623
762
  {#if track.lang}