@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
@@ -10,7 +10,7 @@
10
10
  - Retry button for errors
11
11
  -->
12
12
  <script lang="ts">
13
- import type { StreamStatus } from '@livepeer-frameworks/player-core';
13
+ import type { StreamStatus } from "@livepeer-frameworks/player-core";
14
14
 
15
15
  interface Props {
16
16
  /** Current stream status */
@@ -33,29 +33,149 @@
33
33
  percentage,
34
34
  onRetry,
35
35
  visible = true,
36
- class: className = '',
36
+ class: className = "",
37
37
  }: Props = $props();
38
38
 
39
39
  // Computed states
40
- let showRetry = $derived(status === 'ERROR' || status === 'INVALID' || status === 'OFFLINE');
41
- let showProgress = $derived(status === 'INITIALIZING' && percentage !== undefined);
40
+ let showRetry = $derived(status === "ERROR" || status === "INVALID" || status === "OFFLINE");
41
+ let showProgress = $derived(status === "INITIALIZING" && percentage !== undefined);
42
42
 
43
43
  // Get status label for header
44
44
  function getStatusLabel(status: StreamStatus): string {
45
45
  switch (status) {
46
- case 'ONLINE': return 'ONLINE';
47
- case 'OFFLINE': return 'OFFLINE';
48
- case 'INITIALIZING': return 'INITIALIZING';
49
- case 'BOOTING': return 'STARTING';
50
- case 'WAITING_FOR_DATA': return 'WAITING';
51
- case 'SHUTTING_DOWN': return 'ENDING';
52
- case 'ERROR': return 'ERROR';
53
- case 'INVALID': return 'INVALID';
54
- default: return 'STATUS';
46
+ case "ONLINE":
47
+ return "ONLINE";
48
+ case "OFFLINE":
49
+ return "OFFLINE";
50
+ case "INITIALIZING":
51
+ return "INITIALIZING";
52
+ case "BOOTING":
53
+ return "STARTING";
54
+ case "WAITING_FOR_DATA":
55
+ return "WAITING";
56
+ case "SHUTTING_DOWN":
57
+ return "ENDING";
58
+ case "ERROR":
59
+ return "ERROR";
60
+ case "INVALID":
61
+ return "INVALID";
62
+ default:
63
+ return "STATUS";
55
64
  }
56
65
  }
57
66
  </script>
58
67
 
