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
@@ -2,7 +2,7 @@ import '../chunk-BJTO5JO5.mjs';
2
2
  import React6, { forwardRef, useRef, useImperativeHandle, useEffect, useState, useCallback } from 'react';
3
3
  import * as THREE from 'three';
4
4
  import { OrbitControls, MMDLoader, MMDAnimationHelper } from 'three-stdlib';
5
- import { SkipBack, Pause, Play, SkipForward, Repeat, Repeat1, Grid3x3, Settings, Minimize, Maximize, X, Video, Check, User, Image, Music } from 'lucide-react';
5
+ import { SkipBack, Pause, Play, SkipForward, Repeat, Repeat1, Shuffle, ListMusic, Music, X, Grid3x3, Settings, Minimize, Maximize, Video, Check, User, Image } from 'lucide-react';
6
6
  import { createPortal } from 'react-dom';
7
7
 
8
8
  // src/mmd/utils/ammo-loader.ts
@@ -47,6 +47,9 @@ var loadAmmo = (path = "/libs/ammo.wasm.js") => {
47
47
  };
48
48
 
49
49
  // src/mmd/components/MMDPlayerBase.tsx
50
+ if (typeof window !== "undefined") {
51
+ THREE.Cache.enabled = true;
52
+ }
50
53
  async function waitForMaterialsReady(object, renderer, scene, camera) {
51
54
  const textures = [];
52
55
  let meshCount = 0;
@@ -2572,6 +2575,61 @@ var LoadingOverlay = ({
2572
2575
  ));
2573
2576
  };
2574
2577
  LoadingOverlay.displayName = "LoadingOverlay";
2578
+ var SkipConfirmDialog = ({
2579
+ onConfirm,
2580
+ onCancel
2581
+ }) => {
2582
+ const [isMounted, setIsMounted] = React6.useState(false);
2583
+ React6.useEffect(() => {
2584
+ setIsMounted(true);
2585
+ }, []);
2586
+ if (!isMounted) return null;
2587
+ const content = /* @__PURE__ */ React6.createElement(
2588
+ "div",
2589
+ {
2590
+ className: "fixed inset-0 flex items-center justify-center bg-black/40 backdrop-blur-sm pointer-events-auto",
2591
+ style: { zIndex: 999999 }
2592
+ },
2593
+ /* @__PURE__ */ React6.createElement(
2594
+ "div",
2595
+ {
2596
+ className: "p-8 rounded-3xl border border-white/30 shadow-2xl max-w-sm w-full mx-4 overflow-hidden relative",
2597
+ style: {
2598
+ background: `linear-gradient(135deg,
2599
+ rgba(255, 255, 255, 0.2) 0%,
2600
+ rgba(255, 255, 255, 0.1) 100%)`,
2601
+ backdropFilter: "blur(32px) saturate(200%)",
2602
+ WebkitBackdropFilter: "blur(32px) saturate(200%)"
2603
+ }
2604
+ },
2605
+ /* @__PURE__ */ React6.createElement("h3", { className: "text-xl font-bold text-white mb-4 drop-shadow-md" }, "\u52A8\u753B\u5C1A\u672A\u64AD\u653E\u5B8C\u6210"),
2606
+ /* @__PURE__ */ React6.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"),
2607
+ /* @__PURE__ */ React6.createElement("div", { className: "flex justify-end gap-4" }, /* @__PURE__ */ React6.createElement(
2608
+ "button",
2609
+ {
2610
+ onClick: onCancel,
2611
+ 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"
2612
+ },
2613
+ "\u53D6\u6D88"
2614
+ ), /* @__PURE__ */ React6.createElement(
2615
+ "button",
2616
+ {
2617
+ onClick: onConfirm,
2618
+ 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"
2619
+ },
2620
+ "\u76F4\u63A5\u8DF3\u8F6C"
2621
+ ))
2622
+ )
2623
+ );
2624
+ let portalContainer = document.getElementById("dialogue-portal-root");
2625
+ if (!portalContainer) {
2626
+ portalContainer = document.createElement("div");
2627
+ portalContainer.id = "dialogue-portal-root";
2628
+ portalContainer.style.cssText = "position: fixed; inset: 0; pointer-events: none; z-index: 999999;";
2629
+ document.body.appendChild(portalContainer);
2630
+ }
2631
+ return createPortal(content, portalContainer);
2632
+ };
2575
2633
 
