@livepeer-frameworks/player-svelte 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE.md +24 -0
  2. package/README.md +6 -2
  3. package/dist/DevModePanel.svelte +53 -16
  4. package/dist/IdleScreen.svelte +36 -28
  5. package/dist/LoadingScreen.svelte +107 -67
  6. package/dist/LoadingScreen.svelte.bak +702 -0
  7. package/dist/Player.svelte +200 -53
  8. package/dist/Player.svelte.d.ts +6 -1
  9. package/dist/PlayerControls.svelte +114 -32
  10. package/dist/PlayerControls.svelte.d.ts +3 -0
  11. package/dist/StreamStateOverlay.svelte +33 -21
  12. package/dist/SubtitleRenderer.svelte +2 -2
  13. package/dist/controls/FullscreenButton.svelte +26 -0
  14. package/dist/controls/FullscreenButton.svelte.d.ts +3 -0
  15. package/dist/controls/LiveBadge.svelte +23 -0
  16. package/dist/controls/LiveBadge.svelte.d.ts +3 -0
  17. package/dist/controls/PlayButton.svelte +26 -0
  18. package/dist/controls/PlayButton.svelte.d.ts +3 -0
  19. package/dist/controls/SettingsMenu.svelte +208 -0
  20. package/dist/controls/SettingsMenu.svelte.d.ts +28 -0
  21. package/dist/controls/SkipButton.svelte +33 -0
  22. package/dist/controls/SkipButton.svelte.d.ts +7 -0
  23. package/dist/controls/TimeDisplay.svelte +18 -0
  24. package/dist/controls/TimeDisplay.svelte.d.ts +3 -0
  25. package/dist/controls/VolumeControl.svelte +26 -0
  26. package/dist/controls/VolumeControl.svelte.d.ts +3 -0
  27. package/dist/controls/index.d.ts +7 -0
  28. package/dist/controls/index.js +7 -0
  29. package/dist/index.d.ts +3 -2
  30. package/dist/index.js +3 -1
  31. package/dist/stores/i18n.d.ts +3 -0
  32. package/dist/stores/i18n.js +4 -0
  33. package/dist/stores/index.d.ts +1 -0
  34. package/dist/stores/index.js +2 -0
  35. package/dist/stores/playerController.d.ts +2 -0
  36. package/dist/stores/playerController.js +4 -0
  37. package/package.json +19 -19
  38. package/src/DevModePanel.svelte +53 -16
  39. package/src/IdleScreen.svelte +12 -4
  40. package/src/LoadingScreen.svelte +90 -50
  41. package/src/LoadingScreen.svelte.bak +702 -0
  42. package/src/Player.svelte +200 -53
  43. package/src/PlayerControls.svelte +114 -32
  44. package/src/StreamStateOverlay.svelte +17 -5
  45. package/src/controls/FullscreenButton.svelte +26 -0
  46. package/src/controls/LiveBadge.svelte +23 -0
  47. package/src/controls/PlayButton.svelte +26 -0
  48. package/src/controls/SettingsMenu.svelte +208 -0
  49. package/src/controls/SkipButton.svelte +33 -0
  50. package/src/controls/TimeDisplay.svelte +18 -0
  51. package/src/controls/VolumeControl.svelte +26 -0
  52. package/src/controls/index.ts +7 -0
  53. package/src/index.ts +10 -0
  54. package/src/stores/i18n.ts +7 -0
  55. package/src/stores/index.ts +3 -0
  56. package/src/stores/playerController.ts +7 -0
