@sweet-player/core 0.2.0 → 0.3.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 +130 -0
- package/dist/index.cjs +264 -46
- package/dist/index.d.cts +24 -4
- package/dist/index.d.ts +24 -4
- package/dist/index.js +263 -46
- package/dist/sweet-player.global.js +156 -24
- 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;
|
|
@@ -596,33 +629,37 @@ function createVolumeControl(opts) {
|
|
|
596
629
|
|
|
597
630
|
// src/ui/controls.ts
|
|
598
631
|
function createControls(ctx) {
|
|
599
|
-
const { video, actions, i18n } = ctx;
|
|
632
|
+
const { video, actions, i18n, hidden } = ctx;
|
|
633
|
+
const show = (name) => !hidden.has(name);
|
|
600
634
|
const topEl = createEl("div", { className: "sp-top" });
|
|
601
|
-
|
|
602
|
-
titleEl.addEventListener("click", actions.onTitleClick);
|
|
635
|
+
if (show("title")) createEl("div", { className: "sp-title", text: ctx.title, parent: topEl });
|
|
603
636
|
const bottomEl = createEl("div", { className: "sp-bottom" });
|
|
604
637
|
const progress = createProgressBar(video, (t) => {
|
|
605
638
|
video.currentTime = t;
|
|
606
639
|
});
|
|
607
|
-
bottomEl.appendChild(progress.el);
|
|
640
|
+
if (show("progress")) bottomEl.appendChild(progress.el);
|
|
608
641
|
const row = createEl("div", { className: "sp-controls", parent: bottomEl });
|
|
609
|
-
const button = (html, title, onClick, disabled = false) => {
|
|
642
|
+
const button = (html, title, onClick, disabled = false, visible = true) => {
|
|
610
643
|
const btn = createEl("button", {
|
|
611
644
|
className: "sp-btn",
|
|
612
645
|
html,
|
|
613
646
|
attrs: { type: "button", title, "aria-label": title },
|
|
614
|
-
parent: row
|
|
647
|
+
parent: visible ? row : void 0
|
|
615
648
|
});
|
|
616
649
|
btn.disabled = disabled;
|
|
617
650
|
btn.addEventListener("click", onClick);
|
|
618
651
|
return btn;
|
|
619
652
|
};
|
|
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", {
|
|
653
|
+
button(icons.prev, i18n.t("prev"), () => actions.onPrev?.(), !actions.onPrev, show("prev"));
|
|
654
|
+
button(icons.seekBack, i18n.t("seekBack", { n: ctx.seekStep }), () => actions.seekBy(-ctx.seekStep), false, show("seekBack"));
|
|
655
|
+
const playBtn = button(icons.play, i18n.t("playPause"), actions.togglePlay, false, show("play"));
|
|
656
|
+
button(icons.seekForward, i18n.t("seekForward", { n: ctx.seekStep }), () => actions.seekBy(ctx.seekStep), false, show("seekForward"));
|
|
657
|
+
button(icons.next, i18n.t("next"), () => actions.onNext?.(), !actions.onNext, show("next"));
|
|
658
|
+
const timeEl = createEl("span", {
|
|
659
|
+
className: "sp-time",
|
|
660
|
+
text: "0:00 / 0:00",
|
|
661
|
+
parent: show("time") ? row : void 0
|
|
662
|
+
});
|
|
626
663
|
createEl("div", { className: "sp-controls-spacer", parent: row });
|
|
627
664
|
const rateMenu = createPopupMenu({
|
|
628
665
|
buttonHtml: "1x",
|
|
@@ -632,14 +669,14 @@ function createControls(ctx) {
|
|
|
632
669
|
});
|
|
633
670
|
rateMenu.setItems(ctx.playbackRates.map((r) => ({ label: `${r}x`, value: r })));
|
|
634
671
|
rateMenu.setActive(video.playbackRate);
|
|
635
|
-
row.appendChild(rateMenu.el);
|
|
672
|
+
if (show("rate")) row.appendChild(rateMenu.el);
|
|
636
673
|
const qualityMenu = createPopupMenu({
|
|
637
674
|
buttonHtml: i18n.t("quality"),
|
|
638
675
|
title: i18n.t("quality"),
|
|
639
676
|
emptyText: i18n.t("empty"),
|
|
640
677
|
onSelect: (item) => actions.selectQuality(item.value)
|
|
641
678
|
});
|
|
642
|
-
row.appendChild(qualityMenu.el);
|
|
679
|
+
if (show("quality")) row.appendChild(qualityMenu.el);
|
|
643
680
|
const ratioMenu = createPopupMenu({
|
|
644
681
|
buttonHtml: i18n.t("aspectRatio"),
|
|
645
682
|
title: i18n.t("aspectRatio"),
|
|
@@ -650,24 +687,24 @@ function createControls(ctx) {
|
|
|
650
687
|
ctx.aspectRatios.map((r) => ({ label: r === "original" ? i18n.t("ratioOriginal") : r, value: r }))
|
|
651
688
|
);
|
|
652
689
|
ratioMenu.setActive("original");
|
|
653
|
-
row.appendChild(ratioMenu.el);
|
|
690
|
+
if (show("ratio")) row.appendChild(ratioMenu.el);
|
|
654
691
|
const audioMenu = createPopupMenu({
|
|
655
692
|
buttonHtml: icons.audio,
|
|
656
693
|
title: i18n.t("audioTrack"),
|
|
657
694
|
emptyText: i18n.t("empty"),
|
|
658
695
|
onSelect: (item) => actions.selectAudioTrack(item.value)
|
|
659
696
|
});
|
|
660
|
-
row.appendChild(audioMenu.el);
|
|
697
|
+
if (show("audioTrack")) row.appendChild(audioMenu.el);
|
|
661
698
|
const volume = createVolumeControl({
|
|
662
699
|
muteTitle: i18n.t("mute"),
|
|
663
700
|
onVolumeChange: actions.setVolume,
|
|
664
701
|
onToggleMute: actions.toggleMute
|
|
665
702
|
});
|
|
666
|
-
row.appendChild(volume.el);
|
|
667
|
-
if (document.pictureInPictureEnabled) {
|
|
703
|
+
if (show("volume")) row.appendChild(volume.el);
|
|
704
|
+
if (show("pip") && document.pictureInPictureEnabled) {
|
|
668
705
|
button(icons.pip, i18n.t("pip"), actions.togglePip);
|
|
669
706
|
}
|
|
670
|
-
const fsBtn = button(icons.fullscreen, i18n.t("fullscreen"), actions.toggleFullscreen);
|
|
707
|
+
const fsBtn = button(icons.fullscreen, i18n.t("fullscreen"), actions.toggleFullscreen, false, show("fullscreen"));
|
|
671
708
|
return {
|
|
672
709
|
topEl,
|
|
673
710
|
bottomEl,
|
|
@@ -725,6 +762,94 @@ function createOsd() {
|
|
|
725
762
|
};
|
|
726
763
|
}
|
|
727
764
|
|
|
765
|
+
// src/ui/components/contextMenu.ts
|
|
766
|
+
function createContextMenu(container, items) {
|
|
767
|
+
const menu = createEl("div", { className: "sp-context-menu", parent: container });
|
|
768
|
+
for (const item of items) {
|
|
769
|
+
const btn = createEl("button", {
|
|
770
|
+
className: "sp-context-item",
|
|
771
|
+
text: item.label,
|
|
772
|
+
attrs: { type: "button" },
|
|
773
|
+
parent: menu
|
|
774
|
+
});
|
|
775
|
+
btn.addEventListener("click", () => {
|
|
776
|
+
hide();
|
|
777
|
+
item.onClick();
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
function show(clientX, clientY) {
|
|
781
|
+
menu.classList.add("sp-visible");
|
|
782
|
+
const rect = container.getBoundingClientRect();
|
|
783
|
+
const x = Math.max(4, Math.min(clientX - rect.left, rect.width - menu.offsetWidth - 4));
|
|
784
|
+
const y = Math.max(4, Math.min(clientY - rect.top, rect.height - menu.offsetHeight - 4));
|
|
785
|
+
menu.style.left = `${x}px`;
|
|
786
|
+
menu.style.top = `${y}px`;
|
|
787
|
+
}
|
|
788
|
+
function hide() {
|
|
789
|
+
menu.classList.remove("sp-visible");
|
|
790
|
+
}
|
|
791
|
+
const onContextMenu = (e) => {
|
|
792
|
+
e.preventDefault();
|
|
793
|
+
show(e.clientX, e.clientY);
|
|
794
|
+
};
|
|
795
|
+
const onPointerDown = (e) => {
|
|
796
|
+
if (!menu.contains(e.target)) hide();
|
|
797
|
+
};
|
|
798
|
+
container.addEventListener("contextmenu", onContextMenu);
|
|
799
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
800
|
+
return {
|
|
801
|
+
hide,
|
|
802
|
+
destroy() {
|
|
803
|
+
container.removeEventListener("contextmenu", onContextMenu);
|
|
804
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
805
|
+
menu.remove();
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/ui/components/shortcutsOverlay.ts
|
|
811
|
+
function createShortcutsOverlay(container, i18n, seekStep) {
|
|
812
|
+
let root = null;
|
|
813
|
+
function show() {
|
|
814
|
+
if (root) return;
|
|
815
|
+
root = createEl("div", { className: "sp-shortcuts", parent: container });
|
|
816
|
+
const closeBtn = createEl("button", {
|
|
817
|
+
className: "sp-shortcuts-close",
|
|
818
|
+
text: "\u2715",
|
|
819
|
+
attrs: { type: "button", "aria-label": i18n.t("close") },
|
|
820
|
+
parent: root
|
|
821
|
+
});
|
|
822
|
+
closeBtn.addEventListener("click", hide);
|
|
823
|
+
createEl("div", { className: "sp-shortcuts-title", text: i18n.t("shortcuts"), parent: root });
|
|
824
|
+
const dl = createEl("dl", { className: "sp-shortcuts-body", parent: root });
|
|
825
|
+
const rows = [
|
|
826
|
+
[i18n.t("scKeySpace"), i18n.t("scPlayPause")],
|
|
827
|
+
["\u2190 / \u2192", i18n.t("scSeek", { n: seekStep })],
|
|
828
|
+
[i18n.t("scKeyHold"), i18n.t("scLongSeek")],
|
|
829
|
+
["\u2191 / \u2193", i18n.t("scVolume")],
|
|
830
|
+
["F", i18n.t("scFullscreen")],
|
|
831
|
+
["M", i18n.t("scMute")]
|
|
832
|
+
];
|
|
833
|
+
for (const [key, action] of rows) {
|
|
834
|
+
const row = createEl("div", { className: "sp-shortcuts-row", parent: dl });
|
|
835
|
+
const dt = createEl("dt", { parent: row });
|
|
836
|
+
createEl("span", { className: "sp-kbd", text: key, parent: dt });
|
|
837
|
+
createEl("dd", { text: action, parent: row });
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
function hide() {
|
|
841
|
+
root?.remove();
|
|
842
|
+
root = null;
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
toggle() {
|
|
846
|
+
root ? hide() : show();
|
|
847
|
+
},
|
|
848
|
+
hide,
|
|
849
|
+
destroy: hide
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
728
853
|
// src/ui/components/statsOverlay.ts
|
|
729
854
|
function createStatsOverlay(container, video, media, i18n) {
|
|
730
855
|
let root = null;
|
|
@@ -763,11 +888,31 @@ function createStatsOverlay(container, video, media, i18n) {
|
|
|
763
888
|
function show() {
|
|
764
889
|
if (root) return;
|
|
765
890
|
root = createEl("div", { className: "sp-stats", parent: container });
|
|
891
|
+
const header = createEl("div", { className: "sp-stats-header", parent: root });
|
|
892
|
+
const copyBtn = createEl("button", {
|
|
893
|
+
className: "sp-stats-copy",
|
|
894
|
+
text: i18n.t("copyLog"),
|
|
895
|
+
attrs: { type: "button" },
|
|
896
|
+
parent: header
|
|
897
|
+
});
|
|
898
|
+
copyBtn.addEventListener("click", () => {
|
|
899
|
+
const text = getLogText();
|
|
900
|
+
void navigator.clipboard.writeText(text || "(empty)").then(
|
|
901
|
+
() => {
|
|
902
|
+
copyBtn.textContent = i18n.t("logCopied");
|
|
903
|
+
setTimeout(() => {
|
|
904
|
+
copyBtn.textContent = i18n.t("copyLog");
|
|
905
|
+
}, 1500);
|
|
906
|
+
},
|
|
907
|
+
() => {
|
|
908
|
+
}
|
|
909
|
+
);
|
|
910
|
+
});
|
|
766
911
|
const closeBtn = createEl("button", {
|
|
767
912
|
className: "sp-stats-close",
|
|
768
913
|
text: "\u2715",
|
|
769
914
|
attrs: { type: "button", "aria-label": i18n.t("closeStats") },
|
|
770
|
-
parent:
|
|
915
|
+
parent: header
|
|
771
916
|
});
|
|
772
917
|
closeBtn.addEventListener("click", hide);
|
|
773
918
|
createEl("dl", { className: "sp-stats-body", parent: root });
|
|
@@ -932,7 +1077,21 @@ var zhCN = {
|
|
|
932
1077
|
autoNextIn: "{n} \u79D2\u540E\u64AD\u653E\u4E0B\u4E00\u4E2A",
|
|
933
1078
|
cancel: "\u53D6\u6D88",
|
|
934
1079
|
loadError: "\u89C6\u9891\u52A0\u8F7D\u5931\u8D25",
|
|
935
|
-
retry: "\u91CD\u8BD5"
|
|
1080
|
+
retry: "\u91CD\u8BD5",
|
|
1081
|
+
changelog: "\u66F4\u65B0\u8BB0\u5F55",
|
|
1082
|
+
videoInfo: "\u89C6\u9891\u4FE1\u606F",
|
|
1083
|
+
shortcuts: "\u5FEB\u6377\u952E",
|
|
1084
|
+
close: "\u5173\u95ED",
|
|
1085
|
+
scKeySpace: "\u7A7A\u683C",
|
|
1086
|
+
scKeyHold: "\u2190 / \u2192 \u957F\u6309",
|
|
1087
|
+
scPlayPause: "\u64AD\u653E / \u6682\u505C",
|
|
1088
|
+
scSeek: "\u5FEB\u9000 / \u5FEB\u8FDB {n} \u79D2",
|
|
1089
|
+
scLongSeek: "\u9636\u68AF\u7D2F\u8BA1\u5FEB\u8FDB\u5FEB\u9000\uFF0C\u677E\u5F00\u6267\u884C",
|
|
1090
|
+
scVolume: "\u97F3\u91CF \xB15",
|
|
1091
|
+
scFullscreen: "\u5168\u5C4F\u5207\u6362",
|
|
1092
|
+
scMute: "\u9759\u97F3\u5207\u6362",
|
|
1093
|
+
copyLog: "\u590D\u5236\u65E5\u5FD7",
|
|
1094
|
+
logCopied: "\u5DF2\u590D\u5236"
|
|
936
1095
|
};
|
|
937
1096
|
var en = {
|
|
938
1097
|
play: "Play",
|
|
@@ -961,7 +1120,21 @@ var en = {
|
|
|
961
1120
|
autoNextIn: "Playing next in {n}s",
|
|
962
1121
|
cancel: "Cancel",
|
|
963
1122
|
loadError: "Failed to load video",
|
|
964
|
-
retry: "Retry"
|
|
1123
|
+
retry: "Retry",
|
|
1124
|
+
changelog: "Changelog",
|
|
1125
|
+
videoInfo: "Video info",
|
|
1126
|
+
shortcuts: "Shortcuts",
|
|
1127
|
+
close: "Close",
|
|
1128
|
+
scKeySpace: "Space",
|
|
1129
|
+
scKeyHold: "Hold \u2190 / \u2192",
|
|
1130
|
+
scPlayPause: "Play / Pause",
|
|
1131
|
+
scSeek: "Rewind / forward {n}s",
|
|
1132
|
+
scLongSeek: "Accelerating seek, release to apply",
|
|
1133
|
+
scVolume: "Volume \xB15",
|
|
1134
|
+
scFullscreen: "Toggle fullscreen",
|
|
1135
|
+
scMute: "Toggle mute",
|
|
1136
|
+
copyLog: "Copy log",
|
|
1137
|
+
logCopied: "Copied"
|
|
965
1138
|
};
|
|
966
1139
|
var locales = { "zh-CN": zhCN, en };
|
|
967
1140
|
var I18n = class {
|
|
@@ -1034,8 +1207,11 @@ function clearProgress(id) {
|
|
|
1034
1207
|
}
|
|
1035
1208
|
}
|
|
1036
1209
|
|
|
1210
|
+
// src/version.ts
|
|
1211
|
+
var VERSION = true ? "0.3.0" : "dev";
|
|
1212
|
+
|
|
1037
1213
|
// 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";
|
|
1214
|
+
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/* ---------- \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
1215
|
|
|
1040
1216
|
// src/player.ts
|
|
1041
1217
|
var DEFAULT_RATES = [0.5, 1, 1.5, 2];
|
|
@@ -1044,19 +1220,17 @@ var DEFAULT_LONG_SEEK_STEPS = [10, 30, 60];
|
|
|
1044
1220
|
var DEFAULT_STEP_UP_INTERVAL = 2e3;
|
|
1045
1221
|
var DEFAULT_AUTO_NEXT_SECONDS = 5;
|
|
1046
1222
|
var CONTROLS_HIDE_DELAY = 3e3;
|
|
1047
|
-
var TITLE_CLICK_COUNT = 10;
|
|
1048
|
-
var TITLE_CLICK_WINDOW = 1500;
|
|
1049
1223
|
var SINGLE_CLICK_DELAY = 250;
|
|
1224
|
+
var NPM_URL = "https://www.npmjs.com/package/@sweet-player/core";
|
|
1050
1225
|
var PROGRESS_SAVE_INTERVAL = 5e3;
|
|
1051
1226
|
var PROGRESS_END_GUARD = 10;
|
|
1052
1227
|
var SweetPlayer = class {
|
|
1053
1228
|
constructor(options) {
|
|
1054
1229
|
this.emitter = new EventEmitter();
|
|
1230
|
+
this.contextMenu = null;
|
|
1055
1231
|
this.hideTimer = null;
|
|
1056
1232
|
this.clickTimer = null;
|
|
1057
1233
|
this.progressTimer = null;
|
|
1058
|
-
this.titleClicks = 0;
|
|
1059
|
-
this.lastTitleClick = 0;
|
|
1060
1234
|
this.currentRatio = "original";
|
|
1061
1235
|
/** 画质/音轨菜单当前是否由 hls.js 自动接管(业务 setQualities 会关闭) */
|
|
1062
1236
|
this.hlsManagedQuality = false;
|
|
@@ -1089,9 +1263,27 @@ var SweetPlayer = class {
|
|
|
1089
1263
|
onLevels: autoQuality && !options.qualities?.length ? (levels) => this.applyHlsLevels(levels) : void 0,
|
|
1090
1264
|
onAudioTracks: autoQuality && !options.audioTracks?.length ? (tracks) => this.applyHlsAudioTracks(tracks) : void 0
|
|
1091
1265
|
});
|
|
1266
|
+
const hidden = new Set(options.hiddenControls ?? []);
|
|
1092
1267
|
this.osd = createOsd();
|
|
1093
1268
|
this.stats = createStatsOverlay(this.container, this.video, this.media, this.i18n);
|
|
1269
|
+
this.shortcutsPanel = createShortcutsOverlay(this.container, this.i18n, options.seekStep ?? 10);
|
|
1094
1270
|
this.state = createStateOverlay(this.i18n);
|
|
1271
|
+
if (!hidden.has("contextMenu")) {
|
|
1272
|
+
this.contextMenu = createContextMenu(this.container, [
|
|
1273
|
+
{
|
|
1274
|
+
label: `${this.i18n.t("changelog")}: v${VERSION}`,
|
|
1275
|
+
onClick: () => window.open(NPM_URL, "_blank", "noopener")
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
label: this.i18n.t("videoInfo"),
|
|
1279
|
+
onClick: () => this.stats.toggle()
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
label: this.i18n.t("shortcuts"),
|
|
1283
|
+
onClick: () => this.shortcutsPanel.toggle()
|
|
1284
|
+
}
|
|
1285
|
+
]);
|
|
1286
|
+
}
|
|
1095
1287
|
this.controls = createControls({
|
|
1096
1288
|
video: this.video,
|
|
1097
1289
|
title: options.title ?? "",
|
|
@@ -1099,6 +1291,7 @@ var SweetPlayer = class {
|
|
|
1099
1291
|
playbackRates: options.playbackRates ?? DEFAULT_RATES,
|
|
1100
1292
|
aspectRatios: options.aspectRatios ?? DEFAULT_RATIOS,
|
|
1101
1293
|
seekStep: options.seekStep ?? 10,
|
|
1294
|
+
hidden,
|
|
1102
1295
|
actions: {
|
|
1103
1296
|
togglePlay: () => this.toggle(),
|
|
1104
1297
|
seekBy: (d) => this.seekBy(d),
|
|
@@ -1111,8 +1304,7 @@ var SweetPlayer = class {
|
|
|
1111
1304
|
selectQuality: (q) => this.handleQualitySelect(q),
|
|
1112
1305
|
selectAudioTrack: (t) => this.handleAudioTrackSelect(t),
|
|
1113
1306
|
onPrev: options.onPrev,
|
|
1114
|
-
onNext: options.onNext
|
|
1115
|
-
onTitleClick: () => this.handleTitleClick()
|
|
1307
|
+
onNext: options.onNext
|
|
1116
1308
|
}
|
|
1117
1309
|
});
|
|
1118
1310
|
this.container.appendChild(this.controls.topEl);
|
|
@@ -1168,6 +1360,7 @@ var SweetPlayer = class {
|
|
|
1168
1360
|
this.bindActivityTracking();
|
|
1169
1361
|
this.disposers.push(
|
|
1170
1362
|
onFullscreenChange(this.container, (fs) => {
|
|
1363
|
+
log("\u64AD\u653E\u5668", fs ? "\u8FDB\u5165\u5168\u5C4F" : "\u9000\u51FA\u5168\u5C4F");
|
|
1171
1364
|
this.controls.updateFullscreen(fs);
|
|
1172
1365
|
this.emitter.emit("fullscreenchange", fs);
|
|
1173
1366
|
})
|
|
@@ -1203,6 +1396,7 @@ var SweetPlayer = class {
|
|
|
1203
1396
|
if (showOsd) this.osd.flash(`${delta > 0 ? "+" : ""}${delta} ${this.i18n.t("seconds")}`);
|
|
1204
1397
|
}
|
|
1205
1398
|
setRate(rate) {
|
|
1399
|
+
log("\u64AD\u653E\u5668", `\u500D\u901F\u5207\u6362: ${rate}x`);
|
|
1206
1400
|
this.video.playbackRate = rate;
|
|
1207
1401
|
}
|
|
1208
1402
|
/** 0-100 */
|
|
@@ -1289,7 +1483,9 @@ var SweetPlayer = class {
|
|
|
1289
1483
|
this.keyboard.destroy();
|
|
1290
1484
|
this.gestures.destroy();
|
|
1291
1485
|
this.stats.destroy();
|
|
1486
|
+
this.shortcutsPanel.destroy();
|
|
1292
1487
|
this.state.destroy();
|
|
1488
|
+
this.contextMenu?.destroy();
|
|
1293
1489
|
this.controls.destroy();
|
|
1294
1490
|
this.disposers.forEach((d) => d());
|
|
1295
1491
|
if (this.hideTimer) clearTimeout(this.hideTimer);
|
|
@@ -1312,6 +1508,7 @@ var SweetPlayer = class {
|
|
|
1312
1508
|
this.hlsManagedAudio = true;
|
|
1313
1509
|
}
|
|
1314
1510
|
handleQualitySelect(quality) {
|
|
1511
|
+
log("\u64AD\u653E\u5668", `\u5207\u6362\u753B\u8D28: ${quality.label}`);
|
|
1315
1512
|
this.controls.qualityMenu.setActive(quality);
|
|
1316
1513
|
if (this.hlsManagedQuality && typeof quality.value === "number") {
|
|
1317
1514
|
this.media.setLevel(quality.value);
|
|
@@ -1324,6 +1521,7 @@ var SweetPlayer = class {
|
|
|
1324
1521
|
this.emitter.emit("qualitychange", quality);
|
|
1325
1522
|
}
|
|
1326
1523
|
handleAudioTrackSelect(track) {
|
|
1524
|
+
log("\u64AD\u653E\u5668", `\u5207\u6362\u97F3\u8F68: ${track.label}`);
|
|
1327
1525
|
this.controls.audioMenu.setActive(track);
|
|
1328
1526
|
if (this.hlsManagedAudio && typeof track.value === "number") {
|
|
1329
1527
|
this.media.setAudioTrack(track.value);
|
|
@@ -1364,15 +1562,6 @@ var SweetPlayer = class {
|
|
|
1364
1562
|
}
|
|
1365
1563
|
}
|
|
1366
1564
|
// ---------- 内部:交互 ----------
|
|
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
1565
|
bindMediaEvents() {
|
|
1377
1566
|
const v = this.video;
|
|
1378
1567
|
const listen = (event, fn) => {
|
|
@@ -1380,6 +1569,7 @@ var SweetPlayer = class {
|
|
|
1380
1569
|
this.disposers.push(() => v.removeEventListener(event, fn));
|
|
1381
1570
|
};
|
|
1382
1571
|
listen("loadedmetadata", () => {
|
|
1572
|
+
log("\u539F\u751F\u4E8B\u4EF6", `\u83B7\u53D6\u5143\u6570\u636E (${v.videoWidth}x${v.videoHeight})`);
|
|
1383
1573
|
this.controls.updateTime();
|
|
1384
1574
|
this.emitter.emit("ready", void 0);
|
|
1385
1575
|
});
|
|
@@ -1395,6 +1585,7 @@ var SweetPlayer = class {
|
|
|
1395
1585
|
this.emitter.emit("pause", void 0);
|
|
1396
1586
|
});
|
|
1397
1587
|
listen("ended", () => {
|
|
1588
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u64AD\u653E\u7ED3\u675F");
|
|
1398
1589
|
this.saveProgressNow();
|
|
1399
1590
|
this.showControls();
|
|
1400
1591
|
const onNext = this.options.onNext;
|
|
@@ -1424,23 +1615,47 @@ var SweetPlayer = class {
|
|
|
1424
1615
|
this.controls.volume.update(volume, v.muted);
|
|
1425
1616
|
this.emitter.emit("volumechange", { volume, muted: v.muted });
|
|
1426
1617
|
});
|
|
1427
|
-
listen("waiting", () =>
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1618
|
+
listen("waiting", () => {
|
|
1619
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u7B49\u5F85\u7F13\u51B2\u4E2D");
|
|
1620
|
+
this.state.showLoading();
|
|
1621
|
+
});
|
|
1622
|
+
listen("stalled", () => {
|
|
1623
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u6570\u636E\u52A0\u8F7D\u505C\u6EDE");
|
|
1624
|
+
this.state.showLoading();
|
|
1625
|
+
});
|
|
1626
|
+
listen("seeking", () => {
|
|
1627
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u5F00\u59CB\u8DF3\u8F6C");
|
|
1628
|
+
this.state.showLoading();
|
|
1629
|
+
});
|
|
1630
|
+
listen("canplay", () => {
|
|
1631
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u51C6\u5907\u597D\u5F00\u59CB\u64AD\u653E");
|
|
1632
|
+
this.state.hideLoading();
|
|
1633
|
+
});
|
|
1431
1634
|
listen("playing", () => {
|
|
1432
1635
|
this.state.hideLoading();
|
|
1433
1636
|
this.state.hideError();
|
|
1434
1637
|
});
|
|
1435
|
-
listen("seeked", () =>
|
|
1436
|
-
|
|
1638
|
+
listen("seeked", () => {
|
|
1639
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u8DF3\u8F6C\u5B8C\u6210");
|
|
1640
|
+
this.state.hideLoading();
|
|
1641
|
+
});
|
|
1642
|
+
listen("error", () => {
|
|
1643
|
+
log("\u539F\u751F\u4E8B\u4EF6", `\u64AD\u653E\u9519\u8BEF: ${v.error?.message ?? "\u672A\u77E5\u9519\u8BEF"}`);
|
|
1644
|
+
this.showErrorState({ type: "media", detail: v.error });
|
|
1645
|
+
});
|
|
1437
1646
|
this.disposers.push(
|
|
1438
1647
|
this.emitter.on("error", (payload) => {
|
|
1439
1648
|
if (payload.type.startsWith("hls-")) this.showErrorStateUi();
|
|
1440
1649
|
})
|
|
1441
1650
|
);
|
|
1442
|
-
listen("enterpictureinpicture", () =>
|
|
1443
|
-
|
|
1651
|
+
listen("enterpictureinpicture", () => {
|
|
1652
|
+
log("\u64AD\u653E\u5668", "\u8FDB\u5165\u753B\u4E2D\u753B");
|
|
1653
|
+
this.emitter.emit("pipchange", true);
|
|
1654
|
+
});
|
|
1655
|
+
listen("leavepictureinpicture", () => {
|
|
1656
|
+
log("\u64AD\u653E\u5668", "\u9000\u51FA\u753B\u4E2D\u753B");
|
|
1657
|
+
this.emitter.emit("pipchange", false);
|
|
1658
|
+
});
|
|
1444
1659
|
const onVideoClick = (e) => {
|
|
1445
1660
|
if (e.target !== this.video || e.pointerType === "touch") return;
|
|
1446
1661
|
if (this.clickTimer) return;
|
|
@@ -1507,7 +1722,9 @@ var SweetPlayer = class {
|
|
|
1507
1722
|
});
|
|
1508
1723
|
}
|
|
1509
1724
|
};
|
|
1725
|
+
SweetPlayer.version = VERSION;
|
|
1510
1726
|
export {
|
|
1511
1727
|
SweetPlayer,
|
|
1728
|
+
VERSION,
|
|
1512
1729
|
registerLocale
|
|
1513
1730
|
};
|