修复:管理员进房烟花无声问题(AudioContext suspended)
根本原因:管理员进房特效在 800ms 后自动触发, 此时用户尚未与新页面交互,浏览器的 AudioContext 处于 suspended 状态,之前代码同步调用 resume() 但未 await 其 Promise,导致音频节点创建后无法出声。 修复方式: - play() 和 ding() 均改为先检查 ctx.state - 若为 suspended,用 ctx.resume().then(...) 链式执行 - resolver 成功后真正创建音频节点并播放 - 若浏览器拒绝 resume(无用户手势),catch 静默处理 此修复使所有自动触发的音效(进房烟花、任命公告等) 在 AudioContext 未激活时也能正确播放。
This commit is contained in:
@@ -397,6 +397,8 @@ const EffectSounds = (() => {
|
||||
/**
|
||||
* 播放指定特效对应的音效(自动停止上一个)。
|
||||
* 静音状态下直接跳过,不做任何音频操作。
|
||||
* 当 AudioContext 处于 suspended 状态时,先 resume() 再播放,
|
||||
* 解决页面无用户手势时的自动静音问题(如管理员进房自动烟花)。
|
||||
*
|
||||
* @param {string} type 'lightning' | 'fireworks' | 'rain' | 'snow'
|
||||
*/
|
||||
@@ -404,22 +406,42 @@ const EffectSounds = (() => {
|
||||
// 用户开启禁音则跳过
|
||||
if (localStorage.getItem("chat_sound_muted") === "1") return;
|
||||
stop();
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case "lightning":
|
||||
_stopFn = _startLightning();
|
||||
break;
|
||||
case "fireworks":
|
||||
_stopFn = _startFireworks();
|
||||
break;
|
||||
case "rain":
|
||||
_stopFn = _startRain();
|
||||
break;
|
||||
case "snow":
|
||||
_stopFn = _startSnow();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
const ctx = _getCtx();
|
||||
|
||||
const _doPlay = () => {
|
||||
try {
|
||||
switch (type) {
|
||||
case "lightning":
|
||||
_stopFn = _startLightning();
|
||||
break;
|
||||
case "fireworks":
|
||||
_stopFn = _startFireworks();
|
||||
break;
|
||||
case "rain":
|
||||
_stopFn = _startRain();
|
||||
break;
|
||||
case "snow":
|
||||
_stopFn = _startSnow();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("[EffectSounds] 音效内部错误:", e);
|
||||
}
|
||||
};
|
||||
|
||||
if (ctx.state === "suspended") {
|
||||
// AudioContext 尚未被用户手势激活,先 resume 再播放
|
||||
ctx.resume()
|
||||
.then(_doPlay)
|
||||
.catch(() => {
|
||||
// 浏览器拒绝 resume(无用户手势),静默处理
|
||||
});
|
||||
} else {
|
||||
_doPlay();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
@@ -442,54 +464,59 @@ const EffectSounds = (() => {
|
||||
if (localStorage.getItem("chat_sound_muted") === "1") return;
|
||||
try {
|
||||
const ctx = _getCtx();
|
||||
const master = ctx.createGain();
|
||||
master.gain.value = 0.45;
|
||||
master.connect(ctx.destination);
|
||||
|
||||
/**
|
||||
* 播放单音:快速冲击 + 铃铛衰减包络
|
||||
*
|
||||
* @param {number} freq 频率(Hz)
|
||||
* @param {number} t0 相对于 ctx.currentTime 的时刻
|
||||
* @param {number} decay 衰减时长(秒)
|
||||
*/
|
||||
function _tone(freq, t0, decay) {
|
||||
const osc = ctx.createOscillator();
|
||||
osc.type = "sine";
|
||||
osc.frequency.value = freq;
|
||||
|
||||
// 轻柔泛音(×2.76 铃铛泛音比,音量 10%)
|
||||
const osc2 = ctx.createOscillator();
|
||||
osc2.type = "sine";
|
||||
osc2.frequency.value = freq * 2.76;
|
||||
const g2 = ctx.createGain();
|
||||
g2.gain.value = 0.1;
|
||||
|
||||
const env = ctx.createGain();
|
||||
env.gain.setValueAtTime(1.0, t0);
|
||||
env.gain.exponentialRampToValueAtTime(0.001, t0 + decay);
|
||||
|
||||
osc.connect(env);
|
||||
osc2.connect(g2);
|
||||
g2.connect(env);
|
||||
env.connect(master);
|
||||
|
||||
osc.start(t0);
|
||||
osc.stop(t0 + decay + 0.05);
|
||||
osc2.start(t0);
|
||||
osc2.stop(t0 + decay + 0.05);
|
||||
}
|
||||
|
||||
const now = ctx.currentTime;
|
||||
_tone(880, now, 0.35); // 叮:A5,先响
|
||||
_tone(659, now + 0.11, 0.4); // 咚:E5,稍低稍长
|
||||
|
||||
// 0.7 秒后释放主节点
|
||||
setTimeout(() => {
|
||||
const _doDing = () => {
|
||||
try {
|
||||
master.disconnect();
|
||||
} catch (_) {}
|
||||
}, 700);
|
||||
const master = ctx.createGain();
|
||||
master.gain.value = 0.45;
|
||||
master.connect(ctx.destination);
|
||||
|
||||
function _tone(freq, t0, decay) {
|
||||
const osc = ctx.createOscillator();
|
||||
osc.type = "sine";
|
||||
osc.frequency.value = freq;
|
||||
const osc2 = ctx.createOscillator();
|
||||
osc2.type = "sine";
|
||||
osc2.frequency.value = freq * 2.76;
|
||||
const g2 = ctx.createGain();
|
||||
g2.gain.value = 0.1;
|
||||
const env = ctx.createGain();
|
||||
env.gain.setValueAtTime(1.0, t0);
|
||||
env.gain.exponentialRampToValueAtTime(
|
||||
0.001,
|
||||
t0 + decay,
|
||||
);
|
||||
osc.connect(env);
|
||||
osc2.connect(g2);
|
||||
g2.connect(env);
|
||||
env.connect(master);
|
||||
osc.start(t0);
|
||||
osc.stop(t0 + decay + 0.05);
|
||||
osc2.start(t0);
|
||||
osc2.stop(t0 + decay + 0.05);
|
||||
}
|
||||
|
||||
const now = ctx.currentTime;
|
||||
_tone(880, now, 0.35); // 叮:A5
|
||||
_tone(659, now + 0.11, 0.4); // 咚:E5
|
||||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
master.disconnect();
|
||||
} catch (_) {}
|
||||
}, 700);
|
||||
} catch (e) {
|
||||
console.warn("[EffectSounds.ding] 通知音内部错误:", e);
|
||||
}
|
||||
};
|
||||
|
||||
if (ctx.state === "suspended") {
|
||||
ctx.resume()
|
||||
.then(_doDing)
|
||||
.catch(() => {});
|
||||
} else {
|
||||
_doDing();
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("[EffectSounds.ding] 通知音播放失败:", e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user