@sweet-player/core 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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;
@@ -461,69 +495,14 @@ var icons = {
461
495
  ),
462
496
  audio: createSvgIcon(
463
497
  "M12 3a9 9 0 0 0-9 9v7c0 1.1.9 2 2 2h4v-8H5v-1c0-3.87 3.13-7 7-7s7 3.13 7 7v1h-4v8h4c1.1 0 2-.9 2-2v-7a9 9 0 0 0-9-9z"
464
- )
498
+ ),
499
+ settings: createSvgIcon(
500
+ "M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65a.5.5 0 0 0 .12-.64l-2-3.46a.5.5 0 0 0-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65A.49.49 0 0 0 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1a.49.49 0 0 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65a.5.5 0 0 0-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46a.5.5 0 0 0-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"
501
+ ),
502
+ chevronRight: createSvgIcon("M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"),
503
+ chevronLeft: createSvgIcon("M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z")
465
504
  };
466
505
 
467
- // src/ui/components/menu.ts
468
- function createPopupMenu(opts) {
469
- const root = createEl("div", { className: "sp-menu" });
470
- const btn = createEl("button", {
471
- className: "sp-btn sp-menu-btn",
472
- html: opts.buttonHtml,
473
- attrs: { type: "button", title: opts.title, "aria-label": opts.title },
474
- parent: root
475
- });
476
- const panel = createEl("div", { className: "sp-menu-panel", parent: root });
477
- let items = [];
478
- let activeValue;
479
- function render() {
480
- panel.innerHTML = "";
481
- if (items.length === 0) {
482
- createEl("div", { className: "sp-menu-empty", text: opts.emptyText ?? "-", parent: panel });
483
- return;
484
- }
485
- for (const item of items) {
486
- const itemBtn = createEl("button", {
487
- className: "sp-menu-item" + (item.value === activeValue ? " sp-active" : ""),
488
- text: item.label,
489
- attrs: { type: "button" },
490
- parent: panel
491
- });
492
- itemBtn.addEventListener("click", (e) => {
493
- e.stopPropagation();
494
- close();
495
- opts.onSelect(item);
496
- });
497
- }
498
- }
499
- function close() {
500
- root.classList.remove("sp-open");
501
- }
502
- btn.addEventListener("click", (e) => {
503
- e.stopPropagation();
504
- const willOpen = !root.classList.contains("sp-open");
505
- root.closest(".sp-controls")?.querySelectorAll(".sp-menu.sp-open").forEach((m) => m.classList.remove("sp-open"));
506
- root.classList.toggle("sp-open", willOpen);
507
- });
508
- document.addEventListener("click", close);
509
- render();
510
- return {
511
- el: root,
512
- setItems(next) {
513
- items = next;
514
- render();
515
- },
516
- setActive(value) {
517
- activeValue = value;
518
- render();
519
- },
520
- setButtonContent(html) {
521
- btn.innerHTML = html;
522
- },
523
- close
524
- };
525
- }
526
-
527
506
  // src/ui/components/progressBar.ts
528
507
  function createProgressBar(video, onSeek) {
529
508
  const root = createEl("div", { className: "sp-progress" });
@@ -631,87 +610,299 @@ function createVolumeControl(opts) {
631
610
  };
632
611
  }
633
612
 
613
+ // src/ui/components/settingsPanel.ts
614
+ function createSettingsPanel(opts) {
615
+ const root = createEl("div", { className: "sp-menu sp-settings" });
616
+ const btn = createEl("button", {
617
+ className: "sp-btn",
618
+ html: icons.settings,
619
+ attrs: { type: "button", title: opts.buttonTitle, "aria-label": opts.buttonTitle },
620
+ parent: root
621
+ });
622
+ const panel = createEl("div", { className: "sp-settings-panel", parent: root });
623
+ const mainView = createEl("div", { className: "sp-settings-main", parent: panel });
624
+ const subView = createEl("div", { className: "sp-settings-sub", parent: panel });
625
+ let sections = opts.sections;
626
+ let activeSub = null;
627
+ function renderMain() {
628
+ mainView.innerHTML = "";
629
+ for (const sec of sections) {
630
+ if (sec.toggle) {
631
+ const row2 = createEl("div", {
632
+ className: "sp-settings-row sp-settings-toggle-row",
633
+ attrs: { "data-key": sec.key },
634
+ parent: mainView
635
+ });
636
+ createEl("span", { className: "sp-settings-label", text: sec.label, parent: row2 });
637
+ const track = createEl("div", {
638
+ className: "sp-toggle" + (sec.toggle.checked ? " sp-on" : ""),
639
+ attrs: { role: "switch", "aria-checked": String(sec.toggle.checked), tabindex: "0" },
640
+ parent: row2
641
+ });
642
+ createEl("span", { className: "sp-toggle-thumb", parent: track });
643
+ const handler = (e) => {
644
+ e.stopPropagation();
645
+ sec.toggle.onToggle();
646
+ };
647
+ row2.addEventListener("click", handler);
648
+ track.addEventListener("keydown", (e) => {
649
+ if (e.key === "Enter" || e.key === " ") {
650
+ e.preventDefault();
651
+ handler(e);
652
+ }
653
+ });
654
+ continue;
655
+ }
656
+ if (sec.items.length === 0) continue;
657
+ const row = createEl("button", {
658
+ className: "sp-settings-row",
659
+ attrs: { type: "button", "data-key": sec.key },
660
+ parent: mainView
661
+ });
662
+ createEl("span", { className: "sp-settings-label", text: sec.label, parent: row });
663
+ createEl("span", { className: "sp-settings-value", text: sec.currentValue, parent: row });
664
+ const arrow = createEl("span", { className: "sp-settings-arrow", parent: row });
665
+ arrow.innerHTML = icons.chevronRight;
666
+ row.addEventListener("click", (e) => {
667
+ e.stopPropagation();
668
+ showSub(sec.key);
669
+ });
670
+ }
671
+ }
672
+ function renderSub(key) {
673
+ subView.innerHTML = "";
674
+ const sec = sections.find((s) => s.key === key);
675
+ if (!sec) return;
676
+ const back = createEl("button", {
677
+ className: "sp-settings-back",
678
+ attrs: { type: "button" },
679
+ parent: subView
680
+ });
681
+ const backArrow = createEl("span", { className: "sp-settings-back-arrow", parent: back });
682
+ backArrow.innerHTML = icons.chevronLeft;
683
+ createEl("span", { text: sec.label, parent: back });
684
+ back.addEventListener("click", (e) => {
685
+ e.stopPropagation();
686
+ showMain();
687
+ });
688
+ for (const item of sec.items) {
689
+ const itemBtn = createEl("button", {
690
+ className: "sp-menu-item" + (item.value === sec.activeValue ? " sp-active" : ""),
691
+ text: item.label,
692
+ attrs: { type: "button" },
693
+ parent: subView
694
+ });
695
+ itemBtn.addEventListener("click", (e) => {
696
+ e.stopPropagation();
697
+ close();
698
+ sec.onSelect(item);
699
+ });
700
+ }
701
+ }
702
+ function showSub(key) {
703
+ activeSub = key;
704
+ renderSub(key);
705
+ mainView.style.display = "none";
706
+ subView.style.display = "";
707
+ }
708
+ function showMain() {
709
+ activeSub = null;
710
+ mainView.style.display = "";
711
+ subView.style.display = "none";
712
+ }
713
+ function close() {
714
+ root.classList.remove("sp-open");
715
+ showMain();
716
+ }
717
+ btn.addEventListener("click", (e) => {
718
+ e.stopPropagation();
719
+ const willOpen = !root.classList.contains("sp-open");
720
+ root.closest(".sp-controls")?.querySelectorAll(".sp-menu.sp-open").forEach((m) => m.classList.remove("sp-open"));
721
+ if (willOpen) {
722
+ renderMain();
723
+ showMain();
724
+ }
725
+ root.classList.toggle("sp-open", willOpen);
726
+ });
727
+ const onDocClick = () => close();
728
+ document.addEventListener("click", onDocClick);
729
+ panel.addEventListener("click", (e) => e.stopPropagation());
730
+ renderMain();
731
+ showMain();
732
+ return {
733
+ el: root,
734
+ updateSection(key, patch) {
735
+ const sec = sections.find((s) => s.key === key);
736
+ if (!sec) return;
737
+ if (patch.items !== void 0) sec.items = patch.items;
738
+ if (patch.currentValue !== void 0) sec.currentValue = patch.currentValue;
739
+ if (patch.activeValue !== void 0) sec.activeValue = patch.activeValue;
740
+ if (patch.toggle) {
741
+ if (sec.toggle) {
742
+ sec.toggle.checked = patch.toggle.checked;
743
+ if (patch.toggle.onToggle) sec.toggle.onToggle = patch.toggle.onToggle;
744
+ } else {
745
+ sec.toggle = { checked: patch.toggle.checked, onToggle: patch.toggle.onToggle ?? (() => {
746
+ }) };
747
+ }
748
+ }
749
+ if (sec.toggle) {
750
+ if (!activeSub) renderMain();
751
+ return;
752
+ }
753
+ if (activeSub === key) renderSub(key);
754
+ if (!activeSub) renderMain();
755
+ },
756
+ close,
757
+ destroy() {
758
+ document.removeEventListener("click", onDocClick);
759
+ root.remove();
760
+ }
761
+ };
762
+ }
763
+
634
764
  // src/ui/controls.ts
