@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/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
- const titleEl = createEl("div", { className: "sp-title", text: ctx.title, parent: topEl });
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", { className: "sp-time", text: "0:00 / 0:00", parent: row });
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: root
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", () => this.state.showLoading());
1428
- listen("stalled", () => this.state.showLoading());
1429
- listen("seeking", () => this.state.showLoading());
1430
- listen("canplay", () => this.state.hideLoading());
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", () => this.state.hideLoading());
1436
- listen("error", () => this.showErrorState({ type: "media", detail: v.error }));
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", () => this.emitter.emit("pipchange", true));
1443
- listen("leavepictureinpicture", () => this.emitter.emit("pipchange", false));
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
  };