@@ -0,0 +1,208 @@
1
+ <script lang="ts">
2
+ import { getContext } from "svelte";
3
+ import type { Readable } from "svelte/store";
4
+ import { SettingsIcon } from "../icons";
5
+ import {
6
+ SPEED_PRESETS,
7
+ getAvailableLocales,
8
+ getLocaleDisplayName,
9
+ createTranslator,
10
+ type TranslateFn,
11
+ } from "@livepeer-frameworks/player-core";
12
+ import type { FwLocale } from "@livepeer-frameworks/player-core";
13
+
14
+ interface Props {
15
+ qualities?: Array<{ id: string; label: string; active?: boolean }>;
16
+ activeQuality?: string;
17
+ onSelectQuality?: (id: string) => void;
18
+ textTracks?: Array<{ id: string; label: string; active?: boolean }>;
19
+ activeCaption?: string;
20
+ onSelectCaption?: (id: string) => void;
21
+ playbackRate?: number;
22
+ onSpeedChange?: (rate: number) => void;
23
+ supportsSpeed?: boolean;
24
+ playbackMode?: "auto" | "low-latency" | "quality";
25
+ onModeChange?: (mode: "auto" | "low-latency" | "quality") => void;
26
+ showModeSelector?: boolean;
27
+ activeLocale?: FwLocale;
28
+ onLocaleChange?: (locale: FwLocale) => void;
29
+ }
30
+
31
+ let {
32
+ qualities: propQualities,
33
+ activeQuality,
34
+ onSelectQuality,
35
+ textTracks: propTextTracks,
36
+ activeCaption,
37
+ onSelectCaption,
38
+ playbackRate = 1,
39
+ onSpeedChange,
40
+ supportsSpeed = true,
41
+ playbackMode,
42
+ onModeChange,
43
+ showModeSelector = false,
44
+ activeLocale = undefined,
45
+ onLocaleChange = undefined,
46
+ }: Props = $props();
47
+
48
+ let availableLocales = getAvailableLocales();
49
+
50
+ let controller: any = getContext("fw-player-controller");
51
+ const translatorCtx = getContext<Readable<TranslateFn> | undefined>("fw-translator");
52
+ const fallbackT = createTranslator({ locale: "en" });
53
+ let t: TranslateFn = $derived(translatorCtx ? $translatorCtx : fallbackT);
54
+ let isOpen = $state(false);
55
+
56
+ let qualities = $derived(propQualities ?? controller?.getQualities?.() ?? []);
57
+ let qualityValue = $derived(activeQuality ?? qualities.find((q: any) => q.active)?.id ?? "auto");
58
+ let textTracks = $derived(propTextTracks ?? []);
59
+ let captionValue = $derived(activeCaption ?? textTracks.find((t: any) => t.active)?.id ?? "none");
60
+
61
+ function selectQuality(id: string) {
62
+ if (onSelectQuality) onSelectQuality(id);
63
+ else controller?.selectQuality?.(id);
64
+ isOpen = false;
65
+ }
66
+
67
+ function handleKeyDown(e: KeyboardEvent) {
68
+ if (e.key === "Escape") {
69
+ isOpen = false;
70
+ e.preventDefault();
71
+ return;
72
+ }
73
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
74
+ e.preventDefault();
75
+ const menu = e.currentTarget as HTMLElement;
76
+ const items = menu.querySelectorAll<HTMLButtonElement>("button");
77
+ if (!items.length) return;
78
+ const current = Array.from(items).indexOf(document.activeElement as HTMLButtonElement);
79
+ const next =
80
+ e.key === "ArrowDown"
81
+ ? (current + 1) % items.length
82
+ : (current - 1 + items.length) % items.length;
83
+ items[next]?.focus();
84
+ }
85
+ }
86
+ </script>
87
+
88
+ <div class="fw-control-group" style="position: relative">
89
+ <button
90
+ type="button"
91
+ class="fw-btn-flush group"
92
+ class:fw-btn-flush--active={isOpen}
93
+ aria-label={t("settings")}
94
+ title={t("settings")}
95
+ onclick={() => (isOpen = !isOpen)}
96
+ >
97
+ <SettingsIcon size={16} />
98
+ </button>
99
+
100
+ {#if isOpen}
101
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
102
+ <div class="fw-settings-menu" role="menu" aria-label={t("settings")} onkeydown={handleKeyDown}>
103
+ {#if showModeSelector && onModeChange}
104
+ <div class="fw-settings-section">
105
+ <div class="fw-settings-label">{t("mode")}</div>
106
+ <div class="fw-settings-options">
107
+ {#each ["auto", "low-latency", "quality"] as mode}
108
+ <button
109
+ class="fw-settings-btn"
110
+ class:fw-settings-btn--active={playbackMode === mode}
111
+ onclick={() => {
112
+ onModeChange?.(mode as any);
113
+ isOpen = false;
114
+ }}
115
+ >
116
+ {mode === "low-latency" ? t("fast") : mode === "quality" ? t("stable") : t("auto")}
117
+ </button>
118
+ {/each}
119
+ </div>
120
+ </div>
121
+ {/if}
122
+
123
+ {#if supportsSpeed}
124
+ <div class="fw-settings-section">
125
+ <div class="fw-settings-label">{t("speed")}</div>
126
+ <div class="fw-settings-options fw-settings-options--wrap">
127
+ {#each SPEED_PRESETS as rate}
128
+ <button
129
+ class="fw-settings-btn"
130
+ class:fw-settings-btn--active={playbackRate === rate}
131
+ onclick={() => {
132
+ onSpeedChange?.(rate);
133
+ isOpen = false;
134
+ }}
135
+ >
136
+ {rate}x
137
+ </button>
138
+ {/each}
139
+ </div>
140
+ </div>
141
+ {/if}
142
+
143
+ {#if qualities.length > 0}
144
+ <div class="fw-settings-section">
145
+ <div class="fw-settings-label">{t("quality")}</div>
146
+ <div class="fw-settings-list">
147
+ <button
148
+ class="fw-settings-list-item"
149
+ class:fw-settings-list-item--active={qualityValue === "auto"}
150
+ onclick={() => selectQuality("auto")}>{t("auto")}</button
151
+ >
152
+ {#each qualities as q}
153
+ <button
154
+ class="fw-settings-list-item"
155
+ class:fw-settings-list-item--active={qualityValue === q.id}
156
+ onclick={() => selectQuality(q.id)}>{q.label}</button
157
+ >
158
+ {/each}
159
+ </div>
160
+ </div>
161
+ {/if}
162
+
163
+ {#if textTracks.length > 0}
164
+ <div class="fw-settings-section">
165
+ <div class="fw-settings-label">{t("captions")}</div>
166
+ <div class="fw-settings-list">
167
+ <button
168
+ class="fw-settings-list-item"
169
+ class:fw-settings-list-item--active={captionValue === "none"}
170
+ onclick={() => {
171
+ onSelectCaption?.("none");
172
+ isOpen = false;
173
+ }}>{t("captionsOff")}</button
174
+ >
175
+ {#each textTracks as tt}
176
+ <button
177
+ class="fw-settings-list-item"
178
+ class:fw-settings-list-item--active={captionValue === tt.id}
179
+ onclick={() => {
180
+ onSelectCaption?.(tt.id);
181
+ isOpen = false;
182
+ }}>{tt.label || tt.id}</button
183
+ >
184
+ {/each}
185
+ </div>
186
+ </div>
187
+ {/if}
188
+
189
+ {#if onLocaleChange}
190
+ <div class="fw-settings-section">
191
+ <div class="fw-settings-label">{t("language")}</div>
192
+ <div class="fw-settings-list">
193
+ {#each availableLocales as l}
194
+ <button
195
+ class="fw-settings-list-item"
196
+ class:fw-settings-list-item--active={activeLocale === l}
197
+ onclick={() => {
198
+ onLocaleChange?.(l);
199
+ isOpen = false;
200
+ }}>{getLocaleDisplayName(l)}</button
201
+ >
202
+ {/each}
203
+ </div>
204
+ </div>
205
+ {/if}
206
+ </div>
207
+ {/if}
208
+ </div>
@@ -0,0 +1,28 @@
1
+ import type { FwLocale } from "@livepeer-frameworks/player-core";
2
+ interface Props {
3
+ qualities?: Array<{
4
+ id: string;
5
+ label: string;
6
+ active?: boolean;
7
+ }>;
8
+ activeQuality?: string;
9
+ onSelectQuality?: (id: string) => void;
10
+ textTracks?: Array<{
11
+ id: string;
12
+ label: string;
13
+ active?: boolean;
14
+ }>;
15
+ activeCaption?: string;
16
+ onSelectCaption?: (id: string) => void;
17
+ playbackRate?: number;
18
+ onSpeedChange?: (rate: number) => void;
19
+ supportsSpeed?: boolean;
20
+ playbackMode?: "auto" | "low-latency" | "quality";
21
+ onModeChange?: (mode: "auto" | "low-latency" | "quality") => void;
22
+ showModeSelector?: boolean;
23
+ activeLocale?: FwLocale;
24
+ onLocaleChange?: (locale: FwLocale) => void;
25
+ }
26
+ declare const SettingsMenu: import("svelte").Component<Props, {}, "">;
27
+ type SettingsMenu = ReturnType<typeof SettingsMenu>;
28
+ export default SettingsMenu;
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import { getContext } from "svelte";
3
+ import type { Readable } from "svelte/store";
4
+ import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
5
+ import { SkipBackIcon, SkipForwardIcon } from "../icons";
6
+
7
+ interface Props {
8
+ direction: "back" | "forward";
9
+ seconds?: number;
10
+ }
11
+
12
+ let { direction, seconds = 10 }: Props = $props();
13
+ let pc: any = getContext("fw-player-controller");
14
+ const translatorCtx = getContext<Readable<TranslateFn> | undefined>("fw-translator");
15
+ const fallbackT = createTranslator({ locale: "en" });
16
+ let t: TranslateFn = $derived(translatorCtx ? $translatorCtx : fallbackT);
17
+
18
+ let label = $derived(direction === "back" ? t("skipBackward") : t("skipForward"));
19
+ </script>
20
+
21
+ <button
22
+ type="button"
23
+ class="fw-btn-flush"
24
+ aria-label={label}
25
+ title={label}
26
+ onclick={() => pc?.seek((pc?.currentTime ?? 0) + (direction === "back" ? -seconds : seconds))}
27
+ >
28
+ {#if direction === "back"}
29
+ <SkipBackIcon size={16} />
30
+ {:else}
31
+ <SkipForwardIcon size={16} />
32
+ {/if}
33
+ </button>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ direction: "back" | "forward";
3
+ seconds?: number;
4
+ }
5
+ declare const SkipButton: import("svelte").Component<Props, {}, "">;
6
+ type SkipButton = ReturnType<typeof SkipButton>;
7
+ export default SkipButton;
@@ -0,0 +1,18 @@
1
+ <script lang="ts">
2
+ import { getContext } from "svelte";
3
+ import { formatTimeDisplay } from "@livepeer-frameworks/player-core";
4
+
5
+ let pc: any = getContext("fw-player-controller");
6
+
7
+ let timeText = $derived(
8
+ formatTimeDisplay({
9
+ isLive: pc?.isEffectivelyLive ?? false,
10
+ currentTime: pc?.currentTime ?? 0,
11
+ duration: pc?.duration ?? NaN,
12
+ liveEdge: pc?.duration ?? 0,
13
+ seekableStart: 0,
14
+ })
15
+ );
16
+ </script>
17
+
18
+ <span class="fw-time-display">{timeText}</span>
@@ -0,0 +1,3 @@
1
+ declare const TimeDisplay: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type TimeDisplay = ReturnType<typeof TimeDisplay>;
3
+ export default TimeDisplay;
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import { getContext } from "svelte";
3
+ import type { Readable } from "svelte/store";
4
+ import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
5
+ import { VolumeUpIcon, VolumeOffIcon } from "../icons";
6
+
7
+ let pc: any = getContext("fw-player-controller");
8
+ const translatorCtx = getContext<Readable<TranslateFn> | undefined>("fw-translator");
9
+ const fallbackT = createTranslator({ locale: "en" });
10
+ let t: TranslateFn = $derived(translatorCtx ? $translatorCtx : fallbackT);
11
+ </script>
12
+
13
+ <button
14
+ type="button"
15
+ class="fw-btn-flush"
16
+ aria-label={pc?.isMuted ? t("unmute") : t("mute")}
17
+ aria-pressed={pc?.isMuted ?? false}
18
+ title={pc?.isMuted ? t("unmute") : t("mute")}
19
+ onclick={() => pc?.toggleMute()}
20
+ >
21
+ {#if pc?.isMuted}
22
+ <VolumeOffIcon size={16} />
23
+ {:else}
24
+ <VolumeUpIcon size={16} />
25
+ {/if}
26
+ </button>
@@ -0,0 +1,3 @@
1
+ declare const VolumeControl: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type VolumeControl = ReturnType<typeof VolumeControl>;
3
+ export default VolumeControl;
@@ -0,0 +1,7 @@
1
+ export { default as PlayButton } from "./PlayButton.svelte";
2
+ export { default as SkipButton } from "./SkipButton.svelte";
3
+ export { default as VolumeControl } from "./VolumeControl.svelte";
4
+ export { default as TimeDisplay } from "./TimeDisplay.svelte";
5
+ export { default as LiveBadge } from "./LiveBadge.svelte";
6
+ export { default as FullscreenButton } from "./FullscreenButton.svelte";
7
+ export { default as SettingsMenu } from "./SettingsMenu.svelte";
@@ -0,0 +1,7 @@
1
+ export { default as PlayButton } from "./PlayButton.svelte";
2
+ export { default as SkipButton } from "./SkipButton.svelte";
3
+ export { default as VolumeControl } from "./VolumeControl.svelte";
4
+ export { default as TimeDisplay } from "./TimeDisplay.svelte";
5
+ export { default as LiveBadge } from "./LiveBadge.svelte";
6
+ export { default as FullscreenButton } from "./FullscreenButton.svelte";
7
+ export { default as SettingsMenu } from "./SettingsMenu.svelte";
package/dist/index.d.ts CHANGED
@@ -41,10 +41,11 @@ export { default as TitleOverlay } from "./TitleOverlay.svelte";
41
41
  export { default as ThumbnailOverlay } from "./ThumbnailOverlay.svelte";
42
42
  export { default as StatsPanel } from "./StatsPanel.svelte";
43
43
  export { default as DevModePanel } from "./DevModePanel.svelte";
44
+ export * from "./controls";
44
45
  export * from "./icons";
45
46
  export * from "./stores";
46
47
  export * from "./ui/context-menu";
47
48
  export type { SkipDirection } from "./types";
48
- export { PlayerController, PlayerManager, globalPlayerManager, } from "@livepeer-frameworks/player-core";
49
+ export { PlayerController, PlayerManager, globalPlayerManager, createTranslator, getAvailableLocales, getLocaleDisplayName, } from "@livepeer-frameworks/player-core";
49
50
  export type { PlayerControllerConfig, PlayerControllerEvents, } from "@livepeer-frameworks/player-core";
50
- export type { PlayerState, PlayerStateContext, StreamState, StreamStatus, ContentMetadata, ContentEndpoints, EndpointInfo, PlaybackMode, PlaybackQuality, MistStreamInfo, PlayerOptions, PlayerMetadata, PlayerSelection, PlayerCombination, PlayerManagerOptions, PlayerManagerEvents, } from "@livepeer-frameworks/player-core";
51
+ export type { PlayerState, PlayerStateContext, StreamState, StreamStatus, ContentMetadata, ContentEndpoints, EndpointInfo, PlaybackMode, PlaybackQuality, MistStreamInfo, PlayerOptions, PlayerMetadata, PlayerSelection, PlayerCombination, PlayerManagerOptions, PlayerManagerEvents, FwLocale, TranslateFn, TranslationStrings, I18nConfig, } from "@livepeer-frameworks/player-core";
package/dist/index.js CHANGED
@@ -44,6 +44,8 @@ export { default as TitleOverlay } from "./TitleOverlay.svelte";
44
44
  export { default as ThumbnailOverlay } from "./ThumbnailOverlay.svelte";
45
45
  export { default as StatsPanel } from "./StatsPanel.svelte";
46
46
  export { default as DevModePanel } from "./DevModePanel.svelte";
47
+ // Composable control components
48
+ export * from "./controls";
47
49
  // Icon components
48
50
  export * from "./icons";
49
51
  // Stores
@@ -51,4 +53,4 @@ export * from "./stores";
51
53
  // Context menu components
52
54
  export * from "./ui/context-menu";
53
55
  // Re-export core types and classes for Svelte users
54
- export { PlayerController, PlayerManager, globalPlayerManager, } from "@livepeer-frameworks/player-core";
56
+ export { PlayerController, PlayerManager, globalPlayerManager, createTranslator, getAvailableLocales, getLocaleDisplayName, } from "@livepeer-frameworks/player-core";
@@ -0,0 +1,3 @@
1
+ import { type FwLocale } from "@livepeer-frameworks/player-core";
2
+ export declare const localeStore: import("svelte/store").Writable<FwLocale>;
3
+ export declare const translatorStore: import("svelte/store").Readable<import("@livepeer-frameworks/player-core").TranslateFn>;
@@ -0,0 +1,4 @@
1
+ import { writable, derived } from "svelte/store";
2
+ import { createTranslator } from "@livepeer-frameworks/player-core";
3
+ export const localeStore = writable("en");
4
+ export const translatorStore = derived(localeStore, ($locale) => createTranslator({ locale: $locale }));
@@ -12,4 +12,5 @@ export { createEndpointResolver, createDerivedEndpoints, createDerivedPrimaryEnd
12
12
  export { createPlayerContext, setPlayerContextInComponent, getPlayerContextFromComponent, getPlayerContextOrFallback, createDerivedVideoElement, createDerivedIsReady, createDerivedPlayerInfo, type PlayerContextState, type PlayerContextStore, } from "./playerContext";
13
13
  export { createPlaybackQualityMonitor, createDerivedQualityScore, createDerivedStallCount, createDerivedFrameDropRate, createDerivedBitrate, createDerivedLatency, type PlaybackQualityOptions, type PlaybackQualityStore, } from "./playbackQuality";
14
14
  export { createPlayerSelectionStore, createDerivedSelection, createDerivedCombinations, createDerivedReady, createDerivedSelectedPlayer, createDerivedSelectedSourceType, createDerivedCompatibleCombinations, createDerivedIncompatibleCombinations, type PlayerSelectionOptions, type PlayerSelectionState, type PlayerSelectionStore, } from "./playerSelection";
15
+ export { localeStore, translatorStore } from "./i18n";
15
16
  export { createPlayerControllerStore, createDerivedState, createDerivedIsPlaying, createDerivedCurrentTime, createDerivedDuration, createDerivedError, createDerivedVideoElement as createDerivedControllerVideoElement, createDerivedShouldShowControls, createDerivedShouldShowIdleScreen, type PlayerControllerStoreConfig, type PlayerControllerState, type PlayerControllerStore, } from "./playerController";
@@ -17,5 +17,7 @@ export { createPlayerContext, setPlayerContextInComponent, getPlayerContextFromC
17
17
  export { createPlaybackQualityMonitor, createDerivedQualityScore, createDerivedStallCount, createDerivedFrameDropRate, createDerivedBitrate, createDerivedLatency, } from "./playbackQuality";
18
18
  // Player selection (event-driven, cached)
19
19
  export { createPlayerSelectionStore, createDerivedSelection, createDerivedCombinations, createDerivedReady, createDerivedSelectedPlayer, createDerivedSelectedSourceType, createDerivedCompatibleCombinations, createDerivedIncompatibleCombinations, } from "./playerSelection";
20
+ // i18n (locale + translator)
21
+ export { localeStore, translatorStore } from "./i18n";
20
22
  // PlayerController store (central orchestrator)
21
23
  export { createPlayerControllerStore, createDerivedState, createDerivedIsPlaying, createDerivedCurrentTime, createDerivedDuration, createDerivedError, createDerivedVideoElement as createDerivedControllerVideoElement, createDerivedShouldShowControls, createDerivedShouldShowIdleScreen, } from "./playerController";
@@ -98,6 +98,8 @@ export interface PlayerControllerStore extends Readable<PlayerControllerState> {
98
98
  seek: (time: number) => void;
99
99
  /** Seek by delta */
100
100
  seekBy: (delta: number) => void;
101
+ /** Jump to live edge (for live streams) */
102
+ jumpToLive: () => void;
101
103
  /** Set volume */
102
104
  setVolume: (volume: number) => void;
103
105
  /** Toggle mute */
@@ -267,6 +267,9 @@ export function createPlayerControllerStore(config) {
267
267
  function seekBy(delta) {
268
268
  controller?.seekBy(delta);
269
269
  }
270
+ function jumpToLive() {
271
+ controller?.jumpToLive();
272
+ }
270
273
  function setVolume(volume) {
271
274
  controller?.setVolume(volume);
272
275
  }
@@ -338,6 +341,7 @@ export function createPlayerControllerStore(config) {
338
341
  togglePlay,
339
342
  seek,
340
343
  seekBy,
344
+ jumpToLive,
341
345
  setVolume,
342
346
  toggleMute,
343
347
  toggleLoop,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livepeer-frameworks/player-svelte",
3
- "version": "0.1.2",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Svelte 5 components for FrameWorks streaming player",
6
6
  "svelte": "./dist/index.js",
@@ -20,30 +20,22 @@
20
20
  },
21
21
  "./player.css": "./src/player.css"
22
22
  },
23
- "scripts": {
24
- "build": "svelte-package -i src -o dist",
25
- "build:watch": "svelte-package -i src -o dist --watch",
26
- "type-check": "svelte-check",
27
- "test": "vitest run",
28
- "test:watch": "vitest",
29
- "test:coverage": "vitest run --coverage"
30
- },
31
23
  "dependencies": {
32
- "@livepeer-frameworks/player-core": "workspace:*",
33
- "bits-ui": "^2.15.5"
24
+ "bits-ui": "^2.15.5",
25
+ "@livepeer-frameworks/player-core": "0.2.1"
34
26
  },
35
27
  "peerDependencies": {
36
28
  "svelte": "^5.0.0"
37
29
  },
38
30
  "devDependencies": {
39
- "@sveltejs/package": "^2.3.7",
31
+ "@sveltejs/package": "^2.5.7",
40
32
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
41
- "@testing-library/svelte": "^5.2.7",
33
+ "@testing-library/svelte": "^5.3.1",
42
34
  "@vitest/coverage-v8": "^4.0.18",
43
- "jsdom": "^28.0.0",
44
- "svelte": "^5.50.0",
45
- "svelte-check": "^4.3.6",
46
- "typescript": "^5.9.2",
35
+ "jsdom": "^28.1.0",
36
+ "svelte": "^5.51.2",
37
+ "svelte-check": "^4.4.0",
38
+ "typescript": "^5.9.3",
47
39
  "vite": "^7.3.1",
48
40
  "vitest": "^4.0.18"
49
41
  },
@@ -55,5 +47,13 @@
55
47
  "video"
56
48
  ],
57
49
  "author": "Livepeer FrameWorks",
58
- "license": "Unlicense"
59
- }
50
+ "license": "Unlicense",
51
+ "scripts": {
52
+ "build": "svelte-package -i src -o dist",
53
+ "build:watch": "svelte-package -i src -o dist --watch",
54
+ "type-check": "svelte-check",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest",
57
+ "test:coverage": "vitest run --coverage"
58
+ }
59
+ }
@@ -67,7 +67,7 @@
67
67
  let internalIsOpen = $state(false);
68
68
  let activeTab = $state<"config" | "stats">("config");
69
69
  let hoveredComboIndex = $state<number | null>(null);
70
- let tooltipAbove = $state(false);
70
+ let tooltipPos = $state<{ top: number; left: number } | null>(null);
71
71
  let showDisabledPlayers = $state(false);
72
72
  let comboListRef: HTMLDivElement | undefined = $state();
73
73
 
@@ -163,14 +163,12 @@
163
163
 
164
164
  function handleComboHover(index: number, e: MouseEvent) {
165
165
  hoveredComboIndex = index;
166
- if (comboListRef) {
167
- const container = comboListRef;
168
- const row = e.currentTarget as HTMLElement;
169
- const containerRect = container.getBoundingClientRect();
170
- const rowRect = row.getBoundingClientRect();
171
- const relativePosition = (rowRect.top - containerRect.top) / containerRect.height;
172
- tooltipAbove = relativePosition > 0.6;
173
- }
166
+ const row = e.currentTarget as HTMLElement;
167
+ const rowRect = row.getBoundingClientRect();
168
+ tooltipPos = {
169
+ top: Math.max(8, Math.min(rowRect.top, window.innerHeight - 200)),
170
+ left: Math.max(8, rowRect.left - 228),
171
+ };
174
172
  }
175
173
 
176
174
  // Quality monitoring
@@ -387,7 +385,10 @@
387
385
  class="fw-dev-combo"
388
386
  role="listitem"
389
387
  onmouseenter={(e) => handleComboHover(index, e)}
390
- onmouseleave={() => (hoveredComboIndex = null)}
388
+ onmouseleave={() => {
389
+ hoveredComboIndex = null;
390
+ tooltipPos = null;
391
+ }}
391
392
  >
392
393
  <button
393
394
  type="button"
@@ -446,12 +447,10 @@
446
447
  </button>
447
448
 
448
449
  <!-- Tooltip -->
449
- {#if hoveredComboIndex === index}
450
+ {#if hoveredComboIndex === index && tooltipPos}
450
451
  <div
451
- class={cn(
452
- "fw-dev-tooltip",
453
- tooltipAbove ? "fw-dev-tooltip--above" : "fw-dev-tooltip--below"
454
- )}
452
+ class="fw-dev-tooltip"
453
+ style="top: {tooltipPos.top}px; left: {tooltipPos.left}px;"
455
454
  >
456
455
  <div class="fw-dev-tooltip-header">
457
456
  <div class="fw-dev-tooltip-title">{combo.playerName}</div>
@@ -467,7 +466,8 @@
467
466
  {#if combo.compatible && combo.scoreBreakdown}
468
467
  <div class="fw-dev-tooltip-score">Score: {combo.score.toFixed(2)}</div>
469
468
  <div class="fw-dev-tooltip-row">
470
- Tracks: <span class="fw-dev-tooltip-value"
469
+ Tracks [{combo.scoreBreakdown.trackTypes.join(", ")}]:
470
+ <span class="fw-dev-tooltip-value"
471
471
  >{combo.scoreBreakdown.trackScore.toFixed(2)}</span
472
472
  >
473
473
  <span class="fw-dev-tooltip-weight"
@@ -490,6 +490,43 @@
490
490
  >x{combo.scoreBreakdown.weights.source}</span
491
491
  >
492
492
  </div>
493
+ {#if combo.scoreBreakdown.reliabilityScore !== undefined}
494
+ <div class="fw-dev-tooltip-row">
495
+ Reliability: <span class="fw-dev-tooltip-value"
496
+ >{combo.scoreBreakdown.reliabilityScore.toFixed(2)}</span
497
+ >
498
+ <span class="fw-dev-tooltip-weight"
499
+ >x{combo.scoreBreakdown.weights.reliability ?? 0}</span
500
+ >
501
+ </div>
502
+ {/if}
503
+ {#if combo.scoreBreakdown.modeBonus !== undefined && combo.scoreBreakdown.modeBonus !== 0}
504
+ <div class="fw-dev-tooltip-row">
505
+ Mode ({playbackMode}):
506
+ <span class="fw-dev-tooltip-bonus"
507
+ >+{combo.scoreBreakdown.modeBonus.toFixed(2)}</span
508
+ >
509
+ <span class="fw-dev-tooltip-weight"
510
+ >x{combo.scoreBreakdown.weights.mode ?? 0}</span
511
+ >
512
+ </div>
513
+ {/if}
514
+ {#if combo.scoreBreakdown.routingBonus !== undefined && combo.scoreBreakdown.routingBonus !== 0}
515
+ <div class="fw-dev-tooltip-row">
516
+ Routing: <span
517
+ class={combo.scoreBreakdown.routingBonus > 0
518
+ ? "fw-dev-tooltip-bonus"
519
+ : "fw-dev-tooltip-penalty"}
520
+ >
521
+ {combo.scoreBreakdown.routingBonus > 0
522
+ ? "+"
523
+ : ""}{combo.scoreBreakdown.routingBonus.toFixed(2)}
524
+ </span>
525
+ <span class="fw-dev-tooltip-weight"
526
+ >x{combo.scoreBreakdown.weights.routing ?? 0}</span
527
+ >
528
+ </div>
529
+ {/if}
493
530
  {:else}
494
531
  <div class="fw-dev-tooltip-error">
495
532
  {combo.incompatibleReason || "Incompatible"}