sa2kit 1.4.3 → 1.6.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/ConfigService-7MEZXKJ5.js +21 -0
- package/dist/ConfigService-7MEZXKJ5.js.map +1 -0
- package/dist/ConfigService-BV57YYFW.mjs +4 -0
- package/dist/ConfigService-BV57YYFW.mjs.map +1 -0
- package/dist/ConfigService-BxK06xP6.d.mts +262 -0
- package/dist/ConfigService-BxK06xP6.d.ts +262 -0
- package/dist/audioDetection/index.d.mts +449 -0
- package/dist/audioDetection/index.d.ts +449 -0
- package/dist/audioDetection/index.js +1244 -0
- package/dist/audioDetection/index.js.map +1 -0
- package/dist/audioDetection/index.mjs +1227 -0
- package/dist/audioDetection/index.mjs.map +1 -0
- package/dist/chunk-5XUE72Y3.mjs +1001 -0
- package/dist/chunk-5XUE72Y3.mjs.map +1 -0
- package/dist/chunk-DQVPZTVC.js +1009 -0
- package/dist/chunk-DQVPZTVC.js.map +1 -0
- package/dist/chunk-NEPD75MX.mjs +467 -0
- package/dist/chunk-NEPD75MX.mjs.map +1 -0
- package/dist/chunk-OEDY7GI4.js +473 -0
- package/dist/chunk-OEDY7GI4.js.map +1 -0
- package/dist/chunk-TFQF2HDO.mjs +354 -0
- package/dist/chunk-TFQF2HDO.mjs.map +1 -0
- package/dist/chunk-TOC5FSHP.js +358 -0
- package/dist/chunk-TOC5FSHP.js.map +1 -0
- package/dist/imageCrop/index.d.mts +165 -0
- package/dist/imageCrop/index.d.ts +165 -0
- package/dist/imageCrop/index.js +559 -0
- package/dist/imageCrop/index.js.map +1 -0
- package/dist/imageCrop/index.mjs +540 -0
- package/dist/imageCrop/index.mjs.map +1 -0
- package/dist/index.d.mts +139 -0
- package/dist/index.d.ts +139 -0
- package/dist/index.js +670 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +662 -0
- package/dist/index.mjs.map +1 -1
- package/dist/mmd/admin/index.d.mts +1 -1
- package/dist/mmd/admin/index.d.ts +1 -1
- package/dist/mmd/index.d.mts +138 -4
- package/dist/mmd/index.d.ts +138 -4
- package/dist/mmd/index.js +714 -12
- package/dist/mmd/index.js.map +1 -1
- package/dist/mmd/index.mjs +713 -15
- package/dist/mmd/index.mjs.map +1 -1
- package/dist/mmd/server/index.d.mts +1 -1
- package/dist/mmd/server/index.d.ts +1 -1
- package/dist/testYourself/admin/index.d.mts +58 -0
- package/dist/testYourself/admin/index.d.ts +58 -0
- package/dist/testYourself/admin/index.js +17 -0
- package/dist/testYourself/admin/index.js.map +1 -0
- package/dist/testYourself/admin/index.mjs +4 -0
- package/dist/testYourself/admin/index.mjs.map +1 -0
- package/dist/testYourself/index.d.mts +6 -98
- package/dist/testYourself/index.d.ts +6 -98
- package/dist/testYourself/index.js +90 -334
- package/dist/testYourself/index.js.map +1 -1
- package/dist/testYourself/index.mjs +47 -333
- package/dist/testYourself/index.mjs.map +1 -1
- package/dist/testYourself/server/index.d.mts +1029 -0
- package/dist/testYourself/server/index.d.ts +1029 -0
- package/dist/testYourself/server/index.js +42 -0
- package/dist/testYourself/server/index.js.map +1 -0
- package/dist/testYourself/server/index.mjs +5 -0
- package/dist/testYourself/server/index.mjs.map +1 -0
- package/dist/{types-Bc_p-zAR.d.mts → types-CsTSddwu.d.mts} +7 -0
- package/dist/{types-Bc_p-zAR.d.ts → types-CsTSddwu.d.ts} +7 -0
- package/dist/universalFile/server/index.js +5 -5
- package/dist/universalFile/server/index.mjs +1 -1
- package/package.json +62 -20
package/dist/mmd/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import '../chunk-BJTO5JO5.mjs';
|
|
2
|
-
import React6, { forwardRef, useRef,
|
|
2
|
+
import React6, { forwardRef, useRef, useEffect, useImperativeHandle, 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,
|
|
5
|
+
import { SkipBack, Pause, Play, SkipForward, Camera, 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;
|
|
@@ -160,6 +163,7 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
160
163
|
onPause,
|
|
161
164
|
onEnded,
|
|
162
165
|
onTimeUpdate,
|
|
166
|
+
onCameraChange,
|
|
163
167
|
className,
|
|
164
168
|
style
|
|
165
169
|
} = props;
|
|
@@ -180,6 +184,10 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
180
184
|
const animationClipRef = useRef(null);
|
|
181
185
|
const loopRef = useRef(loop);
|
|
182
186
|
const audioRef = useRef(null);
|
|
187
|
+
const latestCallbacks = useRef({ onPlay, onPause, onEnded, onTimeUpdate });
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
latestCallbacks.current = { onPlay, onPause, onEnded, onTimeUpdate };
|
|
190
|
+
}, [onPlay, onPause, onEnded, onTimeUpdate]);
|
|
183
191
|
const physicsComponentsRef = useRef({
|
|
184
192
|
configs: [],
|
|
185
193
|
dispatchers: [],
|
|
@@ -194,18 +202,18 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
194
202
|
if (!isReadyRef.current) return;
|
|
195
203
|
isPlayingRef.current = true;
|
|
196
204
|
if (!clockRef.current.running) clockRef.current.start();
|
|
197
|
-
onPlay?.();
|
|
205
|
+
latestCallbacks.current.onPlay?.();
|
|
198
206
|
},
|
|
199
207
|
pause: () => {
|
|
200
208
|
if (!isPlayingRef.current) return;
|
|
201
209
|
isPlayingRef.current = false;
|
|
202
210
|
clockRef.current.stop();
|
|
203
|
-
onPause?.();
|
|
211
|
+
latestCallbacks.current.onPause?.();
|
|
204
212
|
},
|
|
205
213
|
stop: () => {
|
|
206
214
|
isPlayingRef.current = false;
|
|
207
215
|
clockRef.current.stop();
|
|
208
|
-
onPause?.();
|
|
216
|
+
latestCallbacks.current.onPause?.();
|
|
209
217
|
},
|
|
210
218
|
seek: (time) => {
|
|
211
219
|
console.warn("Seek not fully implemented in MMDPlayerBase yet");
|
|
@@ -226,6 +234,24 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
226
234
|
rendererRef.current.render(sceneRef.current, cameraRef.current);
|
|
227
235
|
}
|
|
228
236
|
return rendererRef.current.domElement.toDataURL("image/png");
|
|
237
|
+
},
|
|
238
|
+
resetCamera: () => {
|
|
239
|
+
if (!cameraRef.current || !controlsRef.current) return;
|
|
240
|
+
const { cameraPosition, cameraTarget } = stage;
|
|
241
|
+
if (cameraPosition) {
|
|
242
|
+
const pos = cameraPosition;
|
|
243
|
+
cameraRef.current.position.set(pos.x, pos.y, pos.z);
|
|
244
|
+
} else {
|
|
245
|
+
cameraRef.current.position.set(0, 20, 30);
|
|
246
|
+
}
|
|
247
|
+
if (cameraTarget) {
|
|
248
|
+
const target = cameraTarget;
|
|
249
|
+
controlsRef.current.target.set(target.x, target.y, target.z);
|
|
250
|
+
} else {
|
|
251
|
+
controlsRef.current.target.set(0, 10, 0);
|
|
252
|
+
}
|
|
253
|
+
controlsRef.current.update();
|
|
254
|
+
onCameraChange?.(false);
|
|
229
255
|
}
|
|
230
256
|
}));
|
|
231
257
|
useEffect(() => {
|
|
@@ -312,6 +338,11 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
312
338
|
const scene = new THREE.Scene();
|
|
313
339
|
if (stage.backgroundColor) {
|
|
314
340
|
scene.background = new THREE.Color(stage.backgroundColor);
|
|
341
|
+
} else if (stage.backgroundImage) {
|
|
342
|
+
const textureLoader = new THREE.TextureLoader();
|
|
343
|
+
textureLoader.load(stage.backgroundImage, (texture) => {
|
|
344
|
+
scene.background = texture;
|
|
345
|
+
});
|
|
315
346
|
}
|
|
316
347
|
sceneRef.current = scene;
|
|
317
348
|
const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 2e3);
|
|
@@ -368,6 +399,9 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
368
399
|
controls.target.set(0, 10, 0);
|
|
369
400
|
}
|
|
370
401
|
controls.update();
|
|
402
|
+
controls.addEventListener("start", () => {
|
|
403
|
+
onCameraChange?.(true);
|
|
404
|
+
});
|
|
371
405
|
controlsRef.current = controls;
|
|
372
406
|
if (showAxes) {
|
|
373
407
|
const axesHelper = new THREE.AxesHelper(20);
|
|
@@ -586,6 +620,25 @@ var MMDPlayerBase = forwardRef((props, ref) => {
|
|
|
586
620
|
console.log("[MMDPlayerBase] \u2705 Stage materials and textures loaded");
|
|
587
621
|
scene.add(stageMesh);
|
|
588
622
|
console.log("[MMDPlayerBase] \u2705 Stage added to scene (fully loaded)");
|
|
623
|
+
if (resources.stageMotionPath && stageMesh) {
|
|
624
|
+
console.log("[MMDPlayerBase] Loading stage motion:", resources.stageMotionPath);
|
|
625
|
+
const anyLoader = loader;
|
|
626
|
+
const anyHelper = helper;
|
|
627
|
+
const anyStage = stageMesh;
|
|
628
|
+
anyLoader.loadAnimation(
|
|
629
|
+
resources.stageMotionPath,
|
|
630
|
+
anyStage,
|
|
631
|
+
(stageAnimation) => {
|
|
632
|
+
if (checkCancelled()) return;
|
|
633
|
+
anyHelper.add(anyStage, {
|
|
634
|
+
animation: stageAnimation
|
|
635
|
+
});
|
|
636
|
+
console.log("[MMDPlayerBase] \u2705 Stage motion bound successfully");
|
|
637
|
+
},
|
|
638
|
+
void 0,
|
|
639
|
+
(err) => console.error("Failed to load stage motion:", err)
|
|
640
|
+
);
|
|
641
|
+
}
|
|
589
642
|
} catch (err) {
|
|
590
643
|
console.error("Failed to load stage:", err);
|
|
591
644
|
}
|
|
@@ -1031,6 +1084,38 @@ ${errorMessage}
|
|
|
1031
1084
|
audioRef.current.setLoop(loop);
|
|
1032
1085
|
}
|
|
1033
1086
|
}, [loop]);
|
|
1087
|
+
useEffect(() => {
|
|
1088
|
+
if (!isReadyRef.current) return;
|
|
1089
|
+
if (sceneRef.current) {
|
|
1090
|
+
if (stage.backgroundColor) {
|
|
1091
|
+
sceneRef.current.background = new THREE.Color(stage.backgroundColor);
|
|
1092
|
+
} else if (stage.backgroundImage) {
|
|
1093
|
+
const textureLoader = new THREE.TextureLoader();
|
|
1094
|
+
textureLoader.load(stage.backgroundImage, (texture) => {
|
|
1095
|
+
if (sceneRef.current) sceneRef.current.background = texture;
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (sceneRef.current) {
|
|
1100
|
+
sceneRef.current.traverse((obj) => {
|
|
1101
|
+
if (obj instanceof THREE.AmbientLight && stage.ambientLightIntensity !== void 0) {
|
|
1102
|
+
obj.intensity = stage.ambientLightIntensity;
|
|
1103
|
+
}
|
|
1104
|
+
if (obj instanceof THREE.DirectionalLight && stage.directionalLightIntensity !== void 0) {
|
|
1105
|
+
obj.intensity = stage.directionalLightIntensity;
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
if (cameraRef.current && stage.cameraPosition) {
|
|
1110
|
+
const pos = stage.cameraPosition;
|
|
1111
|
+
cameraRef.current.position.set(pos.x, pos.y, pos.z);
|
|
1112
|
+
}
|
|
1113
|
+
if (controlsRef.current && stage.cameraTarget) {
|
|
1114
|
+
const target = stage.cameraTarget;
|
|
1115
|
+
controlsRef.current.target.set(target.x, target.y, target.z);
|
|
1116
|
+
controlsRef.current.update();
|
|
1117
|
+
}
|
|
1118
|
+
}, [stage.backgroundColor, stage.backgroundImage, stage.ambientLightIntensity, stage.directionalLightIntensity, stage.cameraPosition, stage.cameraTarget]);
|
|
1034
1119
|
const animate = () => {
|
|
1035
1120
|
animationIdRef.current = requestAnimationFrame(animate);
|
|
1036
1121
|
if (rendererRef.current && sceneRef.current && cameraRef.current) {
|
|
@@ -1040,11 +1125,11 @@ ${errorMessage}
|
|
|
1040
1125
|
const elapsed = clockRef.current.elapsedTime;
|
|
1041
1126
|
const duration = durationRef.current;
|
|
1042
1127
|
const currentTime = duration > 0 && loopRef.current ? elapsed % duration : elapsed;
|
|
1043
|
-
onTimeUpdate?.(currentTime);
|
|
1128
|
+
latestCallbacks.current.onTimeUpdate?.(currentTime);
|
|
1044
1129
|
if (!loopRef.current && duration > 0 && elapsed >= duration) {
|
|
1045
1130
|
isPlayingRef.current = false;
|
|
1046
1131
|
clockRef.current.stop();
|
|
1047
|
-
onEnded?.();
|
|
1132
|
+
latestCallbacks.current.onEnded?.();
|
|
1048
1133
|
}
|
|
1049
1134
|
}
|
|
1050
1135
|
rendererRef.current.render(sceneRef.current, cameraRef.current);
|
|
@@ -1073,6 +1158,7 @@ var ControlPanel = ({
|
|
|
1073
1158
|
isFullscreen,
|
|
1074
1159
|
isLooping,
|
|
1075
1160
|
isListLooping,
|
|
1161
|
+
isCameraManual = false,
|
|
1076
1162
|
showSettings,
|
|
1077
1163
|
showAxes = false,
|
|
1078
1164
|
showPrevNext = false,
|
|
@@ -1085,7 +1171,8 @@ var ControlPanel = ({
|
|
|
1085
1171
|
onToggleAxes,
|
|
1086
1172
|
onOpenSettings,
|
|
1087
1173
|
onPrevious,
|
|
1088
|
-
onNext
|
|
1174
|
+
onNext,
|
|
1175
|
+
onResetCamera
|
|
1089
1176
|
}) => {
|
|
1090
1177
|
return /* @__PURE__ */ React6.createElement("div", { className: "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-4 transition-opacity duration-300 hover:opacity-100" }, /* @__PURE__ */ React6.createElement("div", { className: "flex items-center justify-between text-white" }, /* @__PURE__ */ React6.createElement("div", { className: "flex items-center gap-2" }, showPrevNext && onPrevious && /* @__PURE__ */ React6.createElement(
|
|
1091
1178
|
"button",
|
|
@@ -1127,6 +1214,14 @@ var ControlPanel = ({
|
|
|
1127
1214
|
title: isLooping ? "\u5355\u66F2\u5FAA\u73AF\uFF1A\u5F00\u542F" : "\u5355\u66F2\u5FAA\u73AF\uFF1A\u5173\u95ED"
|
|
1128
1215
|
},
|
|
1129
1216
|
/* @__PURE__ */ React6.createElement(Repeat1, { size: 20 })
|
|
1217
|
+
), isCameraManual && onResetCamera && /* @__PURE__ */ React6.createElement(
|
|
1218
|
+
"button",
|
|
1219
|
+
{
|
|
1220
|
+
onClick: onResetCamera,
|
|
1221
|
+
className: "rounded-full p-2 bg-blue-500/30 text-blue-400 hover:bg-blue-500/50 hover:text-blue-300 transition-all animate-in zoom-in duration-300",
|
|
1222
|
+
title: "\u6062\u590D\u521D\u59CB\u89C6\u89D2"
|
|
1223
|
+
},
|
|
1224
|
+
/* @__PURE__ */ React6.createElement(Camera, { size: 20 })
|
|
1130
1225
|
), onToggleAxes && /* @__PURE__ */ React6.createElement(
|
|
1131
1226
|
"button",
|
|
1132
1227
|
{
|
|
@@ -1533,6 +1628,7 @@ var MMDPlaylist = ({
|
|
|
1533
1628
|
const [isListLooping, setIsListLooping] = useState(loop);
|
|
1534
1629
|
const [showPlaylist, setShowPlaylist] = useState(false);
|
|
1535
1630
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
1631
|
+
const [isCameraManual, setIsCameraManual] = useState(false);
|
|
1536
1632
|
const playerRef = useRef(null);
|
|
1537
1633
|
const containerRef = useRef(null);
|
|
1538
1634
|
const preloadedRef = useRef(/* @__PURE__ */ new Set());
|
|
@@ -1684,7 +1780,7 @@ var MMDPlaylist = ({
|
|
|
1684
1780
|
key: currentNode.id,
|
|
1685
1781
|
ref: playerRef,
|
|
1686
1782
|
resources: currentNode.resources,
|
|
1687
|
-
stage,
|
|
1783
|
+
stage: { ...stage, ...currentNode.stage },
|
|
1688
1784
|
autoPlay: autoPlay && currentIndex === 0,
|
|
1689
1785
|
loop: isLooping,
|
|
1690
1786
|
showAxes,
|
|
@@ -1697,6 +1793,7 @@ var MMDPlaylist = ({
|
|
|
1697
1793
|
},
|
|
1698
1794
|
onPlay: () => setIsPlaying(true),
|
|
1699
1795
|
onPause: () => setIsPlaying(false),
|
|
1796
|
+
onCameraChange: (isManual) => setIsCameraManual(isManual),
|
|
1700
1797
|
onEnded: handleEnded,
|
|
1701
1798
|
onError
|
|
1702
1799
|
}
|
|
@@ -1712,6 +1809,7 @@ var MMDPlaylist = ({
|
|
|
1712
1809
|
isFullscreen,
|
|
1713
1810
|
isLooping,
|
|
1714
1811
|
isListLooping,
|
|
1812
|
+
isCameraManual,
|
|
1715
1813
|
showSettings: true,
|
|
1716
1814
|
showAxes,
|
|
1717
1815
|
showPrevNext,
|
|
@@ -1724,7 +1822,11 @@ var MMDPlaylist = ({
|
|
|
1724
1822
|
onToggleLoop: () => setIsLooping(!isLooping),
|
|
1725
1823
|
onToggleListLoop: () => setIsListLooping(!isListLooping),
|
|
1726
1824
|
onToggleAxes: () => setShowAxes(!showAxes),
|
|
1727
|
-
onOpenSettings: () => setShowPlaylist(!showPlaylist)
|
|
1825
|
+
onOpenSettings: () => setShowPlaylist(!showPlaylist),
|
|
1826
|
+
onResetCamera: () => {
|
|
1827
|
+
playerRef.current?.resetCamera();
|
|
1828
|
+
setIsCameraManual(false);
|
|
1829
|
+
}
|
|
1728
1830
|
}
|
|
1729
1831
|
)
|
|
1730
1832
|
), showPlaylist && /* @__PURE__ */ React6.createElement("div", { className: "absolute inset-0 z-20 flex items-end bg-black/80 backdrop-blur-sm" }, /* @__PURE__ */ React6.createElement("div", { className: "w-full max-h-[60vh] overflow-y-auto bg-gray-900/95 rounded-t-xl" }, /* @__PURE__ */ React6.createElement("div", { className: "sticky top-0 flex items-center justify-between bg-gray-800 px-4 py-3 border-b border-gray-700" }, /* @__PURE__ */ React6.createElement("div", null, /* @__PURE__ */ React6.createElement("h3", { className: "text-white font-semibold" }, playlist.name), /* @__PURE__ */ React6.createElement("p", { className: "text-xs text-gray-400 mt-0.5" }, "\u5171 ", nodes.length, " \u4E2A\u8282\u70B9")), /* @__PURE__ */ React6.createElement(
|
|
@@ -1826,6 +1928,8 @@ var DialogueBox = ({
|
|
|
1826
1928
|
onToggleAuto,
|
|
1827
1929
|
onOpenHistory,
|
|
1828
1930
|
onSkip,
|
|
1931
|
+
onResetCamera,
|
|
1932
|
+
isCameraManual = false,
|
|
1829
1933
|
showControls = true,
|
|
1830
1934
|
showSkipButton = true,
|
|
1831
1935
|
showAutoButton = true,
|
|
@@ -1989,6 +2093,21 @@ var DialogueBox = ({
|
|
|
1989
2093
|
title: "\u5386\u53F2\u8BB0\u5F55"
|
|
1990
2094
|
},
|
|
1991
2095
|
"\u{1F4DC} \u5386\u53F2"
|
|
2096
|
+
), isCameraManual && /* @__PURE__ */ React6.createElement(
|
|
2097
|
+
"button",
|
|
2098
|
+
{
|
|
2099
|
+
onClick: (e) => {
|
|
2100
|
+
e.stopPropagation();
|
|
2101
|
+
onResetCamera?.();
|
|
2102
|
+
},
|
|
2103
|
+
className: "px-4 py-2 text-xs rounded-xl text-white font-medium hover:text-white transition-all backdrop-blur-lg border border-blue-400/50 hover:border-blue-400 hover:scale-105 active:scale-95 shadow-lg animate-in zoom-in duration-300",
|
|
2104
|
+
style: {
|
|
2105
|
+
background: "linear-gradient(135deg, rgba(59, 130, 246, 0.3), rgba(37, 99, 235, 0.2))",
|
|
2106
|
+
boxShadow: "0 4px 16px rgba(59, 130, 246, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.2)"
|
|
2107
|
+
},
|
|
2108
|
+
title: "\u6062\u590D\u521D\u59CB\u89C6\u89D2"
|
|
2109
|
+
},
|
|
2110
|
+
"\u{1F3A5} \u6062\u590D\u89C6\u89D2"
|
|
1992
2111
|
), showAutoButton && /* @__PURE__ */ React6.createElement(
|
|
1993
2112
|
"button",
|
|
1994
2113
|
{
|
|
@@ -2572,6 +2691,105 @@ var LoadingOverlay = ({
|
|
|
2572
2691
|
));
|
|
2573
2692
|
};
|
|
2574
2693
|
LoadingOverlay.displayName = "LoadingOverlay";
|
|
2694
|
+
var SkipConfirmDialog = ({
|
|
2695
|
+
onConfirm,
|
|
2696
|
+
onCancel
|
|
2697
|
+
}) => {
|
|
2698
|
+
const [isMounted, setIsMounted] = React6.useState(false);
|
|
2699
|
+
React6.useEffect(() => {
|
|
2700
|
+
setIsMounted(true);
|
|
2701
|
+
}, []);
|
|
2702
|
+
if (!isMounted) return null;
|
|
2703
|
+
const content = /* @__PURE__ */ React6.createElement(
|
|
2704
|
+
"div",
|
|
2705
|
+
{
|
|
2706
|
+
className: "fixed inset-0 flex items-center justify-center bg-black/40 backdrop-blur-sm pointer-events-auto",
|
|
2707
|
+
style: { zIndex: 999999 }
|
|
2708
|
+
},
|
|
2709
|
+
/* @__PURE__ */ React6.createElement(
|
|
2710
|
+
"div",
|
|
2711
|
+
{
|
|
2712
|
+
className: "p-8 rounded-3xl border border-white/30 shadow-2xl max-w-sm w-full mx-4 overflow-hidden relative",
|
|
2713
|
+
style: {
|
|
2714
|
+
background: `linear-gradient(135deg,
|
|
2715
|
+
rgba(255, 255, 255, 0.2) 0%,
|
|
2716
|
+
rgba(255, 255, 255, 0.1) 100%)`,
|
|
2717
|
+
backdropFilter: "blur(32px) saturate(200%)",
|
|
2718
|
+
WebkitBackdropFilter: "blur(32px) saturate(200%)"
|
|
2719
|
+
}
|
|
2720
|
+
},
|
|
2721
|
+
/* @__PURE__ */ React6.createElement("h3", { className: "text-xl font-bold text-white mb-4 drop-shadow-md" }, "\u52A8\u753B\u5C1A\u672A\u64AD\u653E\u5B8C\u6210"),
|
|
2722
|
+
/* @__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"),
|
|
2723
|
+
/* @__PURE__ */ React6.createElement("div", { className: "flex justify-end gap-4" }, /* @__PURE__ */ React6.createElement(
|
|
2724
|
+
"button",
|
|
2725
|
+
{
|
|
2726
|
+
onClick: onCancel,
|
|
2727
|
+
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"
|
|
2728
|
+
},
|
|
2729
|
+
"\u53D6\u6D88"
|
|
2730
|
+
), /* @__PURE__ */ React6.createElement(
|
|
2731
|
+
"button",
|
|
2732
|
+
{
|
|
2733
|
+
onClick: onConfirm,
|
|
2734
|
+
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"
|
|
2735
|
+
},
|
|
2736
|
+
"\u76F4\u63A5\u8DF3\u8F6C"
|
|
2737
|
+
))
|
|
2738
|
+
)
|
|
2739
|
+
);
|
|
2740
|
+
let portalContainer = document.getElementById("dialogue-portal-root");
|
|
2741
|
+
if (!portalContainer) {
|
|
2742
|
+
portalContainer = document.createElement("div");
|
|
2743
|
+
portalContainer.id = "dialogue-portal-root";
|
|
2744
|
+
portalContainer.style.cssText = "position: fixed; inset: 0; pointer-events: none; z-index: 999999;";
|
|
2745
|
+
document.body.appendChild(portalContainer);
|
|
2746
|
+
}
|
|
2747
|
+
return createPortal(content, portalContainer);
|
|
2748
|
+
};
|
|
2749
|
+
var ChoiceMenu = ({
|
|
2750
|
+
choices,
|
|
2751
|
+
onSelect,
|
|
2752
|
+
theme
|
|
2753
|
+
}) => {
|
|
2754
|
+
const [isMounted, setIsMounted] = React6.useState(false);
|
|
2755
|
+
React6.useEffect(() => {
|
|
2756
|
+
setIsMounted(true);
|
|
2757
|
+
}, []);
|
|
2758
|
+
if (!isMounted) return null;
|
|
2759
|
+
const content = /* @__PURE__ */ React6.createElement(
|
|
2760
|
+
"div",
|
|
2761
|
+
{
|
|
2762
|
+
className: "fixed inset-0 flex flex-col items-center justify-center bg-black/20 backdrop-blur-sm pointer-events-auto transition-all animate-in fade-in duration-500",
|
|
2763
|
+
style: { zIndex: 1e6 }
|
|
2764
|
+
},
|
|
2765
|
+
/* @__PURE__ */ React6.createElement("div", { className: "flex flex-col gap-4 w-full max-w-md px-6" }, choices.map((choice, index) => /* @__PURE__ */ React6.createElement(
|
|
2766
|
+
"button",
|
|
2767
|
+
{
|
|
2768
|
+
key: index,
|
|
2769
|
+
onClick: () => onSelect(choice),
|
|
2770
|
+
className: "w-full py-4 px-8 rounded-2xl text-white font-bold text-lg transition-all border border-white/20 shadow-xl hover:scale-105 active:scale-95 group relative overflow-hidden",
|
|
2771
|
+
style: {
|
|
2772
|
+
background: `linear-gradient(135deg,
|
|
2773
|
+
rgba(255, 255, 255, 0.2) 0%,
|
|
2774
|
+
rgba(255, 255, 255, 0.1) 100%)`,
|
|
2775
|
+
backdropFilter: "blur(32px) saturate(200%)",
|
|
2776
|
+
WebkitBackdropFilter: "blur(32px) saturate(200%)"
|
|
2777
|
+
}
|
|
2778
|
+
},
|
|
2779
|
+
/* @__PURE__ */ React6.createElement("div", { className: "absolute inset-0 bg-white/10 opacity-0 group-hover:opacity-100 transition-opacity" }),
|
|
2780
|
+
/* @__PURE__ */ React6.createElement("div", { className: "absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent -translate-x-full group-hover:translate-x-full transition-transform duration-1000 ease-in-out" }),
|
|
2781
|
+
/* @__PURE__ */ React6.createElement("span", { className: "relative z-10 drop-shadow-md" }, choice.text)
|
|
2782
|
+
)))
|
|
2783
|
+
);
|
|
2784
|
+
let portalContainer = document.getElementById("dialogue-portal-root");
|
|
2785
|
+
if (!portalContainer) {
|
|
2786
|
+
portalContainer = document.createElement("div");
|
|
2787
|
+
portalContainer.id = "dialogue-portal-root";
|
|
2788
|
+
portalContainer.style.cssText = "position: fixed; inset: 0; pointer-events: none; z-index: 999999;";
|
|
2789
|
+
document.body.appendChild(portalContainer);
|
|
2790
|
+
}
|
|
2791
|
+
return createPortal(content, portalContainer);
|
|
2792
|
+
};
|
|
2575
2793
|
|
|
2576
2794
|
// src/mmd/visual-novel/MMDVisualNovel.tsx
|
|
2577
2795
|
var MMDVisualNovel = forwardRef(
|
|
@@ -2605,11 +2823,17 @@ var MMDVisualNovel = forwardRef(
|
|
|
2605
2823
|
const [showHistory, setShowHistory] = useState(false);
|
|
2606
2824
|
const [history, setHistory] = useState([]);
|
|
2607
2825
|
const [isStarted, setIsStarted] = useState(autoStart);
|
|
2826
|
+
const [isVmdFinished, setIsVmdFinished] = useState(false);
|
|
2827
|
+
const [pendingNodeIndex, setPendingNodeIndex] = useState(null);
|
|
2828
|
+
const [showChoices, setShowChoices] = useState(false);
|
|
2829
|
+
const [isCameraManual, setIsCameraManual] = useState(false);
|
|
2608
2830
|
const playerRef = useRef(null);
|
|
2609
2831
|
const containerRef = useRef(null);
|
|
2610
2832
|
const autoTimerRef = useRef(null);
|
|
2611
2833
|
const typingCompleteRef = useRef(false);
|
|
2612
2834
|
const isStartedRef = useRef(autoStart);
|
|
2835
|
+
const lastAnimationTimeRef = useRef(0);
|
|
2836
|
+
const isVmdFinishedRef = useRef(false);
|
|
2613
2837
|
const currentNode = nodes[currentNodeIndex];
|
|
2614
2838
|
const currentDialogue = currentNode?.dialogues[currentDialogueIndex] || null;
|
|
2615
2839
|
const addToHistory = useCallback((dialogue, nodeIndex, dialogueIndex) => {
|
|
@@ -2637,6 +2861,8 @@ var MMDVisualNovel = forwardRef(
|
|
|
2637
2861
|
addToHistory(nextDialogue, currentNodeIndex, nextDialogueIndex);
|
|
2638
2862
|
onDialogueChange?.(nextDialogue, nextDialogueIndex, currentNodeIndex);
|
|
2639
2863
|
typingCompleteRef.current = false;
|
|
2864
|
+
} else if (currentNode.choices && currentNode.choices.length > 0) {
|
|
2865
|
+
setShowChoices(true);
|
|
2640
2866
|
} else {
|
|
2641
2867
|
const nextNodeIndex = currentNodeIndex + 1;
|
|
2642
2868
|
if (nextNodeIndex < nodes.length) {
|
|
@@ -2649,15 +2875,26 @@ var MMDVisualNovel = forwardRef(
|
|
|
2649
2875
|
}
|
|
2650
2876
|
}, [currentNode, currentDialogueIndex, currentNodeIndex, nodes.length, loop, addToHistory, onDialogueChange, onScriptComplete]);
|
|
2651
2877
|
const goToNode = useCallback(
|
|
2652
|
-
(nodeIndex) => {
|
|
2878
|
+
(nodeIndex, force = false) => {
|
|
2653
2879
|
if (nodeIndex < 0 || nodeIndex >= nodes.length) return;
|
|
2654
2880
|
if (isTransitioning) return;
|
|
2655
2881
|
const node = nodes[nodeIndex];
|
|
2656
2882
|
if (!node) return;
|
|
2883
|
+
const currentResources = nodes[currentNodeIndex]?.resources;
|
|
2884
|
+
if (!force && currentResources?.motionPath && !isVmdFinishedRef.current) {
|
|
2885
|
+
console.log("[MMDVisualNovel] VMD not finished, showing confirmation");
|
|
2886
|
+
setPendingNodeIndex(nodeIndex);
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2657
2889
|
console.log(`[MMDVisualNovel] Transitioning to node ${nodeIndex}`);
|
|
2658
2890
|
setIsTransitioning(true);
|
|
2659
2891
|
setIsLoading(true);
|
|
2660
2892
|
setIsAnimationPlaying(false);
|
|
2893
|
+
setIsVmdFinished(false);
|
|
2894
|
+
isVmdFinishedRef.current = false;
|
|
2895
|
+
setPendingNodeIndex(null);
|
|
2896
|
+
setShowChoices(false);
|
|
2897
|
+
lastAnimationTimeRef.current = 0;
|
|
2661
2898
|
setTimeout(() => {
|
|
2662
2899
|
setCurrentNodeIndex(nodeIndex);
|
|
2663
2900
|
setCurrentDialogueIndex(0);
|
|
@@ -2675,7 +2912,7 @@ var MMDVisualNovel = forwardRef(
|
|
|
2675
2912
|
}, 100);
|
|
2676
2913
|
}, 300);
|
|
2677
2914
|
},
|
|
2678
|
-
[nodes, isTransitioning, addToHistory, onNodeChange, onDialogueChange]
|
|
2915
|
+
[nodes, isTransitioning, addToHistory, onNodeChange, onDialogueChange, currentNodeIndex, isVmdFinished]
|
|
2679
2916
|
);
|
|
2680
2917
|
const goToDialogue = useCallback(
|
|
2681
2918
|
(dialogueIndex) => {
|
|
@@ -2724,6 +2961,10 @@ var MMDVisualNovel = forwardRef(
|
|
|
2724
2961
|
setIsAutoMode((prev) => !prev);
|
|
2725
2962
|
}, []);
|
|
2726
2963
|
const handleSkip = useCallback(() => {
|
|
2964
|
+
if (currentNode?.choices && currentNode.choices.length > 0) {
|
|
2965
|
+
setShowChoices(true);
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2727
2968
|
const nextNodeIndex = currentNodeIndex + 1;
|
|
2728
2969
|
if (nextNodeIndex < nodes.length) {
|
|
2729
2970
|
goToNode(nextNodeIndex);
|
|
@@ -2797,7 +3038,7 @@ var MMDVisualNovel = forwardRef(
|
|
|
2797
3038
|
key: currentNode.id,
|
|
2798
3039
|
ref: playerRef,
|
|
2799
3040
|
resources: currentNode.resources,
|
|
2800
|
-
stage,
|
|
3041
|
+
stage: { ...stage, ...currentNode.stage },
|
|
2801
3042
|
autoPlay: isStarted,
|
|
2802
3043
|
loop: currentNode.loopAnimation === true,
|
|
2803
3044
|
mobileOptimization,
|
|
@@ -2815,6 +3056,27 @@ var MMDVisualNovel = forwardRef(
|
|
|
2815
3056
|
console.log("[MMDVisualNovel] MMDPlayerBase onPlay called");
|
|
2816
3057
|
setIsAnimationPlaying(true);
|
|
2817
3058
|
},
|
|
3059
|
+
onTimeUpdate: (time) => {
|
|
3060
|
+
const duration = playerRef.current?.getDuration() || 0;
|
|
3061
|
+
const isNearEnd = duration > 0 && time > duration * 0.98;
|
|
3062
|
+
const isLooped = time < lastAnimationTimeRef.current && lastAnimationTimeRef.current > 0;
|
|
3063
|
+
if (isNearEnd || isLooped) {
|
|
3064
|
+
if (!isVmdFinishedRef.current) {
|
|
3065
|
+
console.log("[MMDVisualNovel] VMD finished/looped, marking as finished");
|
|
3066
|
+
isVmdFinishedRef.current = true;
|
|
3067
|
+
setIsVmdFinished(true);
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
lastAnimationTimeRef.current = time;
|
|
3071
|
+
},
|
|
3072
|
+
onEnded: () => {
|
|
3073
|
+
console.log("[MMDVisualNovel] VMD ended, marking as finished");
|
|
3074
|
+
isVmdFinishedRef.current = true;
|
|
3075
|
+
setIsVmdFinished(true);
|
|
3076
|
+
},
|
|
3077
|
+
onCameraChange: (isManual) => {
|
|
3078
|
+
setIsCameraManual(isManual);
|
|
3079
|
+
},
|
|
2818
3080
|
onError
|
|
2819
3081
|
}
|
|
2820
3082
|
)
|
|
@@ -2841,12 +3103,13 @@ var MMDVisualNovel = forwardRef(
|
|
|
2841
3103
|
}
|
|
2842
3104
|
),
|
|
2843
3105
|
(() => {
|
|
2844
|
-
const shouldShow = isStarted && isAnimationPlaying && currentDialogue && !showHistory;
|
|
3106
|
+
const shouldShow = isStarted && isAnimationPlaying && currentDialogue && !showHistory && !showChoices;
|
|
2845
3107
|
console.log("[MMDVisualNovel] DialogueBox render condition:", {
|
|
2846
3108
|
isStarted,
|
|
2847
3109
|
isAnimationPlaying,
|
|
2848
3110
|
hasDialogue: !!currentDialogue,
|
|
2849
3111
|
showHistory,
|
|
3112
|
+
showChoices,
|
|
2850
3113
|
shouldShow,
|
|
2851
3114
|
dialogue: currentDialogue
|
|
2852
3115
|
});
|
|
@@ -2864,6 +3127,11 @@ var MMDVisualNovel = forwardRef(
|
|
|
2864
3127
|
onToggleAuto: toggleAutoMode,
|
|
2865
3128
|
onOpenHistory: () => setShowHistory(true),
|
|
2866
3129
|
onSkip: handleSkip,
|
|
3130
|
+
onResetCamera: () => {
|
|
3131
|
+
playerRef.current?.resetCamera();
|
|
3132
|
+
setIsCameraManual(false);
|
|
3133
|
+
},
|
|
3134
|
+
isCameraManual,
|
|
2867
3135
|
showControls: true,
|
|
2868
3136
|
showSkipButton,
|
|
2869
3137
|
showAutoButton,
|
|
@@ -2871,6 +3139,35 @@ var MMDVisualNovel = forwardRef(
|
|
|
2871
3139
|
}
|
|
2872
3140
|
) : null;
|
|
2873
3141
|
})(),
|
|
3142
|
+
pendingNodeIndex !== null && /* @__PURE__ */ React6.createElement(
|
|
3143
|
+
SkipConfirmDialog,
|
|
3144
|
+
{
|
|
3145
|
+
onConfirm: () => {
|
|
3146
|
+
if (pendingNodeIndex !== null) {
|
|
3147
|
+
goToNode(pendingNodeIndex, true);
|
|
3148
|
+
}
|
|
3149
|
+
},
|
|
3150
|
+
onCancel: () => {
|
|
3151
|
+
setPendingNodeIndex(null);
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
),
|
|
3155
|
+
showChoices && currentNode.choices && /* @__PURE__ */ React6.createElement(
|
|
3156
|
+
ChoiceMenu,
|
|
3157
|
+
{
|
|
3158
|
+
choices: currentNode.choices,
|
|
3159
|
+
theme: dialogueTheme,
|
|
3160
|
+
onSelect: (choice) => {
|
|
3161
|
+
choice.onSelect?.();
|
|
3162
|
+
if (choice.nextNodeIndex === currentNodeIndex) {
|
|
3163
|
+
goToDialogue(choice.nextDialogueIndex || 0);
|
|
3164
|
+
setShowChoices(false);
|
|
3165
|
+
} else {
|
|
3166
|
+
goToNode(choice.nextNodeIndex, true);
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
),
|
|
2874
3171
|
showHistory && /* @__PURE__ */ React6.createElement(
|
|
2875
3172
|
HistoryPanel,
|
|
2876
3173
|
{
|
|
@@ -2883,7 +3180,408 @@ var MMDVisualNovel = forwardRef(
|
|
|
2883
3180
|
}
|
|
2884
3181
|
);
|
|
2885
3182
|
MMDVisualNovel.displayName = "MMDVisualNovel";
|
|
3183
|
+
var MusicControls = ({
|
|
3184
|
+
isPlaying,
|
|
3185
|
+
currentTime,
|
|
3186
|
+
duration,
|
|
3187
|
+
loopMode,
|
|
3188
|
+
isCameraManual = false,
|
|
3189
|
+
onPlayPause,
|
|
3190
|
+
onNext,
|
|
3191
|
+
onPrevious,
|
|
3192
|
+
onSeek,
|
|
3193
|
+
onToggleLoop,
|
|
3194
|
+
onTogglePlaylist,
|
|
3195
|
+
onResetCamera,
|
|
3196
|
+
className = ""
|
|
3197
|
+
}) => {
|
|
3198
|
+
const formatTime = (seconds) => {
|
|
3199
|
+
const mins = Math.floor(seconds / 60);
|
|
3200
|
+
const secs = Math.floor(seconds % 60);
|
|
3201
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
3202
|
+
};
|
|
3203
|
+
const progress = duration > 0 ? currentTime / duration * 100 : 0;
|
|
3204
|
+
return /* @__PURE__ */ React6.createElement(
|
|
3205
|
+
"div",
|
|
3206
|
+
{
|
|
3207
|
+
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}`
|
|
3208
|
+
},
|
|
3209
|
+
/* @__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(
|
|
3210
|
+
"div",
|
|
3211
|
+
{
|
|
3212
|
+
className: "absolute h-full bg-blue-500 rounded-full transition-all duration-300",
|
|
3213
|
+
style: { width: `${progress}%` }
|
|
3214
|
+
}
|
|
3215
|
+
), /* @__PURE__ */ React6.createElement(
|
|
3216
|
+
"input",
|
|
3217
|
+
{
|
|
3218
|
+
type: "range",
|
|
3219
|
+
min: 0,
|
|
3220
|
+
max: duration || 100,
|
|
3221
|
+
value: currentTime,
|
|
3222
|
+
onChange: (e) => onSeek(Number(e.target.value)),
|
|
3223
|
+
className: "absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
|
3224
|
+
}
|
|
3225
|
+
)),
|
|
3226
|
+
/* @__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(
|
|
3227
|
+
"button",
|
|
3228
|
+
{
|
|
3229
|
+
onClick: onPrevious,
|
|
3230
|
+
className: "text-white/80 hover:text-white transition-colors"
|
|
3231
|
+
},
|
|
3232
|
+
/* @__PURE__ */ React6.createElement(SkipBack, { className: "w-6 h-6 fill-current" })
|
|
3233
|
+
), /* @__PURE__ */ React6.createElement(
|
|
3234
|
+
"button",
|
|
3235
|
+
{
|
|
3236
|
+
onClick: onPlayPause,
|
|
3237
|
+
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"
|
|
3238
|
+
},
|
|
3239
|
+
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" })
|
|
3240
|
+
), /* @__PURE__ */ React6.createElement(
|
|
3241
|
+
"button",
|
|
3242
|
+
{
|
|
3243
|
+
onClick: onNext,
|
|
3244
|
+
className: "text-white/80 hover:text-white transition-colors"
|
|
3245
|
+
},
|
|
3246
|
+
/* @__PURE__ */ React6.createElement(SkipForward, { className: "w-6 h-6 fill-current" })
|
|
3247
|
+
)), /* @__PURE__ */ React6.createElement("div", { className: "flex items-center gap-4 w-32 justify-end" }, isCameraManual && /* @__PURE__ */ React6.createElement(
|
|
3248
|
+
"button",
|
|
3249
|
+
{
|
|
3250
|
+
onClick: onResetCamera,
|
|
3251
|
+
className: "text-blue-400 hover:text-blue-300 transition-colors animate-in zoom-in duration-300",
|
|
3252
|
+
title: "\u6062\u590D\u521D\u59CB\u89C6\u89D2"
|
|
3253
|
+
},
|
|
3254
|
+
/* @__PURE__ */ React6.createElement(Camera, { className: "w-5 h-5" })
|
|
3255
|
+
), /* @__PURE__ */ React6.createElement(
|
|
3256
|
+
"button",
|
|
3257
|
+
{
|
|
3258
|
+
onClick: onToggleLoop,
|
|
3259
|
+
className: "text-white/60 hover:text-white transition-colors",
|
|
3260
|
+
title: loopMode
|
|
3261
|
+
},
|
|
3262
|
+
loopMode === "list" && /* @__PURE__ */ React6.createElement(Repeat, { className: "w-5 h-5" }),
|
|
3263
|
+
loopMode === "single" && /* @__PURE__ */ React6.createElement(Repeat1, { className: "w-5 h-5 text-blue-400" }),
|
|
3264
|
+
loopMode === "shuffle" && /* @__PURE__ */ React6.createElement(Shuffle, { className: "w-5 h-5 text-orange-400" })
|
|
3265
|
+
), /* @__PURE__ */ React6.createElement(
|
|
3266
|
+
"button",
|
|
3267
|
+
{
|
|
3268
|
+
onClick: onTogglePlaylist,
|
|
3269
|
+
className: "text-white/60 hover:text-white transition-colors"
|
|
3270
|
+
},
|
|
3271
|
+
/* @__PURE__ */ React6.createElement(ListMusic, { className: "w-5 h-5" })
|
|
3272
|
+
)))
|
|
3273
|
+
);
|
|
3274
|
+
};
|
|
3275
|
+
var PlaylistPanel = ({
|
|
3276
|
+
tracks,
|
|
3277
|
+
currentIndex,
|
|
3278
|
+
isOpen,
|
|
3279
|
+
onClose,
|
|
3280
|
+
onSelectTrack,
|
|
3281
|
+
className = ""
|
|
3282
|
+
}) => {
|
|
3283
|
+
if (!isOpen) return null;
|
|
3284
|
+
return /* @__PURE__ */ React6.createElement(
|
|
3285
|
+
"div",
|
|
3286
|
+
{
|
|
3287
|
+
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}`
|
|
3288
|
+
},
|
|
3289
|
+
/* @__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(
|
|
3290
|
+
"button",
|
|
3291
|
+
{
|
|
3292
|
+
onClick: onClose,
|
|
3293
|
+
className: "p-2 text-white/60 hover:text-white transition-colors"
|
|
3294
|
+
},
|
|
3295
|
+
/* @__PURE__ */ React6.createElement(X, { className: "w-5 h-5" })
|
|
3296
|
+
)),
|
|
3297
|
+
/* @__PURE__ */ React6.createElement("div", { className: "flex-1 overflow-y-auto p-4 space-y-2 custom-scrollbar" }, tracks.map((track, index) => {
|
|
3298
|
+
const isActive = index === currentIndex;
|
|
3299
|
+
return /* @__PURE__ */ React6.createElement(
|
|
3300
|
+
"button",
|
|
3301
|
+
{
|
|
3302
|
+
key: track.id,
|
|
3303
|
+
onClick: () => onSelectTrack(index),
|
|
3304
|
+
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"}`
|
|
3305
|
+
},
|
|
3306
|
+
/* @__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" })))),
|
|
3307
|
+
/* @__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")),
|
|
3308
|
+
!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" }))
|
|
3309
|
+
);
|
|
3310
|
+
})),
|
|
3311
|
+
/* @__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")),
|
|
3312
|
+
/* @__PURE__ */ React6.createElement("style", { dangerouslySetInnerHTML: { __html: `
|
|
3313
|
+
.custom-scrollbar::-webkit-scrollbar {
|
|
3314
|
+
width: 4px;
|
|
3315
|
+
}
|
|
3316
|
+
.custom-scrollbar::-webkit-scrollbar-track {
|
|
3317
|
+
background: transparent;
|
|
3318
|
+
}
|
|
3319
|
+
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
3320
|
+
background: rgba(255, 255, 255, 0.1);
|
|
3321
|
+
border-radius: 10px;
|
|
3322
|
+
}
|
|
3323
|
+
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
3324
|
+
background: rgba(255, 255, 255, 0.2);
|
|
3325
|
+
}
|
|
3326
|
+
@keyframes music-bar-1 {
|
|
3327
|
+
0%, 100% { height: 4px; }
|
|
3328
|
+
50% { height: 12px; }
|
|
3329
|
+
}
|
|
3330
|
+
@keyframes music-bar-2 {
|
|
3331
|
+
0%, 100% { height: 8px; }
|
|
3332
|
+
50% { height: 16px; }
|
|
3333
|
+
}
|
|
3334
|
+
@keyframes music-bar-3 {
|
|
3335
|
+
0%, 100% { height: 6px; }
|
|
3336
|
+
50% { height: 14px; }
|
|
3337
|
+
}
|
|
3338
|
+
.animate-music-bar-1 { animation: music-bar-1 0.6s infinite; }
|
|
3339
|
+
.animate-music-bar-2 { animation: music-bar-2 0.8s infinite; }
|
|
3340
|
+
.animate-music-bar-3 { animation: music-bar-3 0.7s infinite; }
|
|
3341
|
+
` } })
|
|
3342
|
+
);
|
|
3343
|
+
};
|
|
3344
|
+
var TrackInfo = ({ track, className = "" }) => {
|
|
3345
|
+
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"));
|
|
3346
|
+
};
|
|
3347
|
+
|
|
3348
|
+
// src/mmd/music-player/MMDMusicPlayer.tsx
|
|
3349
|
+
var MMDMusicPlayer = forwardRef(
|
|
3350
|
+
({
|
|
3351
|
+
config,
|
|
3352
|
+
stage,
|
|
3353
|
+
mobileOptimization,
|
|
3354
|
+
initialTrackIndex = 0,
|
|
3355
|
+
onTrackChange,
|
|
3356
|
+
onPlayPause,
|
|
3357
|
+
onProgress,
|
|
3358
|
+
onError,
|
|
3359
|
+
className,
|
|
3360
|
+
style
|
|
3361
|
+
}, ref) => {
|
|
3362
|
+
const { tracks, autoPlay = false, defaultLoopMode = "list" } = config;
|
|
3363
|
+
const [currentIndex, setCurrentIndex] = useState(initialTrackIndex);
|
|
3364
|
+
const [isPlaying, setIsPlaying] = useState(autoPlay);
|
|
3365
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
3366
|
+
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
3367
|
+
const [currentTime, setCurrentTime] = useState(0);
|
|
3368
|
+
const [duration, setDuration] = useState(0);
|
|
3369
|
+
const [loopMode, setLoopMode] = useState(defaultLoopMode);
|
|
3370
|
+
const [showPlaylist, setShowPlaylist] = useState(false);
|
|
3371
|
+
const [isUIVisible, setIsUIVisible] = useState(true);
|
|
3372
|
+
const [isCameraManual, setIsCameraManual] = useState(false);
|
|
3373
|
+
const playerRef = useRef(null);
|
|
3374
|
+
const containerRef = useRef(null);
|
|
3375
|
+
const isStartedRef = useRef(autoPlay);
|
|
3376
|
+
const uiTimeoutRef = useRef(null);
|
|
3377
|
+
const currentTrack = tracks[currentIndex];
|
|
3378
|
+
const goToTrack = useCallback(
|
|
3379
|
+
(index) => {
|
|
3380
|
+
if (index < 0 || index >= tracks.length) return;
|
|
3381
|
+
if (isTransitioning) return;
|
|
3382
|
+
const track = tracks[index];
|
|
3383
|
+
if (!track) return;
|
|
3384
|
+
console.log(`[MMDMusicPlayer] Transitioning to track ${index}: ${track.title}`);
|
|
3385
|
+
setIsTransitioning(true);
|
|
3386
|
+
setIsLoading(true);
|
|
3387
|
+
const wasPlaying = isPlaying;
|
|
3388
|
+
setIsPlaying(false);
|
|
3389
|
+
setTimeout(() => {
|
|
3390
|
+
setCurrentIndex(index);
|
|
3391
|
+
setCurrentTime(0);
|
|
3392
|
+
setDuration(0);
|
|
3393
|
+
onTrackChange?.(track, index);
|
|
3394
|
+
setTimeout(() => {
|
|
3395
|
+
setIsTransitioning(false);
|
|
3396
|
+
if (wasPlaying) {
|
|
3397
|
+
isStartedRef.current = true;
|
|
3398
|
+
}
|
|
3399
|
+
}, 100);
|
|
3400
|
+
}, 300);
|
|
3401
|
+
},
|
|
3402
|
+
[tracks, isTransitioning, isPlaying, onTrackChange]
|
|
3403
|
+
);
|
|
3404
|
+
const next = useCallback(() => {
|
|
3405
|
+
let nextIndex = currentIndex + 1;
|
|
3406
|
+
if (loopMode === "shuffle") {
|
|
3407
|
+
nextIndex = Math.floor(Math.random() * tracks.length);
|
|
3408
|
+
} else if (nextIndex >= tracks.length) {
|
|
3409
|
+
nextIndex = 0;
|
|
3410
|
+
}
|
|
3411
|
+
goToTrack(nextIndex);
|
|
3412
|
+
}, [currentIndex, tracks.length, loopMode, goToTrack]);
|
|
3413
|
+
const previous = useCallback(() => {
|
|
3414
|
+
let prevIndex = currentIndex - 1;
|
|
3415
|
+
if (prevIndex < 0) {
|
|
3416
|
+
prevIndex = tracks.length - 1;
|
|
3417
|
+
}
|
|
3418
|
+
goToTrack(prevIndex);
|
|
3419
|
+
}, [currentIndex, tracks.length, goToTrack]);
|
|
3420
|
+
useImperativeHandle(
|
|
3421
|
+
ref,
|
|
3422
|
+
() => ({
|
|
3423
|
+
play: () => {
|
|
3424
|
+
setIsPlaying(true);
|
|
3425
|
+
isStartedRef.current = true;
|
|
3426
|
+
playerRef.current?.play();
|
|
3427
|
+
},
|
|
3428
|
+
pause: () => {
|
|
3429
|
+
setIsPlaying(false);
|
|
3430
|
+
isStartedRef.current = false;
|
|
3431
|
+
playerRef.current?.pause();
|
|
3432
|
+
},
|
|
3433
|
+
next,
|
|
3434
|
+
previous,
|
|
3435
|
+
goToTrack,
|
|
3436
|
+
setLoopMode,
|
|
3437
|
+
getState: () => ({
|
|
3438
|
+
currentIndex,
|
|
3439
|
+
isPlaying,
|
|
3440
|
+
currentTime,
|
|
3441
|
+
duration,
|
|
3442
|
+
loopMode
|
|
3443
|
+
})
|
|
3444
|
+
}),
|
|
3445
|
+
[next, previous, goToTrack, currentIndex, isPlaying, currentTime, duration, loopMode]
|
|
3446
|
+
);
|
|
3447
|
+
const handleEnded = useCallback(() => {
|
|
3448
|
+
if (loopMode === "single") {
|
|
3449
|
+
playerRef.current?.seek(0);
|
|
3450
|
+
playerRef.current?.play();
|
|
3451
|
+
} else {
|
|
3452
|
+
next();
|
|
3453
|
+
}
|
|
3454
|
+
}, [loopMode, next]);
|
|
3455
|
+
const handleTimeUpdate = useCallback((time) => {
|
|
3456
|
+
setCurrentTime(time);
|
|
3457
|
+
if (playerRef.current) {
|
|
3458
|
+
const total = playerRef.current.getDuration();
|
|
3459
|
+
setDuration(total);
|
|
3460
|
+
onProgress?.(time, total);
|
|
3461
|
+
}
|
|
3462
|
+
}, [onProgress]);
|
|
3463
|
+
const resetUITimeout = useCallback(() => {
|
|
3464
|
+
setIsUIVisible(true);
|
|
3465
|
+
if (uiTimeoutRef.current) clearTimeout(uiTimeoutRef.current);
|
|
3466
|
+
if (isPlaying) {
|
|
3467
|
+
uiTimeoutRef.current = setTimeout(() => {
|
|
3468
|
+
if (!showPlaylist) setIsUIVisible(false);
|
|
3469
|
+
}, 5e3);
|
|
3470
|
+
}
|
|
3471
|
+
}, [isPlaying, showPlaylist]);
|
|
3472
|
+
useEffect(() => {
|
|
3473
|
+
resetUITimeout();
|
|
3474
|
+
return () => {
|
|
3475
|
+
if (uiTimeoutRef.current) clearTimeout(uiTimeoutRef.current);
|
|
3476
|
+
};
|
|
3477
|
+
}, [resetUITimeout]);
|
|
3478
|
+
if (!currentTrack) {
|
|
3479
|
+
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");
|
|
3480
|
+
}
|
|
3481
|
+
return /* @__PURE__ */ React6.createElement(
|
|
3482
|
+
"div",
|
|
3483
|
+
{
|
|
3484
|
+
ref: containerRef,
|
|
3485
|
+
className: `relative bg-black group ${className}`,
|
|
3486
|
+
style: { width: "100%", height: "100%", overflow: "hidden", ...style },
|
|
3487
|
+
onMouseMove: resetUITimeout,
|
|
3488
|
+
onClick: resetUITimeout
|
|
3489
|
+
},
|
|
3490
|
+
/* @__PURE__ */ React6.createElement(
|
|
3491
|
+
"div",
|
|
3492
|
+
{
|
|
3493
|
+
className: "absolute inset-0 w-full h-full",
|
|
3494
|
+
style: {
|
|
3495
|
+
zIndex: 0,
|
|
3496
|
+
opacity: isLoading || isTransitioning ? 0 : 1,
|
|
3497
|
+
transition: "opacity 0.5s ease-in-out"
|
|
3498
|
+
}
|
|
3499
|
+
},
|
|
3500
|
+
!isTransitioning && /* @__PURE__ */ React6.createElement(
|
|
3501
|
+
MMDPlayerBase,
|
|
3502
|
+
{
|
|
3503
|
+
key: currentTrack.id,
|
|
3504
|
+
ref: playerRef,
|
|
3505
|
+
resources: currentTrack.resources,
|
|
3506
|
+
stage: { ...stage, ...currentTrack.stage },
|
|
3507
|
+
autoPlay: isStartedRef.current,
|
|
3508
|
+
loop: loopMode === "single",
|
|
3509
|
+
mobileOptimization,
|
|
3510
|
+
onLoad: () => {
|
|
3511
|
+
console.log("[MMDMusicPlayer] Track loaded");
|
|
3512
|
+
setIsLoading(false);
|
|
3513
|
+
if (isStartedRef.current) {
|
|
3514
|
+
playerRef.current?.play();
|
|
3515
|
+
setIsPlaying(true);
|
|
3516
|
+
}
|
|
3517
|
+
},
|
|
3518
|
+
onPlay: () => {
|
|
3519
|
+
setIsPlaying(true);
|
|
3520
|
+
onPlayPause?.(true);
|
|
3521
|
+
},
|
|
3522
|
+
onPause: () => {
|
|
3523
|
+
setIsPlaying(false);
|
|
3524
|
+
onPlayPause?.(false);
|
|
3525
|
+
},
|
|
3526
|
+
onCameraChange: (isManual) => {
|
|
3527
|
+
setIsCameraManual(isManual);
|
|
3528
|
+
},
|
|
3529
|
+
onTimeUpdate: handleTimeUpdate,
|
|
3530
|
+
onEnded: handleEnded,
|
|
3531
|
+
onError
|
|
3532
|
+
}
|
|
3533
|
+
)
|
|
3534
|
+
),
|
|
3535
|
+
(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..."))),
|
|
3536
|
+
/* @__PURE__ */ React6.createElement(
|
|
3537
|
+
"div",
|
|
3538
|
+
{
|
|
3539
|
+
className: `absolute inset-0 z-10 flex flex-col justify-between transition-opacity duration-700 pointer-events-none ${isUIVisible ? "opacity-100" : "opacity-0"}`
|
|
3540
|
+
},
|
|
3541
|
+
/* @__PURE__ */ React6.createElement("div", { className: "pt-12 px-8 flex justify-center" }, /* @__PURE__ */ React6.createElement(TrackInfo, { track: currentTrack })),
|
|
3542
|
+
/* @__PURE__ */ React6.createElement("div", { className: "pb-12 px-8" }, /* @__PURE__ */ React6.createElement(
|
|
3543
|
+
MusicControls,
|
|
3544
|
+
{
|
|
3545
|
+
isPlaying,
|
|
3546
|
+
currentTime,
|
|
3547
|
+
duration,
|
|
3548
|
+
loopMode,
|
|
3549
|
+
isCameraManual,
|
|
3550
|
+
onPlayPause: () => isPlaying ? playerRef.current?.pause() : playerRef.current?.play(),
|
|
3551
|
+
onNext: next,
|
|
3552
|
+
onPrevious: previous,
|
|
3553
|
+
onSeek: (time) => playerRef.current?.seek(time),
|
|
3554
|
+
onResetCamera: () => {
|
|
3555
|
+
playerRef.current?.resetCamera();
|
|
3556
|
+
setIsCameraManual(false);
|
|
3557
|
+
},
|
|
3558
|
+
onToggleLoop: () => {
|
|
3559
|
+
const modes = ["list", "single", "shuffle"];
|
|
3560
|
+
const nextMode = modes[(modes.indexOf(loopMode) + 1) % modes.length];
|
|
3561
|
+
setLoopMode(nextMode);
|
|
3562
|
+
},
|
|
3563
|
+
onTogglePlaylist: () => setShowPlaylist(!showPlaylist)
|
|
3564
|
+
}
|
|
3565
|
+
))
|
|
3566
|
+
),
|
|
3567
|
+
/* @__PURE__ */ React6.createElement(
|
|
3568
|
+
PlaylistPanel,
|
|
3569
|
+
{
|
|
3570
|
+
tracks,
|
|
3571
|
+
currentIndex,
|
|
3572
|
+
isOpen: showPlaylist,
|
|
3573
|
+
onClose: () => setShowPlaylist(false),
|
|
3574
|
+
onSelectTrack: (index) => {
|
|
3575
|
+
goToTrack(index);
|
|
3576
|
+
setShowPlaylist(false);
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
)
|
|
3580
|
+
);
|
|
3581
|
+
}
|
|
3582
|
+
);
|
|
3583
|
+
MMDMusicPlayer.displayName = "MMDMusicPlayer";
|
|
2886
3584
|
|
|
2887
|
-
export { DialogueBox, HistoryPanel, LoadingOverlay, LoadingScreen, MMDPlayerBase, MMDPlayerEnhanced, MMDPlayerEnhancedDebugInfo, MMDPlaylist, MMDPlaylistDebugInfo, MMDVisualNovel, StartScreen, loadAmmo };
|
|
3585
|
+
export { DialogueBox, HistoryPanel, LoadingOverlay, LoadingScreen, MMDMusicPlayer, MMDPlayerBase, MMDPlayerEnhanced, MMDPlayerEnhancedDebugInfo, MMDPlaylist, MMDPlaylistDebugInfo, MMDVisualNovel, MusicControls, PlaylistPanel, StartScreen, TrackInfo, loadAmmo };
|
|
2888
3586
|
//# sourceMappingURL=index.mjs.map
|
|
2889
3587
|
//# sourceMappingURL=index.mjs.map
|