261 lines
5.8 KiB
JavaScript
261 lines
5.8 KiB
JavaScript
|
|
// 音频管理工具 - 环境白噪音与音效管理
|
||
|
|
|
||
|
|
// 背景音频管理器(用于环境白噪音)
|
||
|
|
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];
|
||
|
|
});
|
||
|
|
};
|