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