68
+ {#if visible && status !== "ONLINE"}
69
+ <div class="overlay-backdrop {className}" role="status" aria-live="polite">
70
+ <div class="slab">
71
+ <!-- Slab header - status label with icon -->
72
+ <div class="slab-header">
73
+ <!-- Status Icon -->
74
+ {#if status === "OFFLINE"}
75
+ <svg class="icon icon-offline" fill="none" viewBox="0 0 24 24" stroke="currentColor">
76
+ <path
77
+ stroke-linecap="round"
78
+ stroke-linejoin="round"
79
+ stroke-width="2"
80
+ d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414"
81
+ />
82
+ </svg>
83
+ {:else if status === "INITIALIZING" || status === "BOOTING" || status === "WAITING_FOR_DATA"}
84
+ <svg class="icon icon-warning animate-spin" fill="none" viewBox="0 0 24 24">
85
+ <circle
86
+ class="opacity-25"
87
+ cx="12"
88
+ cy="12"
89
+ r="10"
90
+ stroke="currentColor"
91
+ stroke-width="4"
92
+ />
93
+ <path
94
+ class="opacity-75"
95
+ fill="currentColor"
96
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
97
+ />
98
+ </svg>
99
+ {:else if status === "SHUTTING_DOWN"}
100
+ <svg class="icon icon-warning" fill="none" viewBox="0 0 24 24" stroke="currentColor">
101
+ <path
102
+ stroke-linecap="round"
103
+ stroke-linejoin="round"
104
+ stroke-width="2"
105
+ d="M13 10V3L4 14h7v7l9-11h-7z"
106
+ />
107
+ </svg>
108
+ {:else}
109
+ <!-- ERROR or INVALID -->
110
+ <svg class="icon icon-offline" fill="none" viewBox="0 0 24 24" stroke="currentColor">
111
+ <path
112
+ stroke-linecap="round"
113
+ stroke-linejoin="round"
114
+ stroke-width="2"
115
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
116
+ />
117
+ </svg>
118
+ {/if}
119
+
120
+ <span>{getStatusLabel(status)}</span>
121
+ </div>
122
+
123
+ <!-- Slab body - message and progress -->
124
+ <div class="slab-body">
125
+ <p style="font-size: 0.875rem; color: hsl(var(--tn-fg, 233 23% 75%));">
126
+ {message}
127
+ </p>
128
+
129
+ {#if showProgress && percentage !== undefined}
130
+ <div style="margin-top: 0.75rem;">
131
+ <div class="progress-bar">
132
+ <div class="progress-fill" style="width: {Math.min(100, percentage)};"></div>
133
+ </div>
134
+ <p
135
+ style="margin-top: 0.375rem; font-size: 0.75rem; font-family: monospace; color: hsl(var(--tn-fg-dark, 233 23% 60%));"
136
+ >
137
+ {Math.round(percentage)}%
138
+ </p>
139
+ </div>
140
+ {/if}
141
+
142
+ {#if status === "OFFLINE"}
143
+ <p
144
+ style="margin-top: 0.5rem; font-size: 0.75rem; color: hsl(var(--tn-fg-dark, 233 23% 60%));"
145
+ >
146
+ The stream will start when the broadcaster goes live
147
+ </p>
148
+ {/if}
149
+
150
+ {#if status === "BOOTING" || status === "WAITING_FOR_DATA"}
151
+ <p
152
+ style="margin-top: 0.5rem; font-size: 0.75rem; color: hsl(var(--tn-fg-dark, 233 23% 60%));"
153
+ >
154
+ Please wait while the stream prepares...
155
+ </p>
156
+ {/if}
157
+
158
+ <!-- Polling indicator for non-error states -->
159
+ {#if !showRetry}
160
+ <div class="polling-indicator">
161
+ <span class="polling-dot"></span>
162
+ <span>Checking stream status...</span>
163
+ </div>
164
+ {/if}
165
+ </div>
166
+
167
+ <!-- Slab actions - flush retry button -->
168
+ {#if showRetry && onRetry}
169
+ <div class="slab-actions">
170
+ <button type="button" class="btn-flush" onclick={onRetry} aria-label="Retry connection">
171
+ Retry Connection
172
+ </button>
173
+ </div>
174
+ {/if}
175
+ </div>
176
+ </div>
177
+ {/if}
178
+
59
179
  <style>
60
180
  .fw-player-root .overlay-backdrop {
61
181
  position: absolute;
@@ -161,106 +281,22 @@
161
281
  }
162
282
 
163
283
  @keyframes pulse {
164
- 0%, 100% { opacity: 1; }
165
- 50% { opacity: 0.5; }
284
+ 0%,
285
+ 100% {
286
+ opacity: 1;
287
+ }
288
+ 50% {
289
+ opacity: 0.5;
290
+ }
166
291
  }
167
292
 
168
293
  @keyframes spin {
169
- to { transform: rotate(360deg); }
294
+ to {
295
+ transform: rotate(360deg);
296
+ }
170
297
  }
171
298
 
172
299
  .fw-player-root .animate-spin {
173
300
  animation: spin 1s linear infinite;
174
301
  }
175
302
  </style>
176
-
177
- {#if visible && status !== 'ONLINE'}
178
- <div
179
- class="overlay-backdrop {className}"
180
- role="status"
181
- aria-live="polite"
182
- >
183
- <div class="slab">
184
- <!-- Slab header - status label with icon -->
185
- <div class="slab-header">
186
- <!-- Status Icon -->
187
- {#if status === 'OFFLINE'}
188
- <svg class="icon icon-offline" fill="none" viewBox="0 0 24 24" stroke="currentColor">
189
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" />
190
- </svg>
191
- {:else if status === 'INITIALIZING' || status === 'BOOTING' || status === 'WAITING_FOR_DATA'}
192
- <svg class="icon icon-warning animate-spin" fill="none" viewBox="0 0 24 24">
193
- <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
194
- <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
195
- </svg>
196
- {:else if status === 'SHUTTING_DOWN'}
197
- <svg class="icon icon-warning" fill="none" viewBox="0 0 24 24" stroke="currentColor">
198
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
199
- </svg>
200
- {:else}
201
- <!-- ERROR or INVALID -->
202
- <svg class="icon icon-offline" fill="none" viewBox="0 0 24 24" stroke="currentColor">
203
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
204
- </svg>
205
- {/if}
206
-
207
- <span>{getStatusLabel(status)}</span>
208
- </div>
209
-
210
- <!-- Slab body - message and progress -->
211
- <div class="slab-body">
212
- <p style="font-size: 0.875rem; color: hsl(var(--tn-fg, 233 23% 75%));">
213
- {message}
214
- </p>
215
-
216
- {#if showProgress && percentage !== undefined}
217
- <div style="margin-top: 0.75rem;">
218
- <div class="progress-bar">
219
- <div
220
- class="progress-fill"
221
- style="width: {Math.min(100, percentage)}%;"
222
- />
223
- </div>
224
- <p style="margin-top: 0.375rem; font-size: 0.75rem; font-family: monospace; color: hsl(var(--tn-fg-dark, 233 23% 60%));">
225
- {Math.round(percentage)}%
226
- </p>
227
- </div>
228
- {/if}
229
-
230
- {#if status === 'OFFLINE'}
231
- <p style="margin-top: 0.5rem; font-size: 0.75rem; color: hsl(var(--tn-fg-dark, 233 23% 60%));">
232
- The stream will start when the broadcaster goes live
233
- </p>
234
- {/if}
235
-
236
- {#if status === 'BOOTING' || status === 'WAITING_FOR_DATA'}
237
- <p style="margin-top: 0.5rem; font-size: 0.75rem; color: hsl(var(--tn-fg-dark, 233 23% 60%));">
238
- Please wait while the stream prepares...
239
- </p>
240
- {/if}
241
-
242
- <!-- Polling indicator for non-error states -->
243
- {#if !showRetry}
244
- <div class="polling-indicator">
245
- <span class="polling-dot" />
246
- <span>Checking stream status...</span>
247
- </div>
248
- {/if}
249
- </div>
250
-
251
- <!-- Slab actions - flush retry button -->
252
- {#if showRetry && onRetry}
253
- <div class="slab-actions">
254
- <button
255
- type="button"
256
- class="btn-flush"
257
- onclick={onRetry}
258
- aria-label="Retry connection"
259
- >
260
- Retry Connection
261
- </button>
262
- </div>
263
- {/if}
264
- </div>
265
- </div>
266
- {/if}
@@ -1,4 +1,4 @@
1
- import type { StreamStatus } from '@livepeer-frameworks/player-core';
1
+ import type { StreamStatus } from "@livepeer-frameworks/player-core";
2
2
  interface Props {
3
3
  /** Current stream status */
4
4
  status: StreamStatus;
@@ -9,7 +9,7 @@
9
9
  - Automatic timing synchronization with video
10
10
  -->
11
11
  <script lang="ts">
12
- import { onDestroy } from 'svelte';
12
+ import { onDestroy } from "svelte";
13
13
 
14
14
  interface SubtitleCue {
15
15
  id: string;
@@ -44,7 +44,10 @@
44
44
  /** Subtitle cues to render (static or from meta track) */
45
45
  cues?: SubtitleCue[];
46
46
  /** Subscribe to meta track function (for live subtitles) */
47
- subscribeToMetaTrack?: (trackId: string, callback: (event: MetaTrackEvent) => void) => () => void;
47
+ subscribeToMetaTrack?: (
48
+ trackId: string,
49
+ callback: (event: MetaTrackEvent) => void
50
+ ) => () => void;
48
51
  /** Meta track ID for live subtitles */
49
52
  metaTrackId?: string;
50
53
  /** Custom styles */
@@ -60,24 +63,24 @@
60
63
  subscribeToMetaTrack,
61
64
  metaTrackId,
62
65
  style: customStyle,
63
- class: className = '',
66
+ class: className = "",
64
67
  }: Props = $props();
65
68
 
66
69
  const DEFAULT_STYLE: SubtitleStyle = {
67
- fontSize: '1.5rem',
68
- fontFamily: 'system-ui, -apple-system, sans-serif',
69
- color: 'white',
70
- backgroundColor: 'rgba(0, 0, 0, 0.75)',
71
- textShadow: '2px 2px 4px rgba(0, 0, 0, 0.5)',
72
- bottom: '5%',
73
- maxWidth: '90%',
74
- padding: '0.5em 1em',
75
- borderRadius: '4px',
70
+ fontSize: "1.5rem",
71
+ fontFamily: "system-ui, -apple-system, sans-serif",
72
+ color: "white",
73
+ backgroundColor: "rgba(0, 0, 0, 0.75)",
74
+ textShadow: "2px 2px 4px rgba(0, 0, 0, 0.5)",
75
+ bottom: "5%",
76
+ maxWidth: "90%",
77
+ padding: "0.5em 1em",
78
+ borderRadius: "4px",
76
79
  };
77
80
 
78
81
  // State
79
82
  let liveCues = $state<SubtitleCue[]>([]);
80
- let displayedText = $state<string>('');
83
+ let displayedText = $state<string>("");
81
84
  let _lastCueId: string | null = null;
82
85
  let unsubscribe: (() => void) | null = null;
83
86
 
@@ -89,30 +92,30 @@
89
92
 
90
93
  // Parse subtitle cue from meta track event data
91
94
  function parseSubtitleCue(data: unknown): SubtitleCue | null {
92
- if (typeof data !== 'object' || data === null) return null;
95
+ if (typeof data !== "object" || data === null) return null;
93
96
 
94
97
  const obj = data as Record<string, unknown>;
95
98
 
96
- const text = typeof obj.text === 'string' ? obj.text : String(obj.text ?? '');
99
+ const text = typeof obj.text === "string" ? obj.text : String(obj.text ?? "");
97
100
  if (!text) return null;
98
101
 
99
102
  let startTime = 0;
100
103
  let endTime = Infinity;
101
104
 
102
- if ('startTime' in obj) startTime = Number(obj.startTime);
103
- else if ('start' in obj) startTime = Number(obj.start);
105
+ if ("startTime" in obj) startTime = Number(obj.startTime);
106
+ else if ("start" in obj) startTime = Number(obj.start);
104
107
 
105
- if ('endTime' in obj) endTime = Number(obj.endTime);
106
- else if ('end' in obj) endTime = Number(obj.end);
108
+ if ("endTime" in obj) endTime = Number(obj.endTime);
109
+ else if ("end" in obj) endTime = Number(obj.end);
107
110
 
108
- const id = typeof obj.id === 'string' ? obj.id : String(Date.now());
111
+ const id = typeof obj.id === "string" ? obj.id : String(Date.now());
109
112
 
110
113
  return {
111
114
  id,
112
115
  text,
113
116
  startTime,
114
117
  endTime,
115
- lang: typeof obj.lang === 'string' ? obj.lang : undefined,
118
+ lang: typeof obj.lang === "string" ? obj.lang : undefined,
116
119
  };
117
120
  }
118
121
 
@@ -127,11 +130,11 @@
127
130
  }
128
131
 
129
132
  const handleMetaEvent = (event: MetaTrackEvent) => {
130
- if (event.type === 'subtitle') {
133
+ if (event.type === "subtitle") {
131
134
  const cue = parseSubtitleCue(event.data);
132
135
  if (cue) {
133
136
  // Deduplicate by ID
134
- const existing = liveCues.find(c => c.id === cue.id);
137
+ const existing = liveCues.find((c) => c.id === cue.id);
135
138
  if (!existing) {
136
139
  // Keep last 50 cues max
137
140
  liveCues = [...liveCues, cue].slice(-50);
@@ -153,12 +156,12 @@
153
156
  // Find active cue based on current time
154
157
  $effect(() => {
155
158
  if (!enabled) {
156
- displayedText = '';
159
+ displayedText = "";
157
160
  return;
158
161
  }
159
162
 
160
163
  const currentTimeMs = currentTime * 1000;
161
- const activeCue = allCues.find(cue => {
164
+ const activeCue = allCues.find((cue) => {
162
165
  const start = cue.startTime;
163
166
  const end = cue.endTime;
164
167
  return currentTimeMs >= start && currentTimeMs < end;
@@ -168,7 +171,7 @@
168
171
  displayedText = activeCue.text;
169
172
  _lastCueId = activeCue.id;
170
173
  } else {
171
- displayedText = '';
174
+ displayedText = "";
172
175
  _lastCueId = null;
173
176
  }
174
177
  });
@@ -177,7 +180,7 @@
177
180
  $effect(() => {
178
181
  const currentTimeMs = currentTime * 1000;
179
182
 
180
- liveCues = liveCues.filter(cue => {
183
+ liveCues = liveCues.filter((cue) => {
181
184
  const endTime = cue.endTime === Infinity ? cue.startTime + 10000 : cue.endTime;
182
185
  return endTime >= currentTimeMs - 30000;
183
186
  });
@@ -192,22 +195,6 @@
192
195
  });
193
196
  </script>
194
197
 
195
- <style>
196
- .fw-player-root .subtitle-container {
197
- position: absolute;
198
- left: 50%;
199
- transform: translateX(-50%);
200
- z-index: 30;
201
- text-align: center;
202
- pointer-events: none;
203
- }
204
-
205
- .fw-player-root .subtitle-text {
206
- display: inline-block;
207
- white-space: pre-wrap;
208
- }
209
- </style>
210
-
211
198
  {#if enabled && displayedText}
212
199
  <div
213
200
  class="subtitle-container {className}"
@@ -232,3 +219,19 @@
232
219
  </span>
233
220
  </div>
234
221
  {/if}
222
+
223
+ <style>
224
+ .fw-player-root .subtitle-container {
225
+ position: absolute;
226
+ left: 50%;
227
+ transform: translateX(-50%);
228
+ z-index: 30;
229
+ text-align: center;
230
+ pointer-events: none;
231
+ }
232
+
233
+ .fw-player-root .subtitle-text {
234
+ display: inline-block;
235
+ white-space: pre-wrap;
236
+ }
237
+ </style>
@@ -3,7 +3,7 @@
3
3
  Port of src/components/ThumbnailOverlay.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
  thumbnailUrl?: string | null;
@@ -19,8 +19,8 @@
19
19
  onPlay = undefined,
20
20
  message = null,
21
21
  showUnmuteMessage = false,
22
- style = '',
23
- class: className = '',
22
+ style = "",
23
+ class: className = "",
24
24
  }: Props = $props();
25
25
 
26
26
  function handleClick() {
@@ -28,7 +28,7 @@
28
28
  }
29
29
 
30
30
  function handleKeyDown(event: KeyboardEvent) {
31
- if (event.key === 'Enter' || event.key === ' ') {
31
+ if (event.key === "Enter" || event.key === " ") {
32
32
  event.preventDefault();
33
33
  handleClick();
34
34
  }
@@ -42,7 +42,7 @@
42
42
  onkeydown={handleKeyDown}
43
43
  {style}
44
44
  class={cn(
45
- 'fw-player-thumbnail relative flex h-full min-h-[280px] w-full cursor-pointer items-center justify-center overflow-hidden rounded-xl bg-slate-950 text-foreground outline-none transition focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background',
45
+ "fw-player-thumbnail relative flex h-full min-h-[280px] w-full cursor-pointer items-center justify-center overflow-hidden rounded-xl bg-slate-950 text-foreground outline-none transition focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background",
46
46
  className
47
47
  )}
48
48
  >
@@ -55,15 +55,21 @@
55
55
 
56
56
  <div
57
57
  class={cn(
58
- 'absolute inset-0 bg-slate-950/70',
59
- !thumbnailUrl && 'bg-gradient-to-br from-slate-900 via-slate-950 to-slate-900'
58
+ "absolute inset-0 bg-slate-950/70",
59
+ !thumbnailUrl && "bg-gradient-to-br from-slate-900 via-slate-950 to-slate-900"
60
60
  )}
61
61
  ></div>
62
62
 
63
- <div class="relative z-10 flex max-w-[320px] flex-col items-center gap-4 px-6 text-center text-sm sm:gap-6">
63
+ <div
64
+ class="relative z-10 flex max-w-[320px] flex-col items-center gap-4 px-6 text-center text-sm sm:gap-6"
65
+ >
64
66
  {#if showUnmuteMessage}
65
- <div class="w-full rounded-lg border border-white/15 bg-black/80 p-4 text-sm text-white shadow-lg backdrop-blur">
66
- <div class="mb-1 flex items-center justify-center gap-2 text-base font-semibold text-primary">
67
+ <div
68
+ class="w-full rounded-lg border border-white/15 bg-black/80 p-4 text-sm text-white shadow-lg backdrop-blur"
69
+ >
70
+ <div
71
+ class="mb-1 flex items-center justify-center gap-2 text-base font-semibold text-primary"
72
+ >
67
73
  <span aria-hidden="true">🔇</span> Click to unmute
68
74
  </div>
69
75
  <p class="text-xs text-white/80">Stream is playing muted — tap to enable sound.</p>
@@ -74,21 +80,18 @@
74
80
  class="h-20 w-20 rounded-full bg-primary/90 text-primary-foreground shadow-lg shadow-primary/40 transition hover:bg-primary focus-visible:bg-primary flex items-center justify-center"
75
81
  aria-label="Play stream"
76
82
  >
77
- <svg
78
- viewBox="0 0 24 24"
79
- fill="currentColor"
80
- class="ml-0.5 h-8 w-8"
81
- aria-hidden="true"
82
- >
83
+ <svg viewBox="0 0 24 24" fill="currentColor" class="ml-0.5 h-8 w-8" aria-hidden="true">
83
84
  <path d="M8 5v14l11-7z" />
84
85
  </svg>
85
86
  </button>
86
- <div class="w-full rounded-lg border border-white/10 bg-black/70 p-5 text-white shadow-inner backdrop-blur">
87
+ <div
88
+ class="w-full rounded-lg border border-white/10 bg-black/70 p-5 text-white shadow-inner backdrop-blur"
89
+ >
87
90
  <p class="text-base font-semibold text-primary">
88
- {message ?? 'Click to play'}
91
+ {message ?? "Click to play"}
89
92
  </p>
90
93
  <p class="mt-1 text-xs text-white/70">
91
- {message ? 'Start streaming instantly' : 'Jump into the live feed'}
94
+ {message ? "Start streaming instantly" : "Jump into the live feed"}
92
95
  </p>
93
96
  </div>
94
97
  {/if}
@@ -3,7 +3,7 @@
3
3
  Port of src/components/TitleOverlay.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
  title?: string | null;
@@ -12,12 +12,7 @@
12
12
  class?: string;
13
13
  }
14
14
 
15
- let {
16
- title = null,
17
- description = null,
18
- isVisible,
19
- class: className = '',
20
- }: Props = $props();
15
+ let { title = null, description = null, isVisible, class: className = "" }: Props = $props();
21
16
 
22
17
  // Don't render if no content
23
18
  let hasContent = $derived(!!title || !!description);
@@ -26,10 +21,10 @@
26
21
  {#if hasContent}
27
22
  <div
28
23
  class={cn(
29
- 'fw-title-overlay absolute inset-x-0 top-0 z-20 pointer-events-none',
30
- 'bg-gradient-to-b from-black/70 via-black/40 to-transparent',
31
- 'px-4 py-3 transition-opacity duration-300',
32
- isVisible ? 'opacity-100' : 'opacity-0',
24
+ "fw-title-overlay absolute inset-x-0 top-0 z-20 pointer-events-none",
25
+ "bg-gradient-to-b from-black/70 via-black/40 to-transparent",
26
+ "px-4 py-3 transition-opacity duration-300",
27
+ isVisible ? "opacity-100" : "opacity-0",
33
28
  className
34
29
  )}
35
30
  >
@@ -1,10 +1,10 @@
1
1
  <script lang="ts">
2
2
  let {
3
3
  size = 16,
4
- color: _color = 'currentColor',
5
- className = '',
4
+ color: _color = "currentColor",
5
+ className = "",
6
6
  isMuted = false,
7
- volume = 1 // 0-1 range
7
+ volume = 1, // 0-1 range
8
8
  }: {
9
9
  size?: number;
10
10
  color?: string;
@@ -24,7 +24,9 @@
24
24
  class={className}
25
25
  aria-hidden="true"
26
26
  >
27
- <path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>
27
+ <path
28
+ d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"
29
+ />
28
30
  </svg>
29
31
  {:else if volume < 0.5}
30
32
  <!-- Low volume icon -->
@@ -36,7 +38,9 @@
36
38
  class={className}
37
39
  aria-hidden="true"
38
40
  >
39
- <path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"/>
41
+ <path
42
+ d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"
43
+ />
40
44
  </svg>
41
45
  {:else}
42
46
  <!-- High volume icon -->
@@ -48,6 +52,8 @@
48
52
  class={className}
49
53
  aria-hidden="true"
50
54
  >
51
- <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
55
+ <path
56
+ d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"
57
+ />
52
58
  </svg>
53
59
  {/if}
package/dist/global.d.ts CHANGED
@@ -1,15 +1,15 @@
1
1
  // Asset module declarations
2
- declare module '*.svg' {
2
+ declare module "*.svg" {
3
3
  const content: string;
4
4
  export default content;
5
5
  }
6
6
 
7
- declare module '*.png' {
7
+ declare module "*.png" {
8
8
  const content: string;
9
9
  export default content;
10
10
  }
11
11
 
12
- declare module '*.jpg' {
12
+ declare module "*.jpg" {
13
13
  const content: string;
14
14
  export default content;
15
15
  }