diff --git a/apps/web/src/pages/generate/hooks/useStep5Voice.tsx b/apps/web/src/pages/generate/hooks/useStep5Voice.tsx index 27fa35269..3ee459c9b 100755 --- a/apps/web/src/pages/generate/hooks/useStep5Voice.tsx +++ b/apps/web/src/pages/generate/hooks/useStep5Voice.tsx @@ -2,30 +2,30 @@ * Step 5 配音选择 Hook * 封装 AI 推荐、预设音色试听、TTS 自定义合成、存为素材等逻辑 */ -import { useState, useRef, useCallback, useEffect } from "react"; -import { message } from "antd"; -import type { VoiceClone } from "@/api/voice-clone"; -import { VOICE_GENDER_ICON, CLONE_STATUS_CONFIG } from "../constants"; -import { formatDuration } from "../utils/formatDuration"; -import { useVoiceAudio } from "./step5-voice/useVoiceAudio"; -import { useVoiceRecommend } from "./step5-voice/useVoiceRecommend"; -import { useTtsSynthesis } from "./step5-voice/useTtsSynthesis"; -import { useSaveToLibrary } from "./step5-voice/useSaveToLibrary"; -import { useVoiceModeSelection } from "./step5-voice/useVoiceModeSelection"; +import { useState, useRef, useCallback, useEffect } from "react" +import { message } from "antd" +import type { VoiceClone } from "@/api/voice-clone" +import { VOICE_GENDER_ICON, CLONE_STATUS_CONFIG } from "../constants" +import { formatDuration } from "../utils/formatDuration" +import { useVoiceAudio } from "./step5-voice/useVoiceAudio" +import { useVoiceRecommend } from "./step5-voice/useVoiceRecommend" +import { useTtsSynthesis } from "./step5-voice/useTtsSynthesis" +import { useSaveToLibrary } from "./step5-voice/useSaveToLibrary" +import { useVoiceModeSelection } from "./step5-voice/useVoiceModeSelection" interface UseStep5VoiceProps { - selectedVoice: string; - onSelectedVoiceChange: (voiceId: string) => void; - voiceMode: "preset" | "custom" | "clone"; - onVoiceModeChange: (mode: "preset" | "custom" | "clone") => void; - selectedClonedVoice: string; - onSelectedClonedVoiceChange: (voiceId: string) => void; - clonedVoices: VoiceClone[]; - addClone: (voice: VoiceClone) => void; - hasProcessing: boolean; - cloneModalOpen: boolean; - onCloneModalOpenChange: (open: boolean) => void; - titleText: string; + selectedVoice: string + onSelectedVoiceChange: (voiceId: string) => void + voiceMode: "preset" | "custom" | "clone" + onVoiceModeChange: (mode: "preset" | "custom" | "clone") => void + selectedClonedVoice: string + onSelectedClonedVoiceChange: (voiceId: string) => void + clonedVoices: VoiceClone[] + addClone: (voice: VoiceClone) => void + hasProcessing: boolean + cloneModalOpen: boolean + onCloneModalOpenChange: (open: boolean) => void + titleText: string } export function useStep5Voice({ @@ -43,7 +43,7 @@ export function useStep5Voice({ titleText, }: UseStep5VoiceProps) { /* ── 子模块 ── */ - const { playingVoice, toggleVoicePlay } = useVoiceAudio(); + const { playingVoice, toggleVoicePlay } = useVoiceAudio() const { presetVoices, @@ -52,7 +52,7 @@ export function useStep5Voice({ voiceRecommendations, hasVoiceRecommend, handleVoiceRecommend, - } = useVoiceRecommend(titleText); + } = useVoiceRecommend(titleText) const { customVoiceText, @@ -64,7 +64,7 @@ export function useStep5Voice({ synthesizeMutation, handleSynthesizeVoice, resetTtsState, - } = useTtsSynthesis(selectedVoice); + } = useTtsSynthesis(selectedVoice) const { saveModalOpen, @@ -80,7 +80,7 @@ export function useStep5Voice({ handleOpenSaveModal, handleConfirmSave, handleAddTagInModal, - } = useSaveToLibrary(completedTtsJobId, resetTtsState); + } = useSaveToLibrary(completedTtsJobId, resetTtsState) const { handleSelectRecommendedVoice, @@ -99,81 +99,79 @@ export function useStep5Voice({ addClone, cloneModalOpen, onCloneModalOpenChange, - }); + }) /* ── 克隆音色试听播放 ── */ - const [playingCloneVoice, setPlayingCloneVoice] = useState( - null, - ); - const cloneAudioRef = useRef(null); - const cloneMountedRef = useRef(true); + const [playingCloneVoice, setPlayingCloneVoice] = useState(null) + const cloneAudioRef = useRef(null) + const cloneMountedRef = useRef(true) // 组件卸载时清理克隆音色试听的音频资源 useEffect(() => { - cloneMountedRef.current = true; + cloneMountedRef.current = true return () => { - cloneMountedRef.current = false; + cloneMountedRef.current = false if (cloneAudioRef.current) { - cloneAudioRef.current.onended = null; - cloneAudioRef.current.pause(); - cloneAudioRef.current = null; + cloneAudioRef.current.onended = null + cloneAudioRef.current.pause() + cloneAudioRef.current = null } - }; - }, []); + } + }, []) const handlePlayCloneSample = useCallback( (voice: VoiceClone) => { if (playingCloneVoice === voice.id) { if (cloneAudioRef.current) { - cloneAudioRef.current.onended = null; - cloneAudioRef.current.pause(); + cloneAudioRef.current.onended = null + cloneAudioRef.current.pause() } - cloneAudioRef.current = null; - setPlayingCloneVoice(null); - return; + cloneAudioRef.current = null + setPlayingCloneVoice(null) + return } // 暂停上一个音频并清除事件监听,避免竞态 if (cloneAudioRef.current) { - cloneAudioRef.current.onended = null; - cloneAudioRef.current.pause(); + cloneAudioRef.current.onended = null + cloneAudioRef.current.pause() } // TODO: 后端克隆音色试听接口出来后,改为调用接口获取试听音频 // 目前使用 sample_url 字段,如果没有则提示 - const audioUrl = voice.sample_url; + const audioUrl = voice.sample_url if (!audioUrl) { - message.warning("该克隆音色暂无试听音频"); - return; + message.warning("该克隆音色暂无试听音频") + return } - const audio = new Audio(audioUrl); - cloneAudioRef.current = audio; + const audio = new Audio(audioUrl) + cloneAudioRef.current = audio audio .play() .then(() => { // 只有当前活跃的音频实例才能更新状态,防止快速切换竞态 if (cloneMountedRef.current && cloneAudioRef.current === audio) { - setPlayingCloneVoice(voice.id); + setPlayingCloneVoice(voice.id) } }) .catch(() => { if (cloneMountedRef.current && cloneAudioRef.current === audio) { - message.error("播放失败,请检查网络"); - cloneAudioRef.current = null; + message.error("播放失败,请检查网络") + cloneAudioRef.current = null } - }); + }) audio.onended = () => { if (cloneMountedRef.current && cloneAudioRef.current === audio) { - setPlayingCloneVoice(null); - cloneAudioRef.current = null; + setPlayingCloneVoice(null) + cloneAudioRef.current = null } - }; + } }, [playingCloneVoice], - ); + ) const handleCloneSuccess = (voice: VoiceClone) => { - rawCloneSuccess(voice); - message.success("音色克隆成功!"); - }; + rawCloneSuccess(voice) + message.success("音色克隆成功!") + } return { // 数据 @@ -232,7 +230,7 @@ export function useStep5Voice({ VOICE_GENDER_ICON, CLONE_STATUS_CONFIG, formatDuration, - }; + } } -export default useStep5Voice; +export default useStep5Voice