635
765
  function createControls(ctx) {
636
- const { video, actions, i18n } = ctx;
766
+ const { video, actions, i18n, hidden } = ctx;
767
+ const show = (name) => !hidden.has(name);
637
768
  const topEl = createEl("div", { className: "sp-top" });
638
- const titleEl = createEl("div", { className: "sp-title", text: ctx.title, parent: topEl });
639
- titleEl.addEventListener("click", actions.onTitleClick);
769
+ if (show("title")) createEl("div", { className: "sp-title", text: ctx.title, parent: topEl });
640
770
  const bottomEl = createEl("div", { className: "sp-bottom" });
641
771
  const progress = createProgressBar(video, (t) => {
642
772
  video.currentTime = t;
643
773
  });
644
- bottomEl.appendChild(progress.el);
774
+ if (show("progress")) bottomEl.appendChild(progress.el);
645
775
  const row = createEl("div", { className: "sp-controls", parent: bottomEl });
646
- const button = (html, title, onClick, disabled = false) => {
776
+ const button = (html, title, onClick, disabled = false, visible = true) => {
647
777
  const btn = createEl("button", {
648
778
  className: "sp-btn",
649
779
  html,
650
780
  attrs: { type: "button", title, "aria-label": title },
651
- parent: row
781
+ parent: visible ? row : void 0
652
782
  });
653
783
  btn.disabled = disabled;
654
784
  btn.addEventListener("click", onClick);
655
785
  return btn;
656
786
  };
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", { className: "sp-time", text: "0:00 / 0:00", parent: row });
663
- createEl("div", { className: "sp-controls-spacer", parent: row });
664
- const rateMenu = createPopupMenu({
665
- buttonHtml: "1x",
666
- title: i18n.t("speed"),
667
- emptyText: i18n.t("empty"),
668
- onSelect: (item) => actions.setRate(item.value)
787
+ button(icons.prev, i18n.t("prev"), () => actions.onPrev?.(), !actions.onPrev, show("prev"));
788
+ button(icons.seekBack, i18n.t("seekBack", { n: ctx.seekStep }), () => actions.seekBy(-ctx.seekStep), false, show("seekBack"));
789
+ const playBtn = button(icons.play, i18n.t("playPause"), actions.togglePlay, false, show("play"));
790
+ button(icons.seekForward, i18n.t("seekForward", { n: ctx.seekStep }), () => actions.seekBy(ctx.seekStep), false, show("seekForward"));
791
+ button(icons.next, i18n.t("next"), () => actions.onNext?.(), !actions.onNext, show("next"));
792
+ const timeEl = createEl("span", {
793
+ className: "sp-time",
794
+ text: "0:00 / 0:00",
795
+ parent: show("time") ? row : void 0
669
796
  });
670
- rateMenu.setItems(ctx.playbackRates.map((r) => ({ label: `${r}x`, value: r })));
671
- rateMenu.setActive(video.playbackRate);
672
- row.appendChild(rateMenu.el);
673
- const qualityMenu = createPopupMenu({
674
- buttonHtml: i18n.t("quality"),
675
- title: i18n.t("quality"),
676
- emptyText: i18n.t("empty"),
677
- onSelect: (item) => actions.selectQuality(item.value)
678
- });
679
- row.appendChild(qualityMenu.el);
680
- const ratioMenu = createPopupMenu({
681
- buttonHtml: i18n.t("aspectRatio"),
682
- title: i18n.t("aspectRatio"),
683
- emptyText: i18n.t("empty"),
684
- onSelect: (item) => actions.setAspectRatio(item.value)
685
- });
686
- ratioMenu.setItems(
687
- ctx.aspectRatios.map((r) => ({ label: r === "original" ? i18n.t("ratioOriginal") : r, value: r }))
688
- );
689
- ratioMenu.setActive("original");
690
- row.appendChild(ratioMenu.el);
691
- const audioMenu = createPopupMenu({
692
- buttonHtml: icons.audio,
693
- title: i18n.t("audioTrack"),
694
- emptyText: i18n.t("empty"),
695
- onSelect: (item) => actions.selectAudioTrack(item.value)
696
- });
697
- row.appendChild(audioMenu.el);
797
+ createEl("div", { className: "sp-controls-spacer", parent: row });
698
798
  const volume = createVolumeControl({
699
799
  muteTitle: i18n.t("mute"),
700
800
  onVolumeChange: actions.setVolume,
701
801
  onToggleMute: actions.toggleMute
702
802
  });
703
- row.appendChild(volume.el);
704
- if (document.pictureInPictureEnabled) {
705
- button(icons.pip, i18n.t("pip"), actions.togglePip);
706
- }
707
- const fsBtn = button(icons.fullscreen, i18n.t("fullscreen"), actions.toggleFullscreen);
803
+ if (show("volume")) row.appendChild(volume.el);
804
+ let currentRate = video.playbackRate;
805
+ let qualityItems = [];
806
+ let activeQuality;
807
+ let audioItems = [];
808
+ let activeAudio;
809
+ const settingsPanel = createSettingsPanel({
810
+ buttonTitle: i18n.t("settings"),
811
+ sections: [
812
+ ...show("rate") ? [{
813
+ key: "rate",
814
+ label: i18n.t("speed"),
815
+ currentValue: `${currentRate}x`,
816
+ items: ctx.playbackRates.map((r) => ({ label: `${r}x`, value: r })),
817
+ activeValue: currentRate,
818
+ onSelect: (item) => actions.setRate(item.value)
819
+ }] : [],
820
+ ...show("quality") ? [{
821
+ key: "quality",
822
+ label: i18n.t("quality"),
823
+ currentValue: i18n.t("qualityAuto"),
824
+ items: qualityItems,
825
+ activeValue: activeQuality,
826
+ onSelect: (item) => actions.selectQuality(item.value)
827
+ }] : [],
828
+ ...show("ratio") ? [{
829
+ key: "ratio",
830
+ label: i18n.t("aspectRatio"),
831
+ currentValue: i18n.t("ratioOriginal"),
832
+ items: ctx.aspectRatios.map((r) => ({
833
+ label: r === "original" ? i18n.t("ratioOriginal") : r,
834
+ value: r
835
+ })),
836
+ activeValue: "original",
837
+ onSelect: (item) => actions.setAspectRatio(item.value)
838
+ }] : [],
839
+ ...show("audioTrack") ? [{
840
+ key: "audioTrack",
841
+ label: i18n.t("audioTrack"),
842
+ currentValue: "-",
843
+ items: audioItems,
844
+ activeValue: activeAudio,
845
+ onSelect: (item) => actions.selectAudioTrack(item.value)
846
+ }] : [],
847
+ ...show("pip") && document.pictureInPictureEnabled ? [{
848
+ key: "pip",
849
+ label: i18n.t("pip"),
850
+ currentValue: "",
851
+ items: [],
852
+ activeValue: void 0,
853
+ onSelect: () => {
854
+ },
855
+ toggle: {
856
+ checked: false,
857
+ onToggle: () => actions.togglePip()
858
+ }
859
+ }] : []
860
+ ]
861
+ });
862
+ if (show("settings")) row.appendChild(settingsPanel.el);
863
+ const fsBtn = button(icons.fullscreen, i18n.t("fullscreen"), actions.toggleFullscreen, false, show("fullscreen"));
864
+ const qualityMenuAdapter = {
865
+ el: document.createElement("div"),
866
+ setItems(items) {
867
+ qualityItems = items;
868
+ const cur = activeQuality ? items.find((i) => i.value === activeQuality)?.label : i18n.t("qualityAuto");
869
+ settingsPanel.updateSection("quality", { items, currentValue: cur ?? i18n.t("qualityAuto") });
870
+ },
871
+ setActive(value) {
872
+ activeQuality = value;
873
+ const label = qualityItems.find((i) => i.value === value)?.label ?? i18n.t("qualityAuto");
874
+ settingsPanel.updateSection("quality", { activeValue: value, currentValue: label });
875
+ },
876
+ setButtonContent() {
877
+ },
878
+ close() {
879
+ }
880
+ };
881
+ const audioMenuAdapter = {
882
+ el: document.createElement("div"),
883
+ setItems(items) {
884
+ audioItems = items;
885
+ const cur = activeAudio ? items.find((i) => i.value === activeAudio)?.label : "-";
886
+ settingsPanel.updateSection("audioTrack", { items, currentValue: cur ?? "-" });
887
+ },
888
+ setActive(value) {
889
+ activeAudio = value;
890
+ const label = audioItems.find((i) => i.value === value)?.label ?? "-";
891
+ settingsPanel.updateSection("audioTrack", { activeValue: value, currentValue: label });
892
+ },
893
+ setButtonContent() {
894
+ },
895
+ close() {
896
+ }
897
+ };
708
898
  return {
709
899
  topEl,
710
900
  bottomEl,
711
901
  progress,
712
902
  volume,
713
- qualityMenu,
714
- audioMenu,
903
+ qualityMenu: qualityMenuAdapter,
904
+ audioMenu: audioMenuAdapter,
905
+ settingsPanel,
715
906
  updatePlayState(playing) {
716
907
  playBtn.innerHTML = playing ? icons.pause : icons.play;
717
908
  },
@@ -720,16 +911,21 @@ function createControls(ctx) {
720
911
  progress.update();
721
912
  },
722
913
  updateRate(rate) {
723
- rateMenu.setButtonContent(`${rate}x`);
724
- rateMenu.setActive(rate);
914
+ currentRate = rate;
915
+ settingsPanel.updateSection("rate", { activeValue: rate, currentValue: `${rate}x` });
725
916
  },
726
917
  updateFullscreen(fs) {
727
918
  fsBtn.innerHTML = fs ? icons.fullscreenExit : icons.fullscreen;
728
919
  },
920
+ updatePip(pip) {
921
+ settingsPanel.updateSection("pip", { toggle: { checked: pip } });
922
+ },
729
923
  updateRatio(ratio) {
730
- ratioMenu.setActive(ratio);
924
+ const label = ratio === "original" ? i18n.t("ratioOriginal") : ratio;
925
+ settingsPanel.updateSection("ratio", { activeValue: ratio, currentValue: label });
731
926
  },
732
927
  destroy() {
928
+ settingsPanel.destroy();
733
929
  topEl.remove();
734
930
  bottomEl.remove();
735
931
  }
@@ -762,6 +958,94 @@ function createOsd() {
762
958
  };
763
959
  }
764
960
 
961
+ // src/ui/components/contextMenu.ts
962
+ function createContextMenu(container, items) {
963
+ const menu = createEl("div", { className: "sp-context-menu", parent: container });
964
+ for (const item of items) {
965
+ const btn = createEl("button", {
966
+ className: "sp-context-item",
967
+ text: item.label,
968
+ attrs: { type: "button" },
969
+ parent: menu
970
+ });
971
+ btn.addEventListener("click", () => {
972
+ hide();
973
+ item.onClick();
974
+ });
975
+ }
976
+ function show(clientX, clientY) {
977
+ menu.classList.add("sp-visible");
978
+ const rect = container.getBoundingClientRect();
979
+ const x = Math.max(4, Math.min(clientX - rect.left, rect.width - menu.offsetWidth - 4));
980
+ const y = Math.max(4, Math.min(clientY - rect.top, rect.height - menu.offsetHeight - 4));
981
+ menu.style.left = `${x}px`;
982
+ menu.style.top = `${y}px`;
983
+ }
984
+ function hide() {
985
+ menu.classList.remove("sp-visible");
986
+ }
987
+ const onContextMenu = (e) => {
988
+ e.preventDefault();
989
+ show(e.clientX, e.clientY);
990
+ };
991
+ const onPointerDown = (e) => {
992
+ if (!menu.contains(e.target)) hide();
993
+ };
994
+ container.addEventListener("contextmenu", onContextMenu);
995
+ document.addEventListener("pointerdown", onPointerDown, true);
996
+ return {
997
+ hide,
998
+ destroy() {
999
+ container.removeEventListener("contextmenu", onContextMenu);
1000
+ document.removeEventListener("pointerdown", onPointerDown, true);
1001
+ menu.remove();
1002
+ }
1003
+ };
1004
+ }
1005
+
1006
+ // src/ui/components/shortcutsOverlay.ts
1007
+ function createShortcutsOverlay(container, i18n, seekStep) {
1008
+ let root = null;
1009
+ function show() {
1010
+ if (root) return;
1011
+ root = createEl("div", { className: "sp-shortcuts", parent: container });
1012
+ const closeBtn = createEl("button", {
1013
+ className: "sp-shortcuts-close",
1014
+ text: "\u2715",
1015
+ attrs: { type: "button", "aria-label": i18n.t("close") },
1016
+ parent: root
1017
+ });
1018
+ closeBtn.addEventListener("click", hide);
1019
+ createEl("div", { className: "sp-shortcuts-title", text: i18n.t("shortcuts"), parent: root });
1020
+ const dl = createEl("dl", { className: "sp-shortcuts-body", parent: root });
1021
+ const rows = [
1022
+ [i18n.t("scKeySpace"), i18n.t("scPlayPause")],
1023
+ ["\u2190 / \u2192", i18n.t("scSeek", { n: seekStep })],
1024
+ [i18n.t("scKeyHold"), i18n.t("scLongSeek")],
1025
+ ["\u2191 / \u2193", i18n.t("scVolume")],
1026
+ ["F", i18n.t("scFullscreen")],
1027
+ ["M", i18n.t("scMute")]
1028
+ ];
1029
+ for (const [key, action] of rows) {
1030
+ const row = createEl("div", { className: "sp-shortcuts-row", parent: dl });
1031
+ const dt = createEl("dt", { parent: row });
1032
+ createEl("span", { className: "sp-kbd", text: key, parent: dt });
1033
+ createEl("dd", { text: action, parent: row });
1034
+ }
1035
+ }
1036
+ function hide() {
1037
+ root?.remove();
1038
+ root = null;
1039
+ }
1040
+ return {
1041
+ toggle() {
1042
+ root ? hide() : show();
1043
+ },
1044
+ hide,
1045
+ destroy: hide
1046
+ };
1047
+ }
1048
+
765
1049
  // src/ui/components/statsOverlay.ts
766
1050
  function createStatsOverlay(container, video, media, i18n) {
767
1051
  let root = null;
@@ -800,11 +1084,31 @@ function createStatsOverlay(container, video, media, i18n) {
800
1084
  function show() {
801
1085
  if (root) return;
802
1086
  root = createEl("div", { className: "sp-stats", parent: container });
1087
+ const header = createEl("div", { className: "sp-stats-header", parent: root });
1088
+ const copyBtn = createEl("button", {
1089
+ className: "sp-stats-copy",
1090
+ text: i18n.t("copyLog"),
1091
+ attrs: { type: "button" },
1092
+ parent: header
1093
+ });
1094
+ copyBtn.addEventListener("click", () => {
1095
+ const text = getLogText();
1096
+ void navigator.clipboard.writeText(text || "(empty)").then(
1097
+ () => {
1098
+ copyBtn.textContent = i18n.t("logCopied");
1099
+ setTimeout(() => {
1100
+ copyBtn.textContent = i18n.t("copyLog");
1101
+ }, 1500);
1102
+ },
1103
+ () => {
1104
+ }
1105
+ );
1106
+ });
803
1107
  const closeBtn = createEl("button", {
804
1108
  className: "sp-stats-close",
805
1109
  text: "\u2715",
806
1110
  attrs: { type: "button", "aria-label": i18n.t("closeStats") },
807
- parent: root
1111
+ parent: header
808
1112
  });
809
1113
  closeBtn.addEventListener("click", hide);
810
1114
  createEl("dl", { className: "sp-stats-body", parent: root });
@@ -969,7 +1273,22 @@ var zhCN = {
969
1273
  autoNextIn: "{n} \u79D2\u540E\u64AD\u653E\u4E0B\u4E00\u4E2A",
970
1274
  cancel: "\u53D6\u6D88",
971
1275
  loadError: "\u89C6\u9891\u52A0\u8F7D\u5931\u8D25",
972
- retry: "\u91CD\u8BD5"
1276
+ retry: "\u91CD\u8BD5",
1277
+ changelog: "\u66F4\u65B0\u8BB0\u5F55",
1278
+ videoInfo: "\u89C6\u9891\u4FE1\u606F",
1279
+ shortcuts: "\u5FEB\u6377\u952E",
1280
+ close: "\u5173\u95ED",
1281
+ scKeySpace: "\u7A7A\u683C",
1282
+ scKeyHold: "\u2190 / \u2192 \u957F\u6309",
1283
+ scPlayPause: "\u64AD\u653E / \u6682\u505C",
1284
+ scSeek: "\u5FEB\u9000 / \u5FEB\u8FDB {n} \u79D2",
1285
+ scLongSeek: "\u9636\u68AF\u7D2F\u8BA1\u5FEB\u8FDB\u5FEB\u9000\uFF0C\u677E\u5F00\u6267\u884C",
1286
+ scVolume: "\u97F3\u91CF \xB15",
1287
+ scFullscreen: "\u5168\u5C4F\u5207\u6362",
1288
+ scMute: "\u9759\u97F3\u5207\u6362",
1289
+ settings: "\u8BBE\u7F6E",
1290
+ copyLog: "\u590D\u5236\u65E5\u5FD7",
1291
+ logCopied: "\u5DF2\u590D\u5236"
973
1292
  };
974
1293
  var en = {
975
1294
  play: "Play",
@@ -998,7 +1317,22 @@ var en = {
998
1317
  autoNextIn: "Playing next in {n}s",
999
1318
  cancel: "Cancel",
1000
1319
  loadError: "Failed to load video",
1001
- retry: "Retry"
1320
+ retry: "Retry",
1321
+ changelog: "Changelog",
1322
+ videoInfo: "Video info",
1323
+ shortcuts: "Shortcuts",
1324
+ close: "Close",
1325
+ scKeySpace: "Space",
1326
+ scKeyHold: "Hold \u2190 / \u2192",
1327
+ scPlayPause: "Play / Pause",
1328
+ scSeek: "Rewind / forward {n}s",
1329
+ scLongSeek: "Accelerating seek, release to apply",
1330
+ scVolume: "Volume \xB15",
1331
+ scFullscreen: "Toggle fullscreen",
1332
+ scMute: "Toggle mute",
1333
+ settings: "Settings",
1334
+ copyLog: "Copy log",
1335
+ logCopied: "Copied"
1002
1336
  };
1003
1337
  var locales = { "zh-CN": zhCN, en };
1004
1338
  var I18n = class {
@@ -1071,8 +1405,11 @@ function clearProgress(id) {
1071
1405
  }
1072
1406
  }
1073
1407
 
1408
+ // src/version.ts
1409
+ var VERSION = true ? "0.4.0" : "dev";
1410
+
1074
1411
  // 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";
1412
+ var player_default = ".sweet-player {\n --sp-accent: #ff4d6d;\n --sp-bg: #000;\n --sp-text: #fff;\n --sp-bar-height: 4px;\n --sp-bar-hover-height: 6px;\n --sp-control-size: 36px;\n --sp-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n\n position: relative;\n width: 100%;\n height: 100%;\n background: var(--sp-bg);\n color: var(--sp-text);\n font-family: var(--sp-font);\n font-size: 13px;\n overflow: hidden;\n user-select: none;\n -webkit-user-select: none;\n outline: none;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.sweet-player *,\n.sweet-player *::before,\n.sweet-player *::after {\n box-sizing: border-box;\n}\n\n.sp-video {\n width: 100%;\n height: 100%;\n object-fit: contain;\n display: block;\n}\n\n/* \u5F3A\u5236\u753B\u9762\u6BD4\u4F8B\uFF1Avideo \u5C45\u4E2D\u3001\u6309\u6BD4\u4F8B\u9650\u5236\u5C3A\u5BF8\u5E76\u62C9\u4F38\u586B\u5145 */\n.sweet-player[data-ratio]:not([data-ratio='original']) .sp-video {\n width: auto;\n height: auto;\n max-width: 100%;\n max-height: 100%;\n aspect-ratio: var(--sp-forced-ratio);\n object-fit: fill;\n}\n\n/* ---------- \u9876\u90E8\u6807\u9898\u533A ---------- */\n.sp-top {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n padding: 12px 16px 28px;\n background: linear-gradient(to bottom, rgba(0, 0, 0, 0.6), transparent);\n transition: opacity 0.25s ease;\n pointer-events: none;\n}\n\n.sp-title {\n display: inline-block;\n max-width: 70%;\n font-size: 15px;\n font-weight: 500;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n pointer-events: auto;\n cursor: default;\n}\n\n/* ---------- \u7EDF\u8BA1\u4FE1\u606F\u8499\u5C42 ---------- */\n.sp-stats {\n position: absolute;\n top: 12px;\n left: 12px;\n z-index: 30;\n min-width: 300px;\n max-width: calc(100% - 24px);\n padding: 10px 14px;\n background: rgba(0, 0, 0, 0.75);\n border-radius: 6px;\n font-size: 12px;\n line-height: 1.7;\n font-family: 'SFMono-Regular', Consolas, monospace;\n pointer-events: auto;\n}\n\n.sp-stats-header {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n gap: 4px;\n margin: -4px -6px 4px 0;\n}\n\n.sp-stats-copy {\n border: none;\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n opacity: 0.7;\n font-size: 12px;\n line-height: 22px;\n padding: 0 6px;\n border-radius: 3px;\n white-space: nowrap;\n}\n\n.sp-stats-copy:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.1);\n}\n\n.sp-stats-close {\n width: 22px;\n height: 22px;\n border: none;\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n opacity: 0.7;\n font-size: 16px;\n line-height: 1;\n flex-shrink: 0;\n}\n\n.sp-stats-close:hover {\n opacity: 1;\n}\n\n.sp-stats-row {\n display: flex;\n gap: 12px;\n}\n\n.sp-stats-row dt {\n flex: 0 0 110px;\n opacity: 0.65;\n margin: 0;\n}\n\n.sp-stats-row dd {\n margin: 0;\n word-break: break-all;\n}\n\n/* ---------- \u5FEB\u6377\u952E\u9762\u677F ---------- */\n.sp-shortcuts {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 30;\n min-width: 280px;\n max-width: calc(100% - 24px);\n max-height: calc(100% - 24px);\n overflow-y: auto;\n padding: 14px 20px 14px;\n background: rgba(0, 0, 0, 0.8);\n border-radius: 8px;\n font-size: 13px;\n line-height: 2.1;\n pointer-events: auto;\n}\n\n.sp-shortcuts-title {\n font-size: 14px;\n font-weight: 600;\n margin-bottom: 6px;\n}\n\n.sp-shortcuts-body {\n margin: 0;\n}\n\n.sp-shortcuts-row {\n display: flex;\n align-items: center;\n gap: 16px;\n}\n\n.sp-shortcuts-row dt {\n flex: 0 0 100px;\n margin: 0;\n}\n\n.sp-shortcuts-row dd {\n margin: 0;\n opacity: 0.85;\n}\n\n.sp-kbd {\n display: inline-block;\n padding: 0 7px;\n background: rgba(255, 255, 255, 0.15);\n border-radius: 4px;\n font-family: 'SFMono-Regular', Consolas, monospace;\n font-size: 12px;\n line-height: 20px;\n white-space: nowrap;\n}\n\n.sp-shortcuts-close {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 22px;\n height: 22px;\n border: none;\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n opacity: 0.7;\n font-size: 16px;\n line-height: 1;\n}\n\n.sp-shortcuts-close:hover {\n opacity: 1;\n}\n\n/* ---------- \u53F3\u952E\u83DC\u5355 ---------- */\n.sp-context-menu {\n position: absolute;\n z-index: 40;\n min-width: 150px;\n padding: 4px 0;\n background: rgba(28, 28, 28, 0.95);\n border-radius: 6px;\n display: none;\n}\n\n.sp-context-menu.sp-visible {\n display: block;\n}\n\n.sp-context-item {\n display: block;\n width: 100%;\n padding: 8px 16px;\n border: none;\n background: transparent;\n color: var(--sp-text);\n text-align: left;\n cursor: pointer;\n font-size: 13px;\n white-space: nowrap;\n}\n\n.sp-context-item:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n\n/* ---------- \u4E2D\u592E OSD \u53CD\u9988 ---------- */\n.sp-osd {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n z-index: 20;\n padding: 10px 18px;\n background: rgba(0, 0, 0, 0.65);\n border-radius: 6px;\n font-size: 16px;\n font-weight: 500;\n opacity: 0;\n transition: opacity 0.15s ease;\n pointer-events: none;\n white-space: nowrap;\n}\n\n.sp-osd.sp-visible {\n opacity: 1;\n}\n\n/* ---------- \u5E95\u90E8\u63A7\u5236\u533A ---------- */\n.sp-bottom {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n padding: 24px 12px 6px;\n background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);\n transition: opacity 0.25s ease;\n z-index: 10;\n}\n\n.sweet-player.sp-controls-hidden .sp-bottom,\n.sweet-player.sp-controls-hidden .sp-top {\n opacity: 0;\n pointer-events: none;\n}\n\n.sweet-player.sp-controls-hidden {\n cursor: none;\n}\n\n/* ---------- \u8FDB\u5EA6\u6761 ---------- */\n.sp-progress {\n position: relative;\n height: 14px;\n display: flex;\n align-items: center;\n cursor: pointer;\n margin-bottom: 4px;\n}\n\n.sp-progress-track {\n position: relative;\n width: 100%;\n height: var(--sp-bar-height);\n background: rgba(255, 255, 255, 0.25);\n border-radius: 2px;\n overflow: visible;\n transition: height 0.1s ease;\n}\n\n.sp-progress:hover .sp-progress-track {\n height: var(--sp-bar-hover-height);\n}\n\n.sp-progress-buffered,\n.sp-progress-played {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n border-radius: 2px;\n}\n\n.sp-progress-buffered {\n background: rgba(255, 255, 255, 0.35);\n}\n\n.sp-progress-played {\n background: var(--sp-accent);\n}\n\n.sp-progress-thumb {\n position: absolute;\n top: 50%;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: var(--sp-accent);\n transform: translate(-50%, -50%) scale(0);\n transition: transform 0.1s ease;\n pointer-events: none;\n}\n\n.sp-progress:hover .sp-progress-thumb,\n.sp-progress.sp-dragging .sp-progress-thumb {\n transform: translate(-50%, -50%) scale(1);\n}\n\n.sp-progress-tooltip {\n position: absolute;\n bottom: 18px;\n transform: translateX(-50%);\n padding: 2px 6px;\n background: rgba(0, 0, 0, 0.8);\n border-radius: 3px;\n font-size: 12px;\n display: none;\n pointer-events: none;\n white-space: nowrap;\n}\n\n.sp-progress:hover .sp-progress-tooltip {\n display: block;\n}\n\n/* ---------- \u63A7\u5236\u6309\u94AE\u884C ---------- */\n.sp-controls {\n display: flex;\n align-items: center;\n gap: 2px;\n}\n\n.sp-controls-spacer {\n flex: 1;\n}\n\n.sp-btn {\n width: var(--sp-control-size);\n height: var(--sp-control-size);\n border: none;\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 4px;\n padding: 0;\n opacity: 0.9;\n transition: opacity 0.1s ease, background 0.1s ease;\n}\n\n.sp-btn:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.15);\n}\n\n.sp-btn svg {\n width: 22px;\n height: 22px;\n}\n\n.sp-btn:disabled {\n opacity: 0.35;\n cursor: not-allowed;\n}\n\n.sp-time {\n padding: 0 8px;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n opacity: 0.95;\n}\n\n/* ---------- \u5F39\u51FA\u83DC\u5355 ---------- */\n.sp-menu {\n position: relative;\n}\n\n.sp-menu-btn {\n min-width: var(--sp-control-size);\n width: auto;\n padding: 0 8px;\n font-size: 13px;\n font-weight: 500;\n}\n\n.sp-menu-panel {\n position: absolute;\n bottom: calc(100% + 8px);\n left: 50%;\n transform: translateX(-50%);\n min-width: 80px;\n max-height: 220px;\n overflow-y: auto;\n background: rgba(28, 28, 28, 0.95);\n border-radius: 6px;\n padding: 4px 0;\n display: none;\n z-index: 25;\n}\n\n.sp-menu.sp-open .sp-menu-panel {\n display: block;\n}\n\n.sp-menu-item {\n display: block;\n width: 100%;\n padding: 7px 16px;\n border: none;\n background: transparent;\n color: var(--sp-text);\n text-align: center;\n cursor: pointer;\n font-size: 13px;\n white-space: nowrap;\n}\n\n.sp-menu-item:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n\n.sp-menu-item.sp-active {\n color: var(--sp-accent);\n font-weight: 600;\n}\n\n.sp-menu-empty {\n padding: 7px 16px;\n opacity: 0.5;\n white-space: nowrap;\n}\n\n/* ---------- \u72B6\u6001\u8499\u5C42\uFF1Aloading / \u9519\u8BEF / \u7ED3\u675F ---------- */\n.sp-state {\n position: absolute;\n inset: 0;\n z-index: 15;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n}\n\n.sp-spinner {\n opacity: 0;\n transition: opacity 0.2s ease 0.3s; /* \u5EF6\u8FDF\u51FA\u73B0\uFF0C\u907F\u514D\u77ED\u6682 seek \u95EA\u70C1 */\n}\n\n.sp-state.sp-loading .sp-spinner {\n opacity: 1;\n}\n\n.sp-spinner-ring {\n width: 44px;\n height: 44px;\n border: 4px solid rgba(255, 255, 255, 0.25);\n border-top-color: var(--sp-accent);\n border-radius: 50%;\n animation: sp-spin 0.8s linear infinite;\n}\n\n@keyframes sp-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n.sp-state-panel {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 14px;\n background: rgba(0, 0, 0, 0.55);\n pointer-events: auto;\n}\n\n.sp-state-message {\n font-size: 15px;\n opacity: 0.95;\n}\n\n.sp-state-actions {\n display: flex;\n gap: 12px;\n}\n\n.sp-state-btn {\n padding: 8px 22px;\n border: 1px solid rgba(255, 255, 255, 0.5);\n border-radius: 18px;\n background: transparent;\n color: var(--sp-text);\n font-size: 14px;\n cursor: pointer;\n transition: background 0.1s ease;\n}\n\n.sp-state-btn:hover {\n background: rgba(255, 255, 255, 0.15);\n}\n\n.sp-state-btn-primary {\n background: var(--sp-accent);\n border-color: var(--sp-accent);\n}\n\n.sp-state-btn-primary:hover {\n background: var(--sp-accent);\n filter: brightness(1.1);\n}\n\n.sp-state-countdown {\n font-size: 13px;\n opacity: 0.85;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.sp-state-cancel {\n border: none;\n background: transparent;\n color: var(--sp-accent);\n cursor: pointer;\n font-size: 13px;\n padding: 2px 6px;\n}\n\n/* ---------- \u8BBE\u7F6E\u9762\u677F ---------- */\n.sp-settings-panel {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n width: 180px;\n background: rgba(28, 28, 28, 0.95);\n border-radius: 6px;\n padding: 4px 0;\n display: none;\n z-index: 25;\n}\n\n.sp-settings.sp-open .sp-settings-panel {\n display: block;\n}\n\n.sp-settings-row {\n display: flex;\n align-items: center;\n width: 100%;\n padding: 7px 12px;\n border: none;\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n font-size: 13px;\n white-space: nowrap;\n gap: 4px;\n}\n\n.sp-settings-row:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n\n.sp-settings-label {\n flex: 1;\n text-align: left;\n}\n\n.sp-settings-value {\n opacity: 0.6;\n font-size: 12px;\n}\n\n.sp-settings-arrow {\n display: inline-flex;\n align-items: center;\n opacity: 0.5;\n}\n\n.sp-settings-arrow svg {\n width: 16px;\n height: 16px;\n}\n\n.sp-settings-back {\n display: flex;\n align-items: center;\n width: 100%;\n padding: 7px 12px;\n border: none;\n border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n background: transparent;\n color: var(--sp-text);\n cursor: pointer;\n font-size: 13px;\n font-weight: 500;\n gap: 2px;\n margin-bottom: 2px;\n}\n\n.sp-settings-back:hover {\n background: rgba(255, 255, 255, 0.08);\n}\n\n.sp-settings-back-arrow {\n display: inline-flex;\n align-items: center;\n opacity: 0.7;\n}\n\n.sp-settings-back-arrow svg {\n width: 18px;\n height: 18px;\n}\n\n.sp-settings-sub .sp-menu-item {\n text-align: left;\n padding: 7px 16px;\n}\n\n/* ---------- \u884C\u5185\u5F00\u5173 ---------- */\n.sp-settings-toggle-row {\n cursor: pointer;\n}\n\n.sp-toggle {\n position: relative;\n width: 30px;\n height: 17px;\n background: rgba(255, 255, 255, 0.25);\n border-radius: 9px;\n flex-shrink: 0;\n transition: background 0.15s ease;\n outline: none;\n}\n\n.sp-toggle:focus-visible {\n box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.4);\n}\n\n.sp-toggle.sp-on {\n background: var(--sp-accent);\n}\n\n.sp-toggle-thumb {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 13px;\n height: 13px;\n background: #fff;\n border-radius: 50%;\n transition: transform 0.15s ease;\n}\n\n.sp-toggle.sp-on .sp-toggle-thumb {\n transform: translateX(13px);\n}\n\n/* ---------- \u97F3\u91CF ---------- */\n.sp-volume {\n display: flex;\n align-items: center;\n}\n\n.sp-volume-slider {\n width: 0;\n overflow: hidden;\n transition: width 0.15s ease;\n display: flex;\n align-items: center;\n}\n\n.sp-volume:hover .sp-volume-slider,\n.sp-volume.sp-dragging .sp-volume-slider {\n width: 64px;\n}\n\n.sp-volume-track {\n position: relative;\n width: 56px;\n height: var(--sp-bar-height);\n margin: 0 4px;\n background: rgba(255, 255, 255, 0.25);\n border-radius: 2px;\n cursor: pointer;\n}\n\n.sp-volume-fill {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--sp-text);\n border-radius: 2px;\n}\n\n.sp-volume-thumb {\n position: absolute;\n top: 50%;\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: var(--sp-text);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n";
1076
1413
 
1077
1414
  // src/player.ts
1078
1415
  var DEFAULT_RATES = [0.5, 1, 1.5, 2];
@@ -1081,19 +1418,17 @@ var DEFAULT_LONG_SEEK_STEPS = [10, 30, 60];
1081
1418
  var DEFAULT_STEP_UP_INTERVAL = 2e3;
1082
1419
  var DEFAULT_AUTO_NEXT_SECONDS = 5;
1083
1420
  var CONTROLS_HIDE_DELAY = 3e3;
1084
- var TITLE_CLICK_COUNT = 10;
1085
- var TITLE_CLICK_WINDOW = 1500;
1086
1421
  var SINGLE_CLICK_DELAY = 250;
1422
+ var NPM_URL = "https://www.npmjs.com/package/@sweet-player/core";
1087
1423
  var PROGRESS_SAVE_INTERVAL = 5e3;
1088
1424
  var PROGRESS_END_GUARD = 10;
1089
1425
  var SweetPlayer = class {
1090
1426
  constructor(options) {
1091
1427
  this.emitter = new EventEmitter();
1428
+ this.contextMenu = null;
1092
1429
  this.hideTimer = null;
1093
1430
  this.clickTimer = null;
1094
1431
  this.progressTimer = null;
1095
- this.titleClicks = 0;
1096
- this.lastTitleClick = 0;
1097
1432
  this.currentRatio = "original";
1098
1433
  /** 画质/音轨菜单当前是否由 hls.js 自动接管(业务 setQualities 会关闭) */
1099
1434
  this.hlsManagedQuality = false;
@@ -1126,9 +1461,27 @@ var SweetPlayer = class {
1126
1461
  onLevels: autoQuality && !options.qualities?.length ? (levels) => this.applyHlsLevels(levels) : void 0,
1127
1462
  onAudioTracks: autoQuality && !options.audioTracks?.length ? (tracks) => this.applyHlsAudioTracks(tracks) : void 0
1128
1463
  });
1464
+ const hidden = new Set(options.hiddenControls ?? []);
1129
1465
  this.osd = createOsd();
1130
1466
  this.stats = createStatsOverlay(this.container, this.video, this.media, this.i18n);
1467
+ this.shortcutsPanel = createShortcutsOverlay(this.container, this.i18n, options.seekStep ?? 10);
1131
1468
  this.state = createStateOverlay(this.i18n);
1469
+ if (!hidden.has("contextMenu")) {
1470
+ this.contextMenu = createContextMenu(this.container, [
1471
+ {
1472
+ label: `${this.i18n.t("changelog")}: v${VERSION}`,
1473
+ onClick: () => window.open(NPM_URL, "_blank", "noopener")
1474
+ },
1475
+ {
1476
+ label: this.i18n.t("videoInfo"),
1477
+ onClick: () => this.stats.toggle()
1478
+ },
1479
+ {
1480
+ label: this.i18n.t("shortcuts"),
1481
+ onClick: () => this.shortcutsPanel.toggle()
1482
+ }
1483
+ ]);
1484
+ }
1132
1485
  this.controls = createControls({
1133
1486
  video: this.video,
1134
1487
  title: options.title ?? "",
@@ -1136,6 +1489,7 @@ var SweetPlayer = class {
1136
1489
  playbackRates: options.playbackRates ?? DEFAULT_RATES,
1137
1490
  aspectRatios: options.aspectRatios ?? DEFAULT_RATIOS,
1138
1491
  seekStep: options.seekStep ?? 10,
1492
+ hidden,
1139
1493
  actions: {
1140
1494
  togglePlay: () => this.toggle(),
1141
1495
  seekBy: (d) => this.seekBy(d),
@@ -1148,8 +1502,7 @@ var SweetPlayer = class {
1148
1502
  selectQuality: (q) => this.handleQualitySelect(q),
1149
1503
  selectAudioTrack: (t) => this.handleAudioTrackSelect(t),
1150
1504
  onPrev: options.onPrev,
1151
- onNext: options.onNext,
1152
- onTitleClick: () => this.handleTitleClick()
1505
+ onNext: options.onNext
1153
1506
  }
1154
1507
  });
1155
1508
  this.container.appendChild(this.controls.topEl);
@@ -1205,6 +1558,7 @@ var SweetPlayer = class {
1205
1558
  this.bindActivityTracking();
1206
1559
  this.disposers.push(
1207
1560
  onFullscreenChange(this.container, (fs) => {
1561
+ log("\u64AD\u653E\u5668", fs ? "\u8FDB\u5165\u5168\u5C4F" : "\u9000\u51FA\u5168\u5C4F");
1208
1562
  this.controls.updateFullscreen(fs);
1209
1563
  this.emitter.emit("fullscreenchange", fs);
1210
1564
  })
@@ -1240,6 +1594,7 @@ var SweetPlayer = class {
1240
1594
  if (showOsd) this.osd.flash(`${delta > 0 ? "+" : ""}${delta} ${this.i18n.t("seconds")}`);
1241
1595
  }
1242
1596
  setRate(rate) {
1597
+ log("\u64AD\u653E\u5668", `\u500D\u901F\u5207\u6362: ${rate}x`);
1243
1598
  this.video.playbackRate = rate;
1244
1599
  }
1245
1600
  /** 0-100 */
@@ -1326,7 +1681,9 @@ var SweetPlayer = class {
1326
1681
  this.keyboard.destroy();
1327
1682
  this.gestures.destroy();
1328
1683
  this.stats.destroy();
1684
+ this.shortcutsPanel.destroy();
1329
1685
  this.state.destroy();
1686
+ this.contextMenu?.destroy();
1330
1687
  this.controls.destroy();
1331
1688
  this.disposers.forEach((d) => d());
1332
1689
  if (this.hideTimer) clearTimeout(this.hideTimer);
@@ -1349,6 +1706,7 @@ var SweetPlayer = class {
1349
1706
  this.hlsManagedAudio = true;
1350
1707
  }
1351
1708
  handleQualitySelect(quality) {
1709
+ log("\u64AD\u653E\u5668", `\u5207\u6362\u753B\u8D28: ${quality.label}`);
1352
1710
  this.controls.qualityMenu.setActive(quality);
1353
1711
  if (this.hlsManagedQuality && typeof quality.value === "number") {
1354
1712
  this.media.setLevel(quality.value);
@@ -1361,6 +1719,7 @@ var SweetPlayer = class {
1361
1719
  this.emitter.emit("qualitychange", quality);
1362
1720
  }
1363
1721
  handleAudioTrackSelect(track) {
1722
+ log("\u64AD\u653E\u5668", `\u5207\u6362\u97F3\u8F68: ${track.label}`);
1364
1723
  this.controls.audioMenu.setActive(track);
1365
1724
  if (this.hlsManagedAudio && typeof track.value === "number") {
1366
1725
  this.media.setAudioTrack(track.value);
@@ -1401,15 +1760,6 @@ var SweetPlayer = class {
1401
1760
  }
1402
1761
  }
1403
1762
  // ---------- 内部:交互 ----------
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
1763
  bindMediaEvents() {
1414
1764
  const v = this.video;
1415
1765
  const listen = (event, fn) => {
@@ -1417,6 +1767,7 @@ var SweetPlayer = class {
1417
1767
  this.disposers.push(() => v.removeEventListener(event, fn));
1418
1768
  };
1419
1769
  listen("loadedmetadata", () => {
1770
+ log("\u539F\u751F\u4E8B\u4EF6", `\u83B7\u53D6\u5143\u6570\u636E (${v.videoWidth}x${v.videoHeight})`);
1420
1771
  this.controls.updateTime();
1421
1772
  this.emitter.emit("ready", void 0);
1422
1773
  });
@@ -1432,6 +1783,7 @@ var SweetPlayer = class {
1432
1783
  this.emitter.emit("pause", void 0);
1433
1784
  });
1434
1785
  listen("ended", () => {
1786
+ log("\u539F\u751F\u4E8B\u4EF6", "\u64AD\u653E\u7ED3\u675F");
1435
1787
  this.saveProgressNow();
1436
1788
  this.showControls();
1437
1789
  const onNext = this.options.onNext;
@@ -1461,23 +1813,49 @@ var SweetPlayer = class {
1461
1813
  this.controls.volume.update(volume, v.muted);
1462
1814
  this.emitter.emit("volumechange", { volume, muted: v.muted });
1463
1815
  });
1464
- listen("waiting", () => this.state.showLoading());
1465
- listen("stalled", () => this.state.showLoading());
1466
- listen("seeking", () => this.state.showLoading());
1467
- listen("canplay", () => this.state.hideLoading());
1816
+ listen("waiting", () => {
1817
+ log("\u539F\u751F\u4E8B\u4EF6", "\u7B49\u5F85\u7F13\u51B2\u4E2D");
1818
+ this.state.showLoading();
1819
+ });
1820
+ listen("stalled", () => {
1821
+ log("\u539F\u751F\u4E8B\u4EF6", "\u6570\u636E\u52A0\u8F7D\u505C\u6EDE");
1822
+ this.state.showLoading();
1823
+ });
1824
+ listen("seeking", () => {
1825
+ log("\u539F\u751F\u4E8B\u4EF6", "\u5F00\u59CB\u8DF3\u8F6C");
1826
+ this.state.showLoading();
1827
+ });
1828
+ listen("canplay", () => {
1829
+ log("\u539F\u751F\u4E8B\u4EF6", "\u51C6\u5907\u597D\u5F00\u59CB\u64AD\u653E");
1830
+ this.state.hideLoading();
1831
+ });
1468
1832
  listen("playing", () => {
1469
1833
  this.state.hideLoading();
1470
1834
  this.state.hideError();
1471
1835
  });
1472
- listen("seeked", () => this.state.hideLoading());
1473
- listen("error", () => this.showErrorState({ type: "media", detail: v.error }));
1836
+ listen("seeked", () => {
1837
+ log("\u539F\u751F\u4E8B\u4EF6", "\u8DF3\u8F6C\u5B8C\u6210");
1838
+ this.state.hideLoading();
1839
+ });
1840
+ listen("error", () => {
1841
+ log("\u539F\u751F\u4E8B\u4EF6", `\u64AD\u653E\u9519\u8BEF: ${v.error?.message ?? "\u672A\u77E5\u9519\u8BEF"}`);
1842
+ this.showErrorState({ type: "media", detail: v.error });
1843
+ });
1474
1844
  this.disposers.push(
1475
1845
  this.emitter.on("error", (payload) => {
1476
1846
  if (payload.type.startsWith("hls-")) this.showErrorStateUi();
1477
1847
  })
1478
1848
  );
1479
- listen("enterpictureinpicture", () => this.emitter.emit("pipchange", true));
1480
- listen("leavepictureinpicture", () => this.emitter.emit("pipchange", false));
1849
+ listen("enterpictureinpicture", () => {
1850
+ log("\u64AD\u653E\u5668", "\u8FDB\u5165\u753B\u4E2D\u753B");
1851
+ this.controls.updatePip(true);
1852
+ this.emitter.emit("pipchange", true);
1853
+ });
1854
+ listen("leavepictureinpicture", () => {
1855
+ log("\u64AD\u653E\u5668", "\u9000\u51FA\u753B\u4E2D\u753B");
1856
+ this.controls.updatePip(false);
1857
+ this.emitter.emit("pipchange", false);
1858
+ });
1481
1859
  const onVideoClick = (e) => {
1482
1860
  if (e.target !== this.video || e.pointerType === "touch") return;
1483
1861
  if (this.clickTimer) return;
@@ -1544,8 +1922,10 @@ var SweetPlayer = class {
1544
1922
  });
1545
1923
  }
1546
1924
  };
1925
+ SweetPlayer.version = VERSION;
1547
1926
  // Annotate the CommonJS export names for ESM import in node:
1548
1927
  0 && (module.exports = {
1549
1928
  SweetPlayer,
1929
+ VERSION,
1550
1930
  registerLocale
1551
1931
  });