2576
2634
  // src/mmd/visual-novel/MMDVisualNovel.tsx
2577
2635
  var MMDVisualNovel = forwardRef(
@@ -2605,11 +2663,14 @@ var MMDVisualNovel = forwardRef(
2605
2663
  const [showHistory, setShowHistory] = useState(false);
2606
2664
  const [history, setHistory] = useState([]);
2607
2665
  const [isStarted, setIsStarted] = useState(autoStart);
2666
+ const [isVmdFinished, setIsVmdFinished] = useState(false);
2667
+ const [pendingNodeIndex, setPendingNodeIndex] = useState(null);
2608
2668
  const playerRef = useRef(null);
2609
2669
  const containerRef = useRef(null);
2610
2670
  const autoTimerRef = useRef(null);
2611
2671
  const typingCompleteRef = useRef(false);
2612
2672
  const isStartedRef = useRef(autoStart);
2673
+ const lastAnimationTimeRef = useRef(0);
2613
2674
  const currentNode = nodes[currentNodeIndex];
2614
2675
  const currentDialogue = currentNode?.dialogues[currentDialogueIndex] || null;
2615
2676
  const addToHistory = useCallback((dialogue, nodeIndex, dialogueIndex) => {
@@ -2649,15 +2710,24 @@ var MMDVisualNovel = forwardRef(
2649
2710
  }
2650
2711
  }, [currentNode, currentDialogueIndex, currentNodeIndex, nodes.length, loop, addToHistory, onDialogueChange, onScriptComplete]);
2651
2712
  const goToNode = useCallback(
2652
- (nodeIndex) => {
2713
+ (nodeIndex, force = false) => {
2653
2714
  if (nodeIndex < 0 || nodeIndex >= nodes.length) return;
2654
2715
  if (isTransitioning) return;
2655
2716
  const node = nodes[nodeIndex];
2656
2717
  if (!node) return;
2718
+ const currentResources = nodes[currentNodeIndex]?.resources;
2719
+ if (!force && currentResources?.motionPath && !isVmdFinished) {
2720
+ console.log("[MMDVisualNovel] VMD not finished, showing confirmation");
2721
+ setPendingNodeIndex(nodeIndex);
2722
+ return;
2723
+ }
2657
2724
  console.log(`[MMDVisualNovel] Transitioning to node ${nodeIndex}`);
2658
2725
  setIsTransitioning(true);
2659
2726
  setIsLoading(true);
2660
2727
  setIsAnimationPlaying(false);
2728
+ setIsVmdFinished(false);
2729
+ setPendingNodeIndex(null);
2730
+ lastAnimationTimeRef.current = 0;
2661
2731
  setTimeout(() => {
2662
2732
  setCurrentNodeIndex(nodeIndex);
2663
2733
  setCurrentDialogueIndex(0);
@@ -2675,7 +2745,7 @@ var MMDVisualNovel = forwardRef(
2675
2745
  }, 100);
2676
2746
  }, 300);
2677
2747
  },
2678
- [nodes, isTransitioning, addToHistory, onNodeChange, onDialogueChange]
2748
+ [nodes, isTransitioning, addToHistory, onNodeChange, onDialogueChange, currentNodeIndex, isVmdFinished]
2679
2749
  );
2680
2750
  const goToDialogue = useCallback(
2681
2751
  (dialogueIndex) => {
@@ -2815,6 +2885,19 @@ var MMDVisualNovel = forwardRef(
2815
2885
  console.log("[MMDVisualNovel] MMDPlayerBase onPlay called");
2816
2886
  setIsAnimationPlaying(true);
2817
2887
  },
2888
+ onTimeUpdate: (time) => {
2889
+ if (time < lastAnimationTimeRef.current && lastAnimationTimeRef.current > 0) {
2890
+ if (!isVmdFinished) {
2891
+ console.log("[MMDVisualNovel] VMD loop detected, marking as finished");
2892
+ setIsVmdFinished(true);
2893
+ }
2894
+ }
2895
+ lastAnimationTimeRef.current = time;
2896
+ },
2897
+ onEnded: () => {
2898
+ console.log("[MMDVisualNovel] VMD ended, marking as finished");
2899
+ setIsVmdFinished(true);
2900
+ },
2818
2901
  onError
2819
2902
  }
2820
2903
  )
@@ -2871,6 +2954,19 @@ var MMDVisualNovel = forwardRef(
2871
2954
  }
2872
2955
  ) : null;
2873
2956
  })(),
