@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
@@ -3,15 +3,15 @@
3
3
  Thin wrapper over PlayerController from @livepeer-frameworks/player-core
4
4
  -->
5
5
  <script lang="ts">
6
- import { onMount } from 'svelte';
7
- import IdleScreen from './IdleScreen.svelte';
8
- import SubtitleRenderer from './SubtitleRenderer.svelte';
9
- import PlayerControls from './PlayerControls.svelte';
10
- import SpeedIndicator from './SpeedIndicator.svelte';
11
- import SkipIndicator from './SkipIndicator.svelte';
12
- import TitleOverlay from './TitleOverlay.svelte';
13
- import StatsPanel from './StatsPanel.svelte';
14
- import DevModePanel from './DevModePanel.svelte';
6
+ import { onMount } from "svelte";
7
+ import IdleScreen from "./IdleScreen.svelte";
8
+ import SubtitleRenderer from "./SubtitleRenderer.svelte";
9
+ import PlayerControls from "./PlayerControls.svelte";
10
+ import SpeedIndicator from "./SpeedIndicator.svelte";
11
+ import SkipIndicator from "./SkipIndicator.svelte";
12
+ import TitleOverlay from "./TitleOverlay.svelte";
13
+ import StatsPanel from "./StatsPanel.svelte";
14
+ import DevModePanel from "./DevModePanel.svelte";
15
15
  import {
16
16
  ContextMenu,
17
17
  ContextMenuTrigger,
@@ -19,11 +19,23 @@
19
19
  ContextMenuContent,
20
20
  ContextMenuItem,
21
21
  ContextMenuSeparator,
22
- } from './ui/context-menu';
23
- import { StatsIcon, SettingsIcon, PictureInPictureIcon } from './icons';
24
- import { cn, type PlaybackMode, type ContentEndpoints, type PlayerState, type PlayerStateContext, type ContentType, type EndpointInfo, type PlayerMetadata } from '@livepeer-frameworks/player-core';
25
- import { createPlayerControllerStore, type PlayerControllerStore } from './stores/playerController';
26
- import type { SkipDirection } from './SkipIndicator.svelte';
22
+ } from "./ui/context-menu";
23
+ import { StatsIcon, SettingsIcon, PictureInPictureIcon } from "./icons";
24
+ import {
25
+ cn,
26
+ type PlaybackMode,
27
+ type ContentEndpoints,
28
+ type PlayerState,
29
+ type PlayerStateContext,
30
+ type ContentType,
31
+ type EndpointInfo,
32
+ type PlayerMetadata,
33
+ } from "@livepeer-frameworks/player-core";
34
+ import {
35
+ createPlayerControllerStore,
36
+ type PlayerControllerStore,
37
+ } from "./stores/playerController";
38
+ import type { SkipDirection } from "./SkipIndicator.svelte";
27
39
 
28
40
  // Props - aligned with React Player
