sa2kit 1.4.2 → 1.5.1

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.
Files changed (63) hide show
  1. package/dist/ConfigService-7MEZXKJ5.js +21 -0
  2. package/dist/ConfigService-7MEZXKJ5.js.map +1 -0
  3. package/dist/ConfigService-BV57YYFW.mjs +4 -0
  4. package/dist/ConfigService-BV57YYFW.mjs.map +1 -0
  5. package/dist/ConfigService-BxK06xP6.d.mts +262 -0
  6. package/dist/ConfigService-BxK06xP6.d.ts +262 -0
  7. package/dist/audioDetection/index.d.mts +449 -0
  8. package/dist/audioDetection/index.d.ts +449 -0
  9. package/dist/audioDetection/index.js +1244 -0
  10. package/dist/audioDetection/index.js.map +1 -0
  11. package/dist/audioDetection/index.mjs +1227 -0
  12. package/dist/audioDetection/index.mjs.map +1 -0
  13. package/dist/chunk-5XUE72Y3.mjs +1001 -0
  14. package/dist/chunk-5XUE72Y3.mjs.map +1 -0
  15. package/dist/chunk-DQVPZTVC.js +1009 -0
  16. package/dist/chunk-DQVPZTVC.js.map +1 -0
  17. package/dist/chunk-NEPD75MX.mjs +467 -0
  18. package/dist/chunk-NEPD75MX.mjs.map +1 -0
  19. package/dist/chunk-OEDY7GI4.js +473 -0
  20. package/dist/chunk-OEDY7GI4.js.map +1 -0
  21. package/dist/chunk-TFQF2HDO.mjs +354 -0
  22. package/dist/chunk-TFQF2HDO.mjs.map +1 -0
  23. package/dist/chunk-TOC5FSHP.js +358 -0
  24. package/dist/chunk-TOC5FSHP.js.map +1 -0
  25. package/dist/imageCrop/index.d.mts +165 -0
  26. package/dist/imageCrop/index.d.ts +165 -0
  27. package/dist/imageCrop/index.js +559 -0
  28. package/dist/imageCrop/index.js.map +1 -0
  29. package/dist/imageCrop/index.mjs +540 -0
  30. package/dist/imageCrop/index.mjs.map +1 -0
  31. package/dist/index.d.mts +139 -0
  32. package/dist/index.d.ts +139 -0
  33. package/dist/index.js +670 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/index.mjs +662 -0
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/mmd/index.d.mts +113 -2
  38. package/dist/mmd/index.d.ts +113 -2
  39. package/dist/mmd/index.js +484 -2
  40. package/dist/mmd/index.js.map +1 -1
  41. package/dist/mmd/index.mjs +482 -4
  42. package/dist/mmd/index.mjs.map +1 -1
  43. package/dist/testYourself/admin/index.d.mts +58 -0
  44. package/dist/testYourself/admin/index.d.ts +58 -0
  45. package/dist/testYourself/admin/index.js +17 -0
  46. package/dist/testYourself/admin/index.js.map +1 -0
  47. package/dist/testYourself/admin/index.mjs +4 -0
  48. package/dist/testYourself/admin/index.mjs.map +1 -0
  49. package/dist/testYourself/index.d.mts +6 -98
  50. package/dist/testYourself/index.d.ts +6 -98
  51. package/dist/testYourself/index.js +90 -334
  52. package/dist/testYourself/index.js.map +1 -1
  53. package/dist/testYourself/index.mjs +47 -333
  54. package/dist/testYourself/index.mjs.map +1 -1
  55. package/dist/testYourself/server/index.d.mts +1029 -0
  56. package/dist/testYourself/server/index.d.ts +1029 -0
  57. package/dist/testYourself/server/index.js +42 -0
  58. package/dist/testYourself/server/index.js.map +1 -0
  59. package/dist/testYourself/server/index.mjs +5 -0
  60. package/dist/testYourself/server/index.mjs.map +1 -0
  61. package/dist/universalFile/server/index.js +5 -5
  62. package/dist/universalFile/server/index.mjs +1 -1
  63. package/package.json +62 -20
package/dist/mmd/index.js CHANGED
@@ -72,6 +72,9 @@ var loadAmmo = (path = "/libs/ammo.wasm.js") => {
72
72
  };
73
73
 
74
74
  // src/mmd/components/MMDPlayerBase.tsx
75
+ if (typeof window !== "undefined") {
76
+ THREE__namespace.Cache.enabled = true;
77
+ }
75
78
  async function waitForMaterialsReady(object, renderer, scene, camera) {
76
79
  const textures = [];
77
80
  let meshCount = 0;
@@ -2597,6 +2600,61 @@ var LoadingOverlay = ({
2597
2600
  ));
2598
2601
  };
