fix(#1195): Prettier格式对齐 + 音频实例竞态校验 + 卸载清理
CI/CD Pipeline / Check if frontend-only change (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1151h45m50s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1151h45m51s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1151h45m53s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1151h45m55s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1151h45m54s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1152h19m1s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1152h19m4s

This commit is contained in:
2026-07-30 15:41:40 +08:00
parent 71ca97f03a
commit db213d1c35
@@ -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<string | null>(
null,
);
const cloneAudioRef = useRef<HTMLAudioElement | null>(null);
const cloneMountedRef = useRef(true);
const [playingCloneVoice, setPlayingCloneVoice] = useState<string | null>(null)
const cloneAudioRef = useRef<HTMLAudioElement | null>(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