@livepeer-frameworks/player-svelte 0.1.2 → 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.
Files changed (56) hide show
  1. package/LICENSE.md +24 -0
  2. package/README.md +6 -2
  3. package/dist/DevModePanel.svelte +53 -16
  4. package/dist/IdleScreen.svelte +36 -28
  5. package/dist/LoadingScreen.svelte +107 -67
  6. package/dist/LoadingScreen.svelte.bak +702 -0
  7. package/dist/Player.svelte +200 -53
  8. package/dist/Player.svelte.d.ts +6 -1
  9. package/dist/PlayerControls.svelte +114 -32
  10. package/dist/PlayerControls.svelte.d.ts +3 -0
  11. package/dist/StreamStateOverlay.svelte +33 -21
  12. package/dist/SubtitleRenderer.svelte +2 -2
  13. package/dist/controls/FullscreenButton.svelte +26 -0
  14. package/dist/controls/FullscreenButton.svelte.d.ts +3 -0
  15. package/dist/controls/LiveBadge.svelte +23 -0
  16. package/dist/controls/LiveBadge.svelte.d.ts +3 -0
  17. package/dist/controls/PlayButton.svelte +26 -0
  18. package/dist/controls/PlayButton.svelte.d.ts +3 -0
  19. package/dist/controls/SettingsMenu.svelte +208 -0
  20. package/dist/controls/SettingsMenu.svelte.d.ts +28 -0
  21. package/dist/controls/SkipButton.svelte +33 -0
  22. package/dist/controls/SkipButton.svelte.d.ts +7 -0
  23. package/dist/controls/TimeDisplay.svelte +18 -0
  24. package/dist/controls/TimeDisplay.svelte.d.ts +3 -0
  25. package/dist/controls/VolumeControl.svelte +26 -0
  26. package/dist/controls/VolumeControl.svelte.d.ts +3 -0
  27. package/dist/controls/index.d.ts +7 -0
  28. package/dist/controls/index.js +7 -0
  29. package/dist/index.d.ts +3 -2
  30. package/dist/index.js +3 -1
  31. package/dist/stores/i18n.d.ts +3 -0
  32. package/dist/stores/i18n.js +4 -0
  33. package/dist/stores/index.d.ts +1 -0
  34. package/dist/stores/index.js +2 -0
  35. package/dist/stores/playerController.d.ts +2 -0
  36. package/dist/stores/playerController.js +4 -0
  37. package/package.json +19 -19
  38. package/src/DevModePanel.svelte +53 -16
  39. package/src/IdleScreen.svelte +12 -4
  40. package/src/LoadingScreen.svelte +90 -50
  41. package/src/LoadingScreen.svelte.bak +702 -0
  42. package/src/Player.svelte +200 -53
  43. package/src/PlayerControls.svelte +114 -32
  44. package/src/StreamStateOverlay.svelte +17 -5
  45. package/src/controls/FullscreenButton.svelte +26 -0
  46. package/src/controls/LiveBadge.svelte +23 -0
  47. package/src/controls/PlayButton.svelte +26 -0
  48. package/src/controls/SettingsMenu.svelte +208 -0
  49. package/src/controls/SkipButton.svelte +33 -0
  50. package/src/controls/TimeDisplay.svelte +18 -0
  51. package/src/controls/VolumeControl.svelte +26 -0
  52. package/src/controls/index.ts +7 -0
  53. package/src/index.ts +10 -0
  54. package/src/stores/i18n.ts +7 -0
  55. package/src/stores/index.ts +3 -0
  56. package/src/stores/playerController.ts +7 -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-player-surface fw-controls-wrapper",
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 ? "Pause" : "Play"}
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="Skip back 10s"
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="Skip forward 10s"
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="Volume controls"
651
+ aria-label={t("volume")}
596
652
  onmouseenter={() => hasAudio && (isVolumeHovered = true)}
