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