@sweet-player/core 0.2.1 → 0.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/README.md +29 -7
- package/dist/index.cjs +524 -144
- package/dist/index.d.cts +25 -4
- package/dist/index.d.ts +25 -4
- package/dist/index.js +523 -144
- package/dist/sweet-player.global.js +291 -28
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25,6 +25,25 @@ var EventEmitter = class {
|
|
|
25
25
|
|
|
26
26
|
// src/core/media.ts
|
|
27
27
|
import Hls from "hls.js";
|
|
28
|
+
|
|
29
|
+
// src/logger.ts
|
|
30
|
+
var MAX_ENTRIES = 2e3;
|
|
31
|
+
var entries = [];
|
|
32
|
+
function pad2(n) {
|
|
33
|
+
return n < 10 ? `0${n}` : String(n);
|
|
34
|
+
}
|
|
35
|
+
function formatTs(d) {
|
|
36
|
+
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
|
|
37
|
+
}
|
|
38
|
+
function log(category, message) {
|
|
39
|
+
if (entries.length >= MAX_ENTRIES) entries.shift();
|
|
40
|
+
entries.push({ time: /* @__PURE__ */ new Date(), category, message });
|
|
41
|
+
}
|
|
42
|
+
function getLogText() {
|
|
43
|
+
return entries.map((e) => `[${formatTs(e.time)}] [${e.category}]\uFF1A${e.message}`).join("\n");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/core/media.ts
|
|
28
47
|
function isHlsSource(src) {
|
|
29
48
|
return /\.m3u8($|\?)/i.test(src);
|
|
30
49
|
}
|
|
@@ -41,10 +60,15 @@ var MediaController = class {
|
|
|
41
60
|
load(src) {
|
|
42
61
|
this.detachHls();
|
|
43
62
|
this.currentSrc = src;
|
|
63
|
+
log("\u64AD\u653E\u5668", `\u52A0\u8F7D\u6E90: ${src}`);
|
|
44
64
|
if (isHlsSource(src)) {
|
|
45
65
|
if (Hls.isSupported()) {
|
|
46
66
|
this.hls = new Hls(this.hlsConfig);
|
|
67
|
+
this.hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
|
68
|
+
log("hls\u4E8B\u4EF6", "\u5A92\u4F53\u6210\u529F\u9644\u52A0\u5230\u64AD\u653E\u5668");
|
|
69
|
+
});
|
|
47
70
|
this.hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
71
|
+
log("hls\u4E8B\u4EF6", `${data.fatal ? "\u81F4\u547D" : ""}\u9519\u8BEF: ${data.type} - ${data.details}`);
|
|
48
72
|
if (data.fatal) {
|
|
49
73
|
switch (data.type) {
|
|
50
74
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
|
@@ -60,8 +84,17 @@ var MediaController = class {
|
|
|
60
84
|
}
|
|
61
85
|
});
|
|
62
86
|
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
|
87
|
+
log("hls\u4E8B\u4EF6", "\u64AD\u653E\u6E05\u5355\u89E3\u6790\u5B8C\u6BD5");
|
|
63
88
|
this.notifyTracks();
|
|
64
89
|
});
|
|
90
|
+
this.hls.on(Hls.Events.BUFFER_CREATED, () => {
|
|
91
|
+
log("hls\u4E8B\u4EF6", "\u7F13\u51B2\u533A\u521B\u5EFA");
|
|
92
|
+
});
|
|
93
|
+
this.hls.on(Hls.Events.LEVEL_SWITCHED, (_event, data) => {
|
|
94
|
+
const level = this.hls?.levels[data.level];
|
|
95
|
+
const label = level?.height ? `${level.height}P` : `Level ${data.level}`;
|
|
96
|
+
log("hls\u4E8B\u4EF6", `\u753B\u8D28\u6863\u4F4D\u5207\u6362: ${label}`);
|
|
97
|
+
});
|
|
65
98
|
this.hls.loadSource(src);
|
|
66
99
|
this.hls.attachMedia(this.video);
|
|
67
100
|
return;
|
|
@@ -424,69 +457,14 @@ var icons = {
|
|
|
424
457
|
),
|
|
425
458
|
audio: createSvgIcon(
|
|
426
459
|
"M12 3a9 9 0 0 0-9 9v7c0 1.1.9 2 2 2h4v-8H5v-1c0-3.87 3.13-7 7-7s7 3.13 7 7v1h-4v8h4c1.1 0 2-.9 2-2v-7a9 9 0 0 0-9-9z"
|
|
427
|
-
)
|
|
460
|
+
),
|
|
461
|
+
settings: createSvgIcon(
|
|
462
|
+
"M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65a.5.5 0 0 0 .12-.64l-2-3.46a.5.5 0 0 0-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65A.49.49 0 0 0 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1a.49.49 0 0 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65a.5.5 0 0 0-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46a.5.5 0 0 0-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"
|
|
463
|
+
),
|
|
464
|
+
chevronRight: createSvgIcon("M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"),
|
|
465
|
+
chevronLeft: createSvgIcon("M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z")
|
|
428
466
|
};
|
|
429
467
|
|
|
430
|
-
// src/ui/components/menu.ts
|
|
431
|
-
function createPopupMenu(opts) {
|
|
432
|
-
const root = createEl("div", { className: "sp-menu" });
|
|
433
|
-
const btn = createEl("button", {
|
|
434
|
-
className: "sp-btn sp-menu-btn",
|
|
435
|
-
html: opts.buttonHtml,
|
|
436
|
-
attrs: { type: "button", title: opts.title, "aria-label": opts.title },
|
|
437
|
-
parent: root
|
|
438
|
-
});
|
|
439
|
-
const panel = createEl("div", { className: "sp-menu-panel", parent: root });
|
|
440
|
-
let items = [];
|
|
441
|
-
let activeValue;
|
|
442
|
-
function render() {
|
|
443
|
-
panel.innerHTML = "";
|
|
444
|
-
if (items.length === 0) {
|
|
445
|
-
createEl("div", { className: "sp-menu-empty", text: opts.emptyText ?? "-", parent: panel });
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
for (const item of items) {
|
|
449
|
-
const itemBtn = createEl("button", {
|
|
450
|
-
className: "sp-menu-item" + (item.value === activeValue ? " sp-active" : ""),
|
|
451
|
-
text: item.label,
|
|
452
|
-
attrs: { type: "button" },
|
|
453
|
-
parent: panel
|
|
454
|
-
});
|
|
455
|
-
itemBtn.addEventListener("click", (e) => {
|
|
456
|
-
e.stopPropagation();
|
|
457
|
-
close();
|
|
458
|
-
opts.onSelect(item);
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
function close() {
|
|
463
|
-
root.classList.remove("sp-open");
|
|
464
|
-
}
|
|
465
|
-
btn.addEventListener("click", (e) => {
|
|
466
|
-
e.stopPropagation();
|
|
467
|
-
const willOpen = !root.classList.contains("sp-open");
|
|
468
|
-
root.closest(".sp-controls")?.querySelectorAll(".sp-menu.sp-open").forEach((m) => m.classList.remove("sp-open"));
|
|
469
|
-
root.classList.toggle("sp-open", willOpen);
|
|
470
|
-
});
|
|
471
|
-
document.addEventListener("click", close);
|
|
472
|
-
render();
|
|
473
|
-
return {
|
|
474
|
-
el: root,
|
|
475
|
-
setItems(next) {
|
|
476
|
-
items = next;
|
|
477
|
-
render();
|
|
478
|
-
},
|
|
479
|
-
setActive(value) {
|
|
480
|
-
activeValue = value;
|
|
481
|
-
render();
|
|
482
|
-
},
|
|
483
|
-
setButtonContent(html) {
|
|
484
|
-
btn.innerHTML = html;
|
|
485
|
-
},
|
|
486
|
-
close
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
|
|
490
468
|
// src/ui/components/progressBar.ts
|
|
491
469
|
function createProgressBar(video, onSeek) {
|
|
492
470
|
const root = createEl("div", { className: "sp-progress" });
|
|
@@ -594,87 +572,299 @@ function createVolumeControl(opts) {
|
|
|
594
572
|
};
|
|
595
573
|
}
|
|
596
574
|
|
|
575
|
+
// src/ui/components/settingsPanel.ts
|
|
576
|
+
function createSettingsPanel(opts) {
|
|
577
|
+
const root = createEl("div", { className: "sp-menu sp-settings" });
|
|
578
|
+
const btn = createEl("button", {
|
|
579
|
+
className: "sp-btn",
|
|
580
|
+
html: icons.settings,
|
|
581
|
+
attrs: { type: "button", title: opts.buttonTitle, "aria-label": opts.buttonTitle },
|
|
582
|
+
parent: root
|
|
583
|
+
});
|
|
584
|
+
const panel = createEl("div", { className: "sp-settings-panel", parent: root });
|
|
585
|
+
const mainView = createEl("div", { className: "sp-settings-main", parent: panel });
|
|
586
|
+
const subView = createEl("div", { className: "sp-settings-sub", parent: panel });
|
|
587
|
+
let sections = opts.sections;
|
|
588
|
+
let activeSub = null;
|
|
589
|
+
function renderMain() {
|
|
590
|
+
mainView.innerHTML = "";
|
|
591
|
+
for (const sec of sections) {
|
|
592
|
+
if (sec.toggle) {
|
|
593
|
+
const row2 = createEl("div", {
|
|
594
|
+
className: "sp-settings-row sp-settings-toggle-row",
|
|
595
|
+
attrs: { "data-key": sec.key },
|
|
596
|
+
parent: mainView
|
|
597
|
+
});
|
|
598
|
+
createEl("span", { className: "sp-settings-label", text: sec.label, parent: row2 });
|
|
599
|
+
const track = createEl("div", {
|
|
600
|
+
className: "sp-toggle" + (sec.toggle.checked ? " sp-on" : ""),
|
|
601
|
+
attrs: { role: "switch", "aria-checked": String(sec.toggle.checked), tabindex: "0" },
|
|
602
|
+
parent: row2
|
|
603
|
+
});
|
|
604
|
+
createEl("span", { className: "sp-toggle-thumb", parent: track });
|
|
605
|
+
const handler = (e) => {
|
|
606
|
+
e.stopPropagation();
|
|
607
|
+
sec.toggle.onToggle();
|
|
608
|
+
};
|
|
609
|
+
row2.addEventListener("click", handler);
|
|
610
|
+
track.addEventListener("keydown", (e) => {
|
|
611
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
612
|
+
e.preventDefault();
|
|
613
|
+
handler(e);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
if (sec.items.length === 0) continue;
|
|
619
|
+
const row = createEl("button", {
|
|
620
|
+
className: "sp-settings-row",
|
|
621
|
+
attrs: { type: "button", "data-key": sec.key },
|
|
622
|
+
parent: mainView
|
|
623
|
+
});
|
|
624
|
+
createEl("span", { className: "sp-settings-label", text: sec.label, parent: row });
|
|
625
|
+
createEl("span", { className: "sp-settings-value", text: sec.currentValue, parent: row });
|
|
626
|
+
const arrow = createEl("span", { className: "sp-settings-arrow", parent: row });
|
|
627
|
+
arrow.innerHTML = icons.chevronRight;
|
|
628
|
+
row.addEventListener("click", (e) => {
|
|
629
|
+
e.stopPropagation();
|
|
630
|
+
showSub(sec.key);
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
function renderSub(key) {
|
|
635
|
+
subView.innerHTML = "";
|
|
636
|
+
const sec = sections.find((s) => s.key === key);
|
|
637
|
+
if (!sec) return;
|
|
638
|
+
const back = createEl("button", {
|
|
639
|
+
className: "sp-settings-back",
|
|
640
|
+
attrs: { type: "button" },
|
|
641
|
+
parent: subView
|
|
642
|
+
});
|
|
643
|
+
const backArrow = createEl("span", { className: "sp-settings-back-arrow", parent: back });
|
|
644
|
+
backArrow.innerHTML = icons.chevronLeft;
|
|
645
|
+
createEl("span", { text: sec.label, parent: back });
|
|
646
|
+
back.addEventListener("click", (e) => {
|
|
647
|
+
e.stopPropagation();
|
|
648
|
+
showMain();
|
|
649
|
+
});
|
|
650
|
+
for (const item of sec.items) {
|
|
651
|
+
const itemBtn = createEl("button", {
|
|
652
|
+
className: "sp-menu-item" + (item.value === sec.activeValue ? " sp-active" : ""),
|
|
653
|
+
text: item.label,
|
|
654
|
+
attrs: { type: "button" },
|
|
655
|
+
parent: subView
|
|
656
|
+
});
|
|
657
|
+
itemBtn.addEventListener("click", (e) => {
|
|
658
|
+
e.stopPropagation();
|
|
659
|
+
close();
|
|
660
|
+
sec.onSelect(item);
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function showSub(key) {
|
|
665
|
+
activeSub = key;
|
|
666
|
+
renderSub(key);
|
|
667
|
+
mainView.style.display = "none";
|
|
668
|
+
subView.style.display = "";
|
|
669
|
+
}
|
|
670
|
+
function showMain() {
|
|
671
|
+
activeSub = null;
|
|
672
|
+
mainView.style.display = "";
|
|
673
|
+
subView.style.display = "none";
|
|
674
|
+
}
|
|
675
|
+
function close() {
|
|
676
|
+
root.classList.remove("sp-open");
|
|
677
|
+
showMain();
|
|
678
|
+
}
|
|
679
|
+
btn.addEventListener("click", (e) => {
|
|
680
|
+
e.stopPropagation();
|
|
681
|
+
const willOpen = !root.classList.contains("sp-open");
|
|
682
|
+
root.closest(".sp-controls")?.querySelectorAll(".sp-menu.sp-open").forEach((m) => m.classList.remove("sp-open"));
|
|
683
|
+
if (willOpen) {
|
|
684
|
+
renderMain();
|
|
685
|
+
showMain();
|
|
686
|
+
}
|
|
687
|
+
root.classList.toggle("sp-open", willOpen);
|
|
688
|
+
});
|
|
689
|
+
const onDocClick = () => close();
|
|
690
|
+
document.addEventListener("click", onDocClick);
|
|
691
|
+
panel.addEventListener("click", (e) => e.stopPropagation());
|
|
692
|
+
renderMain();
|
|
693
|
+
showMain();
|
|
694
|
+
return {
|
|
695
|
+
el: root,
|
|
696
|
+
updateSection(key, patch) {
|
|
697
|
+
const sec = sections.find((s) => s.key === key);
|
|
698
|
+
if (!sec) return;
|
|
699
|
+
if (patch.items !== void 0) sec.items = patch.items;
|
|
700
|
+
if (patch.currentValue !== void 0) sec.currentValue = patch.currentValue;
|
|
701
|
+
if (patch.activeValue !== void 0) sec.activeValue = patch.activeValue;
|
|
702
|
+
if (patch.toggle) {
|
|
703
|
+
if (sec.toggle) {
|
|
704
|
+
sec.toggle.checked = patch.toggle.checked;
|
|
705
|
+
if (patch.toggle.onToggle) sec.toggle.onToggle = patch.toggle.onToggle;
|
|
706
|
+
} else {
|
|
707
|
+
sec.toggle = { checked: patch.toggle.checked, onToggle: patch.toggle.onToggle ?? (() => {
|
|
708
|
+
}) };
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (sec.toggle) {
|
|
712
|
+
if (!activeSub) renderMain();
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (activeSub === key) renderSub(key);
|
|
716
|
+
if (!activeSub) renderMain();
|
|
717
|
+
},
|
|
718
|
+
close,
|
|
719
|
+
destroy() {
|
|
720
|
+
document.removeEventListener("click", onDocClick);
|
|
721
|
+
root.remove();
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
597
726
|
// src/ui/controls.ts
|
|
598
727
|
function createControls(ctx) {
|
|
599
|
-
const { video, actions, i18n } = ctx;
|
|
728
|
+
const { video, actions, i18n, hidden } = ctx;
|
|
729
|
+
const show = (name) => !hidden.has(name);
|
|
600
730
|
const topEl = createEl("div", { className: "sp-top" });
|
|
601
|
-
|
|
602
|
-
titleEl.addEventListener("click", actions.onTitleClick);
|
|
731
|
+
if (show("title")) createEl("div", { className: "sp-title", text: ctx.title, parent: topEl });
|
|
603
732
|
const bottomEl = createEl("div", { className: "sp-bottom" });
|
|
604
733
|
const progress = createProgressBar(video, (t) => {
|
|
605
734
|
video.currentTime = t;
|
|
606
735
|
});
|
|
607
|
-
bottomEl.appendChild(progress.el);
|
|
736
|
+
if (show("progress")) bottomEl.appendChild(progress.el);
|
|
608
737
|
const row = createEl("div", { className: "sp-controls", parent: bottomEl });
|
|
609
|
-
const button = (html, title, onClick, disabled = false) => {
|
|
738
|
+
const button = (html, title, onClick, disabled = false, visible = true) => {
|
|
610
739
|
const btn = createEl("button", {
|
|
611
740
|
className: "sp-btn",
|
|
612
741
|
html,
|
|
613
742
|
attrs: { type: "button", title, "aria-label": title },
|
|
614
|
-
parent: row
|
|
743
|
+
parent: visible ? row : void 0
|
|
615
744
|
});
|
|
616
745
|
btn.disabled = disabled;
|
|
617
746
|
btn.addEventListener("click", onClick);
|
|
618
747
|
return btn;
|
|
619
748
|
};
|
|
620
|
-
button(icons.prev, i18n.t("prev"), () => actions.onPrev?.(), !actions.onPrev);
|
|
621
|
-
button(icons.seekBack, i18n.t("seekBack", { n: ctx.seekStep }), () => actions.seekBy(-ctx.seekStep));
|
|
622
|
-
const playBtn = button(icons.play, i18n.t("playPause"), actions.togglePlay);
|
|
623
|
-
button(icons.seekForward, i18n.t("seekForward", { n: ctx.seekStep }), () => actions.seekBy(ctx.seekStep));
|
|
624
|
-
button(icons.next, i18n.t("next"), () => actions.onNext?.(), !actions.onNext);
|
|
625
|
-
const timeEl = createEl("span", {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
title: i18n.t("speed"),
|
|
630
|
-
emptyText: i18n.t("empty"),
|
|
631
|
-
onSelect: (item) => actions.setRate(item.value)
|
|
749
|
+
button(icons.prev, i18n.t("prev"), () => actions.onPrev?.(), !actions.onPrev, show("prev"));
|
|
750
|
+
button(icons.seekBack, i18n.t("seekBack", { n: ctx.seekStep }), () => actions.seekBy(-ctx.seekStep), false, show("seekBack"));
|
|
751
|
+
const playBtn = button(icons.play, i18n.t("playPause"), actions.togglePlay, false, show("play"));
|
|
752
|
+
button(icons.seekForward, i18n.t("seekForward", { n: ctx.seekStep }), () => actions.seekBy(ctx.seekStep), false, show("seekForward"));
|
|
753
|
+
button(icons.next, i18n.t("next"), () => actions.onNext?.(), !actions.onNext, show("next"));
|
|
754
|
+
const timeEl = createEl("span", {
|
|
755
|
+
className: "sp-time",
|
|
756
|
+
text: "0:00 / 0:00",
|
|
757
|
+
parent: show("time") ? row : void 0
|
|
632
758
|
});
|
|
633
|
-
|
|
634
|
-
rateMenu.setActive(video.playbackRate);
|
|
635
|
-
row.appendChild(rateMenu.el);
|
|
636
|
-
const qualityMenu = createPopupMenu({
|
|
637
|
-
buttonHtml: i18n.t("quality"),
|
|
638
|
-
title: i18n.t("quality"),
|
|
639
|
-
emptyText: i18n.t("empty"),
|
|
640
|
-
onSelect: (item) => actions.selectQuality(item.value)
|
|
641
|
-
});
|
|
642
|
-
row.appendChild(qualityMenu.el);
|
|
643
|
-
const ratioMenu = createPopupMenu({
|
|
644
|
-
buttonHtml: i18n.t("aspectRatio"),
|
|
645
|
-
title: i18n.t("aspectRatio"),
|
|
646
|
-
emptyText: i18n.t("empty"),
|
|
647
|
-
onSelect: (item) => actions.setAspectRatio(item.value)
|
|
648
|
-
});
|
|
649
|
-
ratioMenu.setItems(
|
|
650
|
-
ctx.aspectRatios.map((r) => ({ label: r === "original" ? i18n.t("ratioOriginal") : r, value: r }))
|
|
651
|
-
);
|
|
652
|
-
ratioMenu.setActive("original");
|
|
653
|
-
row.appendChild(ratioMenu.el);
|
|
654
|
-
const audioMenu = createPopupMenu({
|
|
655
|
-
buttonHtml: icons.audio,
|
|
656
|
-
title: i18n.t("audioTrack"),
|
|
657
|
-
emptyText: i18n.t("empty"),
|
|
658
|
-
onSelect: (item) => actions.selectAudioTrack(item.value)
|
|
659
|
-
});
|
|
660
|
-
row.appendChild(audioMenu.el);
|
|
759
|
+
createEl("div", { className: "sp-controls-spacer", parent: row });
|
|
661
760
|
const volume = createVolumeControl({
|
|
662
761
|
muteTitle: i18n.t("mute"),
|
|
663
762
|
onVolumeChange: actions.setVolume,
|
|
664
763
|
onToggleMute: actions.toggleMute
|
|
665
764
|
});
|
|
666
|
-
row.appendChild(volume.el);
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
765
|
+
if (show("volume")) row.appendChild(volume.el);
|
|
766
|
+
let currentRate = video.playbackRate;
|
|
767
|
+
let qualityItems = [];
|
|
768
|
+
let activeQuality;
|
|
769
|
+
let audioItems = [];
|
|
770
|
+
let activeAudio;
|
|
771
|
+
const settingsPanel = createSettingsPanel({
|
|
772
|
+
buttonTitle: i18n.t("settings"),
|
|
773
|
+
sections: [
|
|
774
|
+
...show("rate") ? [{
|
|
775
|
+
key: "rate",
|
|
776
|
+
label: i18n.t("speed"),
|
|
777
|
+
currentValue: `${currentRate}x`,
|
|
778
|
+
items: ctx.playbackRates.map((r) => ({ label: `${r}x`, value: r })),
|
|
779
|
+
activeValue: currentRate,
|
|
780
|
+
onSelect: (item) => actions.setRate(item.value)
|
|
781
|
+
}] : [],
|
|
782
|
+
...show("quality") ? [{
|
|
783
|
+
key: "quality",
|
|
784
|
+
label: i18n.t("quality"),
|
|
785
|
+
currentValue: i18n.t("qualityAuto"),
|
|
786
|
+
items: qualityItems,
|
|
787
|
+
activeValue: activeQuality,
|
|
788
|
+
onSelect: (item) => actions.selectQuality(item.value)
|
|
789
|
+
}] : [],
|
|
790
|
+
...show("ratio") ? [{
|
|
791
|
+
key: "ratio",
|
|
792
|
+
label: i18n.t("aspectRatio"),
|
|
793
|
+
currentValue: i18n.t("ratioOriginal"),
|
|
794
|
+
items: ctx.aspectRatios.map((r) => ({
|
|
795
|
+
label: r === "original" ? i18n.t("ratioOriginal") : r,
|
|
796
|
+
value: r
|
|
797
|
+
})),
|
|
798
|
+
activeValue: "original",
|
|
799
|
+
onSelect: (item) => actions.setAspectRatio(item.value)
|
|
800
|
+
}] : [],
|
|
801
|
+
...show("audioTrack") ? [{
|
|
802
|
+
key: "audioTrack",
|
|
803
|
+
label: i18n.t("audioTrack"),
|
|
804
|
+
currentValue: "-",
|
|
805
|
+
items: audioItems,
|
|
806
|
+
activeValue: activeAudio,
|
|
807
|
+
onSelect: (item) => actions.selectAudioTrack(item.value)
|
|
808
|
+
}] : [],
|
|
809
|
+
...show("pip") && document.pictureInPictureEnabled ? [{
|
|
810
|
+
key: "pip",
|
|
811
|
+
label: i18n.t("pip"),
|
|
812
|
+
currentValue: "",
|
|
813
|
+
items: [],
|
|
814
|
+
activeValue: void 0,
|
|
815
|
+
onSelect: () => {
|
|
816
|
+
},
|
|
817
|
+
toggle: {
|
|
818
|
+
checked: false,
|
|
819
|
+
onToggle: () => actions.togglePip()
|
|
820
|
+
}
|
|
821
|
+
}] : []
|
|
822
|
+
]
|
|
823
|
+
});
|
|
824
|
+
if (show("settings")) row.appendChild(settingsPanel.el);
|
|
825
|
+
const fsBtn = button(icons.fullscreen, i18n.t("fullscreen"), actions.toggleFullscreen, false, show("fullscreen"));
|
|
826
|
+
const qualityMenuAdapter = {
|
|
827
|
+
el: document.createElement("div"),
|
|
828
|
+
setItems(items) {
|
|
829
|
+
qualityItems = items;
|
|
830
|
+
const cur = activeQuality ? items.find((i) => i.value === activeQuality)?.label : i18n.t("qualityAuto");
|
|
831
|
+
settingsPanel.updateSection("quality", { items, currentValue: cur ?? i18n.t("qualityAuto") });
|
|
832
|
+
},
|
|
833
|
+
setActive(value) {
|
|
834
|
+
activeQuality = value;
|
|
835
|
+
const label = qualityItems.find((i) => i.value === value)?.label ?? i18n.t("qualityAuto");
|
|
836
|
+
settingsPanel.updateSection("quality", { activeValue: value, currentValue: label });
|
|
837
|
+
},
|
|
838
|
+
setButtonContent() {
|
|
839
|
+
},
|
|
840
|
+
close() {
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
const audioMenuAdapter = {
|
|
844
|
+
el: document.createElement("div"),
|
|
845
|
+
setItems(items) {
|
|
846
|
+
audioItems = items;
|
|
847
|
+
const cur = activeAudio ? items.find((i) => i.value === activeAudio)?.label : "-";
|
|
848
|
+
settingsPanel.updateSection("audioTrack", { items, currentValue: cur ?? "-" });
|
|
849
|
+
},
|
|
850
|
+
setActive(value) {
|
|
851
|
+
activeAudio = value;
|
|
852
|
+
const label = audioItems.find((i) => i.value === value)?.label ?? "-";
|
|
853
|
+
settingsPanel.updateSection("audioTrack", { activeValue: value, currentValue: label });
|
|
854
|
+
},
|
|
855
|
+
setButtonContent() {
|
|
856
|
+
},
|
|
857
|
+
close() {
|
|
858
|
+
}
|
|
859
|
+
};
|
|
671
860
|
return {
|
|
672
861
|
topEl,
|
|
673
862
|
bottomEl,
|
|
674
863
|
progress,
|
|
675
864
|
volume,
|
|
676
|
-
qualityMenu,
|
|
677
|
-
audioMenu,
|
|
865
|
+
qualityMenu: qualityMenuAdapter,
|
|
866
|
+
audioMenu: audioMenuAdapter,
|
|
867
|
+
settingsPanel,
|
|
678
868
|
updatePlayState(playing) {
|
|
679
869
|
playBtn.innerHTML = playing ? icons.pause : icons.play;
|
|
680
870
|
},
|
|
@@ -683,16 +873,21 @@ function createControls(ctx) {
|
|
|
683
873
|
progress.update();
|
|
684
874
|
},
|
|
685
875
|
updateRate(rate) {
|
|
686
|
-
|
|
687
|
-
|
|
876
|
+
currentRate = rate;
|
|
877
|
+
settingsPanel.updateSection("rate", { activeValue: rate, currentValue: `${rate}x` });
|
|
688
878
|
},
|
|
689
879
|
updateFullscreen(fs) {
|
|
690
880
|
fsBtn.innerHTML = fs ? icons.fullscreenExit : icons.fullscreen;
|
|
691
881
|
},
|
|
882
|
+
updatePip(pip) {
|
|
883
|
+
settingsPanel.updateSection("pip", { toggle: { checked: pip } });
|
|
884
|
+
},
|
|
692
885
|
updateRatio(ratio) {
|
|
693
|
-
|
|
886
|
+
const label = ratio === "original" ? i18n.t("ratioOriginal") : ratio;
|
|
887
|
+
settingsPanel.updateSection("ratio", { activeValue: ratio, currentValue: label });
|
|
694
888
|
},
|
|
695
889
|
destroy() {
|
|
890
|
+
settingsPanel.destroy();
|
|
696
891
|
topEl.remove();
|
|
697
892
|
bottomEl.remove();
|
|
698
893
|
}
|
|
@@ -725,6 +920,94 @@ function createOsd() {
|
|
|
725
920
|
};
|
|
726
921
|
}
|
|
727
922
|
|
|
923
|
+
// src/ui/components/contextMenu.ts
|
|
924
|
+
function createContextMenu(container, items) {
|
|
925
|
+
const menu = createEl("div", { className: "sp-context-menu", parent: container });
|
|
926
|
+
for (const item of items) {
|
|
927
|
+
const btn = createEl("button", {
|
|
928
|
+
className: "sp-context-item",
|
|
929
|
+
text: item.label,
|
|
930
|
+
attrs: { type: "button" },
|
|
931
|
+
parent: menu
|
|
932
|
+
});
|
|
933
|
+
btn.addEventListener("click", () => {
|
|
934
|
+
hide();
|
|
935
|
+
item.onClick();
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
function show(clientX, clientY) {
|
|
939
|
+
menu.classList.add("sp-visible");
|
|
940
|
+
const rect = container.getBoundingClientRect();
|
|
941
|
+
const x = Math.max(4, Math.min(clientX - rect.left, rect.width - menu.offsetWidth - 4));
|
|
942
|
+
const y = Math.max(4, Math.min(clientY - rect.top, rect.height - menu.offsetHeight - 4));
|
|
943
|
+
menu.style.left = `${x}px`;
|
|
944
|
+
menu.style.top = `${y}px`;
|
|
945
|
+
}
|
|
946
|
+
function hide() {
|
|
947
|
+
menu.classList.remove("sp-visible");
|
|
948
|
+
}
|
|
949
|
+
const onContextMenu = (e) => {
|
|
950
|
+
e.preventDefault();
|
|
951
|
+
show(e.clientX, e.clientY);
|
|
952
|
+
};
|
|
953
|
+
const onPointerDown = (e) => {
|
|
954
|
+
if (!menu.contains(e.target)) hide();
|
|
955
|
+
};
|
|
956
|
+
container.addEventListener("contextmenu", onContextMenu);
|
|
957
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
958
|
+
return {
|
|
959
|
+
hide,
|
|
960
|
+
destroy() {
|
|
961
|
+
container.removeEventListener("contextmenu", onContextMenu);
|
|
962
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
963
|
+
menu.remove();
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// src/ui/components/shortcutsOverlay.ts
|
|
969
|
+
function createShortcutsOverlay(container, i18n, seekStep) {
|
|
970
|
+
let root = null;
|
|
971
|
+
function show() {
|
|
972
|
+
if (root) return;
|
|
973
|
+
root = createEl("div", { className: "sp-shortcuts", parent: container });
|
|
974
|
+
const closeBtn = createEl("button", {
|
|
975
|
+
className: "sp-shortcuts-close",
|
|
976
|
+
text: "\u2715",
|
|
977
|
+
attrs: { type: "button", "aria-label": i18n.t("close") },
|
|
978
|
+
parent: root
|
|
979
|
+
});
|
|
980
|
+
closeBtn.addEventListener("click", hide);
|
|
981
|
+
createEl("div", { className: "sp-shortcuts-title", text: i18n.t("shortcuts"), parent: root });
|
|
982
|
+
const dl = createEl("dl", { className: "sp-shortcuts-body", parent: root });
|
|
983
|
+
const rows = [
|
|
984
|
+
[i18n.t("scKeySpace"), i18n.t("scPlayPause")],
|
|
985
|
+
["\u2190 / \u2192", i18n.t("scSeek", { n: seekStep })],
|
|
986
|
+
[i18n.t("scKeyHold"), i18n.t("scLongSeek")],
|
|
987
|
+
["\u2191 / \u2193", i18n.t("scVolume")],
|
|
988
|
+
["F", i18n.t("scFullscreen")],
|
|
989
|
+
["M", i18n.t("scMute")]
|
|
990
|
+
];
|
|
991
|
+
for (const [key, action] of rows) {
|
|
992
|
+
const row = createEl("div", { className: "sp-shortcuts-row", parent: dl });
|
|
993
|
+
const dt = createEl("dt", { parent: row });
|
|
994
|
+
createEl("span", { className: "sp-kbd", text: key, parent: dt });
|
|
995
|
+
createEl("dd", { text: action, parent: row });
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
function hide() {
|
|
999
|
+
root?.remove();
|
|
1000
|
+
root = null;
|
|
1001
|
+
}
|
|
1002
|
+
return {
|
|
1003
|
+
toggle() {
|
|
1004
|
+
root ? hide() : show();
|
|
1005
|
+
},
|
|
1006
|
+
hide,
|
|
1007
|
+
destroy: hide
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
|
|
728
1011
|
// src/ui/components/statsOverlay.ts
|
|
729
1012
|
function createStatsOverlay(container, video, media, i18n) {
|
|
730
1013
|
let root = null;
|
|
@@ -763,11 +1046,31 @@ function createStatsOverlay(container, video, media, i18n) {
|
|
|
763
1046
|
function show() {
|
|
764
1047
|
if (root) return;
|
|
765
1048
|
root = createEl("div", { className: "sp-stats", parent: container });
|
|
1049
|
+
const header = createEl("div", { className: "sp-stats-header", parent: root });
|
|
1050
|
+
const copyBtn = createEl("button", {
|
|
1051
|
+
className: "sp-stats-copy",
|
|
1052
|
+
text: i18n.t("copyLog"),
|
|
1053
|
+
attrs: { type: "button" },
|
|
1054
|
+
parent: header
|
|
1055
|
+
});
|
|
1056
|
+
copyBtn.addEventListener("click", () => {
|
|
1057
|
+
const text = getLogText();
|
|
1058
|
+
void navigator.clipboard.writeText(text || "(empty)").then(
|
|
1059
|
+
() => {
|
|
1060
|
+
copyBtn.textContent = i18n.t("logCopied");
|
|
1061
|
+
setTimeout(() => {
|
|
1062
|
+
copyBtn.textContent = i18n.t("copyLog");
|
|
1063
|
+
}, 1500);
|
|
1064
|
+
},
|
|
1065
|
+
() => {
|
|
1066
|
+
}
|
|
1067
|
+
);
|
|
1068
|
+
});
|
|
766
1069
|
const closeBtn = createEl("button", {
|
|
767
1070
|
className: "sp-stats-close",
|
|
768
1071
|
text: "\u2715",
|
|
769
1072
|
attrs: { type: "button", "aria-label": i18n.t("closeStats") },
|
|
770
|
-
parent:
|
|
1073
|
+
parent: header
|
|
771
1074
|
});
|
|
772
1075
|
closeBtn.addEventListener("click", hide);
|
|
773
1076
|
createEl("dl", { className: "sp-stats-body", parent: root });
|
|
@@ -932,7 +1235,22 @@ var zhCN = {
|
|
|
932
1235
|
autoNextIn: "{n} \u79D2\u540E\u64AD\u653E\u4E0B\u4E00\u4E2A",
|
|
933
1236
|
cancel: "\u53D6\u6D88",
|
|
934
1237
|
loadError: "\u89C6\u9891\u52A0\u8F7D\u5931\u8D25",
|
|
935
|
-
retry: "\u91CD\u8BD5"
|
|
1238
|
+
retry: "\u91CD\u8BD5",
|
|
1239
|
+
changelog: "\u66F4\u65B0\u8BB0\u5F55",
|
|
1240
|
+
videoInfo: "\u89C6\u9891\u4FE1\u606F",
|
|
1241
|
+
shortcuts: "\u5FEB\u6377\u952E",
|
|
1242
|
+
close: "\u5173\u95ED",
|
|
1243
|
+
scKeySpace: "\u7A7A\u683C",
|
|
1244
|
+
scKeyHold: "\u2190 / \u2192 \u957F\u6309",
|
|
1245
|
+
scPlayPause: "\u64AD\u653E / \u6682\u505C",
|
|
1246
|
+
scSeek: "\u5FEB\u9000 / \u5FEB\u8FDB {n} \u79D2",
|
|
1247
|
+
scLongSeek: "\u9636\u68AF\u7D2F\u8BA1\u5FEB\u8FDB\u5FEB\u9000\uFF0C\u677E\u5F00\u6267\u884C",
|
|
1248
|
+
scVolume: "\u97F3\u91CF \xB15",
|
|
1249
|
+
scFullscreen: "\u5168\u5C4F\u5207\u6362",
|
|
1250
|
+
scMute: "\u9759\u97F3\u5207\u6362",
|
|
1251
|
+
settings: "\u8BBE\u7F6E",
|
|
1252
|
+
copyLog: "\u590D\u5236\u65E5\u5FD7",
|
|
1253
|
+
logCopied: "\u5DF2\u590D\u5236"
|
|
936
1254
|
};
|
|
937
1255
|
var en = {
|
|
938
1256
|
play: "Play",
|
|
@@ -961,7 +1279,22 @@ var en = {
|
|
|
961
1279
|
autoNextIn: "Playing next in {n}s",
|
|
962
1280
|
cancel: "Cancel",
|
|
963
1281
|
loadError: "Failed to load video",
|
|
964
|
-
retry: "Retry"
|
|
1282
|
+
retry: "Retry",
|
|
1283
|
+
changelog: "Changelog",
|
|
1284
|
+
videoInfo: "Video info",
|
|
1285
|
+
shortcuts: "Shortcuts",
|
|
1286
|
+
close: "Close",
|
|
1287
|
+
scKeySpace: "Space",
|
|
1288
|
+
scKeyHold: "Hold \u2190 / \u2192",
|
|
1289
|
+
scPlayPause: "Play / Pause",
|
|
1290
|
+
scSeek: "Rewind / forward {n}s",
|
|
1291
|
+
scLongSeek: "Accelerating seek, release to apply",
|
|
1292
|
+
scVolume: "Volume \xB15",
|
|
1293
|
+
scFullscreen: "Toggle fullscreen",
|
|
1294
|
+
scMute: "Toggle mute",
|
|
1295
|
+
settings: "Settings",
|
|
1296
|
+
copyLog: "Copy log",
|
|
1297
|
+
logCopied: "Copied"
|
|
965
1298
|
};
|
|
966
1299
|
var locales = { "zh-CN": zhCN, en };
|
|
967
1300
|
var I18n = class {
|
|
@@ -1034,8 +1367,11 @@ function clearProgress(id) {
|
|
|
1034
1367
|
}
|
|
1035
1368
|
}
|
|
1036
1369
|
|
|
1370
|
+
// src/version.ts
|
|
1371
|
+
var VERSION = true ? "0.4.0" : "dev";
|
|
1372
|
+
|
|
1037
1373
|
// src/styles/player.css
|
|
1038
|
-
var player_default = ".sweet-player {\n --sp-accent: #ff4d6d;\n --sp-bg: #000;\n --sp-text: #fff;\n --sp-bar-height: 4px;\n --sp-bar-hover-height: 6px;\n --sp-control-size: 36px;\n --sp-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n\n position: relative;\n width: 100%;\n height: 100%;\n background: var(--sp-bg);\n color: var(--sp-text);\n font-family: var(--sp-font);\n font-size: 13px;\n overflow: hidden;\n user-select: none;\n -webkit-user-select: none;\n outline: none;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.sweet-player *,\n.sweet-player *::before,\n.sweet-player *::after {\n box-sizing: border-box;\n}\n\n.sp-video {\n width: 100%;\n height: 100%;\n object-fit: contain;\n display: block;\n}\n\n/* \u5F3A\u5236\u753B\u9762\u6BD4\u4F8B\uFF1Avideo \u5C45\u4E2D\u3001\u6309\u6BD4\u4F8B\u9650\u5236\u5C3A\u5BF8\u5E76\u62C9\u4F38\u586B\u5145 */\n.sweet-player[data-ratio]:not([data-ratio='original']) .sp-video {\n width: auto;\n height: auto;\n max-width: 100%;\n max-height: 100%;\n aspect-ratio: var(--sp-forced-ratio);\n object-fit: fill;\n}\n\n/* ---------- \u9876\u90E8\u6807\u9898\u533A ---------- */\n.sp-top {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n padding: 12px 16px 28px;\n background: linear-gradient(to bottom, rgba(0, 0, 0, 0.6), transparent);\n transition: opacity 0.25s ease;\n pointer-events: none;\n}\n\n.sp-title {\n display: inline-block;\n max-width: 70%;\n font-size: 15px;\n font-weight: 500;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n pointer-events: auto;\n cursor: default;\n}\n\n/* ---------- \u7EDF\u8BA1\u4FE1\u606F\u8499\u5C42 ---------- */\n.sp-stats {\n position: absolute;\n top: 12px;\n left: 12px;\n z-index: 30;\n min-width: 300px;\n max-width: calc(100% - 24px);\n padding: 10px 14px;\n background: rgba(0, 0, 0, 0.75);\n border-radius: 6px;\n font-size: 12px;\n line-height: 1.7;\n font-family: 'SFMono-Regular', Consolas, monospace;\n pointer-events: auto;\n}\n\n.sp-stats-close {\n position: absolute;\n top: 6px;\n right: 6px;\n width: 22px;\n height: 22px;\n border: none;\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n opacity: 0.7;\n font-size: 16px;\n line-height: 1;\n}\n\n.sp-stats-close:hover {\n opacity: 1;\n}\n\n.sp-stats-row {\n display: flex;\n gap: 12px;\n}\n\n.sp-stats-row dt {\n flex: 0 0 110px;\n opacity: 0.65;\n margin: 0;\n}\n\n.sp-stats-row dd {\n margin: 0;\n word-break: break-all;\n}\n\n/* ---------- \u4E2D\u592E OSD \u53CD\u9988 ---------- */\n.sp-osd {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 20;\n padding: 10px 18px;\n background: rgba(0, 0, 0, 0.65);\n border-radius: 6px;\n font-size: 16px;\n font-weight: 500;\n opacity: 0;\n transition: opacity 0.15s ease;\n pointer-events: none;\n white-space: nowrap;\n}\n\n.sp-osd.sp-visible {\n opacity: 1;\n}\n\n/* ---------- \u5E95\u90E8\u63A7\u5236\u533A ---------- */\n.sp-bottom {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 24px 12px 6px;\n background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);\n transition: opacity 0.25s ease;\n z-index: 10;\n}\n\n.sweet-player.sp-controls-hidden .sp-bottom,\n.sweet-player.sp-controls-hidden .sp-top {\n opacity: 0;\n pointer-events: none;\n}\n\n.sweet-player.sp-controls-hidden {\n cursor: none;\n}\n\n/* ---------- \u8FDB\u5EA6\u6761 ---------- */\n.sp-progress {\n position: relative;\n height: 14px;\n display: flex;\n align-items: center;\n cursor: pointer;\n margin-bottom: 4px;\n}\n\n.sp-progress-track {\n position: relative;\n width: 100%;\n height: var(--sp-bar-height);\n background: rgba(255, 255, 255, 0.25);\n border-radius: 2px;\n overflow: visible;\n transition: height 0.1s ease;\n}\n\n.sp-progress:hover .sp-progress-track {\n height: var(--sp-bar-hover-height);\n}\n\n.sp-progress-buffered,\n.sp-progress-played {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n border-radius: 2px;\n}\n\n.sp-progress-buffered {\n background: rgba(255, 255, 255, 0.35);\n}\n\n.sp-progress-played {\n background: var(--sp-accent);\n}\n\n.sp-progress-thumb {\n position: absolute;\n top: 50%;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: var(--sp-accent);\n transform: translate(-50%, -50%) scale(0);\n transition: transform 0.1s ease;\n pointer-events: none;\n}\n\n.sp-progress:hover .sp-progress-thumb,\n.sp-progress.sp-dragging .sp-progress-thumb {\n transform: translate(-50%, -50%) scale(1);\n}\n\n.sp-progress-tooltip {\n position: absolute;\n bottom: 18px;\n transform: translateX(-50%);\n padding: 2px 6px;\n background: rgba(0, 0, 0, 0.8);\n border-radius: 3px;\n font-size: 12px;\n display: none;\n pointer-events: none;\n white-space: nowrap;\n}\n\n.sp-progress:hover .sp-progress-tooltip {\n display: block;\n}\n\n/* ---------- \u63A7\u5236\u6309\u94AE\u884C ---------- */\n.sp-controls {\n display: flex;\n align-items: center;\n gap: 2px;\n}\n\n.sp-controls-spacer {\n flex: 1;\n}\n\n.sp-btn {\n width: var(--sp-control-size);\n height: var(--sp-control-size);\n border: none;\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n padding: 0;\n opacity: 0.9;\n transition: opacity 0.1s ease, background 0.1s ease;\n}\n\n.sp-btn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.15);\n}\n\n.sp-btn svg {\n width: 22px;\n height: 22px;\n}\n\n.sp-btn:disabled {\n opacity: 0.35;\n cursor: not-allowed;\n}\n\n.sp-time {\n padding: 0 8px;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n opacity: 0.95;\n}\n\n/* ---------- \u5F39\u51FA\u83DC\u5355 ---------- */\n.sp-menu {\n position: relative;\n}\n\n.sp-menu-btn {\n min-width: var(--sp-control-size);\n width: auto;\n padding: 0 8px;\n font-size: 13px;\n font-weight: 500;\n}\n\n.sp-menu-panel {\n position: absolute;\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n min-width: 80px;\n max-height: 220px;\n overflow-y: auto;\n background: rgba(28, 28, 28, 0.95);\n border-radius: 6px;\n padding: 4px 0;\n display: none;\n z-index: 25;\n}\n\n.sp-menu.sp-open .sp-menu-panel {\n display: block;\n}\n\n.sp-menu-item {\n display: block;\n width: 100%;\n padding: 7px 16px;\n border: none;\n background: transparent;\n color: var(--sp-text);\n text-align: center;\n cursor: pointer;\n font-size: 13px;\n white-space: nowrap;\n}\n\n.sp-menu-item:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n\n.sp-menu-item.sp-active {\n color: var(--sp-accent);\n font-weight: 600;\n}\n\n.sp-menu-empty {\n padding: 7px 16px;\n opacity: 0.5;\n white-space: nowrap;\n}\n\n/* ---------- \u72B6\u6001\u8499\u5C42\uFF1Aloading / \u9519\u8BEF / \u7ED3\u675F ---------- */\n.sp-state {\n position: absolute;\n inset: 0;\n z-index: 15;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n}\n\n.sp-spinner {\n opacity: 0;\n transition: opacity 0.2s ease 0.3s; /* \u5EF6\u8FDF\u51FA\u73B0\uFF0C\u907F\u514D\u77ED\u6682 seek \u95EA\u70C1 */\n}\n\n.sp-state.sp-loading .sp-spinner {\n opacity: 1;\n}\n\n.sp-spinner-ring {\n width: 44px;\n height: 44px;\n border: 4px solid rgba(255, 255, 255, 0.25);\n border-top-color: var(--sp-accent);\n border-radius: 50%;\n animation: sp-spin 0.8s linear infinite;\n}\n\n@keyframes sp-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n.sp-state-panel {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 14px;\n background: rgba(0, 0, 0, 0.55);\n pointer-events: auto;\n}\n\n.sp-state-message {\n font-size: 15px;\n opacity: 0.95;\n}\n\n.sp-state-actions {\n display: flex;\n gap: 12px;\n}\n\n.sp-state-btn {\n padding: 8px 22px;\n border: 1px solid rgba(255, 255, 255, 0.5);\n border-radius: 18px;\n background: transparent;\n color: var(--sp-text);\n font-size: 14px;\n cursor: pointer;\n transition: background 0.1s ease;\n}\n\n.sp-state-btn:hover {\n background: rgba(255, 255, 255, 0.15);\n}\n\n.sp-state-btn-primary {\n background: var(--sp-accent);\n border-color: var(--sp-accent);\n}\n\n.sp-state-btn-primary:hover {\n background: var(--sp-accent);\n filter: brightness(1.1);\n}\n\n.sp-state-countdown {\n font-size: 13px;\n opacity: 0.85;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.sp-state-cancel {\n border: none;\n background: transparent;\n color: var(--sp-accent);\n cursor: pointer;\n font-size: 13px;\n padding: 2px 6px;\n}\n\n/* ---------- \u97F3\u91CF ---------- */\n.sp-volume {\n display: flex;\n align-items: center;\n}\n\n.sp-volume-slider {\n width: 0;\n overflow: hidden;\n transition: width 0.15s ease;\n display: flex;\n align-items: center;\n}\n\n.sp-volume:hover .sp-volume-slider,\n.sp-volume.sp-dragging .sp-volume-slider {\n width: 64px;\n}\n\n.sp-volume-track {\n position: relative;\n width: 56px;\n height: var(--sp-bar-height);\n margin: 0 4px;\n background: rgba(255, 255, 255, 0.25);\n border-radius: 2px;\n cursor: pointer;\n}\n\n.sp-volume-fill {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--sp-text);\n border-radius: 2px;\n}\n\n.sp-volume-thumb {\n position: absolute;\n top: 50%;\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: var(--sp-text);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n";
|
|
1374
|
+
var player_default = ".sweet-player {\n --sp-accent: #ff4d6d;\n --sp-bg: #000;\n --sp-text: #fff;\n --sp-bar-height: 4px;\n --sp-bar-hover-height: 6px;\n --sp-control-size: 36px;\n --sp-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n\n position: relative;\n width: 100%;\n height: 100%;\n background: var(--sp-bg);\n color: var(--sp-text);\n font-family: var(--sp-font);\n font-size: 13px;\n overflow: hidden;\n user-select: none;\n -webkit-user-select: none;\n outline: none;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.sweet-player *,\n.sweet-player *::before,\n.sweet-player *::after {\n box-sizing: border-box;\n}\n\n.sp-video {\n width: 100%;\n height: 100%;\n object-fit: contain;\n display: block;\n}\n\n/* \u5F3A\u5236\u753B\u9762\u6BD4\u4F8B\uFF1Avideo \u5C45\u4E2D\u3001\u6309\u6BD4\u4F8B\u9650\u5236\u5C3A\u5BF8\u5E76\u62C9\u4F38\u586B\u5145 */\n.sweet-player[data-ratio]:not([data-ratio='original']) .sp-video {\n width: auto;\n height: auto;\n max-width: 100%;\n max-height: 100%;\n aspect-ratio: var(--sp-forced-ratio);\n object-fit: fill;\n}\n\n/* ---------- \u9876\u90E8\u6807\u9898\u533A ---------- */\n.sp-top {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n padding: 12px 16px 28px;\n background: linear-gradient(to bottom, rgba(0, 0, 0, 0.6), transparent);\n transition: opacity 0.25s ease;\n pointer-events: none;\n}\n\n.sp-title {\n display: inline-block;\n max-width: 70%;\n font-size: 15px;\n font-weight: 500;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n pointer-events: auto;\n cursor: default;\n}\n\n/* ---------- \u7EDF\u8BA1\u4FE1\u606F\u8499\u5C42 ---------- */\n.sp-stats {\n position: absolute;\n top: 12px;\n left: 12px;\n z-index: 30;\n min-width: 300px;\n max-width: calc(100% - 24px);\n padding: 10px 14px;\n background: rgba(0, 0, 0, 0.75);\n border-radius: 6px;\n font-size: 12px;\n line-height: 1.7;\n font-family: 'SFMono-Regular', Consolas, monospace;\n pointer-events: auto;\n}\n\n.sp-stats-header {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: 4px;\n margin: -4px -6px 4px 0;\n}\n\n.sp-stats-copy {\n border: none;\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n opacity: 0.7;\n font-size: 12px;\n line-height: 22px;\n padding: 0 6px;\n border-radius: 3px;\n white-space: nowrap;\n}\n\n.sp-stats-copy:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.1);\n}\n\n.sp-stats-close {\n width: 22px;\n height: 22px;\n border: none;\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n opacity: 0.7;\n font-size: 16px;\n line-height: 1;\n flex-shrink: 0;\n}\n\n.sp-stats-close:hover {\n opacity: 1;\n}\n\n.sp-stats-row {\n display: flex;\n gap: 12px;\n}\n\n.sp-stats-row dt {\n flex: 0 0 110px;\n opacity: 0.65;\n margin: 0;\n}\n\n.sp-stats-row dd {\n margin: 0;\n word-break: break-all;\n}\n\n/* ---------- \u5FEB\u6377\u952E\u9762\u677F ---------- */\n.sp-shortcuts {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 30;\n min-width: 280px;\n max-width: calc(100% - 24px);\n max-height: calc(100% - 24px);\n overflow-y: auto;\n padding: 14px 20px 14px;\n background: rgba(0, 0, 0, 0.8);\n border-radius: 8px;\n font-size: 13px;\n line-height: 2.1;\n pointer-events: auto;\n}\n\n.sp-shortcuts-title {\n font-size: 14px;\n font-weight: 600;\n margin-bottom: 6px;\n}\n\n.sp-shortcuts-body {\n margin: 0;\n}\n\n.sp-shortcuts-row {\n display: flex;\n align-items: center;\n gap: 16px;\n}\n\n.sp-shortcuts-row dt {\n flex: 0 0 100px;\n margin: 0;\n}\n\n.sp-shortcuts-row dd {\n margin: 0;\n opacity: 0.85;\n}\n\n.sp-kbd {\n display: inline-block;\n padding: 0 7px;\n background: rgba(255, 255, 255, 0.15);\n border-radius: 4px;\n font-family: 'SFMono-Regular', Consolas, monospace;\n font-size: 12px;\n line-height: 20px;\n white-space: nowrap;\n}\n\n.sp-shortcuts-close {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 22px;\n height: 22px;\n border: none;\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n opacity: 0.7;\n font-size: 16px;\n line-height: 1;\n}\n\n.sp-shortcuts-close:hover {\n opacity: 1;\n}\n\n/* ---------- \u53F3\u952E\u83DC\u5355 ---------- */\n.sp-context-menu {\n position: absolute;\n z-index: 40;\n min-width: 150px;\n padding: 4px 0;\n background: rgba(28, 28, 28, 0.95);\n border-radius: 6px;\n display: none;\n}\n\n.sp-context-menu.sp-visible {\n display: block;\n}\n\n.sp-context-item {\n display: block;\n width: 100%;\n padding: 8px 16px;\n border: none;\n background: transparent;\n color: var(--sp-text);\n text-align: left;\n cursor: pointer;\n font-size: 13px;\n white-space: nowrap;\n}\n\n.sp-context-item:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n\n/* ---------- \u4E2D\u592E OSD \u53CD\u9988 ---------- */\n.sp-osd {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 20;\n padding: 10px 18px;\n background: rgba(0, 0, 0, 0.65);\n border-radius: 6px;\n font-size: 16px;\n font-weight: 500;\n opacity: 0;\n transition: opacity 0.15s ease;\n pointer-events: none;\n white-space: nowrap;\n}\n\n.sp-osd.sp-visible {\n opacity: 1;\n}\n\n/* ---------- \u5E95\u90E8\u63A7\u5236\u533A ---------- */\n.sp-bottom {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 24px 12px 6px;\n background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);\n transition: opacity 0.25s ease;\n z-index: 10;\n}\n\n.sweet-player.sp-controls-hidden .sp-bottom,\n.sweet-player.sp-controls-hidden .sp-top {\n opacity: 0;\n pointer-events: none;\n}\n\n.sweet-player.sp-controls-hidden {\n cursor: none;\n}\n\n/* ---------- \u8FDB\u5EA6\u6761 ---------- */\n.sp-progress {\n position: relative;\n height: 14px;\n display: flex;\n align-items: center;\n cursor: pointer;\n margin-bottom: 4px;\n}\n\n.sp-progress-track {\n position: relative;\n width: 100%;\n height: var(--sp-bar-height);\n background: rgba(255, 255, 255, 0.25);\n border-radius: 2px;\n overflow: visible;\n transition: height 0.1s ease;\n}\n\n.sp-progress:hover .sp-progress-track {\n height: var(--sp-bar-hover-height);\n}\n\n.sp-progress-buffered,\n.sp-progress-played {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n border-radius: 2px;\n}\n\n.sp-progress-buffered {\n background: rgba(255, 255, 255, 0.35);\n}\n\n.sp-progress-played {\n background: var(--sp-accent);\n}\n\n.sp-progress-thumb {\n position: absolute;\n top: 50%;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: var(--sp-accent);\n transform: translate(-50%, -50%) scale(0);\n transition: transform 0.1s ease;\n pointer-events: none;\n}\n\n.sp-progress:hover .sp-progress-thumb,\n.sp-progress.sp-dragging .sp-progress-thumb {\n transform: translate(-50%, -50%) scale(1);\n}\n\n.sp-progress-tooltip {\n position: absolute;\n bottom: 18px;\n transform: translateX(-50%);\n padding: 2px 6px;\n background: rgba(0, 0, 0, 0.8);\n border-radius: 3px;\n font-size: 12px;\n display: none;\n pointer-events: none;\n white-space: nowrap;\n}\n\n.sp-progress:hover .sp-progress-tooltip {\n display: block;\n}\n\n/* ---------- \u63A7\u5236\u6309\u94AE\u884C ---------- */\n.sp-controls {\n display: flex;\n align-items: center;\n gap: 2px;\n}\n\n.sp-controls-spacer {\n flex: 1;\n}\n\n.sp-btn {\n width: var(--sp-control-size);\n height: var(--sp-control-size);\n border: none;\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n padding: 0;\n opacity: 0.9;\n transition: opacity 0.1s ease, background 0.1s ease;\n}\n\n.sp-btn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.15);\n}\n\n.sp-btn svg {\n width: 22px;\n height: 22px;\n}\n\n.sp-btn:disabled {\n opacity: 0.35;\n cursor: not-allowed;\n}\n\n.sp-time {\n padding: 0 8px;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n opacity: 0.95;\n}\n\n/* ---------- \u5F39\u51FA\u83DC\u5355 ---------- */\n.sp-menu {\n position: relative;\n}\n\n.sp-menu-btn {\n min-width: var(--sp-control-size);\n width: auto;\n padding: 0 8px;\n font-size: 13px;\n font-weight: 500;\n}\n\n.sp-menu-panel {\n position: absolute;\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n min-width: 80px;\n max-height: 220px;\n overflow-y: auto;\n background: rgba(28, 28, 28, 0.95);\n border-radius: 6px;\n padding: 4px 0;\n display: none;\n z-index: 25;\n}\n\n.sp-menu.sp-open .sp-menu-panel {\n display: block;\n}\n\n.sp-menu-item {\n display: block;\n width: 100%;\n padding: 7px 16px;\n border: none;\n background: transparent;\n color: var(--sp-text);\n text-align: center;\n cursor: pointer;\n font-size: 13px;\n white-space: nowrap;\n}\n\n.sp-menu-item:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n\n.sp-menu-item.sp-active {\n color: var(--sp-accent);\n font-weight: 600;\n}\n\n.sp-menu-empty {\n padding: 7px 16px;\n opacity: 0.5;\n white-space: nowrap;\n}\n\n/* ---------- \u72B6\u6001\u8499\u5C42\uFF1Aloading / \u9519\u8BEF / \u7ED3\u675F ---------- */\n.sp-state {\n position: absolute;\n inset: 0;\n z-index: 15;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n}\n\n.sp-spinner {\n opacity: 0;\n transition: opacity 0.2s ease 0.3s; /* \u5EF6\u8FDF\u51FA\u73B0\uFF0C\u907F\u514D\u77ED\u6682 seek \u95EA\u70C1 */\n}\n\n.sp-state.sp-loading .sp-spinner {\n opacity: 1;\n}\n\n.sp-spinner-ring {\n width: 44px;\n height: 44px;\n border: 4px solid rgba(255, 255, 255, 0.25);\n border-top-color: var(--sp-accent);\n border-radius: 50%;\n animation: sp-spin 0.8s linear infinite;\n}\n\n@keyframes sp-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n.sp-state-panel {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 14px;\n background: rgba(0, 0, 0, 0.55);\n pointer-events: auto;\n}\n\n.sp-state-message {\n font-size: 15px;\n opacity: 0.95;\n}\n\n.sp-state-actions {\n display: flex;\n gap: 12px;\n}\n\n.sp-state-btn {\n padding: 8px 22px;\n border: 1px solid rgba(255, 255, 255, 0.5);\n border-radius: 18px;\n background: transparent;\n color: var(--sp-text);\n font-size: 14px;\n cursor: pointer;\n transition: background 0.1s ease;\n}\n\n.sp-state-btn:hover {\n background: rgba(255, 255, 255, 0.15);\n}\n\n.sp-state-btn-primary {\n background: var(--sp-accent);\n border-color: var(--sp-accent);\n}\n\n.sp-state-btn-primary:hover {\n background: var(--sp-accent);\n filter: brightness(1.1);\n}\n\n.sp-state-countdown {\n font-size: 13px;\n opacity: 0.85;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.sp-state-cancel {\n border: none;\n background: transparent;\n color: var(--sp-accent);\n cursor: pointer;\n font-size: 13px;\n padding: 2px 6px;\n}\n\n/* ---------- \u8BBE\u7F6E\u9762\u677F ---------- */\n.sp-settings-panel {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n width: 180px;\n background: rgba(28, 28, 28, 0.95);\n border-radius: 6px;\n padding: 4px 0;\n display: none;\n z-index: 25;\n}\n\n.sp-settings.sp-open .sp-settings-panel {\n display: block;\n}\n\n.sp-settings-row {\n display: flex;\n align-items: center;\n width: 100%;\n padding: 7px 12px;\n border: none;\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n font-size: 13px;\n white-space: nowrap;\n gap: 4px;\n}\n\n.sp-settings-row:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n\n.sp-settings-label {\n flex: 1;\n text-align: left;\n}\n\n.sp-settings-value {\n opacity: 0.6;\n font-size: 12px;\n}\n\n.sp-settings-arrow {\n display: inline-flex;\n align-items: center;\n opacity: 0.5;\n}\n\n.sp-settings-arrow svg {\n width: 16px;\n height: 16px;\n}\n\n.sp-settings-back {\n display: flex;\n align-items: center;\n width: 100%;\n padding: 7px 12px;\n border: none;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n gap: 2px;\n margin-bottom: 2px;\n}\n\n.sp-settings-back:hover {\n background: rgba(255, 255, 255, 0.08);\n}\n\n.sp-settings-back-arrow {\n display: inline-flex;\n align-items: center;\n opacity: 0.7;\n}\n\n.sp-settings-back-arrow svg {\n width: 18px;\n height: 18px;\n}\n\n.sp-settings-sub .sp-menu-item {\n text-align: left;\n padding: 7px 16px;\n}\n\n/* ---------- \u884C\u5185\u5F00\u5173 ---------- */\n.sp-settings-toggle-row {\n cursor: pointer;\n}\n\n.sp-toggle {\n position: relative;\n width: 30px;\n height: 17px;\n background: rgba(255, 255, 255, 0.25);\n border-radius: 9px;\n flex-shrink: 0;\n transition: background 0.15s ease;\n outline: none;\n}\n\n.sp-toggle:focus-visible {\n box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.4);\n}\n\n.sp-toggle.sp-on {\n background: var(--sp-accent);\n}\n\n.sp-toggle-thumb {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 13px;\n height: 13px;\n background: #fff;\n border-radius: 50%;\n transition: transform 0.15s ease;\n}\n\n.sp-toggle.sp-on .sp-toggle-thumb {\n transform: translateX(13px);\n}\n\n/* ---------- \u97F3\u91CF ---------- */\n.sp-volume {\n display: flex;\n align-items: center;\n}\n\n.sp-volume-slider {\n width: 0;\n overflow: hidden;\n transition: width 0.15s ease;\n display: flex;\n align-items: center;\n}\n\n.sp-volume:hover .sp-volume-slider,\n.sp-volume.sp-dragging .sp-volume-slider {\n width: 64px;\n}\n\n.sp-volume-track {\n position: relative;\n width: 56px;\n height: var(--sp-bar-height);\n margin: 0 4px;\n background: rgba(255, 255, 255, 0.25);\n border-radius: 2px;\n cursor: pointer;\n}\n\n.sp-volume-fill {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--sp-text);\n border-radius: 2px;\n}\n\n.sp-volume-thumb {\n position: absolute;\n top: 50%;\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: var(--sp-text);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n";
|
|
1039
1375
|
|
|
1040
1376
|
// src/player.ts
|
|
1041
1377
|
var DEFAULT_RATES = [0.5, 1, 1.5, 2];
|
|
@@ -1044,19 +1380,17 @@ var DEFAULT_LONG_SEEK_STEPS = [10, 30, 60];
|
|
|
1044
1380
|
var DEFAULT_STEP_UP_INTERVAL = 2e3;
|
|
1045
1381
|
var DEFAULT_AUTO_NEXT_SECONDS = 5;
|
|
1046
1382
|
var CONTROLS_HIDE_DELAY = 3e3;
|
|
1047
|
-
var TITLE_CLICK_COUNT = 10;
|
|
1048
|
-
var TITLE_CLICK_WINDOW = 1500;
|
|
1049
1383
|
var SINGLE_CLICK_DELAY = 250;
|
|
1384
|
+
var NPM_URL = "https://www.npmjs.com/package/@sweet-player/core";
|
|
1050
1385
|
var PROGRESS_SAVE_INTERVAL = 5e3;
|
|
1051
1386
|
var PROGRESS_END_GUARD = 10;
|
|
1052
1387
|
var SweetPlayer = class {
|
|
1053
1388
|
constructor(options) {
|
|
1054
1389
|
this.emitter = new EventEmitter();
|
|
1390
|
+
this.contextMenu = null;
|
|
1055
1391
|
this.hideTimer = null;
|
|
1056
1392
|
this.clickTimer = null;
|
|
1057
1393
|
this.progressTimer = null;
|
|
1058
|
-
this.titleClicks = 0;
|
|
1059
|
-
this.lastTitleClick = 0;
|
|
1060
1394
|
this.currentRatio = "original";
|
|
1061
1395
|
/** 画质/音轨菜单当前是否由 hls.js 自动接管(业务 setQualities 会关闭) */
|
|
1062
1396
|
this.hlsManagedQuality = false;
|
|
@@ -1089,9 +1423,27 @@ var SweetPlayer = class {
|
|
|
1089
1423
|
onLevels: autoQuality && !options.qualities?.length ? (levels) => this.applyHlsLevels(levels) : void 0,
|
|
1090
1424
|
onAudioTracks: autoQuality && !options.audioTracks?.length ? (tracks) => this.applyHlsAudioTracks(tracks) : void 0
|
|
1091
1425
|
});
|
|
1426
|
+
const hidden = new Set(options.hiddenControls ?? []);
|
|
1092
1427
|
this.osd = createOsd();
|
|
1093
1428
|
this.stats = createStatsOverlay(this.container, this.video, this.media, this.i18n);
|
|
1429
|
+
this.shortcutsPanel = createShortcutsOverlay(this.container, this.i18n, options.seekStep ?? 10);
|
|
1094
1430
|
this.state = createStateOverlay(this.i18n);
|
|
1431
|
+
if (!hidden.has("contextMenu")) {
|
|
1432
|
+
this.contextMenu = createContextMenu(this.container, [
|
|
1433
|
+
{
|
|
1434
|
+
label: `${this.i18n.t("changelog")}: v${VERSION}`,
|
|
1435
|
+
onClick: () => window.open(NPM_URL, "_blank", "noopener")
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
label: this.i18n.t("videoInfo"),
|
|
1439
|
+
onClick: () => this.stats.toggle()
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
label: this.i18n.t("shortcuts"),
|
|
1443
|
+
onClick: () => this.shortcutsPanel.toggle()
|
|
1444
|
+
}
|
|
1445
|
+
]);
|
|
1446
|
+
}
|
|
1095
1447
|
this.controls = createControls({
|
|
1096
1448
|
video: this.video,
|
|
1097
1449
|
title: options.title ?? "",
|
|
@@ -1099,6 +1451,7 @@ var SweetPlayer = class {
|
|
|
1099
1451
|
playbackRates: options.playbackRates ?? DEFAULT_RATES,
|
|
1100
1452
|
aspectRatios: options.aspectRatios ?? DEFAULT_RATIOS,
|
|
1101
1453
|
seekStep: options.seekStep ?? 10,
|
|
1454
|
+
hidden,
|
|
1102
1455
|
actions: {
|
|
1103
1456
|
togglePlay: () => this.toggle(),
|
|
1104
1457
|
seekBy: (d) => this.seekBy(d),
|
|
@@ -1111,8 +1464,7 @@ var SweetPlayer = class {
|
|
|
1111
1464
|
selectQuality: (q) => this.handleQualitySelect(q),
|
|
1112
1465
|
selectAudioTrack: (t) => this.handleAudioTrackSelect(t),
|
|
1113
1466
|
onPrev: options.onPrev,
|
|
1114
|
-
onNext: options.onNext
|
|
1115
|
-
onTitleClick: () => this.handleTitleClick()
|
|
1467
|
+
onNext: options.onNext
|
|
1116
1468
|
}
|
|
1117
1469
|
});
|
|
1118
1470
|
this.container.appendChild(this.controls.topEl);
|
|
@@ -1168,6 +1520,7 @@ var SweetPlayer = class {
|
|
|
1168
1520
|
this.bindActivityTracking();
|
|
1169
1521
|
this.disposers.push(
|
|
1170
1522
|
onFullscreenChange(this.container, (fs) => {
|
|
1523
|
+
log("\u64AD\u653E\u5668", fs ? "\u8FDB\u5165\u5168\u5C4F" : "\u9000\u51FA\u5168\u5C4F");
|
|
1171
1524
|
this.controls.updateFullscreen(fs);
|
|
1172
1525
|
this.emitter.emit("fullscreenchange", fs);
|
|
1173
1526
|
})
|
|
@@ -1203,6 +1556,7 @@ var SweetPlayer = class {
|
|
|
1203
1556
|
if (showOsd) this.osd.flash(`${delta > 0 ? "+" : ""}${delta} ${this.i18n.t("seconds")}`);
|
|
1204
1557
|
}
|
|
1205
1558
|
setRate(rate) {
|
|
1559
|
+
log("\u64AD\u653E\u5668", `\u500D\u901F\u5207\u6362: ${rate}x`);
|
|
1206
1560
|
this.video.playbackRate = rate;
|
|
1207
1561
|
}
|
|
1208
1562
|
/** 0-100 */
|
|
@@ -1289,7 +1643,9 @@ var SweetPlayer = class {
|
|
|
1289
1643
|
this.keyboard.destroy();
|
|
1290
1644
|
this.gestures.destroy();
|
|
1291
1645
|
this.stats.destroy();
|
|
1646
|
+
this.shortcutsPanel.destroy();
|
|
1292
1647
|
this.state.destroy();
|
|
1648
|
+
this.contextMenu?.destroy();
|
|
1293
1649
|
this.controls.destroy();
|
|
1294
1650
|
this.disposers.forEach((d) => d());
|
|
1295
1651
|
if (this.hideTimer) clearTimeout(this.hideTimer);
|
|
@@ -1312,6 +1668,7 @@ var SweetPlayer = class {
|
|
|
1312
1668
|
this.hlsManagedAudio = true;
|
|
1313
1669
|
}
|
|
1314
1670
|
handleQualitySelect(quality) {
|
|
1671
|
+
log("\u64AD\u653E\u5668", `\u5207\u6362\u753B\u8D28: ${quality.label}`);
|
|
1315
1672
|
this.controls.qualityMenu.setActive(quality);
|
|
1316
1673
|
if (this.hlsManagedQuality && typeof quality.value === "number") {
|
|
1317
1674
|
this.media.setLevel(quality.value);
|
|
@@ -1324,6 +1681,7 @@ var SweetPlayer = class {
|
|
|
1324
1681
|
this.emitter.emit("qualitychange", quality);
|
|
1325
1682
|
}
|
|
1326
1683
|
handleAudioTrackSelect(track) {
|
|
1684
|
+
log("\u64AD\u653E\u5668", `\u5207\u6362\u97F3\u8F68: ${track.label}`);
|
|
1327
1685
|
this.controls.audioMenu.setActive(track);
|
|
1328
1686
|
if (this.hlsManagedAudio && typeof track.value === "number") {
|
|
1329
1687
|
this.media.setAudioTrack(track.value);
|
|
@@ -1364,15 +1722,6 @@ var SweetPlayer = class {
|
|
|
1364
1722
|
}
|
|
1365
1723
|
}
|
|
1366
1724
|
// ---------- 内部:交互 ----------
|
|
1367
|
-
handleTitleClick() {
|
|
1368
|
-
const now = Date.now();
|
|
1369
|
-
this.titleClicks = now - this.lastTitleClick <= TITLE_CLICK_WINDOW ? this.titleClicks + 1 : 1;
|
|
1370
|
-
this.lastTitleClick = now;
|
|
1371
|
-
if (this.titleClicks >= TITLE_CLICK_COUNT) {
|
|
1372
|
-
this.titleClicks = 0;
|
|
1373
|
-
this.stats.toggle();
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
1725
|
bindMediaEvents() {
|
|
1377
1726
|
const v = this.video;
|
|
1378
1727
|
const listen = (event, fn) => {
|
|
@@ -1380,6 +1729,7 @@ var SweetPlayer = class {
|
|
|
1380
1729
|
this.disposers.push(() => v.removeEventListener(event, fn));
|
|
1381
1730
|
};
|
|
1382
1731
|
listen("loadedmetadata", () => {
|
|
1732
|
+
log("\u539F\u751F\u4E8B\u4EF6", `\u83B7\u53D6\u5143\u6570\u636E (${v.videoWidth}x${v.videoHeight})`);
|
|
1383
1733
|
this.controls.updateTime();
|
|
1384
1734
|
this.emitter.emit("ready", void 0);
|
|
1385
1735
|
});
|
|
@@ -1395,6 +1745,7 @@ var SweetPlayer = class {
|
|
|
1395
1745
|
this.emitter.emit("pause", void 0);
|
|
1396
1746
|
});
|
|
1397
1747
|
listen("ended", () => {
|
|
1748
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u64AD\u653E\u7ED3\u675F");
|
|
1398
1749
|
this.saveProgressNow();
|
|
1399
1750
|
this.showControls();
|
|
1400
1751
|
const onNext = this.options.onNext;
|
|
@@ -1424,23 +1775,49 @@ var SweetPlayer = class {
|
|
|
1424
1775
|
this.controls.volume.update(volume, v.muted);
|
|
1425
1776
|
this.emitter.emit("volumechange", { volume, muted: v.muted });
|
|
1426
1777
|
});
|
|
1427
|
-
listen("waiting", () =>
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1778
|
+
listen("waiting", () => {
|
|
1779
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u7B49\u5F85\u7F13\u51B2\u4E2D");
|
|
1780
|
+
this.state.showLoading();
|
|
1781
|
+
});
|
|
1782
|
+
listen("stalled", () => {
|
|
1783
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u6570\u636E\u52A0\u8F7D\u505C\u6EDE");
|
|
1784
|
+
this.state.showLoading();
|
|
1785
|
+
});
|
|
1786
|
+
listen("seeking", () => {
|
|
1787
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u5F00\u59CB\u8DF3\u8F6C");
|
|
1788
|
+
this.state.showLoading();
|
|
1789
|
+
});
|
|
1790
|
+
listen("canplay", () => {
|
|
1791
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u51C6\u5907\u597D\u5F00\u59CB\u64AD\u653E");
|
|
1792
|
+
this.state.hideLoading();
|
|
1793
|
+
});
|
|
1431
1794
|
listen("playing", () => {
|
|
1432
1795
|
this.state.hideLoading();
|
|
1433
1796
|
this.state.hideError();
|
|
1434
1797
|
});
|
|
1435
|
-
listen("seeked", () =>
|
|
1436
|
-
|
|
1798
|
+
listen("seeked", () => {
|
|
1799
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u8DF3\u8F6C\u5B8C\u6210");
|
|
1800
|
+
this.state.hideLoading();
|
|
1801
|
+
});
|
|
1802
|
+
listen("error", () => {
|
|
1803
|
+
log("\u539F\u751F\u4E8B\u4EF6", `\u64AD\u653E\u9519\u8BEF: ${v.error?.message ?? "\u672A\u77E5\u9519\u8BEF"}`);
|
|
1804
|
+
this.showErrorState({ type: "media", detail: v.error });
|
|
1805
|
+
});
|
|
1437
1806
|
this.disposers.push(
|
|
1438
1807
|
this.emitter.on("error", (payload) => {
|
|
1439
1808
|
if (payload.type.startsWith("hls-")) this.showErrorStateUi();
|
|
1440
1809
|
})
|
|
1441
1810
|
);
|
|
1442
|
-
listen("enterpictureinpicture", () =>
|
|
1443
|
-
|
|
1811
|
+
listen("enterpictureinpicture", () => {
|
|
1812
|
+
log("\u64AD\u653E\u5668", "\u8FDB\u5165\u753B\u4E2D\u753B");
|
|
1813
|
+
this.controls.updatePip(true);
|
|
1814
|
+
this.emitter.emit("pipchange", true);
|
|
1815
|
+
});
|
|
1816
|
+
listen("leavepictureinpicture", () => {
|
|
1817
|
+
log("\u64AD\u653E\u5668", "\u9000\u51FA\u753B\u4E2D\u753B");
|
|
1818
|
+
this.controls.updatePip(false);
|
|
1819
|
+
this.emitter.emit("pipchange", false);
|
|
1820
|
+
});
|
|
1444
1821
|
const onVideoClick = (e) => {
|
|
1445
1822
|
if (e.target !== this.video || e.pointerType === "touch") return;
|
|
1446
1823
|
if (this.clickTimer) return;
|
|
@@ -1507,7 +1884,9 @@ var SweetPlayer = class {
|
|
|
1507
1884
|
});
|
|
1508
1885
|
}
|
|
1509
1886
|
};
|
|
1887
|
+
SweetPlayer.version = VERSION;
|
|
1510
1888
|
export {
|
|
1511
1889
|
SweetPlayer,
|
|
1890
|
+
VERSION,
|
|
1512
1891
|
registerLocale
|
|
1513
1892
|
};
|