2957
+ pendingNodeIndex !== null && /* @__PURE__ */ React6.createElement(
2958
+ SkipConfirmDialog,
2959
+ {
2960
+ onConfirm: () => {
2961
+ if (pendingNodeIndex !== null) {
2962
+ goToNode(pendingNodeIndex, true);
2963
+ }
2964
+ },
2965
+ onCancel: () => {
2966
+ setPendingNodeIndex(null);
2967
+ }
2968
+ }
2969
+ ),
2874
2970
  showHistory && /* @__PURE__ */ React6.createElement(
2875
2971
  HistoryPanel,
2876
2972
  {
@@ -2883,7 +2979,389 @@ var MMDVisualNovel = forwardRef(
2883
2979
  }
2884
2980
  );
2885
2981
  MMDVisualNovel.displayName = "MMDVisualNovel";
2982
+ var MusicControls = ({
2983
+ isPlaying,
2984
+ currentTime,
2985
+ duration,
2986
+ loopMode,
2987
+ onPlayPause,
2988
+ onNext,
2989
+ onPrevious,
2990
+ onSeek,
2991
+ onToggleLoop,
2992
+ onTogglePlaylist,
2993
+ className = ""
2994
+ }) => {
2995
+ const formatTime = (seconds) => {
2996
+ const mins = Math.floor(seconds / 60);
2997
+ const secs = Math.floor(seconds % 60);
2998
+ return `${mins}:${secs.toString().padStart(2, "0")}`;
2999
+ };
3000
+ const progress = duration > 0 ? currentTime / duration * 100 : 0;
3001
+ return /* @__PURE__ */ React6.createElement(
3002
+ "div",
3003
+ {
3004
+ 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}`
3005
+ },
3006
+ /* @__PURE__ */ React6.createElement("div", { className: "relative w-full h-1.5 bg-white/20 rounded-full mb-4 cursor-pointer group/progress overflow-hidden" }, /* @__PURE__ */ React6.createElement(
3007
+ "div",
3008
+ {
3009
+ className: "absolute h-full bg-blue-500 rounded-full transition-all duration-300",
3010
+ style: { width: `${progress}%` }
3011
+ }
3012
+ ), /* @__PURE__ */ React6.createElement(
3013
+ "input",
3014
+ {
3015
+ type: "range",
3016
+ min: 0,
3017
+ max: duration || 100,
3018
+ value: currentTime,
3019
+ onChange: (e) => onSeek(Number(e.target.value)),
3020
+ className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer"
3021
+ }
3022
+ )),
3023
+ /* @__PURE__ */ React6.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React6.createElement("div", { className: "flex items-center gap-2 text-xs font-mono text-white/60 w-32" }, /* @__PURE__ */ React6.createElement("span", null, formatTime(currentTime)), /* @__PURE__ */ React6.createElement("span", null, "/"), /* @__PURE__ */ React6.createElement("span", null, formatTime(duration))), /* @__PURE__ */ React6.createElement("div", { className: "flex items-center gap-6" }, /* @__PURE__ */ React6.createElement(
3024
+ "button",
3025
+ {
3026
+ onClick: onPrevious,
3027
+ className: "text-white/80 hover:text-white transition-colors"
3028
+ },
3029
+ /* @__PURE__ */ React6.createElement(SkipBack, { className: "w-6 h-6 fill-current" })
3030
+ ), /* @__PURE__ */ React6.createElement(
3031
+ "button",
3032
+ {
3033
+ onClick: onPlayPause,
3034
+ 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"
3035
+ },
3036
+ isPlaying ? /* @__PURE__ */ React6.createElement(Pause, { className: "w-6 h-6 fill-current" }) : /* @__PURE__ */ React6.createElement(Play, { className: "w-6 h-6 fill-current ml-1" })
3037
+ ), /* @__PURE__ */ React6.createElement(
3038
+ "button",
3039
+ {
3040
+ onClick: onNext,
3041
+ className: "text-white/80 hover:text-white transition-colors"
3042
+ },
3043
+ /* @__PURE__ */ React6.createElement(SkipForward, { className: "w-6 h-6 fill-current" })
3044
+ )), /* @__PURE__ */ React6.createElement("div", { className: "flex items-center gap-4 w-32 justify-end" }, /* @__PURE__ */ React6.createElement(
3045
+ "button",
3046
+ {
3047
+ onClick: onToggleLoop,
3048
+ className: "text-white/60 hover:text-white transition-colors",
3049
+ title: loopMode
3050
+ },
3051
+ loopMode === "list" && /* @__PURE__ */ React6.createElement(Repeat, { className: "w-5 h-5" }),
3052
+ loopMode === "single" && /* @__PURE__ */ React6.createElement(Repeat1, { className: "w-5 h-5 text-blue-400" }),
3053
+ loopMode === "shuffle" && /* @__PURE__ */ React6.createElement(Shuffle, { className: "w-5 h-5 text-orange-400" })
3054
+ ), /* @__PURE__ */ React6.createElement(
3055
+ "button",
3056
+ {
3057
+ onClick: onTogglePlaylist,
3058
+ className: "text-white/60 hover:text-white transition-colors"
3059
+ },
3060
+ /* @__PURE__ */ React6.createElement(ListMusic, { className: "w-5 h-5" })
3061
+ )))
3062
+ );
3063
+ };
3064
+ var PlaylistPanel = ({
3065
+ tracks,
3066
+ currentIndex,
3067
+ isOpen,
3068
+ onClose,
3069
+ onSelectTrack,
3070
+ className = ""
3071
+ }) => {
3072
+ if (!isOpen) return null;
3073
+ return /* @__PURE__ */ React6.createElement(
3074
+ "div",
3075
+ {
3076
+ 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}`
3077
+ },
3078
+ /* @__PURE__ */ React6.createElement("div", { className: "flex items-center justify-between p-6 border-b border-white/10" }, /* @__PURE__ */ React6.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React6.createElement(Music, { className: "w-5 h-5 text-blue-400" }), /* @__PURE__ */ React6.createElement("h3", { className: "text-lg font-bold text-white" }, "\u64AD\u653E\u5217\u8868")), /* @__PURE__ */ React6.createElement(
3079
+ "button",
3080
+ {
3081
+ onClick: onClose,
3082
+ className: "p-2 text-white/60 hover:text-white transition-colors"
3083
+ },
3084
+ /* @__PURE__ */ React6.createElement(X, { className: "w-5 h-5" })
3085
+ )),
3086
+ /* @__PURE__ */ React6.createElement("div", { className: "flex-1 overflow-y-auto p-4 space-y-2 custom-scrollbar" }, tracks.map((track, index) => {
3087
+ const isActive = index === currentIndex;
3088
+ return /* @__PURE__ */ React6.createElement(
3089
+ "button",
3090
+ {
3091
+ key: track.id,
3092
+ onClick: () => onSelectTrack(index),
3093
+ 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"}`
3094
+ },
3095
+ /* @__PURE__ */ React6.createElement("div", { className: "relative w-12 h-12 flex-shrink-0 rounded-lg overflow-hidden bg-gray-800" }, track.coverUrl ? /* @__PURE__ */ React6.createElement("img", { src: track.coverUrl, alt: track.title, className: "w-full h-full object-cover" }) : /* @__PURE__ */ React6.createElement("div", { className: "w-full h-full flex items-center justify-center text-white/20" }, /* @__PURE__ */ React6.createElement(Music, { className: "w-6 h-6" })), isActive && /* @__PURE__ */ React6.createElement("div", { className: "absolute inset-0 bg-blue-500/40 flex items-center justify-center" }, /* @__PURE__ */ React6.createElement("div", { className: "flex gap-1 items-end h-4" }, /* @__PURE__ */ React6.createElement("div", { className: "w-1 bg-white animate-music-bar-1" }), /* @__PURE__ */ React6.createElement("div", { className: "w-1 bg-white animate-music-bar-2" }), /* @__PURE__ */ React6.createElement("div", { className: "w-1 bg-white animate-music-bar-3" })))),
3096
+ /* @__PURE__ */ React6.createElement("div", { className: "flex-1 text-left min-w-0" }, /* @__PURE__ */ React6.createElement("h4", { className: `text-sm font-bold truncate ${isActive ? "text-blue-400" : "text-white/90"}` }, track.title), /* @__PURE__ */ React6.createElement("p", { className: "text-xs text-white/40 truncate mt-0.5" }, track.artist || "\u672A\u77E5\u827A\u672F\u5BB6")),
3097
+ !isActive && /* @__PURE__ */ React6.createElement("div", { className: "opacity-0 group-hover:opacity-100 transition-opacity" }, /* @__PURE__ */ React6.createElement(Play, { className: "w-4 h-4 text-white/40 fill-current" }))
3098
+ );
3099
+ })),
3100
+ /* @__PURE__ */ React6.createElement("div", { className: "p-6 border-t border-white/10" }, /* @__PURE__ */ React6.createElement("p", { className: "text-xs text-gray-500 text-center" }, "\u5171 ", tracks.length, " \u9996\u66F2\u76EE")),
3101
+ /* @__PURE__ */ React6.createElement("style", { dangerouslySetInnerHTML: { __html: `
3102
+ .custom-scrollbar::-webkit-scrollbar {
3103
+ width: 4px;
3104
+ }
3105
+ .custom-scrollbar::-webkit-scrollbar-track {
3106
+ background: transparent;
3107
+ }
3108
+ .custom-scrollbar::-webkit-scrollbar-thumb {
3109
+ background: rgba(255, 255, 255, 0.1);
3110
+ border-radius: 10px;
3111
+ }
3112
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
3113
+ background: rgba(255, 255, 255, 0.2);
3114
+ }
3115
+ @keyframes music-bar-1 {
3116
+ 0%, 100% { height: 4px; }
3117
+ 50% { height: 12px; }
3118
+ }
3119
+ @keyframes music-bar-2 {
3120
+ 0%, 100% { height: 8px; }
3121
+ 50% { height: 16px; }
3122
+ }
3123
+ @keyframes music-bar-3 {
3124
+ 0%, 100% { height: 6px; }
3125
+ 50% { height: 14px; }
3126
+ }
3127
+ .animate-music-bar-1 { animation: music-bar-1 0.6s infinite; }
3128
+ .animate-music-bar-2 { animation: music-bar-2 0.8s infinite; }
3129
+ .animate-music-bar-3 { animation: music-bar-3 0.7s infinite; }
3130
+ ` } })
3131
+ );
3132
+ };
3133
+ var TrackInfo = ({ track, className = "" }) => {
3134
+ return /* @__PURE__ */ React6.createElement("div", { className: `flex flex-col items-center text-center gap-2 ${className}` }, /* @__PURE__ */ React6.createElement("div", { className: "px-4 py-1.5 bg-black/40 backdrop-blur-md rounded-full border border-white/10 shadow-lg" }, /* @__PURE__ */ React6.createElement("h2", { className: "text-lg font-bold text-white tracking-wider truncate max-w-md" }, track.title)), /* @__PURE__ */ React6.createElement("p", { className: "text-sm font-medium text-white/60 drop-shadow-md" }, track.artist || "\u672A\u77E5\u827A\u672F\u5BB6"));
3135
+ };
3136
+
3137
+ // src/mmd/music-player/MMDMusicPlayer.tsx
3138
+ var MMDMusicPlayer = forwardRef(
3139
+ ({
3140
+ config,
3141
+ stage,
3142
+ mobileOptimization,
3143
+ initialTrackIndex = 0,
3144
+ onTrackChange,
3145
+ onPlayPause,
3146
+ onProgress,
3147
+ onError,
3148
+ className,
3149
+ style
3150
+ }, ref) => {
3151
+ const { tracks, autoPlay = false, defaultLoopMode = "list" } = config;
3152
+ const [currentIndex, setCurrentIndex] = useState(initialTrackIndex);
3153
+ const [isPlaying, setIsPlaying] = useState(autoPlay);
3154
+ const [isLoading, setIsLoading] = useState(true);
3155
+ const [isTransitioning, setIsTransitioning] = useState(false);
3156
+ const [currentTime, setCurrentTime] = useState(0);
3157
+ const [duration, setDuration] = useState(0);
3158
+ const [loopMode, setLoopMode] = useState(defaultLoopMode);
3159
+ const [showPlaylist, setShowPlaylist] = useState(false);
3160
+ const [isUIVisible, setIsUIVisible] = useState(true);
3161
+ const playerRef = useRef(null);
3162
+ const containerRef = useRef(null);
3163
+ const isStartedRef = useRef(autoPlay);
3164
+ const uiTimeoutRef = useRef(null);
3165
+ const currentTrack = tracks[currentIndex];
3166
+ const goToTrack = useCallback(
3167
+ (index) => {
3168
+ if (index < 0 || index >= tracks.length) return;
3169
+ if (isTransitioning) return;
3170
+ const track = tracks[index];
3171
+ if (!track) return;
3172
+ console.log(`[MMDMusicPlayer] Transitioning to track ${index}: ${track.title}`);
3173
+ setIsTransitioning(true);
3174
+ setIsLoading(true);
3175
+ const wasPlaying = isPlaying;
3176
+ setIsPlaying(false);
3177
+ setTimeout(() => {
3178
+ setCurrentIndex(index);
3179
+ setCurrentTime(0);
3180
+ setDuration(0);
3181
+ onTrackChange?.(track, index);
3182
+ setTimeout(() => {
3183
+ setIsTransitioning(false);
3184
+ if (wasPlaying) {
3185
+ isStartedRef.current = true;
3186
+ }
3187
+ }, 100);
3188
+ }, 300);
3189
+ },
3190
+ [tracks, isTransitioning, isPlaying, onTrackChange]
3191
+ );
3192
+ const next = useCallback(() => {
3193
+ let nextIndex = currentIndex + 1;
3194
+ if (loopMode === "shuffle") {
3195
+ nextIndex = Math.floor(Math.random() * tracks.length);
3196
+ } else if (nextIndex >= tracks.length) {
3197
+ nextIndex = 0;
3198
+ }
3199
+ goToTrack(nextIndex);
3200
+ }, [currentIndex, tracks.length, loopMode, goToTrack]);
3201
+ const previous = useCallback(() => {
3202
+ let prevIndex = currentIndex - 1;
3203
+ if (prevIndex < 0) {
3204
+ prevIndex = tracks.length - 1;
3205
+ }
3206
+ goToTrack(prevIndex);
3207
+ }, [currentIndex, tracks.length, goToTrack]);
3208
+ useImperativeHandle(
3209
+ ref,
3210
+ () => ({
3211
+ play: () => {
3212
+ setIsPlaying(true);
3213
+ isStartedRef.current = true;
3214
+ playerRef.current?.play();
3215
+ },
3216
+ pause: () => {
3217
+ setIsPlaying(false);
3218
+ isStartedRef.current = false;
3219
+ playerRef.current?.pause();
3220
+ },
3221
+ next,
3222
+ previous,
3223
+ goToTrack,
3224
+ setLoopMode,
3225
+ getState: () => ({
3226
+ currentIndex,
3227
+ isPlaying,
3228
+ currentTime,
3229
+ duration,
3230
+ loopMode
3231
+ })
3232
+ }),
3233
+ [next, previous, goToTrack, currentIndex, isPlaying, currentTime, duration, loopMode]
3234
+ );
3235
+ const handleEnded = useCallback(() => {
3236
+ if (loopMode === "single") {
3237
+ playerRef.current?.seek(0);
3238
+ playerRef.current?.play();
3239
+ } else {
3240
+ next();
3241
+ }
3242
+ }, [loopMode, next]);
3243
+ const handleTimeUpdate = useCallback((time) => {
3244
+ setCurrentTime(time);
3245
+ if (playerRef.current) {
3246
+ const total = playerRef.current.getDuration();
3247
+ setDuration(total);
3248
+ onProgress?.(time, total);
3249
+ }
3250
+ }, [onProgress]);
3251
+ const resetUITimeout = useCallback(() => {
3252
+ setIsUIVisible(true);
3253
+ if (uiTimeoutRef.current) clearTimeout(uiTimeoutRef.current);
3254
+ if (isPlaying) {
3255
+ uiTimeoutRef.current = setTimeout(() => {
3256
+ if (!showPlaylist) setIsUIVisible(false);
3257
+ }, 5e3);
3258
+ }
3259
+ }, [isPlaying, showPlaylist]);
3260
+ useEffect(() => {
3261
+ resetUITimeout();
3262
+ return () => {
3263
+ if (uiTimeoutRef.current) clearTimeout(uiTimeoutRef.current);
3264
+ };
3265
+ }, [resetUITimeout]);
3266
+ if (!currentTrack) {
3267
+ return /* @__PURE__ */ React6.createElement("div", { className: "flex h-full w-full items-center justify-center bg-black text-white" }, "\u64AD\u653E\u5217\u8868\u4E3A\u7A7A");
3268
+ }
3269
+ return /* @__PURE__ */ React6.createElement(
3270
+ "div",
3271
+ {
3272
+ ref: containerRef,
3273
+ className: `relative bg-black group ${className}`,
3274
+ style: { width: "100%", height: "100%", overflow: "hidden", ...style },
3275
+ onMouseMove: resetUITimeout,
3276
+ onClick: resetUITimeout
3277
+ },
3278
+ /* @__PURE__ */ React6.createElement(
3279
+ "div",
3280
+ {
3281
+ className: "absolute inset-0 w-full h-full",
3282
+ style: {
3283
+ zIndex: 0,
3284
+ opacity: isLoading || isTransitioning ? 0 : 1,
3285
+ transition: "opacity 0.5s ease-in-out"
3286
+ }
3287
+ },
3288
+ !isTransitioning && /* @__PURE__ */ React6.createElement(
3289
+ MMDPlayerBase,
3290
+ {
3291
+ key: currentTrack.id,
3292
+ ref: playerRef,
3293
+ resources: currentTrack.resources,
3294
+ stage,
3295
+ autoPlay: isStartedRef.current,
3296
+ loop: loopMode === "single",
3297
+ mobileOptimization,
3298
+ onLoad: () => {
3299
+ console.log("[MMDMusicPlayer] Track loaded");
3300
+ setIsLoading(false);
3301
+ if (isStartedRef.current) {
3302
+ playerRef.current?.play();
3303
+ setIsPlaying(true);
3304
+ }
3305
+ },
3306
+ onPlay: () => {
3307
+ setIsPlaying(true);
3308
+ onPlayPause?.(true);
3309
+ },
3310
+ onPause: () => {
3311
+ setIsPlaying(false);
3312
+ onPlayPause?.(false);
3313
+ },
3314
+ onTimeUpdate: handleTimeUpdate,
3315
+ onEnded: handleEnded,
3316
+ onError
3317
+ }
3318
+ )
3319
+ ),
3320
+ (isLoading || isTransitioning) && /* @__PURE__ */ React6.createElement("div", { className: "absolute inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-md" }, /* @__PURE__ */ React6.createElement("div", { className: "flex flex-col items-center gap-4" }, /* @__PURE__ */ React6.createElement("div", { className: "h-12 w-12 animate-spin rounded-full border-4 border-white/20 border-t-blue-500" }), /* @__PURE__ */ React6.createElement("div", { className: "text-white font-medium" }, isTransitioning ? "\u51C6\u5907\u4E0B\u4E00\u9996..." : "\u6B63\u5728\u52A0\u8F7D\u821E\u53F0..."))),
3321
+ /* @__PURE__ */ React6.createElement(
3322
+ "div",
3323
+ {
3324
+ className: `absolute inset-0 z-10 flex flex-col justify-between transition-opacity duration-700 pointer-events-none ${isUIVisible ? "opacity-100" : "opacity-0"}`
3325
+ },
3326
+ /* @__PURE__ */ React6.createElement("div", { className: "pt-12 px-8 flex justify-center" }, /* @__PURE__ */ React6.createElement(TrackInfo, { track: currentTrack })),
3327
+ /* @__PURE__ */ React6.createElement("div", { className: "pb-12 px-8" }, /* @__PURE__ */ React6.createElement(
3328
+ MusicControls,
3329
+ {
3330
+ isPlaying,
3331
+ currentTime,
3332
+ duration,
3333
+ loopMode,
3334
+ onPlayPause: () => isPlaying ? playerRef.current?.pause() : playerRef.current?.play(),
3335
+ onNext: next,
3336
+ onPrevious: previous,
3337
+ onSeek: (time) => playerRef.current?.seek(time),
3338
+ onToggleLoop: () => {
3339
+ const modes = ["list", "single", "shuffle"];
3340
+ const nextMode = modes[(modes.indexOf(loopMode) + 1) % modes.length];
3341
+ setLoopMode(nextMode);
3342
+ },
3343
+ onTogglePlaylist: () => setShowPlaylist(!showPlaylist)
3344
+ }
3345
+ ))
3346
+ ),
3347
+ /* @__PURE__ */ React6.createElement(
3348
+ PlaylistPanel,
3349
+ {
3350
+ tracks,
3351
+ currentIndex,
3352
+ isOpen: showPlaylist,
3353
+ onClose: () => setShowPlaylist(false),
3354
+ onSelectTrack: (index) => {
3355
+ goToTrack(index);
3356
+ setShowPlaylist(false);
3357
+ }
3358
+ }
3359
+ )
3360
+ );
3361
+ }
3362
+ );
3363
+ MMDMusicPlayer.displayName = "MMDMusicPlayer";
2886
3364
 
2887
- export { DialogueBox, HistoryPanel, LoadingOverlay, LoadingScreen, MMDPlayerBase, MMDPlayerEnhanced, MMDPlayerEnhancedDebugInfo, MMDPlaylist, MMDPlaylistDebugInfo, MMDVisualNovel, StartScreen, loadAmmo };
3365
+ export { DialogueBox, HistoryPanel, LoadingOverlay, LoadingScreen, MMDMusicPlayer, MMDPlayerBase, MMDPlayerEnhanced, MMDPlayerEnhancedDebugInfo, MMDPlaylist, MMDPlaylistDebugInfo, MMDVisualNovel, MusicControls, PlaylistPanel, StartScreen, TrackInfo, loadAmmo };
2888
3366
  //# sourceMappingURL=index.mjs.map
2889
3367
  //# sourceMappingURL=index.mjs.map