@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.
- package/dist/DevModePanel.svelte +34 -25
- package/dist/IdleScreen.svelte +14 -4
- package/dist/LoadingScreen.svelte +92 -50
- package/dist/LoadingScreen.svelte.bak +702 -0
- package/dist/Player.svelte +206 -55
- package/dist/Player.svelte.d.ts +6 -1
- package/dist/PlayerControls.svelte +159 -74
- package/dist/PlayerControls.svelte.d.ts +7 -0
- package/dist/SeekBar.svelte +15 -14
- package/dist/SeekBar.svelte.d.ts +4 -4
- package/dist/StreamStateOverlay.svelte +20 -6
- package/dist/SubtitleRenderer.svelte +3 -3
- package/dist/SubtitleRenderer.svelte.d.ts +1 -1
- package/dist/controls/FullscreenButton.svelte +28 -0
- package/dist/controls/FullscreenButton.svelte.d.ts +3 -0
- package/dist/controls/LiveBadge.svelte +25 -0
- package/dist/controls/LiveBadge.svelte.d.ts +3 -0
- package/dist/controls/PlayButton.svelte +28 -0
- package/dist/controls/PlayButton.svelte.d.ts +3 -0
- package/dist/controls/SettingsMenu.svelte +210 -0
- package/dist/controls/SettingsMenu.svelte.d.ts +28 -0
- package/dist/controls/SkipButton.svelte +36 -0
- package/dist/controls/SkipButton.svelte.d.ts +7 -0
- package/dist/controls/TimeDisplay.svelte +18 -0
- package/dist/controls/TimeDisplay.svelte.d.ts +3 -0
- package/dist/controls/VolumeControl.svelte +28 -0
- package/dist/controls/VolumeControl.svelte.d.ts +3 -0
- package/dist/controls/index.d.ts +7 -0
- package/dist/controls/index.js +7 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -1
- package/dist/stores/i18n.d.ts +3 -0
- package/dist/stores/i18n.js +4 -0
- package/dist/stores/index.d.ts +1 -0
- package/dist/stores/index.js +2 -0
- package/dist/stores/playerController.d.ts +4 -4
- package/dist/stores/playerController.js +1 -1
- package/package.json +8 -8
- package/src/DevModePanel.svelte +34 -25
- package/src/IdleScreen.svelte +14 -4
- package/src/LoadingScreen.svelte +92 -50
- package/src/LoadingScreen.svelte.bak +702 -0
- package/src/Player.svelte +206 -55
- package/src/PlayerControls.svelte +159 -74
- package/src/SeekBar.svelte +15 -14
- package/src/StreamStateOverlay.svelte +20 -6
- package/src/SubtitleRenderer.svelte +3 -3
- package/src/controls/FullscreenButton.svelte +28 -0
- package/src/controls/LiveBadge.svelte +25 -0
- package/src/controls/PlayButton.svelte +28 -0
- package/src/controls/SettingsMenu.svelte +210 -0
- package/src/controls/SkipButton.svelte +36 -0
- package/src/controls/TimeDisplay.svelte +18 -0
- package/src/controls/VolumeControl.svelte +28 -0
- package/src/controls/index.ts +7 -0
- package/src/index.ts +10 -0
- package/src/stores/i18n.ts +7 -0
- package/src/stores/index.ts +3 -0
- 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
|
+
);
|
package/src/stores/index.ts
CHANGED
|
@@ -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:
|
|
167
|
+
isMuted: false,
|
|
168
168
|
volume: 1,
|
|
169
169
|
error: null,
|
|
170
170
|
errorDetails: null,
|