2599
2602
  LoadingOverlay.displayName = "LoadingOverlay";
2603
+ var SkipConfirmDialog = ({
2604
+ onConfirm,
2605
+ onCancel
2606
+ }) => {
2607
+ const [isMounted, setIsMounted] = React6__default.default.useState(false);
2608
+ React6__default.default.useEffect(() => {
2609
+ setIsMounted(true);
2610
+ }, []);
2611
+ if (!isMounted) return null;
2612
+ const content = /* @__PURE__ */ React6__default.default.createElement(
2613
+ "div",
2614
+ {
2615
+ className: "fixed inset-0 flex items-center justify-center bg-black/40 backdrop-blur-sm pointer-events-auto",
2616
+ style: { zIndex: 999999 }
2617
+ },
2618
+ /* @__PURE__ */ React6__default.default.createElement(
2619
+ "div",
2620
+ {
2621
+ className: "p-8 rounded-3xl border border-white/30 shadow-2xl max-w-sm w-full mx-4 overflow-hidden relative",
2622
+ style: {
2623
+ background: `linear-gradient(135deg,
2624
+ rgba(255, 255, 255, 0.2) 0%,
2625
+ rgba(255, 255, 255, 0.1) 100%)`,
2626
+ backdropFilter: "blur(32px) saturate(200%)",
2627
+ WebkitBackdropFilter: "blur(32px) saturate(200%)"
2628
+ }
2629
+ },
2630
+ /* @__PURE__ */ React6__default.default.createElement("h3", { className: "text-xl font-bold text-white mb-4 drop-shadow-md" }, "\u52A8\u753B\u5C1A\u672A\u64AD\u653E\u5B8C\u6210"),
2631
+ /* @__PURE__ */ React6__default.default.createElement("p", { className: "text-white/90 mb-8 leading-relaxed drop-shadow-sm" }, "\u5F53\u524D\u573A\u666F\u7684\u52A8\u753B\u8FD8\u6CA1\u6709\u5B8C\u6574\u64AD\u653E\u4E00\u904D\uFF0C\u662F\u5426\u76F4\u63A5\u8DF3\u8F6C\u5230\u4E0B\u4E00\u4E2A\u573A\u666F\uFF1F"),
2632
+ /* @__PURE__ */ React6__default.default.createElement("div", { className: "flex justify-end gap-4" }, /* @__PURE__ */ React6__default.default.createElement(
2633
+ "button",
2634
+ {
2635
+ onClick: onCancel,
2636
+ className: "px-6 py-2.5 rounded-2xl text-white/80 font-medium hover:text-white hover:bg-white/10 transition-all border border-white/10 hover:border-white/30"
2637
+ },
2638
+ "\u53D6\u6D88"
2639
+ ), /* @__PURE__ */ React6__default.default.createElement(
2640
+ "button",
2641
+ {
2642
+ onClick: onConfirm,
2643
+ className: "px-6 py-2.5 rounded-2xl bg-white/20 hover:bg-white/30 text-white font-bold transition-all border border-white/40 shadow-lg hover:scale-105 active:scale-95"
2644
+ },
2645
+ "\u76F4\u63A5\u8DF3\u8F6C"
2646
+ ))
2647
+ )
2648
+ );
2649
+ let portalContainer = document.getElementById("dialogue-portal-root");
2650
+ if (!portalContainer) {
2651
+ portalContainer = document.createElement("div");
2652
+ portalContainer.id = "dialogue-portal-root";
2653
+ portalContainer.style.cssText = "position: fixed; inset: 0; pointer-events: none; z-index: 999999;";
2654
+ document.body.appendChild(portalContainer);
2655
+ }
2656
+ return reactDom.createPortal(content, portalContainer);
2657
+ };
2600
2658
 
2601
2659
  // src/mmd/visual-novel/MMDVisualNovel.tsx
