@livepeer-frameworks/player-svelte 0.2.1 → 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 +20 -10
- package/dist/IdleScreen.svelte +5 -3
- package/dist/LoadingScreen.svelte +5 -3
- package/dist/Player.svelte +18 -14
- package/dist/PlayerControls.svelte +53 -45
- package/dist/PlayerControls.svelte.d.ts +4 -0
- package/dist/SeekBar.svelte +15 -14
- package/dist/SeekBar.svelte.d.ts +4 -4
- package/dist/StreamStateOverlay.svelte +6 -4
- package/dist/SubtitleRenderer.svelte +3 -3
- package/dist/SubtitleRenderer.svelte.d.ts +1 -1
- package/dist/controls/FullscreenButton.svelte +5 -3
- package/dist/controls/LiveBadge.svelte +5 -3
- package/dist/controls/PlayButton.svelte +5 -3
- package/dist/controls/SettingsMenu.svelte +5 -3
- package/dist/controls/SkipButton.svelte +7 -4
- package/dist/controls/VolumeControl.svelte +5 -3
- package/dist/stores/playerController.d.ts +4 -4
- package/dist/stores/playerController.js +1 -1
- package/package.json +2 -2
- package/src/DevModePanel.svelte +20 -10
- package/src/IdleScreen.svelte +5 -3
- package/src/LoadingScreen.svelte +5 -3
- package/src/Player.svelte +18 -14
- package/src/PlayerControls.svelte +53 -45
- package/src/SeekBar.svelte +15 -14
- package/src/StreamStateOverlay.svelte +6 -4
- package/src/SubtitleRenderer.svelte +3 -3
- package/src/controls/FullscreenButton.svelte +5 -3
- package/src/controls/LiveBadge.svelte +5 -3
- package/src/controls/PlayButton.svelte +5 -3
- package/src/controls/SettingsMenu.svelte +5 -3
- package/src/controls/SkipButton.svelte +7 -4
- package/src/controls/VolumeControl.svelte +5 -3
- package/src/stores/playerController.ts +5 -5
package/dist/DevModePanel.svelte
CHANGED
|
@@ -375,7 +375,9 @@
|
|
|
375
375
|
{:else}
|
|
376
376
|
{#each allCombinations as combo, index}
|
|
377
377
|
{@const isCodecIncompat = (combo as any).codecIncompatible === true}
|
|
378
|
-
{@const
|
|
378
|
+
{@const isPartial = ((combo as any).missingTracks?.length ?? 0) > 0}
|
|
379
|
+
{@const isWarn = isCodecIncompat || isPartial}
|
|
380
|
+
{@const shouldShow = combo.compatible || isWarn || showDisabledPlayers}
|
|
379
381
|
{@const isActive = activeComboIndex === index}
|
|
380
382
|
{@const typeLabel =
|
|
381
383
|
SOURCE_TYPE_LABELS[combo.sourceType] || combo.sourceType.split("/").pop()}
|
|
@@ -396,8 +398,8 @@
|
|
|
396
398
|
class={cn(
|
|
397
399
|
"fw-dev-combo-btn",
|
|
398
400
|
isActive && "fw-dev-combo-btn--active",
|
|
399
|
-
!combo.compatible && !
|
|
400
|
-
|
|
401
|
+
!combo.compatible && !isWarn && "fw-dev-combo-btn--disabled",
|
|
402
|
+
isWarn && "fw-dev-combo-btn--codec-warn"
|
|
401
403
|
)}
|
|
402
404
|
>
|
|
403
405
|
<!-- Rank -->
|
|
@@ -406,14 +408,14 @@
|
|
|
406
408
|
"fw-dev-combo-rank",
|
|
407
409
|
isActive
|
|
408
410
|
? "fw-dev-combo-rank--active"
|
|
409
|
-
: !combo.compatible && !
|
|
411
|
+
: !combo.compatible && !isWarn
|
|
410
412
|
? "fw-dev-combo-rank--disabled"
|
|
411
|
-
:
|
|
413
|
+
: isWarn
|
|
412
414
|
? "fw-dev-combo-rank--warn"
|
|
413
415
|
: ""
|
|
414
416
|
)}
|
|
415
417
|
>
|
|
416
|
-
{combo.compatible ? index + 1 :
|
|
418
|
+
{combo.compatible && !isPartial ? index + 1 : isWarn ? "⚠" : "—"}
|
|
417
419
|
</span>
|
|
418
420
|
<!-- Player + Protocol -->
|
|
419
421
|
<span class="fw-dev-combo-name">
|
|
@@ -422,8 +424,8 @@
|
|
|
422
424
|
<span
|
|
423
425
|
class={cn(
|
|
424
426
|
"fw-dev-combo-type",
|
|
425
|
-
|
|
426
|
-
!combo.compatible && !
|
|
427
|
+
isWarn && "fw-dev-combo-type--warn",
|
|
428
|
+
!combo.compatible && !isWarn && "fw-dev-combo-type--disabled"
|
|
427
429
|
)}>{typeLabel}</span
|
|
428
430
|
>
|
|
429
431
|
</span>
|
|
@@ -431,9 +433,9 @@
|
|
|
431
433
|
<span
|
|
432
434
|
class={cn(
|
|
433
435
|
"fw-dev-combo-score",
|
|
434
|
-
!combo.compatible && !
|
|
436
|
+
!combo.compatible && !isWarn
|
|
435
437
|
? "fw-dev-combo-score--disabled"
|
|
436
|
-
:
|
|
438
|
+
: isWarn
|
|
437
439
|
? "fw-dev-combo-score--low"
|
|
438
440
|
: combo.score >= 2
|
|
439
441
|
? "fw-dev-combo-score--high"
|
|
@@ -463,6 +465,14 @@
|
|
|
463
465
|
</div>
|
|
464
466
|
{/if}
|
|
465
467
|
</div>
|
|
468
|
+
{#if combo.note}
|
|
469
|
+
<div class="fw-dev-tooltip-note">{combo.note}</div>
|
|
470
|
+
{/if}
|
|
471
|
+
{#if isPartial}
|
|
472
|
+
<div class="fw-dev-tooltip-note">
|
|
473
|
+
No compatible {(combo as any).missingTracks.join(", ")} codec
|
|
474
|
+
</div>
|
|
475
|
+
{/if}
|
|
466
476
|
{#if combo.compatible && combo.scoreBreakdown}
|
|
467
477
|
<div class="fw-dev-tooltip-score">Score: {combo.score.toFixed(2)}</div>
|
|
468
478
|
<div class="fw-dev-tooltip-row">
|
package/dist/IdleScreen.svelte
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
-->
|
|
15
15
|
<script lang="ts">
|
|
16
16
|
import { onMount, onDestroy, getContext } from "svelte";
|
|
17
|
+
import { readable } from "svelte/store";
|
|
17
18
|
import type { Readable } from "svelte/store";
|
|
18
19
|
import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
|
|
19
20
|
import type { StreamStatus } from "@livepeer-frameworks/player-core";
|
|
@@ -28,9 +29,10 @@
|
|
|
28
29
|
onRetry?: () => void;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const translatorStore: Readable<TranslateFn> =
|
|
33
|
+
getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
|
|
34
|
+
readable(createTranslator({ locale: "en" }));
|
|
35
|
+
let t: TranslateFn = $derived($translatorStore);
|
|
34
36
|
|
|
35
37
|
let {
|
|
36
38
|
status = "OFFLINE",
|
|
@@ -13,14 +13,16 @@
|
|
|
13
13
|
-->
|
|
14
14
|
<script lang="ts">
|
|
15
15
|
import { onMount, onDestroy, getContext } from "svelte";
|
|
16
|
+
import { readable } from "svelte/store";
|
|
16
17
|
import type { Readable } from "svelte/store";
|
|
17
18
|
import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
|
|
18
19
|
import DvdLogo from "./DvdLogo.svelte";
|
|
19
20
|
import logomarkAsset from "./assets/logomark.svg";
|
|
20
21
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const translatorStore: Readable<TranslateFn> =
|
|
23
|
+
getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
|
|
24
|
+
readable(createTranslator({ locale: "en" }));
|
|
25
|
+
let t: TranslateFn = $derived($translatorStore);
|
|
24
26
|
|
|
25
27
|
interface Props {
|
|
26
28
|
message?: string;
|
package/dist/Player.svelte
CHANGED
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
isPlaying: false,
|
|
203
203
|
isPaused: true,
|
|
204
204
|
isBuffering: false,
|
|
205
|
-
isMuted:
|
|
205
|
+
isMuted: false,
|
|
206
206
|
volume: 1,
|
|
207
207
|
error: null as string | null,
|
|
208
208
|
isPassiveError: false,
|
|
@@ -247,7 +247,7 @@
|
|
|
247
247
|
mistUrl: options?.mistUrl,
|
|
248
248
|
authToken: options?.authToken,
|
|
249
249
|
autoplay: options?.autoplay !== false,
|
|
250
|
-
muted: options?.muted
|
|
250
|
+
muted: options?.muted === true,
|
|
251
251
|
controls: options?.stockControls === true,
|
|
252
252
|
poster: thumbnailUrl || undefined,
|
|
253
253
|
debug: options?.debug,
|
|
@@ -587,7 +587,7 @@
|
|
|
587
587
|
>
|
|
588
588
|
{$translatorStore("retry")}
|
|
589
589
|
</button>
|
|
590
|
-
{#if playerStore?.getController()?.canAttemptFallback()}
|
|
590
|
+
{#if options?.devMode && playerStore?.getController()?.canAttemptFallback()}
|
|
591
591
|
<button
|
|
592
592
|
type="button"
|
|
593
593
|
class="fw-error-btn fw-error-btn--secondary"
|
|
@@ -600,17 +600,19 @@
|
|
|
600
600
|
{$translatorStore("tryNext")}
|
|
601
601
|
</button>
|
|
602
602
|
{/if}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
603
|
+
{#if options?.devMode}
|
|
604
|
+
<button
|
|
605
|
+
type="button"
|
|
606
|
+
class="fw-error-btn fw-error-btn--secondary"
|
|
607
|
+
onclick={() => {
|
|
608
|
+
playerStore?.clearError();
|
|
609
|
+
playerStore?.reload();
|
|
610
|
+
}}
|
|
611
|
+
aria-label={$translatorStore("reloadPlayer")}
|
|
612
|
+
>
|
|
613
|
+
{$translatorStore("reloadPlayer")}
|
|
614
|
+
</button>
|
|
615
|
+
{/if}
|
|
614
616
|
</div>
|
|
615
617
|
</div>
|
|
616
618
|
</div>
|
|
@@ -665,6 +667,8 @@
|
|
|
665
667
|
onStatsToggle={() => (isStatsOpen = !isStatsOpen)}
|
|
666
668
|
isContentLive={storeState.isEffectivelyLive}
|
|
667
669
|
onJumpToLive={() => playerStore?.getController()?.jumpToLive()}
|
|
670
|
+
controllerSeekableStart={playerStore?.getController()?.getSeekableStart()}
|
|
671
|
+
controllerLiveEdge={playerStore?.getController()?.getLiveEdge()}
|
|
668
672
|
{activeLocale}
|
|
669
673
|
onLocaleChange={(l) => {
|
|
670
674
|
activeLocale = l;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getContext } from "svelte";
|
|
3
|
+
import { readable } from "svelte/store";
|
|
3
4
|
import type { Readable } from "svelte/store";
|
|
4
5
|
import {
|
|
5
6
|
cn,
|
|
@@ -39,9 +40,10 @@
|
|
|
39
40
|
} from "./icons";
|
|
40
41
|
|
|
41
42
|
// i18n: get translator from context, fall back to default English
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const translatorStore: Readable<TranslateFn> =
|
|
44
|
+
getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
|
|
45
|
+
readable(createTranslator({ locale: "en" }));
|
|
46
|
+
let t: TranslateFn = $derived($translatorStore);
|
|
45
47
|
|
|
46
48
|
// Props - aligned with React PlayerControls
|
|
47
49
|
interface Props {
|
|
@@ -63,6 +65,10 @@
|
|
|
63
65
|
onJumpToLive?: () => void;
|
|
64
66
|
activeLocale?: FwLocale;
|
|
65
67
|
onLocaleChange?: (locale: FwLocale) => void;
|
|
68
|
+
/** Controller-derived seekable start (ms) — preferred over player direct */
|
|
69
|
+
controllerSeekableStart?: number;
|
|
70
|
+
/** Controller-derived live edge (ms) — preferred over player direct */
|
|
71
|
+
controllerLiveEdge?: number;
|
|
66
72
|
}
|
|
67
73
|
|
|
68
74
|
let {
|
|
@@ -82,6 +88,8 @@
|
|
|
82
88
|
onJumpToLive = undefined,
|
|
83
89
|
activeLocale = undefined,
|
|
84
90
|
onLocaleChange = undefined,
|
|
91
|
+
controllerSeekableStart = undefined,
|
|
92
|
+
controllerLiveEdge = undefined,
|
|
85
93
|
}: Props = $props();
|
|
86
94
|
|
|
87
95
|
// Video element discovery
|
|
@@ -224,16 +232,22 @@
|
|
|
224
232
|
let isWebRTC = $derived(isMediaStreamSource(video));
|
|
225
233
|
let supportsPlaybackRate = $derived(coreSupportsPlaybackRate(video));
|
|
226
234
|
function deriveBufferWindowMs(
|
|
227
|
-
tracks?: Record<string, { firstms?: number; lastms?: number }>
|
|
235
|
+
tracks?: Record<string, { type?: string; firstms?: number; lastms?: number }>
|
|
228
236
|
): number | undefined {
|
|
229
237
|
if (!tracks) return undefined;
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
238
|
+
const trackValues = Object.values(tracks).filter(
|
|
239
|
+
(t) => t.type !== "meta" && (t.lastms === undefined || t.lastms > 0)
|
|
240
|
+
);
|
|
241
|
+
if (trackValues.length === 0) return undefined;
|
|
242
|
+
const firstmsValues = trackValues
|
|
243
|
+
.map((t) => t.firstms)
|
|
244
|
+
.filter((v): v is number => v !== undefined);
|
|
245
|
+
const lastmsValues = trackValues
|
|
246
|
+
.map((t) => t.lastms)
|
|
247
|
+
.filter((v): v is number => v !== undefined);
|
|
234
248
|
if (firstmsValues.length === 0 || lastmsValues.length === 0) return undefined;
|
|
235
|
-
const firstms = Math.
|
|
236
|
-
const lastms = Math.
|
|
249
|
+
const firstms = Math.min(...firstmsValues);
|
|
250
|
+
const lastms = Math.max(...lastmsValues);
|
|
237
251
|
const window = lastms - firstms;
|
|
238
252
|
if (!Number.isFinite(window) || window <= 0) return undefined;
|
|
239
253
|
return window;
|
|
@@ -243,27 +257,11 @@
|
|
|
243
257
|
mistStreamInfo?.meta?.buffer_window ??
|
|
244
258
|
deriveBufferWindowMs(
|
|
245
259
|
mistStreamInfo?.meta?.tracks as
|
|
246
|
-
| Record<string, { firstms?: number; lastms?: number }>
|
|
260
|
+
| Record<string, { type?: string; firstms?: number; lastms?: number }>
|
|
247
261
|
| undefined
|
|
248
262
|
)
|
|
249
263
|
);
|
|
250
264
|
|
|
251
|
-
function getPlayerSeekableRange(): { seekableStart: number; liveEdge: number } | null {
|
|
252
|
-
const player = globalPlayerManager.getCurrentPlayer();
|
|
253
|
-
if (player && typeof (player as any).getSeekableRange === "function") {
|
|
254
|
-
const range = (player as any).getSeekableRange();
|
|
255
|
-
if (
|
|
256
|
-
range &&
|
|
257
|
-
Number.isFinite(range.start) &&
|
|
258
|
-
Number.isFinite(range.end) &&
|
|
259
|
-
range.end >= range.start
|
|
260
|
-
) {
|
|
261
|
-
return { seekableStart: range.start, liveEdge: range.end };
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
265
|
let allowMediaStreamDvr = $derived(
|
|
268
266
|
isMediaStreamSource(video) &&
|
|
269
267
|
bufferWindowMs !== undefined &&
|
|
@@ -272,19 +270,29 @@
|
|
|
272
270
|
sourceType !== "webrtc"
|
|
273
271
|
);
|
|
274
272
|
|
|
275
|
-
// Seekable range
|
|
276
|
-
let
|
|
277
|
-
(
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
273
|
+
// Seekable range: prefer controller-derived values (same pattern as React/WC)
|
|
274
|
+
let calcRange = $derived(
|
|
275
|
+
calculateSeekableRange({
|
|
276
|
+
isLive,
|
|
277
|
+
video,
|
|
278
|
+
mistStreamInfo,
|
|
279
|
+
currentTime,
|
|
280
|
+
duration,
|
|
281
|
+
allowMediaStreamDvr,
|
|
282
|
+
})
|
|
283
|
+
);
|
|
284
|
+
let useControllerRange = $derived(
|
|
285
|
+
Number.isFinite(controllerSeekableStart) &&
|
|
286
|
+
Number.isFinite(controllerLiveEdge) &&
|
|
287
|
+
(controllerLiveEdge as number) >= (controllerSeekableStart as number) &&
|
|
288
|
+
((controllerLiveEdge as number) > 0 || (controllerSeekableStart as number) > 0)
|
|
287
289
|
);
|
|
290
|
+
let seekableRange = $derived({
|
|
291
|
+
seekableStart: useControllerRange
|
|
292
|
+
? (controllerSeekableStart as number)
|
|
293
|
+
: calcRange.seekableStart,
|
|
294
|
+
liveEdge: useControllerRange ? (controllerLiveEdge as number) : calcRange.liveEdge,
|
|
295
|
+
});
|
|
288
296
|
let seekableStart = $derived(seekableRange.seekableStart);
|
|
289
297
|
let liveEdge = $derived(seekableRange.liveEdge);
|
|
290
298
|
let hasDvrWindow = $derived(
|
|
@@ -422,24 +430,24 @@
|
|
|
422
430
|
}
|
|
423
431
|
|
|
424
432
|
function handleSkipBack() {
|
|
425
|
-
const newTime = Math.max(0, currentTime -
|
|
433
|
+
const newTime = Math.max(0, currentTime - 10000);
|
|
426
434
|
if (onseek) {
|
|
427
435
|
onseek(newTime);
|
|
428
436
|
return;
|
|
429
437
|
}
|
|
430
438
|
const v = findVideoElement();
|
|
431
|
-
if (v) v.currentTime = newTime;
|
|
439
|
+
if (v) v.currentTime = newTime / 1000;
|
|
432
440
|
}
|
|
433
441
|
|
|
434
442
|
function handleSkipForward() {
|
|
435
|
-
const maxTime = Number.isFinite(duration) ? duration : currentTime +
|
|
436
|
-
const newTime = Math.min(maxTime, currentTime +
|
|
443
|
+
const maxTime = Number.isFinite(duration) ? duration : currentTime + 10000;
|
|
444
|
+
const newTime = Math.min(maxTime, currentTime + 10000);
|
|
437
445
|
if (onseek) {
|
|
438
446
|
onseek(newTime);
|
|
439
447
|
return;
|
|
440
448
|
}
|
|
441
449
|
const v = findVideoElement();
|
|
442
|
-
if (v) v.currentTime = newTime;
|
|
450
|
+
if (v) v.currentTime = newTime / 1000;
|
|
443
451
|
}
|
|
444
452
|
|
|
445
453
|
function handleMute() {
|
|
@@ -592,7 +600,7 @@
|
|
|
592
600
|
if (onseek) {
|
|
593
601
|
onseek(time);
|
|
594
602
|
} else if (video) {
|
|
595
|
-
video.currentTime = time;
|
|
603
|
+
video.currentTime = time / 1000;
|
|
596
604
|
}
|
|
597
605
|
}}
|
|
598
606
|
/>
|
|
@@ -19,6 +19,10 @@ interface Props {
|
|
|
19
19
|
onJumpToLive?: () => void;
|
|
20
20
|
activeLocale?: FwLocale;
|
|
21
21
|
onLocaleChange?: (locale: FwLocale) => void;
|
|
22
|
+
/** Controller-derived seekable start (ms) — preferred over player direct */
|
|
23
|
+
controllerSeekableStart?: number;
|
|
24
|
+
/** Controller-derived live edge (ms) — preferred over player direct */
|
|
25
|
+
controllerLiveEdge?: number;
|
|
22
26
|
}
|
|
23
27
|
declare const PlayerControls: import("svelte").Component<Props, {}, "">;
|
|
24
28
|
type PlayerControls = ReturnType<typeof PlayerControls>;
|
package/dist/SeekBar.svelte
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
import { cn } from "@livepeer-frameworks/player-core";
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
|
-
/** Current playback time in
|
|
9
|
+
/** Current playback time in milliseconds */
|
|
10
10
|
currentTime: number;
|
|
11
|
-
/** Total duration in
|
|
11
|
+
/** Total duration in milliseconds */
|
|
12
12
|
duration: number;
|
|
13
13
|
/** Buffered time ranges from video element */
|
|
14
14
|
buffered?: TimeRanges;
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
class?: string;
|
|
21
21
|
/** Whether this is a live stream */
|
|
22
22
|
isLive?: boolean;
|
|
23
|
-
/** For live: start of seekable DVR window (
|
|
23
|
+
/** For live: start of seekable DVR window (ms) */
|
|
24
24
|
seekableStart?: number;
|
|
25
|
-
/** For live: current live edge position (
|
|
25
|
+
/** For live: current live edge position (ms) */
|
|
26
26
|
liveEdge?: number;
|
|
27
27
|
/** Defer seeking until drag release */
|
|
28
28
|
commitOnRelease?: boolean;
|
|
@@ -80,8 +80,9 @@
|
|
|
80
80
|
|
|
81
81
|
const segments: Array<{ startPercent: number; endPercent: number }> = [];
|
|
82
82
|
for (let i = 0; i < buffered.length; i++) {
|
|
83
|
-
|
|
84
|
-
const
|
|
83
|
+
// buffered TimeRanges are in seconds (browser API), convert to ms
|
|
84
|
+
const start = buffered.start(i) * 1000;
|
|
85
|
+
const end = buffered.end(i) * 1000;
|
|
85
86
|
|
|
86
87
|
const relativeStart = start - rangeStart;
|
|
87
88
|
const relativeEnd = end - rangeStart;
|
|
@@ -95,9 +96,9 @@
|
|
|
95
96
|
});
|
|
96
97
|
|
|
97
98
|
// Format time as MM:SS or HH:MM:SS
|
|
98
|
-
function formatTime(
|
|
99
|
-
if (!Number.isFinite(
|
|
100
|
-
const total = Math.floor(
|
|
99
|
+
function formatTime(ms: number): string {
|
|
100
|
+
if (!Number.isFinite(ms) || ms < 0) return "0:00";
|
|
101
|
+
const total = Math.floor(ms / 1000);
|
|
101
102
|
const hours = Math.floor(total / 3600);
|
|
102
103
|
const minutes = Math.floor((total % 3600) / 60);
|
|
103
104
|
const secs = total % 60;
|
|
@@ -108,10 +109,10 @@
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
// Format relative time for live streams
|
|
111
|
-
function formatLiveTime(
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
114
|
-
const total = Math.floor(
|
|
112
|
+
function formatLiveTime(ms: number, edgeMs: number): string {
|
|
113
|
+
const behindMs = edgeMs - ms;
|
|
114
|
+
if (behindMs < 1000) return "LIVE";
|
|
115
|
+
const total = Math.floor(behindMs / 1000);
|
|
115
116
|
const hours = Math.floor(total / 3600);
|
|
116
117
|
const minutes = Math.floor((total % 3600) / 60);
|
|
117
118
|
const secs = total % 60;
|
|
@@ -218,7 +219,7 @@
|
|
|
218
219
|
// Handle keyboard navigation for accessibility
|
|
219
220
|
function handleKeyDown(e: KeyboardEvent) {
|
|
220
221
|
if (disabled) return;
|
|
221
|
-
const step = e.shiftKey ?
|
|
222
|
+
const step = e.shiftKey ? 10000 : 5000;
|
|
222
223
|
const rangeEnd = isLive ? effectiveLiveEdge : duration;
|
|
223
224
|
const rangeStart = isLive ? seekableStart : 0;
|
|
224
225
|
|
package/dist/SeekBar.svelte.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
interface Props {
|
|
2
|
-
/** Current playback time in
|
|
2
|
+
/** Current playback time in milliseconds */
|
|
3
3
|
currentTime: number;
|
|
4
|
-
/** Total duration in
|
|
4
|
+
/** Total duration in milliseconds */
|
|
5
5
|
duration: number;
|
|
6
6
|
/** Buffered time ranges from video element */
|
|
7
7
|
buffered?: TimeRanges;
|
|
@@ -13,9 +13,9 @@ interface Props {
|
|
|
13
13
|
class?: string;
|
|
14
14
|
/** Whether this is a live stream */
|
|
15
15
|
isLive?: boolean;
|
|
16
|
-
/** For live: start of seekable DVR window (
|
|
16
|
+
/** For live: start of seekable DVR window (ms) */
|
|
17
17
|
seekableStart?: number;
|
|
18
|
-
/** For live: current live edge position (
|
|
18
|
+
/** For live: current live edge position (ms) */
|
|
19
19
|
liveEdge?: number;
|
|
20
20
|
/** Defer seeking until drag release */
|
|
21
21
|
commitOnRelease?: boolean;
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
-->
|
|
12
12
|
<script lang="ts">
|
|
13
13
|
import { getContext } from "svelte";
|
|
14
|
+
import { readable } from "svelte/store";
|
|
14
15
|
import type { Readable } from "svelte/store";
|
|
15
16
|
import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
|
|
16
17
|
import type { StreamStatus } from "@livepeer-frameworks/player-core";
|
|
@@ -39,9 +40,10 @@
|
|
|
39
40
|
class: className = "",
|
|
40
41
|
}: Props = $props();
|
|
41
42
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const translatorStore: Readable<TranslateFn> =
|
|
44
|
+
getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
|
|
45
|
+
readable(createTranslator({ locale: "en" }));
|
|
46
|
+
let t: TranslateFn = $derived($translatorStore);
|
|
45
47
|
|
|
46
48
|
// Computed states
|
|
47
49
|
let showRetry = $derived(status === "ERROR" || status === "INVALID" || status === "OFFLINE");
|
|
@@ -136,7 +138,7 @@
|
|
|
136
138
|
{#if showProgress && percentage !== undefined}
|
|
137
139
|
<div style="margin-top: 0.75rem;">
|
|
138
140
|
<div class="progress-bar">
|
|
139
|
-
<div class="progress-fill" style="width: {Math.min(100, percentage)}
|
|
141
|
+
<div class="progress-fill" style="width: {Math.min(100, percentage)}%;"></div>
|
|
140
142
|
</div>
|
|
141
143
|
<p
|
|
142
144
|
style="margin-top: 0.375rem; font-size: 0.75rem; font-family: monospace; color: hsl(var(--tn-fg-dark, 233 23% 60%));"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
interface Props {
|
|
40
|
-
/** Current video playback time in
|
|
40
|
+
/** Current video playback time in milliseconds */
|
|
41
41
|
currentTime: number;
|
|
42
42
|
/** Whether subtitles are enabled */
|
|
43
43
|
enabled?: boolean;
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
return;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
const currentTimeMs = currentTime
|
|
163
|
+
const currentTimeMs = currentTime;
|
|
164
164
|
const activeCue = allCues.find((cue) => {
|
|
165
165
|
const start = cue.startTime;
|
|
166
166
|
const end = cue.endTime;
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
|
|
179
179
|
// Clean up expired cues
|
|
180
180
|
$effect(() => {
|
|
181
|
-
const currentTimeMs = currentTime
|
|
181
|
+
const currentTimeMs = currentTime;
|
|
182
182
|
|
|
183
183
|
liveCues = liveCues.filter((cue) => {
|
|
184
184
|
const endTime = cue.endTime === Infinity ? cue.startTime + 10000 : cue.endTime;
|
|
@@ -21,7 +21,7 @@ interface SubtitleStyle {
|
|
|
21
21
|
borderRadius?: string;
|
|
22
22
|
}
|
|
23
23
|
interface Props {
|
|
24
|
-
/** Current video playback time in
|
|
24
|
+
/** Current video playback time in milliseconds */
|
|
25
25
|
currentTime: number;
|
|
26
26
|
/** Whether subtitles are enabled */
|
|
27
27
|
enabled?: boolean;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getContext } from "svelte";
|
|
3
|
+
import { readable } from "svelte/store";
|
|
3
4
|
import type { Readable } from "svelte/store";
|
|
4
5
|
import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
|
|
5
6
|
import { FullscreenIcon, FullscreenExitIcon } from "../icons";
|
|
6
7
|
|
|
7
8
|
let pc: any = getContext("fw-player-controller");
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const translatorStore: Readable<TranslateFn> =
|
|
10
|
+
getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
|
|
11
|
+
readable(createTranslator({ locale: "en" }));
|
|
12
|
+
let t: TranslateFn = $derived($translatorStore);
|
|
11
13
|
</script>
|
|
12
14
|
|
|
13
15
|
<button
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getContext } from "svelte";
|
|
3
|
+
import { readable } from "svelte/store";
|
|
3
4
|
import type { Readable } from "svelte/store";
|
|
4
5
|
import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
|
|
5
6
|
import { SeekToLiveIcon } from "../icons";
|
|
6
7
|
|
|
7
8
|
let pc: any = getContext("fw-player-controller");
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const translatorStore: Readable<TranslateFn> =
|
|
10
|
+
getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
|
|
11
|
+
readable(createTranslator({ locale: "en" }));
|
|
12
|
+
let t: TranslateFn = $derived($translatorStore);
|
|
11
13
|
</script>
|
|
12
14
|
|
|
13
15
|
{#if pc?.isEffectivelyLive}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getContext } from "svelte";
|
|
3
|
+
import { readable } from "svelte/store";
|
|
3
4
|
import type { Readable } from "svelte/store";
|
|
4
5
|
import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
|
|
5
6
|
import { PlayIcon, PauseIcon } from "../icons";
|
|
6
7
|
|
|
7
8
|
let pc: any = getContext("fw-player-controller");
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
const translatorStore: Readable<TranslateFn> =
|
|
10
|
+
getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
|
|
11
|
+
readable(createTranslator({ locale: "en" }));
|
|
12
|
+
let t: TranslateFn = $derived($translatorStore);
|
|
11
13
|
</script>
|
|
12
14
|
|
|
13
15
|
<button
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getContext } from "svelte";
|
|
3
|
+
import { readable } from "svelte/store";
|
|
3
4
|
import type { Readable } from "svelte/store";
|
|
4
5
|
import { SettingsIcon } from "../icons";
|
|
5
6
|
import {
|
|
@@ -48,9 +49,10 @@
|
|
|
48
49
|
let availableLocales = getAvailableLocales();
|
|
49
50
|
|
|
50
51
|
let controller: any = getContext("fw-player-controller");
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
const translatorStore: Readable<TranslateFn> =
|
|
53
|
+
getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
|
|
54
|
+
readable(createTranslator({ locale: "en" }));
|
|
55
|
+
let t: TranslateFn = $derived($translatorStore);
|
|
54
56
|
let isOpen = $state(false);
|
|
55
57
|
|
|
56
58
|
let qualities = $derived(propQualities ?? controller?.getQualities?.() ?? []);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getContext } from "svelte";
|
|
3
|
+
import { readable } from "svelte/store";
|
|
3
4
|
import type { Readable } from "svelte/store";
|
|
4
5
|
import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
|
|
5
6
|
import { SkipBackIcon, SkipForwardIcon } from "../icons";
|
|
@@ -11,9 +12,10 @@
|
|
|
11
12
|
|
|
12
13
|
let { direction, seconds = 10 }: Props = $props();
|
|
13
14
|
let pc: any = getContext("fw-player-controller");
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const translatorStore: Readable<TranslateFn> =
|
|
16
|
+
getContext<Readable<TranslateFn> | undefined>("fw-translator") ??
|
|
17
|
+
readable(createTranslator({ locale: "en" }));
|
|
18
|
+
let t: TranslateFn = $derived($translatorStore);
|
|
17
19
|
|
|
18
20
|
let label = $derived(direction === "back" ? t("skipBackward") : t("skipForward"));
|
|
19
21
|
</script>
|
|
@@ -23,7 +25,8 @@
|
|
|
23
25
|
class="fw-btn-flush"
|
|
24
26
|
aria-label={label}
|
|
25
27
|
title={label}
|
|
26
|
-
onclick={() =>
|
|
28
|
+
onclick={() =>
|
|
29
|
+
pc?.seek((pc?.currentTime ?? 0) + (direction === "back" ? -seconds * 1000 : seconds * 1000))}
|
|
27
30
|
>
|
|
28
31
|
{#if direction === "back"}
|
|
29
32
|
<SkipBackIcon size={16} />
|