// 音频管理工具 - 环境白噪音与音效管理 // 背景音频管理器(用于环境白噪音) let bgmManager = null; // 音效上下文缓存 const soundEffects = {}; // 当前环境音类型 let currentAmbienceType = 'silent'; // 音量渐变定时器 let fadeTimer = null; /** * 初始化背景音频管理器 */ export const initBGM = () => { if (!bgmManager) { bgmManager = wx.getBackgroundAudioManager(); bgmManager.title = '见素-环境音'; bgmManager.epname = '环境白噪音'; bgmManager.singer = '见素'; } return bgmManager; }; /** * 播放环境白噪音 * @param {string} type - 环境音类型: 'silent' | 'wind' | 'rain' | 'guqin' | 'white' | 'forest' | 'stream' * @param {boolean} fade - 是否淡入 */ export const playAmbience = (type = 'wind', fade = true) => { const bgm = initBGM(); // 如果相同类型且正在播放,不重复操作 if (currentAmbienceType === type && bgm.currentTime > 0) { return; } currentAmbienceType = type; // 静音模式 if (type === 'silent') { fadeOutAndStop(); return; } // 音频资源映射(使用本地音频) const audioMap = { wind: '/assets/audios/wind.mp3', // 风铃 rain: '/assets/audios/rain.mp3', // 雨声 guqin: '/assets/audios/guqin.mp3', // 古琴 white: '/assets/audios/white.mp3', // 白噪音 forest: '/assets/audios/forest.mp3', // 森林 stream: '/assets/audios/stream.mp3' // 溪流 }; // 音频类型名称映射 const typeNameMap = { wind: '风铃', rain: '雨落', guqin: '古琴', white: '白噪音', forest: '森林', stream: '溪流' }; const src = audioMap[type]; if (!src) return; // 设置音频属性 bgm.title = `见素 - ${typeNameMap[type] || '环境音'}`; bgm.singer = '环境音'; bgm.coverImgUrl = ''; // 监听错误事件 bgm.onError((err) => { console.error('音频播放错误:', err); wx.showToast({ title: '音频加载失败', icon: 'none' }); }); // 淡入效果 if (fade) { bgm.volume = 0; bgm.src = src; bgm.play(); fadeIn(0.3, 1000); // 1秒内淡入到30%音量 } else { bgm.volume = 0.3; bgm.src = src; bgm.play(); } console.log(`播放环境音: ${type}`); }; /** * 淡入音量 * @param {number} targetVolume - 目标音量 0-1 * @param {number} duration - 淡入时长 ms */ const fadeIn = (targetVolume = 0.3, duration = 1000) => { if (!bgmManager) return; clearInterval(fadeTimer); const step = targetVolume / (duration / 50); // 每50ms调整一次 fadeTimer = setInterval(() => { if (bgmManager.volume < targetVolume) { bgmManager.volume = Math.min(bgmManager.volume + step, targetVolume); } else { clearInterval(fadeTimer); } }, 50); }; /** * 淡出并停止 * @param {number} duration - 淡出时长 ms */ export const fadeOutAndStop = (duration = 500) => { if (!bgmManager) return; clearInterval(fadeTimer); const startVolume = bgmManager.volume; const step = startVolume / (duration / 50); fadeTimer = setInterval(() => { if (bgmManager.volume > 0.01) { bgmManager.volume = Math.max(bgmManager.volume - step, 0); } else { clearInterval(fadeTimer); bgmManager.stop(); currentAmbienceType = 'silent'; } }, 50); }; /** * 播放音效 * @param {string} type - 音效类型 * @param {object} options - 配置选项 */ export const playSoundEffect = (type, options = {}) => { const { volume = 1, loop = false } = options; // 音效资源映射 const effectMap = { flip: '/assets/audios/paper.mp3', // 翻页/纸张摩擦声 success: '/assets/audios/success.mp3', // 成功 inkDrop: '/assets/audios/inkdrop.mp3', // 水滴/入墨声 swipe: '/assets/audios/swipe.mp3', // 滑动 tap: '/assets/audios/tap.mp3', // 点击 breathe: '/assets/audios/breathe.mp3', // 呼吸 char: '/assets/audios/char.mp3' // 字显现 }; const src = effectMap[type]; if (!src) return; // 复用或创建音频上下文 if (!soundEffects[type]) { soundEffects[type] = wx.createInnerAudioContext(); } const ctx = soundEffects[type]; ctx.src = src; ctx.volume = volume; ctx.loop = loop; ctx.stop(); try { ctx.play(); } catch (err) { console.log('音效播放失败:', err); } return ctx; }; /** * 停止指定音效 * @param {string} type - 音效类型 */ export const stopSoundEffect = (type) => { if (soundEffects[type]) { soundEffects[type].stop(); } }; /** * 停止所有音效 */ export const stopAllSoundEffects = () => { Object.keys(soundEffects).forEach(type => { soundEffects[type].stop(); }); }; /** * 停止环境音 */ export const stopAmbience = () => { fadeOutAndStop(); }; /** * 获取当前环境音类型 */ export const getCurrentAmbience = () => currentAmbienceType; /** * 切换环境音 * @param {string} type - 环境音类型 */ export const toggleAmbience = (type) => { if (currentAmbienceType === type) { playAmbience('silent'); } else { playAmbience(type); } }; /** * 呼吸震动效果 - 配合名字逐字显现 * @param {number} duration - 总时长 ms * @param {number} intensity - 震动强度 light/medium/heavy */ export const breatheVibration = (duration = 2000, intensity = 'light') => { const interval = duration / 4; // 分4次震动 let count = 0; const vibrate = () => { if (count < 4) { wx.vibrateShort({ type: intensity }); count++; setTimeout(vibrate, interval); } }; vibrate(); }; /** * 清理音频资源 */ export const cleanup = () => { clearInterval(fadeTimer); if (bgmManager) { bgmManager.stop(); } Object.keys(soundEffects).forEach(type => { soundEffects[type].destroy(); delete soundEffects[type]; }); };