2602
2660
  var MMDVisualNovel = React6.forwardRef(
@@ -2630,11 +2688,14 @@ var MMDVisualNovel = React6.forwardRef(
2630
2688
  const [showHistory, setShowHistory] = React6.useState(false);
2631
2689
  const [history, setHistory] = React6.useState([]);
2632
2690
  const [isStarted, setIsStarted] = React6.useState(autoStart);
2691
+ const [isVmdFinished, setIsVmdFinished] = React6.useState(false);
2692
+ const [pendingNodeIndex, setPendingNodeIndex] = React6.useState(null);
2633
2693
  const playerRef = React6.useRef(null);
2634
2694
  const containerRef = React6.useRef(null);
2635
2695
  const autoTimerRef = React6.useRef(null);
2636
2696
  const typingCompleteRef = React6.useRef(false);
2637
2697
  const isStartedRef = React6.useRef(autoStart);
2698
+ const lastAnimationTimeRef = React6.useRef(0);
2638
2699
  const currentNode = nodes[currentNodeIndex];
2639
2700
  const currentDialogue = currentNode?.dialogues[currentDialogueIndex] || null;
2640
2701
  const addToHistory = React6.useCallback((dialogue, nodeIndex, dialogueIndex) => {
@@ -2674,15 +2735,24 @@ var MMDVisualNovel = React6.forwardRef(
2674
2735
  }
2675
2736
  }, [currentNode, currentDialogueIndex, currentNodeIndex, nodes.length, loop, addToHistory, onDialogueChange, onScriptComplete]);
2676
2737
  const goToNode = React6.useCallback(
2677
- (nodeIndex) => {
2738
+ (nodeIndex, force = false) => {
2678
2739
  if (nodeIndex < 0 || nodeIndex >= nodes.length) return;
2679
2740
  if (isTransitioning) return;
2680
2741
  const node = nodes[nodeIndex];
2681
2742
  if (!node) return;
2743
+ const currentResources = nodes[currentNodeIndex]?.resources;
2744
+ if (!force && currentResources?.motionPath && !isVmdFinished) {
2745
+ console.log("[MMDVisualNovel] VMD not finished, showing confirmation");
2746
+ setPendingNodeIndex(nodeIndex);
2747
+ return;
2748
+ }
2682
2749
  console.log(`[MMDVisualNovel] Transitioning to node ${nodeIndex}`);
2683
2750
  setIsTransitioning(true);
2684
2751
  setIsLoading(true);
2685
2752
  setIsAnimationPlaying(false);
2753
+ setIsVmdFinished(false);
2754
+ setPendingNodeIndex(null);
2755
+ lastAnimationTimeRef.current = 0;
2686
2756
  setTimeout(() => {
2687
2757
  setCurrentNodeIndex(nodeIndex);
2688
2758
  setCurrentDialogueIndex(0);
@@ -2700,7 +2770,7 @@ var MMDVisualNovel = React6.forwardRef(
2700
2770
  }, 100);
2701
2771
  }, 300);
2702
2772
  },
2703
- [nodes, isTransitioning, addToHistory, onNodeChange, onDialogueChange]
2773
+ [nodes, isTransitioning, addToHistory, onNodeChange, onDialogueChange, currentNodeIndex, isVmdFinished]
2704
2774
  );
2705
2775
  const goToDialogue = React6.useCallback(
2706
2776
  (dialogueIndex) => {
@@ -2840,6 +2910,19 @@ var MMDVisualNovel = React6.forwardRef(
2840
2910
  console.log("[MMDVisualNovel] MMDPlayerBase onPlay called");
2841
2911
  setIsAnimationPlaying(true);
2842
2912
  },
2913
+ onTimeUpdate: (time) => {
2914
+ if (time < lastAnimationTimeRef.current && lastAnimationTimeRef.current > 0) {
2915
+ if (!isVmdFinished) {
2916
+ console.log("[MMDVisualNovel] VMD loop detected, marking as finished");
2917
+ setIsVmdFinished(true);
2918
+ }
2919
+ }
2920
+ lastAnimationTimeRef.current = time;
2921
+ },
2922
+ onEnded: () => {
2923
+ console.log("[MMDVisualNovel] VMD ended, marking as finished");
2924
+ setIsVmdFinished(true);
2925
+ },
2843
2926
  onError
2844
2927
  }
2845
2928
  )
@@ -2896,6 +2979,19 @@ var MMDVisualNovel = React6.forwardRef(
2896
2979
  }
2897
2980
  ) : null;
2898
2981
  })(),
