feat:优化
This commit is contained in:
@@ -1,172 +1,147 @@
|
|||||||
/**
|
/**
|
||||||
* TTS (Text-to-Speech) 公共Hook
|
* TTS (Text-to-Speech) 公共 Hook
|
||||||
* 支持多个供应商:CosyVoice, SiliconFlow, Azure, AWS等
|
* 支持多个供应商:CosyVoice, SiliconFlow, Azure, AWS等
|
||||||
*/
|
*/
|
||||||
import { ref, computed } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
import { VoiceService } from '@/api/voice'
|
import { VoiceService } from '@/api/voice'
|
||||||
import { normalizeProviderType, VOICE_PROVIDER_TYPES } from '@/config/voiceConfig'
|
import { normalizeProviderType, VOICE_PROVIDER_TYPES } from '@/config/voiceConfig'
|
||||||
|
|
||||||
// ========== 常量 ==========
|
// ========== 常量 ==========
|
||||||
|
|
||||||
/** 兼容旧代码的导出 */
|
|
||||||
const TTS_PROVIDERS = VOICE_PROVIDER_TYPES
|
const TTS_PROVIDERS = VOICE_PROVIDER_TYPES
|
||||||
|
|
||||||
/** 默认配置 */
|
|
||||||
const DEFAULT_CONFIG = {
|
const DEFAULT_CONFIG = {
|
||||||
apiEndpoint: '/api/tik/voice/tts',
|
apiEndpoint: '/api/tik/voice/tts',
|
||||||
audioFormat: 'mp3',
|
audioFormat: 'mp3',
|
||||||
supportedFormats: ['mp3', 'wav']
|
supportedFormats: ['mp3', 'wav']
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 最大预览缓存数量 */
|
|
||||||
const MAX_PREVIEW_CACHE_SIZE = 50
|
const MAX_PREVIEW_CACHE_SIZE = 50
|
||||||
|
|
||||||
// ========== 类型定义 ==========
|
// ========== 工具函数 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} AudioData
|
* 从字符串提取 ID
|
||||||
* @property {Blob} blob - 音频 Blob
|
* @param {string} idStr - 格式如 'user-123'
|
||||||
* @property {string} objectUrl - 对象 URL
|
* @returns {number|null}
|
||||||
* @property {string} format - 音频格式
|
|
||||||
*/
|
*/
|
||||||
|
function extractIdFromString(idStr) {
|
||||||
|
if (typeof idStr !== 'string' || !idStr.startsWith('user-')) return null
|
||||||
|
const id = parseInt(idStr.replace('user-', ''))
|
||||||
|
return Number.isNaN(id) ? null : id
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解码 Base64 音频为 Blob
|
||||||
|
* @param {string} base64 - Base64 编码的音频
|
||||||
|
* @param {string} format - 音频格式
|
||||||
|
* @returns {{ blob: Blob, objectUrl: string }}
|
||||||
|
*/
|
||||||
|
function decodeBase64Audio(base64, format = 'mp3') {
|
||||||
|
const chars = window.atob(base64)
|
||||||
|
const bytes = new Uint8Array(chars.length)
|
||||||
|
for (let i = 0; i < chars.length; i++) {
|
||||||
|
bytes[i] = chars.charCodeAt(i)
|
||||||
|
}
|
||||||
|
const mime = format === 'mp3' ? 'audio/mpeg' : `audio/${format}`
|
||||||
|
const blob = new Blob([bytes], { type: mime })
|
||||||
|
return { blob, objectUrl: URL.createObjectURL(blob) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成缓存键
|
||||||
|
*/
|
||||||
|
function generateCacheKey(voiceId, text, rate) {
|
||||||
|
return `${voiceId}:${text}:${rate}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== useTTS 主函数 ==========
|
||||||
|
|
||||||
export function useTTS(options = {}) {
|
export function useTTS(options = {}) {
|
||||||
const {
|
const { provider = VOICE_PROVIDER_TYPES.SILICONFLOW } = options
|
||||||
provider = VOICE_PROVIDER_TYPES.SILICONFLOW,
|
|
||||||
customConfig = {}
|
|
||||||
} = options
|
|
||||||
|
|
||||||
// 状态管理(移到函数内部,避免模块级状态污染)
|
// ----- 状态 -----
|
||||||
const previewAudioCache = new Map()
|
|
||||||
const previewLoadingVoiceId = ref(null)
|
|
||||||
const playingPreviewVoiceId = ref(null)
|
|
||||||
const ttsText = ref('')
|
const ttsText = ref('')
|
||||||
const speechRate = ref(1.0)
|
const speechRate = ref(1.0)
|
||||||
|
const previewLoadingVoiceId = ref(null)
|
||||||
|
const playingPreviewVoiceId = ref(null)
|
||||||
|
|
||||||
// 音频实例(移到函数内部)
|
// ----- 音频实例 -----
|
||||||
let previewAudio = null
|
let previewAudio = null
|
||||||
let previewObjectUrl = ''
|
let previewObjectUrl = ''
|
||||||
|
const previewAudioCache = new Map()
|
||||||
|
|
||||||
// ========== 辅助函数 ==========
|
// ----- 音频播放 -----
|
||||||
|
|
||||||
function getProviderConfig() {
|
|
||||||
return DEFAULT_CONFIG
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 播放音频预览
|
* 播放音频
|
||||||
* @param {string} url - 音频 URL
|
* @param {string} url - 音频 URL
|
||||||
* @param {Object} playOptions - 播放选项
|
* @param {Object} opts - 选项
|
||||||
* @param {boolean} [playOptions.revokeOnEnd=false] - 播放结束后是否释放 URL
|
* @param {boolean} [opts.revokeOnEnd] - 播放结束后释放 URL
|
||||||
* @param {Function} [playOptions.onEnded] - 播放结束回调
|
* @param {Function} [opts.onEnded] - 播放结束回调
|
||||||
*/
|
*/
|
||||||
function playAudioPreview(url, playOptions = {}) {
|
function playAudioPreview(url, opts = {}) {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
message.warning('暂无可试听的音频')
|
message.warning('暂无可试听的音频')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 停止当前播放
|
stopCurrentPlayback()
|
||||||
try {
|
|
||||||
previewAudio?.pause?.()
|
|
||||||
previewAudio = null
|
|
||||||
} catch {
|
|
||||||
// 忽略停止播放的错误
|
|
||||||
}
|
|
||||||
|
|
||||||
const audio = new Audio(url)
|
const audio = new Audio(url)
|
||||||
|
const cleanup = () => {
|
||||||
function cleanup() {
|
if (opts.revokeOnEnd && url.startsWith('blob:')) {
|
||||||
if (playOptions.revokeOnEnd && url.startsWith('blob:')) {
|
|
||||||
URL.revokeObjectURL(url)
|
URL.revokeObjectURL(url)
|
||||||
if (previewObjectUrl === url) {
|
if (previewObjectUrl === url) previewObjectUrl = ''
|
||||||
previewObjectUrl = ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
previewAudio = null
|
previewAudio = null
|
||||||
playOptions.onEnded?.()
|
opts.onEnded?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
audio.play()
|
audio.play()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
previewAudio = audio
|
previewAudio = audio
|
||||||
audio.onended = cleanup
|
audio.onended = cleanup
|
||||||
audio.onerror = () => {
|
audio.onerror = () => { cleanup(); message.error('播放失败') }
|
||||||
cleanup()
|
|
||||||
message.error('播放失败')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
cleanup()
|
|
||||||
message.error('播放失败')
|
|
||||||
})
|
})
|
||||||
|
.catch(() => { cleanup(); message.error('播放失败') })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成预览缓存键
|
* 停止当前播放
|
||||||
* @param {Object} voice - 音色对象
|
|
||||||
* @returns {string} 缓存键
|
|
||||||
*/
|
*/
|
||||||
function generatePreviewCacheKey(voice) {
|
function stopCurrentPlayback() {
|
||||||
const voiceId = voice.voiceId || voice.rawId || voice.id
|
try {
|
||||||
const text = ttsText.value.trim()
|
previewAudio?.pause?.()
|
||||||
const rate = speechRate.value
|
previewAudio = null
|
||||||
return `${voiceId}:${text}:${rate}`
|
} catch { /* ignore */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解码并缓存Base64音频
|
* 清除音频缓存
|
||||||
* @param {string} audioBase64 - Base64 编码的音频数据
|
|
||||||
* @param {string} [format='mp3'] - 音频格式
|
|
||||||
* @param {string} cacheKey - 缓存键
|
|
||||||
* @returns {Promise<Object>} 音频数据
|
|
||||||
*/
|
*/
|
||||||
async function decodeAndCacheBase64(audioBase64, format = 'mp3', cacheKey) {
|
function clearAudioCache() {
|
||||||
const byteCharacters = window.atob(audioBase64)
|
previewAudioCache.forEach(data => URL.revokeObjectURL(data.objectUrl))
|
||||||
const byteNumbers = new Uint8Array(byteCharacters.length)
|
previewAudioCache.clear()
|
||||||
for (let i = 0; i < byteCharacters.length; i++) {
|
}
|
||||||
byteNumbers[i] = byteCharacters.charCodeAt(i)
|
|
||||||
}
|
|
||||||
const mime = format === 'mp3' ? 'audio/mpeg' : `audio/${format}`
|
|
||||||
const blob = new Blob([byteNumbers], { type: mime })
|
|
||||||
const objectUrl = URL.createObjectURL(blob)
|
|
||||||
|
|
||||||
const audioData = { blob, objectUrl, format }
|
// ----- 缓存管理 -----
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存音频数据
|
||||||
|
*/
|
||||||
|
function cacheAudio(cacheKey, audioData) {
|
||||||
previewAudioCache.set(cacheKey, audioData)
|
previewAudioCache.set(cacheKey, audioData)
|
||||||
|
|
||||||
if (previewAudioCache.size > MAX_PREVIEW_CACHE_SIZE) {
|
if (previewAudioCache.size > MAX_PREVIEW_CACHE_SIZE) {
|
||||||
const firstKey = previewAudioCache.keys().next().value
|
const firstKey = previewAudioCache.keys().next().value
|
||||||
const oldData = previewAudioCache.get(firstKey)
|
const oldData = previewAudioCache.get(firstKey)
|
||||||
URL.revokeObjectURL(oldData.objectUrl)
|
URL.revokeObjectURL(oldData.objectUrl)
|
||||||
previewAudioCache.delete(firstKey)
|
previewAudioCache.delete(firstKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
return audioData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ----- 预览/试听 -----
|
||||||
* 播放缓存的音频
|
|
||||||
* @param {Object} audioData - 音频数据
|
|
||||||
* @param {Function} onEnded - 播放结束回调
|
|
||||||
*/
|
|
||||||
function playCachedAudio(audioData, onEnded) {
|
|
||||||
if (previewObjectUrl && previewObjectUrl !== audioData.objectUrl) {
|
|
||||||
URL.revokeObjectURL(previewObjectUrl)
|
|
||||||
}
|
|
||||||
previewObjectUrl = audioData.objectUrl
|
|
||||||
|
|
||||||
playAudioPreview(previewObjectUrl, {
|
|
||||||
revokeOnEnd: false,
|
|
||||||
onEnded() {
|
|
||||||
if (audioData.objectUrl?.startsWith('blob:')) {
|
|
||||||
URL.revokeObjectURL(audioData.objectUrl)
|
|
||||||
}
|
|
||||||
onEnded?.()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置预览状态
|
* 重置预览状态
|
||||||
@@ -176,21 +151,8 @@ export function useTTS(options = {}) {
|
|||||||
playingPreviewVoiceId.value = null
|
playingPreviewVoiceId.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 提取ID从字符串
|
|
||||||
* @param {string} idStr - 包含前缀的ID字符串
|
|
||||||
* @returns {number|null} 提取的ID
|
|
||||||
*/
|
|
||||||
function extractIdFromString(idStr) {
|
|
||||||
if (typeof idStr !== 'string' || !idStr.startsWith('user-')) return null
|
|
||||||
const extractedId = parseInt(idStr.replace('user-', ''))
|
|
||||||
return Number.isNaN(extractedId) ? null : extractedId
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建预览参数
|
* 构建预览参数
|
||||||
* @param {Object} voice - 音色对象
|
|
||||||
* @returns {Object|null} 预览参数
|
|
||||||
*/
|
*/
|
||||||
function buildPreviewParams(voice) {
|
function buildPreviewParams(voice) {
|
||||||
const configId = voice.rawId || extractIdFromString(voice.id)
|
const configId = voice.rawId || extractIdFromString(voice.id)
|
||||||
@@ -198,53 +160,64 @@ export function useTTS(options = {}) {
|
|||||||
message.error('配音配置无效')
|
message.error('配音配置无效')
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const providerConfig = getProviderConfig()
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
voiceConfigId: configId,
|
voiceConfigId: configId,
|
||||||
inputText: ttsText.value,
|
inputText: ttsText.value,
|
||||||
speechRate: speechRate.value || 1.0,
|
speechRate: speechRate.value || 1.0,
|
||||||
audioFormat: providerConfig.audioFormat,
|
audioFormat: DEFAULT_CONFIG.audioFormat,
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
providerType: normalizeProviderType(provider)
|
providerType: normalizeProviderType(provider)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 播放缓存的音频
|
||||||
|
*/
|
||||||
|
function playCachedAudio(audioData) {
|
||||||
|
if (previewObjectUrl && previewObjectUrl !== audioData.objectUrl) {
|
||||||
|
URL.revokeObjectURL(previewObjectUrl)
|
||||||
|
}
|
||||||
|
previewObjectUrl = audioData.objectUrl
|
||||||
|
playAudioPreview(previewObjectUrl, {
|
||||||
|
revokeOnEnd: false,
|
||||||
|
onEnded: () => {
|
||||||
|
if (audioData.objectUrl?.startsWith('blob:')) {
|
||||||
|
URL.revokeObjectURL(audioData.objectUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 播放音色试听
|
* 播放音色试听
|
||||||
* @param {Object} voice - 音色对象
|
* @param {Object} voice - 音色对象
|
||||||
* @param {Function} onSuccess - 成功回调
|
* @param {Function} onSuccess - 成功回调
|
||||||
* @param {Function} onError - 错误回调
|
* @param {Function} onError - 错误回调
|
||||||
* @param {Object} options - 选项
|
* @param {Object} opts - 选项 { autoPlay: boolean }
|
||||||
* @param {boolean} options.autoPlay - 是否自动播放(默认 true)
|
|
||||||
*/
|
*/
|
||||||
async function playVoiceSample(voice, onSuccess, onError, options = { autoPlay: true }) {
|
async function playVoiceSample(voice, onSuccess, onError, opts = { autoPlay: true }) {
|
||||||
if (!voice) return
|
if (!voice) return
|
||||||
if (previewLoadingVoiceId.value === voice.id || playingPreviewVoiceId.value === voice.id) {
|
|
||||||
return
|
const voiceId = voice.voiceId || voice.rawId || voice.id
|
||||||
}
|
const cacheKey = generateCacheKey(voiceId, ttsText.value.trim(), speechRate.value)
|
||||||
|
|
||||||
|
// 防止重复点击
|
||||||
|
if (previewLoadingVoiceId.value === voice.id || playingPreviewVoiceId.value === voice.id) return
|
||||||
|
|
||||||
|
// 停止其他播放
|
||||||
if (playingPreviewVoiceId.value && playingPreviewVoiceId.value !== voice.id) {
|
if (playingPreviewVoiceId.value && playingPreviewVoiceId.value !== voice.id) {
|
||||||
try {
|
stopCurrentPlayback()
|
||||||
previewAudio?.pause?.()
|
|
||||||
previewAudio = null
|
|
||||||
} catch {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
previewLoadingVoiceId.value = voice.id
|
previewLoadingVoiceId.value = voice.id
|
||||||
playingPreviewVoiceId.value = voice.id
|
playingPreviewVoiceId.value = voice.id
|
||||||
|
|
||||||
const cacheKey = generatePreviewCacheKey(voice)
|
// 命中缓存
|
||||||
const cachedAudio = previewAudioCache.get(cacheKey)
|
const cached = previewAudioCache.get(cacheKey)
|
||||||
|
if (cached) {
|
||||||
if (cachedAudio) {
|
if (opts.autoPlay !== false) playCachedAudio(cached)
|
||||||
if (options.autoPlay !== false) {
|
resetPreviewState()
|
||||||
playCachedAudio(cachedAudio, resetPreviewState)
|
onSuccess?.(cached)
|
||||||
} else {
|
|
||||||
resetPreviewState()
|
|
||||||
}
|
|
||||||
onSuccess?.(cachedAudio)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,31 +237,30 @@ export function useTTS(options = {}) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理音频 URL
|
||||||
if (res.data?.audioUrl) {
|
if (res.data?.audioUrl) {
|
||||||
resetPreviewState()
|
resetPreviewState()
|
||||||
if (options.autoPlay !== false) {
|
if (opts.autoPlay !== false) {
|
||||||
playAudioPreview(res.data.audioUrl, {
|
playAudioPreview(res.data.audioUrl, { revokeOnEnd: true })
|
||||||
revokeOnEnd: true,
|
|
||||||
onEnded() {
|
|
||||||
URL.revokeObjectURL(res.data.audioUrl)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
onSuccess?.(res.data)
|
onSuccess?.(res.data)
|
||||||
} else if (res.data?.audioBase64) {
|
return
|
||||||
const audioData = await decodeAndCacheBase64(res.data.audioBase64, res.data.format, cacheKey)
|
|
||||||
resetPreviewState()
|
|
||||||
if (options.autoPlay !== false) {
|
|
||||||
playCachedAudio(audioData, () => {
|
|
||||||
URL.revokeObjectURL(audioData.objectUrl)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onSuccess?.(audioData)
|
|
||||||
} else {
|
|
||||||
message.error('试听失败')
|
|
||||||
resetPreviewState()
|
|
||||||
onError?.(new Error('未收到音频数据'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理 Base64 音频
|
||||||
|
if (res.data?.audioBase64) {
|
||||||
|
const { blob, objectUrl } = decodeBase64Audio(res.data.audioBase64, res.data.format)
|
||||||
|
const audioData = { blob, objectUrl, format: res.data.format }
|
||||||
|
cacheAudio(cacheKey, audioData)
|
||||||
|
resetPreviewState()
|
||||||
|
if (opts.autoPlay !== false) playCachedAudio(audioData)
|
||||||
|
onSuccess?.(audioData)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
message.error('试听失败')
|
||||||
|
resetPreviewState()
|
||||||
|
onError?.(new Error('未收到音频数据'))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('试听失败')
|
message.error('试听失败')
|
||||||
resetPreviewState()
|
resetPreviewState()
|
||||||
@@ -296,71 +268,41 @@ export function useTTS(options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----- TTS 合成 -----
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TTS文本转语音
|
* TTS 文本转语音
|
||||||
* @param {Object} params - TTS 参数
|
* @param {Object} params - TTS 参数
|
||||||
* @returns {Promise<Object>} TTS 结果
|
* @returns {Promise<Object>}
|
||||||
*/
|
*/
|
||||||
async function synthesize(params) {
|
async function synthesize(params) {
|
||||||
const providerConfig = getProviderConfig()
|
return VoiceService.synthesize({
|
||||||
|
|
||||||
const ttsParams = {
|
|
||||||
inputText: params.inputText || ttsText.value,
|
inputText: params.inputText || ttsText.value,
|
||||||
voiceConfigId: params.voiceConfigId,
|
voiceConfigId: params.voiceConfigId,
|
||||||
speechRate: params.speechRate || speechRate.value,
|
speechRate: params.speechRate || speechRate.value,
|
||||||
audioFormat: params.audioFormat || providerConfig.audioFormat,
|
audioFormat: params.audioFormat || DEFAULT_CONFIG.audioFormat,
|
||||||
providerType: normalizeProviderType(provider)
|
providerType: normalizeProviderType(provider)
|
||||||
}
|
})
|
||||||
|
|
||||||
return await VoiceService.synthesize(ttsParams)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ----- Setters -----
|
||||||
* 设置文本
|
|
||||||
* @param {string} text - 要设置的文本
|
|
||||||
*/
|
|
||||||
function setText(text) {
|
function setText(text) {
|
||||||
ttsText.value = text
|
ttsText.value = text
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置语速
|
|
||||||
* @param {number} rate - 语速倍率
|
|
||||||
*/
|
|
||||||
function setSpeechRate(rate) {
|
function setSpeechRate(rate) {
|
||||||
speechRate.value = rate
|
speechRate.value = rate
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ----- 返回接口 -----
|
||||||
* 清除音频缓存
|
|
||||||
*/
|
|
||||||
function clearAudioCache() {
|
|
||||||
previewAudioCache.forEach((audioData) => {
|
|
||||||
URL.revokeObjectURL(audioData.objectUrl)
|
|
||||||
})
|
|
||||||
previewAudioCache.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 停止当前播放
|
|
||||||
*/
|
|
||||||
function stopCurrentPlayback() {
|
|
||||||
try {
|
|
||||||
previewAudio?.pause?.()
|
|
||||||
previewAudio = null
|
|
||||||
} catch {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 返回接口 ==========
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
// 状态
|
||||||
previewLoadingVoiceId,
|
|
||||||
playingPreviewVoiceId,
|
|
||||||
ttsText,
|
ttsText,
|
||||||
speechRate,
|
speechRate,
|
||||||
|
previewLoadingVoiceId,
|
||||||
|
playingPreviewVoiceId,
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
playVoiceSample,
|
playVoiceSample,
|
||||||
@@ -373,9 +315,9 @@ export function useTTS(options = {}) {
|
|||||||
resetPreviewState,
|
resetPreviewState,
|
||||||
|
|
||||||
// 配置
|
// 配置
|
||||||
getProviderConfig,
|
|
||||||
TTS_PROVIDERS,
|
TTS_PROVIDERS,
|
||||||
DEFAULT_CONFIG
|
DEFAULT_CONFIG,
|
||||||
|
getProviderConfig: () => DEFAULT_CONFIG
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -336,7 +336,8 @@ public class TikUserFileServiceImpl implements TikUserFileService {
|
|||||||
throw exception(FILE_CATEGORY_INVALID, "文件不是视频类型");
|
throw exception(FILE_CATEGORY_INVALID, "文件不是视频类型");
|
||||||
}
|
}
|
||||||
|
|
||||||
return getCachedPresignUrl(file.getFileUrl(), PRESIGN_URL_EXPIRATION_SECONDS);
|
// 视频播放URL不缓存,每次都生成新的签名URL
|
||||||
|
return fileApi.presignGetUrl(file.getFileUrl(), PRESIGN_URL_EXPIRATION_SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -347,7 +348,8 @@ public class TikUserFileServiceImpl implements TikUserFileService {
|
|||||||
throw exception(FILE_CATEGORY_INVALID, "文件不是音频类型");
|
throw exception(FILE_CATEGORY_INVALID, "文件不是音频类型");
|
||||||
}
|
}
|
||||||
|
|
||||||
return getCachedPresignUrl(file.getFileUrl(), PRESIGN_URL_EXPIRATION_SECONDS);
|
// 音频播放URL不缓存,每次都生成新的签名URL
|
||||||
|
return fileApi.presignGetUrl(file.getFileUrl(), PRESIGN_URL_EXPIRATION_SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user