/** * 统一音频管理器 * 实现单实例音频管理,避免多个音频同时播放造成的冲突 */ class AudioManager { constructor() { // 当前音频实例 this.currentAudio = null; // 音频类型:'sentence' | 'word' this.currentAudioType = null; // 音频状态 this.isPlaying = false; // 复用的音频实例池 this.audioInstance = null; // 事件监听器 this.listeners = { play: [], pause: [], ended: [], error: [], timeupdate: [], canplay: [] }; // 播放速度支持检测 this.playbackRateSupported = true; // 统一音频配置 this.globalPlaybackRate = 1.0; // 全局播放速度 this.globalVoiceId = ''; // 全局音色ID this.speedOptions = [0.5, 0.8, 1.0, 1.25, 1.5, 2.0]; // 支持的播放速度选项 } /** * 创建HTML5 Audio实例并包装为uni-app兼容接口 */ createHTML5Audio() { const audio = new Audio(); // 包装为uni-app兼容的接口 const wrappedAudio = { // 原生HTML5 Audio实例 _nativeAudio: audio, // 基本属性 get src() { return audio.src; }, set src(value) { audio.src = value; }, get duration() { return audio.duration || 0; }, get currentTime() { return audio.currentTime || 0; }, get paused() { return audio.paused; }, // 支持倍速的关键属性 get playbackRate() { return audio.playbackRate; }, set playbackRate(value) { try { audio.playbackRate = value; console.log(`🎵 HTML5 Audio倍速设置成功: ${value}x`); } catch (error) { console.error('❌ HTML5 Audio倍速设置失败:', error); } }, // 基本方法 play() { return audio.play().catch(error => { console.error('HTML5 Audio播放失败:', error); }); }, pause() { audio.pause(); }, stop() { audio.pause(); audio.currentTime = 0; }, seek(time) { audio.currentTime = time; }, destroy() { audio.pause(); audio.src = ''; audio.load(); }, // 事件绑定方法 onCanplay(callback) { audio.addEventListener('canplay', callback); }, onPlay(callback) { audio.addEventListener('play', callback); }, onPause(callback) { audio.addEventListener('pause', callback); }, onEnded(callback) { audio.addEventListener('ended', callback); }, onTimeUpdate(callback) { audio.addEventListener('timeupdate', callback); }, onError(callback) { // 包装错误事件,过滤掉非关键错误 const wrappedCallback = (error) => { // 只在有src且音频正在播放时才传递错误事件 if (audio.src && audio.src.trim() !== '' && !audio.paused) { callback(error); } else { console.log('🔇 HTML5 Audio错误(已忽略):', { hasSrc: !!audio.src, paused: audio.paused, errorType: error.type || 'unknown' }); } }; audio.addEventListener('error', wrappedCallback); }, // 移除事件监听 offCanplay(callback) { audio.removeEventListener('canplay', callback); }, offPlay(callback) { audio.removeEventListener('play', callback); }, offPause(callback) { audio.removeEventListener('pause', callback); }, offEnded(callback) { audio.removeEventListener('ended', callback); }, offTimeUpdate(callback) { audio.removeEventListener('timeupdate', callback); }, offError(callback) { audio.removeEventListener('error', callback); } }; return wrappedAudio; } /** * 创建音频实例 * 优先使用HTML5 Audio(支持倍速),降级到uni.createInnerAudioContext * 复用已存在的音频实例以提高性能 */ createAudioInstance() { // 如果已有音频实例,直接复用 if (this.audioInstance) { console.log('🔄 复用现有音频实例'); return this.audioInstance; } // 在H5环境下优先使用HTML5 Audio // #ifdef H5 try { this.audioInstance = this.createHTML5Audio(); console.log('🎵 创建新的HTML5 Audio实例'); } catch (error) { console.warn('⚠️ HTML5 Audio创建失败,降级到uni音频:', error); audio = uni.createInnerAudioContext(); this.playbackRateSupported = false; } // #endif // 在非H5环境下使用uni音频 // #ifndef H5 this.audioInstance = uni.createInnerAudioContext(); this.playbackRateSupported = false; console.log('🎵 创建新的uni音频实例'); // #endif return this.audioInstance; } /** * 停止当前音频 */ stopCurrentAudio() { if (this.currentAudio) { console.log(`🛑 停止当前音频 (类型: ${this.currentAudioType})`); try { this.currentAudio.pause(); // 不销毁音频实例,保留以供复用 // this.currentAudio.destroy(); } catch (error) { console.error('⚠️ 停止音频时出错:', error); } this.currentAudio = null; this.currentAudioType = null; this.isPlaying = false; // 触发暂停事件 this.emit('pause'); } } /** * 播放音频 * @param {string} audioUrl - 音频URL * @param {string} audioType - 音频类型 ('sentence' | 'word') * @param {Object} options - 播放选项 */ async playAudio(audioUrl, audioType = 'word', options = {}) { try { console.log(`🎵 开始播放${audioType}音频:`, audioUrl); // 停止当前播放的音频 this.stopCurrentAudio(); // 创建新的音频实例 const audio = this.createAudioInstance(); audio.src = audioUrl; // 设置播放选项,优先使用全局播放速度 const playbackRate = options.playbackRate || this.globalPlaybackRate; if (playbackRate && this.playbackRateSupported) { audio.playbackRate = playbackRate; console.log(`🎵 设置音频播放速度: ${playbackRate}x`); } // 绑定事件监听器 this.bindAudioEvents(audio, audioType); // 保存当前音频实例 this.currentAudio = audio; this.currentAudioType = audioType; // 延迟播放,确保音频实例完全准备好 setTimeout(() => { if (this.currentAudio === audio) { try { audio.play(); console.log(`✅ ${audioType}音频开始播放`); } catch (playError) { console.error('❌ 播放命令失败:', playError); this.emit('error', playError); } } }, 100); return audio; } catch (error) { console.error('❌ 播放音频异常:', error); this.emit('error', error); throw error; } } /** * 绑定音频事件监听器 */ bindAudioEvents(audio, audioType) { // 播放开始 audio.onPlay(() => { console.log(`🎵 ${audioType}音频播放开始`); this.isPlaying = true; this.emit('play', { audioType, audio }); }); // 播放暂停 audio.onPause(() => { console.log(`⏸️ ${audioType}音频播放暂停`); this.isPlaying = false; this.emit('pause', { audioType, audio }); }); // 播放结束 audio.onEnded(() => { console.log(`🏁 ${audioType}音频播放结束`); this.isPlaying = false; // 不销毁音频实例,保留以供复用 // 只清理当前播放状态 if (this.currentAudio === audio) { this.currentAudio = null; this.currentAudioType = null; } this.emit('ended', { audioType, audio }); }); // 播放错误 audio.onError((error) => { console.error(`❌ ${audioType}音频播放失败:`, error); this.isPlaying = false; // 发生错误时才销毁音频实例 try { audio.destroy(); } catch (destroyError) { console.error('⚠️ 销毁音频实例时出错:', destroyError); } if (this.currentAudio === audio) { this.currentAudio = null; this.currentAudioType = null; } // 清理复用的音频实例引用 if (this.audioInstance === audio) { this.audioInstance = null; } this.emit('error', { error, audioType, audio }); }); // 时间更新 audio.onTimeUpdate(() => { this.emit('timeupdate', { currentTime: audio.currentTime, duration: audio.duration, audioType, audio }); }); // 可以播放 audio.onCanplay(() => { this.emit('canplay', { audioType, audio }); }); } /** * 暂停当前音频 */ pause() { if (this.currentAudio && this.isPlaying) { this.currentAudio.pause(); } } /** * 恢复播放 */ resume() { if (this.currentAudio && !this.isPlaying) { this.currentAudio.play(); } } /** * 设置播放速度 */ setPlaybackRate(rate) { if (this.currentAudio && this.playbackRateSupported) { this.currentAudio.playbackRate = rate; return true; } return false; } /** * 设置全局播放速度 * @param {number} rate - 播放速度 (0.5 - 2.0) */ setGlobalPlaybackRate(rate) { if (rate < 0.5 || rate > 2.0) { console.warn('⚠️ 播放速度超出支持范围 (0.5-2.0):', rate); return false; } this.globalPlaybackRate = rate; console.log(`🎵 设置全局播放速度: ${rate}x`); // 如果当前有音频在播放,立即应用新的播放速度 if (this.currentAudio && this.playbackRateSupported) { try { this.currentAudio.playbackRate = rate; console.log(`✅ 当前音频播放速度已更新: ${rate}x`); } catch (error) { console.error('❌ 更新当前音频播放速度失败:', error); } } return true; } /** * 获取全局播放速度 */ getGlobalPlaybackRate() { return this.globalPlaybackRate; } /** * 设置全局音色ID * @param {string|number} voiceId - 音色ID */ setGlobalVoiceId(voiceId) { this.globalVoiceId = String(voiceId); console.log(`🎵 设置全局音色ID: ${this.globalVoiceId}`); } /** * 获取全局音色ID */ getGlobalVoiceId() { return this.globalVoiceId; } /** * 获取支持的播放速度选项 */ getSpeedOptions() { return [...this.speedOptions]; } /** * 切换到下一个播放速度 */ togglePlaybackRate() { const currentIndex = this.speedOptions.indexOf(this.globalPlaybackRate); const nextIndex = (currentIndex + 1) % this.speedOptions.length; const nextRate = this.speedOptions[nextIndex]; this.setGlobalPlaybackRate(nextRate); return nextRate; } /** * 获取当前播放状态 */ getPlaybackState() { return { isPlaying: this.isPlaying, currentAudioType: this.currentAudioType, hasAudio: !!this.currentAudio, playbackRateSupported: this.playbackRateSupported, currentTime: this.currentAudio ? this.currentAudio.currentTime : 0, duration: this.currentAudio ? this.currentAudio.duration : 0 }; } /** * 添加事件监听器 */ on(event, callback) { if (this.listeners[event]) { // 检查是否已经绑定过相同的回调函数,避免重复绑定 if (this.listeners[event].indexOf(callback) === -1) { this.listeners[event].push(callback); console.log(`🎵 添加事件监听器: ${event}, 当前监听器数量: ${this.listeners[event].length}`); } else { console.warn(`⚠️ 事件监听器已存在,跳过重复绑定: ${event}`); } } } /** * 移除事件监听器 */ off(event, callback) { if (this.listeners[event]) { const index = this.listeners[event].indexOf(callback); if (index > -1) { this.listeners[event].splice(index, 1); } } } /** * 触发事件 */ emit(event, data) { if (this.listeners[event]) { this.listeners[event].forEach(callback => { try { callback(data); } catch (error) { console.error(`事件监听器执行错误 (${event}):`, error); } }); } } /** * 销毁音频管理器 */ destroy() { this.stopCurrentAudio(); // 销毁复用的音频实例 // if (this.audioInstance) { // try { // this.audioInstance.destroy(); // } catch (error) { // console.error('⚠️ 销毁音频实例时出错:', error); // } // this.audioInstance = null; // } this.listeners = { play: [], pause: [], ended: [], error: [], timeupdate: [], canplay: [] }; } } // 创建全局单例 const audioManager = new AudioManager(); export default audioManager;