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