fix : 切换播放,暂停播放bug

This commit is contained in:
2025-12-22 18:03:10 +08:00
parent 8f1dbc28f7
commit ffcf34fb10
2 changed files with 77 additions and 15 deletions

View File

@@ -37,6 +37,7 @@ export const useAudioSpeak = (config = {}) => {
let firstSegmentHeader = null
let lastPlayedIndex = -1
let currentPlayingIndex = -1 // 当前正在播放的片段索引
let isInterrupted = false // 是否被中断
// 按标点分割文本
const splitTextByPunctuation = (text) => {
@@ -167,7 +168,7 @@ export const useAudioSpeak = (config = {}) => {
// 初始化音频上下文
const initAudioContext = () => {
if (!audioContext) {
if (!audioContext || audioContext.state === 'closed') {
audioContext = new (window.AudioContext || window.webkitAudioContext)()
console.log('音频上下文已初始化')
}
@@ -178,6 +179,7 @@ export const useAudioSpeak = (config = {}) => {
const decodeAndPlayBlob = async (audioBlob, segmentIndex) => {
return new Promise((resolve, reject) => {
if (isCancelled || !audioContext) {
console.log('播放已取消或音频上下文不存在,跳过播放')
resolve()
return
}
@@ -193,6 +195,13 @@ export const useAudioSpeak = (config = {}) => {
const arrayBuffer = e.target.result
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer)
// 如果在此期间被取消,直接返回
if (isCancelled) {
console.log('播放过程中被取消')
resolve()
return
}
audioSource = audioContext.createBufferSource()
audioSource.buffer = audioBuffer
audioSource.connect(audioContext.destination)
@@ -212,6 +221,12 @@ export const useAudioSpeak = (config = {}) => {
}
console.log(`▶️开始播放第${segmentIndex + 1}个片段`)
// 如果音频上下文被暂停,先恢复
if (audioContext.state === 'suspended') {
await audioContext.resume()
}
audioSource.start(0)
} catch (error) {
@@ -295,7 +310,7 @@ export const useAudioSpeak = (config = {}) => {
isPlayingQueue = true
try {
while (audioQueue.length > 0 && !isCancelled) {
while (audioQueue.length > 0 && !isCancelled && !isInterrupted) {
const audioItem = audioQueue[0]
currentSegmentIndex.value = audioItem.segmentIndex
@@ -303,6 +318,11 @@ export const useAudioSpeak = (config = {}) => {
console.log(`准备播放第${audioItem.segmentIndex + 1}个片段: "${audioItem.text}"`)
await decodeAndPlayBlob(audioItem.blob, audioItem.segmentIndex)
if (isCancelled || isInterrupted) {
console.log('播放被中断,退出播放队列')
break
}
// 播放完成后移除
audioQueue.shift()
console.log(`片段${audioItem.segmentIndex + 1}播放完成,队列剩余: ${audioQueue.length}`)
@@ -329,6 +349,12 @@ export const useAudioSpeak = (config = {}) => {
// 向队列添加音频
const addToQueue = (audioItem) => {
// 如果被取消或中断,不添加到队列
if (isCancelled || isInterrupted) {
console.log('播放已中断,不添加到队列')
return
}
// 按segmentIndex插入到正确位置
let insertIndex = 0
for (let i = audioQueue.length - 1; i >= 0; i--) {
@@ -471,19 +497,23 @@ export const useAudioSpeak = (config = {}) => {
// 清理资源
const cleanup = () => {
console.log('开始清理资源')
isCancelled = true
isInterrupted = true
if (audioSource) {
try {
audioSource.stop()
console.log('音频源已停止')
} catch (e) {
// 忽略错误
console.warn('停止音频源失败:', e)
}
audioSource = null
}
if (audioContext && audioContext.state !== 'closed') {
audioContext.close()
console.log('音频上下文已关闭')
}
audioContext = null
@@ -500,38 +530,57 @@ export const useAudioSpeak = (config = {}) => {
isPaused.value = false
isLoading.value = false
progress.value = 0
console.log('资源清理完成')
}
// 停止播放
const stopAudio = () => {
console.log('停止音频播放')
isCancelled = true
isInterrupted = true
if (audioSource) {
try {
audioSource.stop()
console.log('音频源已停止')
} catch (e) {
// 忽略错误
console.warn('停止音频源失败:', e)
}
audioSource = null
}
audioQueue = []
isPlayingQueue = false
currentPlayingIndex = -1
isSpeaking.value = false
isPaused.value = false
isLoading.value = false
// 恢复中断标志,为下一次播放准备
setTimeout(() => {
isCancelled = false
isInterrupted = false
}, 100)
}
// 主speak方法
const speak = async (text) => {
console.log('开始新的语音播报')
// 先停止当前播放
if (isSpeaking.value || audioQueue.length > 0) {
console.log('检测到正在播放,先停止')
stopAudio()
// 等待一小段时间确保资源清理完成
await new Promise(resolve => setTimeout(resolve, 200))
}
text = extractSpeechText(text)
console.log('开始语音播报:', text)
// 如果正在播放,先停止
if (isSpeaking.value) {
stopAudio()
}
// 重置状态
isCancelled = false
isInterrupted = false
currentText.value = text
isLoading.value = true
isSpeaking.value = true
@@ -562,7 +611,10 @@ export const useAudioSpeak = (config = {}) => {
// 1. 串行请求所有音频片段
for (let i = 0; i < segments.length; i++) {
if (isCancelled) break
if (isCancelled || isInterrupted) {
console.log('播放被取消或中断,停止请求')
break
}
console.log(`串行请求第${i + 1}/${segments.length}个片段`)
@@ -572,6 +624,11 @@ export const useAudioSpeak = (config = {}) => {
// 请求音频片段
const audioItem = await fetchAudioSegment(segments[i], i)
if (isCancelled || isInterrupted) {
console.log('播放被取消或中断,停止添加队列')
break
}
// 添加到播放队列
addToQueue({
blob: audioItem.blob,
@@ -586,6 +643,11 @@ export const useAudioSpeak = (config = {}) => {
}
}
if (isCancelled || isInterrupted) {
console.log('播放被取消或中断,退出播放')
return
}
// 2. 所有请求完成
console.log('所有音频片段请求完成')
allRequestsCompleted = true
@@ -594,7 +656,7 @@ export const useAudioSpeak = (config = {}) => {
tryMergeRemainingSegments()
// 4. 等待所有音频播放完成
while (audioQueue.length > 0 && !isCancelled) {
while (audioQueue.length > 0 && !isCancelled && !isInterrupted) {
await new Promise(resolve => setTimeout(resolve, 100))
}
@@ -604,9 +666,11 @@ export const useAudioSpeak = (config = {}) => {
console.error('语音播报失败:', error)
} finally {
// 最终清理
if (isCancelled) {
if (isCancelled || isInterrupted) {
console.log('播放被取消或中断,进行清理')
cleanup()
} else {
console.log('播放正常完成')
isSpeaking.value = false
isPaused.value = false
isLoading.value = false
@@ -642,8 +706,6 @@ export const useAudioSpeak = (config = {}) => {
cleanup()
}
// 组件卸载时清理
return {
// 状态
isSpeaking,

View File

@@ -734,8 +734,8 @@ function colseFeeBack() {
}
function readMarkdown(value, index) {
speechIndex.value = index;
if (speechIndex.value !== index) {
speechIndex.value = index;
speak(value);
return;
}