2982
+ pendingNodeIndex !== null && /* @__PURE__ */ React6__default.default.createElement(
2983
+ SkipConfirmDialog,
2984
+ {
2985
+ onConfirm: () => {
2986
+ if (pendingNodeIndex !== null) {
2987
+ goToNode(pendingNodeIndex, true);
2988
+ }
2989
+ },
2990
+ onCancel: () => {
2991
+ setPendingNodeIndex(null);
2992
+ }
2993
+ }
2994
+ ),
2899
2995
  showHistory && /* @__PURE__ */ React6__default.default.createElement(
2900
2996
  HistoryPanel,
2901
2997
  {
@@ -2908,18 +3004,404 @@ var MMDVisualNovel = React6.forwardRef(
2908
3004
  }
2909
3005
  );
2910
3006
  MMDVisualNovel.displayName = "MMDVisualNovel";
3007
+ var MusicControls = ({
3008
+ isPlaying,
3009
+ currentTime,
3010
+ duration,
3011
+ loopMode,
3012
+ onPlayPause,
3013
+ onNext,
3014
+ onPrevious,
3015
+ onSeek,
3016
+ onToggleLoop,
3017
+ onTogglePlaylist,
3018
+ className = ""
3019
+ }) => {
3020
+ const formatTime = (seconds) => {
3021
+ const mins = Math.floor(seconds / 60);
3022
+ const secs = Math.floor(seconds % 60);
3023
+ return `${mins}:${secs.toString().padStart(2, "0")}`;
3024
+ };
3025
+ const progress = duration > 0 ? currentTime / duration * 100 : 0;
3026
+ return /* @__PURE__ */ React6__default.default.createElement(
3027
+ "div",
3028
+ {
3029
+ className: `w-full max-w-4xl mx-auto px-6 py-4 bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl shadow-2xl pointer-events-auto transition-all group ${className}`
3030
+ },
3031
+ /* @__PURE__ */ React6__default.default.createElement("div", { className: "relative w-full h-1.5 bg-white/20 rounded-full mb-4 cursor-pointer group/progress overflow-hidden" }, /* @__PURE__ */ React6__default.default.createElement(
3032
+ "div",
3033
+ {
3034
+ className: "absolute h-full bg-blue-500 rounded-full transition-all duration-300",
3035
+ style: { width: `${progress}%` }
3036
+ }
3037
+ ), /* @__PURE__ */ React6__default.default.createElement(
3038
+ "input",
3039
+ {
3040
+ type: "range",
3041
+ min: 0,
3042
+ max: duration || 100,
3043
+ value: currentTime,
3044
+ onChange: (e) => onSeek(Number(e.target.value)),
3045
+ className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer"
3046
+ }
3047
+ )),
3048
+ /* @__PURE__ */ React6__default.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React6__default.default.createElement("div", { className: "flex items-center gap-2 text-xs font-mono text-white/60 w-32" }, /* @__PURE__ */ React6__default.default.createElement("span", null, formatTime(currentTime)), /* @__PURE__ */ React6__default.default.createElement("span", null, "/"), /* @__PURE__ */ React6__default.default.createElement("span", null, formatTime(duration))), /* @__PURE__ */ React6__default.default.createElement("div", { className: "flex items-center gap-6" }, /* @__PURE__ */ React6__default.default.createElement(
3049
+ "button",
3050
+ {
3051
+ onClick: onPrevious,
3052
+ className: "text-white/80 hover:text-white transition-colors"
3053
+ },
3054
+ /* @__PURE__ */ React6__default.default.createElement(lucideReact.SkipBack, { className: "w-6 h-6 fill-current" })
3055
+ ), /* @__PURE__ */ React6__default.default.createElement(
3056
+ "button",
3057
+ {
3058
+ onClick: onPlayPause,
3059
+ className: "w-12 h-12 flex items-center justify-center bg-blue-500 hover:bg-blue-400 text-white rounded-full shadow-lg shadow-blue-500/20 transition-all hover:scale-105 active:scale-95"
3060
+ },
3061
+ isPlaying ? /* @__PURE__ */ React6__default.default.createElement(lucideReact.Pause, { className: "w-6 h-6 fill-current" }) : /* @__PURE__ */ React6__default.default.createElement(lucideReact.Play, { className: "w-6 h-6 fill-current ml-1" })
3062
+ ), /* @__PURE__ */ React6__default.default.createElement(
3063
+ "button",
3064
+ {
3065
+ onClick: onNext,
3066
+ className: "text-white/80 hover:text-white transition-colors"
3067
+ },
3068
+ /* @__PURE__ */ React6__default.default.createElement(lucideReact.SkipForward, { className: "w-6 h-6 fill-current" })
3069
+ )), /* @__PURE__ */ React6__default.default.createElement("div", { className: "flex items-center gap-4 w-32 justify-end" }, /* @__PURE__ */ React6__default.default.createElement(
3070
+ "button",
3071
+ {
3072
+ onClick: onToggleLoop,
3073
+ className: "text-white/60 hover:text-white transition-colors",
3074
+ title: loopMode
3075
+ },
3076
+ loopMode === "list" && /* @__PURE__ */ React6__default.default.createElement(lucideReact.Repeat, { className: "w-5 h-5" }),
3077
+ loopMode === "single" && /* @__PURE__ */ React6__default.default.createElement(lucideReact.Repeat1, { className: "w-5 h-5 text-blue-400" }),
3078
+ loopMode === "shuffle" && /* @__PURE__ */ React6__default.default.createElement(lucideReact.Shuffle, { className: "w-5 h-5 text-orange-400" })
3079
+ ), /* @__PURE__ */ React6__default.default.createElement(
3080
+ "button",
3081
+ {
3082
+ onClick: onTogglePlaylist,
3083
+ className: "text-white/60 hover:text-white transition-colors"
3084
+ },
3085
+ /* @__PURE__ */ React6__default.default.createElement(lucideReact.ListMusic, { className: "w-5 h-5" })
3086
+ )))
3087
+ );
3088
+ };
3089
+ var PlaylistPanel = ({
3090
+ tracks,
3091
+ currentIndex,
3092
+ isOpen,
3093
+ onClose,
3094
+ onSelectTrack,
3095
+ className = ""
3096
+ }) => {
3097
+ if (!isOpen) return null;
3098
+ return /* @__PURE__ */ React6__default.default.createElement(
3099
+ "div",
3100
+ {
3101
+ className: `fixed inset-y-0 right-0 w-80 bg-gray-900/90 backdrop-blur-2xl border-l border-white/10 shadow-2xl z-50 flex flex-col pointer-events-auto transform transition-transform duration-500 ease-out ${isOpen ? "translate-x-0" : "translate-x-full"} ${className}`
3102
+ },
3103
+ /* @__PURE__ */ React6__default.default.createElement("div", { className: "flex items-center justify-between p-6 border-b border-white/10" }, /* @__PURE__ */ React6__default.default.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React6__default.default.createElement(lucideReact.Music, { className: "w-5 h-5 text-blue-400" }), /* @__PURE__ */ React6__default.default.createElement("h3", { className: "text-lg font-bold text-white" }, "\u64AD\u653E\u5217\u8868")), /* @__PURE__ */ React6__default.default.createElement(
3104
+ "button",
3105
+ {
3106
+ onClick: onClose,
3107
+ className: "p-2 text-white/60 hover:text-white transition-colors"
3108
+ },
3109
+ /* @__PURE__ */ React6__default.default.createElement(lucideReact.X, { className: "w-5 h-5" })
3110
+ )),
3111
+ /* @__PURE__ */ React6__default.default.createElement("div", { className: "flex-1 overflow-y-auto p-4 space-y-2 custom-scrollbar" }, tracks.map((track, index) => {
3112
+ const isActive = index === currentIndex;
3113
+ return /* @__PURE__ */ React6__default.default.createElement(
3114
+ "button",
3115
+ {
3116
+ key: track.id,
3117
+ onClick: () => onSelectTrack(index),
3118
+ className: `w-full flex items-center gap-4 p-3 rounded-xl transition-all group ${isActive ? "bg-blue-500/20 border border-blue-500/30" : "hover:bg-white/5 border border-transparent"}`
3119
+ },
3120
+ /* @__PURE__ */ React6__default.default.createElement("div", { className: "relative w-12 h-12 flex-shrink-0 rounded-lg overflow-hidden bg-gray-800" }, track.coverUrl ? /* @__PURE__ */ React6__default.default.createElement("img", { src: track.coverUrl, alt: track.title, className: "w-full h-full object-cover" }) : /* @__PURE__ */ React6__default.default.createElement("div", { className: "w-full h-full flex items-center justify-center text-white/20" }, /* @__PURE__ */ React6__default.default.createElement(lucideReact.Music, { className: "w-6 h-6" })), isActive && /* @__PURE__ */ React6__default.default.createElement("div", { className: "absolute inset-0 bg-blue-500/40 flex items-center justify-center" }, /* @__PURE__ */ React6__default.default.createElement("div", { className: "flex gap-1 items-end h-4" }, /* @__PURE__ */ React6__default.default.createElement("div", { className: "w-1 bg-white animate-music-bar-1" }), /* @__PURE__ */ React6__default.default.createElement("div", { className: "w-1 bg-white animate-music-bar-2" }), /* @__PURE__ */ React6__default.default.createElement("div", { className: "w-1 bg-white animate-music-bar-3" })))),
3121
+ /* @__PURE__ */ React6__default.default.createElement("div", { className: "flex-1 text-left min-w-0" }, /* @__PURE__ */ React6__default.default.createElement("h4", { className: `text-sm font-bold truncate ${isActive ? "text-blue-400" : "text-white/90"}` }, track.title), /* @__PURE__ */ React6__default.default.createElement("p", { className: "text-xs text-white/40 truncate mt-0.5" }, track.artist || "\u672A\u77E5\u827A\u672F\u5BB6")),
3122
+ !isActive && /* @__PURE__ */ React6__default.default.createElement("div", { className: "opacity-0 group-hover:opacity-100 transition-opacity" }, /* @__PURE__ */ React6__default.default.createElement(lucideReact.Play, { className: "w-4 h-4 text-white/40 fill-current" }))
3123
+ );
3124
+ })),
3125
+ /* @__PURE__ */ React6__default.default.createElement("div", { className: "p-6 border-t border-white/10" }, /* @__PURE__ */ React6__default.default.createElement("p", { className: "text-xs text-gray-500 text-center" }, "\u5171 ", tracks.length, " \u9996\u66F2\u76EE")),
3126
+ /* @__PURE__ */ React6__default.default.createElement("style", { dangerouslySetInnerHTML: { __html: `
3127
+ .custom-scrollbar::-webkit-scrollbar {
3128
+ width: 4px;
3129
+ }
3130
+ .custom-scrollbar::-webkit-scrollbar-track {
3131
+ background: transparent;
3132
+ }
3133
+ .custom-scrollbar::-webkit-scrollbar-thumb {
3134
+ background: rgba(255, 255, 255, 0.1);
3135
+ border-radius: 10px;
3136
+ }
3137
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
3138
+ background: rgba(255, 255, 255, 0.2);
3139
+ }
3140
+ @keyframes music-bar-1 {
3141
+ 0%, 100% { height: 4px; }
3142
+ 50% { height: 12px; }
3143
+ }
3144
+ @keyframes music-bar-2 {
3145
+ 0%, 100% { height: 8px; }
3146
+ 50% { height: 16px; }
3147
+ }
3148
+ @keyframes music-bar-3 {
3149
+ 0%, 100% { height: 6px; }
3150
+ 50% { height: 14px; }
3151
+ }
3152
+ .animate-music-bar-1 { animation: music-bar-1 0.6s infinite; }
3153
+ .animate-music-bar-2 { animation: music-bar-2 0.8s infinite; }
3154
+ .animate-music-bar-3 { animation: music-bar-3 0.7s infinite; }
3155
+ ` } })
3156
+ );
3157
+ };
3158
+ var TrackInfo = ({ track, className = "" }) => {
3159
+ return /* @__PURE__ */ React6__default.default.createElement("div", { className: `flex flex-col items-center text-center gap-2 ${className}` }, /* @__PURE__ */ React6__default.default.createElement("div", { className: "px-4 py-1.5 bg-black/40 backdrop-blur-md rounded-full border border-white/10 shadow-lg" }, /* @__PURE__ */ React6__default.default.createElement("h2", { className: "text-lg font-bold text-white tracking-wider truncate max-w-md" }, track.title)), /* @__PURE__ */ React6__default.default.createElement("p", { className: "text-sm font-medium text-white/60 drop-shadow-md" }, track.artist || "\u672A\u77E5\u827A\u672F\u5BB6"));
3160
+ };
3161
+
3162
+ // src/mmd/music-player/MMDMusicPlayer.tsx
3163
+ var MMDMusicPlayer = React6.forwardRef(
3164
+ ({
3165
+ config,
3166
+ stage,
3167
+ mobileOptimization,
3168
+ initialTrackIndex = 0,
3169
+ onTrackChange,
3170
+ onPlayPause,
3171
+ onProgress,
3172
+ onError,
3173
+ className,
3174
+ style
3175
+ }, ref) => {
3176
+ const { tracks, autoPlay = false, defaultLoopMode = "list" } = config;
3177
+ const [currentIndex, setCurrentIndex] = React6.useState(initialTrackIndex);
3178
+ const [isPlaying, setIsPlaying] = React6.useState(autoPlay);
3179
+ const [isLoading, setIsLoading] = React6.useState(true);
3180
+ const [isTransitioning, setIsTransitioning] = React6.useState(false);
3181
+ const [currentTime, setCurrentTime] = React6.useState(0);
3182
+ const [duration, setDuration] = React6.useState(0);
3183
+ const [loopMode, setLoopMode] = React6.useState(defaultLoopMode);
3184
+ const [showPlaylist, setShowPlaylist] = React6.useState(false);
3185
+ const [isUIVisible, setIsUIVisible] = React6.useState(true);
3186
+ const playerRef = React6.useRef(null);
3187
+ const containerRef = React6.useRef(null);
3188
+ const isStartedRef = React6.useRef(autoPlay);
3189
+ const uiTimeoutRef = React6.useRef(null);
3190
+ const currentTrack = tracks[currentIndex];
3191
+ const goToTrack = React6.useCallback(
3192
+ (index) => {
3193
+ if (index < 0 || index >= tracks.length) return;
3194
+ if (isTransitioning) return;
3195
+ const track = tracks[index];
3196
+ if (!track) return;
3197
+ console.log(`[MMDMusicPlayer] Transitioning to track ${index}: ${track.title}`);
3198
+ setIsTransitioning(true);
3199
+ setIsLoading(true);
3200
+ const wasPlaying = isPlaying;
3201
+ setIsPlaying(false);
3202
+ setTimeout(() => {
3203
+ setCurrentIndex(index);
3204
+ setCurrentTime(0);
3205
+ setDuration(0);
3206
+ onTrackChange?.(track, index);
3207
+ setTimeout(() => {
3208
+ setIsTransitioning(false);
3209
+ if (wasPlaying) {
3210
+ isStartedRef.current = true;
3211
+ }
3212
+ }, 100);
3213
+ }, 300);
3214
+ },
3215
+ [tracks, isTransitioning, isPlaying, onTrackChange]
3216
+ );
3217
+ const next = React6.useCallback(() => {
3218
+ let nextIndex = currentIndex + 1;
3219
+ if (loopMode === "shuffle") {
3220
+ nextIndex = Math.floor(Math.random() * tracks.length);
3221
+ } else if (nextIndex >= tracks.length) {
3222
+ nextIndex = 0;
3223
+ }
3224
+ goToTrack(nextIndex);
3225
+ }, [currentIndex, tracks.length, loopMode, goToTrack]);
3226
+ const previous = React6.useCallback(() => {
3227
+ let prevIndex = currentIndex - 1;
3228
+ if (prevIndex < 0) {
3229
+ prevIndex = tracks.length - 1;
3230
+ }
3231
+ goToTrack(prevIndex);
3232
+ }, [currentIndex, tracks.length, goToTrack]);
3233
+ React6.useImperativeHandle(
3234
+ ref,
3235
+ () => ({
3236
+ play: () => {
3237
+ setIsPlaying(true);
3238
+ isStartedRef.current = true;
3239
+ playerRef.current?.play();
3240
+ },
3241
+ pause: () => {
3242
+ setIsPlaying(false);
3243
+ isStartedRef.current = false;
3244
+ playerRef.current?.pause();
3245
+ },
3246
+ next,
3247
+ previous,
3248
+ goToTrack,
3249
+ setLoopMode,
3250
+ getState: () => ({
3251
+ currentIndex,
3252
+ isPlaying,
3253
+ currentTime,
3254
+ duration,
3255
+ loopMode
3256
+ })
3257
+ }),
3258
+ [next, previous, goToTrack, currentIndex, isPlaying, currentTime, duration, loopMode]
3259
+ );
3260
+ const handleEnded = React6.useCallback(() => {
3261
+ if (loopMode === "single") {
3262
+ playerRef.current?.seek(0);
3263
+ playerRef.current?.play();
3264
+ } else {
3265
+ next();
3266
+ }
3267
+ }, [loopMode, next]);
3268
+ const handleTimeUpdate = React6.useCallback((time) => {
3269
+ setCurrentTime(time);
3270
+ if (playerRef.current) {
3271
+ const total = playerRef.current.getDuration();
3272
+ setDuration(total);
3273
+ onProgress?.(time, total);
3274
+ }
3275
+ }, [onProgress]);
3276
+ const resetUITimeout = React6.useCallback(() => {
3277
+ setIsUIVisible(true);
3278
+ if (uiTimeoutRef.current) clearTimeout(uiTimeoutRef.current);
3279
+ if (isPlaying) {
3280
+ uiTimeoutRef.current = setTimeout(() => {
3281
+ if (!showPlaylist) setIsUIVisible(false);
3282
+ }, 5e3);
3283
+ }
3284
+ }, [isPlaying, showPlaylist]);
3285
+ React6.useEffect(() => {
3286
+ resetUITimeout();
3287
+ return () => {
3288
+ if (uiTimeoutRef.current) clearTimeout(uiTimeoutRef.current);
3289
+ };
3290
+ }, [resetUITimeout]);
3291
+ if (!currentTrack) {
3292
+ return /* @__PURE__ */ React6__default.default.createElement("div", { className: "flex h-full w-full items-center justify-center bg-black text-white" }, "\u64AD\u653E\u5217\u8868\u4E3A\u7A7A");
3293
+ }
3294
+ return /* @__PURE__ */ React6__default.default.createElement(
3295
+ "div",
3296
+ {
3297
+ ref: containerRef,
3298
+ className: `relative bg-black group ${className}`,
3299
+ style: { width: "100%", height: "100%", overflow: "hidden", ...style },
3300
+ onMouseMove: resetUITimeout,
3301
+ onClick: resetUITimeout
3302
+ },
3303
+ /* @__PURE__ */ React6__default.default.createElement(
3304
+ "div",
3305
+ {
3306
+ className: "absolute inset-0 w-full h-full",
3307
+ style: {
3308
+ zIndex: 0,
3309
+ opacity: isLoading || isTransitioning ? 0 : 1,
3310
+ transition: "opacity 0.5s ease-in-out"
3311
+ }
3312
+ },
3313
+ !isTransitioning && /* @__PURE__ */ React6__default.default.createElement(
3314
+ MMDPlayerBase,
3315
+ {
3316
+ key: currentTrack.id,
3317
+ ref: playerRef,
3318
+ resources: currentTrack.resources,
3319
+ stage,
3320
+ autoPlay: isStartedRef.current,
3321
+ loop: loopMode === "single",
3322
+ mobileOptimization,
3323
+ onLoad: () => {
3324
+ console.log("[MMDMusicPlayer] Track loaded");
3325
+ setIsLoading(false);
3326
+ if (isStartedRef.current) {
3327
+ playerRef.current?.play();
3328
+ setIsPlaying(true);
3329
+ }
3330
+ },
3331
+ onPlay: () => {
3332
+ setIsPlaying(true);
3333
+ onPlayPause?.(true);
3334
+ },
3335
+ onPause: () => {
3336
+ setIsPlaying(false);
3337
+ onPlayPause?.(false);
3338
+ },
3339
+ onTimeUpdate: handleTimeUpdate,
3340
+ onEnded: handleEnded,
3341
+ onError
3342
+ }
3343
+ )
3344
+ ),
3345
+ (isLoading || isTransitioning) && /* @__PURE__ */ React6__default.default.createElement("div", { className: "absolute inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-md" }, /* @__PURE__ */ React6__default.default.createElement("div", { className: "flex flex-col items-center gap-4" }, /* @__PURE__ */ React6__default.default.createElement("div", { className: "h-12 w-12 animate-spin rounded-full border-4 border-white/20 border-t-blue-500" }), /* @__PURE__ */ React6__default.default.createElement("div", { className: "text-white font-medium" }, isTransitioning ? "\u51C6\u5907\u4E0B\u4E00\u9996..." : "\u6B63\u5728\u52A0\u8F7D\u821E\u53F0..."))),
3346
+ /* @__PURE__ */ React6__default.default.createElement(
3347
+ "div",
3348
+ {
3349
+ className: `absolute inset-0 z-10 flex flex-col justify-between transition-opacity duration-700 pointer-events-none ${isUIVisible ? "opacity-100" : "opacity-0"}`
3350
+ },
3351
+ /* @__PURE__ */ React6__default.default.createElement("div", { className: "pt-12 px-8 flex justify-center" }, /* @__PURE__ */ React6__default.default.createElement(TrackInfo, { track: currentTrack })),
3352
+ /* @__PURE__ */ React6__default.default.createElement("div", { className: "pb-12 px-8" }, /* @__PURE__ */ React6__default.default.createElement(
3353
+ MusicControls,
3354
+ {
3355
+ isPlaying,
3356
+ currentTime,
3357
+ duration,
3358
+ loopMode,
3359
+ onPlayPause: () => isPlaying ? playerRef.current?.pause() : playerRef.current?.play(),
3360
+ onNext: next,
3361
+ onPrevious: previous,
3362
+ onSeek: (time) => playerRef.current?.seek(time),
3363
+ onToggleLoop: () => {
3364
+ const modes = ["list", "single", "shuffle"];
3365
+ const nextMode = modes[(modes.indexOf(loopMode) + 1) % modes.length];
3366
+ setLoopMode(nextMode);
3367
+ },
3368
+ onTogglePlaylist: () => setShowPlaylist(!showPlaylist)
3369
+ }
3370
+ ))
3371
+ ),
3372
+ /* @__PURE__ */ React6__default.default.createElement(
3373
+ PlaylistPanel,
3374
+ {
3375
+ tracks,
3376
+ currentIndex,
3377
+ isOpen: showPlaylist,
3378
+ onClose: () => setShowPlaylist(false),
3379
+ onSelectTrack: (index) => {
3380
+ goToTrack(index);
3381
+ setShowPlaylist(false);
3382
+ }
3383
+ }
3384
+ )
3385
+ );
3386
+ }
3387
+ );
3388
+ MMDMusicPlayer.displayName = "MMDMusicPlayer";
2911
3389
 
