@livepeer-frameworks/player-svelte 0.1.1 → 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 (88) hide show
  1. package/dist/DevModePanel.svelte +266 -127
  2. package/dist/DevModePanel.svelte.d.ts +1 -1
  3. package/dist/DvdLogo.svelte +17 -21
  4. package/dist/Icons.svelte +5 -3
  5. package/dist/Icons.svelte.d.ts +6 -19
  6. package/dist/IdleScreen.svelte +277 -186
  7. package/dist/IdleScreen.svelte.d.ts +1 -1
  8. package/dist/LoadingScreen.svelte +190 -162
  9. package/dist/Player.svelte +244 -111
  10. package/dist/Player.svelte.d.ts +1 -1
  11. package/dist/PlayerControls.svelte +263 -168
  12. package/dist/PlayerControls.svelte.d.ts +1 -1
  13. package/dist/SeekBar.svelte +61 -35
  14. package/dist/SkipIndicator.svelte +4 -4
  15. package/dist/SkipIndicator.svelte.d.ts +1 -1
  16. package/dist/SpeedIndicator.svelte +1 -1
  17. package/dist/StatsPanel.svelte +76 -57
  18. package/dist/StatsPanel.svelte.d.ts +1 -1
  19. package/dist/StreamStateOverlay.svelte +143 -107
  20. package/dist/StreamStateOverlay.svelte.d.ts +1 -1
  21. package/dist/SubtitleRenderer.svelte +46 -43
  22. package/dist/ThumbnailOverlay.svelte +22 -19
  23. package/dist/TitleOverlay.svelte +6 -11
  24. package/dist/components/VolumeIcons.svelte +12 -6
  25. package/dist/global.d.ts +3 -3
  26. package/dist/icons/FullscreenExitIcon.svelte +1 -5
  27. package/dist/icons/FullscreenIcon.svelte +1 -5
  28. package/dist/icons/PauseIcon.svelte +1 -5
  29. package/dist/icons/PictureInPictureIcon.svelte +12 -6
  30. package/dist/icons/PlayIcon.svelte +1 -5
  31. package/dist/icons/SeekToLiveIcon.svelte +1 -5
  32. package/dist/icons/SettingsIcon.svelte +1 -5
  33. package/dist/icons/SkipBackIcon.svelte +1 -5
  34. package/dist/icons/SkipForwardIcon.svelte +1 -5
  35. package/dist/icons/StatsIcon.svelte +1 -5
  36. package/dist/icons/VolumeOffIcon.svelte +1 -5
  37. package/dist/icons/VolumeUpIcon.svelte +1 -5
  38. package/dist/icons/index.d.ts +12 -12
  39. package/dist/icons/index.js +12 -12
  40. package/dist/index.d.ts +24 -24
  41. package/dist/index.js +21 -21
  42. package/dist/stores/index.d.ts +6 -6
  43. package/dist/stores/index.js +6 -6
  44. package/dist/stores/playbackQuality.d.ts +2 -2
  45. package/dist/stores/playbackQuality.js +7 -7
  46. package/dist/stores/playerContext.d.ts +2 -2
  47. package/dist/stores/playerContext.js +17 -17
  48. package/dist/stores/playerController.d.ts +13 -4
  49. package/dist/stores/playerController.js +80 -56
  50. package/dist/stores/playerSelection.d.ts +2 -2
  51. package/dist/stores/playerSelection.js +7 -7
  52. package/dist/stores/streamState.d.ts +2 -2
  53. package/dist/stores/streamState.js +56 -56
  54. package/dist/stores/viewerEndpoints.d.ts +3 -3
  55. package/dist/stores/viewerEndpoints.js +21 -21
  56. package/dist/types.d.ts +1 -1
  57. package/dist/ui/Badge.svelte +9 -10
  58. package/dist/ui/Badge.svelte.d.ts +8 -29
  59. package/dist/ui/Button.svelte +16 -16
  60. package/dist/ui/Button.svelte.d.ts +8 -29
  61. package/dist/ui/Slider.svelte +21 -55
  62. package/dist/ui/badge.js +1 -1
  63. package/dist/ui/button.js +2 -2
  64. package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte +5 -7
  65. package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte.d.ts +6 -27
  66. package/dist/ui/context-menu/ContextMenuContent.svelte +2 -9
  67. package/dist/ui/context-menu/ContextMenuItem.svelte +1 -5
  68. package/dist/ui/context-menu/ContextMenuLabel.svelte +1 -5
  69. package/dist/ui/context-menu/ContextMenuRadioItem.svelte +5 -7
  70. package/dist/ui/context-menu/ContextMenuRadioItem.svelte.d.ts +6 -27
  71. package/dist/ui/context-menu/ContextMenuSeparator.svelte +2 -8
  72. package/dist/ui/context-menu/ContextMenuShortcut.svelte +2 -12
  73. package/dist/ui/context-menu/ContextMenuSubContent.svelte +1 -5
  74. package/package.json +15 -7
  75. package/src/DevModePanel.svelte +1 -0
  76. package/src/Icons.svelte +5 -3
  77. package/src/IdleScreen.svelte +21 -14
  78. package/src/LoadingScreen.svelte +20 -13
  79. package/src/Player.svelte +48 -2
  80. package/src/PlayerControls.svelte +36 -17
  81. package/src/SeekBar.svelte +33 -0
  82. package/src/StreamStateOverlay.svelte +2 -2
  83. package/src/stores/playerController.ts +39 -1
  84. package/src/stores/viewerEndpoints.ts +1 -1
  85. package/src/ui/Badge.svelte +7 -4
  86. package/src/ui/Button.svelte +13 -13
  87. package/src/ui/context-menu/ContextMenuCheckboxItem.svelte +4 -2
  88. package/src/ui/context-menu/ContextMenuRadioItem.svelte +4 -2