597
653
  onmouseleave={() => {
598
654
  isVolumeHovered = false;
@@ -602,12 +658,17 @@
602
658
  onfocusout={(e) => {
603
659
  if (!e.currentTarget.contains(e.relatedTarget as Node)) isVolumeFocused = false;
604
660
  }}
661
+ onpointerup={(e) => {
662
+ if (hasAudio && e.target === e.currentTarget) {
663
+ handleMute();
664
+ }
665
+ }}
605
666
  >
606
667
  <button
607
668
  type="button"
608
669
  class="fw-volume-btn"
609
- aria-label={!hasAudio ? "No audio" : isMuted ? "Unmute" : "Mute"}
610
- title={!hasAudio ? "No audio" : isMuted ? "Unmute" : "Mute"}
670
+ aria-label={!hasAudio ? t("muted") : isMuted ? t("unmute") : t("mute")}
671
+ title={!hasAudio ? t("muted") : isMuted ? t("unmute") : t("mute")}
611
672
  onclick={handleMute}
612
673
  disabled={!hasAudio}
613
674
  >
@@ -629,7 +690,7 @@
629
690
  oninput={handleVolumeChange}
630
691
  orientation="horizontal"
631
692
  className="w-full"
632
- aria-label="Volume"
693
+ aria-label={t("volume")}
633
694
  disabled={!hasAudio}
634
695
  />
635
696
  </div>
@@ -651,13 +712,9 @@
651
712
  "fw-live-badge",
652
713
  !hasDvrWindow || isNearLiveState ? "fw-live-badge--active" : "fw-live-badge--behind"
653
714
  )}
654
- title={!hasDvrWindow
655
- ? "Live only"
656
- : isNearLiveState
657
- ? "At live edge"
658
- : "Jump to live"}
715
+ title={t("live")}
659
716
  >
