From ebb6bc6e3388573a118869a73648dc0e01ff08d4 Mon Sep 17 00:00:00 2001 From: francis_fh <13935151924@163.com> Date: Fri, 23 Jan 2026 13:10:40 +0800 Subject: [PATCH] =?UTF-8?q?bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hook/useTTSPlayer.js | 313 +++++++++++++++++++++++----- pages/chat/components/ai-paging.vue | 22 +- 2 files changed, 276 insertions(+), 59 deletions(-) diff --git a/hook/useTTSPlayer.js b/hook/useTTSPlayer.js index 2864987..a7a6d1b 100644 --- a/hook/useTTSPlayer.js +++ b/hook/useTTSPlayer.js @@ -15,14 +15,15 @@ export function useTTSPlayer(httpUrl) { const isComplete = ref(false) // #ifdef H5 - const audioContext = typeof window !== 'undefined' && (window.AudioContext || window.webkitAudioContext) - ? new(window.AudioContext || window.webkitAudioContext)() - : null + // H5环境,使用 AudioContext + // 初始化时不立即创建,而是在需要时创建,确保在用户交互后创建 + let audioContext = null // #endif // #ifdef MP-WEIXIN const audioContext = null // 微信小程序不支持 AudioContext let innerAudioContext = null // 微信小程序音频上下文 + let backgroundAudioManager = null // 微信小程序背景音频管理器 // #endif let currentAudioBuffer = null @@ -31,41 +32,103 @@ export function useTTSPlayer(httpUrl) { // 初始化微信小程序音频上下文 // #ifdef MP-WEIXIN - const initInnerAudioContext = () => { - if (!innerAudioContext) { - innerAudioContext = uni.createInnerAudioContext() - innerAudioContext.autoplay = false - innerAudioContext.onPlay(() => { - console.log('🎵 微信小程序音频播放开始') + const initAudioManager = () => { + try { + console.log('📱 微信小程序:创建背景音频管理器') + backgroundAudioManager = uni.getBackgroundAudioManager() + + // 设置默认配置 + backgroundAudioManager.title = 'AI语音播报' + backgroundAudioManager.singer = 'KS AI' + backgroundAudioManager.coverImgUrl = '/static/icon/logo.png' + backgroundAudioManager.volume = 1.0 + + backgroundAudioManager.onPlay(() => { + console.log('🎵 微信小程序背景音频播放开始') isSpeaking.value = true isPaused.value = false }) - innerAudioContext.onPause(() => { - console.log('⏸️ 微信小程序音频播放暂停') + + backgroundAudioManager.onPause(() => { + console.log('⏸️ 微信小程序背景音频播放暂停') isPaused.value = true }) - innerAudioContext.onStop(() => { - console.log('⏹️ 微信小程序音频播放停止') + + backgroundAudioManager.onStop(() => { + console.log('⏹️ 微信小程序背景音频播放停止') isSpeaking.value = false isComplete.value = true }) - innerAudioContext.onEnded(() => { - console.log('🎵 微信小程序音频播放结束') + + backgroundAudioManager.onEnded(() => { + console.log('🎵 微信小程序背景音频播放结束') isSpeaking.value = false isComplete.value = true }) - innerAudioContext.onError((res) => { - console.error('❌ 微信小程序音频播放错误:', res.errMsg) + + backgroundAudioManager.onError((res) => { + console.error('❌ 微信小程序背景音频播放错误:', res.errMsg, '错误码:', res.errCode) isSpeaking.value = false isComplete.value = false }) - innerAudioContext.onCanplay(() => { - console.log('🎵 微信小程序音频可以播放了') - // 只有在需要播放且未播放状态下才调用play - if (isSpeaking.value && !isPaused.value) { - innerAudioContext.play() - } + + backgroundAudioManager.onCanplay(() => { + console.log('🎵 微信小程序背景音频可以播放了') }) + + backgroundAudioManager.onWaiting(() => { + console.log('⏳ 微信小程序背景音频加载中...') + }) + + console.log('✅ 微信小程序背景音频管理器初始化成功') + return true + } catch (e) { + console.error('❌ 微信小程序背景音频管理器初始化失败:', e) + // 降级使用InnerAudioContext + console.log('🔄 微信小程序:背景音频不可用,降级使用InnerAudioContext') + + if (!innerAudioContext) { + innerAudioContext = uni.createInnerAudioContext() + innerAudioContext.autoplay = false + innerAudioContext.obeyMuteSwitch = false + + innerAudioContext.onPlay(() => { + console.log('🎵 微信小程序InnerAudioContext播放开始') + isSpeaking.value = true + isPaused.value = false + }) + + innerAudioContext.onPause(() => { + console.log('⏸️ 微信小程序InnerAudioContext播放暂停') + isPaused.value = true + }) + + innerAudioContext.onStop(() => { + console.log('⏹️ 微信小程序InnerAudioContext播放停止') + isSpeaking.value = false + isComplete.value = true + }) + + innerAudioContext.onEnded(() => { + console.log('🎵 微信小程序InnerAudioContext播放结束') + isSpeaking.value = false + isComplete.value = true + }) + + innerAudioContext.onError((res) => { + console.error('❌ 微信小程序InnerAudioContext错误:', res.errMsg, '错误码:', res.errCode) + isSpeaking.value = false + isComplete.value = false + }) + + innerAudioContext.onCanplay(() => { + console.log('🎵 微信小程序InnerAudioContext可以播放了') + if (isSpeaking.value && !isPaused.value) { + innerAudioContext.play() + } + }) + } + return false } } // #endif @@ -84,20 +147,97 @@ export function useTTSPlayer(httpUrl) { console.log('🔗 Final GET URL:', url); // #ifdef MP-WEIXIN - // 微信小程序环境,使用微信音频API - initInnerAudioContext() - console.log('🎵 微信小程序:设置音频 src 为:', url); + // 微信小程序环境,使用背景音频管理器 + const isBackgroundAudioAvailable = initAudioManager() + // 重置音频状态 isSpeaking.value = true isPaused.value = false isComplete.value = false - // 设置src,等待onCanplay事件触发后再播放 - innerAudioContext.src = url + + if (isBackgroundAudioAvailable && backgroundAudioManager) { + console.log('🎵 微信小程序:使用背景音频管理器播放,URL:', url); + + // 设置背景音频参数 + backgroundAudioManager.title = 'AI语音播报' + backgroundAudioManager.singer = 'KS AI' + backgroundAudioManager.coverImgUrl = '/static/icon/logo.png' + + // 直接设置src并播放 + backgroundAudioManager.src = url + console.log('🎵 微信小程序背景音频开始播放'); + } else { + // 降级方案:使用InnerAudioContext + console.log('🔄 微信小程序:背景音频不可用,降级使用InnerAudioContext'); + + // 如果已有音频上下文,先销毁再重新创建 + if (innerAudioContext) { + innerAudioContext.destroy() + innerAudioContext = null + } + + innerAudioContext = uni.createInnerAudioContext() + innerAudioContext.autoplay = false + innerAudioContext.obeyMuteSwitch = false + innerAudioContext.volume = 1.0 + + innerAudioContext.onPlay(() => { + console.log('🎵 微信小程序InnerAudioContext播放开始') + isSpeaking.value = true + isPaused.value = false + }) + + innerAudioContext.onPause(() => { + console.log('⏸️ 微信小程序InnerAudioContext播放暂停') + isPaused.value = true + }) + + innerAudioContext.onStop(() => { + console.log('⏹️ 微信小程序InnerAudioContext播放停止') + isSpeaking.value = false + isComplete.value = true + }) + + innerAudioContext.onEnded(() => { + console.log('🎵 微信小程序InnerAudioContext播放结束') + isSpeaking.value = false + isComplete.value = true + }) + + innerAudioContext.onError((res) => { + console.error('❌ 微信小程序InnerAudioContext错误:', res.errMsg, '错误码:', res.errCode) + isSpeaking.value = false + isComplete.value = false + }) + + innerAudioContext.onCanplay(() => { + console.log('🎵 微信小程序InnerAudioContext可以播放了') + if (isSpeaking.value && !isPaused.value) { + innerAudioContext.play() + } + }) + + innerAudioContext.src = url + console.log('🎵 微信小程序InnerAudioContext开始播放'); + } // #endif // #ifdef H5 // H5环境,使用 AudioContext - if (audioContext) { + try { + // 确保AudioContext已创建 + if (!audioContext) { + console.log('🎵 H5: 创建新的AudioContext'); + audioContext = new (window.AudioContext || window.webkitAudioContext)(); + } + + // 检查并恢复AudioContext状态(浏览器安全策略要求用户交互后才能播放音频) + if (audioContext.state === 'suspended') { + console.log('🎵 H5: 恢复挂起的AudioContext'); + await audioContext.resume(); + console.log('✅ H5: AudioContext已恢复,状态:', audioContext.state); + } + // 发送GET请求获取语音数据 const response = await fetch(url) if (!response.ok) { @@ -106,20 +246,52 @@ export function useTTSPlayer(httpUrl) { // 获取二进制数据 const arrayBuffer = await response.arrayBuffer() - console.log('✅ Received audio data, size:', arrayBuffer.byteLength + ' bytes'); + console.log('✅ H5: Received audio data, size:', arrayBuffer.byteLength + ' bytes'); try { // 直接使用 audioContext.decodeAudioData 解码,不依赖外部库 const decoded = await audioContext.decodeAudioData(arrayBuffer) - console.log('✅ Audio decoded, sampleRate:', decoded.sampleRate, 'channels:', decoded.numberOfChannels); + console.log('✅ H5: Audio decoded, sampleRate:', decoded.sampleRate, 'channels:', decoded.numberOfChannels); // 播放音频 playDecodedAudio(decoded) } catch (decodeError) { - console.error('❌ AudioContext decodeAudioData failed:', decodeError); + console.error('❌ H5: AudioContext decodeAudioData failed:', decodeError); // 降级处理:创建一个简单的音频缓冲区 createFallbackAudio(arrayBuffer) } + } catch (h5Error) { + console.error('❌ H5: Audio playback failed:', h5Error); + // 尝试使用HTML5 Audio元素作为最终降级方案 + try { + console.log('🔄 H5: 尝试使用HTML5 Audio元素播放'); + const audio = new Audio(url); + audio.play(); + console.log('✅ H5: HTML5 Audio元素开始播放'); + + // 设置音频状态 + isSpeaking.value = true; + isPaused.value = false; + isComplete.value = false; + + // 监听播放结束 + audio.onended = () => { + console.log('🎵 H5: HTML5 Audio播放结束'); + isSpeaking.value = false; + isComplete.value = true; + }; + + // 监听播放错误 + audio.onerror = (error) => { + console.error('❌ H5: HTML5 Audio播放错误:', error); + isSpeaking.value = false; + isComplete.value = false; + }; + } catch (audioError) { + console.error('❌ H5: HTML5 Audio播放也失败了:', audioError); + isSpeaking.value = false; + isComplete.value = false; + } } // #endif @@ -177,11 +349,29 @@ export function useTTSPlayer(httpUrl) { // #endif const pause = () => { + console.log('⏸️ TTS pause called'); + // #ifdef MP-WEIXIN + // 优先使用背景音频管理器 + if (backgroundAudioManager) { + try { + backgroundAudioManager.pause() + console.log('⏸️ 微信小程序背景音频暂停'); + return + } catch (e) { + console.error('❌ 微信小程序背景音频暂停失败:', e); + } + } + + // 降级使用InnerAudioContext if (innerAudioContext && isSpeaking.value && !isPaused.value) { - console.log('⏸️ 微信小程序音频暂停'); - innerAudioContext.pause() - return + try { + innerAudioContext.pause() + console.log('⏸️ 微信小程序InnerAudioContext暂停'); + return + } catch (e) { + console.error('❌ 微信小程序InnerAudioContext暂停失败:', e); + } } // #endif @@ -191,24 +381,40 @@ export function useTTSPlayer(httpUrl) { return; } - console.log('⏸️ TTS pause called'); - if (audioContext.state === 'running') { audioContext.suspend() isPaused.value = true // 保存当前播放位置 playTimeOffset = audioContext.currentTime - console.log('✅ Audio paused successfully'); + console.log('✅ H5 Audio paused successfully'); } // #endif } const resume = () => { + console.log('▶️ TTS resume called'); + // #ifdef MP-WEIXIN + // 优先使用背景音频管理器 + if (backgroundAudioManager) { + try { + backgroundAudioManager.play() + console.log('▶️ 微信小程序背景音频恢复播放'); + return + } catch (e) { + console.error('❌ 微信小程序背景音频恢复失败:', e); + } + } + + // 降级使用InnerAudioContext if (innerAudioContext && isSpeaking.value && isPaused.value) { - console.log('▶️ 微信小程序音频恢复播放'); - innerAudioContext.play() - return + try { + innerAudioContext.play() + console.log('▶️ 微信小程序InnerAudioContext恢复播放'); + return + } catch (e) { + console.error('❌ 微信小程序InnerAudioContext恢复失败:', e); + } } // #endif @@ -218,12 +424,10 @@ export function useTTSPlayer(httpUrl) { return; } - console.log('▶️ TTS resume called'); - if (audioContext.state === 'suspended') { audioContext.resume() isPaused.value = false - console.log('✅ Audio resumed successfully'); + console.log('✅ H5 Audio resumed successfully'); } // #endif } @@ -236,12 +440,25 @@ export function useTTSPlayer(httpUrl) { console.log('⏹️ TTS stop called'); // #ifdef MP-WEIXIN + // 优先使用背景音频管理器 + if (backgroundAudioManager) { + try { + backgroundAudioManager.stop() + console.log('✅ 微信小程序背景音频停止'); + } catch (e) { + console.error('❌ 微信小程序背景音频停止失败:', e); + } + } + + // 降级使用InnerAudioContext if (innerAudioContext) { try { innerAudioContext.stop() - console.log('✅ 微信小程序音频停止'); + console.log('✅ 微信小程序InnerAudioContext停止'); + innerAudioContext.destroy() + innerAudioContext = null } catch (e) { - console.error('❌ 微信小程序音频停止错误:', e); + console.error('❌ 微信小程序InnerAudioContext停止错误:', e); } } // #endif @@ -252,7 +469,7 @@ export function useTTSPlayer(httpUrl) { currentSource.stop() currentSource.disconnect() } catch (e) { - console.error('❌ Error stopping audio source:', e); + console.error('❌ Error stopping H5 audio source:', e); } currentSource = null } @@ -261,7 +478,7 @@ export function useTTSPlayer(httpUrl) { try { audioContext.suspend() } catch (e) { - console.error('❌ Error suspending audio context:', e); + console.error('❌ Error suspending H5 audio context:', e); } } // #endif diff --git a/pages/chat/components/ai-paging.vue b/pages/chat/components/ai-paging.vue index ffeb33f..48cfa47 100644 --- a/pages/chat/components/ai-paging.vue +++ b/pages/chat/components/ai-paging.vue @@ -135,14 +135,14 @@ -
- - - - - + + + + + + AI正在思考中... -
+
@@ -1299,7 +1299,7 @@ image-margin-top = 40rpx } /* 三个点的样式 - 使用标准CSS语法,不使用嵌套 */ -.ai-loading span { +.ai-loading view { display: inline-block; width: 12rpx; height: 12rpx; @@ -1311,15 +1311,15 @@ image-margin-top = 40rpx } /* 为每个点设置不同的动画延迟 */ -.ai-loading span:nth-child(1) { +.ai-loading view:nth-child(1) { animation-delay: -0.32s; } -.ai-loading span:nth-child(2) { +.ai-loading view:nth-child(2) { animation-delay: -0.16s; } -.ai-loading span:nth-child(3) { +.ai-loading view:nth-child(3) { animation-delay: 0s; }