diff --git a/src/features/DanceList/Item/index.tsx b/src/features/DanceList/Item/index.tsx index 220c6b62..579c9651 100644 --- a/src/features/DanceList/Item/index.tsx +++ b/src/features/DanceList/Item/index.tsx @@ -42,22 +42,22 @@ const DanceItem = (props: DanceItemProps) => { const { t } = useTranslation('common'); const { downloading: audioDownloading, percent: audioPercent, fetchAudioUrl } = useLoadAudio(); - const { downloading: danceDownloading, percent: dancePercent, fetchDanceBuffer } = useLoadDance(); + const { downloading: danceDownloading, percent: dancePercent, fetchDanceUrl } = useLoadDance(); const viewer = useGlobalStore((s) => s.viewer); const handlePlayPause = () => { - viewer.model?.resetToIdle(); + viewer.model?.disposeAll(); if (isPlaying && isCurrentPlay) { setIsPlaying(false); } else { setCurrentPlayId(danceItem.danceId); setIsPlaying(true); const audioPromise = fetchAudioUrl(danceItem.danceId, danceItem.audio); - const dancePromise = fetchDanceBuffer(danceItem.danceId, danceItem.src); + const dancePromise = fetchDanceUrl(danceItem.danceId, danceItem.src); Promise.all([dancePromise, audioPromise]).then((res) => { if (!res) return; - const [danceBuffer, audioUrl] = res; - viewer.model?.dance(danceBuffer, audioUrl); + const [danceUrl, audioUrl] = res; + if (danceUrl && audioUrl) viewer.model?.dance(danceUrl, audioUrl); }); } }; diff --git a/src/features/MotionList/ActionList/ListItem.tsx b/src/features/MotionList/ActionList/ListItem.tsx index 07b3dd3f..f402376b 100644 --- a/src/features/MotionList/ActionList/ListItem.tsx +++ b/src/features/MotionList/ActionList/ListItem.tsx @@ -72,8 +72,8 @@ const TouchActionListItem = memo(({ item }) => { showAction={true} onClick={async () => { if (item.url) { - const url = await fetchMotionUrl(item.id, item.url); - viewer.model?.loadFBX(url); + const motionUrl = await fetchMotionUrl(item.id, item.url); + if (motionUrl) viewer.model?.loadFBX(motionUrl); } }} title={item.name} diff --git a/src/features/PostureList/ActionList/ListItem.tsx b/src/features/PostureList/ActionList/ListItem.tsx index 07b3dd3f..f402376b 100644 --- a/src/features/PostureList/ActionList/ListItem.tsx +++ b/src/features/PostureList/ActionList/ListItem.tsx @@ -72,8 +72,8 @@ const TouchActionListItem = memo(({ item }) => { showAction={true} onClick={async () => { if (item.url) { - const url = await fetchMotionUrl(item.id, item.url); - viewer.model?.loadFBX(url); + const motionUrl = await fetchMotionUrl(item.id, item.url); + if (motionUrl) viewer.model?.loadFBX(motionUrl); } }} title={item.name} diff --git a/src/hooks/useLoadAudio.tsx b/src/hooks/useLoadAudio.tsx index 1a2e960f..266c7446 100644 --- a/src/hooks/useLoadAudio.tsx +++ b/src/hooks/useLoadAudio.tsx @@ -33,6 +33,8 @@ export const useLoadAudio = () => { } finally { setDownloading(false); } + if (!audioBlob) return null; + return URL.createObjectURL(audioBlob); }; diff --git a/src/hooks/useLoadDance.tsx b/src/hooks/useLoadDance.tsx index 6771e1fc..53e8d159 100644 --- a/src/hooks/useLoadDance.tsx +++ b/src/hooks/useLoadDance.tsx @@ -1,3 +1,4 @@ +import { isArrayBuffer } from 'lodash-es'; import { useState } from 'react'; import { fetchWithProgress } from '@/utils/fetch'; @@ -8,31 +9,40 @@ export const useLoadDance = () => { const [downloading, setDownloading] = useState(false); const [percent, setPercent] = useState(0); - const fetchDanceBuffer = async (danceId: string, src: string) => { + const fetchDanceUrl = async (danceId: string, src: string) => { const localDancePath = getDancePathByDanceId(danceId); - let danceBuffer = (await storage.getItem(localDancePath)) as ArrayBuffer; + let danceBlob = await storage.getItem(localDancePath); + + // 存量转换 + if (danceBlob && isArrayBuffer(danceBlob)) { + // 如果存的是 ArrayBuffer,设置为空重新下载; + danceBlob = null; + } try { - if (!danceBuffer) { + if (!danceBlob) { setDownloading(true); setPercent(0); - danceBuffer = await fetchWithProgress(src, { + danceBlob = await fetchWithProgress(src, { onProgress: (loaded, total) => { setPercent((loaded / total) * 100); }, - }).then((res) => res.arrayBuffer()); - await storage.setItem(localDancePath, danceBuffer); + }); + await storage.setItem(localDancePath, danceBlob); } } finally { setDownloading(false); } - return danceBuffer; + + if (!danceBlob) return null; + + return URL.createObjectURL(danceBlob); }; return { downloading, percent, - fetchDanceBuffer, + fetchDanceUrl, }; }; diff --git a/src/hooks/useLoadModel.tsx b/src/hooks/useLoadModel.tsx index a7a7beb6..1d1cd191 100644 --- a/src/hooks/useLoadModel.tsx +++ b/src/hooks/useLoadModel.tsx @@ -16,20 +16,20 @@ export const useLoadModel = () => { if (!modelBlob) { setDownloading(true); setPercent(0); - const blob = await fetchWithProgress(remoteModelUrl, { + modelBlob = await fetchWithProgress(remoteModelUrl, { onProgress: (loaded, total) => { setPercent(Math.ceil((loaded / total) * 100)); }, }); const modelPath = getModelPathByAgentId(agentId); - await storage.setItem(modelPath, blob); + await storage.setItem(modelPath, modelBlob); } - } catch (e) { - console.error(e); - return null; } finally { setDownloading(false); } + + if (!modelBlob) return null; + return URL.createObjectURL(modelBlob); }; diff --git a/src/hooks/useLoadMotion.tsx b/src/hooks/useLoadMotion.tsx index bc7414b5..e7eafea7 100644 --- a/src/hooks/useLoadMotion.tsx +++ b/src/hooks/useLoadMotion.tsx @@ -10,7 +10,8 @@ export const useLoadMotion = () => { const fetchMotionUrl = async (motionId: string, motionUrl: string) => { const localMotionPath = getMotionPathByMotionId(motionId); - let motionBlob = (await storage.getItem(localMotionPath)) as Blob; + let motionBlob = await storage.getItem(localMotionPath); + try { if (!motionBlob) { setDownloading(true); @@ -26,6 +27,9 @@ export const useLoadMotion = () => { } finally { setDownloading(false); } + + if (!motionBlob) return null; + return URL.createObjectURL(motionBlob); }; diff --git a/src/libs/vrmViewer/model.ts b/src/libs/vrmViewer/model.ts index 06fefee6..6a77137d 100644 --- a/src/libs/vrmViewer/model.ts +++ b/src/libs/vrmViewer/model.ts @@ -156,17 +156,15 @@ export class Model { /** * 播放舞蹈,以音乐文件的播放作为结束标志。 */ - public async dance(dance: ArrayBuffer, audioUrl: string, onEnd?: () => void) { + public async dance(danceUrl: string, audioUrl: string, onEnd?: () => void) { const { vrm, mixer } = this; if (vrm && mixer) { this.disposeAll(); - const animation = convert(dance, toOffset(vrm)); - const clip = bindToVRM(animation, vrm); + const clip = await loadVMDAnimation(danceUrl, vrm); const action = mixer.clipAction(clip); action.setLoop(LoopOnce, 1).play(); // play animation if (audioUrl) { this._audioPlayer?.playFromURL(audioUrl, () => { - this.resetToIdle(); onEnd?.(); }); this._audio = audioUrl; @@ -178,12 +176,9 @@ export class Model { } public async resetToIdle() { - const { vrm, mixer } = this; - if (vrm && mixer) { - this.disposeAll(); + this.disposeAll(); - await this.loadIdleAnimation(); - } + await this.loadIdleAnimation(); } /** * 语音播放,配合人物表情动作