@@ -8,7 +8,6 @@ import {
onHide ,
onUnload
} from '@dcloudio/uni-app'
import WavDecoder from '@/lib/wav-decoder@1.3.0.js'
export function useTTSPlayer ( httpUrl ) {
const isSpeaking = ref ( false )
@@ -23,22 +22,79 @@ export function useTTSPlayer(httpUrl) {
// #ifdef MP-WEIXIN
const audioContext = null // 微信小程序不支持 AudioContext
let innerAudioContext = null // 微信小程序音频上下文
let backgroundAudioManager = null // 微信小程序背景音频管理器
// #endif
let currentAudioBuffer = null
let currentSource = null
let playTimeOffset = 0
const speak = async ( text ) => {
if ( ! audioContext ) {
console . warn ( '⚠️ TTS not supported in current environment' ) ;
return ;
// 初始化微信小程序音频上下文
// #ifdef MP-WEIXIN
const initAudioManager = ( ) => {
// 优先使用BackgroundAudioManager, 更适合真机环境
try {
console . log ( '<27> 微信小程序:创建背景音频管理器' )
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
} )
backgroundAudioManager . onPause ( ( ) => {
console . log ( '⏸️ 微信小程序背景音频播放暂停' )
isPaused . value = true
} )
backgroundAudioManager . onStop ( ( ) => {
console . log ( '⏹️ 微信小程序背景音频播放停止' )
isSpeaking . value = false
isComplete . value = true
} )
backgroundAudioManager . onEnded ( ( ) => {
console . log ( '🎵 微信小程序背景音频播放结束' )
isSpeaking . value = false
isComplete . value = true
} )
backgroundAudioManager . onError ( ( res ) => {
console . error ( '❌ 微信小程序背景音频播放错误:' , res . errMsg , '错误码:' , res . errCode )
isSpeaking . value = false
isComplete . value = false
} )
backgroundAudioManager . onCanplay ( ( ) => {
console . log ( '🎵 微信小程序背景音频可以播放了' )
} )
backgroundAudioManager . onWaiting ( ( ) => {
console . log ( '⏳ 微信小程序背景音频加载中...' )
} )
backgroundAudioManager . onTimeUpdate ( ( ) => {
console . log ( '⏱️ 微信小程序背景音频播放进度:' , backgroundAudioManager . currentTime )
} )
console . log ( '✅ 微信小程序背景音频管理器初始化成功' )
return true
} catch ( e ) {
console . error ( '❌ 微信小程序背景音频管理器初始化失败:' , e )
return false
}
}
// #endif
console . log ( '🎤 TTS speak function called' ) ;
console . log ( '📝 Text to synthesize:' , text ? text . substring ( 0 , 100 ) + '...' : 'No text' ) ;
console . log ( '🔗 HTTP URL:' , httpUrl ) ;
const speak = async ( text ) => {
// 停止当前播放
stop ( )
@@ -51,6 +107,68 @@ export function useTTSPlayer(httpUrl) {
const url = ` ${ httpUrl } ?text= ${ encodeURIComponent ( speechText ) } `
console . log ( '🔗 Final GET URL:' , url ) ;
// #ifdef MP-WEIXIN
// 微信小程序环境,使用背景音频管理器
const isBackgroundAudioAvailable = initAudioManager ( )
// 重置音频状态
isSpeaking . value = true
isPaused . value = false
isComplete . value = false
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 . 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 . src = url
innerAudioContext . play ( )
console . log ( '🎵 微信小程序InnerAudioContext开始播放' ) ;
}
// #endif
// #ifdef H5
// H5环境, 使用 AudioContext
if ( audioContext ) {
// 发送GET请求获取语音数据
const response = await fetch ( url )
if ( ! response . ok ) {
@@ -61,12 +179,21 @@ export function useTTSPlayer(httpUrl) {
const arrayBuffer = await response . arrayBuffer ( )
console . log ( '✅ Received audio data, size:' , arrayBuffer . byteLength + ' bytes' ) ;
// 解码音频数据
const decoded = await WavDecoder . decode ( arrayBuffer )
console . log ( '✅ Audio decoded, sampleRate:' , decoded . sampleRate , 'channels:' , decoded . channel Data. length ) ;
try {
// 直接使用 audioContext.decodeAudioData 解码,不依赖外部库
const decoded = await audioContext . decodeAudio Data ( arrayBuffer )
console . log ( '✅ Audio decoded, sampleRate:' , decoded . sampleRate , 'channels:' , decoded . numberOfChannels ) ;
// 播放音频
playDecodedAudio ( decoded )
} catch ( decodeError ) {
console . error ( '❌ AudioContext decodeAudioData failed:' , decodeError ) ;
// 降级处理:创建一个简单的音频缓冲区
createFallbackAudio ( arrayBuffer )
}
}
// #endif
} catch ( error ) {
console . error ( '❌ TTS synthesis failed:' , error ) ;
isSpeaking . value = false
@@ -74,18 +201,13 @@ export function useTTSPlayer(httpUrl) {
}
}
// #ifdef H5
const playDecodedAudio = ( decoded ) => {
if ( ! audioContext ) return ;
// 使用第一个声道的数据
const audioBuffer = audioContext . createBuffer ( 1 , decoded . channelData [ 0 ] . length , decoded . sampleRate )
audioBuffer . copyToChannel ( decoded . channelData [ 0 ] , 0 )
currentAudioBuffer = audioBuffer
// 创建音频源
currentSource = audioContext . createBufferSource ( )
currentSource . buffer = audioBuffer
currentSource . buffer = decoded
currentSource . connect ( audioContext . destination )
// 监听播放结束
@@ -100,39 +222,113 @@ export function useTTSPlayer(httpUrl) {
isSpeaking . value = true
isPaused . value = false
isComplete . value = false
console . log ( '<EFBFBD> Audio playback started' ) ;
console . log ( '🎵 Audio playback started' ) ;
}
// 降级处理:创建一个简单的音频缓冲区
const createFallbackAudio = ( arrayBuffer ) => {
console . log ( '🔄 使用降级方案创建音频' ) ;
// 创建一个简单的音频缓冲区,生成提示音
const sampleRate = 44100
const duration = 1 // 1秒
const frameCount = sampleRate * duration
const audioBuffer = audioContext . createBuffer ( 1 , frameCount , sampleRate )
const channelData = audioBuffer . getChannelData ( 0 )
// 生成一个简单的提示音(正弦波)
for ( let i = 0 ; i < frameCount ; i ++ ) {
const t = i / sampleRate
channelData [ i ] = Math . sin ( 2 * Math . PI * 440 * t ) * 0.1 // 440Hz正弦波, 音量0.1
}
playDecodedAudio ( audioBuffer )
}
// #endif
const pause = ( ) => {
if ( ! audioContext || ! isSpeaking . value || isPaused . value ) {
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 ) {
try {
innerAudioContext . pause ( )
console . log ( '⏸️ 微信小程序InnerAudioContext暂停' ) ;
return
} catch ( e ) {
console . error ( '❌ 微信小程序InnerAudioContext暂停失败:' , e ) ;
}
}
// #endif
// #ifdef H5
if ( audioContext && ! isSpeaking . value || isPaused . value ) {
console . warn ( '⚠️ Cannot pause TTS playback' ) ;
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 = ( ) => {
if ( ! audioContext || ! isSpeaking . value || ! isPaused . value ) {
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 ) {
try {
innerAudioContext . play ( )
console . log ( '▶️ 微信小程序InnerAudioContext恢复播放' ) ;
return
} catch ( e ) {
console . error ( '❌ 微信小程序InnerAudioContext恢复失败:' , e ) ;
}
}
// #endif
// #ifdef H5
if ( audioContext && ! isSpeaking . value || ! isPaused . value ) {
console . warn ( '⚠️ Cannot resume TTS playback' ) ;
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
}
const cancelAudio = ( ) => {
@@ -142,12 +338,37 @@ export function useTTSPlayer(httpUrl) {
const stop = ( ) => {
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 ( '✅ 微信小程序InnerAudioContext停止' ) ;
innerAudioContext . destroy ( )
innerAudioContext = null
} catch ( e ) {
console . error ( '❌ 微信小程序InnerAudioContext停止错误:' , e ) ;
}
}
// #endif
// #ifdef H5
if ( currentSource ) {
try {
currentSource . stop ( )
currentSource . disconnect ( )
} catch ( e ) {
console . error ( '❌ Error stopping audio source:' , e ) ;
console . error ( '❌ Error stopping H5 audio source:' , e ) ;
}
currentSource = null
}
@@ -156,9 +377,10 @@ 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
isSpeaking . value = false
isPaused . value = false