修复:管理员进房烟花无声问题(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'
|
* @param {string} type 'lightning' | 'fireworks' | 'rain' | 'snow'
|
||||||
*/
|
*/
|
||||||
@@ -404,22 +406,42 @@ const EffectSounds = (() => {
|
|||||||
// 用户开启禁音则跳过
|
// 用户开启禁音则跳过
|
||||||
if (localStorage.getItem("chat_sound_muted") === "1") return;
|
if (localStorage.getItem("chat_sound_muted") === "1") return;
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (type) {
|
const ctx = _getCtx();
|
||||||
case "lightning":
|
|
||||||
_stopFn = _startLightning();
|
const _doPlay = () => {
|
||||||
break;
|
try {
|
||||||
case "fireworks":
|
switch (type) {
|
||||||
_stopFn = _startFireworks();
|
case "lightning":
|
||||||
break;
|
_stopFn = _startLightning();
|
||||||
case "rain":
|
break;
|
||||||
_stopFn = _startRain();
|
case "fireworks":
|
||||||
break;
|
_stopFn = _startFireworks();
|
||||||
case "snow":
|
break;
|
||||||
_stopFn = _startSnow();
|
case "rain":
|
||||||
break;
|
_stopFn = _startRain();
|
||||||
default:
|
break;
|
||||||
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) {
|
} catch (e) {
|
||||||
console.warn(
|
console.warn(
|
||||||
@@ -442,54 +464,59 @@ const EffectSounds = (() => {
|
|||||||
if (localStorage.getItem("chat_sound_muted") === "1") return;
|
if (localStorage.getItem("chat_sound_muted") === "1") return;
|
||||||
try {
|
try {
|
||||||
const ctx = _getCtx();
|
const ctx = _getCtx();
|
||||||
const master = ctx.createGain();
|
|
||||||
master.gain.value = 0.45;
|
|
||||||
master.connect(ctx.destination);
|
|
||||||
|
|
||||||
/**
|
const _doDing = () => {
|
||||||
* 播放单音:快速冲击 + 铃铛衰减包络
|
|
||||||
*
|
|
||||||
* @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(() => {
|
|
||||||
try {
|
try {
|
||||||
master.disconnect();
|
const master = ctx.createGain();
|
||||||
} catch (_) {}
|
master.gain.value = 0.45;
|
||||||
}, 700);
|
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) {
|
} catch (e) {
|
||||||
console.warn("[EffectSounds.ding] 通知音播放失败:", e);
|
console.warn("[EffectSounds.ding] 通知音播放失败:", e);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user