@stargate91/pill-player 0.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,1702 @@
1
+ // src/components/PillPlayer.tsx
2
+ import { useState as useState4, useEffect as useEffect7, useRef as useRef7, useCallback as useCallback7 } from "react";
3
+
4
+ // src/types/index.ts
5
+ var DEFAULT_PILL_LABELS = {
6
+ badge: "AUDIO PLAYER",
7
+ openPlayer: "Open player",
8
+ closePlayer: "Close player",
9
+ play: "Play",
10
+ pause: "Pause",
11
+ prevTrack: "Previous",
12
+ nextTrack: "Next",
13
+ mute: "Mute",
14
+ unmute: "Unmute",
15
+ directPlayHint: "Stream loaded. Use controls to play or pause.",
16
+ listenExternal: "Open stream",
17
+ selectTrack: "PLAYLIST",
18
+ frequenciesActive: "PLAYING",
19
+ frequenciesStandby: "PAUSED",
20
+ spectrumHeader: "EQUALIZER",
21
+ spectrumDsp: "",
22
+ subtitle: "Audio"
23
+ };
24
+
25
+ // src/hooks/useAudioPlayer.ts
26
+ import { useState as useState2, useEffect as useEffect5, useRef as useRef5, useCallback as useCallback5, useMemo } from "react";
27
+
28
+ // src/utils/youtube.ts
29
+ function extractYouTubeId(urlOrId) {
30
+ if (!urlOrId) return null;
31
+ const trimmed = urlOrId.trim();
32
+ if (/^[a-zA-Z0-9_-]{11}$/.test(trimmed)) {
33
+ return trimmed;
34
+ }
35
+ const match = trimmed.match(
36
+ /(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=|shorts\/))([a-zA-Z0-9_-]{11})/
37
+ );
38
+ return match ? match[1] : null;
39
+ }
40
+
41
+ // src/utils/time.ts
42
+ function formatTime(totalSeconds) {
43
+ if (isNaN(totalSeconds) || totalSeconds <= 0) return "00:00";
44
+ const h = Math.floor(totalSeconds / 3600);
45
+ const m = Math.floor(totalSeconds % 3600 / 60);
46
+ const s = Math.floor(totalSeconds % 60);
47
+ if (h > 0) {
48
+ return h.toString().padStart(2, "0") + ":" + m.toString().padStart(2, "0") + ":" + s.toString().padStart(2, "0");
49
+ }
50
+ return m.toString().padStart(2, "0") + ":" + s.toString().padStart(2, "0");
51
+ }
52
+ function parseDurationToSeconds(duration) {
53
+ if (!duration) return 0;
54
+ const parts = duration.split(":");
55
+ if (parts.length === 2) {
56
+ const min = parseInt(parts[0], 10);
57
+ const sec = parseInt(parts[1], 10);
58
+ if (!isNaN(min) && !isNaN(sec)) return min * 60 + sec;
59
+ }
60
+ if (duration.includes("min")) {
61
+ const parsed = parseInt(duration, 10);
62
+ if (!isNaN(parsed)) return parsed * 60;
63
+ }
64
+ const directNum = parseFloat(duration);
65
+ return !isNaN(directNum) ? directNum : 0;
66
+ }
67
+ function calculateBpmTiming(bpm) {
68
+ const numericBpm = typeof bpm === "number" ? bpm : parseInt(String(bpm || "174").replace(/[^0-9]/g, ""), 10) || 174;
69
+ const beatMs = Math.round(60 / numericBpm * 1e3);
70
+ const halfBeatMs = Math.round(beatMs / 2);
71
+ const twoBeatsMs = Math.round(beatMs * 2);
72
+ const barMs = Math.round(beatMs * 4);
73
+ return { numericBpm, beatMs, halfBeatMs, twoBeatsMs, barMs };
74
+ }
75
+
76
+ // src/engines/html5Engine.ts
77
+ import { useState, useRef, useEffect, useCallback } from "react";
78
+ function useHtml5Engine(callbacks) {
79
+ const [frequencyData, setFrequencyData] = useState(new Array(16).fill(0));
80
+ const audioRef = useRef(null);
81
+ const audioContextRef = useRef(null);
82
+ const analyserRef = useRef(null);
83
+ const animFrameRef = useRef(null);
84
+ useEffect(() => {
85
+ if (typeof window === "undefined") return;
86
+ const audio = new Audio();
87
+ audio.preload = "metadata";
88
+ audioRef.current = audio;
89
+ audio.ontimeupdate = () => {
90
+ callbacks.onTimeUpdate(audio.currentTime);
91
+ };
92
+ audio.onloadedmetadata = () => {
93
+ callbacks.onDurationChange(audio.duration || 0);
94
+ };
95
+ audio.ondurationchange = () => {
96
+ callbacks.onDurationChange(audio.duration || 0);
97
+ };
98
+ audio.onplay = () => {
99
+ callbacks.onPlay();
100
+ if (audioContextRef.current?.state === "suspended") {
101
+ audioContextRef.current.resume().catch(() => {
102
+ });
103
+ }
104
+ };
105
+ audio.onpause = () => {
106
+ callbacks.onPause();
107
+ setFrequencyData(new Array(16).fill(0));
108
+ };
109
+ audio.onended = () => {
110
+ callbacks.onEnded();
111
+ };
112
+ const updateFrequencies = () => {
113
+ if (analyserRef.current) {
114
+ const bufferLength = analyserRef.current.frequencyBinCount;
115
+ const dataArray = new Uint8Array(bufferLength);
116
+ analyserRef.current.getByteFrequencyData(dataArray);
117
+ const bands = 16;
118
+ const step = Math.max(1, Math.floor(bufferLength / bands));
119
+ const normalized = new Array(bands).fill(0).map((_, i) => {
120
+ const val = dataArray[i * step] || 0;
121
+ return val / 255;
122
+ });
123
+ setFrequencyData(normalized);
124
+ }
125
+ animFrameRef.current = requestAnimationFrame(updateFrequencies);
126
+ };
127
+ animFrameRef.current = requestAnimationFrame(updateFrequencies);
128
+ return () => {
129
+ if (animFrameRef.current) {
130
+ cancelAnimationFrame(animFrameRef.current);
131
+ }
132
+ audio.pause();
133
+ audio.src = "";
134
+ if (audioContextRef.current && audioContextRef.current.state !== "closed") {
135
+ audioContextRef.current.close().catch(() => {
136
+ });
137
+ }
138
+ };
139
+ }, [callbacks]);
140
+ const initAudioContext = useCallback(() => {
141
+ if (!audioContextRef.current && audioRef.current && typeof window !== "undefined") {
142
+ try {
143
+ const AudioCtx = window.AudioContext || window.webkitAudioContext;
144
+ if (AudioCtx) {
145
+ const ctx = new AudioCtx();
146
+ const source = ctx.createMediaElementSource(audioRef.current);
147
+ const analyser = ctx.createAnalyser();
148
+ analyser.fftSize = 64;
149
+ source.connect(analyser);
150
+ analyser.connect(ctx.destination);
151
+ audioContextRef.current = ctx;
152
+ analyserRef.current = analyser;
153
+ }
154
+ } catch {
155
+ }
156
+ }
157
+ }, []);
158
+ const loadTrack = useCallback((src, autoPlay = true) => {
159
+ if (!audioRef.current) return;
160
+ audioRef.current.src = src;
161
+ if (autoPlay) {
162
+ initAudioContext();
163
+ audioRef.current.play().catch(() => {
164
+ });
165
+ }
166
+ }, [initAudioContext]);
167
+ const play = useCallback(() => {
168
+ initAudioContext();
169
+ audioRef.current?.play().catch(() => {
170
+ });
171
+ }, [initAudioContext]);
172
+ const pause = useCallback(() => {
173
+ audioRef.current?.pause();
174
+ setFrequencyData(new Array(16).fill(0));
175
+ }, []);
176
+ const seek = useCallback((seconds) => {
177
+ if (audioRef.current) {
178
+ audioRef.current.currentTime = seconds;
179
+ }
180
+ }, []);
181
+ const setMute = useCallback((muted) => {
182
+ if (audioRef.current) {
183
+ audioRef.current.muted = muted;
184
+ }
185
+ }, []);
186
+ return {
187
+ audioRef,
188
+ frequencyData,
189
+ hasLiveFrequency: frequencyData.some((v) => v > 0.05),
190
+ loadTrack,
191
+ play,
192
+ pause,
193
+ seek,
194
+ setMute,
195
+ initAudioContext
196
+ };
197
+ }
198
+
199
+ // src/engines/mixcloudEngine.ts
200
+ import { useRef as useRef2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
201
+ var ALLOWED_ORIGINS = /* @__PURE__ */ new Set([
202
+ "https://player-widget.mixcloud.com",
203
+ "https://www.mixcloud.com",
204
+ "https://widget.mixcloud.com"
205
+ ]);
206
+ function useMixcloudEngine(isLoaded, isMixcloud, callbacks) {
207
+ const iframeRef = useRef2(null);
208
+ const widgetRef = useRef2(null);
209
+ const isWidgetReadyRef = useRef2(false);
210
+ const pendingPlayRef = useRef2(false);
211
+ const setupMixcloudWidget = useCallback2(() => {
212
+ if (!iframeRef.current || typeof window === "undefined" || !window.Mixcloud) return;
213
+ try {
214
+ const widget = window.Mixcloud.PlayerWidget(iframeRef.current);
215
+ widgetRef.current = widget;
216
+ widget.ready.then((resolved) => {
217
+ const active = resolved || widget;
218
+ widgetRef.current = active;
219
+ isWidgetReadyRef.current = true;
220
+ active.events?.play?.on?.(() => {
221
+ callbacks.onPlay();
222
+ active.getDuration?.().then((dur) => {
223
+ if (typeof dur === "number" && dur > 0) callbacks.onDurationChange(dur);
224
+ }).catch(() => {
225
+ });
226
+ });
227
+ active.events?.pause?.on?.(() => callbacks.onPause());
228
+ active.events?.progress?.on?.((pos, dur) => {
229
+ callbacks.onPlay();
230
+ if (typeof pos === "number") callbacks.onTimeUpdate(pos);
231
+ if (typeof dur === "number" && dur > 0) callbacks.onDurationChange(dur);
232
+ });
233
+ active.events?.ended?.on?.(() => callbacks.onEnded());
234
+ if (pendingPlayRef.current) {
235
+ pendingPlayRef.current = false;
236
+ active.play?.().catch(() => {
237
+ });
238
+ }
239
+ }).catch(() => {
240
+ });
241
+ } catch {
242
+ }
243
+ }, [callbacks]);
244
+ useEffect2(() => {
245
+ if (typeof window === "undefined" || !isLoaded || !isMixcloud) return;
246
+ if (window.Mixcloud) {
247
+ setupMixcloudWidget();
248
+ return;
249
+ }
250
+ let script = document.getElementById("mixcloud-widget-api");
251
+ if (!script) {
252
+ script = document.createElement("script");
253
+ script.id = "mixcloud-widget-api";
254
+ script.src = "https://widget.mixcloud.com/media/js/widgetApi.js";
255
+ script.async = true;
256
+ document.body.appendChild(script);
257
+ }
258
+ const onLoad = () => setupMixcloudWidget();
259
+ script.addEventListener("load", onLoad);
260
+ return () => script?.removeEventListener("load", onLoad);
261
+ }, [isLoaded, isMixcloud, setupMixcloudWidget]);
262
+ useEffect2(() => {
263
+ if (typeof window === "undefined") return;
264
+ const handleMessage = (event) => {
265
+ if (!ALLOWED_ORIGINS.has(event.origin)) return;
266
+ try {
267
+ let data = event.data;
268
+ if (typeof data === "string" && (data.includes("play") || data.includes("pause") || data.includes("ended"))) {
269
+ data = JSON.parse(data);
270
+ }
271
+ if (typeof data === "object" && data !== null) {
272
+ const eventName = data.widgetEvent || data.event || data.type;
273
+ if (eventName === "play") callbacks.onPlay();
274
+ else if (eventName === "pause") callbacks.onPause();
275
+ else if (eventName === "ended") callbacks.onEnded();
276
+ }
277
+ } catch {
278
+ }
279
+ };
280
+ window.addEventListener("message", handleMessage);
281
+ return () => window.removeEventListener("message", handleMessage);
282
+ }, [callbacks]);
283
+ useEffect2(() => {
284
+ if (!isLoaded || !isMixcloud) return;
285
+ const interval = setInterval(() => {
286
+ const widget = widgetRef.current;
287
+ if (widget && typeof widget.getIsPaused === "function") {
288
+ widget.getIsPaused().then((isPaused) => {
289
+ if (isPaused) callbacks.onPause();
290
+ else callbacks.onPlay();
291
+ }).catch(() => {
292
+ });
293
+ if (typeof widget.getPosition === "function") {
294
+ widget.getPosition().then((pos) => {
295
+ if (typeof pos === "number") callbacks.onTimeUpdate(pos);
296
+ }).catch(() => {
297
+ });
298
+ }
299
+ }
300
+ }, 800);
301
+ return () => clearInterval(interval);
302
+ }, [isLoaded, isMixcloud, callbacks]);
303
+ const loadTrack = useCallback2((feed, autoPlay = true) => {
304
+ const widget = widgetRef.current;
305
+ if (widget?.load) {
306
+ widget.load(feed, autoPlay).catch(() => {
307
+ });
308
+ if (autoPlay) callbacks.onPlay();
309
+ } else {
310
+ pendingPlayRef.current = autoPlay;
311
+ if (autoPlay) callbacks.onPlay();
312
+ }
313
+ }, [callbacks]);
314
+ const play = useCallback2(() => {
315
+ const widget = widgetRef.current;
316
+ if (widget?.play) {
317
+ widget.play().catch(() => {
318
+ });
319
+ callbacks.onPlay();
320
+ } else {
321
+ pendingPlayRef.current = true;
322
+ callbacks.onPlay();
323
+ }
324
+ }, [callbacks]);
325
+ const pause = useCallback2(() => {
326
+ const widget = widgetRef.current;
327
+ if (widget?.pause) {
328
+ widget.pause().catch(() => {
329
+ });
330
+ callbacks.onPause();
331
+ }
332
+ }, [callbacks]);
333
+ const seek = useCallback2((seconds) => {
334
+ const widget = widgetRef.current;
335
+ if (widget && typeof widget.seek === "function") {
336
+ widget.seek(seconds).catch(() => {
337
+ });
338
+ }
339
+ }, []);
340
+ const setMute = useCallback2((muted) => {
341
+ const widget = widgetRef.current;
342
+ if (widget?.setVolume) {
343
+ widget.setVolume(muted ? 0 : 1).catch(() => {
344
+ });
345
+ }
346
+ }, []);
347
+ return {
348
+ iframeRef,
349
+ widgetRef,
350
+ setupMixcloudWidget,
351
+ loadTrack,
352
+ play,
353
+ pause,
354
+ seek,
355
+ setMute
356
+ };
357
+ }
358
+
359
+ // src/engines/soundCloudEngine.ts
360
+ import { useRef as useRef3, useEffect as useEffect3, useCallback as useCallback3 } from "react";
361
+ function useSoundCloudEngine(isLoaded, isSoundCloud, callbacks) {
362
+ const scIframeRef = useRef3(null);
363
+ const scWidgetRef = useRef3(null);
364
+ const pendingPlayRef = useRef3(false);
365
+ const setupSoundCloudWidget = useCallback3(() => {
366
+ if (!scIframeRef.current || typeof window === "undefined" || !window.SC?.Widget) return;
367
+ try {
368
+ const widget = window.SC.Widget(scIframeRef.current);
369
+ scWidgetRef.current = widget;
370
+ const events = window.SC?.Widget?.Events;
371
+ if (!events) return;
372
+ widget.bind(events.READY, () => {
373
+ widget.getDuration?.((ms) => {
374
+ if (ms) callbacks.onDurationChange(ms / 1e3);
375
+ });
376
+ widget.bind(events.PLAY, () => {
377
+ callbacks.onPlay();
378
+ widget.getDuration?.((ms) => {
379
+ if (ms) callbacks.onDurationChange(ms / 1e3);
380
+ });
381
+ });
382
+ widget.bind(events.PAUSE, () => callbacks.onPause());
383
+ widget.bind(events.FINISH, () => callbacks.onEnded());
384
+ widget.bind(events.PLAY_PROGRESS, (data) => {
385
+ if (data && typeof data.currentPosition === "number") {
386
+ callbacks.onTimeUpdate(data.currentPosition / 1e3);
387
+ }
388
+ if (data && typeof data.relativePosition === "number" && data.relativePosition > 0 && typeof data.currentPosition === "number") {
389
+ const calculatedDuration = data.currentPosition / data.relativePosition / 1e3;
390
+ if (!isNaN(calculatedDuration) && isFinite(calculatedDuration) && calculatedDuration > 0) {
391
+ callbacks.onDurationChange(calculatedDuration);
392
+ }
393
+ }
394
+ });
395
+ if (pendingPlayRef.current) {
396
+ pendingPlayRef.current = false;
397
+ widget.play();
398
+ }
399
+ });
400
+ } catch {
401
+ }
402
+ }, [callbacks]);
403
+ useEffect3(() => {
404
+ if (typeof window === "undefined" || !isLoaded || !isSoundCloud) return;
405
+ if (window.SC?.Widget) {
406
+ setupSoundCloudWidget();
407
+ return;
408
+ }
409
+ let script = document.getElementById("soundcloud-widget-api");
410
+ if (!script) {
411
+ script = document.createElement("script");
412
+ script.id = "soundcloud-widget-api";
413
+ script.src = "https://w.soundcloud.com/player/api.js";
414
+ script.async = true;
415
+ document.body.appendChild(script);
416
+ }
417
+ const onLoad = () => setupSoundCloudWidget();
418
+ script.addEventListener("load", onLoad);
419
+ return () => script?.removeEventListener("load", onLoad);
420
+ }, [isLoaded, isSoundCloud, setupSoundCloudWidget]);
421
+ const loadTrack = useCallback3((url, autoPlay = true) => {
422
+ const widget = scWidgetRef.current;
423
+ if (widget?.load) {
424
+ widget.load(url, { auto_play: autoPlay });
425
+ if (autoPlay) callbacks.onPlay();
426
+ } else {
427
+ pendingPlayRef.current = autoPlay;
428
+ if (autoPlay) callbacks.onPlay();
429
+ }
430
+ }, [callbacks]);
431
+ const play = useCallback3(() => {
432
+ const widget = scWidgetRef.current;
433
+ if (widget?.play) {
434
+ widget.play();
435
+ callbacks.onPlay();
436
+ } else {
437
+ pendingPlayRef.current = true;
438
+ callbacks.onPlay();
439
+ }
440
+ }, [callbacks]);
441
+ const pause = useCallback3(() => {
442
+ const widget = scWidgetRef.current;
443
+ if (widget?.pause) {
444
+ widget.pause();
445
+ callbacks.onPause();
446
+ }
447
+ }, [callbacks]);
448
+ const seek = useCallback3((seconds) => {
449
+ const widget = scWidgetRef.current;
450
+ if (widget?.seekTo) {
451
+ widget.seekTo(seconds * 1e3);
452
+ }
453
+ }, []);
454
+ const setMute = useCallback3((muted) => {
455
+ const widget = scWidgetRef.current;
456
+ if (widget?.setVolume) {
457
+ widget.setVolume(muted ? 0 : 100);
458
+ }
459
+ }, []);
460
+ return {
461
+ scIframeRef,
462
+ scWidgetRef,
463
+ setupSoundCloudWidget,
464
+ loadTrack,
465
+ play,
466
+ pause,
467
+ seek,
468
+ setMute
469
+ };
470
+ }
471
+
472
+ // src/engines/youTubeEngine.ts
473
+ import { useRef as useRef4, useEffect as useEffect4, useCallback as useCallback4 } from "react";
474
+ function useYouTubeEngine(isLoaded, isYouTube, isPlaying, isMuted, callbacks) {
475
+ const ytIframeRef = useRef4(null);
476
+ const ytPlayerRef = useRef4(null);
477
+ const pendingPlayRef = useRef4(false);
478
+ const setupYouTubePlayer = useCallback4(() => {
479
+ if (!ytIframeRef.current || typeof window === "undefined" || !window.YT?.Player) return;
480
+ try {
481
+ if (ytPlayerRef.current) return;
482
+ new window.YT.Player(ytIframeRef.current, {
483
+ events: {
484
+ onReady: (event) => {
485
+ ytPlayerRef.current = event.target;
486
+ const dur = event.target.getDuration?.();
487
+ if (dur && typeof dur === "number" && dur > 0) {
488
+ callbacks.onDurationChange(dur);
489
+ }
490
+ if (isMuted) {
491
+ event.target.mute?.();
492
+ }
493
+ if (pendingPlayRef.current) {
494
+ pendingPlayRef.current = false;
495
+ event.target.playVideo?.();
496
+ }
497
+ },
498
+ onStateChange: (event) => {
499
+ if (event.data === 1) {
500
+ callbacks.onPlay();
501
+ const dur = event.target.getDuration?.();
502
+ if (dur && typeof dur === "number" && dur > 0) {
503
+ callbacks.onDurationChange(dur);
504
+ }
505
+ } else if (event.data === 2) {
506
+ callbacks.onPause();
507
+ } else if (event.data === 0) {
508
+ callbacks.onEnded();
509
+ }
510
+ }
511
+ }
512
+ });
513
+ } catch {
514
+ }
515
+ }, [callbacks, isMuted]);
516
+ useEffect4(() => {
517
+ if (typeof window === "undefined" || !isLoaded || !isYouTube) return;
518
+ if (window.YT && window.YT.Player) {
519
+ setupYouTubePlayer();
520
+ return;
521
+ }
522
+ let script = document.getElementById("youtube-iframe-api");
523
+ if (!script) {
524
+ script = document.createElement("script");
525
+ script.id = "youtube-iframe-api";
526
+ script.src = "https://www.youtube.com/iframe_api";
527
+ script.async = true;
528
+ document.body.appendChild(script);
529
+ }
530
+ const prevOnReady = window.onYouTubeIframeAPIReady;
531
+ window.onYouTubeIframeAPIReady = () => {
532
+ prevOnReady?.();
533
+ setupYouTubePlayer();
534
+ };
535
+ }, [isLoaded, isYouTube, setupYouTubePlayer]);
536
+ useEffect4(() => {
537
+ if (!isYouTube || !isPlaying || !ytPlayerRef.current) return;
538
+ const interval = setInterval(() => {
539
+ const player = ytPlayerRef.current;
540
+ if (player && typeof player.getCurrentTime === "function") {
541
+ try {
542
+ const time = player.getCurrentTime();
543
+ if (typeof time === "number") callbacks.onTimeUpdate(time);
544
+ const dur = player.getDuration?.();
545
+ if (typeof dur === "number" && dur > 0) callbacks.onDurationChange(dur);
546
+ } catch {
547
+ }
548
+ }
549
+ }, 400);
550
+ return () => clearInterval(interval);
551
+ }, [isYouTube, isPlaying, callbacks]);
552
+ const loadTrack = useCallback4((ytId, autoPlay = true) => {
553
+ const player = ytPlayerRef.current;
554
+ if (player && typeof player.loadVideoById === "function") {
555
+ player.loadVideoById(ytId);
556
+ if (autoPlay) callbacks.onPlay();
557
+ } else {
558
+ pendingPlayRef.current = autoPlay;
559
+ if (autoPlay) callbacks.onPlay();
560
+ }
561
+ }, [callbacks]);
562
+ const play = useCallback4(() => {
563
+ const player = ytPlayerRef.current;
564
+ if (player?.playVideo) {
565
+ player.playVideo();
566
+ callbacks.onPlay();
567
+ } else {
568
+ pendingPlayRef.current = true;
569
+ callbacks.onPlay();
570
+ }
571
+ }, [callbacks]);
572
+ const pause = useCallback4(() => {
573
+ const player = ytPlayerRef.current;
574
+ if (player?.pauseVideo) {
575
+ player.pauseVideo();
576
+ callbacks.onPause();
577
+ }
578
+ }, [callbacks]);
579
+ const seek = useCallback4((seconds) => {
580
+ ytPlayerRef.current?.seekTo?.(seconds, true);
581
+ }, []);
582
+ const setMute = useCallback4((muted) => {
583
+ if (ytPlayerRef.current) {
584
+ if (muted) ytPlayerRef.current.mute?.();
585
+ else ytPlayerRef.current.unMute?.();
586
+ }
587
+ }, []);
588
+ return {
589
+ ytIframeRef,
590
+ ytPlayerRef,
591
+ setupYouTubePlayer,
592
+ loadTrack,
593
+ play,
594
+ pause,
595
+ seek,
596
+ setMute
597
+ };
598
+ }
599
+
600
+ // src/hooks/useAudioPlayer.ts
601
+ var STORAGE_TRACK_KEY = "pill_player_track_index";
602
+ function useAudioPlayer({
603
+ tracks,
604
+ persistState = true,
605
+ onTrackChange,
606
+ onPlayStateChange
607
+ }) {
608
+ const [currentTrackIndex, setCurrentTrackIndex] = useState2(0);
609
+ const [isPlaying, setIsPlaying] = useState2(false);
610
+ const [isMuted, setIsMuted] = useState2(false);
611
+ const [isLoaded, setIsLoaded] = useState2(false);
612
+ const [currentTime, setCurTime] = useState2(0);
613
+ const [duration, setDur] = useState2(0);
614
+ const currentTrack = tracks[currentTrackIndex] || tracks[0];
615
+ const youTubeId = extractYouTubeId(
616
+ currentTrack?.youtube || (currentTrack?.src && currentTrack.src.includes("youtube.com") ? currentTrack.src : void 0)
617
+ );
618
+ const isYouTube = Boolean(youTubeId);
619
+ const soundCloudUrl = currentTrack?.soundcloud || (currentTrack?.src && currentTrack.src.includes("soundcloud.com") ? currentTrack.src : "");
620
+ const isSoundCloud = Boolean(soundCloudUrl);
621
+ const isMixcloud = Boolean(currentTrack?.feed);
622
+ const isHtmlAudio = Boolean(currentTrack?.src && !currentTrack.src.includes("soundcloud.com") && !currentTrack.src.includes("youtube.com"));
623
+ useEffect5(() => {
624
+ if (!persistState || typeof window === "undefined") return;
625
+ try {
626
+ const stored = localStorage.getItem(STORAGE_TRACK_KEY);
627
+ if (stored !== null) {
628
+ const parsed = parseInt(stored, 10);
629
+ if (!isNaN(parsed) && parsed >= 0 && parsed < tracks.length) {
630
+ setCurrentTrackIndex(parsed);
631
+ }
632
+ }
633
+ } catch {
634
+ }
635
+ }, [persistState, tracks.length]);
636
+ const nextTrackRef = useRef5(() => {
637
+ });
638
+ const callbacks = useMemo(() => ({
639
+ onPlay: () => {
640
+ setIsPlaying(true);
641
+ onPlayStateChange?.(true);
642
+ },
643
+ onPause: () => {
644
+ setIsPlaying(false);
645
+ onPlayStateChange?.(false);
646
+ },
647
+ onTimeUpdate: (time) => {
648
+ setCurTime(time);
649
+ },
650
+ onDurationChange: (dur) => {
651
+ setDur(dur);
652
+ },
653
+ onEnded: () => {
654
+ nextTrackRef.current();
655
+ }
656
+ }), [onPlayStateChange]);
657
+ const html5 = useHtml5Engine(callbacks);
658
+ const mixcloud = useMixcloudEngine(isLoaded, isMixcloud, callbacks);
659
+ const soundCloud = useSoundCloudEngine(isLoaded, isSoundCloud, callbacks);
660
+ const youTube = useYouTubeEngine(isLoaded, isYouTube, isPlaying, isMuted, callbacks);
661
+ const handleSelectTrack = useCallback5((index) => {
662
+ if (index < 0 || index >= tracks.length) return;
663
+ setCurrentTrackIndex(index);
664
+ if (persistState && typeof window !== "undefined") {
665
+ try {
666
+ localStorage.setItem(STORAGE_TRACK_KEY, String(index));
667
+ } catch {
668
+ }
669
+ }
670
+ if (!isLoaded) {
671
+ setIsLoaded(true);
672
+ }
673
+ const target = tracks[index];
674
+ if (target) {
675
+ setCurTime(0);
676
+ setDur(parseDurationToSeconds(target.duration));
677
+ onTrackChange?.(target, index);
678
+ const targetYtId = extractYouTubeId(target.youtube);
679
+ const targetScUrl = target.soundcloud || (target.src && target.src.includes("soundcloud.com") ? target.src : "");
680
+ const targetFeed = target.feed;
681
+ const targetSrc = target.src && !target.src.includes("soundcloud.com") && !target.src.includes("youtube.com") ? target.src : "";
682
+ if (!targetSrc) html5.pause();
683
+ if (!targetScUrl) soundCloud.pause();
684
+ if (!targetFeed) mixcloud.pause();
685
+ if (!targetYtId) youTube.pause();
686
+ if (targetYtId) {
687
+ youTube.loadTrack(targetYtId, true);
688
+ } else if (targetScUrl) {
689
+ soundCloud.loadTrack(targetScUrl, true);
690
+ } else if (targetFeed) {
691
+ mixcloud.loadTrack(targetFeed, true);
692
+ } else if (targetSrc) {
693
+ html5.loadTrack(targetSrc, true);
694
+ }
695
+ }
696
+ }, [tracks, isLoaded, persistState, onTrackChange, html5, soundCloud, mixcloud, youTube]);
697
+ const handleNextTrack = useCallback5(() => {
698
+ const nextIdx = currentTrackIndex < tracks.length - 1 ? currentTrackIndex + 1 : 0;
699
+ handleSelectTrack(nextIdx);
700
+ }, [currentTrackIndex, tracks.length, handleSelectTrack]);
701
+ const handlePrevTrack = useCallback5(() => {
702
+ const prevIdx = currentTrackIndex > 0 ? currentTrackIndex - 1 : tracks.length - 1;
703
+ handleSelectTrack(prevIdx);
704
+ }, [currentTrackIndex, tracks.length, handleSelectTrack]);
705
+ nextTrackRef.current = handleNextTrack;
706
+ const handleTogglePlay = useCallback5(() => {
707
+ if (!isLoaded) {
708
+ setIsLoaded(true);
709
+ }
710
+ if (isYouTube) {
711
+ if (isPlaying) youTube.pause();
712
+ else youTube.play();
713
+ return;
714
+ }
715
+ if (isSoundCloud) {
716
+ if (isPlaying) soundCloud.pause();
717
+ else soundCloud.play();
718
+ return;
719
+ }
720
+ if (isMixcloud) {
721
+ if (isPlaying) mixcloud.pause();
722
+ else mixcloud.play();
723
+ return;
724
+ }
725
+ if (isHtmlAudio) {
726
+ if (isPlaying) html5.pause();
727
+ else html5.play();
728
+ }
729
+ }, [isLoaded, isYouTube, isSoundCloud, isMixcloud, isHtmlAudio, isPlaying, youTube, soundCloud, mixcloud, html5]);
730
+ const handleSeek = useCallback5((seconds) => {
731
+ setCurTime(seconds);
732
+ if (isYouTube) {
733
+ youTube.seek(seconds);
734
+ return;
735
+ }
736
+ if (isSoundCloud) {
737
+ soundCloud.seek(seconds);
738
+ return;
739
+ }
740
+ if (isMixcloud) {
741
+ mixcloud.seek(seconds);
742
+ return;
743
+ }
744
+ if (isHtmlAudio) {
745
+ html5.seek(seconds);
746
+ }
747
+ }, [isYouTube, isSoundCloud, isMixcloud, isHtmlAudio, youTube, soundCloud, mixcloud, html5]);
748
+ const handleToggleMute = useCallback5(() => {
749
+ setIsMuted((prev) => {
750
+ const nextMuted = !prev;
751
+ html5.setMute(nextMuted);
752
+ soundCloud.setMute(nextMuted);
753
+ mixcloud.setMute(nextMuted);
754
+ youTube.setMute(nextMuted);
755
+ return nextMuted;
756
+ });
757
+ }, [html5, soundCloud, mixcloud, youTube]);
758
+ const timing = useMemo(() => calculateBpmTiming(currentTrack?.bpm), [currentTrack?.bpm]);
759
+ const origin = typeof window !== "undefined" && window.location.origin ? encodeURIComponent(window.location.origin) : "";
760
+ const youTubeIframeSrc = youTubeId ? "https://www.youtube-nocookie.com/embed/" + youTubeId + "?enablejsapi=1&origin=" + origin + "&autoplay=0&controls=0&disablekb=1&fs=0&rel=0&playsinline=1" : "";
761
+ const soundCloudIframeSrc = soundCloudUrl ? "https://w.soundcloud.com/player/?url=" + encodeURIComponent(soundCloudUrl) + "&auto_play=false&hide_related=true&show_comments=false&show_user=true&show_reposts=false&show_teaser=false&visual=false" : "";
762
+ const iframeSrc = currentTrack?.feed ? "https://player-widget.mixcloud.com/widget/iframe/?feed=" + encodeURIComponent(currentTrack.feed) + "&hide_cover=1&light=0" : "";
763
+ return {
764
+ tracks,
765
+ currentTrack,
766
+ currentTrackIndex,
767
+ isPlaying,
768
+ isMuted,
769
+ isLoaded,
770
+ currentTime,
771
+ duration,
772
+ handleSeek,
773
+ iframeRef: mixcloud.iframeRef,
774
+ scIframeRef: soundCloud.scIframeRef,
775
+ soundCloudIframeSrc,
776
+ isSoundCloud,
777
+ isYouTube,
778
+ ytIframeRef: youTube.ytIframeRef,
779
+ youTubeIframeSrc,
780
+ setupYouTubePlayer: youTube.setupYouTubePlayer,
781
+ isHtmlAudio,
782
+ isMixcloud,
783
+ setupSoundCloudWidget: soundCloud.setupSoundCloudWidget,
784
+ iframeSrc,
785
+ frequencyData: html5.frequencyData,
786
+ hasLiveFrequency: html5.hasLiveFrequency,
787
+ timing,
788
+ setIsLoaded,
789
+ handleTogglePlay,
790
+ handleToggleMute,
791
+ handleNextTrack,
792
+ handlePrevTrack,
793
+ handleSelectTrack,
794
+ setupMixcloudWidget: mixcloud.setupMixcloudWidget
795
+ };
796
+ }
797
+
798
+ // src/components/Icons.tsx
799
+ import { jsx, jsxs } from "react/jsx-runtime";
800
+ function PlayIcon({ size = 16, ...props }) {
801
+ return /* @__PURE__ */ jsx(
802
+ "svg",
803
+ {
804
+ width: size,
805
+ height: size,
806
+ viewBox: "0 0 24 24",
807
+ fill: "currentColor",
808
+ stroke: "none",
809
+ "aria-hidden": "true",
810
+ ...props,
811
+ children: /* @__PURE__ */ jsx("polygon", { points: "6 4 20 12 6 20 6 4" })
812
+ }
813
+ );
814
+ }
815
+ function PauseIcon({ size = 16, ...props }) {
816
+ return /* @__PURE__ */ jsxs(
817
+ "svg",
818
+ {
819
+ width: size,
820
+ height: size,
821
+ viewBox: "0 0 24 24",
822
+ fill: "currentColor",
823
+ stroke: "none",
824
+ "aria-hidden": "true",
825
+ ...props,
826
+ children: [
827
+ /* @__PURE__ */ jsx("rect", { x: "5", y: "4", width: "4", height: "16", rx: "1" }),
828
+ /* @__PURE__ */ jsx("rect", { x: "15", y: "4", width: "4", height: "16", rx: "1" })
829
+ ]
830
+ }
831
+ );
832
+ }
833
+ function SkipBackIcon({ size = 16, ...props }) {
834
+ return /* @__PURE__ */ jsxs(
835
+ "svg",
836
+ {
837
+ width: size,
838
+ height: size,
839
+ viewBox: "0 0 24 24",
840
+ fill: "currentColor",
841
+ stroke: "none",
842
+ "aria-hidden": "true",
843
+ ...props,
844
+ children: [
845
+ /* @__PURE__ */ jsx("polygon", { points: "19 20 9 12 19 4 19 20" }),
846
+ /* @__PURE__ */ jsx("line", { x1: "5", y1: "4", x2: "5", y2: "20", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })
847
+ ]
848
+ }
849
+ );
850
+ }
851
+ function SkipForwardIcon({ size = 16, ...props }) {
852
+ return /* @__PURE__ */ jsxs(
853
+ "svg",
854
+ {
855
+ width: size,
856
+ height: size,
857
+ viewBox: "0 0 24 24",
858
+ fill: "currentColor",
859
+ stroke: "none",
860
+ "aria-hidden": "true",
861
+ ...props,
862
+ children: [
863
+ /* @__PURE__ */ jsx("polygon", { points: "5 4 15 12 5 20 5 4" }),
864
+ /* @__PURE__ */ jsx("line", { x1: "19", y1: "4", x2: "19", y2: "20", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round" })
865
+ ]
866
+ }
867
+ );
868
+ }
869
+ function VolumeMuteIcon({ size = 16, ...props }) {
870
+ return /* @__PURE__ */ jsxs(
871
+ "svg",
872
+ {
873
+ width: size,
874
+ height: size,
875
+ viewBox: "0 0 24 24",
876
+ fill: "none",
877
+ stroke: "currentColor",
878
+ strokeWidth: "2",
879
+ strokeLinecap: "round",
880
+ strokeLinejoin: "round",
881
+ "aria-hidden": "true",
882
+ ...props,
883
+ children: [
884
+ /* @__PURE__ */ jsx("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 19 11 5", fill: "currentColor", stroke: "none" }),
885
+ /* @__PURE__ */ jsx("line", { x1: "23", y1: "9", x2: "17", y2: "15" }),
886
+ /* @__PURE__ */ jsx("line", { x1: "17", y1: "9", x2: "23", y2: "15" })
887
+ ]
888
+ }
889
+ );
890
+ }
891
+ function VolumeUpIcon({ size = 16, ...props }) {
892
+ return /* @__PURE__ */ jsxs(
893
+ "svg",
894
+ {
895
+ width: size,
896
+ height: size,
897
+ viewBox: "0 0 24 24",
898
+ fill: "none",
899
+ stroke: "currentColor",
900
+ strokeWidth: "2",
901
+ strokeLinecap: "round",
902
+ strokeLinejoin: "round",
903
+ "aria-hidden": "true",
904
+ ...props,
905
+ children: [
906
+ /* @__PURE__ */ jsx("polygon", { points: "11 5 6 9 2 9 2 15 6 15 11 5", fill: "currentColor", stroke: "none" }),
907
+ /* @__PURE__ */ jsx("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
908
+ /* @__PURE__ */ jsx("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
909
+ ]
910
+ }
911
+ );
912
+ }
913
+ function CloseIcon({ size = 16, ...props }) {
914
+ return /* @__PURE__ */ jsxs(
915
+ "svg",
916
+ {
917
+ width: size,
918
+ height: size,
919
+ viewBox: "0 0 24 24",
920
+ fill: "none",
921
+ stroke: "currentColor",
922
+ strokeWidth: "2.5",
923
+ strokeLinecap: "round",
924
+ strokeLinejoin: "round",
925
+ "aria-hidden": "true",
926
+ ...props,
927
+ children: [
928
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
929
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
930
+ ]
931
+ }
932
+ );
933
+ }
934
+ function ExternalLinkIcon({ size = 16, ...props }) {
935
+ return /* @__PURE__ */ jsxs(
936
+ "svg",
937
+ {
938
+ width: size,
939
+ height: size,
940
+ viewBox: "0 0 24 24",
941
+ fill: "none",
942
+ stroke: "currentColor",
943
+ strokeWidth: "2",
944
+ strokeLinecap: "round",
945
+ strokeLinejoin: "round",
946
+ "aria-hidden": "true",
947
+ ...props,
948
+ children: [
949
+ /* @__PURE__ */ jsx("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
950
+ /* @__PURE__ */ jsx("polyline", { points: "15 3 21 3 21 9" }),
951
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
952
+ ]
953
+ }
954
+ );
955
+ }
956
+
957
+ // src/components/AudioTriggerPill.tsx
958
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
959
+ function AudioTriggerPill({
960
+ isOpen,
961
+ isPlaying,
962
+ currentTrack,
963
+ labels,
964
+ icons,
965
+ onToggleOpen
966
+ }) {
967
+ return /* @__PURE__ */ jsxs2(
968
+ "button",
969
+ {
970
+ type: "button",
971
+ className: "pp-pill-btn",
972
+ onClick: onToggleOpen,
973
+ "aria-expanded": isOpen,
974
+ "aria-label": isOpen ? labels.closePlayer : labels.openPlayer,
975
+ children: [
976
+ /* @__PURE__ */ jsxs2("span", { className: "pp-pill-equalizer", "aria-hidden": "true", children: [
977
+ /* @__PURE__ */ jsx2("span", { className: "pp-pill-bar" }),
978
+ /* @__PURE__ */ jsx2("span", { className: "pp-pill-bar" }),
979
+ /* @__PURE__ */ jsx2("span", { className: "pp-pill-bar" }),
980
+ /* @__PURE__ */ jsx2("span", { className: "pp-pill-bar" })
981
+ ] }),
982
+ /* @__PURE__ */ jsxs2("span", { className: "pp-pill-meta", children: [
983
+ /* @__PURE__ */ jsx2("span", { className: "pp-pill-code", children: currentTrack?.code || currentTrack?.title || "AUDIO" }),
984
+ currentTrack?.bpm && /* @__PURE__ */ jsxs2("span", { className: "pp-pill-bpm", children: [
985
+ "\u2022 ",
986
+ currentTrack.bpm
987
+ ] })
988
+ ] }),
989
+ /* @__PURE__ */ jsx2("span", { className: "pp-pill-icon", "aria-hidden": "true", children: isPlaying ? icons?.pause ?? /* @__PURE__ */ jsx2(PauseIcon, { size: 12 }) : icons?.play ?? /* @__PURE__ */ jsx2(PlayIcon, { size: 12 }) })
990
+ ]
991
+ }
992
+ );
993
+ }
994
+
995
+ // src/components/SpectrumVisualizer.tsx
996
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
997
+ function SpectrumVisualizer({
998
+ isPlaying,
999
+ labels,
1000
+ frequencyData = [],
1001
+ hasLiveFrequency = false
1002
+ }) {
1003
+ return /* @__PURE__ */ jsxs3("div", { className: "pp-visualizer", "aria-hidden": "true", children: [
1004
+ /* @__PURE__ */ jsxs3("div", { className: "pp-vis-header", children: [
1005
+ /* @__PURE__ */ jsxs3("span", { className: "pp-vis-title", children: [
1006
+ labels.spectrumHeader,
1007
+ labels.spectrumDsp ? /* @__PURE__ */ jsxs3("span", { className: "pp-vis-dsp", children: [
1008
+ " // ",
1009
+ labels.spectrumDsp
1010
+ ] }) : null
1011
+ ] }),
1012
+ /* @__PURE__ */ jsxs3("span", { className: "pp-vis-status", children: [
1013
+ /* @__PURE__ */ jsx3("span", { className: "pp-status-led " + (isPlaying ? "pp-status-led-active" : "") }),
1014
+ /* @__PURE__ */ jsx3("span", { children: isPlaying ? labels.frequenciesActive : labels.frequenciesStandby })
1015
+ ] })
1016
+ ] }),
1017
+ /* @__PURE__ */ jsxs3("div", { className: "pp-vis-stage", children: [
1018
+ /* @__PURE__ */ jsxs3("div", { className: "pp-vis-grid", children: [
1019
+ /* @__PURE__ */ jsx3("span", { className: "pp-grid-line" }),
1020
+ /* @__PURE__ */ jsx3("span", { className: "pp-grid-line" }),
1021
+ /* @__PURE__ */ jsx3("span", { className: "pp-grid-line" })
1022
+ ] }),
1023
+ /* @__PURE__ */ jsx3("div", { className: "pp-vis-bars", children: Array.from({ length: 16 }, (_, i) => {
1024
+ let barStyle;
1025
+ if (!isPlaying) {
1026
+ barStyle = {
1027
+ height: "3px",
1028
+ opacity: 0.25,
1029
+ animation: "none"
1030
+ };
1031
+ } else if (hasLiveFrequency) {
1032
+ const liveVal = frequencyData[i] || 0;
1033
+ barStyle = {
1034
+ height: Math.max(3, Math.round(liveVal * 38)) + "px",
1035
+ opacity: Math.max(0.45, liveVal),
1036
+ animation: "none"
1037
+ };
1038
+ } else {
1039
+ barStyle = void 0;
1040
+ }
1041
+ return /* @__PURE__ */ jsx3("div", { className: "pp-bar-col", children: /* @__PURE__ */ jsx3("span", { className: "pp-spectrum-bar", style: barStyle }) }, "bar-" + i);
1042
+ }) })
1043
+ ] }),
1044
+ /* @__PURE__ */ jsxs3("div", { className: "pp-freq-scale", children: [
1045
+ /* @__PURE__ */ jsx3("span", { children: "32Hz" }),
1046
+ /* @__PURE__ */ jsx3("span", { children: "125Hz" }),
1047
+ /* @__PURE__ */ jsx3("span", { children: "500Hz" }),
1048
+ /* @__PURE__ */ jsx3("span", { children: "2kHz" }),
1049
+ /* @__PURE__ */ jsx3("span", { children: "8kHz" }),
1050
+ /* @__PURE__ */ jsx3("span", { children: "16kHz" })
1051
+ ] })
1052
+ ] });
1053
+ }
1054
+
1055
+ // src/components/AudioPlaylist.tsx
1056
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1057
+ function AudioPlaylist({
1058
+ tracks,
1059
+ currentTrackIndex,
1060
+ labels,
1061
+ onSelectTrack
1062
+ }) {
1063
+ return /* @__PURE__ */ jsxs4("div", { className: "pp-playlist", children: [
1064
+ /* @__PURE__ */ jsxs4("div", { className: "pp-playlist-header", children: [
1065
+ /* @__PURE__ */ jsx4("span", { children: labels.selectTrack }),
1066
+ /* @__PURE__ */ jsxs4("span", { children: [
1067
+ currentTrackIndex + 1,
1068
+ " / ",
1069
+ tracks.length
1070
+ ] })
1071
+ ] }),
1072
+ /* @__PURE__ */ jsx4("div", { className: "pp-playlist-list", role: "listbox", "aria-label": labels.selectTrack, children: tracks.map((track, idx) => {
1073
+ const isSelected = idx === currentTrackIndex;
1074
+ return /* @__PURE__ */ jsxs4(
1075
+ "button",
1076
+ {
1077
+ type: "button",
1078
+ role: "option",
1079
+ "aria-selected": isSelected,
1080
+ className: "pp-track-item " + (isSelected ? "pp-track-item-active" : ""),
1081
+ onClick: () => onSelectTrack(idx),
1082
+ children: [
1083
+ /* @__PURE__ */ jsxs4("div", { className: "pp-track-item-left", children: [
1084
+ /* @__PURE__ */ jsx4("span", { className: "pp-track-item-code", children: track.code || "#" + (idx + 1) }),
1085
+ /* @__PURE__ */ jsx4("span", { className: "pp-track-item-title", children: track.title })
1086
+ ] }),
1087
+ /* @__PURE__ */ jsxs4("div", { className: "pp-track-item-right", children: [
1088
+ track.bpm && /* @__PURE__ */ jsx4("span", { children: track.bpm }),
1089
+ track.duration && /* @__PURE__ */ jsx4("span", { children: track.duration })
1090
+ ] })
1091
+ ]
1092
+ },
1093
+ track.id || "track-" + idx
1094
+ );
1095
+ }) })
1096
+ ] });
1097
+ }
1098
+
1099
+ // src/components/AudioSeekbar.tsx
1100
+ import { useState as useState3, useRef as useRef6, useCallback as useCallback6, useEffect as useEffect6 } from "react";
1101
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1102
+ function AudioSeekbar({
1103
+ currentTime,
1104
+ duration,
1105
+ onSeek
1106
+ }) {
1107
+ const [isDragging, setIsDragging] = useState3(false);
1108
+ const [dragProgress, setDragProgress] = useState3(0);
1109
+ const trackRef = useRef6(null);
1110
+ const progress = duration > 0 ? Math.min(1, Math.max(0, isDragging ? dragProgress : currentTime / duration)) : 0;
1111
+ const calculateRatio = useCallback6((clientX) => {
1112
+ if (!trackRef.current) return 0;
1113
+ const rect = trackRef.current.getBoundingClientRect();
1114
+ const pos = clientX - rect.left;
1115
+ return Math.min(1, Math.max(0, pos / rect.width));
1116
+ }, []);
1117
+ const handlePointerDown = (e) => {
1118
+ e.preventDefault();
1119
+ setIsDragging(true);
1120
+ const ratio = calculateRatio(e.clientX);
1121
+ setDragProgress(ratio);
1122
+ };
1123
+ const handleTouchStart = (e) => {
1124
+ if (e.touches[0]) {
1125
+ setIsDragging(true);
1126
+ const ratio = calculateRatio(e.touches[0].clientX);
1127
+ setDragProgress(ratio);
1128
+ }
1129
+ };
1130
+ const handleKeyDown = (e) => {
1131
+ if (duration <= 0) return;
1132
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") {
1133
+ e.preventDefault();
1134
+ onSeek(Math.min(duration, currentTime + 5));
1135
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
1136
+ e.preventDefault();
1137
+ onSeek(Math.max(0, currentTime - 5));
1138
+ } else if (e.key === "PageUp") {
1139
+ e.preventDefault();
1140
+ onSeek(Math.min(duration, currentTime + 30));
1141
+ } else if (e.key === "PageDown") {
1142
+ e.preventDefault();
1143
+ onSeek(Math.max(0, currentTime - 30));
1144
+ }
1145
+ };
1146
+ useEffect6(() => {
1147
+ if (!isDragging) return;
1148
+ const handleMouseMove = (e) => {
1149
+ const ratio = calculateRatio(e.clientX);
1150
+ setDragProgress(ratio);
1151
+ };
1152
+ const handleTouchMove = (e) => {
1153
+ if (e.touches[0]) {
1154
+ const ratio = calculateRatio(e.touches[0].clientX);
1155
+ setDragProgress(ratio);
1156
+ }
1157
+ };
1158
+ const handleRelease = (e) => {
1159
+ let clientX = 0;
1160
+ if ("clientX" in e) {
1161
+ clientX = e.clientX;
1162
+ } else if (e.changedTouches && e.changedTouches[0]) {
1163
+ clientX = e.changedTouches[0].clientX;
1164
+ }
1165
+ const ratio = calculateRatio(clientX);
1166
+ setIsDragging(false);
1167
+ if (duration > 0) {
1168
+ onSeek(ratio * duration);
1169
+ }
1170
+ };
1171
+ window.addEventListener("mousemove", handleMouseMove);
1172
+ window.addEventListener("mouseup", handleRelease);
1173
+ window.addEventListener("touchmove", handleTouchMove);
1174
+ window.addEventListener("touchend", handleRelease);
1175
+ return () => {
1176
+ window.removeEventListener("mousemove", handleMouseMove);
1177
+ window.removeEventListener("mouseup", handleRelease);
1178
+ window.removeEventListener("touchmove", handleTouchMove);
1179
+ window.removeEventListener("touchend", handleRelease);
1180
+ };
1181
+ }, [isDragging, calculateRatio, duration, onSeek]);
1182
+ const displayTime = isDragging ? dragProgress * duration : currentTime;
1183
+ return /* @__PURE__ */ jsxs5("div", { className: "pp-seekbar-wrapper", "aria-label": "Audio progress", children: [
1184
+ /* @__PURE__ */ jsx5("span", { className: "pp-time pp-time-current", children: formatTime(displayTime) }),
1185
+ /* @__PURE__ */ jsxs5(
1186
+ "div",
1187
+ {
1188
+ ref: trackRef,
1189
+ className: "pp-seekbar-track " + (isDragging ? "pp-seekbar-dragging" : ""),
1190
+ onMouseDown: handlePointerDown,
1191
+ onTouchStart: handleTouchStart,
1192
+ onKeyDown: handleKeyDown,
1193
+ role: "slider",
1194
+ tabIndex: 0,
1195
+ "aria-valuemin": 0,
1196
+ "aria-valuemax": duration || 100,
1197
+ "aria-valuenow": Math.round(displayTime),
1198
+ "aria-valuetext": formatTime(displayTime),
1199
+ children: [
1200
+ /* @__PURE__ */ jsx5(
1201
+ "div",
1202
+ {
1203
+ className: "pp-seekbar-fill",
1204
+ style: { width: progress * 100 + "%" }
1205
+ }
1206
+ ),
1207
+ /* @__PURE__ */ jsx5(
1208
+ "div",
1209
+ {
1210
+ className: "pp-seekbar-thumb",
1211
+ style: { left: progress * 100 + "%" }
1212
+ }
1213
+ )
1214
+ ]
1215
+ }
1216
+ ),
1217
+ /* @__PURE__ */ jsx5("span", { className: "pp-time pp-time-total", children: formatTime(duration) })
1218
+ ] });
1219
+ }
1220
+
1221
+ // src/components/PlayerHeader.tsx
1222
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1223
+ function PlayerHeader({ labels, icons, onClose }) {
1224
+ return /* @__PURE__ */ jsxs6("div", { className: "pp-card-header", children: [
1225
+ /* @__PURE__ */ jsx6("span", { className: "pp-telemetry-badge", children: labels.badge }),
1226
+ /* @__PURE__ */ jsx6(
1227
+ "button",
1228
+ {
1229
+ type: "button",
1230
+ className: "pp-icon-btn",
1231
+ onClick: onClose,
1232
+ "aria-label": labels.closePlayer,
1233
+ title: labels.closePlayer,
1234
+ children: icons?.close ?? /* @__PURE__ */ jsx6(CloseIcon, { size: 14 })
1235
+ }
1236
+ )
1237
+ ] });
1238
+ }
1239
+
1240
+ // src/components/TrackInfo.tsx
1241
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1242
+ function TrackInfo({ currentTrack, labels }) {
1243
+ return /* @__PURE__ */ jsxs7("div", { className: "pp-track-info", children: [
1244
+ /* @__PURE__ */ jsxs7("div", { className: "pp-track-meta", children: [
1245
+ currentTrack?.genre && /* @__PURE__ */ jsx7("span", { className: "pp-genre-badge", children: currentTrack.genre }),
1246
+ currentTrack?.bpm && /* @__PURE__ */ jsxs7("span", { className: "pp-bpm-badge", children: [
1247
+ "\u2022 ",
1248
+ currentTrack.bpm
1249
+ ] })
1250
+ ] }),
1251
+ /* @__PURE__ */ jsx7("p", { className: "pp-track-title", title: currentTrack?.title, children: currentTrack?.title || "No track selected" }),
1252
+ /* @__PURE__ */ jsx7("p", { className: "pp-track-subtitle", children: currentTrack?.artist ? `${currentTrack.artist} \u2022 ${labels.subtitle}` : labels.subtitle })
1253
+ ] });
1254
+ }
1255
+
1256
+ // src/components/PlayerControls.tsx
1257
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1258
+ function PlayerControls({
1259
+ isPlaying,
1260
+ isMuted,
1261
+ externalLinkUrl,
1262
+ labels,
1263
+ icons,
1264
+ onPrev,
1265
+ onTogglePlay,
1266
+ onNext,
1267
+ onToggleMute
1268
+ }) {
1269
+ return /* @__PURE__ */ jsxs8("div", { className: "pp-controls-row", children: [
1270
+ /* @__PURE__ */ jsx8(
1271
+ "button",
1272
+ {
1273
+ type: "button",
1274
+ className: "pp-ctrl-btn",
1275
+ onClick: onPrev,
1276
+ "aria-label": labels.prevTrack,
1277
+ title: labels.prevTrack,
1278
+ children: icons?.prev ?? /* @__PURE__ */ jsx8(SkipBackIcon, { size: 16 })
1279
+ }
1280
+ ),
1281
+ /* @__PURE__ */ jsxs8(
1282
+ "button",
1283
+ {
1284
+ type: "button",
1285
+ className: "pp-ctrl-btn-main",
1286
+ onClick: onTogglePlay,
1287
+ "aria-label": isPlaying ? labels.pause : labels.play,
1288
+ children: [
1289
+ isPlaying ? icons?.pause ?? /* @__PURE__ */ jsx8(PauseIcon, { size: 14 }) : icons?.play ?? /* @__PURE__ */ jsx8(PlayIcon, { size: 14 }),
1290
+ /* @__PURE__ */ jsx8("span", { children: isPlaying ? labels.pause : labels.play })
1291
+ ]
1292
+ }
1293
+ ),
1294
+ /* @__PURE__ */ jsx8(
1295
+ "button",
1296
+ {
1297
+ type: "button",
1298
+ className: "pp-ctrl-btn",
1299
+ onClick: onNext,
1300
+ "aria-label": labels.nextTrack,
1301
+ title: labels.nextTrack,
1302
+ children: icons?.next ?? /* @__PURE__ */ jsx8(SkipForwardIcon, { size: 16 })
1303
+ }
1304
+ ),
1305
+ /* @__PURE__ */ jsx8(
1306
+ "button",
1307
+ {
1308
+ type: "button",
1309
+ className: "pp-ctrl-btn",
1310
+ onClick: onToggleMute,
1311
+ "aria-label": isMuted ? labels.unmute : labels.mute,
1312
+ title: isMuted ? labels.unmute : labels.mute,
1313
+ children: isMuted ? icons?.unmute ?? /* @__PURE__ */ jsx8(VolumeMuteIcon, { size: 16 }) : icons?.mute ?? /* @__PURE__ */ jsx8(VolumeUpIcon, { size: 16 })
1314
+ }
1315
+ ),
1316
+ externalLinkUrl && /* @__PURE__ */ jsx8(
1317
+ "a",
1318
+ {
1319
+ href: externalLinkUrl,
1320
+ target: "_blank",
1321
+ rel: "noopener noreferrer",
1322
+ className: "pp-ctrl-btn",
1323
+ "aria-label": labels.listenExternal,
1324
+ title: labels.listenExternal,
1325
+ children: icons?.external ?? /* @__PURE__ */ jsx8(ExternalLinkIcon, { size: 15 })
1326
+ }
1327
+ )
1328
+ ] });
1329
+ }
1330
+
1331
+ // src/components/ProviderEmbeds.tsx
1332
+ import { Fragment, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1333
+ function ProviderEmbeds({
1334
+ isLoaded,
1335
+ embedMode,
1336
+ currentTrack,
1337
+ labels,
1338
+ isSoundCloud,
1339
+ soundCloudIframeSrc,
1340
+ scIframeRef,
1341
+ onSoundCloudLoad,
1342
+ isYouTube,
1343
+ youTubeIframeSrc,
1344
+ ytIframeRef,
1345
+ onYouTubeLoad,
1346
+ iframeSrc,
1347
+ iframeRef,
1348
+ onMixcloudLoad
1349
+ }) {
1350
+ if (!isLoaded || !currentTrack) return null;
1351
+ const title = (currentTrack.code || "") + " " + currentTrack.title;
1352
+ const isSeamless = embedMode === "seamless";
1353
+ const isThemed = embedMode === "themed";
1354
+ return /* @__PURE__ */ jsxs9(Fragment, { children: [
1355
+ isSoundCloud && soundCloudIframeSrc && (isSeamless ? /* @__PURE__ */ jsx9("div", { className: "pp-iframe-seamless", "aria-hidden": "true", children: /* @__PURE__ */ jsx9(
1356
+ "iframe",
1357
+ {
1358
+ ref: scIframeRef,
1359
+ tabIndex: -1,
1360
+ title,
1361
+ src: soundCloudIframeSrc,
1362
+ allow: "autoplay",
1363
+ onLoad: onSoundCloudLoad
1364
+ }
1365
+ ) }) : /* @__PURE__ */ jsx9("div", { className: "pp-iframe-wrapper", children: /* @__PURE__ */ jsx9(
1366
+ "iframe",
1367
+ {
1368
+ ref: scIframeRef,
1369
+ title,
1370
+ className: "pp-mixcloud-iframe",
1371
+ src: soundCloudIframeSrc,
1372
+ allow: "autoplay",
1373
+ onLoad: onSoundCloudLoad
1374
+ }
1375
+ ) })),
1376
+ isYouTube && youTubeIframeSrc && (isSeamless ? /* @__PURE__ */ jsx9("div", { className: "pp-iframe-seamless", "aria-hidden": "true", children: /* @__PURE__ */ jsx9(
1377
+ "iframe",
1378
+ {
1379
+ ref: ytIframeRef,
1380
+ tabIndex: -1,
1381
+ title,
1382
+ src: youTubeIframeSrc,
1383
+ allow: "autoplay; encrypted-media",
1384
+ onLoad: onYouTubeLoad
1385
+ }
1386
+ ) }) : /* @__PURE__ */ jsx9("div", { className: "pp-iframe-wrapper " + (isThemed ? "pp-iframe-themed" : ""), children: /* @__PURE__ */ jsx9(
1387
+ "iframe",
1388
+ {
1389
+ ref: ytIframeRef,
1390
+ title,
1391
+ className: "pp-mixcloud-iframe",
1392
+ src: youTubeIframeSrc,
1393
+ allow: "autoplay; encrypted-media",
1394
+ onLoad: onYouTubeLoad
1395
+ }
1396
+ ) })),
1397
+ Boolean(currentTrack.feed) && (isSeamless ? /* @__PURE__ */ jsx9("div", { className: "pp-iframe-seamless", "aria-hidden": "true", children: /* @__PURE__ */ jsx9(
1398
+ "iframe",
1399
+ {
1400
+ ref: iframeRef,
1401
+ tabIndex: -1,
1402
+ title,
1403
+ className: "pp-mixcloud-iframe",
1404
+ src: iframeSrc,
1405
+ allow: "autoplay; encrypted-media; fullscreen; picture-in-picture",
1406
+ onLoad: onMixcloudLoad
1407
+ }
1408
+ ) }) : /* @__PURE__ */ jsxs9(Fragment, { children: [
1409
+ /* @__PURE__ */ jsx9("div", { className: "pp-iframe-wrapper " + (isThemed ? "pp-iframe-themed" : ""), children: /* @__PURE__ */ jsx9(
1410
+ "iframe",
1411
+ {
1412
+ ref: iframeRef,
1413
+ title,
1414
+ className: "pp-mixcloud-iframe",
1415
+ src: iframeSrc,
1416
+ allow: "autoplay; encrypted-media; fullscreen; picture-in-picture",
1417
+ onLoad: onMixcloudLoad
1418
+ }
1419
+ ) }),
1420
+ /* @__PURE__ */ jsx9("span", { className: "pp-iframe-hint", children: labels.directPlayHint })
1421
+ ] }))
1422
+ ] });
1423
+ }
1424
+
1425
+ // src/components/PillPlayer.tsx
1426
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1427
+ function PillPlayer({
1428
+ tracks,
1429
+ theme = "orbital",
1430
+ colors,
1431
+ position = "bottom-right",
1432
+ icons,
1433
+ labels: customLabels,
1434
+ features = {},
1435
+ initialOpen = false,
1436
+ className = "",
1437
+ style,
1438
+ onTrackChange,
1439
+ onPlayStateChange
1440
+ }) {
1441
+ const [isOpen, setIsOpen] = useState4(initialOpen);
1442
+ const [asyncTracks, setAsyncTracks] = useState4([]);
1443
+ useEffect7(() => {
1444
+ if (typeof tracks !== "string") return;
1445
+ let isMounted = true;
1446
+ fetch(tracks).then((res) => {
1447
+ if (!res.ok) throw new Error(`Failed to fetch tracks: ${res.status}`);
1448
+ return res.json();
1449
+ }).then((data) => {
1450
+ if (isMounted && Array.isArray(data)) {
1451
+ setAsyncTracks(data);
1452
+ }
1453
+ }).catch((err) => {
1454
+ console.error("[PillPlayer] Error loading tracks JSON:", err);
1455
+ });
1456
+ return () => {
1457
+ isMounted = false;
1458
+ };
1459
+ }, [tracks]);
1460
+ const resolvedTracks = typeof tracks === "string" ? asyncTracks : tracks;
1461
+ const containerRef = useRef7(null);
1462
+ const {
1463
+ showSpectrum = true,
1464
+ showPlaylist = true,
1465
+ showSeekbar = true,
1466
+ persistState = true,
1467
+ enableKeyboardShortcuts = true,
1468
+ embedMode: propEmbedMode,
1469
+ mixcloudEmbedMode: propMixcloudEmbedMode,
1470
+ beaconGlow = false
1471
+ } = features;
1472
+ const embedMode = propEmbedMode ?? propMixcloudEmbedMode ?? "seamless";
1473
+ const labels = {
1474
+ ...DEFAULT_PILL_LABELS,
1475
+ ...customLabels
1476
+ };
1477
+ const {
1478
+ currentTrack,
1479
+ currentTrackIndex,
1480
+ isPlaying,
1481
+ isMuted,
1482
+ isLoaded,
1483
+ currentTime,
1484
+ duration,
1485
+ handleSeek,
1486
+ iframeRef,
1487
+ iframeSrc,
1488
+ scIframeRef,
1489
+ soundCloudIframeSrc,
1490
+ isSoundCloud,
1491
+ isYouTube,
1492
+ ytIframeRef,
1493
+ youTubeIframeSrc,
1494
+ setupYouTubePlayer,
1495
+ isHtmlAudio,
1496
+ setupSoundCloudWidget,
1497
+ frequencyData,
1498
+ hasLiveFrequency,
1499
+ timing,
1500
+ setIsLoaded,
1501
+ handleTogglePlay,
1502
+ handleToggleMute,
1503
+ handleNextTrack,
1504
+ handlePrevTrack,
1505
+ handleSelectTrack,
1506
+ setupMixcloudWidget
1507
+ } = useAudioPlayer({
1508
+ tracks: resolvedTracks,
1509
+ persistState,
1510
+ onTrackChange,
1511
+ onPlayStateChange
1512
+ });
1513
+ const handleToggleOpen = useCallback7(() => {
1514
+ setIsOpen((prev) => {
1515
+ const next = !prev;
1516
+ if (next && !isLoaded) {
1517
+ setIsLoaded(true);
1518
+ }
1519
+ return next;
1520
+ });
1521
+ }, [isLoaded, setIsLoaded]);
1522
+ const handleClose = useCallback7(() => {
1523
+ setIsOpen(false);
1524
+ }, []);
1525
+ useEffect7(() => {
1526
+ if (!enableKeyboardShortcuts || !isOpen) return;
1527
+ const handleKeyDown = (e) => {
1528
+ if (e.key === "Escape") {
1529
+ handleClose();
1530
+ }
1531
+ };
1532
+ window.addEventListener("keydown", handleKeyDown);
1533
+ return () => window.removeEventListener("keydown", handleKeyDown);
1534
+ }, [enableKeyboardShortcuts, isOpen, handleClose]);
1535
+ useEffect7(() => {
1536
+ if (!isOpen) return;
1537
+ const handlePointerDownOutside = (e) => {
1538
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
1539
+ handleClose();
1540
+ }
1541
+ };
1542
+ document.addEventListener("pointerdown", handlePointerDownOutside);
1543
+ return () => document.removeEventListener("pointerdown", handlePointerDownOutside);
1544
+ }, [isOpen, handleClose]);
1545
+ const customColorStyles = colors ? {
1546
+ ...colors.accent ? { "--pp-accent": colors.accent } : {},
1547
+ ...colors.secondary ? { "--pp-accent-secondary": colors.secondary } : {},
1548
+ ...colors.bg ? { "--pp-bg": colors.bg } : {},
1549
+ ...colors.surface ? { "--pp-surface": colors.surface } : {},
1550
+ ...colors.border ? { "--pp-border": colors.border } : {},
1551
+ ...colors.text ? { "--pp-text": colors.text } : {},
1552
+ ...colors.textDim ? { "--pp-text-dim": colors.textDim } : {},
1553
+ ...colors.glow ? { "--pp-accent-glow": colors.glow } : {}
1554
+ } : {};
1555
+ const externalLinkUrl = currentTrack?.externalUrl || currentTrack?.soundcloud || (currentTrack?.youtube ? currentTrack.youtube.startsWith("http") ? currentTrack.youtube : "https://www.youtube.com/watch?v=" + currentTrack.youtube : void 0) || (currentTrack?.feed ? "https://www.mixcloud.com" + currentTrack.feed : void 0);
1556
+ const shouldShowSeekbar = showSeekbar && (isHtmlAudio || embedMode === "seamless");
1557
+ return /* @__PURE__ */ jsxs10(
1558
+ "div",
1559
+ {
1560
+ ref: containerRef,
1561
+ "data-pill-theme": theme,
1562
+ className: "pp-anchor pp-position-" + position + " " + (isPlaying ? "pp-is-playing " : "") + className,
1563
+ style: {
1564
+ ...customColorStyles,
1565
+ ...style,
1566
+ "--bpm-beat": timing.beatMs + "ms",
1567
+ "--bpm-half-beat": timing.halfBeatMs + "ms",
1568
+ "--bpm-two-beats": timing.twoBeatsMs + "ms",
1569
+ "--bpm-bar": timing.barMs + "ms"
1570
+ },
1571
+ "aria-label": labels.badge,
1572
+ children: [
1573
+ /* @__PURE__ */ jsxs10(
1574
+ "section",
1575
+ {
1576
+ role: isOpen ? "dialog" : void 0,
1577
+ "aria-modal": isOpen ? "false" : void 0,
1578
+ "aria-hidden": !isOpen,
1579
+ "aria-label": currentTrack ? (currentTrack.code || "") + " - " + currentTrack.title : labels.badge,
1580
+ className: "pp-hud-card " + (isOpen ? "pp-hud-card-open" : "pp-hud-card-closed") + (beaconGlow && isOpen ? " pp-hud-beacon" : ""),
1581
+ children: [
1582
+ /* @__PURE__ */ jsx10(
1583
+ PlayerHeader,
1584
+ {
1585
+ labels,
1586
+ icons,
1587
+ onClose: handleClose
1588
+ }
1589
+ ),
1590
+ /* @__PURE__ */ jsxs10("div", { className: "pp-card-body", children: [
1591
+ /* @__PURE__ */ jsx10(
1592
+ TrackInfo,
1593
+ {
1594
+ currentTrack,
1595
+ labels
1596
+ }
1597
+ ),
1598
+ showSpectrum && /* @__PURE__ */ jsx10(
1599
+ SpectrumVisualizer,
1600
+ {
1601
+ isPlaying,
1602
+ labels,
1603
+ frequencyData,
1604
+ hasLiveFrequency
1605
+ }
1606
+ ),
1607
+ /* @__PURE__ */ jsx10(
1608
+ ProviderEmbeds,
1609
+ {
1610
+ isLoaded,
1611
+ embedMode,
1612
+ currentTrack,
1613
+ labels,
1614
+ isSoundCloud,
1615
+ soundCloudIframeSrc,
1616
+ scIframeRef,
1617
+ onSoundCloudLoad: setupSoundCloudWidget,
1618
+ isYouTube,
1619
+ youTubeIframeSrc,
1620
+ ytIframeRef,
1621
+ onYouTubeLoad: setupYouTubePlayer,
1622
+ iframeSrc,
1623
+ iframeRef,
1624
+ onMixcloudLoad: setupMixcloudWidget
1625
+ }
1626
+ ),
1627
+ shouldShowSeekbar && /* @__PURE__ */ jsx10(
1628
+ AudioSeekbar,
1629
+ {
1630
+ currentTime,
1631
+ duration,
1632
+ onSeek: handleSeek
1633
+ }
1634
+ ),
1635
+ /* @__PURE__ */ jsx10(
1636
+ PlayerControls,
1637
+ {
1638
+ isPlaying,
1639
+ isMuted,
1640
+ externalLinkUrl,
1641
+ labels,
1642
+ icons,
1643
+ onPrev: handlePrevTrack,
1644
+ onTogglePlay: handleTogglePlay,
1645
+ onNext: handleNextTrack,
1646
+ onToggleMute: handleToggleMute
1647
+ }
1648
+ ),
1649
+ showPlaylist && resolvedTracks.length > 1 && /* @__PURE__ */ jsx10(
1650
+ AudioPlaylist,
1651
+ {
1652
+ tracks: resolvedTracks,
1653
+ currentTrackIndex,
1654
+ labels,
1655
+ onSelectTrack: handleSelectTrack
1656
+ }
1657
+ )
1658
+ ] })
1659
+ ]
1660
+ }
1661
+ ),
1662
+ /* @__PURE__ */ jsx10(
1663
+ AudioTriggerPill,
1664
+ {
1665
+ isOpen,
1666
+ isPlaying,
1667
+ currentTrack,
1668
+ labels,
1669
+ icons,
1670
+ onToggleOpen: handleToggleOpen
1671
+ }
1672
+ )
1673
+ ]
1674
+ }
1675
+ );
1676
+ }
1677
+ export {
1678
+ AudioPlaylist,
1679
+ AudioSeekbar,
1680
+ AudioTriggerPill,
1681
+ CloseIcon,
1682
+ DEFAULT_PILL_LABELS,
1683
+ ExternalLinkIcon,
1684
+ PauseIcon,
1685
+ PillPlayer,
1686
+ PlayIcon,
1687
+ PlayerControls,
1688
+ PlayerHeader,
1689
+ ProviderEmbeds,
1690
+ SkipBackIcon,
1691
+ SkipForwardIcon,
1692
+ SpectrumVisualizer,
1693
+ TrackInfo,
1694
+ VolumeMuteIcon,
1695
+ VolumeUpIcon,
1696
+ calculateBpmTiming,
1697
+ extractYouTubeId,
1698
+ formatTime,
1699
+ parseDurationToSeconds,
1700
+ useAudioPlayer
1701
+ };
1702
+ //# sourceMappingURL=index.mjs.map