@sweet-player/core 0.2.1 → 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 +24 -6
- 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/README.md
CHANGED
|
@@ -58,7 +58,8 @@ player.destroy();
|
|
|
58
58
|
- **i18n**:内置 zh-CN/en,支持自定义语言
|
|
59
59
|
- **插件机制**:`plugins` 选项或 `player.use(plugin)` 运行时安装
|
|
60
60
|
- **主题定制**:CSS 变量覆盖,如 `--sp-accent`
|
|
61
|
-
-
|
|
61
|
+
- **右键菜单**:屏蔽原生菜单,内置"更新记录(当前版本,跳转 npm)"、"视频信息(YouTube 风格统计面板)"、"快捷键(键位说明面板)"
|
|
62
|
+
- **按需隐藏**:`hiddenControls: ['ratio', 'audioTrack', ...]` 收集不显示的功能,默认全显示(只影响 UI,不影响 API 与快捷键)
|
|
62
63
|
|
|
63
64
|
## 画质 / 音轨
|
|
64
65
|
|
|
@@ -84,23 +85,40 @@ new SweetPlayer({ ..., plugins: [myPlugin] });
|
|
|
84
85
|
|
|
85
86
|
### 接入 sweet-subtitle 字幕
|
|
86
87
|
|
|
88
|
+
插件工厂持有字幕实例并对外暴露切换 API,运行时换字幕无需重建播放器:
|
|
89
|
+
|
|
87
90
|
```ts
|
|
88
91
|
import { SweetSubtitle } from 'sweet-subtitle';
|
|
89
92
|
import type { SweetPlayerPlugin } from '@sweet-player/core';
|
|
90
93
|
|
|
91
|
-
function
|
|
92
|
-
|
|
94
|
+
function createSubtitlePlugin(src?: string) {
|
|
95
|
+
let sub: SweetSubtitle | null = null;
|
|
96
|
+
const plugin: SweetPlayerPlugin = {
|
|
93
97
|
name: 'sweet-subtitle',
|
|
94
98
|
apply(player) {
|
|
95
|
-
|
|
96
|
-
return () => sub
|
|
99
|
+
sub = new SweetSubtitle(player.video, src ? { src } : {});
|
|
100
|
+
return () => { sub?.destroy(); sub = null; }; // 播放器 destroy 时自动清理
|
|
97
101
|
},
|
|
98
102
|
};
|
|
103
|
+
return {
|
|
104
|
+
plugin,
|
|
105
|
+
load: (url: string) => sub?.loadFromUrl(url), // 切换字幕
|
|
106
|
+
show: () => sub?.show(),
|
|
107
|
+
hide: () => sub?.hide(),
|
|
108
|
+
setOffset: (s: number) => sub?.setOffset(s),
|
|
109
|
+
};
|
|
99
110
|
}
|
|
100
111
|
|
|
101
|
-
|
|
112
|
+
const subtitle = createSubtitlePlugin('/subs/ep-01.ass');
|
|
113
|
+
const player = new SweetPlayer({ ..., plugins: [subtitle.plugin] });
|
|
114
|
+
|
|
115
|
+
// 随时换字幕 / 关字幕,播放器不动
|
|
116
|
+
await subtitle.load('/subs/ep-02.ass');
|
|
117
|
+
subtitle.hide();
|
|
102
118
|
```
|
|
103
119
|
|
|
120
|
+
不想用插件的话也可以完全独立管理:`new SweetSubtitle(player.video, ...)` 拿到实例自己持有,只需在销毁播放器前调用 `sub.destroy()`。
|
|
121
|
+
|
|
104
122
|
## 实例 API
|
|
105
123
|
|
|
106
124
|
`play()` `pause()` `toggle()` `seek(t)` `seekBy(±s)` `setRate(r)` `setVolume(0-100)` `setMuted(b)` `setAspectRatio(r)` `setQualities(list)` `setAudioTracks(list)` `toggleFullscreen()` `togglePip()` `load(src)` `setTitle(s)` `use(plugin)` `on/off(event, fn)` `destroy()`
|
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
SweetPlayer: () => SweetPlayer,
|
|
34
|
+
VERSION: () => VERSION,
|
|
34
35
|
registerLocale: () => registerLocale
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -62,6 +63,25 @@ var EventEmitter = class {
|
|
|
62
63
|
|
|
63
64
|
// src/core/media.ts
|
|
64
65
|
var import_hls = __toESM(require("hls.js"), 1);
|
|
66
|
+
|
|
67
|
+
// src/logger.ts
|
|
68
|
+
var MAX_ENTRIES = 2e3;
|
|
69
|
+
var entries = [];
|
|
70
|
+
function pad2(n) {
|
|
71
|
+
return n < 10 ? `0${n}` : String(n);
|
|
72
|
+
}
|
|
73
|
+
function formatTs(d) {
|
|
74
|
+
return `${d.getFullYear()}/${d.getMonth() + 1}/${d.getDate()} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
|
|
75
|
+
}
|
|
76
|
+
function log(category, message) {
|
|
77
|
+
if (entries.length >= MAX_ENTRIES) entries.shift();
|
|
78
|
+
entries.push({ time: /* @__PURE__ */ new Date(), category, message });
|
|
79
|
+
}
|
|
80
|
+
function getLogText() {
|
|
81
|
+
return entries.map((e) => `[${formatTs(e.time)}] [${e.category}]\uFF1A${e.message}`).join("\n");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/core/media.ts
|
|
65
85
|
function isHlsSource(src) {
|
|
66
86
|
return /\.m3u8($|\?)/i.test(src);
|
|
67
87
|
}
|
|
@@ -78,10 +98,15 @@ var MediaController = class {
|
|
|
78
98
|
load(src) {
|
|
79
99
|
this.detachHls();
|
|
80
100
|
this.currentSrc = src;
|
|
101
|
+
log("\u64AD\u653E\u5668", `\u52A0\u8F7D\u6E90: ${src}`);
|
|
81
102
|
if (isHlsSource(src)) {
|
|
82
103
|
if (import_hls.default.isSupported()) {
|
|
83
104
|
this.hls = new import_hls.default(this.hlsConfig);
|
|
105
|
+
this.hls.on(import_hls.default.Events.MEDIA_ATTACHED, () => {
|
|
106
|
+
log("hls\u4E8B\u4EF6", "\u5A92\u4F53\u6210\u529F\u9644\u52A0\u5230\u64AD\u653E\u5668");
|
|
107
|
+
});
|
|
84
108
|
this.hls.on(import_hls.default.Events.ERROR, (_event, data) => {
|
|
109
|
+
log("hls\u4E8B\u4EF6", `${data.fatal ? "\u81F4\u547D" : ""}\u9519\u8BEF: ${data.type} - ${data.details}`);
|
|
85
110
|
if (data.fatal) {
|
|
86
111
|
switch (data.type) {
|
|
87
112
|
case import_hls.default.ErrorTypes.NETWORK_ERROR:
|
|
@@ -97,8 +122,17 @@ var MediaController = class {
|
|
|
97
122
|
}
|
|
98
123
|
});
|
|
99
124
|
this.hls.on(import_hls.default.Events.MANIFEST_PARSED, () => {
|
|
125
|
+
log("hls\u4E8B\u4EF6", "\u64AD\u653E\u6E05\u5355\u89E3\u6790\u5B8C\u6BD5");
|
|
100
126
|
this.notifyTracks();
|
|
101
127
|
});
|
|
128
|
+
this.hls.on(import_hls.default.Events.BUFFER_CREATED, () => {
|
|
129
|
+
log("hls\u4E8B\u4EF6", "\u7F13\u51B2\u533A\u521B\u5EFA");
|
|
130
|
+
});
|
|
131
|
+
this.hls.on(import_hls.default.Events.LEVEL_SWITCHED, (_event, data) => {
|
|
132
|
+
const level = this.hls?.levels[data.level];
|
|
133
|
+
const label = level?.height ? `${level.height}P` : `Level ${data.level}`;
|
|
134
|
+
log("hls\u4E8B\u4EF6", `\u753B\u8D28\u6863\u4F4D\u5207\u6362: ${label}`);
|
|
135
|
+
});
|
|
102
136
|
this.hls.loadSource(src);
|
|
103
137
|
this.hls.attachMedia(this.video);
|
|
104
138
|
return;
|
|
@@ -633,33 +667,37 @@ function createVolumeControl(opts) {
|
|
|
633
667
|
|
|
634
668
|
// src/ui/controls.ts
|
|
635
669
|
function createControls(ctx) {
|
|
636
|
-
const { video, actions, i18n } = ctx;
|
|
670
|
+
const { video, actions, i18n, hidden } = ctx;
|
|
671
|
+
const show = (name) => !hidden.has(name);
|
|
637
672
|
const topEl = createEl("div", { className: "sp-top" });
|
|
638
|
-
|
|
639
|
-
titleEl.addEventListener("click", actions.onTitleClick);
|
|
673
|
+
if (show("title")) createEl("div", { className: "sp-title", text: ctx.title, parent: topEl });
|
|
640
674
|
const bottomEl = createEl("div", { className: "sp-bottom" });
|
|
641
675
|
const progress = createProgressBar(video, (t) => {
|
|
642
676
|
video.currentTime = t;
|
|
643
677
|
});
|
|
644
|
-
bottomEl.appendChild(progress.el);
|
|
678
|
+
if (show("progress")) bottomEl.appendChild(progress.el);
|
|
645
679
|
const row = createEl("div", { className: "sp-controls", parent: bottomEl });
|
|
646
|
-
const button = (html, title, onClick, disabled = false) => {
|
|
680
|
+
const button = (html, title, onClick, disabled = false, visible = true) => {
|
|
647
681
|
const btn = createEl("button", {
|
|
648
682
|
className: "sp-btn",
|
|
649
683
|
html,
|
|
650
684
|
attrs: { type: "button", title, "aria-label": title },
|
|
651
|
-
parent: row
|
|
685
|
+
parent: visible ? row : void 0
|
|
652
686
|
});
|
|
653
687
|
btn.disabled = disabled;
|
|
654
688
|
btn.addEventListener("click", onClick);
|
|
655
689
|
return btn;
|
|
656
690
|
};
|
|
657
|
-
button(icons.prev, i18n.t("prev"), () => actions.onPrev?.(), !actions.onPrev);
|
|
658
|
-
button(icons.seekBack, i18n.t("seekBack", { n: ctx.seekStep }), () => actions.seekBy(-ctx.seekStep));
|
|
659
|
-
const playBtn = button(icons.play, i18n.t("playPause"), actions.togglePlay);
|
|
660
|
-
button(icons.seekForward, i18n.t("seekForward", { n: ctx.seekStep }), () => actions.seekBy(ctx.seekStep));
|
|
661
|
-
button(icons.next, i18n.t("next"), () => actions.onNext?.(), !actions.onNext);
|
|
662
|
-
const timeEl = createEl("span", {
|
|
691
|
+
button(icons.prev, i18n.t("prev"), () => actions.onPrev?.(), !actions.onPrev, show("prev"));
|
|
692
|
+
button(icons.seekBack, i18n.t("seekBack", { n: ctx.seekStep }), () => actions.seekBy(-ctx.seekStep), false, show("seekBack"));
|
|
693
|
+
const playBtn = button(icons.play, i18n.t("playPause"), actions.togglePlay, false, show("play"));
|
|
694
|
+
button(icons.seekForward, i18n.t("seekForward", { n: ctx.seekStep }), () => actions.seekBy(ctx.seekStep), false, show("seekForward"));
|
|
695
|
+
button(icons.next, i18n.t("next"), () => actions.onNext?.(), !actions.onNext, show("next"));
|
|
696
|
+
const timeEl = createEl("span", {
|
|
697
|
+
className: "sp-time",
|
|
698
|
+
text: "0:00 / 0:00",
|
|
699
|
+
parent: show("time") ? row : void 0
|
|
700
|
+
});
|
|
663
701
|
createEl("div", { className: "sp-controls-spacer", parent: row });
|
|
664
702
|
const rateMenu = createPopupMenu({
|
|
665
703
|
buttonHtml: "1x",
|
|
@@ -669,14 +707,14 @@ function createControls(ctx) {
|
|
|
669
707
|
});
|
|
670
708
|
rateMenu.setItems(ctx.playbackRates.map((r) => ({ label: `${r}x`, value: r })));
|
|
671
709
|
rateMenu.setActive(video.playbackRate);
|
|
672
|
-
row.appendChild(rateMenu.el);
|
|
710
|
+
if (show("rate")) row.appendChild(rateMenu.el);
|
|
673
711
|
const qualityMenu = createPopupMenu({
|
|
674
712
|
buttonHtml: i18n.t("quality"),
|
|
675
713
|
title: i18n.t("quality"),
|
|
676
714
|
emptyText: i18n.t("empty"),
|
|
677
715
|
onSelect: (item) => actions.selectQuality(item.value)
|
|
678
716
|
});
|
|
679
|
-
row.appendChild(qualityMenu.el);
|
|
717
|
+
if (show("quality")) row.appendChild(qualityMenu.el);
|
|
680
718
|
const ratioMenu = createPopupMenu({
|
|
681
719
|
buttonHtml: i18n.t("aspectRatio"),
|
|
682
720
|
title: i18n.t("aspectRatio"),
|
|
@@ -687,24 +725,24 @@ function createControls(ctx) {
|
|
|
687
725
|
ctx.aspectRatios.map((r) => ({ label: r === "original" ? i18n.t("ratioOriginal") : r, value: r }))
|
|
688
726
|
);
|
|
689
727
|
ratioMenu.setActive("original");
|
|
690
|
-
row.appendChild(ratioMenu.el);
|
|
728
|
+
if (show("ratio")) row.appendChild(ratioMenu.el);
|
|
691
729
|
const audioMenu = createPopupMenu({
|
|
692
730
|
buttonHtml: icons.audio,
|
|
693
731
|
title: i18n.t("audioTrack"),
|
|
694
732
|
emptyText: i18n.t("empty"),
|
|
695
733
|
onSelect: (item) => actions.selectAudioTrack(item.value)
|
|
696
734
|
});
|
|
697
|
-
row.appendChild(audioMenu.el);
|
|
735
|
+
if (show("audioTrack")) row.appendChild(audioMenu.el);
|
|
698
736
|
const volume = createVolumeControl({
|
|
699
737
|
muteTitle: i18n.t("mute"),
|
|
700
738
|
onVolumeChange: actions.setVolume,
|
|
701
739
|
onToggleMute: actions.toggleMute
|
|
702
740
|
});
|
|
703
|
-
row.appendChild(volume.el);
|
|
704
|
-
if (document.pictureInPictureEnabled) {
|
|
741
|
+
if (show("volume")) row.appendChild(volume.el);
|
|
742
|
+
if (show("pip") && document.pictureInPictureEnabled) {
|
|
705
743
|
button(icons.pip, i18n.t("pip"), actions.togglePip);
|
|
706
744
|
}
|
|
707
|
-
const fsBtn = button(icons.fullscreen, i18n.t("fullscreen"), actions.toggleFullscreen);
|
|
745
|
+
const fsBtn = button(icons.fullscreen, i18n.t("fullscreen"), actions.toggleFullscreen, false, show("fullscreen"));
|
|
708
746
|
return {
|
|
709
747
|
topEl,
|
|
710
748
|
bottomEl,
|
|
@@ -762,6 +800,94 @@ function createOsd() {
|
|
|
762
800
|
};
|
|
763
801
|
}
|
|
764
802
|
|
|
803
|
+
// src/ui/components/contextMenu.ts
|
|
804
|
+
function createContextMenu(container, items) {
|
|
805
|
+
const menu = createEl("div", { className: "sp-context-menu", parent: container });
|
|
806
|
+
for (const item of items) {
|
|
807
|
+
const btn = createEl("button", {
|
|
808
|
+
className: "sp-context-item",
|
|
809
|
+
text: item.label,
|
|
810
|
+
attrs: { type: "button" },
|
|
811
|
+
parent: menu
|
|
812
|
+
});
|
|
813
|
+
btn.addEventListener("click", () => {
|
|
814
|
+
hide();
|
|
815
|
+
item.onClick();
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
function show(clientX, clientY) {
|
|
819
|
+
menu.classList.add("sp-visible");
|
|
820
|
+
const rect = container.getBoundingClientRect();
|
|
821
|
+
const x = Math.max(4, Math.min(clientX - rect.left, rect.width - menu.offsetWidth - 4));
|
|
822
|
+
const y = Math.max(4, Math.min(clientY - rect.top, rect.height - menu.offsetHeight - 4));
|
|
823
|
+
menu.style.left = `${x}px`;
|
|
824
|
+
menu.style.top = `${y}px`;
|
|
825
|
+
}
|
|
826
|
+
function hide() {
|
|
827
|
+
menu.classList.remove("sp-visible");
|
|
828
|
+
}
|
|
829
|
+
const onContextMenu = (e) => {
|
|
830
|
+
e.preventDefault();
|
|
831
|
+
show(e.clientX, e.clientY);
|
|
832
|
+
};
|
|
833
|
+
const onPointerDown = (e) => {
|
|
834
|
+
if (!menu.contains(e.target)) hide();
|
|
835
|
+
};
|
|
836
|
+
container.addEventListener("contextmenu", onContextMenu);
|
|
837
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
838
|
+
return {
|
|
839
|
+
hide,
|
|
840
|
+
destroy() {
|
|
841
|
+
container.removeEventListener("contextmenu", onContextMenu);
|
|
842
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
843
|
+
menu.remove();
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// src/ui/components/shortcutsOverlay.ts
|
|
849
|
+
function createShortcutsOverlay(container, i18n, seekStep) {
|
|
850
|
+
let root = null;
|
|
851
|
+
function show() {
|
|
852
|
+
if (root) return;
|
|
853
|
+
root = createEl("div", { className: "sp-shortcuts", parent: container });
|
|
854
|
+
const closeBtn = createEl("button", {
|
|
855
|
+
className: "sp-shortcuts-close",
|
|
856
|
+
text: "\u2715",
|
|
857
|
+
attrs: { type: "button", "aria-label": i18n.t("close") },
|
|
858
|
+
parent: root
|
|
859
|
+
});
|
|
860
|
+
closeBtn.addEventListener("click", hide);
|
|
861
|
+
createEl("div", { className: "sp-shortcuts-title", text: i18n.t("shortcuts"), parent: root });
|
|
862
|
+
const dl = createEl("dl", { className: "sp-shortcuts-body", parent: root });
|
|
863
|
+
const rows = [
|
|
864
|
+
[i18n.t("scKeySpace"), i18n.t("scPlayPause")],
|
|
865
|
+
["\u2190 / \u2192", i18n.t("scSeek", { n: seekStep })],
|
|
866
|
+
[i18n.t("scKeyHold"), i18n.t("scLongSeek")],
|
|
867
|
+
["\u2191 / \u2193", i18n.t("scVolume")],
|
|
868
|
+
["F", i18n.t("scFullscreen")],
|
|
869
|
+
["M", i18n.t("scMute")]
|
|
870
|
+
];
|
|
871
|
+
for (const [key, action] of rows) {
|
|
872
|
+
const row = createEl("div", { className: "sp-shortcuts-row", parent: dl });
|
|
873
|
+
const dt = createEl("dt", { parent: row });
|
|
874
|
+
createEl("span", { className: "sp-kbd", text: key, parent: dt });
|
|
875
|
+
createEl("dd", { text: action, parent: row });
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function hide() {
|
|
879
|
+
root?.remove();
|
|
880
|
+
root = null;
|
|
881
|
+
}
|
|
882
|
+
return {
|
|
883
|
+
toggle() {
|
|
884
|
+
root ? hide() : show();
|
|
885
|
+
},
|
|
886
|
+
hide,
|
|
887
|
+
destroy: hide
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
|
|
765
891
|
// src/ui/components/statsOverlay.ts
|
|
766
892
|
function createStatsOverlay(container, video, media, i18n) {
|
|
767
893
|
let root = null;
|
|
@@ -800,11 +926,31 @@ function createStatsOverlay(container, video, media, i18n) {
|
|
|
800
926
|
function show() {
|
|
801
927
|
if (root) return;
|
|
802
928
|
root = createEl("div", { className: "sp-stats", parent: container });
|
|
929
|
+
const header = createEl("div", { className: "sp-stats-header", parent: root });
|
|
930
|
+
const copyBtn = createEl("button", {
|
|
931
|
+
className: "sp-stats-copy",
|
|
932
|
+
text: i18n.t("copyLog"),
|
|
933
|
+
attrs: { type: "button" },
|
|
934
|
+
parent: header
|
|
935
|
+
});
|
|
936
|
+
copyBtn.addEventListener("click", () => {
|
|
937
|
+
const text = getLogText();
|
|
938
|
+
void navigator.clipboard.writeText(text || "(empty)").then(
|
|
939
|
+
() => {
|
|
940
|
+
copyBtn.textContent = i18n.t("logCopied");
|
|
941
|
+
setTimeout(() => {
|
|
942
|
+
copyBtn.textContent = i18n.t("copyLog");
|
|
943
|
+
}, 1500);
|
|
944
|
+
},
|
|
945
|
+
() => {
|
|
946
|
+
}
|
|
947
|
+
);
|
|
948
|
+
});
|
|
803
949
|
const closeBtn = createEl("button", {
|
|
804
950
|
className: "sp-stats-close",
|
|
805
951
|
text: "\u2715",
|
|
806
952
|
attrs: { type: "button", "aria-label": i18n.t("closeStats") },
|
|
807
|
-
parent:
|
|
953
|
+
parent: header
|
|
808
954
|
});
|
|
809
955
|
closeBtn.addEventListener("click", hide);
|
|
810
956
|
createEl("dl", { className: "sp-stats-body", parent: root });
|
|
@@ -969,7 +1115,21 @@ var zhCN = {
|
|
|
969
1115
|
autoNextIn: "{n} \u79D2\u540E\u64AD\u653E\u4E0B\u4E00\u4E2A",
|
|
970
1116
|
cancel: "\u53D6\u6D88",
|
|
971
1117
|
loadError: "\u89C6\u9891\u52A0\u8F7D\u5931\u8D25",
|
|
972
|
-
retry: "\u91CD\u8BD5"
|
|
1118
|
+
retry: "\u91CD\u8BD5",
|
|
1119
|
+
changelog: "\u66F4\u65B0\u8BB0\u5F55",
|
|
1120
|
+
videoInfo: "\u89C6\u9891\u4FE1\u606F",
|
|
1121
|
+
shortcuts: "\u5FEB\u6377\u952E",
|
|
1122
|
+
close: "\u5173\u95ED",
|
|
1123
|
+
scKeySpace: "\u7A7A\u683C",
|
|
1124
|
+
scKeyHold: "\u2190 / \u2192 \u957F\u6309",
|
|
1125
|
+
scPlayPause: "\u64AD\u653E / \u6682\u505C",
|
|
1126
|
+
scSeek: "\u5FEB\u9000 / \u5FEB\u8FDB {n} \u79D2",
|
|
1127
|
+
scLongSeek: "\u9636\u68AF\u7D2F\u8BA1\u5FEB\u8FDB\u5FEB\u9000\uFF0C\u677E\u5F00\u6267\u884C",
|
|
1128
|
+
scVolume: "\u97F3\u91CF \xB15",
|
|
1129
|
+
scFullscreen: "\u5168\u5C4F\u5207\u6362",
|
|
1130
|
+
scMute: "\u9759\u97F3\u5207\u6362",
|
|
1131
|
+
copyLog: "\u590D\u5236\u65E5\u5FD7",
|
|
1132
|
+
logCopied: "\u5DF2\u590D\u5236"
|
|
973
1133
|
};
|
|
974
1134
|
var en = {
|
|
975
1135
|
play: "Play",
|
|
@@ -998,7 +1158,21 @@ var en = {
|
|
|
998
1158
|
autoNextIn: "Playing next in {n}s",
|
|
999
1159
|
cancel: "Cancel",
|
|
1000
1160
|
loadError: "Failed to load video",
|
|
1001
|
-
retry: "Retry"
|
|
1161
|
+
retry: "Retry",
|
|
1162
|
+
changelog: "Changelog",
|
|
1163
|
+
videoInfo: "Video info",
|
|
1164
|
+
shortcuts: "Shortcuts",
|
|
1165
|
+
close: "Close",
|
|
1166
|
+
scKeySpace: "Space",
|
|
1167
|
+
scKeyHold: "Hold \u2190 / \u2192",
|
|
1168
|
+
scPlayPause: "Play / Pause",
|
|
1169
|
+
scSeek: "Rewind / forward {n}s",
|
|
1170
|
+
scLongSeek: "Accelerating seek, release to apply",
|
|
1171
|
+
scVolume: "Volume \xB15",
|
|
1172
|
+
scFullscreen: "Toggle fullscreen",
|
|
1173
|
+
scMute: "Toggle mute",
|
|
1174
|
+
copyLog: "Copy log",
|
|
1175
|
+
logCopied: "Copied"
|
|
1002
1176
|
};
|
|
1003
1177
|
var locales = { "zh-CN": zhCN, en };
|
|
1004
1178
|
var I18n = class {
|
|
@@ -1071,8 +1245,11 @@ function clearProgress(id) {
|
|
|
1071
1245
|
}
|
|
1072
1246
|
}
|
|
1073
1247
|
|
|
1248
|
+
// src/version.ts
|
|
1249
|
+
var VERSION = true ? "0.3.0" : "dev";
|
|
1250
|
+
|
|
1074
1251
|
// src/styles/player.css
|
|
1075
|
-
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";
|
|
1252
|
+
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";
|
|
1076
1253
|
|
|
1077
1254
|
// src/player.ts
|
|
1078
1255
|
var DEFAULT_RATES = [0.5, 1, 1.5, 2];
|
|
@@ -1081,19 +1258,17 @@ var DEFAULT_LONG_SEEK_STEPS = [10, 30, 60];
|
|
|
1081
1258
|
var DEFAULT_STEP_UP_INTERVAL = 2e3;
|
|
1082
1259
|
var DEFAULT_AUTO_NEXT_SECONDS = 5;
|
|
1083
1260
|
var CONTROLS_HIDE_DELAY = 3e3;
|
|
1084
|
-
var TITLE_CLICK_COUNT = 10;
|
|
1085
|
-
var TITLE_CLICK_WINDOW = 1500;
|
|
1086
1261
|
var SINGLE_CLICK_DELAY = 250;
|
|
1262
|
+
var NPM_URL = "https://www.npmjs.com/package/@sweet-player/core";
|
|
1087
1263
|
var PROGRESS_SAVE_INTERVAL = 5e3;
|
|
1088
1264
|
var PROGRESS_END_GUARD = 10;
|
|
1089
1265
|
var SweetPlayer = class {
|
|
1090
1266
|
constructor(options) {
|
|
1091
1267
|
this.emitter = new EventEmitter();
|
|
1268
|
+
this.contextMenu = null;
|
|
1092
1269
|
this.hideTimer = null;
|
|
1093
1270
|
this.clickTimer = null;
|
|
1094
1271
|
this.progressTimer = null;
|
|
1095
|
-
this.titleClicks = 0;
|
|
1096
|
-
this.lastTitleClick = 0;
|
|
1097
1272
|
this.currentRatio = "original";
|
|
1098
1273
|
/** 画质/音轨菜单当前是否由 hls.js 自动接管(业务 setQualities 会关闭) */
|
|
1099
1274
|
this.hlsManagedQuality = false;
|
|
@@ -1126,9 +1301,27 @@ var SweetPlayer = class {
|
|
|
1126
1301
|
onLevels: autoQuality && !options.qualities?.length ? (levels) => this.applyHlsLevels(levels) : void 0,
|
|
1127
1302
|
onAudioTracks: autoQuality && !options.audioTracks?.length ? (tracks) => this.applyHlsAudioTracks(tracks) : void 0
|
|
1128
1303
|
});
|
|
1304
|
+
const hidden = new Set(options.hiddenControls ?? []);
|
|
1129
1305
|
this.osd = createOsd();
|
|
1130
1306
|
this.stats = createStatsOverlay(this.container, this.video, this.media, this.i18n);
|
|
1307
|
+
this.shortcutsPanel = createShortcutsOverlay(this.container, this.i18n, options.seekStep ?? 10);
|
|
1131
1308
|
this.state = createStateOverlay(this.i18n);
|
|
1309
|
+
if (!hidden.has("contextMenu")) {
|
|
1310
|
+
this.contextMenu = createContextMenu(this.container, [
|
|
1311
|
+
{
|
|
1312
|
+
label: `${this.i18n.t("changelog")}: v${VERSION}`,
|
|
1313
|
+
onClick: () => window.open(NPM_URL, "_blank", "noopener")
|
|
1314
|
+
},
|
|
1315
|
+
{
|
|
1316
|
+
label: this.i18n.t("videoInfo"),
|
|
1317
|
+
onClick: () => this.stats.toggle()
|
|
1318
|
+
},
|
|
1319
|
+
{
|
|
1320
|
+
label: this.i18n.t("shortcuts"),
|
|
1321
|
+
onClick: () => this.shortcutsPanel.toggle()
|
|
1322
|
+
}
|
|
1323
|
+
]);
|
|
1324
|
+
}
|
|
1132
1325
|
this.controls = createControls({
|
|
1133
1326
|
video: this.video,
|
|
1134
1327
|
title: options.title ?? "",
|
|
@@ -1136,6 +1329,7 @@ var SweetPlayer = class {
|
|
|
1136
1329
|
playbackRates: options.playbackRates ?? DEFAULT_RATES,
|
|
1137
1330
|
aspectRatios: options.aspectRatios ?? DEFAULT_RATIOS,
|
|
1138
1331
|
seekStep: options.seekStep ?? 10,
|
|
1332
|
+
hidden,
|
|
1139
1333
|
actions: {
|
|
1140
1334
|
togglePlay: () => this.toggle(),
|
|
1141
1335
|
seekBy: (d) => this.seekBy(d),
|
|
@@ -1148,8 +1342,7 @@ var SweetPlayer = class {
|
|
|
1148
1342
|
selectQuality: (q) => this.handleQualitySelect(q),
|
|
1149
1343
|
selectAudioTrack: (t) => this.handleAudioTrackSelect(t),
|
|
1150
1344
|
onPrev: options.onPrev,
|
|
1151
|
-
onNext: options.onNext
|
|
1152
|
-
onTitleClick: () => this.handleTitleClick()
|
|
1345
|
+
onNext: options.onNext
|
|
1153
1346
|
}
|
|
1154
1347
|
});
|
|
1155
1348
|
this.container.appendChild(this.controls.topEl);
|
|
@@ -1205,6 +1398,7 @@ var SweetPlayer = class {
|
|
|
1205
1398
|
this.bindActivityTracking();
|
|
1206
1399
|
this.disposers.push(
|
|
1207
1400
|
onFullscreenChange(this.container, (fs) => {
|
|
1401
|
+
log("\u64AD\u653E\u5668", fs ? "\u8FDB\u5165\u5168\u5C4F" : "\u9000\u51FA\u5168\u5C4F");
|
|
1208
1402
|
this.controls.updateFullscreen(fs);
|
|
1209
1403
|
this.emitter.emit("fullscreenchange", fs);
|
|
1210
1404
|
})
|
|
@@ -1240,6 +1434,7 @@ var SweetPlayer = class {
|
|
|
1240
1434
|
if (showOsd) this.osd.flash(`${delta > 0 ? "+" : ""}${delta} ${this.i18n.t("seconds")}`);
|
|
1241
1435
|
}
|
|
1242
1436
|
setRate(rate) {
|
|
1437
|
+
log("\u64AD\u653E\u5668", `\u500D\u901F\u5207\u6362: ${rate}x`);
|
|
1243
1438
|
this.video.playbackRate = rate;
|
|
1244
1439
|
}
|
|
1245
1440
|
/** 0-100 */
|
|
@@ -1326,7 +1521,9 @@ var SweetPlayer = class {
|
|
|
1326
1521
|
this.keyboard.destroy();
|
|
1327
1522
|
this.gestures.destroy();
|
|
1328
1523
|
this.stats.destroy();
|
|
1524
|
+
this.shortcutsPanel.destroy();
|
|
1329
1525
|
this.state.destroy();
|
|
1526
|
+
this.contextMenu?.destroy();
|
|
1330
1527
|
this.controls.destroy();
|
|
1331
1528
|
this.disposers.forEach((d) => d());
|
|
1332
1529
|
if (this.hideTimer) clearTimeout(this.hideTimer);
|
|
@@ -1349,6 +1546,7 @@ var SweetPlayer = class {
|
|
|
1349
1546
|
this.hlsManagedAudio = true;
|
|
1350
1547
|
}
|
|
1351
1548
|
handleQualitySelect(quality) {
|
|
1549
|
+
log("\u64AD\u653E\u5668", `\u5207\u6362\u753B\u8D28: ${quality.label}`);
|
|
1352
1550
|
this.controls.qualityMenu.setActive(quality);
|
|
1353
1551
|
if (this.hlsManagedQuality && typeof quality.value === "number") {
|
|
1354
1552
|
this.media.setLevel(quality.value);
|
|
@@ -1361,6 +1559,7 @@ var SweetPlayer = class {
|
|
|
1361
1559
|
this.emitter.emit("qualitychange", quality);
|
|
1362
1560
|
}
|
|
1363
1561
|
handleAudioTrackSelect(track) {
|
|
1562
|
+
log("\u64AD\u653E\u5668", `\u5207\u6362\u97F3\u8F68: ${track.label}`);
|
|
1364
1563
|
this.controls.audioMenu.setActive(track);
|
|
1365
1564
|
if (this.hlsManagedAudio && typeof track.value === "number") {
|
|
1366
1565
|
this.media.setAudioTrack(track.value);
|
|
@@ -1401,15 +1600,6 @@ var SweetPlayer = class {
|
|
|
1401
1600
|
}
|
|
1402
1601
|
}
|
|
1403
1602
|
// ---------- 内部:交互 ----------
|
|
1404
|
-
handleTitleClick() {
|
|
1405
|
-
const now = Date.now();
|
|
1406
|
-
this.titleClicks = now - this.lastTitleClick <= TITLE_CLICK_WINDOW ? this.titleClicks + 1 : 1;
|
|
1407
|
-
this.lastTitleClick = now;
|
|
1408
|
-
if (this.titleClicks >= TITLE_CLICK_COUNT) {
|
|
1409
|
-
this.titleClicks = 0;
|
|
1410
|
-
this.stats.toggle();
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
1603
|
bindMediaEvents() {
|
|
1414
1604
|
const v = this.video;
|
|
1415
1605
|
const listen = (event, fn) => {
|
|
@@ -1417,6 +1607,7 @@ var SweetPlayer = class {
|
|
|
1417
1607
|
this.disposers.push(() => v.removeEventListener(event, fn));
|
|
1418
1608
|
};
|
|
1419
1609
|
listen("loadedmetadata", () => {
|
|
1610
|
+
log("\u539F\u751F\u4E8B\u4EF6", `\u83B7\u53D6\u5143\u6570\u636E (${v.videoWidth}x${v.videoHeight})`);
|
|
1420
1611
|
this.controls.updateTime();
|
|
1421
1612
|
this.emitter.emit("ready", void 0);
|
|
1422
1613
|
});
|
|
@@ -1432,6 +1623,7 @@ var SweetPlayer = class {
|
|
|
1432
1623
|
this.emitter.emit("pause", void 0);
|
|
1433
1624
|
});
|
|
1434
1625
|
listen("ended", () => {
|
|
1626
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u64AD\u653E\u7ED3\u675F");
|
|
1435
1627
|
this.saveProgressNow();
|
|
1436
1628
|
this.showControls();
|
|
1437
1629
|
const onNext = this.options.onNext;
|
|
@@ -1461,23 +1653,47 @@ var SweetPlayer = class {
|
|
|
1461
1653
|
this.controls.volume.update(volume, v.muted);
|
|
1462
1654
|
this.emitter.emit("volumechange", { volume, muted: v.muted });
|
|
1463
1655
|
});
|
|
1464
|
-
listen("waiting", () =>
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1656
|
+
listen("waiting", () => {
|
|
1657
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u7B49\u5F85\u7F13\u51B2\u4E2D");
|
|
1658
|
+
this.state.showLoading();
|
|
1659
|
+
});
|
|
1660
|
+
listen("stalled", () => {
|
|
1661
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u6570\u636E\u52A0\u8F7D\u505C\u6EDE");
|
|
1662
|
+
this.state.showLoading();
|
|
1663
|
+
});
|
|
1664
|
+
listen("seeking", () => {
|
|
1665
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u5F00\u59CB\u8DF3\u8F6C");
|
|
1666
|
+
this.state.showLoading();
|
|
1667
|
+
});
|
|
1668
|
+
listen("canplay", () => {
|
|
1669
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u51C6\u5907\u597D\u5F00\u59CB\u64AD\u653E");
|
|
1670
|
+
this.state.hideLoading();
|
|
1671
|
+
});
|
|
1468
1672
|
listen("playing", () => {
|
|
1469
1673
|
this.state.hideLoading();
|
|
1470
1674
|
this.state.hideError();
|
|
1471
1675
|
});
|
|
1472
|
-
listen("seeked", () =>
|
|
1473
|
-
|
|
1676
|
+
listen("seeked", () => {
|
|
1677
|
+
log("\u539F\u751F\u4E8B\u4EF6", "\u8DF3\u8F6C\u5B8C\u6210");
|
|
1678
|
+
this.state.hideLoading();
|
|
1679
|
+
});
|
|
1680
|
+
listen("error", () => {
|
|
1681
|
+
log("\u539F\u751F\u4E8B\u4EF6", `\u64AD\u653E\u9519\u8BEF: ${v.error?.message ?? "\u672A\u77E5\u9519\u8BEF"}`);
|
|
1682
|
+
this.showErrorState({ type: "media", detail: v.error });
|
|
1683
|
+
});
|
|
1474
1684
|
this.disposers.push(
|
|
1475
1685
|
this.emitter.on("error", (payload) => {
|
|
1476
1686
|
if (payload.type.startsWith("hls-")) this.showErrorStateUi();
|
|
1477
1687
|
})
|
|
1478
1688
|
);
|
|
1479
|
-
listen("enterpictureinpicture", () =>
|
|
1480
|
-
|
|
1689
|
+
listen("enterpictureinpicture", () => {
|
|
1690
|
+
log("\u64AD\u653E\u5668", "\u8FDB\u5165\u753B\u4E2D\u753B");
|
|
1691
|
+
this.emitter.emit("pipchange", true);
|
|
1692
|
+
});
|
|
1693
|
+
listen("leavepictureinpicture", () => {
|
|
1694
|
+
log("\u64AD\u653E\u5668", "\u9000\u51FA\u753B\u4E2D\u753B");
|
|
1695
|
+
this.emitter.emit("pipchange", false);
|
|
1696
|
+
});
|
|
1481
1697
|
const onVideoClick = (e) => {
|
|
1482
1698
|
if (e.target !== this.video || e.pointerType === "touch") return;
|
|
1483
1699
|
if (this.clickTimer) return;
|
|
@@ -1544,8 +1760,10 @@ var SweetPlayer = class {
|
|
|
1544
1760
|
});
|
|
1545
1761
|
}
|
|
1546
1762
|
};
|
|
1763
|
+
SweetPlayer.version = VERSION;
|
|
1547
1764
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1548
1765
|
0 && (module.exports = {
|
|
1549
1766
|
SweetPlayer,
|
|
1767
|
+
VERSION,
|
|
1550
1768
|
registerLocale
|
|
1551
1769
|
});
|