29
41
  interface Props {
@@ -68,7 +80,12 @@
68
80
  let skipDirection: SkipDirection = $state(null);
69
81
 
70
82
  // Playback mode preference (persistent)
71
- let devPlaybackMode: PlaybackMode = $state(options?.playbackMode || 'auto');
83
+ let devPlaybackMode: PlaybackMode = $state("auto");
84
+ $effect(() => {
85
+ if (options?.playbackMode) {
86
+ devPlaybackMode = options.playbackMode;
87
+ }
88
+ });
72
89
 
73
90
  // Container ref
74
91
  let containerRef: HTMLElement | undefined = $state();
@@ -79,7 +96,7 @@
79
96
  // ============================================================================
80
97
  let playerStore: PlayerControllerStore | null = $state(null);
81
98
  let storeState = $state({
82
- state: 'booting' as PlayerState,
99
+ state: "booting" as PlayerState,
83
100
  streamState: null as any,
84
101
  endpoints: null as any,
85
102
  metadata: null as any,
@@ -107,6 +124,7 @@
107
124
  currentSourceInfo: null as { url: string; type: string } | null,
108
125
  playbackQuality: null as any,
109
126
  subtitlesEnabled: false,
127
+ toast: null as { message: string; timestamp: number } | null,
110
128
  });
111
129
 
112
130
  // Track if we've already attached to prevent double-attach race
@@ -123,7 +141,7 @@
123
141
  onMount(() => {
124
142
  debug(`onMount - contentId: ${contentId}, contentType: ${contentType}`);
125
143
  debug(`onMount - gatewayUrl: ${options?.gatewayUrl}, mistUrl: ${options?.mistUrl}`);
126
- debug(`onMount - endpoints: ${endpoints ? 'provided' : 'not provided'}`);
144
+ debug(`onMount - endpoints: ${endpoints ? "provided" : "not provided"}`);
127
145
 
128
146
  playerStore = createPlayerControllerStore({
129
147
  contentId,
@@ -139,11 +157,11 @@
139
157
  debug: options?.debug,
140
158
  });
141
159
 
142
- debug('playerStore created');
160
+ debug("playerStore created");
143
161
 
144
162
  // Subscribe to store state
145
163
  let prevMetadata: PlayerMetadata | null = null;
146
- const unsubscribe = playerStore.subscribe(state => {
164
+ const unsubscribe = playerStore.subscribe((state) => {
147
165
  storeState = state;
148
166
  // Forward state changes to prop callback
149
167
  if (onStateChange && state.state) {
@@ -157,7 +175,7 @@
157
175
  });
158
176
 
159
177
  return () => {
160
- debug('cleanup - destroying playerStore');
178
+ debug("cleanup - destroying playerStore");
161
179
  unsubscribe();
162
180
  playerStore?.destroy();
163
181
  playerStore = null;
@@ -167,23 +185,41 @@
167
185
 
168
186
  // Attach when container becomes available (only once)
169
187
  $effect(() => {
170
- debug(`$effect - containerRef: ${!!containerRef}, playerStore: ${!!playerStore}, hasAttached: ${hasAttached}`);
188
+ debug(
189
+ `$effect - containerRef: ${!!containerRef}, playerStore: ${!!playerStore}, hasAttached: ${hasAttached}`
190
+ );
171
191
  if (containerRef && playerStore && !hasAttached) {
172
192
  hasAttached = true;
173
- debug('attaching to container');
174
- playerStore.attach(containerRef).then(() => {
175
- debug('attach completed');
176
- }).catch((err) => {
177
- debug(`attach failed: ${err}`);
178
- console.error('[Player.svelte] attach failed:', err);
179
- });
193
+ debug("attaching to container");
194
+ playerStore
195
+ .attach(containerRef)
196
+ .then(() => {
197
+ debug("attach completed");
198
+ })
199
+ .catch((err) => {
200
+ debug(`attach failed: ${err}`);
201
+ console.error("[Player.svelte] attach failed:", err);
202
+ });
180
203
  }
181
204
  });
182
205
 
206
+ // Auto-dismiss toast after 3 seconds
207
+ $effect(() => {
208
+ if (!storeState.toast) return;
209
+ const timer = setTimeout(() => {
210
+ playerStore?.dismissToast();
211
+ }, 3000);
212
+ return () => clearTimeout(timer);
213
+ });
214
+
183
215
  // ============================================================================
184
216
  // Dev Mode Callbacks
185
217
  // ============================================================================
186
- function handleDevSettingsChange(settings: { forcePlayer?: string; forceType?: string; forceSource?: number }) {
218
+ function handleDevSettingsChange(settings: {
219
+ forcePlayer?: string;
220
+ forceType?: string;
221
+ forceSource?: number;
222
+ }) {
187
223
  // One-shot selection - controller handles the state
188
224
  playerStore?.setDevModeOptions({
189
225
  forcePlayer: settings.forcePlayer,
@@ -211,56 +247,59 @@
211
247
  // Derived Values
212
248
  // ============================================================================
213
249
  let primaryEndpoint = $derived(storeState.endpoints?.primary as EndpointInfo | undefined);
214
- let isLegacyPlayer = $derived(storeState.currentPlayerInfo?.shortname === 'mist-legacy');
250
+ let isLegacyPlayer = $derived(storeState.currentPlayerInfo?.shortname === "mist-legacy");
215
251
  let useStockControls = $derived(options?.stockControls === true || isLegacyPlayer);
216
252
  let metadata = $derived(storeState.metadata);
217
253
 
218
254
  // Title overlay visibility: show on hover or when paused
219
255
  let showTitleOverlay = $derived(
220
256
  (storeState.isHovering || storeState.isPaused) &&
221
- !storeState.shouldShowIdleScreen &&
222
- !storeState.isBuffering &&
223
- !storeState.error
257
+ !storeState.shouldShowIdleScreen &&
258
+ !storeState.isBuffering &&
259
+ !storeState.error
224
260
  );
225
261
 
226
262
  // Buffering spinner: only during active playback
227
263
  let showBufferingSpinner = $derived(
228
264
  !storeState.shouldShowIdleScreen &&
229
- storeState.isBuffering &&
230
- !storeState.error &&
231
- storeState.hasPlaybackStarted
265
+ storeState.isBuffering &&
266
+ !storeState.error &&
267
+ storeState.hasPlaybackStarted
232
268
  );
233
269
 
234
270
  // Waiting for endpoint (shown as overlay, not early return)
235
271
  let showWaitingForEndpoint = $derived(
236
- !storeState.endpoints?.primary && storeState.state !== 'booting'
272
+ !storeState.endpoints?.primary && storeState.state !== "booting"
237
273
  );
238
274
 
239
275
  let waitingMessage = $derived(
240
276
  options?.gatewayUrl
241
- ? (storeState.state === 'gateway_loading' ? 'Resolving viewing endpoint...' : 'Waiting for endpoint...')
242
- : 'Waiting for endpoint...'
277
+ ? storeState.state === "gateway_loading"
278
+ ? "Resolving viewing endpoint..."
279
+ : "Waiting for endpoint..."
280
+ : "Waiting for endpoint..."
243
281
  );
244
282
  </script>
245
283
 
246
284
  <ContextMenu>
247
- <ContextMenuTrigger>
248
- {#snippet child({ props })}
285
+ <ContextMenuTrigger>
286
+ {#snippet child({ props })}
249
287
  <div
250
288
  bind:this={playerRootRef}
251
289
  {...props}
252
290
  class={cn(
253
- 'fw-player-surface fw-player-root relative w-full h-full bg-black',
254
- options?.devMode && 'flex'
291
+ "fw-player-surface fw-player-root relative w-full h-full bg-black",
292
+ options?.devMode && "flex"
255
293
  )}
256
294
  data-player-container="true"
257
- tabindex="0"
295
+ role="region"
296
+ aria-label="Video player"
258
297
  onmouseenter={() => playerStore?.handleMouseEnter()}
259
298
  onmouseleave={() => playerStore?.handleMouseLeave()}
260
299
  onmousemove={() => playerStore?.handleMouseMove()}
261
300
  >
262
301
  <!-- Player area -->
263
- <div class={cn('relative', options?.devMode ? 'flex-1 min-w-0 h-full' : 'w-full h-full')}>
302
+ <div class={cn("relative", options?.devMode ? "flex-1 min-w-0 h-full" : "w-full h-full")}>
264
303
  <!-- Video container - PlayerController attaches here -->
265
304
  <div bind:this={containerRef} class="fw-player-container w-full h-full"></div>
266
305
 
@@ -282,7 +321,7 @@
282
321
  <!-- Stats panel -->
283
322
  <StatsPanel
284
323
  isOpen={isStatsOpen}
285
- onClose={() => isStatsOpen = false}
324
+ onClose={() => (isStatsOpen = false)}
286
325
  {metadata}
287
326
  streamState={storeState.streamState}
288
327
  quality={storeState.playbackQuality}
@@ -299,10 +338,17 @@
299
338
  playbackMode={devPlaybackMode}
300
339
  onModeChange={handleModeChange}
301
340
  onReload={handleReload}
302
- streamInfo={storeState.currentSourceInfo ? {
303
- source: [{ url: storeState.currentSourceInfo.url, type: storeState.currentSourceInfo.type }],
304
- meta: { tracks: [] }
305
- } : null}
341
+ streamInfo={storeState.currentSourceInfo
342
+ ? {
343
+ source: [
344
+ {
345
+ url: storeState.currentSourceInfo.url,
346
+ type: storeState.currentSourceInfo.type,
347
+ },
348
+ ],
349
+ meta: { tracks: [] },
350
+ }
351
+ : null}
306
352
  mistStreamInfo={storeState.streamState?.streamInfo}
307
353
  currentPlayer={storeState.currentPlayerInfo}
308
354
  currentSource={storeState.currentSourceInfo}
@@ -311,7 +357,7 @@
311
357
  nodeId={primaryEndpoint?.nodeId}
312
358
  isVisible={storeState.isHovering || storeState.isPaused}
313
359
  isOpen={false}
314
- onOpenChange={(open) => isDevPanelOpen = open}
360
+ onOpenChange={(open) => (isDevPanelOpen = open)}
315
361
  />
316
362
  {/if}
317
363
 
@@ -332,16 +378,26 @@
332
378
  {#if !showWaitingForEndpoint && storeState.shouldShowIdleScreen}
333
379
  <IdleScreen
334
380
  status={storeState.isEffectivelyLive ? storeState.streamState?.status : undefined}
335
- message={storeState.isEffectivelyLive ? storeState.streamState?.message : 'Loading video...'}
336
- percentage={storeState.isEffectivelyLive ? storeState.streamState?.percentage : undefined}
381
+ message={storeState.isEffectivelyLive
382
+ ? storeState.streamState?.message
383
+ : "Loading video..."}
384
+ percentage={storeState.isEffectivelyLive
385
+ ? storeState.streamState?.percentage
386
+ : undefined}
337
387
  />
338
388
  {/if}
339
389
 
340
390
  <!-- Buffering spinner -->
341
391
  {#if showBufferingSpinner}
342
- <div class="absolute inset-0 flex items-center justify-center bg-black/40 backdrop-blur-sm z-20">
343
- <div class="flex items-center gap-3 rounded-lg border border-white/10 bg-black/70 px-4 py-3 text-sm text-white shadow-lg">
344
- <div class="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin"></div>
392
+ <div
393
+ class="absolute inset-0 flex items-center justify-center bg-black/40 backdrop-blur-sm z-20"
394
+ >
395
+ <div
396
+ class="flex items-center gap-3 rounded-lg border border-white/10 bg-black/70 px-4 py-3 text-sm text-white shadow-lg"
397
+ >
398
+ <div
399
+ class="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin"
400
+ ></div>
345
401
  <span>Buffering...</span>
346
402
  </div>
347
403
  </div>
@@ -353,23 +409,37 @@
353
409
  role="alert"
354
410
  aria-live="assertive"
355
411
  class={cn(
356
- 'fw-error-overlay',
357
- storeState.isPassiveError ? 'fw-error-overlay--passive' : 'fw-error-overlay--fullscreen'
412
+ "fw-error-overlay",
413
+ storeState.isPassiveError
414
+ ? "fw-error-overlay--passive"
415
+ : "fw-error-overlay--fullscreen"
358
416
  )}
359
417
  >
360
- <div class={cn(
361
- 'fw-error-popup',
362
- storeState.isPassiveError ? 'fw-error-popup--passive' : 'fw-error-popup--fullscreen'
363
- )}>
364
- <div class={cn(
365
- 'fw-error-header',
366
- storeState.isPassiveError ? 'fw-error-header--warning' : 'fw-error-header--error'
367
- )}>
368
- <span class={cn(
369
- 'fw-error-title',
370
- storeState.isPassiveError ? 'fw-error-title--warning' : 'fw-error-title--error'
371
- )}>
372
- {storeState.isPassiveError ? 'Warning' : 'Error'}
418
+ <div
419
+ class={cn(
420
+ "fw-error-popup",
421
+ storeState.isPassiveError
422
+ ? "fw-error-popup--passive"
423
+ : "fw-error-popup--fullscreen"
424
+ )}
425
+ >
426
+ <div
427
+ class={cn(
428
+ "fw-error-header",
429
+ storeState.isPassiveError
430
+ ? "fw-error-header--warning"
431
+ : "fw-error-header--error"
432
+ )}
433
+ >
434
+ <span
435
+ class={cn(
436
+ "fw-error-title",
437
+ storeState.isPassiveError
438
+ ? "fw-error-title--warning"
439
+ : "fw-error-title--error"
440
+ )}
441
+ >
442
+ {storeState.isPassiveError ? "Warning" : "Error"}
373
443
  </span>
374
444
  <button
375
445
  type="button"
@@ -378,7 +448,12 @@
378
448
  aria-label="Dismiss"
379
449
  >
380
450
  <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
381
- <path d="M9 3L3 9M3 3L9 9" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
451
+ <path
452
+ d="M9 3L3 9M3 3L9 9"
453
+ stroke="currentColor"
454
+ stroke-width="1.5"
455
+ stroke-linecap="round"
456
+ />
382
457
  </svg>
383
458
  </button>
384
459
  </div>
@@ -389,7 +464,10 @@
389
464
  <button
390
465
  type="button"
391
466
  class="fw-error-btn"
392
- onclick={() => { playerStore?.clearError(); playerStore?.retry(); }}
467
+ onclick={() => {
468
+ playerStore?.clearError();
469
+ playerStore?.retry();
470
+ }}
393
471
  aria-label="Retry playback"
394
472
  >
395
473
  Retry
@@ -399,6 +477,36 @@
399
477
  </div>
400
478
  {/if}
401
479
 
480
+ <!-- Toast notification -->
481
+ {#if storeState.toast}
482
+ <div
483
+ class="absolute bottom-20 left-1/2 -translate-x-1/2 z-30 animate-in fade-in slide-in-from-bottom-2 duration-200"
484
+ role="status"
485
+ aria-live="polite"
486
+ >
487
+ <div
488
+ class="flex items-center gap-2 rounded-lg border border-white/10 bg-black/80 px-4 py-2 text-sm text-white shadow-lg backdrop-blur-sm"
489
+ >
490
+ <span>{storeState.toast.message}</span>
491
+ <button
492
+ type="button"
493
+ onclick={() => playerStore?.dismissToast()}
494
+ class="ml-2 text-white/60 hover:text-white"
495
+ aria-label="Dismiss"
496
+ >
497
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
498
+ <path
499
+ d="M9 3L3 9M3 3L9 9"
500
+ stroke="currentColor"
501
+ stroke-width="1.5"
502
+ stroke-linecap="round"
503
+ />
504
+ </svg>
505
+ </button>
506
+ </div>
507
+ </div>
508
+ {/if}
509
+
402
510
  <!-- Player controls -->
403
511
  {#if !useStockControls}
404
512
  <PlayerControls
@@ -412,8 +520,8 @@
412
520
  onModeChange={handleModeChange}
413
521
  mistStreamInfo={storeState.streamState?.streamInfo}
414
522
  showStatsButton={false}
415
- isStatsOpen={isStatsOpen}
416
- onStatsToggle={() => isStatsOpen = !isStatsOpen}
523
+ {isStatsOpen}
524
+ onStatsToggle={() => (isStatsOpen = !isStatsOpen)}
417
525
  isContentLive={storeState.isEffectivelyLive}
418
526
  onJumpToLive={() => playerStore?.getController()?.jumpToLive()}
419
527
  />
@@ -427,10 +535,17 @@
427
535
  playbackMode={devPlaybackMode}
428
536
  onModeChange={handleModeChange}
429
537
  onReload={handleReload}
430
- streamInfo={storeState.currentSourceInfo ? {
431
- source: [{ url: storeState.currentSourceInfo.url, type: storeState.currentSourceInfo.type }],
432
- meta: { tracks: [] }
433
- } : null}
538
+ streamInfo={storeState.currentSourceInfo
539
+ ? {
540
+ source: [
541
+ {
542
+ url: storeState.currentSourceInfo.url,
543
+ type: storeState.currentSourceInfo.type,
544
+ },
545
+ ],
546
+ meta: { tracks: [] },
547
+ }
548
+ : null}
434
549
  mistStreamInfo={storeState.streamState?.streamInfo}
435
550
  currentPlayer={storeState.currentPlayerInfo}
436
551
  currentSource={storeState.currentSourceInfo}
@@ -439,40 +554,58 @@
439
554
  nodeId={primaryEndpoint?.nodeId}
440
555
  isVisible={true}
441
556
  isOpen={true}
442
- onOpenChange={(open) => isDevPanelOpen = open}
557
+ onOpenChange={(open) => (isDevPanelOpen = open)}
443
558
  />
444
559
  {/if}
445
560
  </div>
446
- {/snippet}
447
- </ContextMenuTrigger>
448
-
449
- <ContextMenuPortal>
450
- <ContextMenuContent>
451
- <ContextMenuItem onSelect={() => { isStatsOpen = !isStatsOpen; }}>
452
- <StatsIcon size={14} class="opacity-70 flex-shrink-0 mr-2" />
453
- {isStatsOpen ? 'Hide Stats' : 'Stats'}
454
- </ContextMenuItem>
455
- {#if options?.devMode}
456
- <ContextMenuSeparator />
457
- <ContextMenuItem onSelect={() => { isDevPanelOpen = !isDevPanelOpen; }}>
458
- <SettingsIcon size={14} class="opacity-70 flex-shrink-0 mr-2" />
459
- {isDevPanelOpen ? 'Hide Settings' : 'Settings'}
460
- </ContextMenuItem>
461
- {/if}
561
+ {/snippet}
562
+ </ContextMenuTrigger>
563
+
564
+ <ContextMenuPortal>
565
+ <ContextMenuContent>
566
+ <ContextMenuItem
567
+ onSelect={() => {
568
+ isStatsOpen = !isStatsOpen;
569
+ }}
570
+ >
571
+ <StatsIcon size={14} class="opacity-70 flex-shrink-0 mr-2" />
572
+ {isStatsOpen ? "Hide Stats" : "Stats"}
573
+ </ContextMenuItem>
574
+ {#if options?.devMode}
462
575
  <ContextMenuSeparator />
463
- <ContextMenuItem onSelect={() => playerStore?.togglePiP()}>
464
- <PictureInPictureIcon size={14} class="opacity-70 flex-shrink-0 mr-2" />
465
- Picture-in-Picture
466
- </ContextMenuItem>
467
- <ContextMenuItem onSelect={() => playerStore?.toggleLoop()}>
468
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-70 flex-shrink-0 mr-2">
469
- <polyline points="17 1 21 5 17 9"></polyline>
470
- <path d="M3 11V9a4 4 0 0 1 4-4h14"></path>
471
- <polyline points="7 23 3 19 7 15"></polyline>
472
- <path d="M21 13v2a4 4 0 0 1-4 4H3"></path>
473
- </svg>
474
- {storeState.isLoopEnabled ? 'Disable Loop' : 'Enable Loop'}
576
+ <ContextMenuItem
577
+ onSelect={() => {
578
+ isDevPanelOpen = !isDevPanelOpen;
579
+ }}
580
+ >
581
+ <SettingsIcon size={14} class="opacity-70 flex-shrink-0 mr-2" />
582
+ {isDevPanelOpen ? "Hide Settings" : "Settings"}
475
583
  </ContextMenuItem>
476
- </ContextMenuContent>
477
- </ContextMenuPortal>
478
- </ContextMenu>
584
+ {/if}
585
+ <ContextMenuSeparator />
586
+ <ContextMenuItem onSelect={() => playerStore?.togglePiP()}>
587
+ <PictureInPictureIcon size={14} class="opacity-70 flex-shrink-0 mr-2" />
588
+ Picture-in-Picture
589
+ </ContextMenuItem>
590
+ <ContextMenuItem onSelect={() => playerStore?.toggleLoop()}>
591
+ <svg
592
+ width="14"
593
+ height="14"
594
+ viewBox="0 0 24 24"
595
+ fill="none"
596
+ stroke="currentColor"
597
+ stroke-width="2"
598
+ stroke-linecap="round"
599
+ stroke-linejoin="round"
600
+ class="opacity-70 flex-shrink-0 mr-2"
601
+ >
602
+ <polyline points="17 1 21 5 17 9"></polyline>
603
+ <path d="M3 11V9a4 4 0 0 1 4-4h14"></path>
604
+ <polyline points="7 23 3 19 7 15"></polyline>
605
+ <path d="M21 13v2a4 4 0 0 1-4 4H3"></path>
606
+ </svg>
607
+ {storeState.isLoopEnabled ? "Disable Loop" : "Enable Loop"}
608
+ </ContextMenuItem>
609
+ </ContextMenuContent>
610
+ </ContextMenuPortal>
611
+ </ContextMenu>
@@ -1,4 +1,4 @@
1
- import { type PlaybackMode, type ContentEndpoints, type PlayerState, type PlayerStateContext, type ContentType, type PlayerMetadata } from '@livepeer-frameworks/player-core';
1
+ import { type PlaybackMode, type ContentEndpoints, type PlayerState, type PlayerStateContext, type ContentType, type PlayerMetadata } from "@livepeer-frameworks/player-core";
2
2
  interface Props {
3
3
  contentId: string;
4
4
  contentType?: ContentType;