@@ -1,4 +1,4 @@
1
- import { type MistStreamInfo, type PlaybackMode } from '@livepeer-frameworks/player-core';
1
+ import { type MistStreamInfo, type PlaybackMode } from "@livepeer-frameworks/player-core";
2
2
  interface Props {
3
3
  currentTime: number;
4
4
  duration: number;
@@ -3,7 +3,7 @@
3
3
  Port of src/components/SeekBar.tsx
4
4
  -->
5
5
  <script lang="ts">
6
- import { cn } from '@livepeer-frameworks/player-core';
6
+ import { cn } from "@livepeer-frameworks/player-core";
7
7
 
8
8
  interface Props {
9
9
  /** Current playback time in seconds */
@@ -34,7 +34,7 @@
34
34
  buffered = undefined,
35
35
  disabled = false,
36
36
  onseek = undefined,
37
- class: className = '',
37
+ class: className = "",
38
38
  isLive = false,
39
39
  seekableStart = 0,
40
40
  liveEdge = undefined,
@@ -96,29 +96,29 @@
96
96
 
97
97
  // Format time as MM:SS or HH:MM:SS
98
98
  function formatTime(seconds: number): string {
99
- if (!Number.isFinite(seconds) || seconds < 0) return '0:00';
99
+ if (!Number.isFinite(seconds) || seconds < 0) return "0:00";
100
100
  const total = Math.floor(seconds);
101
101
  const hours = Math.floor(total / 3600);
102
102
  const minutes = Math.floor((total % 3600) / 60);
103
103
  const secs = total % 60;
104
104
  if (hours > 0) {
105
- return `${hours}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
105
+ return `${hours}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
106
106
  }
107
- return `${minutes}:${String(secs).padStart(2, '0')}`;
107
+ return `${minutes}:${String(secs).padStart(2, "0")}`;
108
108
  }
109
109
 
110
110
  // Format relative time for live streams
111
111
  function formatLiveTime(seconds: number, edge: number): string {
112
112
  const behindSeconds = edge - seconds;
113
- if (behindSeconds < 1) return 'LIVE';
113
+ if (behindSeconds < 1) return "LIVE";
114
114
  const total = Math.floor(behindSeconds);
115
115
  const hours = Math.floor(total / 3600);
116
116
  const minutes = Math.floor((total % 3600) / 60);
117
117
  const secs = total % 60;
118
118
  if (hours > 0) {
119
- return `-${hours}:${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
119
+ return `-${hours}:${String(minutes).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
120
120
  }
121
- return `-${minutes}:${String(secs).padStart(2, '0')}`;
121
+ return `-${minutes}:${String(secs).padStart(2, "0")}`;
122
122
  }
123
123
 
124
124
  // Calculate time from mouse position
@@ -130,7 +130,7 @@
130
130
 
131
131
  // Live with valid seekable window
132
132
  if (isLive && Number.isFinite(seekableWindow) && seekableWindow > 0) {
133
- return seekableStart + (percent * seekableWindow);
133
+ return seekableStart + percent * seekableWindow;
134
134
  }
135
135
 
136
136
  // VOD with finite duration
@@ -144,7 +144,7 @@
144
144
  const start = Number.isFinite(seekableStart) ? seekableStart : 0;
145
145
  const window = liveEdge - start;
146
146
  if (window > 0) {
147
- return start + (percent * window);
147
+ return start + percent * window;
148
148
  }
149
149
  }
150
150
 
@@ -189,16 +189,16 @@
189
189
 
190
190
  const handleDragEnd = () => {
191
191
  isDragging = false;
192
- document.removeEventListener('mousemove', handleDragMove);
193
- document.removeEventListener('mouseup', handleDragEnd);
192
+ document.removeEventListener("mousemove", handleDragMove);
193
+ document.removeEventListener("mouseup", handleDragEnd);
194
194
  if (commitOnRelease && dragTime !== null) {
195
195
  onseek?.(dragTime);
196
196
  dragTime = null;
197
197
  }
198
198
  };
199
199
 
200
- document.addEventListener('mousemove', handleDragMove);
201
- document.addEventListener('mouseup', handleDragEnd);
200
+ document.addEventListener("mousemove", handleDragMove);
201
+ document.addEventListener("mouseup", handleDragEnd);
202
202
 
203
203
  // Initial seek
204
204
  const time = getTimeFromPosition(e.clientX);
@@ -211,34 +211,69 @@
211
211
 
212
212
  let showThumb = $derived(isHovering || isDragging);
213
213
  let canShowTooltip = $derived(isLive ? seekableWindow > 0 : Number.isFinite(duration));
214
- let ariaValueText = $derived(isLive ? formatLiveTime(displayTime, effectiveLiveEdge) : formatTime(displayTime));
214
+ let ariaValueText = $derived(
215
+ isLive ? formatLiveTime(displayTime, effectiveLiveEdge) : formatTime(displayTime)
216
+ );
217
+
218
+ // Handle keyboard navigation for accessibility
219
+ function handleKeyDown(e: KeyboardEvent) {
220
+ if (disabled) return;
221
+ const step = e.shiftKey ? 10 : 5; // 5s default, 10s with shift
222
+ const rangeEnd = isLive ? effectiveLiveEdge : duration;
223
+ const rangeStart = isLive ? seekableStart : 0;
224
+
225
+ let newTime: number | null = null;
226
+ switch (e.key) {
227
+ case "ArrowLeft":
228
+ case "ArrowDown":
229
+ newTime = Math.max(rangeStart, currentTime - step);
230
+ break;
231
+ case "ArrowRight":
232
+ case "ArrowUp":
233
+ newTime = Math.min(rangeEnd, currentTime + step);
234
+ break;
235
+ case "Home":
236
+ newTime = rangeStart;
237
+ break;
238
+ case "End":
239
+ newTime = rangeEnd;
240
+ break;
241
+ default:
242
+ return;
243
+ }
244
+ if (newTime !== null) {
245
+ e.preventDefault();
246
+ onseek?.(newTime);
247
+ }
248
+ }
215
249
  </script>
216
250
 
217
251
  <div
218
252
  bind:this={trackRef}
219
253
  class={cn(
220
- 'group relative w-full h-6 flex items-center cursor-pointer',
221
- disabled && 'opacity-50 cursor-not-allowed',
254
+ "group relative w-full h-6 flex items-center cursor-pointer",
255
+ disabled && "opacity-50 cursor-not-allowed",
222
256
  className
223
257
  )}
224
258
  onmouseenter={() => !disabled && (isHovering = true)}
225
- onmouseleave={() => { isHovering = false; isDragging = false; }}
259
+ onmouseleave={() => {
260
+ isHovering = false;
261
+ isDragging = false;
262
+ }}
226
263
  onmousemove={handleMouseMove}
227
264
  onclick={handleClick}
228
265
  onmousedown={handleMouseDown}
266
+ onkeydown={handleKeyDown}
229
267
  role="slider"
230
268
  aria-label="Seek"
231
269
  aria-valuemin={isLive ? seekableStart : 0}
232
- aria-valuemax={isLive ? effectiveLiveEdge : (duration || 100)}
270
+ aria-valuemax={isLive ? effectiveLiveEdge : duration || 100}
233
271
  aria-valuenow={displayTime}
234
272
  aria-valuetext={ariaValueText}
235
273
  tabindex={disabled ? -1 : 0}
236
274
  >
237
275
  <!-- Track background -->
238
- <div class={cn(
239
- 'fw-seek-track',
240
- isDragging && 'fw-seek-track--active'
241
- )}>
276
+ <div class={cn("fw-seek-track", isDragging && "fw-seek-track--active")}>
242
277
  <!-- Buffered segments -->
243
278
  {#each bufferedSegments as segment, _index}
244
279
  <div
@@ -247,27 +282,18 @@
247
282
  ></div>
248
283
  {/each}
249
284
  <!-- Playback progress -->
250
- <div
251
- class="fw-seek-progress"
252
- style="width: {progressPercent}%;"
253
- ></div>
285
+ <div class="fw-seek-progress" style="width: {progressPercent}%;"></div>
254
286
  </div>
255
287
 
256
288
  <!-- Thumb -->
257
289
  <div
258
- class={cn(
259
- 'fw-seek-thumb',
260
- showThumb ? 'fw-seek-thumb--active' : 'fw-seek-thumb--hidden'
261
- )}
290
+ class={cn("fw-seek-thumb", showThumb ? "fw-seek-thumb--active" : "fw-seek-thumb--hidden")}
262
291
  style="left: {progressPercent}%;"
263
292
  ></div>
264
293
 
265
294
  <!-- Hover time tooltip -->
266
295
  {#if isHovering && !isDragging && canShowTooltip}
267
- <div
268
- class="fw-seek-tooltip"
269
- style="left: {hoverPosition}%;"
270
- >
296
+ <div class="fw-seek-tooltip" style="left: {hoverPosition}%;">
271
297
  {isLive ? formatLiveTime(hoverTime, effectiveLiveEdge) : formatTime(hoverTime)}
272
298
  </div>
273
299
  {/if}
@@ -1,16 +1,16 @@
1
1
  <script lang="ts">
2
- import { onMount } from 'svelte';
2
+ import { onMount } from "svelte";
3
3
 
4
4
  /**
5
5
  * Skip indicator overlay that appears when double-tapping to skip.
6
6
  * Shows the skip direction and amount (e.g., "-10s" or "+10s") with a ripple effect.
7
7
  */
8
- export type SkipDirection = 'back' | 'forward' | null;
8
+ export type SkipDirection = "back" | "forward" | null;
9
9
 
10
10
  let {
11
11
  direction = null as SkipDirection,
12
12
  seconds = 10,
13
- class: className = '',
13
+ class: className = "",
14
14
  onhide = undefined as (() => void) | undefined,
15
15
  }: {
16
16
  direction: SkipDirection;
@@ -46,7 +46,7 @@
46
46
  };
47
47
  });
48
48
 
49
- let isBack = $derived(direction === 'back');
49
+ let isBack = $derived(direction === "back");
50
50
  </script>
51
51
 
52
52
  {#if direction}
@@ -2,7 +2,7 @@
2
2
  * Skip indicator overlay that appears when double-tapping to skip.
3
3
  * Shows the skip direction and amount (e.g., "-10s" or "+10s") with a ripple effect.
4
4
  */
5
- export type SkipDirection = 'back' | 'forward' | null;
5
+ export type SkipDirection = "back" | "forward" | null;
6
6
  type $$ComponentProps = {
7
7
  direction: SkipDirection;
8
8
  seconds?: number;
@@ -6,7 +6,7 @@
6
6
  let {
7
7
  isVisible = false,
8
8
  speed = 2,
9
- class: className = '',
9
+ class: className = "",
10
10
  }: {
11
11
  isVisible: boolean;
12
12
  speed: number;
@@ -3,8 +3,13 @@
3
3
  Port of src/components/StatsPanel.tsx
4
4
  -->
5
5
  <script lang="ts">
6
- import { cn, type ContentMetadata, type PlaybackQuality, type StreamState } from '@livepeer-frameworks/player-core';
7
- import Button from './ui/Button.svelte';
6
+ import {
7
+ cn,
8
+ type ContentMetadata,
9
+ type PlaybackQuality,
10
+ type StreamState,
11
+ } from "@livepeer-frameworks/player-core";
12
+ import Button from "./ui/Button.svelte";
8
13
 
9
14
  interface Props {
10
15
  isOpen: boolean;
@@ -31,23 +36,29 @@
31
36
  }: Props = $props();
32
37
 
33
38
  // Video element stats (reactive)
34
- let currentRes = $derived(videoElement ? `${videoElement.videoWidth}x${videoElement.videoHeight}` : '—');
39
+ let currentRes = $derived(
40
+ videoElement ? `${videoElement.videoWidth}x${videoElement.videoHeight}` : "—"
41
+ );
35
42
  let buffered = $derived.by(() => {
36
- if (!videoElement || videoElement.buffered.length === 0) return '';
37
- return (videoElement.buffered.end(videoElement.buffered.length - 1) - videoElement.currentTime).toFixed(1);
43
+ if (!videoElement || videoElement.buffered.length === 0) return "";
44
+ return (
45
+ videoElement.buffered.end(videoElement.buffered.length - 1) - videoElement.currentTime
46
+ ).toFixed(1);
38
47
  });
39
- let playbackRate = $derived(videoElement?.playbackRate?.toFixed(2) ?? '1.00');
48
+ let playbackRate = $derived(videoElement?.playbackRate?.toFixed(2) ?? "1.00");
40
49
 
41
50
  // Quality monitor stats
42
- let qualityScore = $derived(quality?.score?.toFixed(0) ?? '');
43
- let bitrateKbps = $derived(quality?.bitrate ? `${(quality.bitrate / 1000).toFixed(0)} kbps` : '—');
44
- let frameDropRate = $derived(quality?.frameDropRate?.toFixed(1) ?? '');
51
+ let qualityScore = $derived(quality?.score?.toFixed(0) ?? "");
52
+ let bitrateKbps = $derived(
53
+ quality?.bitrate ? `${(quality.bitrate / 1000).toFixed(0)} kbps` : ""
54
+ );
55
+ let frameDropRate = $derived(quality?.frameDropRate?.toFixed(1) ?? "—");
45
56
  let stallCount = $derived(quality?.stallCount ?? 0);
46
- let latency = $derived(quality?.latency ? `${Math.round(quality.latency)} ms` : '');
57
+ let latency = $derived(quality?.latency ? `${Math.round(quality.latency)} ms` : "");
47
58
 
48
59
  // Stream state stats
49
- let viewers = $derived(metadata?.viewers ?? '');
50
- let streamStatus = $derived(streamState?.status ?? metadata?.status ?? '');
60
+ let viewers = $derived(metadata?.viewers ?? "");
61
+ let streamStatus = $derived(streamState?.status ?? metadata?.status ?? "");
51
62
 
52
63
  const mistInfo = $derived(metadata?.mist ?? streamState?.streamInfo);
53
64
 
@@ -59,8 +70,8 @@
59
70
  codec: t.codec,
60
71
  width: t.width,
61
72
  height: t.height,
62
- bitrate: typeof t.bps === 'number' ? Math.round(t.bps) : undefined,
63
- fps: typeof t.fpks === 'number' ? t.fpks / 1000 : undefined,
73
+ bitrate: typeof t.bps === "number" ? Math.round(t.bps) : undefined,
74
+ fps: typeof t.fpks === "number" ? t.fpks / 1000 : undefined,
64
75
  channels: t.channels,
65
76
  sampleRate: t.rate,
66
77
  }));
@@ -69,16 +80,18 @@
69
80
  // Format track info
70
81
  function formatTracks(): string {
71
82
  const tracks = metadata?.tracks ?? deriveTracksFromMist();
72
- if (!tracks?.length) return '';
73
- return tracks.map(t => {
74
- if (t.type === 'video') {
75
- const resolution = t.width && t.height ? `${t.width}x${t.height}` : '?';
76
- const bitrate = t.bitrate ? `${Math.round(t.bitrate / 1000)}kbps` : '?';
77
- return `${t.codec ?? '?'} ${resolution}@${bitrate}`;
78
- }
79
- const channels = t.channels ? `${t.channels}ch` : '?';
80
- return `${t.codec ?? '?'} ${channels}`;
81
- }).join(', ');
83
+ if (!tracks?.length) return "";
84
+ return tracks
85
+ .map((t) => {
86
+ if (t.type === "video") {
87
+ const resolution = t.width && t.height ? `${t.width}x${t.height}` : "?";
88
+ const bitrate = t.bitrate ? `${Math.round(t.bitrate / 1000)}kbps` : "?";
89
+ return `${t.codec ?? "?"} ${resolution}@${bitrate}`;
90
+ }
91
+ const channels = t.channels ? `${t.channels}ch` : "?";
92
+ return `${t.codec ?? "?"} ${channels}`;
93
+ })
94
+ .join(", ");
82
95
  }
83
96
 
84
97
  // Build stats array
@@ -86,44 +99,45 @@
86
99
  const result: Array<{ label: string; value: string }> = [];
87
100
 
88
101
  if (metadata?.title) {
89
- result.push({ label: 'Title', value: metadata.title });
102
+ result.push({ label: "Title", value: metadata.title });
90
103
  }
91
104
 
92
105
  result.push(
93
- { label: 'Resolution', value: currentRes },
94
- { label: 'Buffer', value: `${buffered}s` },
95
- { label: 'Latency', value: latency },
96
- { label: 'Bitrate', value: bitrateKbps },
97
- { label: 'Quality Score', value: `${qualityScore}/100` },
98
- { label: 'Frame Drop Rate', value: `${frameDropRate}%` },
99
- { label: 'Stalls', value: String(stallCount) },
100
- { label: 'Playback Rate', value: `${playbackRate}x` },
101
- { label: 'Protocol', value: protocol ?? '' },
102
- { label: 'Node', value: nodeId ?? '' },
103
- { label: 'Geo Distance', value: geoDistance ? `${geoDistance.toFixed(0)} km` : '' },
104
- { label: 'Viewers', value: String(viewers) },
105
- { label: 'Status', value: streamStatus },
106
- { label: 'Tracks', value: formatTracks() },
107
- { label: 'Mist Type', value: mistInfo?.type ?? '' },
106
+ { label: "Resolution", value: currentRes },
107
+ { label: "Buffer", value: `${buffered}s` },
108
+ { label: "Latency", value: latency },
109
+ { label: "Bitrate", value: bitrateKbps },
110
+ { label: "Quality Score", value: `${qualityScore}/100` },
111
+ { label: "Frame Drop Rate", value: `${frameDropRate}%` },
112
+ { label: "Stalls", value: String(stallCount) },
113
+ { label: "Playback Rate", value: `${playbackRate}x` },
114
+ { label: "Protocol", value: protocol ?? "" },
115
+ { label: "Node", value: nodeId ?? "" },
116
+ { label: "Geo Distance", value: geoDistance ? `${geoDistance.toFixed(0)} km` : "" },
117
+ { label: "Viewers", value: String(viewers) },
118
+ { label: "Status", value: streamStatus },
119
+ { label: "Tracks", value: formatTracks() },
120
+ { label: "Mist Type", value: mistInfo?.type ?? "" },
108
121
  {
109
- label: 'Mist Buffer Window',
110
- value: mistInfo?.meta?.buffer_window != null
111
- ? String(mistInfo.meta.buffer_window)
112
- : '—',
122
+ label: "Mist Buffer Window",
123
+ value: mistInfo?.meta?.buffer_window != null ? String(mistInfo.meta.buffer_window) : "—",
113
124
  },
114
- { label: 'Mist Lastms', value: mistInfo?.lastms != null ? String(mistInfo.lastms) : '' },
115
- { label: 'Mist Unixoffset', value: mistInfo?.unixoffset != null ? String(mistInfo.unixoffset) : '—' },
125
+ { label: "Mist Lastms", value: mistInfo?.lastms != null ? String(mistInfo.lastms) : "" },
126
+ {
127
+ label: "Mist Unixoffset",
128
+ value: mistInfo?.unixoffset != null ? String(mistInfo.unixoffset) : "—",
129
+ }
116
130
  );
117
131
 
118
132
  if (metadata?.durationSeconds) {
119
133
  const mins = Math.floor(metadata.durationSeconds / 60);
120
134
  const secs = metadata.durationSeconds % 60;
121
- result.push({ label: 'Duration', value: `${mins}:${String(secs).padStart(2, '0')}` });
135
+ result.push({ label: "Duration", value: `${mins}:${String(secs).padStart(2, "0")}` });
122
136
  }
123
137
 
124
138
  if (metadata?.recordingSizeBytes) {
125
139
  const mb = (metadata.recordingSizeBytes / (1024 * 1024)).toFixed(1);
126
- result.push({ label: 'Size', value: `${mb} MB` });
140
+ result.push({ label: "Size", value: `${mb} MB` });
127
141
  }
128
142
 
129
143
  return result;
@@ -133,19 +147,17 @@
133
147
  {#if isOpen}
134
148
  <div
135
149
  class={cn(
136
- 'fw-stats-panel absolute top-2 right-2 z-30',
137
- 'bg-black border border-white/10 rounded',
138
- 'text-white text-xs font-mono',
139
- 'max-w-[320px] max-h-[80%] overflow-auto',
140
- 'shadow-lg'
150
+ "fw-stats-panel absolute top-2 right-2 z-30",
151
+ "bg-black border border-white/10 rounded",
152
+ "text-white text-xs font-mono",
153
+ "max-w-[320px] max-h-[80%] overflow-auto",
154
+ "shadow-lg"
141
155
  )}
142
156
  style="background-color: #000000;"
143
157
  >
144
158
  <!-- Header -->
145
159
  <div class="flex items-center justify-between px-3 py-2 border-b border-white/10">
146
- <span class="text-white/70 text-[10px] uppercase tracking-wider">
147
- Stats Overlay
148
- </span>
160
+ <span class="text-white/70 text-[10px] uppercase tracking-wider"> Stats Overlay </span>
149
161
  <Button
150
162
  type="button"
151
163
  variant="ghost"
@@ -153,7 +165,14 @@
153
165
  class="text-white/50 hover:text-white transition-colors p-1 -mr-1 h-auto w-auto min-w-0"
154
166
  aria-label="Close stats panel"
155
167
  >
156
- <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">
168
+ <svg
169
+ width="12"
170
+ height="12"
171
+ viewBox="0 0 12 12"
172
+ fill="none"
173
+ stroke="currentColor"
174
+ stroke-width="1.5"
175
+ >
157
176
  <path d="M2 2l8 8M10 2l-8 8" />
158
177
  </svg>
159
178
  </Button>
@@ -1,4 +1,4 @@
1
- import { type ContentMetadata, type PlaybackQuality, type StreamState } from '@livepeer-frameworks/player-core';
1
+ import { type ContentMetadata, type PlaybackQuality, type StreamState } from "@livepeer-frameworks/player-core";
2
2
  interface Props {
3
3
  isOpen: boolean;
4
4
  onClose: () => void;