smooth-player 1.0.4 → 2.1.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,40 @@ 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);
435
+ panel.classList.toggle("is-open", open);
387
436
  panel.setAttribute("aria-hidden", String(!open));
437
+ if (open) {
438
+ panel.removeAttribute("inert");
439
+ }
440
+ else {
441
+ panel.setAttribute("inert", "");
442
+ }
388
443
  toggleButton.setAttribute("aria-expanded", String(open));
389
444
  toggleButton.setAttribute("aria-label", open ? closeLabel : openLabel);
390
445
  };
@@ -577,12 +632,199 @@ export class SmoothPlayer {
577
632
  offDurationChange();
578
633
  };
579
634
  }
635
+ mountAudioDrop(target, options = {}) {
636
+ const activeClassName = options.activeClassName ?? "is-drag-over";
637
+ const onDropCallback = options.onDrop;
638
+ let dragDepth = 0;
639
+ let activeObjectUrls = [];
640
+ const clearActiveState = () => {
641
+ dragDepth = 0;
642
+ target.classList.remove(activeClassName);
643
+ };
644
+ const revokeActiveUrls = () => {
645
+ for (const url of activeObjectUrls) {
646
+ URL.revokeObjectURL(url);
647
+ }
648
+ activeObjectUrls = [];
649
+ };
650
+ const isAudioFile = (file) => {
651
+ if (file.type.startsWith("audio/"))
652
+ return true;
653
+ return /\.(mp3|wav|ogg|m4a|aac|flac|opus)$/i.test(file.name);
654
+ };
655
+ const isPlaylistFile = (file) => {
656
+ if (/\.m3u8?$/i.test(file.name))
657
+ return true;
658
+ return /(?:application|audio)\/(?:vnd\.apple\.mpegurl|x-mpegurl)/i.test(file.type);
659
+ };
660
+ const baseName = (value) => {
661
+ const noQuery = value.split("?")[0]?.split("#")[0] ?? value;
662
+ const normalized = noQuery.replace(/\\/g, "/");
663
+ const tail = normalized.split("/").pop() ?? normalized;
664
+ try {
665
+ return decodeURIComponent(tail);
666
+ }
667
+ catch {
668
+ return tail;
669
+ }
670
+ };
671
+ const guessTitle = (value) => baseName(value).replace(/\.[^/.]+$/, "") || strings.track.unknownTitle;
672
+ const buildTrackFromAudioFile = (file, index) => {
673
+ const src = URL.createObjectURL(file);
674
+ activeObjectUrls.push(src);
675
+ const track = {
676
+ id: `dropped-${Date.now()}-${index}`,
677
+ src,
678
+ metadata: {
679
+ title: file.name.replace(/\.[^/.]+$/, ""),
680
+ artist: strings.track.localFileArtist,
681
+ },
682
+ };
683
+ if (file.type) {
684
+ track.type = file.type;
685
+ }
686
+ return track;
687
+ };
688
+ const parseM3U = (content, localAudioFiles) => {
689
+ const localByName = new Map();
690
+ localAudioFiles.forEach((file) => {
691
+ localByName.set(file.name.toLowerCase(), file);
692
+ });
693
+ const tracks = [];
694
+ const lines = content.split(/\r?\n/);
695
+ let extInfTitle = null;
696
+ lines.forEach((raw, index) => {
697
+ const line = raw.trim().replace(/^\uFEFF/, "");
698
+ if (!line)
699
+ return;
700
+ if (line.startsWith("#EXTINF:")) {
701
+ const commaIndex = line.indexOf(",");
702
+ extInfTitle = commaIndex >= 0 ? line.slice(commaIndex + 1).trim() : null;
703
+ return;
704
+ }
705
+ if (line.startsWith("#"))
706
+ return;
707
+ const localFile = localByName.get(baseName(line).toLowerCase()) ?? null;
708
+ let src = "";
709
+ let type;
710
+ if (localFile) {
711
+ src = URL.createObjectURL(localFile);
712
+ activeObjectUrls.push(src);
713
+ type = localFile.type || undefined;
714
+ }
715
+ else {
716
+ try {
717
+ src = new URL(line, window.location.href).href;
718
+ }
719
+ catch {
720
+ src = "";
721
+ }
722
+ }
723
+ if (!src) {
724
+ extInfTitle = null;
725
+ return;
726
+ }
727
+ const track = {
728
+ id: `dropped-m3u-${Date.now()}-${index}`,
729
+ src,
730
+ metadata: {
731
+ title: extInfTitle || guessTitle(line),
732
+ artist: localFile ? strings.track.localFileArtist : strings.track.m3uArtist,
733
+ },
734
+ };
735
+ if (type) {
736
+ track.type = type;
737
+ }
738
+ tracks.push(track);
739
+ extInfTitle = null;
740
+ });
741
+ return tracks;
742
+ };
743
+ const hasFilePayload = (event) => {
744
+ const types = event.dataTransfer?.types;
745
+ if (!types)
746
+ return false;
747
+ return Array.from(types).includes("Files");
748
+ };
749
+ const onDragEnter = (event) => {
750
+ if (!hasFilePayload(event))
751
+ return;
752
+ event.preventDefault();
753
+ dragDepth += 1;
754
+ target.classList.add(activeClassName);
755
+ };
756
+ const onDragOver = (event) => {
757
+ if (!hasFilePayload(event))
758
+ return;
759
+ event.preventDefault();
760
+ if (event.dataTransfer) {
761
+ event.dataTransfer.dropEffect = "copy";
762
+ }
763
+ };
764
+ const onDragLeave = (event) => {
765
+ if (!hasFilePayload(event))
766
+ return;
767
+ event.preventDefault();
768
+ dragDepth = Math.max(0, dragDepth - 1);
769
+ if (dragDepth === 0) {
770
+ target.classList.remove(activeClassName);
771
+ }
772
+ };
773
+ const onDropEvent = async (event) => {
774
+ event.preventDefault();
775
+ clearActiveState();
776
+ const files = Array.from(event.dataTransfer?.files ?? []);
777
+ if (!files.length)
778
+ return;
779
+ const playlistFile = files.find(isPlaylistFile) ?? null;
780
+ const audioFiles = files.filter(isAudioFile);
781
+ let tracks = [];
782
+ let kind = "audio";
783
+ let sourceFile = null;
784
+ revokeActiveUrls();
785
+ if (playlistFile) {
786
+ kind = "playlist";
787
+ sourceFile = playlistFile;
788
+ const content = await playlistFile.text();
789
+ tracks = parseM3U(content, audioFiles);
790
+ }
791
+ else if (audioFiles.length) {
792
+ sourceFile = audioFiles[0] ?? null;
793
+ tracks = audioFiles.map((file, index) => buildTrackFromAudioFile(file, index));
794
+ }
795
+ const firstTrack = tracks[0];
796
+ if (!sourceFile || !firstTrack) {
797
+ this.events.emit("error", {
798
+ error: new Error(strings.errors.noPlayableTracksDropped),
799
+ });
800
+ return;
801
+ }
802
+ this.setPlaylist(tracks, 0);
803
+ onDropCallback?.({ file: sourceFile, track: firstTrack, tracks, kind });
804
+ await this.play(0);
805
+ };
806
+ const onDrop = (event) => {
807
+ void onDropEvent(event);
808
+ };
809
+ target.addEventListener("dragenter", onDragEnter);
810
+ target.addEventListener("dragover", onDragOver);
811
+ target.addEventListener("dragleave", onDragLeave);
812
+ target.addEventListener("drop", onDrop);
813
+ return () => {
814
+ clearActiveState();
815
+ target.removeEventListener("dragenter", onDragEnter);
816
+ target.removeEventListener("dragover", onDragOver);
817
+ target.removeEventListener("dragleave", onDragLeave);
818
+ target.removeEventListener("drop", onDrop);
819
+ revokeActiveUrls();
820
+ };
821
+ }
580
822
  async play(index) {
581
823
  if (typeof index === "number") {
582
824
  this.loadTrackByIndex(index);
583
825
  }
584
826
  if (!this.audio.src) {
585
- throw new Error("No track loaded. Use playlist option, setPlaylist(), or loadTrack().");
827
+ throw new Error(strings.errors.noTrackLoaded);
586
828
  }
587
829
  if (this.context.state === "suspended") {
588
830
  await this.context.resume();
@@ -669,7 +911,10 @@ export class SmoothPlayer {
669
911
  playlistTitle: playlist?.title ?? DEFAULT_PLAYLIST_TITLE,
670
912
  playlistCount: this.playlists.length,
671
913
  visualizer: this.visualizerMode,
914
+ spectrumStyle: { ...this.spectrumStyle },
915
+ waveformStyle: { ...this.waveformStyle },
672
916
  accentColor: this.accentColor,
917
+ backgroundColor: this.backgroundColor,
673
918
  shuffle: this.shuffleEnabled,
674
919
  };
675
920
  }
@@ -711,6 +956,18 @@ export class SmoothPlayer {
711
956
  getVisualizer() {
712
957
  return this.visualizerMode;
713
958
  }
959
+ setSpectrumStyle(options) {
960
+ this.spectrumStyle = { ...this.spectrumStyle, ...options };
961
+ }
962
+ getSpectrumStyle() {
963
+ return { ...this.spectrumStyle };
964
+ }
965
+ setWaveformStyle(options) {
966
+ this.waveformStyle = { ...this.waveformStyle, ...options };
967
+ }
968
+ getWaveformStyle() {
969
+ return { ...this.waveformStyle };
970
+ }
714
971
  configureAnalyzer(options = {}) {
715
972
  const config = { ...DEFAULT_ANALYZER, ...options };
716
973
  this.analyser.fftSize = config.fftSize;
@@ -718,6 +975,12 @@ export class SmoothPlayer {
718
975
  this.analyser.minDecibels = config.minDecibels;
719
976
  this.analyser.maxDecibels = config.maxDecibels;
720
977
  }
978
+ buildSurfaceGradient(baseColor) {
979
+ 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%))`;
980
+ }
981
+ buildPanelGradient(baseColor) {
982
+ 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%)`;
983
+ }
721
984
  getActivePlaylist() {
722
985
  if (!this.activePlaylistId)
723
986
  return null;
@@ -767,11 +1030,31 @@ export class SmoothPlayer {
767
1030
  this.events.emit("ended", undefined);
768
1031
  });
769
1032
  this.audio.addEventListener("error", () => {
1033
+ const src = this.audio.currentSrc || this.audio.src || "";
1034
+ const mediaError = this.audio.error;
1035
+ const isCrossOrigin = this.isCrossOriginSource(src);
1036
+ const isPossiblyCors = isCrossOrigin && mediaError?.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED;
1037
+ const message = isPossiblyCors
1038
+ ? strings.errors.corsBlocked
1039
+ : strings.errors.audioPlaybackFailed;
770
1040
  this.events.emit("error", {
771
- error: new Error("Audio playback failed."),
1041
+ error: new Error(message),
772
1042
  });
773
1043
  });
774
1044
  }
1045
+ isCrossOriginSource(src) {
1046
+ if (!src)
1047
+ return false;
1048
+ if (src.startsWith("blob:") || src.startsWith("data:") || src.startsWith("file:"))
1049
+ return false;
1050
+ try {
1051
+ const url = new URL(src, window.location.href);
1052
+ return url.origin !== window.location.origin;
1053
+ }
1054
+ catch {
1055
+ return false;
1056
+ }
1057
+ }
775
1058
  clamp(value) {
776
1059
  return Math.min(1, Math.max(0, value));
777
1060
  }
@@ -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";