2912
3390
  exports.DialogueBox = DialogueBox;
2913
3391
  exports.HistoryPanel = HistoryPanel;
2914
3392
  exports.LoadingOverlay = LoadingOverlay;
2915
3393
  exports.LoadingScreen = LoadingScreen;
3394
+ exports.MMDMusicPlayer = MMDMusicPlayer;
2916
3395
  exports.MMDPlayerBase = MMDPlayerBase;
2917
3396
  exports.MMDPlayerEnhanced = MMDPlayerEnhanced;
2918
3397
  exports.MMDPlayerEnhancedDebugInfo = MMDPlayerEnhancedDebugInfo;
2919
3398
  exports.MMDPlaylist = MMDPlaylist;
2920
3399
  exports.MMDPlaylistDebugInfo = MMDPlaylistDebugInfo;
2921
3400
  exports.MMDVisualNovel = MMDVisualNovel;
3401
+ exports.MusicControls = MusicControls;
3402
+ exports.PlaylistPanel = PlaylistPanel;
2922
3403
  exports.StartScreen = StartScreen;
3404
+ exports.TrackInfo = TrackInfo;
2923
3405
  exports.loadAmmo = loadAmmo;
2924
3406
  //# sourceMappingURL=index.js.map
2925
3407
  //# sourceMappingURL=index.js.map