This commit is contained in:
LiJinHui
2025-11-15 10:45:22 +08:00
parent 94a43432fc
commit d45bf5f60a
3 changed files with 115 additions and 48 deletions
@@ -150,6 +150,8 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
VoiceDTO voiceDTO = new VoiceDTO(); VoiceDTO voiceDTO = new VoiceDTO();
voiceDTO.setId(entity.getId()); voiceDTO.setId(entity.getId());
voiceDTO.setName(MessageUtils.getMessage(ErrorCode.VOICE_CLONE_PREFIX) + entity.getName()); voiceDTO.setName(MessageUtils.getMessage(ErrorCode.VOICE_CLONE_PREFIX) + entity.getName());
// 保留从数据库查询到的voiceDemo字段
voiceDTO.setVoiceDemo(entity.getVoiceDemo());
redisUtils.set(RedisKeys.getTimbreNameById(voiceDTO.getId()), voiceDTO.getName(), redisUtils.set(RedisKeys.getTimbreNameById(voiceDTO.getId()), voiceDTO.getName(),
RedisUtils.NOT_EXPIRE); RedisUtils.NOT_EXPIRE);
voiceDTOs.add(0, voiceDTO); voiceDTOs.add(0, voiceDTO);
@@ -3,7 +3,7 @@
<mapper namespace="xiaozhi.modules.voiceclone.dao.VoiceCloneDao"> <mapper namespace="xiaozhi.modules.voiceclone.dao.VoiceCloneDao">
<select id="getTrainSuccess" resultType="xiaozhi.modules.model.dto.VoiceDTO"> <select id="getTrainSuccess" resultType="xiaozhi.modules.model.dto.VoiceDTO">
select id, name select id, name, voice_id as voiceDemo
from ai_voice_clone from ai_voice_clone
where model_id = #{modelId} and user_id = #{userId} and train_status = 2 where model_id = #{modelId} and user_id = #{userId} and train_status = 2
</select> </select>
+112 -47
View File
@@ -221,25 +221,37 @@
class="form-select" class="form-select"
> >
<el-option <el-option
v-for="(item, index) in voiceOptions" v-for="(item, index) in voiceOptions"
:key="`voice-${index}`" :key="`voice-${index}`"
:label="item.label" :label="item.label"
:value="item.value" :value="item.value"
> >
<div style="display: flex; justify-content: space-between; align-items: center;"> <div
<span>{{ item.label }}</span> style="
<template v-if="hasAudioPreview(item)"> display: flex;
<el-button justify-content: space-between;
type="text" align-items: center;
icon="el-icon-video-play" "
size="small" >
@click.stop="playVoicePreview(item.value)" <span>{{ item.label }}</span>
:loading="playingVoice && form.ttsVoiceId === item.value" <template v-if="hasAudioPreview(item)">
class="play-button" <el-button
/> type="text"
</template> :icon="
</div> playingVoice &&
</el-option> currentPlayingVoiceId === item.value &&
!isPaused
? 'el-icon-video-pause'
: 'el-icon-video-play'
"
size="small"
@click.stop="toggleAudioPlayback(item.value)"
:loading="false"
class="play-button"
/>
</template>
</div>
</el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</div> </div>
@@ -321,7 +333,9 @@ export default {
allFunctions: [], allFunctions: [],
originalFunctions: [], originalFunctions: [],
playingVoice: false, playingVoice: false,
isPaused: false,
currentAudio: null, currentAudio: null,
currentPlayingVoiceId: null,
}; };
}, },
methods: { methods: {
@@ -555,9 +569,16 @@ export default {
this.voiceOptions = data.data.map((voice) => ({ this.voiceOptions = data.data.map((voice) => ({
value: voice.id, value: voice.id,
label: voice.name, label: voice.name,
// 复制音频相关字段,确保hasAudioPreview能检测到
voiceDemo: voice.voiceDemo,
demoUrl: voice.demoUrl,
audioUrl: voice.audioUrl,
voice_demo: voice.voice_demo,
sample_voice: voice.sample_voice,
referenceAudio: voice.referenceAudio,
})); }));
// 保存完整的音色信息,添加调试信息 // 保存完整的音色信息,添加调试信息
console.log('获取到的音色数据:', data.data); console.log("获取到的音色数据:", data.data);
this.voiceDetails = data.data.reduce((acc, voice) => { this.voiceDetails = data.data.reduce((acc, voice) => {
acc[voice.id] = voice; acc[voice.id] = voice;
return acc; return acc;
@@ -676,17 +697,54 @@ export default {
}, },
// 检查是否有音频预览 // 检查是否有音频预览
hasAudioPreview(item) { hasAudioPreview(item) {
// 检查item中是否包含音频相关字段 // 检查item中是否包含有效的音频URL字段,且URL必须以http开头
const hasAudioFields = item.voiceDemo || item.demoUrl || item.audioUrl || const audioFields = [
item.voice_demo || item.sample_voice || item.referenceAudio; item.voiceDemo,
return hasAudioFields && typeof hasAudioFields === 'string' && hasAudioFields.trim() !== ''; item.demoUrl,
item.audioUrl,
item.voice_demo,
item.sample_voice,
item.referenceAudio
];
// 检查是否有任何音频字段是以http开头的有效URL
return audioFields.some(field =>
field !== undefined &&
field !== null &&
typeof field === "string" &&
field.trim() !== "" &&
field.toLowerCase().startsWith("http")
);
}, },
// 播放/暂停音频切换
toggleAudioPlayback(voiceId) {
// 如果点击的是当前正在播放的音频,则切换暂停/播放状态
if (this.playingVoice && this.currentPlayingVoiceId === voiceId) {
if (this.isPaused) {
// 从暂停状态恢复播放
this.currentAudio.play().catch((error) => {
console.error("恢复播放失败:", error);
this.$message.warning("无法恢复播放音频");
});
this.isPaused = false;
} else {
// 暂停播放
this.currentAudio.pause();
this.isPaused = true;
}
return;
}
// 否则开始播放新的音频
this.playVoicePreview(voiceId);
},
// 播放音色预览 // 播放音色预览
playVoicePreview(voiceId = null) { playVoicePreview(voiceId = null) {
// 如果传入了voiceId,则使用传入的,否则使用当前选中的 // 如果传入了voiceId,则使用传入的,否则使用当前选中的
const targetVoiceId = voiceId || this.form.ttsVoiceId; const targetVoiceId = voiceId || this.form.ttsVoiceId;
if (!targetVoiceId) { if (!targetVoiceId) {
this.$message.warning("请先选择一个音色"); this.$message.warning("请先选择一个音色");
return; return;
@@ -698,34 +756,41 @@ export default {
this.currentAudio = null; this.currentAudio = null;
} }
// 重置播放状态
this.isPaused = false;
this.currentPlayingVoiceId = targetVoiceId;
try { try {
// 从保存的音色详情中获取音频URL // 从保存的音色详情中获取音频URL
const voiceDetail = this.voiceDetails[targetVoiceId]; const voiceDetail = this.voiceDetails[targetVoiceId];
// 添加调试信息 // 添加调试信息
console.log('当前选择的音色ID:', targetVoiceId); console.log("当前选择的音色ID:", targetVoiceId);
console.log('音色详情:', voiceDetail); console.log("音色详情:", voiceDetail);
// 尝试多种可能的音频属性名 // 尝试多种可能的音频属性名
let audioUrl = null; let audioUrl = null;
if (voiceDetail) { if (voiceDetail) {
// 首先尝试直接从voiceDetail中获取各种可能的音频字段 // 首先尝试直接从voiceDetail中获取各种可能的音频字段
audioUrl = voiceDetail.voiceDemo || audioUrl =
voiceDetail.demoUrl || voiceDetail.voiceDemo ||
voiceDetail.audioUrl || voiceDetail.demoUrl ||
voiceDetail.voice_demo || voiceDetail.audioUrl ||
voiceDetail.sample_voice; voiceDetail.voice_demo ||
voiceDetail.sample_voice;
// 如果没有找到,尝试检查是否有URL格式的字段 // 如果没有找到,尝试检查是否有URL格式的字段
if (!audioUrl) { if (!audioUrl) {
for (const key in voiceDetail) { for (const key in voiceDetail) {
const value = voiceDetail[key]; const value = voiceDetail[key];
if (typeof value === 'string' && if (
(value.startsWith('http://') || typeof value === "string" &&
value.startsWith('https://') || (value.startsWith("http://") ||
value.endsWith('.mp3') || value.startsWith("https://") ||
value.endsWith('.wav') || value.endsWith(".mp3") ||
value.endsWith('.ogg'))) { value.endsWith(".wav") ||
value.endsWith(".ogg"))
) {
audioUrl = value; audioUrl = value;
console.log(`发现可能的音频URL在字段 '${key}':`, audioUrl); console.log(`发现可能的音频URL在字段 '${key}':`, audioUrl);
break; break;
@@ -733,7 +798,7 @@ export default {
} }
} }
} }
if (!audioUrl) { if (!audioUrl) {
// 如果没有音频URL显示友好的提示 // 如果没有音频URL显示友好的提示
this.$message.warning("该音色暂无可预览的音频"); this.$message.warning("该音色暂无可预览的音频");
@@ -742,7 +807,7 @@ export default {
// 设置播放状态 // 设置播放状态
this.playingVoice = true; this.playingVoice = true;
// 创建并播放音频 // 创建并播放音频
this.currentAudio = new Audio(audioUrl); this.currentAudio = new Audio(audioUrl);
@@ -760,7 +825,7 @@ export default {
// 监听播放错误 // 监听播放错误
this.currentAudio.onerror = () => { this.currentAudio.onerror = () => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
console.error('音频播放错误'); console.error("音频播放错误");
this.$message.warning("音频播放失败"); this.$message.warning("音频播放失败");
this.playingVoice = false; this.playingVoice = false;
}; };
@@ -778,7 +843,7 @@ export default {
// 实际调用play方法开始播放 // 实际调用play方法开始播放
this.currentAudio.play().catch((error) => { this.currentAudio.play().catch((error) => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
console.error('播放失败:', error); console.error("播放失败:", error);
this.$message.warning("无法播放音频"); this.$message.warning("无法播放音频");
this.playingVoice = false; this.playingVoice = false;
}); });
@@ -952,7 +1017,7 @@ export default {
} }
.play-button { .play-button {
color: #409EFF; color: #409eff;
transition: color 0.3s; transition: color 0.3s;
} }