@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.
- package/dist/DevModePanel.svelte +266 -127
- package/dist/DevModePanel.svelte.d.ts +1 -1
- package/dist/DvdLogo.svelte +17 -21
- package/dist/Icons.svelte +5 -3
- package/dist/Icons.svelte.d.ts +6 -19
- package/dist/IdleScreen.svelte +277 -186
- package/dist/IdleScreen.svelte.d.ts +1 -1
- package/dist/LoadingScreen.svelte +190 -162
- package/dist/Player.svelte +244 -111
- package/dist/Player.svelte.d.ts +1 -1
- package/dist/PlayerControls.svelte +263 -168
- package/dist/PlayerControls.svelte.d.ts +1 -1
- package/dist/SeekBar.svelte +61 -35
- package/dist/SkipIndicator.svelte +4 -4
- package/dist/SkipIndicator.svelte.d.ts +1 -1
- package/dist/SpeedIndicator.svelte +1 -1
- package/dist/StatsPanel.svelte +76 -57
- package/dist/StatsPanel.svelte.d.ts +1 -1
- package/dist/StreamStateOverlay.svelte +143 -107
- package/dist/StreamStateOverlay.svelte.d.ts +1 -1
- package/dist/SubtitleRenderer.svelte +46 -43
- package/dist/ThumbnailOverlay.svelte +22 -19
- package/dist/TitleOverlay.svelte +6 -11
- package/dist/components/VolumeIcons.svelte +12 -6
- package/dist/global.d.ts +3 -3
- package/dist/icons/FullscreenExitIcon.svelte +1 -5
- package/dist/icons/FullscreenIcon.svelte +1 -5
- package/dist/icons/PauseIcon.svelte +1 -5
- package/dist/icons/PictureInPictureIcon.svelte +12 -6
- package/dist/icons/PlayIcon.svelte +1 -5
- package/dist/icons/SeekToLiveIcon.svelte +1 -5
- package/dist/icons/SettingsIcon.svelte +1 -5
- package/dist/icons/SkipBackIcon.svelte +1 -5
- package/dist/icons/SkipForwardIcon.svelte +1 -5
- package/dist/icons/StatsIcon.svelte +1 -5
- package/dist/icons/VolumeOffIcon.svelte +1 -5
- package/dist/icons/VolumeUpIcon.svelte +1 -5
- package/dist/icons/index.d.ts +12 -12
- package/dist/icons/index.js +12 -12
- package/dist/index.d.ts +24 -24
- package/dist/index.js +21 -21
- package/dist/stores/index.d.ts +6 -6
- package/dist/stores/index.js +6 -6
- package/dist/stores/playbackQuality.d.ts +2 -2
- package/dist/stores/playbackQuality.js +7 -7
- package/dist/stores/playerContext.d.ts +2 -2
- package/dist/stores/playerContext.js +17 -17
- package/dist/stores/playerController.d.ts +13 -4
- package/dist/stores/playerController.js +80 -56
- package/dist/stores/playerSelection.d.ts +2 -2
- package/dist/stores/playerSelection.js +7 -7
- package/dist/stores/streamState.d.ts +2 -2
- package/dist/stores/streamState.js +56 -56
- package/dist/stores/viewerEndpoints.d.ts +3 -3
- package/dist/stores/viewerEndpoints.js +21 -21
- package/dist/types.d.ts +1 -1
- package/dist/ui/Badge.svelte +9 -10
- package/dist/ui/Badge.svelte.d.ts +8 -29
- package/dist/ui/Button.svelte +16 -16
- package/dist/ui/Button.svelte.d.ts +8 -29
- package/dist/ui/Slider.svelte +21 -55
- package/dist/ui/badge.js +1 -1
- package/dist/ui/button.js +2 -2
- package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte +5 -7
- package/dist/ui/context-menu/ContextMenuCheckboxItem.svelte.d.ts +6 -27
- package/dist/ui/context-menu/ContextMenuContent.svelte +2 -9
- package/dist/ui/context-menu/ContextMenuItem.svelte +1 -5
- package/dist/ui/context-menu/ContextMenuLabel.svelte +1 -5
- package/dist/ui/context-menu/ContextMenuRadioItem.svelte +5 -7
- package/dist/ui/context-menu/ContextMenuRadioItem.svelte.d.ts +6 -27
- package/dist/ui/context-menu/ContextMenuSeparator.svelte +2 -8
- package/dist/ui/context-menu/ContextMenuShortcut.svelte +2 -12
- package/dist/ui/context-menu/ContextMenuSubContent.svelte +1 -5
- package/package.json +15 -7
- package/src/DevModePanel.svelte +1 -0
- package/src/Icons.svelte +5 -3
- package/src/IdleScreen.svelte +21 -14
- package/src/LoadingScreen.svelte +20 -13
- package/src/Player.svelte +48 -2
- package/src/PlayerControls.svelte +36 -17
- package/src/SeekBar.svelte +33 -0
- package/src/StreamStateOverlay.svelte +2 -2
- package/src/stores/playerController.ts +39 -1
- package/src/stores/viewerEndpoints.ts +1 -1
- package/src/ui/Badge.svelte +7 -4
- package/src/ui/Button.svelte +13 -13
- package/src/ui/context-menu/ContextMenuCheckboxItem.svelte +4 -2
- package/src/ui/context-menu/ContextMenuRadioItem.svelte +4 -2
package/dist/Player.svelte
CHANGED
|
@@ -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
|
|
7
|
-
import IdleScreen from
|
|
8
|
-
import SubtitleRenderer from
|
|
9
|
-
import PlayerControls from
|
|
10
|
-
import SpeedIndicator from
|
|
11
|
-
import SkipIndicator from
|
|
12
|
-
import TitleOverlay from
|
|
13
|
-
import StatsPanel from
|
|
14
|
-
import DevModePanel from
|
|
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
|
|
23
|
-
import { StatsIcon, SettingsIcon, PictureInPictureIcon } from
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
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(
|
|
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:
|
|
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 ?
|
|
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(
|
|
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(
|
|
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(
|
|
188
|
+
debug(
|
|
189
|
+
`$effect - containerRef: ${!!containerRef}, playerStore: ${!!playerStore}, hasAttached: ${hasAttached}`
|
|
190
|
+
);
|
|
171
191
|
if (containerRef && playerStore && !hasAttached) {
|
|
172
192
|
hasAttached = true;
|
|
173
|
-
debug(
|
|
174
|
-
playerStore
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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: {
|
|
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 ===
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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 !==
|
|
272
|
+
!storeState.endpoints?.primary && storeState.state !== "booting"
|
|
237
273
|
);
|
|
238
274
|
|
|
239
275
|
let waitingMessage = $derived(
|
|
240
276
|
options?.gatewayUrl
|
|
241
|
-
?
|
|
242
|
-
|
|
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
|
-
|
|
248
|
-
|
|
285
|
+
<ContextMenuTrigger>
|
|
286
|
+
{#snippet child({ props })}
|
|
249
287
|
<div
|
|
250
288
|
bind:this={playerRootRef}
|
|
251
289
|
{...props}
|
|
252
290
|
class={cn(
|
|
253
|
-
|
|
254
|
-
options?.devMode &&
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
|
336
|
-
|
|
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
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
357
|
-
storeState.isPassiveError
|
|
412
|
+
"fw-error-overlay",
|
|
413
|
+
storeState.isPassiveError
|
|
414
|
+
? "fw-error-overlay--passive"
|
|
415
|
+
: "fw-error-overlay--fullscreen"
|
|
358
416
|
)}
|
|
359
417
|
>
|
|
360
|
-
<div
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
|
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={() => {
|
|
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
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
<
|
|
469
|
-
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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>
|
package/dist/Player.svelte.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type PlaybackMode, type ContentEndpoints, type PlayerState, type PlayerStateContext, type ContentType, type PlayerMetadata } from
|
|
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;
|