@scarlett-player/audio-ui 0.2.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.cjs ADDED
@@ -0,0 +1,739 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createAudioUIPlugin: () => createAudioUIPlugin,
24
+ default: () => index_default
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var DEFAULT_THEME = {
28
+ primary: "#6366f1",
29
+ background: "#18181b",
30
+ text: "#fafafa",
31
+ textSecondary: "#a1a1aa",
32
+ progressBackground: "#3f3f46",
33
+ progressFill: "#6366f1",
34
+ borderRadius: "12px",
35
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
36
+ };
37
+ var DEFAULT_CONFIG = {
38
+ layout: "full",
39
+ showArtwork: true,
40
+ showTitle: true,
41
+ showArtist: true,
42
+ showTime: true,
43
+ showVolume: true,
44
+ showShuffle: true,
45
+ showRepeat: true,
46
+ showNavigation: true,
47
+ classPrefix: "scarlett-audio",
48
+ autoHide: 0,
49
+ theme: DEFAULT_THEME
50
+ };
51
+ function formatTime(seconds) {
52
+ if (!isFinite(seconds) || seconds < 0) return "0:00";
53
+ const h = Math.floor(seconds / 3600);
54
+ const m = Math.floor(seconds % 3600 / 60);
55
+ const s = Math.floor(seconds % 60);
56
+ if (h > 0) {
57
+ return `${h}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
58
+ }
59
+ return `${m}:${s.toString().padStart(2, "0")}`;
60
+ }
61
+ function createStyles(prefix, theme) {
62
+ return `
63
+ .${prefix} {
64
+ font-family: ${theme.fontFamily};
65
+ background: ${theme.background};
66
+ color: ${theme.text};
67
+ border-radius: ${theme.borderRadius};
68
+ overflow: hidden;
69
+ user-select: none;
70
+ }
71
+
72
+ .${prefix}--full {
73
+ display: flex;
74
+ flex-direction: column;
75
+ padding: 20px;
76
+ gap: 16px;
77
+ max-width: 400px;
78
+ }
79
+
80
+ .${prefix}--compact {
81
+ display: flex;
82
+ align-items: center;
83
+ padding: 12px 16px;
84
+ gap: 12px;
85
+ }
86
+
87
+ .${prefix}--mini {
88
+ display: flex;
89
+ align-items: center;
90
+ padding: 8px 12px;
91
+ gap: 8px;
92
+ }
93
+
94
+ .${prefix}__artwork {
95
+ flex-shrink: 0;
96
+ background: ${theme.progressBackground};
97
+ border-radius: 8px;
98
+ overflow: hidden;
99
+ display: flex;
100
+ align-items: center;
101
+ justify-content: center;
102
+ }
103
+
104
+ .${prefix}--full .${prefix}__artwork {
105
+ width: 100%;
106
+ aspect-ratio: 1;
107
+ border-radius: ${theme.borderRadius};
108
+ }
109
+
110
+ .${prefix}--compact .${prefix}__artwork {
111
+ width: 56px;
112
+ height: 56px;
113
+ }
114
+
115
+ .${prefix}--mini .${prefix}__artwork {
116
+ width: 40px;
117
+ height: 40px;
118
+ border-radius: 6px;
119
+ }
120
+
121
+ .${prefix}__artwork img {
122
+ width: 100%;
123
+ height: 100%;
124
+ object-fit: cover;
125
+ }
126
+
127
+ .${prefix}__artwork-placeholder {
128
+ width: 50%;
129
+ height: 50%;
130
+ opacity: 0.3;
131
+ }
132
+
133
+ .${prefix}__info {
134
+ flex: 1;
135
+ min-width: 0;
136
+ display: flex;
137
+ flex-direction: column;
138
+ gap: 4px;
139
+ }
140
+
141
+ .${prefix}__title {
142
+ font-size: 16px;
143
+ font-weight: 600;
144
+ white-space: nowrap;
145
+ overflow: hidden;
146
+ text-overflow: ellipsis;
147
+ }
148
+
149
+ .${prefix}--mini .${prefix}__title {
150
+ font-size: 14px;
151
+ display: inline-block;
152
+ animation: none;
153
+ }
154
+
155
+ .${prefix}__title-wrapper {
156
+ overflow: hidden;
157
+ width: 100%;
158
+ }
159
+
160
+ .${prefix}--mini .${prefix}__title-wrapper .${prefix}__title.scrolling {
161
+ animation: marquee 8s linear infinite;
162
+ }
163
+
164
+ @keyframes marquee {
165
+ 0% { transform: translateX(0); }
166
+ 100% { transform: translateX(-50%); }
167
+ }
168
+
169
+ .${prefix}--mini .${prefix}__progress {
170
+ margin-top: 4px;
171
+ }
172
+
173
+ .${prefix}--mini .${prefix}__progress-bar {
174
+ height: 4px;
175
+ }
176
+
177
+ .${prefix}__artist {
178
+ font-size: 14px;
179
+ color: ${theme.textSecondary};
180
+ white-space: nowrap;
181
+ overflow: hidden;
182
+ text-overflow: ellipsis;
183
+ }
184
+
185
+ .${prefix}--mini .${prefix}__artist {
186
+ font-size: 12px;
187
+ }
188
+
189
+ .${prefix}__progress {
190
+ display: flex;
191
+ align-items: center;
192
+ gap: 12px;
193
+ }
194
+
195
+ .${prefix}__progress-bar {
196
+ flex: 1;
197
+ height: 6px;
198
+ background: ${theme.progressBackground};
199
+ border-radius: 3px;
200
+ cursor: pointer;
201
+ position: relative;
202
+ overflow: hidden;
203
+ }
204
+
205
+ .${prefix}__progress-bar:hover {
206
+ height: 8px;
207
+ }
208
+
209
+ .${prefix}__progress-fill {
210
+ height: 100%;
211
+ background: ${theme.progressFill};
212
+ border-radius: 3px;
213
+ width: 100%;
214
+ transform-origin: left center;
215
+ will-change: transform;
216
+ }
217
+
218
+ .${prefix}__progress-buffered {
219
+ position: absolute;
220
+ top: 0;
221
+ left: 0;
222
+ height: 100%;
223
+ background: ${theme.progressBackground};
224
+ opacity: 0.5;
225
+ border-radius: 3px;
226
+ }
227
+
228
+ .${prefix}__time {
229
+ font-size: 12px;
230
+ color: ${theme.textSecondary};
231
+ min-width: 40px;
232
+ text-align: center;
233
+ }
234
+
235
+ .${prefix}__controls {
236
+ display: flex;
237
+ align-items: center;
238
+ justify-content: center;
239
+ gap: 8px;
240
+ }
241
+
242
+ .${prefix}__btn {
243
+ background: transparent;
244
+ border: none;
245
+ color: ${theme.text};
246
+ cursor: pointer;
247
+ padding: 8px;
248
+ border-radius: 50%;
249
+ display: flex;
250
+ align-items: center;
251
+ justify-content: center;
252
+ transition: background 0.2s, transform 0.1s;
253
+ }
254
+
255
+ .${prefix}__btn:hover {
256
+ background: rgba(255, 255, 255, 0.1);
257
+ }
258
+
259
+ .${prefix}__btn:active {
260
+ transform: scale(0.95);
261
+ }
262
+
263
+ .${prefix}__btn--primary {
264
+ background: ${theme.primary};
265
+ width: 48px;
266
+ height: 48px;
267
+ }
268
+
269
+ .${prefix}__btn--primary:hover {
270
+ background: ${theme.primary};
271
+ opacity: 0.9;
272
+ }
273
+
274
+ .${prefix}--mini .${prefix}__btn--primary {
275
+ width: 36px;
276
+ height: 36px;
277
+ }
278
+
279
+ .${prefix}__btn--active {
280
+ color: ${theme.primary};
281
+ }
282
+
283
+ .${prefix}__btn svg {
284
+ width: 20px;
285
+ height: 20px;
286
+ fill: currentColor;
287
+ }
288
+
289
+ .${prefix}__btn--primary svg {
290
+ width: 24px;
291
+ height: 24px;
292
+ }
293
+
294
+ .${prefix}__volume {
295
+ display: flex;
296
+ align-items: center;
297
+ gap: 8px;
298
+ }
299
+
300
+ .${prefix}__volume-slider {
301
+ width: 80px;
302
+ height: 4px;
303
+ background: ${theme.progressBackground};
304
+ border-radius: 2px;
305
+ cursor: pointer;
306
+ position: relative;
307
+ }
308
+
309
+ .${prefix}__volume-fill {
310
+ height: 100%;
311
+ background: ${theme.text};
312
+ border-radius: 2px;
313
+ }
314
+
315
+ .${prefix}__secondary-controls {
316
+ display: flex;
317
+ align-items: center;
318
+ justify-content: space-between;
319
+ }
320
+
321
+ .${prefix}--hidden {
322
+ display: none;
323
+ }
324
+ `;
325
+ }
326
+ var ICONS = {
327
+ play: `<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>`,
328
+ pause: `<svg viewBox="0 0 24 24"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>`,
329
+ previous: `<svg viewBox="0 0 24 24"><path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/></svg>`,
330
+ next: `<svg viewBox="0 0 24 24"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg>`,
331
+ shuffle: `<svg viewBox="0 0 24 24"><path d="M10.59 9.17L5.41 4 4 5.41l5.17 5.17 1.42-1.41zM14.5 4l2.04 2.04L4 18.59 5.41 20 17.96 7.46 20 9.5V4h-5.5zm.33 9.41l-1.41 1.41 3.13 3.13L14.5 20H20v-5.5l-2.04 2.04-3.13-3.13z"/></svg>`,
332
+ repeatOff: `<svg viewBox="0 0 24 24"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/></svg>`,
333
+ repeatAll: `<svg viewBox="0 0 24 24"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/></svg>`,
334
+ repeatOne: `<svg viewBox="0 0 24 24"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4zm-4-2V9h-1l-2 1v1h1.5v4H13z"/></svg>`,
335
+ volumeHigh: `<svg viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/></svg>`,
336
+ volumeMuted: `<svg viewBox="0 0 24 24"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/></svg>`,
337
+ music: `<svg viewBox="0 0 24 24"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></svg>`
338
+ };
339
+ function createAudioUIPlugin(config) {
340
+ const mergedConfig = { ...DEFAULT_CONFIG, ...config };
341
+ const theme = { ...DEFAULT_THEME, ...mergedConfig.theme };
342
+ const prefix = mergedConfig.classPrefix;
343
+ let api = null;
344
+ let container = null;
345
+ let styleElement = null;
346
+ let layout = mergedConfig.layout;
347
+ let isVisible = true;
348
+ let animationFrameId = null;
349
+ let lastKnownTime = 0;
350
+ let lastUpdateTimestamp = 0;
351
+ let isPlaying = false;
352
+ let artworkImg = null;
353
+ let titleEl = null;
354
+ let artistEl = null;
355
+ let progressFill = null;
356
+ let currentTimeEl = null;
357
+ let durationEl = null;
358
+ let playPauseBtn = null;
359
+ let shuffleBtn = null;
360
+ let repeatBtn = null;
361
+ let volumeBtn = null;
362
+ let volumeFill = null;
363
+ const startProgressAnimation = () => {
364
+ if (animationFrameId !== null) return;
365
+ const animate = (timestamp) => {
366
+ if (!api || !isPlaying) {
367
+ animationFrameId = null;
368
+ return;
369
+ }
370
+ const duration = api.getState("duration") || 0;
371
+ if (duration <= 0) {
372
+ animationFrameId = requestAnimationFrame(animate);
373
+ return;
374
+ }
375
+ const elapsed = (timestamp - lastUpdateTimestamp) / 1e3;
376
+ const interpolatedTime = Math.min(lastKnownTime + elapsed, duration);
377
+ const scale = interpolatedTime / duration;
378
+ if (progressFill) {
379
+ progressFill.style.transform = `scaleX(${scale})`;
380
+ }
381
+ if (currentTimeEl) {
382
+ currentTimeEl.textContent = formatTime(interpolatedTime);
383
+ }
384
+ animationFrameId = requestAnimationFrame(animate);
385
+ };
386
+ lastUpdateTimestamp = performance.now();
387
+ animationFrameId = requestAnimationFrame(animate);
388
+ };
389
+ const stopProgressAnimation = () => {
390
+ if (animationFrameId !== null) {
391
+ cancelAnimationFrame(animationFrameId);
392
+ animationFrameId = null;
393
+ }
394
+ };
395
+ const createUI = () => {
396
+ if (!api) return;
397
+ styleElement = document.createElement("style");
398
+ styleElement.textContent = createStyles(prefix, theme);
399
+ document.head.appendChild(styleElement);
400
+ container = document.createElement("div");
401
+ container.className = `${prefix} ${prefix}--${layout}`;
402
+ if (layout === "full") {
403
+ container.innerHTML = buildFullLayout();
404
+ } else if (layout === "compact") {
405
+ container.innerHTML = buildCompactLayout();
406
+ } else {
407
+ container.innerHTML = buildMiniLayout();
408
+ }
409
+ artworkImg = container.querySelector(`.${prefix}__artwork img`);
410
+ titleEl = container.querySelector(`.${prefix}__title`);
411
+ artistEl = container.querySelector(`.${prefix}__artist`);
412
+ progressFill = container.querySelector(`.${prefix}__progress-fill`);
413
+ currentTimeEl = container.querySelector(`.${prefix}__time--current`);
414
+ durationEl = container.querySelector(`.${prefix}__time--duration`);
415
+ playPauseBtn = container.querySelector(`.${prefix}__btn--play`);
416
+ shuffleBtn = container.querySelector(`.${prefix}__btn--shuffle`);
417
+ repeatBtn = container.querySelector(`.${prefix}__btn--repeat`);
418
+ volumeBtn = container.querySelector(`.${prefix}__btn--volume`);
419
+ volumeFill = container.querySelector(`.${prefix}__volume-fill`);
420
+ attachEventListeners();
421
+ api.container.appendChild(container);
422
+ };
423
+ const buildFullLayout = () => {
424
+ return `
425
+ ${mergedConfig.showArtwork ? `
426
+ <div class="${prefix}__artwork">
427
+ <img src="${mergedConfig.defaultArtwork || ""}" alt="Album art" />
428
+ ${!mergedConfig.defaultArtwork ? `<div class="${prefix}__artwork-placeholder">${ICONS.music}</div>` : ""}
429
+ </div>
430
+ ` : ""}
431
+ <div class="${prefix}__info">
432
+ ${mergedConfig.showTitle ? `<div class="${prefix}__title">-</div>` : ""}
433
+ ${mergedConfig.showArtist ? `<div class="${prefix}__artist">-</div>` : ""}
434
+ </div>
435
+ <div class="${prefix}__progress">
436
+ ${mergedConfig.showTime ? `<span class="${prefix}__time ${prefix}__time--current">0:00</span>` : ""}
437
+ <div class="${prefix}__progress-bar">
438
+ <div class="${prefix}__progress-fill" style="transform: scaleX(0)"></div>
439
+ </div>
440
+ ${mergedConfig.showTime ? `<span class="${prefix}__time ${prefix}__time--duration">0:00</span>` : ""}
441
+ </div>
442
+ <div class="${prefix}__controls">
443
+ ${mergedConfig.showShuffle ? `<button class="${prefix}__btn ${prefix}__btn--shuffle" title="Shuffle">${ICONS.shuffle}</button>` : ""}
444
+ ${mergedConfig.showNavigation ? `<button class="${prefix}__btn ${prefix}__btn--prev" title="Previous">${ICONS.previous}</button>` : ""}
445
+ <button class="${prefix}__btn ${prefix}__btn--primary ${prefix}__btn--play" title="Play">${ICONS.play}</button>
446
+ ${mergedConfig.showNavigation ? `<button class="${prefix}__btn ${prefix}__btn--next" title="Next">${ICONS.next}</button>` : ""}
447
+ ${mergedConfig.showRepeat ? `<button class="${prefix}__btn ${prefix}__btn--repeat" title="Repeat">${ICONS.repeatOff}</button>` : ""}
448
+ </div>
449
+ ${mergedConfig.showVolume ? `
450
+ <div class="${prefix}__secondary-controls">
451
+ <div class="${prefix}__volume">
452
+ <button class="${prefix}__btn ${prefix}__btn--volume" title="Volume">${ICONS.volumeHigh}</button>
453
+ <div class="${prefix}__volume-slider">
454
+ <div class="${prefix}__volume-fill" style="width: 100%"></div>
455
+ </div>
456
+ </div>
457
+ </div>
458
+ ` : ""}
459
+ `;
460
+ };
461
+ const buildCompactLayout = () => {
462
+ return `
463
+ ${mergedConfig.showArtwork ? `
464
+ <div class="${prefix}__artwork">
465
+ <img src="${mergedConfig.defaultArtwork || ""}" alt="Album art" />
466
+ </div>
467
+ ` : ""}
468
+ <div class="${prefix}__info">
469
+ ${mergedConfig.showTitle ? `<div class="${prefix}__title">-</div>` : ""}
470
+ ${mergedConfig.showArtist ? `<div class="${prefix}__artist">-</div>` : ""}
471
+ <div class="${prefix}__progress">
472
+ <div class="${prefix}__progress-bar">
473
+ <div class="${prefix}__progress-fill" style="transform: scaleX(0)"></div>
474
+ </div>
475
+ </div>
476
+ </div>
477
+ <div class="${prefix}__controls">
478
+ ${mergedConfig.showNavigation ? `<button class="${prefix}__btn ${prefix}__btn--prev" title="Previous">${ICONS.previous}</button>` : ""}
479
+ <button class="${prefix}__btn ${prefix}__btn--primary ${prefix}__btn--play" title="Play">${ICONS.play}</button>
480
+ ${mergedConfig.showNavigation ? `<button class="${prefix}__btn ${prefix}__btn--next" title="Next">${ICONS.next}</button>` : ""}
481
+ </div>
482
+ `;
483
+ };
484
+ const buildMiniLayout = () => {
485
+ return `
486
+ <button class="${prefix}__btn ${prefix}__btn--primary ${prefix}__btn--play" title="Play">${ICONS.play}</button>
487
+ ${mergedConfig.showArtwork ? `
488
+ <div class="${prefix}__artwork">
489
+ <img src="${mergedConfig.defaultArtwork || ""}" alt="Album art" />
490
+ </div>
491
+ ` : ""}
492
+ <div class="${prefix}__info">
493
+ ${mergedConfig.showTitle ? `<div class="${prefix}__title-wrapper"><div class="${prefix}__title">-</div></div>` : ""}
494
+ <div class="${prefix}__progress">
495
+ <div class="${prefix}__progress-bar">
496
+ <div class="${prefix}__progress-fill" style="transform: scaleX(0)"></div>
497
+ </div>
498
+ </div>
499
+ </div>
500
+ `;
501
+ };
502
+ const attachEventListeners = () => {
503
+ if (!container || !api) return;
504
+ playPauseBtn?.addEventListener("click", () => {
505
+ const playing = api?.getState("playing");
506
+ if (playing) {
507
+ api?.emit("playback:pause", void 0);
508
+ } else {
509
+ api?.emit("playback:play", void 0);
510
+ }
511
+ });
512
+ container.querySelector(`.${prefix}__btn--prev`)?.addEventListener("click", () => {
513
+ const playlist = api?.getPlugin("playlist");
514
+ if (playlist) {
515
+ playlist.previous();
516
+ } else {
517
+ api?.emit("playback:seeking", { time: 0 });
518
+ }
519
+ });
520
+ container.querySelector(`.${prefix}__btn--next`)?.addEventListener("click", () => {
521
+ const playlist = api?.getPlugin("playlist");
522
+ playlist?.next();
523
+ });
524
+ shuffleBtn?.addEventListener("click", () => {
525
+ const playlist = api?.getPlugin("playlist");
526
+ playlist?.toggleShuffle();
527
+ });
528
+ repeatBtn?.addEventListener("click", () => {
529
+ const playlist = api?.getPlugin("playlist");
530
+ playlist?.cycleRepeat();
531
+ });
532
+ volumeBtn?.addEventListener("click", () => {
533
+ const muted = api?.getState("muted");
534
+ api?.emit("volume:mute", { muted: !muted });
535
+ });
536
+ const progressBar = container.querySelector(`.${prefix}__progress-bar`);
537
+ progressBar?.addEventListener("click", (e) => {
538
+ const mouseEvent = e;
539
+ const rect = mouseEvent.currentTarget.getBoundingClientRect();
540
+ const percent = (mouseEvent.clientX - rect.left) / rect.width;
541
+ const duration = api?.getState("duration") || 0;
542
+ const time = percent * duration;
543
+ api?.emit("playback:seeking", { time });
544
+ });
545
+ const volumeSlider = container.querySelector(`.${prefix}__volume-slider`);
546
+ volumeSlider?.addEventListener("click", (e) => {
547
+ const mouseEvent = e;
548
+ const rect = mouseEvent.currentTarget.getBoundingClientRect();
549
+ const percent = Math.max(0, Math.min(1, (mouseEvent.clientX - rect.left) / rect.width));
550
+ api?.emit("volume:change", { volume: percent, muted: false });
551
+ });
552
+ };
553
+ const updateUI = () => {
554
+ if (!api || !container) return;
555
+ const playing = api.getState("playing");
556
+ const wasPlaying = isPlaying;
557
+ isPlaying = playing;
558
+ if (playPauseBtn) {
559
+ playPauseBtn.innerHTML = playing ? ICONS.pause : ICONS.play;
560
+ playPauseBtn.title = playing ? "Pause" : "Play";
561
+ }
562
+ const currentTime = api.getState("currentTime") || 0;
563
+ const duration = api.getState("duration") || 0;
564
+ lastKnownTime = currentTime;
565
+ lastUpdateTimestamp = performance.now();
566
+ if (playing && !wasPlaying) {
567
+ startProgressAnimation();
568
+ } else if (!playing && wasPlaying) {
569
+ stopProgressAnimation();
570
+ }
571
+ if (!playing) {
572
+ const scale = duration > 0 ? currentTime / duration : 0;
573
+ if (progressFill) {
574
+ progressFill.style.transform = `scaleX(${scale})`;
575
+ }
576
+ if (currentTimeEl) {
577
+ currentTimeEl.textContent = formatTime(currentTime);
578
+ }
579
+ }
580
+ if (durationEl) {
581
+ durationEl.textContent = formatTime(duration);
582
+ }
583
+ const title = api.getState("title");
584
+ const poster = api.getState("poster");
585
+ if (titleEl && title) {
586
+ titleEl.textContent = title;
587
+ if (layout === "mini") {
588
+ const wrapper = titleEl.parentElement;
589
+ if (wrapper && titleEl.scrollWidth > wrapper.clientWidth) {
590
+ titleEl.textContent = `${title} \u2022 ${title} \u2022 `;
591
+ titleEl.classList.add("scrolling");
592
+ } else {
593
+ titleEl.classList.remove("scrolling");
594
+ }
595
+ }
596
+ }
597
+ if (artworkImg && poster) {
598
+ artworkImg.src = poster;
599
+ }
600
+ const volume = api.getState("volume") || 1;
601
+ const muted = api.getState("muted");
602
+ if (volumeFill) {
603
+ volumeFill.style.width = `${(muted ? 0 : volume) * 100}%`;
604
+ }
605
+ if (volumeBtn) {
606
+ volumeBtn.innerHTML = muted || volume === 0 ? ICONS.volumeMuted : ICONS.volumeHigh;
607
+ }
608
+ const playlist = api.getPlugin("playlist");
609
+ if (playlist) {
610
+ const state = playlist.getState();
611
+ if (shuffleBtn) {
612
+ shuffleBtn.classList.toggle(`${prefix}__btn--active`, state.shuffle);
613
+ }
614
+ if (repeatBtn) {
615
+ repeatBtn.classList.toggle(`${prefix}__btn--active`, state.repeat !== "none");
616
+ if (state.repeat === "one") {
617
+ repeatBtn.innerHTML = ICONS.repeatOne;
618
+ } else if (state.repeat === "all") {
619
+ repeatBtn.innerHTML = ICONS.repeatAll;
620
+ } else {
621
+ repeatBtn.innerHTML = ICONS.repeatOff;
622
+ }
623
+ }
624
+ }
625
+ };
626
+ const plugin = {
627
+ id: "audio-ui",
628
+ name: "Audio UI",
629
+ version: "1.0.0",
630
+ type: "ui",
631
+ description: "Compact audio player interface",
632
+ async init(pluginApi) {
633
+ api = pluginApi;
634
+ api.logger.info("Audio UI plugin initialized");
635
+ createUI();
636
+ const unsubState = api.subscribeToState(() => {
637
+ updateUI();
638
+ });
639
+ const unsubTime = api.on("playback:timeupdate", () => {
640
+ updateUI();
641
+ });
642
+ const unsubPlaylist = api.on("playlist:change", (payload) => {
643
+ if (payload?.track) {
644
+ if (titleEl) titleEl.textContent = payload.track.title || "-";
645
+ if (artistEl) artistEl.textContent = payload.track.artist || "-";
646
+ if (artworkImg && payload.track.artwork) {
647
+ artworkImg.src = payload.track.artwork;
648
+ }
649
+ }
650
+ });
651
+ const unsubShuffle = api.on("playlist:shuffle", () => {
652
+ updateUI();
653
+ });
654
+ const unsubRepeat = api.on("playlist:repeat", () => {
655
+ updateUI();
656
+ });
657
+ api.onDestroy(() => {
658
+ unsubState();
659
+ unsubTime();
660
+ unsubPlaylist();
661
+ unsubShuffle();
662
+ unsubRepeat();
663
+ });
664
+ updateUI();
665
+ },
666
+ async destroy() {
667
+ api?.logger.info("Audio UI plugin destroying");
668
+ stopProgressAnimation();
669
+ if (container?.parentNode) {
670
+ container.parentNode.removeChild(container);
671
+ }
672
+ if (styleElement?.parentNode) {
673
+ styleElement.parentNode.removeChild(styleElement);
674
+ }
675
+ container = null;
676
+ styleElement = null;
677
+ api = null;
678
+ },
679
+ getElement() {
680
+ return container;
681
+ },
682
+ setLayout(newLayout) {
683
+ if (!container) return;
684
+ stopProgressAnimation();
685
+ layout = newLayout;
686
+ container.className = `${prefix} ${prefix}--${layout}`;
687
+ if (layout === "full") {
688
+ container.innerHTML = buildFullLayout();
689
+ } else if (layout === "compact") {
690
+ container.innerHTML = buildCompactLayout();
691
+ } else {
692
+ container.innerHTML = buildMiniLayout();
693
+ }
694
+ artworkImg = container.querySelector(`.${prefix}__artwork img`);
695
+ titleEl = container.querySelector(`.${prefix}__title`);
696
+ artistEl = container.querySelector(`.${prefix}__artist`);
697
+ progressFill = container.querySelector(`.${prefix}__progress-fill`);
698
+ currentTimeEl = container.querySelector(`.${prefix}__time--current`);
699
+ durationEl = container.querySelector(`.${prefix}__time--duration`);
700
+ playPauseBtn = container.querySelector(`.${prefix}__btn--play`);
701
+ shuffleBtn = container.querySelector(`.${prefix}__btn--shuffle`);
702
+ repeatBtn = container.querySelector(`.${prefix}__btn--repeat`);
703
+ volumeBtn = container.querySelector(`.${prefix}__btn--volume`);
704
+ volumeFill = container.querySelector(`.${prefix}__volume-fill`);
705
+ attachEventListeners();
706
+ updateUI();
707
+ if (isPlaying) {
708
+ startProgressAnimation();
709
+ }
710
+ },
711
+ setTheme(newTheme) {
712
+ Object.assign(theme, newTheme);
713
+ if (styleElement) {
714
+ styleElement.textContent = createStyles(prefix, theme);
715
+ }
716
+ },
717
+ show() {
718
+ isVisible = true;
719
+ container?.classList.remove(`${prefix}--hidden`);
720
+ },
721
+ hide() {
722
+ isVisible = false;
723
+ container?.classList.add(`${prefix}--hidden`);
724
+ },
725
+ toggle() {
726
+ if (isVisible) {
727
+ this.hide();
728
+ } else {
729
+ this.show();
730
+ }
731
+ }
732
+ };
733
+ return plugin;
734
+ }
735
+ var index_default = createAudioUIPlugin;
736
+ // Annotate the CommonJS export names for ESM import in node:
737
+ 0 && (module.exports = {
738
+ createAudioUIPlugin
739
+ });