@arraypress/waveform-bar 1.3.1 → 1.4.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/waveform-bar.css +61 -1
- package/dist/waveform-bar.esm.js +450 -58
- package/dist/waveform-bar.js +450 -58
- package/dist/waveform-bar.min.css +1 -1
- package/dist/waveform-bar.min.js +6 -6
- package/package.json +9 -3
- package/src/css/waveform-bar.css +61 -1
- package/src/js/actions.js +2 -2
- package/src/js/core.js +529 -57
- package/src/js/dom.js +14 -1
- package/src/js/icons.js +3 -0
- package/src/js/utils.js +49 -4
package/dist/waveform-bar.js
CHANGED
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
prev: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/></svg>',
|
|
7
7
|
next: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg>',
|
|
8
8
|
queue: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg>',
|
|
9
|
+
share: '<svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"/><polyline points="16 6 12 2 8 6"/><line x1="12" y1="2" x2="12" y2="15"/></svg>',
|
|
9
10
|
music: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" opacity="0.5"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55C7.79 13 6 14.79 6 17s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></svg>',
|
|
11
|
+
collapse: '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>',
|
|
12
|
+
expand: '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>',
|
|
10
13
|
volHigh: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><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>',
|
|
11
14
|
volLow: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"/></svg>',
|
|
12
15
|
volMute: '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><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>',
|
|
@@ -31,6 +34,15 @@
|
|
|
31
34
|
d.textContent = str;
|
|
32
35
|
return d.innerHTML;
|
|
33
36
|
}
|
|
37
|
+
function isSafeHref(url) {
|
|
38
|
+
if (typeof url !== "string" || url === "") return false;
|
|
39
|
+
try {
|
|
40
|
+
const u = new URL(url, location.href);
|
|
41
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
34
46
|
function formatTime(seconds) {
|
|
35
47
|
if (!seconds || isNaN(seconds)) return "0:00";
|
|
36
48
|
const m = Math.floor(seconds / 60);
|
|
@@ -42,12 +54,21 @@
|
|
|
42
54
|
if (!url) return null;
|
|
43
55
|
let meta = {};
|
|
44
56
|
try {
|
|
45
|
-
|
|
57
|
+
const parsed = JSON.parse(el.dataset.wbMeta || el.dataset.meta || "{}");
|
|
58
|
+
meta = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
59
|
+
} catch (e) {
|
|
60
|
+
}
|
|
61
|
+
let markers = [];
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(el.dataset.wbMarkers || el.dataset.markers || "null");
|
|
64
|
+
markers = Array.isArray(parsed) ? parsed : [];
|
|
46
65
|
} catch (e) {
|
|
47
66
|
}
|
|
48
|
-
|
|
67
|
+
markers = markers.map((m) => m && typeof m === "object" ? { ...m, time: Number(m.time) } : null).filter((m) => m && Number.isFinite(m.time));
|
|
68
|
+
let waveform = null;
|
|
49
69
|
try {
|
|
50
|
-
|
|
70
|
+
const parsed = JSON.parse(el.dataset.wbWaveform || el.dataset.waveform || "null");
|
|
71
|
+
waveform = Array.isArray(parsed) ? parsed : null;
|
|
51
72
|
} catch (e) {
|
|
52
73
|
}
|
|
53
74
|
return {
|
|
@@ -61,7 +82,7 @@
|
|
|
61
82
|
duration: el.dataset.wbDuration || el.dataset.duration || "",
|
|
62
83
|
bpm: el.dataset.wbBpm || el.dataset.bpm || "",
|
|
63
84
|
key: el.dataset.wbKey || el.dataset.key || "",
|
|
64
|
-
waveform
|
|
85
|
+
waveform,
|
|
65
86
|
markers,
|
|
66
87
|
favorited: el.dataset.wbFavorited === "true",
|
|
67
88
|
inCart: el.dataset.wbInCart === "true",
|
|
@@ -133,7 +154,7 @@
|
|
|
133
154
|
try {
|
|
134
155
|
actionConfig.endpoint(payload);
|
|
135
156
|
} catch (err) {
|
|
136
|
-
console.warn("WaveformBar
|
|
157
|
+
console.warn("[WaveformBar] Action callback error:", err);
|
|
137
158
|
}
|
|
138
159
|
return;
|
|
139
160
|
}
|
|
@@ -145,7 +166,7 @@
|
|
|
145
166
|
...actionConfig.headers || {}
|
|
146
167
|
},
|
|
147
168
|
body: JSON.stringify(payload)
|
|
148
|
-
}).catch((err) => console.warn("WaveformBar
|
|
169
|
+
}).catch((err) => console.warn("[WaveformBar] Action request failed:", err));
|
|
149
170
|
}
|
|
150
171
|
}
|
|
151
172
|
|
|
@@ -203,11 +224,15 @@
|
|
|
203
224
|
}
|
|
204
225
|
right += "</div>";
|
|
205
226
|
}
|
|
227
|
+
if (config.share) {
|
|
228
|
+
right += `<button class="wb-btn wb-btn-sm wb-share" aria-label="Share" title="Copy share link">${ICONS.share}</button>`;
|
|
229
|
+
}
|
|
206
230
|
if (config.showQueue) {
|
|
207
231
|
right += `<button class="wb-btn wb-btn-sm wb-queue-btn" aria-label="Queue" title="Queue">${ICONS.queue}</button>`;
|
|
208
232
|
}
|
|
209
233
|
right += "</div>";
|
|
210
|
-
|
|
234
|
+
const collapse = config.collapsible ? `<button class="wb-btn wb-btn-sm wb-collapse" aria-label="Collapse" title="Collapse">${ICONS.collapse}</button>` : "";
|
|
235
|
+
return `<div class="wb-inner">${left}${centre}${right}${collapse}</div>`;
|
|
211
236
|
}
|
|
212
237
|
|
|
213
238
|
// src/js/queue.js
|
|
@@ -311,10 +336,27 @@
|
|
|
311
336
|
// URL to fallback artwork image
|
|
312
337
|
theme: null,
|
|
313
338
|
// 'dark', 'light', or null (dark by default)
|
|
339
|
+
wide: false,
|
|
340
|
+
// true = content spans full width (lifts the 1400px cap)
|
|
341
|
+
maxWidth: null,
|
|
342
|
+
// custom content max-width (CSS value), e.g. '1200px'; overrides `wide`
|
|
343
|
+
position: "bottom",
|
|
344
|
+
// 'bottom' (default) or 'top' — which edge the bar docks to
|
|
345
|
+
collapsible: false,
|
|
346
|
+
// show a collapse button that shrinks the bar to a floating transport pill
|
|
347
|
+
waveform: true,
|
|
348
|
+
// false = classic Spotify-style seek bar instead of the waveform
|
|
349
|
+
errorText: null,
|
|
350
|
+
// custom "audio failed to load" message (null = player default)
|
|
351
|
+
share: false,
|
|
352
|
+
// show a "copy share link" button (emits ?<shareParam>=<seconds>)
|
|
353
|
+
shareParam: "wt",
|
|
354
|
+
// URL query param for the shared timestamp (seconds)
|
|
314
355
|
waveformStyle: "mirror",
|
|
315
356
|
waveformHeight: 32,
|
|
316
357
|
barWidth: 2,
|
|
317
|
-
barSpacing:
|
|
358
|
+
barSpacing: 2,
|
|
359
|
+
// 2px gap between 2px bars — crisp, separated bars (0 = solid "blob")
|
|
318
360
|
waveformColor: null,
|
|
319
361
|
progressColor: null,
|
|
320
362
|
markerColor: "rgba(255, 255, 255, 0.25)",
|
|
@@ -348,6 +390,9 @@
|
|
|
348
390
|
this._activeMarkers = null;
|
|
349
391
|
this._currentMarkerIndex = -1;
|
|
350
392
|
this.repeat = "off";
|
|
393
|
+
this._loadSeq = 0;
|
|
394
|
+
this._restoreSeekTimeout = null;
|
|
395
|
+
this._externalPlayers = /* @__PURE__ */ new Map();
|
|
351
396
|
this.barEl = null;
|
|
352
397
|
this.queueEl = null;
|
|
353
398
|
this.waveformContainer = null;
|
|
@@ -378,9 +423,12 @@
|
|
|
378
423
|
init(config = {}) {
|
|
379
424
|
if (this.isInitialized) this.destroy();
|
|
380
425
|
this.config = { ...DEFAULTS, ...config };
|
|
381
|
-
|
|
426
|
+
const v = Number(this.config.volume);
|
|
427
|
+
this.volume = Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 1;
|
|
428
|
+
this._shareTarget = this._readShareTarget();
|
|
429
|
+
this._shareSeek = this._shareTarget && !this._shareTarget.id && !this._shareTarget.url ? this._shareTarget.time : null;
|
|
382
430
|
if (typeof window.WaveformPlayer === "undefined") {
|
|
383
|
-
console.error("WaveformBar
|
|
431
|
+
console.error("[WaveformBar] WaveformPlayer is required.");
|
|
384
432
|
return this;
|
|
385
433
|
}
|
|
386
434
|
this._createBar();
|
|
@@ -396,6 +444,10 @@
|
|
|
396
444
|
if (this.config.persist) {
|
|
397
445
|
this._restoreState();
|
|
398
446
|
}
|
|
447
|
+
if (this._shareTarget && (this._shareTarget.id || this._shareTarget.url)) {
|
|
448
|
+
const shared = this._resolveSharedTrack(this._shareTarget);
|
|
449
|
+
if (shared) this._loadSharedTrack(shared, this._shareTarget.time);
|
|
450
|
+
}
|
|
399
451
|
this.isInitialized = true;
|
|
400
452
|
this._beforeUnloadHandler = () => this._saveState();
|
|
401
453
|
window.addEventListener("beforeunload", this._beforeUnloadHandler);
|
|
@@ -410,6 +462,37 @@
|
|
|
410
462
|
this.player.destroy();
|
|
411
463
|
this.player = null;
|
|
412
464
|
}
|
|
465
|
+
if (this._docClickVolume) {
|
|
466
|
+
document.removeEventListener("click", this._docClickVolume);
|
|
467
|
+
this._docClickVolume = null;
|
|
468
|
+
}
|
|
469
|
+
if (this._docClickQueue) {
|
|
470
|
+
document.removeEventListener("click", this._docClickQueue);
|
|
471
|
+
this._docClickQueue = null;
|
|
472
|
+
}
|
|
473
|
+
if (this._docClickTriggers) {
|
|
474
|
+
document.removeEventListener("click", this._docClickTriggers);
|
|
475
|
+
this._docClickTriggers = null;
|
|
476
|
+
}
|
|
477
|
+
if (this._externalListenersBound) {
|
|
478
|
+
document.removeEventListener("waveformplayer:request-play", this._onExtRequestPlay);
|
|
479
|
+
document.removeEventListener("waveformplayer:request-pause", this._onExtRequestPause);
|
|
480
|
+
document.removeEventListener("waveformplayer:request-seek", this._onExtRequestSeek);
|
|
481
|
+
document.removeEventListener("waveformplayer:destroy", this._onExtDestroy);
|
|
482
|
+
this._onExtRequestPlay = null;
|
|
483
|
+
this._onExtRequestPause = null;
|
|
484
|
+
this._onExtRequestSeek = null;
|
|
485
|
+
this._onExtDestroy = null;
|
|
486
|
+
this._externalListenersBound = false;
|
|
487
|
+
}
|
|
488
|
+
this._externalPlayers = /* @__PURE__ */ new Map();
|
|
489
|
+
if (this._restoreSeekTimeout) {
|
|
490
|
+
clearTimeout(this._restoreSeekTimeout);
|
|
491
|
+
this._restoreSeekTimeout = null;
|
|
492
|
+
}
|
|
493
|
+
clearTimeout(this._shareFlashTimeout);
|
|
494
|
+
this._shareFlashTimeout = null;
|
|
495
|
+
this._loadSeq++;
|
|
413
496
|
if (this.barEl) {
|
|
414
497
|
this.barEl.remove();
|
|
415
498
|
this.barEl = null;
|
|
@@ -426,7 +509,22 @@
|
|
|
426
509
|
window.removeEventListener("beforeunload", this._beforeUnloadHandler);
|
|
427
510
|
this._beforeUnloadHandler = null;
|
|
428
511
|
}
|
|
429
|
-
|
|
512
|
+
this.volumePopupEl = null;
|
|
513
|
+
this.queueBtnEl = null;
|
|
514
|
+
this.titleEl = null;
|
|
515
|
+
this.artistEl = null;
|
|
516
|
+
this.metaEl = null;
|
|
517
|
+
this.playBtnEl = null;
|
|
518
|
+
this.repeatBtnEl = null;
|
|
519
|
+
this.waveformContainer = null;
|
|
520
|
+
this.queueBodyEl = null;
|
|
521
|
+
this.queueCountEl = null;
|
|
522
|
+
this.muteBtnEl = null;
|
|
523
|
+
this.volumeSliderEl = null;
|
|
524
|
+
this.favBtnEl = null;
|
|
525
|
+
this.cartBtnEl = null;
|
|
526
|
+
this.timeCurrentEl = null;
|
|
527
|
+
this.timeTotalEl = null;
|
|
430
528
|
document.querySelectorAll(".wb-current,.wb-playing").forEach((el) => el.classList.remove("wb-current", "wb-playing"));
|
|
431
529
|
this.queue = [];
|
|
432
530
|
this.currentIndex = -1;
|
|
@@ -445,6 +543,9 @@
|
|
|
445
543
|
const theme = this.config.theme || this._detectTheme();
|
|
446
544
|
if (theme === "light") this.barEl.classList.add("wb-light");
|
|
447
545
|
this._resolvedTheme = theme;
|
|
546
|
+
const maxWidth = this.config.maxWidth || (this.config.wide ? "100%" : null);
|
|
547
|
+
if (maxWidth) this.barEl.style.setProperty("--wb-max-width", maxWidth);
|
|
548
|
+
if (this.config.position === "top") this.barEl.classList.add("wb-top");
|
|
448
549
|
this.barEl.id = "waveform-bar";
|
|
449
550
|
this.barEl.innerHTML = buildBarHTML(this.config);
|
|
450
551
|
document.body.appendChild(this.barEl);
|
|
@@ -454,17 +555,24 @@
|
|
|
454
555
|
this.playBtnEl = this.barEl.querySelector(".wb-play");
|
|
455
556
|
this.waveformContainer = this.barEl.querySelector(".wb-waveform-container");
|
|
456
557
|
this.queueBtnEl = this.barEl.querySelector(".wb-queue-btn");
|
|
558
|
+
this.shareBtnEl = this.barEl.querySelector(".wb-share");
|
|
457
559
|
this.muteBtnEl = this.barEl.querySelector(".wb-mute");
|
|
458
560
|
this.volumeSliderEl = this.barEl.querySelector(".wb-volume-slider");
|
|
459
561
|
this.favBtnEl = this.barEl.querySelector(".wb-fav");
|
|
460
562
|
this.cartBtnEl = this.barEl.querySelector(".wb-cart");
|
|
461
563
|
this.timeCurrentEl = this.barEl.querySelector(".wb-time-current");
|
|
462
564
|
this.timeTotalEl = this.barEl.querySelector(".wb-time-total");
|
|
565
|
+
this.collapseBtnEl = this.barEl.querySelector(".wb-collapse");
|
|
463
566
|
this.playBtnEl.addEventListener("click", () => this.togglePlay());
|
|
567
|
+
if (this.collapseBtnEl) {
|
|
568
|
+
this.collapseBtnEl.addEventListener("click", () => this.toggleCollapse());
|
|
569
|
+
if (this._readCollapsed()) this.collapse();
|
|
570
|
+
}
|
|
464
571
|
const prevBtn = this.barEl.querySelector(".wb-prev");
|
|
465
572
|
const nextBtn = this.barEl.querySelector(".wb-next");
|
|
466
573
|
if (prevBtn) prevBtn.addEventListener("click", () => this.previous());
|
|
467
574
|
if (nextBtn) nextBtn.addEventListener("click", () => this.next());
|
|
575
|
+
if (this.shareBtnEl) this.shareBtnEl.addEventListener("click", () => this._share());
|
|
468
576
|
this.repeatBtnEl = this.barEl.querySelector(".wb-repeat");
|
|
469
577
|
if (this.repeatBtnEl) {
|
|
470
578
|
this.repeat = this.config.repeat || "off";
|
|
@@ -496,17 +604,18 @@
|
|
|
496
604
|
this.setVolume(parseInt(e.target.value) / 100);
|
|
497
605
|
});
|
|
498
606
|
}
|
|
499
|
-
|
|
500
|
-
if (this.volumePopupEl?.classList.contains("wb-volume-open") && !this.barEl
|
|
607
|
+
this._docClickVolume = (e) => {
|
|
608
|
+
if (this.volumePopupEl?.classList.contains("wb-volume-open") && !this.barEl?.querySelector(".wb-volume")?.contains(e.target)) {
|
|
501
609
|
this.closeVolumePopup();
|
|
502
610
|
}
|
|
503
|
-
}
|
|
611
|
+
};
|
|
612
|
+
document.addEventListener("click", this._docClickVolume);
|
|
504
613
|
if (this.favBtnEl) this.favBtnEl.addEventListener("click", () => this.toggleFavorite());
|
|
505
614
|
if (this.cartBtnEl) this.cartBtnEl.addEventListener("click", () => this.addToCart());
|
|
506
615
|
if (this.config.showTrackLink) {
|
|
507
616
|
this.barEl.querySelector(".wb-track").addEventListener("click", () => {
|
|
508
617
|
const t = this.getCurrentTrack();
|
|
509
|
-
if (t && t.link) window.location.href = t.link;
|
|
618
|
+
if (t && t.link && isSafeHref(t.link)) window.location.href = t.link;
|
|
510
619
|
});
|
|
511
620
|
}
|
|
512
621
|
}
|
|
@@ -518,20 +627,26 @@
|
|
|
518
627
|
this.queueBodyEl = this.queueEl.querySelector(".wb-queue-body");
|
|
519
628
|
this.queueCountEl = this.queueEl.querySelector(".wb-queue-count");
|
|
520
629
|
this.queueEl.querySelector(".wb-queue-clear").addEventListener("click", () => this.clearQueue());
|
|
521
|
-
|
|
522
|
-
if (this.queueOpen && !this.queueEl
|
|
630
|
+
this._docClickQueue = (e) => {
|
|
631
|
+
if (this.queueOpen && !this.queueEl?.contains(e.target) && !this.queueBtnEl?.contains(e.target)) {
|
|
523
632
|
this.closeQueuePanel();
|
|
524
633
|
}
|
|
525
|
-
}
|
|
634
|
+
};
|
|
635
|
+
document.addEventListener("click", this._docClickQueue);
|
|
526
636
|
}
|
|
527
637
|
_initPlayer() {
|
|
528
638
|
const opts = {
|
|
529
639
|
showControls: false,
|
|
530
640
|
showInfo: false,
|
|
531
|
-
|
|
641
|
+
// Classic mode reuses the player's own built-in 'seekbar' style —
|
|
642
|
+
// a simple rounded progress bar (no waveform), with the player's
|
|
643
|
+
// native click-to-seek. No custom seek-bar DOM needed.
|
|
644
|
+
waveformStyle: this.config.waveform === false ? "seekbar" : this.config.waveformStyle,
|
|
532
645
|
height: this.config.waveformHeight,
|
|
533
646
|
barWidth: this.config.barWidth,
|
|
534
647
|
barSpacing: this.config.barSpacing,
|
|
648
|
+
errorText: this.config.errorText,
|
|
649
|
+
// null -> player uses its own default
|
|
535
650
|
singlePlay: false,
|
|
536
651
|
onPlay: () => {
|
|
537
652
|
this.isPlaying = true;
|
|
@@ -574,6 +689,18 @@
|
|
|
574
689
|
this._loadCurrentTrack();
|
|
575
690
|
}
|
|
576
691
|
},
|
|
692
|
+
onError: () => {
|
|
693
|
+
this.isPlaying = false;
|
|
694
|
+
this._updatePlayButton();
|
|
695
|
+
this._syncPageState();
|
|
696
|
+
this._pumpExternalPlayState(false);
|
|
697
|
+
const track = this.getCurrentTrack();
|
|
698
|
+
this._emit("error", { track });
|
|
699
|
+
if (this.config.continuous && this.currentIndex < this.queue.length - 1) {
|
|
700
|
+
this.currentIndex++;
|
|
701
|
+
this._loadCurrentTrack();
|
|
702
|
+
}
|
|
703
|
+
},
|
|
577
704
|
onTimeUpdate: (currentTime, duration) => {
|
|
578
705
|
this._lastPosition = currentTime;
|
|
579
706
|
if (this.timeCurrentEl) this.timeCurrentEl.textContent = formatTime(currentTime);
|
|
@@ -587,7 +714,12 @@
|
|
|
587
714
|
this._checkMarkerBoundary(currentTime);
|
|
588
715
|
}
|
|
589
716
|
},
|
|
590
|
-
onLoad:
|
|
717
|
+
onLoad: () => {
|
|
718
|
+
if (this._shareSeek != null && this.player) {
|
|
719
|
+
this.player.seekTo(this._shareSeek);
|
|
720
|
+
this._shareSeek = null;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
591
723
|
};
|
|
592
724
|
if (this.config.waveformColor) opts.waveformColor = this.config.waveformColor;
|
|
593
725
|
if (this.config.progressColor) opts.progressColor = this.config.progressColor;
|
|
@@ -598,25 +730,24 @@
|
|
|
598
730
|
// Triggers (private)
|
|
599
731
|
// =====================================================================
|
|
600
732
|
_bindTriggers() {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
});
|
|
733
|
+
if (!this._docClickTriggers) {
|
|
734
|
+
this._docClickTriggers = (e) => {
|
|
735
|
+
const queueEl = e.target?.closest?.("[data-wb-queue]");
|
|
736
|
+
if (queueEl) {
|
|
737
|
+
e.preventDefault();
|
|
738
|
+
const track = parseTrackFromElement(queueEl);
|
|
739
|
+
if (track) this.addToQueue(track);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const playEl = e.target?.closest?.("[data-wb-play]");
|
|
743
|
+
if (playEl) {
|
|
744
|
+
e.preventDefault();
|
|
745
|
+
const track = parseTrackFromElement(playEl);
|
|
746
|
+
if (track) this.play(track);
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
document.addEventListener("click", this._docClickTriggers);
|
|
750
|
+
}
|
|
620
751
|
this._attachExternalPlayers();
|
|
621
752
|
}
|
|
622
753
|
/**
|
|
@@ -634,13 +765,13 @@
|
|
|
634
765
|
_attachExternalPlayers() {
|
|
635
766
|
if (!this._externalListenersBound) {
|
|
636
767
|
this._externalListenersBound = true;
|
|
637
|
-
|
|
768
|
+
this._onExtRequestPlay = (e) => {
|
|
638
769
|
const t = e.detail;
|
|
639
770
|
if (!t || !t.url) return;
|
|
640
771
|
e.preventDefault();
|
|
641
772
|
this.play(t);
|
|
642
|
-
}
|
|
643
|
-
|
|
773
|
+
};
|
|
774
|
+
this._onExtRequestPause = (e) => {
|
|
644
775
|
const t = e.detail;
|
|
645
776
|
if (!t || !t.url) return;
|
|
646
777
|
const current = this.getCurrentTrack();
|
|
@@ -648,8 +779,8 @@
|
|
|
648
779
|
e.preventDefault();
|
|
649
780
|
if (this.isPlaying) this.togglePlay();
|
|
650
781
|
}
|
|
651
|
-
}
|
|
652
|
-
|
|
782
|
+
};
|
|
783
|
+
this._onExtRequestSeek = (e) => {
|
|
653
784
|
const t = e.detail;
|
|
654
785
|
if (!t || !t.url || typeof t.percent !== "number") return;
|
|
655
786
|
const current = this.getCurrentTrack();
|
|
@@ -657,7 +788,16 @@
|
|
|
657
788
|
e.preventDefault();
|
|
658
789
|
this.player.seekToPercent(t.percent);
|
|
659
790
|
}
|
|
660
|
-
}
|
|
791
|
+
};
|
|
792
|
+
this._onExtDestroy = (e) => {
|
|
793
|
+
const inst = e.detail && e.detail.player;
|
|
794
|
+
if (!inst || !this._externalPlayers) return;
|
|
795
|
+
this._externalPlayers.forEach((set) => set.delete(inst));
|
|
796
|
+
};
|
|
797
|
+
document.addEventListener("waveformplayer:request-play", this._onExtRequestPlay);
|
|
798
|
+
document.addEventListener("waveformplayer:request-pause", this._onExtRequestPause);
|
|
799
|
+
document.addEventListener("waveformplayer:request-seek", this._onExtRequestSeek);
|
|
800
|
+
document.addEventListener("waveformplayer:destroy", this._onExtDestroy);
|
|
661
801
|
}
|
|
662
802
|
const previous = this._externalPlayers || /* @__PURE__ */ new Map();
|
|
663
803
|
this._externalPlayers = /* @__PURE__ */ new Map();
|
|
@@ -734,7 +874,7 @@
|
|
|
734
874
|
_observeDOM() {
|
|
735
875
|
if (typeof MutationObserver === "undefined") return;
|
|
736
876
|
this._observer = new MutationObserver(() => {
|
|
737
|
-
this.
|
|
877
|
+
this._attachExternalPlayers();
|
|
738
878
|
this._syncPageState();
|
|
739
879
|
});
|
|
740
880
|
this._observer.observe(document.body, { childList: true, subtree: true });
|
|
@@ -881,6 +1021,7 @@
|
|
|
881
1021
|
this.isMuted = true;
|
|
882
1022
|
if (this.player) this.player.setVolume(0);
|
|
883
1023
|
this._updateVolumeUI();
|
|
1024
|
+
saveVolume(this.config.storageKey, this.volume, this.isMuted, this._volumeBeforeMute);
|
|
884
1025
|
}
|
|
885
1026
|
return this;
|
|
886
1027
|
}
|
|
@@ -920,7 +1061,9 @@
|
|
|
920
1061
|
this._cartItems.add(id);
|
|
921
1062
|
if (this.cartBtnEl) {
|
|
922
1063
|
this.cartBtnEl.classList.add("wb-action-done");
|
|
923
|
-
setTimeout(() =>
|
|
1064
|
+
setTimeout(() => {
|
|
1065
|
+
if (this.cartBtnEl) this.cartBtnEl.classList.remove("wb-action-done");
|
|
1066
|
+
}, 1500);
|
|
924
1067
|
}
|
|
925
1068
|
this._syncCartAttributes(track.url, true);
|
|
926
1069
|
this._emit("cart", { track });
|
|
@@ -1082,9 +1225,252 @@
|
|
|
1082
1225
|
// =====================================================================
|
|
1083
1226
|
// Internal: Loading & Display
|
|
1084
1227
|
// =====================================================================
|
|
1228
|
+
/**
|
|
1229
|
+
* Parse a share link from the URL: the timestamp (`?<shareParam>=`, seconds)
|
|
1230
|
+
* plus the track identity needed to load it cold — `?wid` (id, preferred),
|
|
1231
|
+
* `?wu` (url, the works-anywhere fallback), and `?wtitle`/`?wartist` for
|
|
1232
|
+
* display before metadata arrives. Returns null when no share params are
|
|
1233
|
+
* present. An unsafe `?wu` (javascript:/data: etc.) is dropped, not loaded.
|
|
1234
|
+
* @returns {{time:number, id:string|null, url:string|null, title:string|null, artist:string|null}|null}
|
|
1235
|
+
* @private
|
|
1236
|
+
*/
|
|
1237
|
+
_readShareTarget() {
|
|
1238
|
+
let q;
|
|
1239
|
+
try {
|
|
1240
|
+
q = new URLSearchParams(window.location.search);
|
|
1241
|
+
} catch (e) {
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
const rawTime = q.get(this.config.shareParam);
|
|
1245
|
+
const id = q.get("wid");
|
|
1246
|
+
const rawUrl = q.get("wu");
|
|
1247
|
+
if (rawTime == null && id == null && rawUrl == null) return null;
|
|
1248
|
+
let time = 0;
|
|
1249
|
+
if (rawTime != null) {
|
|
1250
|
+
const t = Number(rawTime);
|
|
1251
|
+
if (Number.isFinite(t) && t >= 0) time = t;
|
|
1252
|
+
}
|
|
1253
|
+
const url = rawUrl && isSafeHref(rawUrl) ? rawUrl : null;
|
|
1254
|
+
return { time, id: id || null, url, title: q.get("wtitle"), artist: q.get("wartist") };
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Resolve a share target to a loadable track. Prefers an on-page trigger
|
|
1258
|
+
* (matched by `data-wb-id`, then by url) so the cold load inherits the
|
|
1259
|
+
* page's pre-generated peaks, markers, and favorite/cart state; falls back
|
|
1260
|
+
* to a minimal track built from the embedded url + title/artist so the link
|
|
1261
|
+
* still works on a page that doesn't contain the track.
|
|
1262
|
+
* @param {{id:string|null, url:string|null, title:string|null, artist:string|null}} target
|
|
1263
|
+
* @returns {Object|null}
|
|
1264
|
+
* @private
|
|
1265
|
+
*/
|
|
1266
|
+
_resolveSharedTrack(target) {
|
|
1267
|
+
const triggers = document.querySelectorAll("[data-wb-play], [data-wb-queue]");
|
|
1268
|
+
if (target.id) {
|
|
1269
|
+
for (const el of triggers) {
|
|
1270
|
+
if (el.dataset.wbId === target.id || el.dataset.id === target.id) {
|
|
1271
|
+
const t = parseTrackFromElement(el);
|
|
1272
|
+
if (t) return t;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
if (target.url) {
|
|
1277
|
+
for (const el of triggers) {
|
|
1278
|
+
if (el.dataset.wbUrl === target.url || el.dataset.url === target.url) {
|
|
1279
|
+
const t = parseTrackFromElement(el);
|
|
1280
|
+
if (t) return t;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
return {
|
|
1284
|
+
url: target.url,
|
|
1285
|
+
id: target.id || target.url,
|
|
1286
|
+
title: target.title || extractTitle(target.url),
|
|
1287
|
+
artist: target.artist || ""
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
return null;
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Cold-load a share-target track at a timestamp, paused. Mirrors the
|
|
1294
|
+
* restore path (loadTrack with autoplay:false + a `_loadSeq`-guarded,
|
|
1295
|
+
* delayed seek) so a later user action cleanly supersedes it.
|
|
1296
|
+
* @param {Object} track
|
|
1297
|
+
* @param {number} time - seconds to seek to once loaded
|
|
1298
|
+
* @private
|
|
1299
|
+
*/
|
|
1300
|
+
_loadSharedTrack(track, time) {
|
|
1301
|
+
if (!track || !track.url || !this.player) return;
|
|
1302
|
+
const existing = this.queue.findIndex((t) => t.url === track.url);
|
|
1303
|
+
if (existing >= 0) {
|
|
1304
|
+
this.queue[existing] = { ...this.queue[existing], ...track };
|
|
1305
|
+
this.currentIndex = existing;
|
|
1306
|
+
} else {
|
|
1307
|
+
this.queue.push(track);
|
|
1308
|
+
this.currentIndex = this.queue.length - 1;
|
|
1309
|
+
}
|
|
1310
|
+
this.show();
|
|
1311
|
+
this._updateTrackDisplay(track);
|
|
1312
|
+
this._updateFavoriteUI();
|
|
1313
|
+
this._updateNavButtons();
|
|
1314
|
+
const loadOpts = { autoplay: false };
|
|
1315
|
+
if (track.waveform) loadOpts.waveform = track.waveform;
|
|
1316
|
+
if (track.markers && track.markers.length) {
|
|
1317
|
+
const defaultColor = this.config.markerColor;
|
|
1318
|
+
loadOpts.markers = track.markers.map((m) => ({ ...m, color: m.color || defaultColor }));
|
|
1319
|
+
this._activeMarkers = track.markers;
|
|
1320
|
+
} else {
|
|
1321
|
+
loadOpts.markers = [];
|
|
1322
|
+
this._activeMarkers = null;
|
|
1323
|
+
}
|
|
1324
|
+
this._currentMarkerIndex = -1;
|
|
1325
|
+
const seq = ++this._loadSeq;
|
|
1326
|
+
if (this._restoreSeekTimeout) {
|
|
1327
|
+
clearTimeout(this._restoreSeekTimeout);
|
|
1328
|
+
this._restoreSeekTimeout = null;
|
|
1329
|
+
}
|
|
1330
|
+
this.player.loadTrack(track.url, track.title, track.artist, loadOpts).then(() => {
|
|
1331
|
+
if (this._loadSeq !== seq) return;
|
|
1332
|
+
if (this.player) this.player.setVolume(this.isMuted ? 0 : this.volume);
|
|
1333
|
+
if (time > 0) {
|
|
1334
|
+
this._restoreSeekTimeout = setTimeout(() => {
|
|
1335
|
+
this._restoreSeekTimeout = null;
|
|
1336
|
+
if (this._loadSeq !== seq) return;
|
|
1337
|
+
if (this.player) {
|
|
1338
|
+
this.player.seekTo(time);
|
|
1339
|
+
this._lastPosition = time;
|
|
1340
|
+
}
|
|
1341
|
+
}, 100);
|
|
1342
|
+
}
|
|
1343
|
+
}).catch(() => {
|
|
1344
|
+
});
|
|
1345
|
+
this._renderQueue();
|
|
1346
|
+
this._syncPageState();
|
|
1347
|
+
this._saveState();
|
|
1348
|
+
this._updateNavButtons();
|
|
1349
|
+
this._emit("trackchange", { track, index: this.currentIndex });
|
|
1350
|
+
if (this.config.onTrackChange) this.config.onTrackChange(track, this.currentIndex);
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Copy a shareable link to the current track at the current position, use
|
|
1354
|
+
* the native share sheet when available, and emit `waveformbar:share`. The
|
|
1355
|
+
* link carries both the timestamp AND the track identity so a cold open
|
|
1356
|
+
* loads the right audio: `?<shareParam>=<seconds>` plus `wid` (id, when the
|
|
1357
|
+
* track has a real one), `wu` (url — the works-anywhere fallback), and
|
|
1358
|
+
* `wtitle`/`wartist` for display before metadata loads.
|
|
1359
|
+
* @private
|
|
1360
|
+
*/
|
|
1361
|
+
_share() {
|
|
1362
|
+
const track = this.getCurrentTrack();
|
|
1363
|
+
const cur = this.player && this.player.audio ? this.player.audio.currentTime : 0;
|
|
1364
|
+
const seconds = Math.max(0, Math.floor(cur || 0));
|
|
1365
|
+
let link;
|
|
1366
|
+
try {
|
|
1367
|
+
const url = new URL(window.location.href);
|
|
1368
|
+
const p = url.searchParams;
|
|
1369
|
+
p.set(this.config.shareParam, String(seconds));
|
|
1370
|
+
if (track) {
|
|
1371
|
+
if (track.id && track.id !== track.url) p.set("wid", track.id);
|
|
1372
|
+
if (track.url) p.set("wu", track.url);
|
|
1373
|
+
if (track.title) p.set("wtitle", track.title);
|
|
1374
|
+
if (track.artist) p.set("wartist", track.artist);
|
|
1375
|
+
}
|
|
1376
|
+
link = url.toString();
|
|
1377
|
+
} catch (e) {
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
1381
|
+
navigator.clipboard.writeText(link).catch(() => {
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
if (navigator.share) {
|
|
1385
|
+
navigator.share({ title: track && track.title || void 0, url: link }).catch(() => {
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
this._flashShareCopied();
|
|
1389
|
+
this._emit("share", { url: link, time: seconds, track });
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Briefly flag the share button as "copied" for visual feedback.
|
|
1393
|
+
* @private
|
|
1394
|
+
*/
|
|
1395
|
+
_flashShareCopied() {
|
|
1396
|
+
if (!this.shareBtnEl) return;
|
|
1397
|
+
this.shareBtnEl.classList.add("wb-copied");
|
|
1398
|
+
this.shareBtnEl.setAttribute("title", "Link copied!");
|
|
1399
|
+
clearTimeout(this._shareFlashTimeout);
|
|
1400
|
+
this._shareFlashTimeout = setTimeout(() => {
|
|
1401
|
+
if (this.shareBtnEl) {
|
|
1402
|
+
this.shareBtnEl.classList.remove("wb-copied");
|
|
1403
|
+
this.shareBtnEl.setAttribute("title", "Copy share link");
|
|
1404
|
+
}
|
|
1405
|
+
}, 1500);
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Collapse the bar to a small floating pill (artwork + play + expand).
|
|
1409
|
+
* @returns {WaveformBar}
|
|
1410
|
+
*/
|
|
1411
|
+
collapse() {
|
|
1412
|
+
this.isCollapsed = true;
|
|
1413
|
+
if (this.barEl) this.barEl.classList.add("wb-collapsed");
|
|
1414
|
+
this._updateCollapseButton();
|
|
1415
|
+
this._saveCollapsed();
|
|
1416
|
+
this._emit("collapse", { collapsed: true });
|
|
1417
|
+
return this;
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Restore the bar from its collapsed pill back to the full bar.
|
|
1421
|
+
* @returns {WaveformBar}
|
|
1422
|
+
*/
|
|
1423
|
+
expand() {
|
|
1424
|
+
this.isCollapsed = false;
|
|
1425
|
+
if (this.barEl) this.barEl.classList.remove("wb-collapsed");
|
|
1426
|
+
this._updateCollapseButton();
|
|
1427
|
+
this._saveCollapsed();
|
|
1428
|
+
this._emit("collapse", { collapsed: false });
|
|
1429
|
+
return this;
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Toggle the collapsed pill state.
|
|
1433
|
+
* @returns {WaveformBar}
|
|
1434
|
+
*/
|
|
1435
|
+
toggleCollapse() {
|
|
1436
|
+
return this.isCollapsed ? this.expand() : this.collapse();
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Swap the collapse button's icon + labels for the current state.
|
|
1440
|
+
* @private
|
|
1441
|
+
*/
|
|
1442
|
+
_updateCollapseButton() {
|
|
1443
|
+
if (!this.collapseBtnEl) return;
|
|
1444
|
+
this.collapseBtnEl.innerHTML = this.isCollapsed ? ICONS.expand : ICONS.collapse;
|
|
1445
|
+
const label = this.isCollapsed ? "Expand" : "Collapse";
|
|
1446
|
+
this.collapseBtnEl.setAttribute("aria-label", label);
|
|
1447
|
+
this.collapseBtnEl.setAttribute("title", label);
|
|
1448
|
+
}
|
|
1449
|
+
/** Persist the collapsed state (session-scoped) when persistence is on. @private */
|
|
1450
|
+
_saveCollapsed() {
|
|
1451
|
+
if (!this.config.persist) return;
|
|
1452
|
+
try {
|
|
1453
|
+
sessionStorage.setItem(this.config.storageKey + "-collapsed", this.isCollapsed ? "1" : "0");
|
|
1454
|
+
} catch (e) {
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
/** Read the persisted collapsed state. @returns {boolean} @private */
|
|
1458
|
+
_readCollapsed() {
|
|
1459
|
+
if (!this.config.persist) return false;
|
|
1460
|
+
try {
|
|
1461
|
+
return sessionStorage.getItem(this.config.storageKey + "-collapsed") === "1";
|
|
1462
|
+
} catch (e) {
|
|
1463
|
+
return false;
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1085
1466
|
_loadCurrentTrack() {
|
|
1086
1467
|
const track = this.getCurrentTrack();
|
|
1087
1468
|
if (!track || !this.player) return;
|
|
1469
|
+
this._loadSeq++;
|
|
1470
|
+
if (this._restoreSeekTimeout) {
|
|
1471
|
+
clearTimeout(this._restoreSeekTimeout);
|
|
1472
|
+
this._restoreSeekTimeout = null;
|
|
1473
|
+
}
|
|
1088
1474
|
this._pumpExternalPlayState(false);
|
|
1089
1475
|
this.show();
|
|
1090
1476
|
this._updateTrackDisplay(track);
|
|
@@ -1249,7 +1635,7 @@
|
|
|
1249
1635
|
}
|
|
1250
1636
|
if (marker.artwork) {
|
|
1251
1637
|
const artworkEl = this.barEl.querySelector(".wb-artwork");
|
|
1252
|
-
if (artworkEl) artworkEl.innerHTML = `<img src="${marker.artwork}" alt="${marker.title || ""}" />`;
|
|
1638
|
+
if (artworkEl) artworkEl.innerHTML = `<img src="${escapeHtml(marker.artwork)}" alt="${escapeHtml(marker.title || "")}" />`;
|
|
1253
1639
|
}
|
|
1254
1640
|
if (this.metaEl && (marker.bpm || marker.key)) {
|
|
1255
1641
|
const metaTrack = {
|
|
@@ -1430,24 +1816,27 @@
|
|
|
1430
1816
|
this._updateTrackDisplay(track);
|
|
1431
1817
|
this._updateFavoriteUI();
|
|
1432
1818
|
this._updateNavButtons();
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
}
|
|
1436
|
-
this.player.options.title = track.title || "";
|
|
1437
|
-
this.player.options.subtitle = track.artist || "";
|
|
1819
|
+
const loadOpts = { autoplay: false };
|
|
1820
|
+
if (track.waveform) loadOpts.waveform = track.waveform;
|
|
1438
1821
|
if (track.markers && track.markers.length) {
|
|
1439
1822
|
const defaultColor = this.config.markerColor;
|
|
1440
|
-
|
|
1823
|
+
loadOpts.markers = track.markers.map((m) => ({
|
|
1441
1824
|
...m,
|
|
1442
1825
|
color: m.color || defaultColor
|
|
1443
1826
|
}));
|
|
1444
1827
|
this._activeMarkers = track.markers;
|
|
1445
1828
|
} else {
|
|
1446
|
-
|
|
1829
|
+
loadOpts.markers = [];
|
|
1447
1830
|
this._activeMarkers = null;
|
|
1448
1831
|
}
|
|
1449
1832
|
this._currentMarkerIndex = -1;
|
|
1450
|
-
this.
|
|
1833
|
+
const seq = ++this._loadSeq;
|
|
1834
|
+
if (this._restoreSeekTimeout) {
|
|
1835
|
+
clearTimeout(this._restoreSeekTimeout);
|
|
1836
|
+
this._restoreSeekTimeout = null;
|
|
1837
|
+
}
|
|
1838
|
+
this.player.loadTrack(track.url, track.title, track.artist, loadOpts).then(() => {
|
|
1839
|
+
if (this._loadSeq !== seq) return;
|
|
1451
1840
|
if (this.player) this.player.setVolume(this.isMuted ? 0 : this.volume);
|
|
1452
1841
|
if (state.isPlaying && this.config.autoResume) {
|
|
1453
1842
|
try {
|
|
@@ -1466,7 +1855,9 @@
|
|
|
1466
1855
|
}
|
|
1467
1856
|
}
|
|
1468
1857
|
if (state.position > 0) {
|
|
1469
|
-
setTimeout(() => {
|
|
1858
|
+
this._restoreSeekTimeout = setTimeout(() => {
|
|
1859
|
+
this._restoreSeekTimeout = null;
|
|
1860
|
+
if (this._loadSeq !== seq) return;
|
|
1470
1861
|
if (this.player) {
|
|
1471
1862
|
this.player.seekTo(state.position);
|
|
1472
1863
|
this._lastPosition = state.position;
|
|
@@ -1481,7 +1872,8 @@
|
|
|
1481
1872
|
_restoreVolume() {
|
|
1482
1873
|
const data = restoreVolume(this.config.storageKey);
|
|
1483
1874
|
if (!data) return;
|
|
1484
|
-
|
|
1875
|
+
const v = Number(data.volume);
|
|
1876
|
+
this.volume = Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 1;
|
|
1485
1877
|
this.isMuted = data.muted;
|
|
1486
1878
|
this._volumeBeforeMute = data.volumeBeforeMute;
|
|
1487
1879
|
if (this.player) this.player.setVolume(this.isMuted ? 0 : this.volume);
|