smooth-player 1.0.1 → 2.0.0

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.
@@ -1,4 +1,5 @@
1
1
  import { TypedEventEmitter } from "./events.js";
2
+ import { strings } from "./i18n/strings.js";
2
3
  const DEFAULT_ANALYZER = {
3
4
  fftSize: 2048,
4
5
  smoothingTimeConstant: 0.8,
@@ -6,8 +7,19 @@ const DEFAULT_ANALYZER = {
6
7
  maxDecibels: -10,
7
8
  };
8
9
  const DEFAULT_ACCENT_COLOR = "#0ed2a4";
9
- const DEFAULT_PLAYLIST_ID = "default";
10
- const DEFAULT_PLAYLIST_TITLE = "My playlist";
10
+ const DEFAULT_BACKGROUND_COLOR = "#0b1220";
11
+ const DEFAULT_PLAYLIST_ID = strings.playlist.defaultId;
12
+ const DEFAULT_PLAYLIST_TITLE = strings.playlist.defaultTitle;
13
+ const DEFAULT_SPECTRUM_STYLE = {
14
+ dualLayer: false,
15
+ inverted: false,
16
+ barWidth: "medium",
17
+ };
18
+ const DEFAULT_WAVEFORM_STYLE = {
19
+ doubleLine: false,
20
+ fill: false,
21
+ thickLine: false,
22
+ };
11
23
  export class SmoothPlayer {
12
24
  constructor(options = {}) {
13
25
  this.events = new TypedEventEmitter();
@@ -29,7 +41,10 @@ export class SmoothPlayer {
29
41
  this.audio.preload = this.audio.preload || "metadata";
30
42
  this.audio.volume = this.clamp(options.initialVolume ?? 1);
31
43
  this.visualizerMode = options.visualizer ?? "spectrum";
44
+ this.spectrumStyle = { ...DEFAULT_SPECTRUM_STYLE, ...options.spectrumStyle };
45
+ this.waveformStyle = { ...DEFAULT_WAVEFORM_STYLE, ...options.waveformStyle };
32
46
  this.accentColor = options.accentColor ?? DEFAULT_ACCENT_COLOR;
47
+ this.backgroundColor = options.backgroundColor ?? DEFAULT_BACKGROUND_COLOR;
33
48
  this.shuffleEnabled = options.initialShuffle ?? false;
34
49
  this.debugEnabled = options.debug ?? false;
35
50
  this.durationFallbackEnabled = options.durationFallback ?? true;
@@ -63,6 +78,30 @@ export class SmoothPlayer {
63
78
  applyAccentColor(target) {
64
79
  target.style.setProperty("--smooth-player-accent", this.accentColor);
65
80
  }
81
+ setBackgroundColor(color) {
82
+ this.backgroundColor = color;
83
+ }
84
+ getBackgroundColor() {
85
+ return this.backgroundColor;
86
+ }
87
+ applyBackgroundColor(target) {
88
+ const normalized = this.backgroundColor.trim().toLowerCase();
89
+ if (normalized === DEFAULT_BACKGROUND_COLOR) {
90
+ target.style.removeProperty("--smooth-player-background");
91
+ target.style.removeProperty("--smooth-player-surface");
92
+ target.style.removeProperty("--smooth-player-panel");
93
+ target.style.removeProperty("--smooth-player-muted");
94
+ return;
95
+ }
96
+ target.style.setProperty("--smooth-player-background", this.backgroundColor);
97
+ target.style.setProperty("--smooth-player-surface", this.buildSurfaceGradient(this.backgroundColor));
98
+ target.style.setProperty("--smooth-player-panel", this.buildPanelGradient(this.backgroundColor));
99
+ target.style.setProperty("--smooth-player-muted", `color-mix(in srgb, ${this.backgroundColor} 30%, #d2deef 70%)`);
100
+ }
101
+ applyTheme(target) {
102
+ this.applyAccentColor(target);
103
+ this.applyBackgroundColor(target);
104
+ }
66
105
  setShuffle(enabled) {
67
106
  this.shuffleEnabled = enabled;
68
107
  }
@@ -152,8 +191,8 @@ export class SmoothPlayer {
152
191
  titleClassName: options.titleClassName ?? "smooth-player__playlist-title",
153
192
  artistClassName: options.artistClassName ?? "smooth-player__playlist-artist",
154
193
  selectedAriaAttr: options.selectedAriaAttr ?? "aria-current",
155
- getTitle: options.getTitle ?? ((track, index) => track.metadata?.title ?? `Track ${index + 1}`),
156
- getArtist: options.getArtist ?? ((track) => track.metadata?.artist ?? "Unknown artist"),
194
+ getTitle: options.getTitle ?? ((track, index) => track.metadata?.title ?? `${strings.track.unknownTitle} ${index + 1}`),
195
+ getArtist: options.getArtist ?? ((track) => track.metadata?.artist ?? strings.track.unknownArtist),
157
196
  };
158
197
  const onSelect = options.onSelect;
159
198
  const render = () => {
@@ -226,7 +265,7 @@ export class SmoothPlayer {
226
265
  trigger.className = "smooth-player__playlist-switcher-trigger";
227
266
  trigger.setAttribute("aria-haspopup", "listbox");
228
267
  trigger.setAttribute("aria-expanded", String(isOpen));
229
- trigger.textContent = currentPlaylist?.title ?? playlists[0]?.title ?? "Playlist";
268
+ trigger.textContent = currentPlaylist?.title ?? playlists[0]?.title ?? strings.playlist.triggerLabel;
230
269
  const menu = doc.createElement("div");
231
270
  menu.className = "smooth-player__playlist-switcher-menu";
232
271
  menu.hidden = !isOpen;
@@ -295,8 +334,8 @@ export class SmoothPlayer {
295
334
  return () => off();
296
335
  }
297
336
  mountTrackInfo(titleElement, artistElement, options = {}) {
298
- const unknownTitle = options.unknownTitle ?? "Unknown title";
299
- const unknownArtist = options.unknownArtist ?? "Unknown artist";
337
+ const unknownTitle = options.unknownTitle ?? strings.track.unknownTitle;
338
+ const unknownArtist = options.unknownArtist ?? strings.track.unknownArtist;
300
339
  const render = () => {
301
340
  const track = this.getCurrentTrack();
302
341
  titleElement.textContent = track?.metadata?.title ?? unknownTitle;
@@ -308,8 +347,8 @@ export class SmoothPlayer {
308
347
  }
309
348
  mountPlayButton(button, options = {}) {
310
349
  const labelElement = options.labelElement ?? null;
311
- const playLabel = options.playLabel ?? "Riproduci";
312
- const pauseLabel = options.pauseLabel ?? "Pausa";
350
+ const playLabel = options.playLabel ?? strings.playback.playLabel;
351
+ const pauseLabel = options.pauseLabel ?? strings.playback.pauseLabel;
313
352
  const render = () => {
314
353
  const isPlaying = !this.audio.paused;
315
354
  const label = isPlaying ? pauseLabel : playLabel;
@@ -344,7 +383,7 @@ export class SmoothPlayer {
344
383
  };
345
384
  }
346
385
  mountShuffleToggle(options) {
347
- const { button, labelElement = null, activeClassName = "smooth-player__toggle-on", enabledLabel = "Disattiva shuffle", disabledLabel = "Attiva shuffle", initialEnabled = false, } = options;
386
+ const { button, labelElement = null, activeClassName = "smooth-player__toggle-on", enabledLabel = strings.shuffle.enabledLabel, disabledLabel = strings.shuffle.disabledLabel, initialEnabled = false, } = options;
348
387
  const render = () => {
349
388
  const enabled = this.getShuffle();
350
389
  const label = enabled ? enabledLabel : disabledLabel;
@@ -367,24 +406,39 @@ export class SmoothPlayer {
367
406
  };
368
407
  }
369
408
  mountPlaylistPanel(options) {
370
- const { root, toggleButton, panel, closeButton = null, openClassName = "smooth-player--playlist-open", openLabel = "Apri playlist", closeLabel = "Chiudi playlist", } = options;
409
+ const { root, toggleButton, panel, closeButton = null, openClassName = "smooth-player--playlist-open", openLabel = strings.playlist.openLabel, closeLabel = strings.playlist.closeLabel, } = options;
371
410
  let isOpen = false;
372
411
  const hasPlaylist = () => this.getPlaylists().length > 1 || this.getActiveTracks().length > 1;
373
412
  const syncVisibility = () => {
374
413
  toggleButton.hidden = !hasPlaylist();
375
414
  };
376
415
  const setOpen = (open) => {
416
+ const activeElement = (root.ownerDocument ?? document).activeElement;
417
+ const focusedInsidePanel = activeElement instanceof Node && panel.contains(activeElement);
377
418
  if (!hasPlaylist()) {
378
419
  isOpen = false;
420
+ if (focusedInsidePanel) {
421
+ toggleButton.focus();
422
+ }
379
423
  root.classList.remove(openClassName);
380
424
  panel.setAttribute("aria-hidden", "true");
425
+ panel.setAttribute("inert", "");
381
426
  toggleButton.setAttribute("aria-expanded", "false");
382
427
  toggleButton.setAttribute("aria-label", openLabel);
383
428
  return;
384
429
  }
385
430
  isOpen = open;
431
+ if (!open && focusedInsidePanel) {
432
+ toggleButton.focus();
433
+ }
386
434
  root.classList.toggle(openClassName, open);
387
435
  panel.setAttribute("aria-hidden", String(!open));
436
+ if (open) {
437
+ panel.removeAttribute("inert");
438
+ }
439
+ else {
440
+ panel.setAttribute("inert", "");
441
+ }
388
442
  toggleButton.setAttribute("aria-expanded", String(open));
389
443
  toggleButton.setAttribute("aria-label", open ? closeLabel : openLabel);
390
444
  };
@@ -577,12 +631,199 @@ export class SmoothPlayer {
577
631
  offDurationChange();
578
632
  };
579
633
  }
634
+ mountAudioDrop(target, options = {}) {
635
+ const activeClassName = options.activeClassName ?? "is-drag-over";
636
+ const onDropCallback = options.onDrop;
637
+ let dragDepth = 0;
638
+ let activeObjectUrls = [];
639
+ const clearActiveState = () => {
640
+ dragDepth = 0;
641
+ target.classList.remove(activeClassName);
642
+ };
643
+ const revokeActiveUrls = () => {
644
+ for (const url of activeObjectUrls) {
645
+ URL.revokeObjectURL(url);
646
+ }
647
+ activeObjectUrls = [];
648
+ };
649
+ const isAudioFile = (file) => {
650
+ if (file.type.startsWith("audio/"))
651
+ return true;
652
+ return /\.(mp3|wav|ogg|m4a|aac|flac|opus)$/i.test(file.name);
653
+ };
654
+ const isPlaylistFile = (file) => {
655
+ if (/\.m3u8?$/i.test(file.name))
656
+ return true;
657
+ return /(?:application|audio)\/(?:vnd\.apple\.mpegurl|x-mpegurl)/i.test(file.type);
658
+ };
659
+ const baseName = (value) => {
660
+ const noQuery = value.split("?")[0]?.split("#")[0] ?? value;
661
+ const normalized = noQuery.replace(/\\/g, "/");
662
+ const tail = normalized.split("/").pop() ?? normalized;
663
+ try {
664
+ return decodeURIComponent(tail);
665
+ }
666
+ catch {
667
+ return tail;
668
+ }
669
+ };
670
+ const guessTitle = (value) => baseName(value).replace(/\.[^/.]+$/, "") || strings.track.unknownTitle;
671
+ const buildTrackFromAudioFile = (file, index) => {
672
+ const src = URL.createObjectURL(file);
673
+ activeObjectUrls.push(src);
674
+ const track = {
675
+ id: `dropped-${Date.now()}-${index}`,
676
+ src,
677
+ metadata: {
678
+ title: file.name.replace(/\.[^/.]+$/, ""),
679
+ artist: strings.track.localFileArtist,
680
+ },
681
+ };
682
+ if (file.type) {
683
+ track.type = file.type;
684
+ }
685
+ return track;
686
+ };
687
+ const parseM3U = (content, localAudioFiles) => {
688
+ const localByName = new Map();
689
+ localAudioFiles.forEach((file) => {
690
+ localByName.set(file.name.toLowerCase(), file);
691
+ });
692
+ const tracks = [];
693
+ const lines = content.split(/\r?\n/);
694
+ let extInfTitle = null;
695
+ lines.forEach((raw, index) => {
696
+ const line = raw.trim().replace(/^\uFEFF/, "");
697
+ if (!line)
698
+ return;
699
+ if (line.startsWith("#EXTINF:")) {
700
+ const commaIndex = line.indexOf(",");
701
+ extInfTitle = commaIndex >= 0 ? line.slice(commaIndex + 1).trim() : null;
702
+ return;
703
+ }
704
+ if (line.startsWith("#"))
705
+ return;
706
+ const localFile = localByName.get(baseName(line).toLowerCase()) ?? null;
707
+ let src = "";
708
+ let type;
709
+ if (localFile) {
710
+ src = URL.createObjectURL(localFile);
711
+ activeObjectUrls.push(src);
712
+ type = localFile.type || undefined;
713
+ }
714
+ else {
715
+ try {
716
+ src = new URL(line, window.location.href).href;
717
+ }
718
+ catch {
719
+ src = "";
720
+ }
721
+ }
722
+ if (!src) {
723
+ extInfTitle = null;
724
+ return;
725
+ }
726
+ const track = {
727
+ id: `dropped-m3u-${Date.now()}-${index}`,
728
+ src,
729
+ metadata: {
730
+ title: extInfTitle || guessTitle(line),
731
+ artist: localFile ? strings.track.localFileArtist : strings.track.m3uArtist,
732
+ },
733
+ };
734
+ if (type) {
735
+ track.type = type;
736
+ }
737
+ tracks.push(track);
738
+ extInfTitle = null;
739
+ });
740
+ return tracks;
741
+ };
742
+ const hasFilePayload = (event) => {
743
+ const types = event.dataTransfer?.types;
744
+ if (!types)
745
+ return false;
746
+ return Array.from(types).includes("Files");
747
+ };
748
+ const onDragEnter = (event) => {
749
+ if (!hasFilePayload(event))
750
+ return;
751
+ event.preventDefault();
752
+ dragDepth += 1;
753
+ target.classList.add(activeClassName);
754
+ };
755
+ const onDragOver = (event) => {
756
+ if (!hasFilePayload(event))
757
+ return;
758
+ event.preventDefault();
759
+ if (event.dataTransfer) {
760
+ event.dataTransfer.dropEffect = "copy";
761
+ }
762
+ };
763
+ const onDragLeave = (event) => {
764
+ if (!hasFilePayload(event))
765
+ return;
766
+ event.preventDefault();
767
+ dragDepth = Math.max(0, dragDepth - 1);
768
+ if (dragDepth === 0) {
769
+ target.classList.remove(activeClassName);
770
+ }
771
+ };
772
+ const onDropEvent = async (event) => {
773
+ event.preventDefault();
774
+ clearActiveState();
775
+ const files = Array.from(event.dataTransfer?.files ?? []);
776
+ if (!files.length)
777
+ return;
778
+ const playlistFile = files.find(isPlaylistFile) ?? null;
779
+ const audioFiles = files.filter(isAudioFile);
780
+ let tracks = [];
781
+ let kind = "audio";
782
+ let sourceFile = null;
783
+ revokeActiveUrls();
784
+ if (playlistFile) {
785
+ kind = "playlist";
786
+ sourceFile = playlistFile;
787
+ const content = await playlistFile.text();
788
+ tracks = parseM3U(content, audioFiles);
789
+ }
790
+ else if (audioFiles.length) {
791
+ sourceFile = audioFiles[0] ?? null;
792
+ tracks = audioFiles.map((file, index) => buildTrackFromAudioFile(file, index));
793
+ }
794
+ const firstTrack = tracks[0];
795
+ if (!sourceFile || !firstTrack) {
796
+ this.events.emit("error", {
797
+ error: new Error(strings.errors.noPlayableTracksDropped),
798
+ });
799
+ return;
800
+ }
801
+ this.setPlaylist(tracks, 0);
802
+ onDropCallback?.({ file: sourceFile, track: firstTrack, tracks, kind });
803
+ await this.play(0);
804
+ };
805
+ const onDrop = (event) => {
806
+ void onDropEvent(event);
807
+ };
808
+ target.addEventListener("dragenter", onDragEnter);
809
+ target.addEventListener("dragover", onDragOver);
810
+ target.addEventListener("dragleave", onDragLeave);
811
+ target.addEventListener("drop", onDrop);
812
+ return () => {
813
+ clearActiveState();
814
+ target.removeEventListener("dragenter", onDragEnter);
815
+ target.removeEventListener("dragover", onDragOver);
816
+ target.removeEventListener("dragleave", onDragLeave);
817
+ target.removeEventListener("drop", onDrop);
818
+ revokeActiveUrls();
819
+ };
820
+ }
580
821
  async play(index) {
581
822
  if (typeof index === "number") {
582
823
  this.loadTrackByIndex(index);
583
824
  }
584
825
  if (!this.audio.src) {
585
- throw new Error("No track loaded. Use playlist option, setPlaylist(), or loadTrack().");
826
+ throw new Error(strings.errors.noTrackLoaded);
586
827
  }
587
828
  if (this.context.state === "suspended") {
588
829
  await this.context.resume();
@@ -669,7 +910,10 @@ export class SmoothPlayer {
669
910
  playlistTitle: playlist?.title ?? DEFAULT_PLAYLIST_TITLE,
670
911
  playlistCount: this.playlists.length,
671
912
  visualizer: this.visualizerMode,
913
+ spectrumStyle: { ...this.spectrumStyle },
914
+ waveformStyle: { ...this.waveformStyle },
672
915
  accentColor: this.accentColor,
916
+ backgroundColor: this.backgroundColor,
673
917
  shuffle: this.shuffleEnabled,
674
918
  };
675
919
  }
@@ -711,6 +955,18 @@ export class SmoothPlayer {
711
955
  getVisualizer() {
712
956
  return this.visualizerMode;
713
957
  }
958
+ setSpectrumStyle(options) {
959
+ this.spectrumStyle = { ...this.spectrumStyle, ...options };
960
+ }
961
+ getSpectrumStyle() {
962
+ return { ...this.spectrumStyle };
963
+ }
964
+ setWaveformStyle(options) {
965
+ this.waveformStyle = { ...this.waveformStyle, ...options };
966
+ }
967
+ getWaveformStyle() {
968
+ return { ...this.waveformStyle };
969
+ }
714
970
  configureAnalyzer(options = {}) {
715
971
  const config = { ...DEFAULT_ANALYZER, ...options };
716
972
  this.analyser.fftSize = config.fftSize;
@@ -718,6 +974,12 @@ export class SmoothPlayer {
718
974
  this.analyser.minDecibels = config.minDecibels;
719
975
  this.analyser.maxDecibels = config.maxDecibels;
720
976
  }
977
+ buildSurfaceGradient(baseColor) {
978
+ return `linear-gradient(145deg, color-mix(in srgb, ${baseColor} 74%, #2a3f67 26%), color-mix(in srgb, ${baseColor} 82%, #15233e 18%) 56%, color-mix(in srgb, ${baseColor} 88%, #0a1324 12%))`;
979
+ }
980
+ buildPanelGradient(baseColor) {
981
+ return `linear-gradient(190deg, color-mix(in srgb, ${baseColor} 72%, #2a4069 28%) 0%, color-mix(in srgb, ${baseColor} 82%, #16243f 18%) 58%, color-mix(in srgb, ${baseColor} 88%, #0a1222 12%) 100%)`;
982
+ }
721
983
  getActivePlaylist() {
722
984
  if (!this.activePlaylistId)
723
985
  return null;
@@ -767,11 +1029,31 @@ export class SmoothPlayer {
767
1029
  this.events.emit("ended", undefined);
768
1030
  });
769
1031
  this.audio.addEventListener("error", () => {
1032
+ const src = this.audio.currentSrc || this.audio.src || "";
1033
+ const mediaError = this.audio.error;
1034
+ const isCrossOrigin = this.isCrossOriginSource(src);
1035
+ const isPossiblyCors = isCrossOrigin && mediaError?.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED;
1036
+ const message = isPossiblyCors
1037
+ ? strings.errors.corsBlocked
1038
+ : strings.errors.audioPlaybackFailed;
770
1039
  this.events.emit("error", {
771
- error: new Error("Audio playback failed."),
1040
+ error: new Error(message),
772
1041
  });
773
1042
  });
774
1043
  }
1044
+ isCrossOriginSource(src) {
1045
+ if (!src)
1046
+ return false;
1047
+ if (src.startsWith("blob:") || src.startsWith("data:") || src.startsWith("file:"))
1048
+ return false;
1049
+ try {
1050
+ const url = new URL(src, window.location.href);
1051
+ return url.origin !== window.location.origin;
1052
+ }
1053
+ catch {
1054
+ return false;
1055
+ }
1056
+ }
775
1057
  clamp(value) {
776
1058
  return Math.min(1, Math.max(0, value));
777
1059
  }
@@ -0,0 +1,50 @@
1
+ export declare const en: {
2
+ readonly playlist: {
3
+ readonly defaultId: "default";
4
+ readonly defaultTitle: "My playlist";
5
+ readonly triggerLabel: "Playlist";
6
+ readonly openLabel: "Open playlist";
7
+ readonly closeLabel: "Close playlist";
8
+ };
9
+ readonly playback: {
10
+ readonly playLabel: "Play";
11
+ readonly pauseLabel: "Pause";
12
+ readonly stopLabel: "Stop";
13
+ };
14
+ readonly shuffle: {
15
+ readonly enabledLabel: "Disable shuffle";
16
+ readonly disabledLabel: "Enable shuffle";
17
+ };
18
+ readonly visualizer: {
19
+ readonly toggleLabel: "Change visualizer";
20
+ readonly panelTitle: "Visualizer options";
21
+ readonly closeLabel: "Close visualizer options";
22
+ readonly modeLabel: "Mode";
23
+ readonly effectLabel: "Spectrum effects";
24
+ readonly effectDualLayer: "Bi-directional";
25
+ readonly effectInverted: "Inverted";
26
+ readonly barWidthLabel: "Bar width";
27
+ readonly barWidthThin: "Thin";
28
+ readonly barWidthMedium: "Medium";
29
+ readonly barWidthLarge: "Large";
30
+ readonly waveformEffectLabel: "Waveform effects";
31
+ readonly waveformEffectDoubleLine: "Double line";
32
+ readonly waveformEffectFill: "Fill";
33
+ readonly waveformEffectThickLine: "Thick line";
34
+ readonly modeSpectrum: "Spectrum";
35
+ readonly modeWaveform: "Waveform";
36
+ readonly modeNone: "Off";
37
+ };
38
+ readonly track: {
39
+ readonly unknownTitle: "Unknown title";
40
+ readonly unknownArtist: "Unknown artist";
41
+ readonly localFileArtist: "Local file";
42
+ readonly m3uArtist: "M3U playlist";
43
+ };
44
+ readonly errors: {
45
+ readonly noTrackLoaded: "No track loaded. Use playlist option, setPlaylist(), or loadTrack().";
46
+ readonly noPlayableTracksDropped: "No playable tracks found in dropped files.";
47
+ readonly audioPlaybackFailed: "Audio playback failed.";
48
+ readonly corsBlocked: "Unable to play this audio source. It may be blocked by CORS.";
49
+ };
50
+ };
@@ -0,0 +1,51 @@
1
+ /* Auto-generated from src/i18n/en.json. Do not edit manually. */
2
+ export const en = {
3
+ "playlist": {
4
+ "defaultId": "default",
5
+ "defaultTitle": "My playlist",
6
+ "triggerLabel": "Playlist",
7
+ "openLabel": "Open playlist",
8
+ "closeLabel": "Close playlist"
9
+ },
10
+ "playback": {
11
+ "playLabel": "Play",
12
+ "pauseLabel": "Pause",
13
+ "stopLabel": "Stop"
14
+ },
15
+ "shuffle": {
16
+ "enabledLabel": "Disable shuffle",
17
+ "disabledLabel": "Enable shuffle"
18
+ },
19
+ "visualizer": {
20
+ "toggleLabel": "Change visualizer",
21
+ "panelTitle": "Visualizer options",
22
+ "closeLabel": "Close visualizer options",
23
+ "modeLabel": "Mode",
24
+ "effectLabel": "Spectrum effects",
25
+ "effectDualLayer": "Bi-directional",
26
+ "effectInverted": "Inverted",
27
+ "barWidthLabel": "Bar width",
28
+ "barWidthThin": "Thin",
29
+ "barWidthMedium": "Medium",
30
+ "barWidthLarge": "Large",
31
+ "waveformEffectLabel": "Waveform effects",
32
+ "waveformEffectDoubleLine": "Double line",
33
+ "waveformEffectFill": "Fill",
34
+ "waveformEffectThickLine": "Thick line",
35
+ "modeSpectrum": "Spectrum",
36
+ "modeWaveform": "Waveform",
37
+ "modeNone": "Off"
38
+ },
39
+ "track": {
40
+ "unknownTitle": "Unknown title",
41
+ "unknownArtist": "Unknown artist",
42
+ "localFileArtist": "Local file",
43
+ "m3uArtist": "M3U playlist"
44
+ },
45
+ "errors": {
46
+ "noTrackLoaded": "No track loaded. Use playlist option, setPlaylist(), or loadTrack().",
47
+ "noPlayableTracksDropped": "No playable tracks found in dropped files.",
48
+ "audioPlaybackFailed": "Audio playback failed.",
49
+ "corsBlocked": "Unable to play this audio source. It may be blocked by CORS."
50
+ }
51
+ };
@@ -0,0 +1,51 @@
1
+ export interface SmoothPlayerStrings {
2
+ playlist: {
3
+ defaultId: string;
4
+ defaultTitle: string;
5
+ triggerLabel: string;
6
+ openLabel: string;
7
+ closeLabel: string;
8
+ };
9
+ playback: {
10
+ playLabel: string;
11
+ pauseLabel: string;
12
+ stopLabel: string;
13
+ };
14
+ shuffle: {
15
+ enabledLabel: string;
16
+ disabledLabel: string;
17
+ };
18
+ visualizer: {
19
+ toggleLabel: string;
20
+ panelTitle: string;
21
+ closeLabel: string;
22
+ modeLabel: string;
23
+ effectLabel: string;
24
+ effectDualLayer: string;
25
+ effectInverted: string;
26
+ barWidthLabel: string;
27
+ barWidthThin: string;
28
+ barWidthMedium: string;
29
+ barWidthLarge: string;
30
+ waveformEffectLabel: string;
31
+ waveformEffectDoubleLine: string;
32
+ waveformEffectFill: string;
33
+ waveformEffectThickLine: string;
34
+ modeSpectrum: string;
35
+ modeWaveform: string;
36
+ modeNone: string;
37
+ };
38
+ track: {
39
+ unknownTitle: string;
40
+ unknownArtist: string;
41
+ localFileArtist: string;
42
+ m3uArtist: string;
43
+ };
44
+ errors: {
45
+ noTrackLoaded: string;
46
+ noPlayableTracksDropped: string;
47
+ audioPlaybackFailed: string;
48
+ corsBlocked: string;
49
+ };
50
+ }
51
+ export declare const strings: SmoothPlayerStrings;
@@ -0,0 +1,2 @@
1
+ import { en } from "./en.generated.js";
2
+ export const strings = en;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export { SmoothPlayer } from "./SmoothPlayer.js";
2
- export { mountStandardPlayerUI } from "./ui.js";
2
+ export { mountPlayerUI } from "./ui.js";
3
+ export { strings } from "./i18n/strings.js";
4
+ export type { SmoothPlayerStrings } from "./i18n/strings.js";
3
5
  export { CanvasRadialVisualizer, CanvasSpectrumVisualizer, CanvasWaveformVisualizer, type RadialVisualizerOptions, type SpectrumVisualizerOptions, type WaveformVisualizerOptions, } from "./visualizers.js";
4
- export type { AnalyzerOptions, AudioPlaylist, AudioTrack, PlaybackState, PlaylistPanelController, PlaylistPanelMountOptions, PlaylistEntry, PlaylistMountOptions, PlaylistSwitcherMountOptions, PlaylistTitleMountOptions, PlayButtonMountOptions, ProgressMountOptions, PlayerEvents, SmoothPlayerOptions, ShuffleToggleMountOptions, DebugPanelMountOptions, TransportControlsMountOptions, StandardPlayerUIController, StandardPlayerUIMountOptions, TrackInfoMountOptions, TrackMetadata, VisualizerMode, } from "./types.js";
6
+ export type { AnalyzerOptions, AudioDropMountOptions, AudioPlaylist, AudioTrack, PlaybackState, PlaylistPanelController, PlaylistPanelMountOptions, PlaylistEntry, PlaylistMountOptions, PlaylistSwitcherMountOptions, PlaylistTitleMountOptions, PlayButtonMountOptions, ProgressMountOptions, PlayerEvents, SmoothPlayerOptions, ShuffleToggleMountOptions, DebugPanelMountOptions, TransportControlsMountOptions, StandardPlayerUIController, StandardPlayerUIMountOptions, SpectrumStyleOptions, TrackInfoMountOptions, TrackMetadata, VisualizerMode, WaveformStyleOptions, } from "./types.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { SmoothPlayer } from "./SmoothPlayer.js";
2
- export { mountStandardPlayerUI } from "./ui.js";
2
+ export { mountPlayerUI } from "./ui.js";
3
+ export { strings } from "./i18n/strings.js";
3
4
  export { CanvasRadialVisualizer, CanvasSpectrumVisualizer, CanvasWaveformVisualizer, } from "./visualizers.js";