@livepeer-frameworks/player-svelte 0.1.3 → 0.2.3

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 (59) hide show
  1. package/dist/DevModePanel.svelte +34 -25
  2. package/dist/IdleScreen.svelte +14 -4
  3. package/dist/LoadingScreen.svelte +92 -50
  4. package/dist/LoadingScreen.svelte.bak +702 -0
  5. package/dist/Player.svelte +206 -55
  6. package/dist/Player.svelte.d.ts +6 -1
  7. package/dist/PlayerControls.svelte +159 -74
  8. package/dist/PlayerControls.svelte.d.ts +7 -0
  9. package/dist/SeekBar.svelte +15 -14
  10. package/dist/SeekBar.svelte.d.ts +4 -4
  11. package/dist/StreamStateOverlay.svelte +20 -6
  12. package/dist/SubtitleRenderer.svelte +3 -3
  13. package/dist/SubtitleRenderer.svelte.d.ts +1 -1
  14. package/dist/controls/FullscreenButton.svelte +28 -0
  15. package/dist/controls/FullscreenButton.svelte.d.ts +3 -0
  16. package/dist/controls/LiveBadge.svelte +25 -0
  17. package/dist/controls/LiveBadge.svelte.d.ts +3 -0
  18. package/dist/controls/PlayButton.svelte +28 -0
  19. package/dist/controls/PlayButton.svelte.d.ts +3 -0
  20. package/dist/controls/SettingsMenu.svelte +210 -0
  21. package/dist/controls/SettingsMenu.svelte.d.ts +28 -0
  22. package/dist/controls/SkipButton.svelte +36 -0
  23. package/dist/controls/SkipButton.svelte.d.ts +7 -0
  24. package/dist/controls/TimeDisplay.svelte +18 -0
  25. package/dist/controls/TimeDisplay.svelte.d.ts +3 -0
  26. package/dist/controls/VolumeControl.svelte +28 -0
  27. package/dist/controls/VolumeControl.svelte.d.ts +3 -0
  28. package/dist/controls/index.d.ts +7 -0
  29. package/dist/controls/index.js +7 -0
  30. package/dist/index.d.ts +3 -2
  31. package/dist/index.js +3 -1
  32. package/dist/stores/i18n.d.ts +3 -0
  33. package/dist/stores/i18n.js +4 -0
  34. package/dist/stores/index.d.ts +1 -0
  35. package/dist/stores/index.js +2 -0
  36. package/dist/stores/playerController.d.ts +4 -4
  37. package/dist/stores/playerController.js +1 -1
  38. package/package.json +8 -8
  39. package/src/DevModePanel.svelte +34 -25
  40. package/src/IdleScreen.svelte +14 -4
  41. package/src/LoadingScreen.svelte +92 -50
  42. package/src/LoadingScreen.svelte.bak +702 -0
  43. package/src/Player.svelte +206 -55
  44. package/src/PlayerControls.svelte +159 -74
  45. package/src/SeekBar.svelte +15 -14
  46. package/src/StreamStateOverlay.svelte +20 -6
  47. package/src/SubtitleRenderer.svelte +3 -3
  48. package/src/controls/FullscreenButton.svelte +28 -0
  49. package/src/controls/LiveBadge.svelte +25 -0
  50. package/src/controls/PlayButton.svelte +28 -0
  51. package/src/controls/SettingsMenu.svelte +210 -0
  52. package/src/controls/SkipButton.svelte +36 -0
  53. package/src/controls/TimeDisplay.svelte +18 -0
  54. package/src/controls/VolumeControl.svelte +28 -0
  55. package/src/controls/index.ts +7 -0
  56. package/src/index.ts +10 -0
  57. package/src/stores/i18n.ts +7 -0
  58. package/src/stores/index.ts +3 -0
  59. package/src/stores/playerController.ts +5 -5
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ import { getContext } from "svelte";
3
+ import { readable } from "svelte/store";
4
+ import type { Readable } from "svelte/store";
5
+ import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
6
+ import { SeekToLiveIcon } from "../icons";
7
+
8
+ let pc: any = getContext("fw-player-controller");
9
+ const translatorStore: Readable<TranslateFn> =
10
+ getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
11
+ readable(createTranslator({ locale: "en" }));
12
+ let t: TranslateFn = $derived($translatorStore);
13
+ </script>
14
+
15
+ {#if pc?.isEffectivelyLive}
16
+ <button
17
+ type="button"
18
+ class="fw-live-badge fw-live-badge--active"
19
+ onclick={() => pc?.jumpToLive()}
20
+ aria-label={t("live")}
21
+ >
22
+ {t("live").toUpperCase()}
23
+ <SeekToLiveIcon size={10} />
24
+ </button>
25
+ {/if}
@@ -0,0 +1,28 @@
1
+ <script lang="ts">
2
+ import { getContext } from "svelte";
3
+ import { readable } from "svelte/store";
4
+ import type { Readable } from "svelte/store";
5
+ import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
6
+ import { PlayIcon, PauseIcon } from "../icons";
7
+
8
+ let pc: any = getContext("fw-player-controller");
9
+ const translatorStore: Readable<TranslateFn> =
10
+ getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
11
+ readable(createTranslator({ locale: "en" }));
12
+ let t: TranslateFn = $derived($translatorStore);
13
+ </script>
14
+
15
+ <button
16
+ type="button"
17
+ class="fw-btn-flush"
18
+ aria-label={pc?.isPlaying ? t("pause") : t("play")}
19
+ aria-pressed={pc?.isPlaying ?? false}
20
+ title={pc?.isPlaying ? t("pause") : t("play")}
21
+ onclick={() => pc?.togglePlay()}
22
+ >
23
+ {#if pc?.isPlaying}
24
+ <PauseIcon size={18} />
25
+ {:else}
26
+ <PlayIcon size={18} />
27
+ {/if}
28
+ </button>
@@ -0,0 +1,210 @@
1
+ <script lang="ts">
2
+ import { getContext } from "svelte";
3
+ import { readable } from "svelte/store";
4
+ import type { Readable } from "svelte/store";
5
+ import { SettingsIcon } from "../icons";
6
+ import {
7
+ SPEED_PRESETS,
8
+ getAvailableLocales,
9
+ getLocaleDisplayName,
10
+ createTranslator,
11
+ type TranslateFn,
12
+ } from "@livepeer-frameworks/player-core";
13
+ import type { FwLocale } from "@livepeer-frameworks/player-core";
14
+
15
+ interface Props {
16
+ qualities?: Array<{ id: string; label: string; active?: boolean }>;
17
+ activeQuality?: string;
18
+ onSelectQuality?: (id: string) => void;
19
+ textTracks?: Array<{ id: string; label: string; active?: boolean }>;
20
+ activeCaption?: string;
21
+ onSelectCaption?: (id: string) => void;
22
+ playbackRate?: number;
23
+ onSpeedChange?: (rate: number) => void;
24
+ supportsSpeed?: boolean;
25
+ playbackMode?: "auto" | "low-latency" | "quality";
26
+ onModeChange?: (mode: "auto" | "low-latency" | "quality") => void;
27
+ showModeSelector?: boolean;
28
+ activeLocale?: FwLocale;
29
+ onLocaleChange?: (locale: FwLocale) => void;
30
+ }
31
+
32
+ let {
33
+ qualities: propQualities,
34
+ activeQuality,
35
+ onSelectQuality,
36
+ textTracks: propTextTracks,
37
+ activeCaption,
38
+ onSelectCaption,
39
+ playbackRate = 1,
40
+ onSpeedChange,
41
+ supportsSpeed = true,
42
+ playbackMode,
43
+ onModeChange,
44
+ showModeSelector = false,
45
+ activeLocale = undefined,
46
+ onLocaleChange = undefined,
47
+ }: Props = $props();
48
+
49
+ let availableLocales = getAvailableLocales();
50
+
51
+ let controller: any = getContext("fw-player-controller");
52
+ const translatorStore: Readable<TranslateFn> =
53
+ getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
54
+ readable(createTranslator({ locale: "en" }));
55
+ let t: TranslateFn = $derived($translatorStore);
56
+ let isOpen = $state(false);
57
+
58
+ let qualities = $derived(propQualities ?? controller?.getQualities?.() ?? []);
59
+ let qualityValue = $derived(activeQuality ?? qualities.find((q: any) => q.active)?.id ?? "auto");
60
+ let textTracks = $derived(propTextTracks ?? []);
61
+ let captionValue = $derived(activeCaption ?? textTracks.find((t: any) => t.active)?.id ?? "none");
62
+
63
+ function selectQuality(id: string) {
64
+ if (onSelectQuality) onSelectQuality(id);
65
+ else controller?.selectQuality?.(id);
66
+ isOpen = false;
67
+ }
68
+
69
+ function handleKeyDown(e: KeyboardEvent) {
70
+ if (e.key === "Escape") {
71
+ isOpen = false;
72
+ e.preventDefault();
73
+ return;
74
+ }
75
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
76
+ e.preventDefault();
77
+ const menu = e.currentTarget as HTMLElement;
78
+ const items = menu.querySelectorAll<HTMLButtonElement>("button");
79
+ if (!items.length) return;
80
+ const current = Array.from(items).indexOf(document.activeElement as HTMLButtonElement);
81
+ const next =
82
+ e.key === "ArrowDown"
83
+ ? (current + 1) % items.length
84
+ : (current - 1 + items.length) % items.length;
85
+ items[next]?.focus();
86
+ }
87
+ }
88
+ </script>
89
+
90
+ <div class="fw-control-group" style="position: relative">
91
+ <button
92
+ type="button"
93
+ class="fw-btn-flush group"
94
+ class:fw-btn-flush--active={isOpen}
95
+ aria-label={t("settings")}
96
+ title={t("settings")}
97
+ onclick={() => (isOpen = !isOpen)}
98
+ >
99
+ <SettingsIcon size={16} />
100
+ </button>
101
+
102
+ {#if isOpen}
103
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
104
+ <div class="fw-settings-menu" role="menu" aria-label={t("settings")} onkeydown={handleKeyDown}>
105
+ {#if showModeSelector && onModeChange}
106
+ <div class="fw-settings-section">
107
+ <div class="fw-settings-label">{t("mode")}</div>
108
+ <div class="fw-settings-options">
109
+ {#each ["auto", "low-latency", "quality"] as mode}
110
+ <button
111
+ class="fw-settings-btn"
112
+ class:fw-settings-btn--active={playbackMode === mode}
113
+ onclick={() => {
114
+ onModeChange?.(mode as any);
115
+ isOpen = false;
116
+ }}
117
+ >
118
+ {mode === "low-latency" ? t("fast") : mode === "quality" ? t("stable") : t("auto")}
119
+ </button>
120
+ {/each}
121
+ </div>
122
+ </div>
123
+ {/if}
124
+
125
+ {#if supportsSpeed}
126
+ <div class="fw-settings-section">
127
+ <div class="fw-settings-label">{t("speed")}</div>
128
+ <div class="fw-settings-options fw-settings-options--wrap">
129
+ {#each SPEED_PRESETS as rate}
130
+ <button
131
+ class="fw-settings-btn"
132
+ class:fw-settings-btn--active={playbackRate === rate}
133
+ onclick={() => {
134
+ onSpeedChange?.(rate);
135
+ isOpen = false;
136
+ }}
137
+ >
138
+ {rate}x
139
+ </button>
140
+ {/each}
141
+ </div>
142
+ </div>
143
+ {/if}
144
+
145
+ {#if qualities.length > 0}
146
+ <div class="fw-settings-section">
147
+ <div class="fw-settings-label">{t("quality")}</div>
148
+ <div class="fw-settings-list">
149
+ <button
150
+ class="fw-settings-list-item"
151
+ class:fw-settings-list-item--active={qualityValue === "auto"}
152
+ onclick={() => selectQuality("auto")}>{t("auto")}</button
153
+ >
154
+ {#each qualities as q}
155
+ <button
156
+ class="fw-settings-list-item"
157
+ class:fw-settings-list-item--active={qualityValue === q.id}
158
+ onclick={() => selectQuality(q.id)}>{q.label}</button
159
+ >
160
+ {/each}
161
+ </div>
162
+ </div>
163
+ {/if}
164
+
165
+ {#if textTracks.length > 0}
166
+ <div class="fw-settings-section">
167
+ <div class="fw-settings-label">{t("captions")}</div>
168
+ <div class="fw-settings-list">
169
+ <button
170
+ class="fw-settings-list-item"
171
+ class:fw-settings-list-item--active={captionValue === "none"}
172
+ onclick={() => {
173
+ onSelectCaption?.("none");
174
+ isOpen = false;
175
+ }}>{t("captionsOff")}</button
176
+ >
177
+ {#each textTracks as tt}
178
+ <button
179
+ class="fw-settings-list-item"
180
+ class:fw-settings-list-item--active={captionValue === tt.id}
181
+ onclick={() => {
182
+ onSelectCaption?.(tt.id);
183
+ isOpen = false;
184
+ }}>{tt.label || tt.id}</button
185
+ >
186
+ {/each}
187
+ </div>
188
+ </div>
189
+ {/if}
190
+
191
+ {#if onLocaleChange}
192
+ <div class="fw-settings-section">
193
+ <div class="fw-settings-label">{t("language")}</div>
194
+ <div class="fw-settings-list">
195
+ {#each availableLocales as l}
196
+ <button
197
+ class="fw-settings-list-item"
198
+ class:fw-settings-list-item--active={activeLocale === l}
199
+ onclick={() => {
200
+ onLocaleChange?.(l);
201
+ isOpen = false;
202
+ }}>{getLocaleDisplayName(l)}</button
203
+ >
204
+ {/each}
205
+ </div>
206
+ </div>
207
+ {/if}
208
+ </div>
209
+ {/if}
210
+ </div>
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import { getContext } from "svelte";
3
+ import { readable } from "svelte/store";
4
+ import type { Readable } from "svelte/store";
5
+ import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
6
+ import { SkipBackIcon, SkipForwardIcon } from "../icons";
7
+
8
+ interface Props {
9
+ direction: "back" | "forward";
10
+ seconds?: number;
11
+ }
12
+
13
+ let { direction, seconds = 10 }: Props = $props();
14
+ let pc: any = getContext("fw-player-controller");
15
+ const translatorStore: Readable<TranslateFn> =
16
+ getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
17
+ readable(createTranslator({ locale: "en" }));
18
+ let t: TranslateFn = $derived($translatorStore);
19
+
20
+ let label = $derived(direction === "back" ? t("skipBackward") : t("skipForward"));
21
+ </script>
22
+
23
+ <button
24
+ type="button"
25
+ class="fw-btn-flush"
26
+ aria-label={label}
27
+ title={label}
28
+ onclick={() =>
29
+ pc?.seek((pc?.currentTime ?? 0) + (direction === "back" ? -seconds * 1000 : seconds * 1000))}
30
+ >
31
+ {#if direction === "back"}
32
+ <SkipBackIcon size={16} />
33
+ {:else}
34
+ <SkipForwardIcon size={16} />
35
+ {/if}
36
+ </button>
@@ -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,28 @@
1
+ <script lang="ts">
2
+ import { getContext } from "svelte";
3
+ import { readable } from "svelte/store";
4
+ import type { Readable } from "svelte/store";
5
+ import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
6
+ import { VolumeUpIcon, VolumeOffIcon } from "../icons";
7
+
8
+ let pc: any = getContext("fw-player-controller");
9
+ const translatorStore: Readable<TranslateFn> =
10
+ getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
11
+ readable(createTranslator({ locale: "en" }));
12
+ let t: TranslateFn = $derived($translatorStore);
13
+ </script>
14
+
15
+ <button
16
+ type="button"
17
+ class="fw-btn-flush"
18
+ aria-label={pc?.isMuted ? t("unmute") : t("mute")}
19
+ aria-pressed={pc?.isMuted ?? false}
20
+ title={pc?.isMuted ? t("unmute") : t("mute")}
21
+ onclick={() => pc?.toggleMute()}
22
+ >
23
+ {#if pc?.isMuted}
24
+ <VolumeOffIcon size={16} />
25
+ {:else}
26
+ <VolumeUpIcon size={16} />
27
+ {/if}
28
+ </button>
@@ -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/src/index.ts CHANGED
@@ -48,6 +48,9 @@ export { default as ThumbnailOverlay } from "./ThumbnailOverlay.svelte";
48
48
  export { default as StatsPanel } from "./StatsPanel.svelte";
49
49
  export { default as DevModePanel } from "./DevModePanel.svelte";
50
50
 
51
+ // Composable control components
52
+ export * from "./controls";
53
+
51
54
  // Icon components
52
55
  export * from "./icons";
53
56
 
@@ -65,6 +68,9 @@ export {
65
68
  PlayerController,
66
69
  PlayerManager,
67
70
  globalPlayerManager,
71
+ createTranslator,
72
+ getAvailableLocales,
73
+ getLocaleDisplayName,
68
74
  } from "@livepeer-frameworks/player-core";
69
75
  export type {
70
76
  PlayerControllerConfig,
@@ -88,4 +94,8 @@ export type {
88
94
  PlayerCombination,
89
95
  PlayerManagerOptions,
90
96
  PlayerManagerEvents,
97
+ FwLocale,
98
+ TranslateFn,
99
+ TranslationStrings,
100
+ I18nConfig,
91
101
  } from "@livepeer-frameworks/player-core";
@@ -0,0 +1,7 @@
1
+ import { writable, derived } from "svelte/store";
2
+ import { createTranslator, type FwLocale } from "@livepeer-frameworks/player-core";
3
+
4
+ export const localeStore = writable<FwLocale>("en");
5
+ export const translatorStore = derived(localeStore, ($locale) =>
6
+ createTranslator({ locale: $locale })
7
+ );
@@ -71,6 +71,9 @@ export {
71
71
  type PlayerSelectionStore,
72
72
  } from "./playerSelection";
73
73
 
74
+ // i18n (locale + translator)
75
+ export { localeStore, translatorStore } from "./i18n";
76
+
74
77
  // PlayerController store (central orchestrator)
75
78
  export {
76
79
  createPlayerControllerStore,
@@ -35,9 +35,9 @@ export interface PlayerControllerState {
35
35
  metadata: ContentMetadata | null;
36
36
  /** Video element (null if not ready) */
37
37
  videoElement: HTMLVideoElement | null;
38
- /** Current time */
38
+ /** Current time in milliseconds */
39
39
  currentTime: number;
40
- /** Duration */
40
+ /** Duration in milliseconds */
41
41
  duration: number;
42
42
  /** Is playing */
43
43
  isPlaying: boolean;
@@ -102,9 +102,9 @@ export interface PlayerControllerStore extends Readable<PlayerControllerState> {
102
102
  pause: () => void;
103
103
  /** Toggle play/pause */
104
104
  togglePlay: () => void;
105
- /** Seek to time */
105
+ /** Seek to time (milliseconds) */
106
106
  seek: (time: number) => void;
107
- /** Seek by delta */
107
+ /** Seek by delta (milliseconds) */
108
108
  seekBy: (delta: number) => void;
109
109
  /** Jump to live edge (for live streams) */
110
110
  jumpToLive: () => void;
@@ -164,7 +164,7 @@ const initialState: PlayerControllerState = {
164
164
  isPlaying: false,
165
165
  isPaused: true,
166
166
  isBuffering: false,
167
- isMuted: true,
167
+ isMuted: false,
168
168
  volume: 1,
169
169
  error: null,
170
170
  errorDetails: null,