@livepeer-frameworks/player-svelte 0.1.3 → 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.
- package/dist/DevModePanel.svelte +14 -15
- package/dist/IdleScreen.svelte +12 -4
- package/dist/LoadingScreen.svelte +90 -50
- package/dist/LoadingScreen.svelte.bak +702 -0
- package/dist/Player.svelte +200 -53
- package/dist/Player.svelte.d.ts +6 -1
- package/dist/PlayerControls.svelte +109 -32
- package/dist/PlayerControls.svelte.d.ts +3 -0
- package/dist/StreamStateOverlay.svelte +17 -5
- package/dist/controls/FullscreenButton.svelte +26 -0
- package/dist/controls/FullscreenButton.svelte.d.ts +3 -0
- package/dist/controls/LiveBadge.svelte +23 -0
- package/dist/controls/LiveBadge.svelte.d.ts +3 -0
- package/dist/controls/PlayButton.svelte +26 -0
- package/dist/controls/PlayButton.svelte.d.ts +3 -0
- package/dist/controls/SettingsMenu.svelte +208 -0
- package/dist/controls/SettingsMenu.svelte.d.ts +28 -0
- package/dist/controls/SkipButton.svelte +33 -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 +26 -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/package.json +8 -8
- package/src/DevModePanel.svelte +14 -15
- package/src/IdleScreen.svelte +12 -4
- package/src/LoadingScreen.svelte +90 -50
- package/src/LoadingScreen.svelte.bak +702 -0
- package/src/Player.svelte +200 -53
- package/src/PlayerControls.svelte +109 -32
- package/src/StreamStateOverlay.svelte +17 -5
- package/src/controls/FullscreenButton.svelte +26 -0
- package/src/controls/LiveBadge.svelte +23 -0
- package/src/controls/PlayButton.svelte +26 -0
- package/src/controls/SettingsMenu.svelte +208 -0
- package/src/controls/SkipButton.svelte +33 -0
- package/src/controls/TimeDisplay.svelte +18 -0
- package/src/controls/VolumeControl.svelte +26 -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
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { getContext } from "svelte";
|
|
3
|
+
import type { Readable } from "svelte/store";
|
|
2
4
|
import {
|
|
3
5
|
cn,
|
|
4
6
|
globalPlayerManager,
|
|
7
|
+
createTranslator,
|
|
5
8
|
type MistStreamInfo,
|
|
6
9
|
type PlaybackMode,
|
|
10
|
+
type TranslateFn,
|
|
7
11
|
// Seeking utilities from core
|
|
8
12
|
SPEED_PRESETS,
|
|
9
13
|
isMediaStreamSource,
|
|
@@ -15,7 +19,10 @@
|
|
|
15
19
|
isLiveContent,
|
|
16
20
|
// Time formatting from core
|
|
17
21
|
formatTimeDisplay,
|
|
22
|
+
getAvailableLocales,
|
|
23
|
+
getLocaleDisplayName,
|
|
18
24
|
} from "@livepeer-frameworks/player-core";
|
|
25
|
+
import type { FwLocale } from "@livepeer-frameworks/player-core";
|
|
19
26
|
import SeekBar from "./SeekBar.svelte";
|
|
20
27
|
import Slider from "./ui/Slider.svelte";
|
|
21
28
|
import VolumeIcons from "./components/VolumeIcons.svelte";
|
|
@@ -31,6 +38,11 @@
|
|
|
31
38
|
SeekToLiveIcon,
|
|
32
39
|
} from "./icons";
|
|
33
40
|
|
|
41
|
+
// i18n: get translator from context, fall back to default English
|
|
42
|
+
const translatorCtx = getContext<Readable<TranslateFn> | undefined>("fw-translator");
|
|
43
|
+
const fallbackT = createTranslator({ locale: "en" });
|
|
44
|
+
let t: TranslateFn = $derived(translatorCtx ? $translatorCtx : fallbackT);
|
|
45
|
+
|
|
34
46
|
// Props - aligned with React PlayerControls
|
|
35
47
|
interface Props {
|
|
36
48
|
currentTime: number;
|
|
@@ -49,6 +61,8 @@
|
|
|
49
61
|
isContentLive?: boolean;
|
|
50
62
|
/** Jump to live edge callback */
|
|
51
63
|
onJumpToLive?: () => void;
|
|
64
|
+
activeLocale?: FwLocale;
|
|
65
|
+
onLocaleChange?: (locale: FwLocale) => void;
|
|
52
66
|
}
|
|
53
67
|
|
|
54
68
|
let {
|
|
@@ -66,6 +80,8 @@
|
|
|
66
80
|
onStatsToggle = undefined,
|
|
67
81
|
isContentLive = undefined,
|
|
68
82
|
onJumpToLive = undefined,
|
|
83
|
+
activeLocale = undefined,
|
|
84
|
+
onLocaleChange = undefined,
|
|
69
85
|
}: Props = $props();
|
|
70
86
|
|
|
71
87
|
// Video element discovery
|
|
@@ -123,6 +139,46 @@
|
|
|
123
139
|
let qualityValue = $state("auto");
|
|
124
140
|
let captionValue = $state("none");
|
|
125
141
|
|
|
142
|
+
// Audio detection: trust MistServer metadata first, then DOM fallback
|
|
143
|
+
$effect(() => {
|
|
144
|
+
// Primary: trust MistServer stream metadata (matches ddvtech embed approach)
|
|
145
|
+
if (mistStreamInfo?.hasAudio !== undefined) {
|
|
146
|
+
hasAudio = mistStreamInfo.hasAudio;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!video) {
|
|
151
|
+
hasAudio = true;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const checkAudio = () => {
|
|
156
|
+
if (video!.srcObject instanceof MediaStream) {
|
|
157
|
+
hasAudio = video!.srcObject.getAudioTracks().length > 0;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const videoAny = video as any;
|
|
161
|
+
if (videoAny.audioTracks && videoAny.audioTracks.length !== undefined) {
|
|
162
|
+
hasAudio = videoAny.audioTracks.length > 0;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
hasAudio = true;
|
|
166
|
+
};
|
|
167
|
+
checkAudio();
|
|
168
|
+
video.addEventListener("loadedmetadata", checkAudio);
|
|
169
|
+
// Safari: audioTracks may be populated after loadedmetadata for HLS streams
|
|
170
|
+
const audioTracks = (video as any).audioTracks;
|
|
171
|
+
if (audioTracks?.addEventListener) {
|
|
172
|
+
audioTracks.addEventListener("addtrack", checkAudio);
|
|
173
|
+
}
|
|
174
|
+
return () => {
|
|
175
|
+
video!.removeEventListener("loadedmetadata", checkAudio);
|
|
176
|
+
if (audioTracks?.removeEventListener) {
|
|
177
|
+
audioTracks.removeEventListener("addtrack", checkAudio);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
|
|
126
182
|
// Text tracks from player
|
|
127
183
|
let textTracks = $derived.by(() => {
|
|
128
184
|
return globalPlayerManager.getCurrentPlayer()?.getTextTracks?.() ?? [];
|
|
@@ -507,7 +563,7 @@
|
|
|
507
563
|
|
|
508
564
|
<div
|
|
509
565
|
class={cn(
|
|
510
|
-
"fw-
|
|
566
|
+
"fw-controls-wrapper",
|
|
511
567
|
isVisible ? "fw-controls-wrapper--visible" : "fw-controls-wrapper--hidden"
|
|
512
568
|
)}
|
|
513
569
|
>
|
|
@@ -551,7 +607,7 @@
|
|
|
551
607
|
<button
|
|
552
608
|
type="button"
|
|
553
609
|
class="fw-btn-flush"
|
|
554
|
-
aria-label={isPlaying ? "
|
|
610
|
+
aria-label={isPlaying ? t("pause") : t("play")}
|
|
555
611
|
onclick={handlePlayPause}
|
|
556
612
|
{disabled}
|
|
557
613
|
>
|
|
@@ -565,7 +621,7 @@
|
|
|
565
621
|
<button
|
|
566
622
|
type="button"
|
|
567
623
|
class="fw-btn-flush hidden sm:flex"
|
|
568
|
-
aria-label="
|
|
624
|
+
aria-label={t("skipBackward")}
|
|
569
625
|
onclick={handleSkipBack}
|
|
570
626
|
{disabled}
|
|
571
627
|
>
|
|
@@ -574,7 +630,7 @@
|
|
|
574
630
|
<button
|
|
575
631
|
type="button"
|
|
576
632
|
class="fw-btn-flush hidden sm:flex"
|
|
577
|
-
aria-label="
|
|
633
|
+
aria-label={t("skipForward")}
|
|
578
634
|
onclick={handleSkipForward}
|
|
579
635
|
{disabled}
|
|
580
636
|
>
|
|
@@ -592,7 +648,7 @@
|
|
|
592
648
|
!hasAudio && "fw-volume-group--disabled"
|
|
593
649
|
)}
|
|
594
650
|
role="group"
|
|
595
|
-
aria-label="
|
|
651
|
+
aria-label={t("volume")}
|
|
596
652
|
onmouseenter={() => hasAudio && (isVolumeHovered = true)}
|
|
597
653
|
onmouseleave={() => {
|
|
598
654
|
isVolumeHovered = false;
|
|
@@ -611,8 +667,8 @@
|
|
|
611
667
|
<button
|
|
612
668
|
type="button"
|
|
613
669
|
class="fw-volume-btn"
|
|
614
|
-
aria-label={!hasAudio ? "
|
|
615
|
-
title={!hasAudio ? "
|
|
670
|
+
aria-label={!hasAudio ? t("muted") : isMuted ? t("unmute") : t("mute")}
|
|
671
|
+
title={!hasAudio ? t("muted") : isMuted ? t("unmute") : t("mute")}
|
|
616
672
|
onclick={handleMute}
|
|
617
673
|
disabled={!hasAudio}
|
|
618
674
|
>
|
|
@@ -634,7 +690,7 @@
|
|
|
634
690
|
oninput={handleVolumeChange}
|
|
635
691
|
orientation="horizontal"
|
|
636
692
|
className="w-full"
|
|
637
|
-
aria-label="
|
|
693
|
+
aria-label={t("volume")}
|
|
638
694
|
disabled={!hasAudio}
|
|
639
695
|
/>
|
|
640
696
|
</div>
|
|
@@ -656,13 +712,9 @@
|
|
|
656
712
|
"fw-live-badge",
|
|
657
713
|
!hasDvrWindow || isNearLiveState ? "fw-live-badge--active" : "fw-live-badge--behind"
|
|
658
714
|
)}
|
|
659
|
-
title={
|
|
660
|
-
? "Live only"
|
|
661
|
-
: isNearLiveState
|
|
662
|
-
? "At live edge"
|
|
663
|
-
: "Jump to live"}
|
|
715
|
+
title={t("live")}
|
|
664
716
|
>
|
|
665
|
-
|
|
717
|
+
{t("live").toUpperCase()}
|
|
666
718
|
{#if !isNearLiveState && hasDvrWindow}
|
|
667
719
|
<SeekToLiveIcon size={10} />
|
|
668
720
|
{/if}
|
|
@@ -678,8 +730,8 @@
|
|
|
678
730
|
<button
|
|
679
731
|
type="button"
|
|
680
732
|
class={cn("fw-btn-flush", isStatsOpen && "fw-btn-flush--active")}
|
|
681
|
-
aria-label="
|
|
682
|
-
title="
|
|
733
|
+
aria-label={t("showStats")}
|
|
734
|
+
title={t("showStats")}
|
|
683
735
|
onclick={onStatsToggle}
|
|
684
736
|
{disabled}
|
|
685
737
|
>
|
|
@@ -691,8 +743,8 @@
|
|
|
691
743
|
<button
|
|
692
744
|
type="button"
|
|
693
745
|
class={cn("fw-btn-flush group", showSettingsMenu && "fw-btn-flush--active")}
|
|
694
|
-
aria-label="
|
|
695
|
-
title="
|
|
746
|
+
aria-label={t("settings")}
|
|
747
|
+
title={t("settings")}
|
|
696
748
|
onclick={() => (showSettingsMenu = !showSettingsMenu)}
|
|
697
749
|
{disabled}
|
|
698
750
|
>
|
|
@@ -700,11 +752,11 @@
|
|
|
700
752
|
</button>
|
|
701
753
|
|
|
702
754
|
{#if showSettingsMenu}
|
|
703
|
-
<div class="fw-
|
|
755
|
+
<div class="fw-settings-menu">
|
|
704
756
|
<!-- Playback Mode - only show for live content (not VOD/clips) -->
|
|
705
757
|
{#if onModeChange && isContentLive !== false}
|
|
706
758
|
<div class="fw-settings-section">
|
|
707
|
-
<div class="fw-settings-label">
|
|
759
|
+
<div class="fw-settings-label">{t("mode")}</div>
|
|
708
760
|
<div class="fw-settings-options">
|
|
709
761
|
{#each ["auto", "low-latency", "quality"] as mode}
|
|
710
762
|
<button
|
|
@@ -718,7 +770,11 @@
|
|
|
718
770
|
showSettingsMenu = false;
|
|
719
771
|
}}
|
|
720
772
|
>
|
|
721
|
-
{mode === "low-latency"
|
|
773
|
+
{mode === "low-latency"
|
|
774
|
+
? t("fast")
|
|
775
|
+
: mode === "quality"
|
|
776
|
+
? t("stable")
|
|
777
|
+
: t("auto")}
|
|
722
778
|
</button>
|
|
723
779
|
{/each}
|
|
724
780
|
</div>
|
|
@@ -728,7 +784,7 @@
|
|
|
728
784
|
<!-- Speed (hidden for WebRTC MediaStream) -->
|
|
729
785
|
{#if supportsPlaybackRate}
|
|
730
786
|
<div class="fw-settings-section">
|
|
731
|
-
<div class="fw-settings-label">
|
|
787
|
+
<div class="fw-settings-label">{t("speed")}</div>
|
|
732
788
|
<div class="fw-settings-options fw-settings-options--wrap">
|
|
733
789
|
{#each SPEED_PRESETS as rate}
|
|
734
790
|
<button
|
|
@@ -750,7 +806,7 @@
|
|
|
750
806
|
<!-- Quality -->
|
|
751
807
|
{#if qualities.length > 0}
|
|
752
808
|
<div class="fw-settings-section">
|
|
753
|
-
<div class="fw-settings-label">
|
|
809
|
+
<div class="fw-settings-label">{t("quality")}</div>
|
|
754
810
|
<div class="fw-settings-list">
|
|
755
811
|
<button
|
|
756
812
|
class={cn(
|
|
@@ -759,7 +815,7 @@
|
|
|
759
815
|
)}
|
|
760
816
|
onclick={() => handleQualityChange("auto")}
|
|
761
817
|
>
|
|
762
|
-
|
|
818
|
+
{t("auto")}
|
|
763
819
|
</button>
|
|
764
820
|
{#each qualities as q}
|
|
765
821
|
<button
|
|
@@ -779,7 +835,7 @@
|
|
|
779
835
|
<!-- Captions -->
|
|
780
836
|
{#if textTracks.length > 0}
|
|
781
837
|
<div class="fw-settings-section">
|
|
782
|
-
<div class="fw-settings-label">
|
|
838
|
+
<div class="fw-settings-label">{t("captions")}</div>
|
|
783
839
|
<div class="fw-settings-list">
|
|
784
840
|
<button
|
|
785
841
|
class={cn(
|
|
@@ -788,17 +844,38 @@
|
|
|
788
844
|
)}
|
|
789
845
|
onclick={() => handleCaptionChange("none")}
|
|
790
846
|
>
|
|
791
|
-
|
|
847
|
+
{t("captionsOff")}
|
|
792
848
|
</button>
|
|
793
|
-
{#each textTracks as
|
|
849
|
+
{#each textTracks as tt}
|
|
850
|
+
<button
|
|
851
|
+
class={cn(
|
|
852
|
+
"fw-settings-list-item",
|
|
853
|
+
captionValue === tt.id && "fw-settings-list-item--active"
|
|
854
|
+
)}
|
|
855
|
+
onclick={() => handleCaptionChange(tt.id)}
|
|
856
|
+
>
|
|
857
|
+
{tt.label || tt.id}
|
|
858
|
+
</button>
|
|
859
|
+
{/each}
|
|
860
|
+
</div>
|
|
861
|
+
</div>
|
|
862
|
+
{/if}
|
|
863
|
+
|
|
864
|
+
<!-- Locale -->
|
|
865
|
+
{#if onLocaleChange}
|
|
866
|
+
<div class="fw-settings-section">
|
|
867
|
+
<div class="fw-settings-label">{t("language")}</div>
|
|
868
|
+
<div class="fw-settings-list">
|
|
869
|
+
{#each getAvailableLocales() as loc}
|
|
794
870
|
<button
|
|
871
|
+
type="button"
|
|
795
872
|
class={cn(
|
|
796
873
|
"fw-settings-list-item",
|
|
797
|
-
|
|
874
|
+
activeLocale === loc && "fw-settings-list-item--active"
|
|
798
875
|
)}
|
|
799
|
-
onclick={() =>
|
|
876
|
+
onclick={() => onLocaleChange(loc)}
|
|
800
877
|
>
|
|
801
|
-
{
|
|
878
|
+
{getLocaleDisplayName(loc)}
|
|
802
879
|
</button>
|
|
803
880
|
{/each}
|
|
804
881
|
</div>
|
|
@@ -812,8 +889,8 @@
|
|
|
812
889
|
<button
|
|
813
890
|
type="button"
|
|
814
891
|
class="fw-btn-flush"
|
|
815
|
-
aria-label="
|
|
816
|
-
title="
|
|
892
|
+
aria-label={isFullscreen ? t("exitFullscreen") : t("fullscreen")}
|
|
893
|
+
title={t("fullscreen")}
|
|
817
894
|
onclick={handleFullscreen}
|
|
818
895
|
{disabled}
|
|
819
896
|
>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type MistStreamInfo, type PlaybackMode } from "@livepeer-frameworks/player-core";
|
|
2
|
+
import type { FwLocale } from "@livepeer-frameworks/player-core";
|
|
2
3
|
interface Props {
|
|
3
4
|
currentTime: number;
|
|
4
5
|
duration: number;
|
|
@@ -16,6 +17,8 @@ interface Props {
|
|
|
16
17
|
isContentLive?: boolean;
|
|
17
18
|
/** Jump to live edge callback */
|
|
18
19
|
onJumpToLive?: () => void;
|
|
20
|
+
activeLocale?: FwLocale;
|
|
21
|
+
onLocaleChange?: (locale: FwLocale) => void;
|
|
19
22
|
}
|
|
20
23
|
declare const PlayerControls: import("svelte").Component<Props, {}, "">;
|
|
21
24
|
type PlayerControls = ReturnType<typeof PlayerControls>;
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
- Retry button for errors
|
|
11
11
|
-->
|
|
12
12
|
<script lang="ts">
|
|
13
|
+
import { getContext } from "svelte";
|
|
14
|
+
import type { Readable } from "svelte/store";
|
|
15
|
+
import { createTranslator, type TranslateFn } from "@livepeer-frameworks/player-core";
|
|
13
16
|
import type { StreamStatus } from "@livepeer-frameworks/player-core";
|
|
14
17
|
|
|
15
18
|
interface Props {
|
|
@@ -36,6 +39,10 @@
|
|
|
36
39
|
class: className = "",
|
|
37
40
|
}: Props = $props();
|
|
38
41
|
|
|
42
|
+
const translatorCtx = getContext<Readable<TranslateFn> | undefined>("fw-translator");
|
|
43
|
+
const fallbackT = createTranslator({ locale: "en" });
|
|
44
|
+
let t: TranslateFn = $derived(translatorCtx ? $translatorCtx : fallbackT);
|
|
45
|
+
|
|
39
46
|
// Computed states
|
|
40
47
|
let showRetry = $derived(status === "ERROR" || status === "INVALID" || status === "OFFLINE");
|
|
41
48
|
let showProgress = $derived(status === "INITIALIZING" && percentage !== undefined);
|
|
@@ -143,7 +150,7 @@
|
|
|
143
150
|
<p
|
|
144
151
|
style="margin-top: 0.5rem; font-size: 0.75rem; color: hsl(var(--tn-fg-dark, 233 23% 60%));"
|
|
145
152
|
>
|
|
146
|
-
|
|
153
|
+
{t("broadcasterGoLive")}
|
|
147
154
|
</p>
|
|
148
155
|
{/if}
|
|
149
156
|
|
|
@@ -151,7 +158,7 @@
|
|
|
151
158
|
<p
|
|
152
159
|
style="margin-top: 0.5rem; font-size: 0.75rem; color: hsl(var(--tn-fg-dark, 233 23% 60%));"
|
|
153
160
|
>
|
|
154
|
-
|
|
161
|
+
{t("streamPreparing")}
|
|
155
162
|
</p>
|
|
156
163
|
{/if}
|
|
157
164
|
|
|
@@ -159,7 +166,7 @@
|
|
|
159
166
|
{#if !showRetry}
|
|
160
167
|
<div class="polling-indicator">
|
|
161
168
|
<span class="polling-dot"></span>
|
|
162
|
-
<span>
|
|
169
|
+
<span>{t("checkingStatus")}</span>
|
|
163
170
|
</div>
|
|
164
171
|
{/if}
|
|
165
172
|
</div>
|
|
@@ -167,8 +174,13 @@
|
|
|
167
174
|
<!-- Slab actions - flush retry button -->
|
|
168
175
|
{#if showRetry && onRetry}
|
|
169
176
|
<div class="slab-actions">
|
|
170
|
-
<button
|
|
171
|
-
|
|
177
|
+
<button
|
|
178
|
+
type="button"
|
|
179
|
+
class="btn-flush"
|
|
180
|
+
onclick={onRetry}
|
|
181
|
+
aria-label={t("retryConnection")}
|
|
182
|
+
>
|
|
183
|
+
{t("retryConnection")}
|
|
172
184
|
</button>
|
|
173
185
|
</div>
|
|
174
186
|
{/if}
|
|
@@ -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 { FullscreenIcon, FullscreenExitIcon } 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?.isFullscreen ? t("exitFullscreen") : t("fullscreen")}
|
|
17
|
+
aria-pressed={pc?.isFullscreen ?? false}
|
|
18
|
+
title={pc?.isFullscreen ? t("exitFullscreen") : t("fullscreen")}
|
|
19
|
+
onclick={() => pc?.toggleFullscreen()}
|
|
20
|
+
>
|
|
21
|
+
{#if pc?.isFullscreen}
|
|
22
|
+
<FullscreenExitIcon size={16} />
|
|
23
|
+
{:else}
|
|
24
|
+
<FullscreenIcon size={16} />
|
|
25
|
+
{/if}
|
|
26
|
+
</button>
|
|
@@ -0,0 +1,23 @@
|
|
|
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 { SeekToLiveIcon } 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
|
+
{#if pc?.isEffectivelyLive}
|
|
14
|
+
<button
|
|
15
|
+
type="button"
|
|
16
|
+
class="fw-live-badge fw-live-badge--active"
|
|
17
|
+
onclick={() => pc?.jumpToLive()}
|
|
18
|
+
aria-label={t("live")}
|
|
19
|
+
>
|
|
20
|
+
{t("live").toUpperCase()}
|
|
21
|
+
<SeekToLiveIcon size={10} />
|
|
22
|
+
</button>
|
|
23
|
+
{/if}
|
|
@@ -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 { PlayIcon, PauseIcon } 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?.isPlaying ? t("pause") : t("play")}
|
|
17
|
+
aria-pressed={pc?.isPlaying ?? false}
|
|
18
|
+
title={pc?.isPlaying ? t("pause") : t("play")}
|
|
19
|
+
onclick={() => pc?.togglePlay()}
|
|
20
|
+
>
|
|
21
|
+
{#if pc?.isPlaying}
|
|
22
|
+
<PauseIcon size={18} />
|
|
23
|
+
{:else}
|
|
24
|
+
<PlayIcon size={18} />
|
|
25
|
+
{/if}
|
|
26
|
+
</button>
|