660
- LIVE
717
+ {t("live").toUpperCase()}
661
718
  {#if !isNearLiveState && hasDvrWindow}
662
719
  <SeekToLiveIcon size={10} />
663
720
  {/if}
@@ -673,8 +730,8 @@
673
730
  <button
674
731
  type="button"
675
732
  class={cn("fw-btn-flush", isStatsOpen && "fw-btn-flush--active")}
676
- aria-label="Toggle stats"
677
- title="Stats"
733
+ aria-label={t("showStats")}
734
+ title={t("showStats")}
678
735
  onclick={onStatsToggle}
679
736
  {disabled}
680
737
  >
@@ -686,8 +743,8 @@
686
743
  <button
687
744
  type="button"
688
745
  class={cn("fw-btn-flush group", showSettingsMenu && "fw-btn-flush--active")}
689
- aria-label="Settings"
690
- title="Settings"
746
+ aria-label={t("settings")}
747
+ title={t("settings")}
691
748
  onclick={() => (showSettingsMenu = !showSettingsMenu)}
692
749
  {disabled}
693
750
  >
@@ -695,11 +752,11 @@
695
752
  </button>
696
753
 
697
754
  {#if showSettingsMenu}
698
- <div class="fw-player-surface fw-settings-menu">
755
+ <div class="fw-settings-menu">
699
756
  <!-- Playback Mode - only show for live content (not VOD/clips) -->
700
757
  {#if onModeChange && isContentLive !== false}
701
758
  <div class="fw-settings-section">
702
- <div class="fw-settings-label">Mode</div>
759
+ <div class="fw-settings-label">{t("mode")}</div>
703
760
  <div class="fw-settings-options">
704
761
  {#each ["auto", "low-latency", "quality"] as mode}
705
762
  <button
@@ -713,7 +770,11 @@
713
770
  showSettingsMenu = false;
714
771
  }}
715
772
  >
716
- {mode === "low-latency" ? "Fast" : mode === "quality" ? "Stable" : "Auto"}
773
+ {mode === "low-latency"
774
+ ? t("fast")
775
+ : mode === "quality"
776
+ ? t("stable")
777
+ : t("auto")}
717
778
  </button>
718
779
  {/each}
719
780
  </div>
@@ -723,7 +784,7 @@
723
784
  <!-- Speed (hidden for WebRTC MediaStream) -->
724
785
  {#if supportsPlaybackRate}
725
786
  <div class="fw-settings-section">
726
- <div class="fw-settings-label">Speed</div>
787
+ <div class="fw-settings-label">{t("speed")}</div>
727
788
  <div class="fw-settings-options fw-settings-options--wrap">
728
789
  {#each SPEED_PRESETS as rate}
729
790
  <button
@@ -745,7 +806,7 @@
745
806
  <!-- Quality -->
746
807
  {#if qualities.length > 0}
747
808
  <div class="fw-settings-section">
748
- <div class="fw-settings-label">Quality</div>
809
+ <div class="fw-settings-label">{t("quality")}</div>
749
810
  <div class="fw-settings-list">
750
811
  <button
751
812
  class={cn(
@@ -754,7 +815,7 @@
754
815
  )}
755
816
  onclick={() => handleQualityChange("auto")}
756
817
  >
757
- Auto
818
+ {t("auto")}
758
819
  </button>
759
820
  {#each qualities as q}
760
821
  <button
@@ -774,7 +835,7 @@
774
835
  <!-- Captions -->
775
836
  {#if textTracks.length > 0}
776
837
  <div class="fw-settings-section">
777
- <div class="fw-settings-label">Captions</div>
838
+ <div class="fw-settings-label">{t("captions")}</div>
778
839
  <div class="fw-settings-list">
779
840
  <button
780
841
  class={cn(
@@ -783,17 +844,38 @@
783
844
  )}
784
845
  onclick={() => handleCaptionChange("none")}
785
846
  >
786
- Off
847
+ {t("captionsOff")}
787
848
  </button>
788
- {#each textTracks as t}
849
+ {#each textTracks as tt}
789
850
  <button
790
851
  class={cn(
791
852
  "fw-settings-list-item",
792
- captionValue === t.id && "fw-settings-list-item--active"
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}
870
+ <button
871
+ type="button"
872
+ class={cn(
873
+ "fw-settings-list-item",
874
+ activeLocale === loc && "fw-settings-list-item--active"
793
875
  )}
794
- onclick={() => handleCaptionChange(t.id)}
876
+ onclick={() => onLocaleChange(loc)}
795
877
  >
796
- {t.label || t.id}
878
+ {getLocaleDisplayName(loc)}
797
879
  </button>
798
880
  {/each}
799
881
  </div>
@@ -807,8 +889,8 @@
807
889
  <button
808
890
  type="button"
809
891
  class="fw-btn-flush"
810
- aria-label="Toggle fullscreen"
811
- title="Fullscreen"
892
+ aria-label={isFullscreen ? t("exitFullscreen") : t("fullscreen")}
893
+ title={t("fullscreen")}
812
894
  onclick={handleFullscreen}
813
895
  {disabled}
814
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
- The stream will start when the broadcaster goes live
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
- Please wait while the stream prepares...
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>Checking stream status...</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 type="button" class="btn-flush" onclick={onRetry} aria-label="Retry connection">
171
- Retry Connection
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}
@@ -177,7 +189,7 @@
177
189
  {/if}
178
190
 
179
191
  <style>
180
- .fw-player-root .overlay-backdrop {
192
+ .overlay-backdrop {
181
193
  position: absolute;
182
194
  inset: 0;
183
195
  z-index: 20;
@@ -188,14 +200,14 @@
188
200
  backdrop-filter: blur(4px);
189
201
  }
190
202
 
191
- .fw-player-root .slab {
203
+ .slab {
192
204
  width: 280px;
193
205
  max-width: 90%;
194
206
  background-color: hsl(var(--tn-bg, 233 23% 17%) / 0.95);
195
207
  border: 1px solid hsl(var(--tn-border, 233 23% 25%) / 0.3);
196
208
  }
197
209
 
198
- .fw-player-root .slab-header {
210
+ .slab-header {
199
211
  display: flex;
200
212
  align-items: center;
201
213
  gap: 0.5rem;
@@ -208,15 +220,15 @@
208
220
  color: hsl(var(--tn-fg-dark, 233 23% 60%));
209
221
  }
210
222
 
211
- .fw-player-root .slab-body {
223
+ .slab-body {
212
224
  padding: 1rem;
213
225
  }
214
226
 
215
- .fw-player-root .slab-actions {
227
+ .slab-actions {
216
228
  border-top: 1px solid hsl(var(--tn-border, 233 23% 25%) / 0.3);
217
229
  }
218
230
 
219
- .fw-player-root .btn-flush {
231
+ .btn-flush {
220
232
  width: 100%;
221
233
  padding: 0.625rem 1rem;
222
234
  background: none;
@@ -230,41 +242,41 @@
230
242
  transition: background-color 0.15s;
231
243
  }
232
244
 
233
- .fw-player-root .btn-flush:hover {
245
+ .btn-flush:hover {
234
246
  background-color: hsl(var(--tn-bg-visual, 233 23% 20%) / 0.5);
235
247
  }
236
248
 
237
- .fw-player-root .progress-bar {
249
+ .progress-bar {
238
250
  height: 0.375rem;
239
251
  width: 100%;
240
252
  overflow: hidden;
241
253
  background-color: hsl(var(--tn-bg-visual, 233 23% 20%));
242
254
  }
243
255
 
244
- .fw-player-root .progress-fill {
256
+ .progress-fill {
245
257
  height: 100%;
246
258
  transition: width 0.3s ease;
247
259
  background-color: hsl(var(--tn-yellow, 40 70% 64%));
248
260
  }
249
261
 
250
- .fw-player-root .icon {
262
+ .icon {
251
263
  width: 1.25rem;
252
264
  height: 1.25rem;
253
265
  }
254
266
 
255
- .fw-player-root .icon-online {
267
+ .icon-online {
256
268
  color: hsl(var(--tn-green, 115 54% 57%));
257
269
  }
258
270
 
259
- .fw-player-root .icon-offline {
271
+ .icon-offline {
260
272
  color: hsl(var(--tn-red, 355 68% 65%));
261
273
  }
262
274
 
263
- .fw-player-root .icon-warning {
275
+ .icon-warning {
264
276
  color: hsl(var(--tn-yellow, 40 70% 64%));
265
277
  }
266
278
 
267
- .fw-player-root .polling-indicator {
279
+ .polling-indicator {
268
280
  display: flex;
269
281
  align-items: center;
270
282
  gap: 0.5rem;
@@ -273,7 +285,7 @@
273
285
  color: hsl(var(--tn-fg-dark, 233 23% 60%));
274
286
  }
275
287
 
276
- .fw-player-root .polling-dot {
288
+ .polling-dot {
277
289
  width: 0.375rem;
278
290
  height: 0.375rem;
279
291
  background-color: hsl(var(--tn-cyan, 192 78% 73%));
@@ -296,7 +308,7 @@
296
308
  }
297
309
  }
298
310
 
299
- .fw-player-root .animate-spin {
311
+ .animate-spin {
300
312
  animation: spin 1s linear infinite;
301
313
  }
302
314
  </style>
@@ -221,7 +221,7 @@
221
221
  {/if}
222
222
 
223
223
  <style>
224
- .fw-player-root .subtitle-container {
224
+ .subtitle-container {
225
225
  position: absolute;
226
226
  left: 50%;
227
227
  transform: translateX(-50%);
@@ -230,7 +230,7 @@
230
230
  pointer-events: none;
231
231
  }
232
232
 
233
- .fw-player-root .subtitle-text {
233
+ .subtitle-text {
234
234
  display: inline-block;
235
235
  white-space: pre-wrap;
236
236
  }
@@ -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,3 @@
1
+ declare const FullscreenButton: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type FullscreenButton = ReturnType<typeof FullscreenButton>;
3
+ export default FullscreenButton;
@@ -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,3 @@
1
+ declare const LiveBadge: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type LiveBadge = ReturnType<typeof LiveBadge>;
3
+ export default LiveBadge;
@@ -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>
@@ -0,0 +1,3 @@
1
+ declare const PlayButton: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type PlayButton = ReturnType<typeof PlayButton>;
3
+ export default PlayButton;