v26110446 预览:专属福利不跟后台开关一直显示;「宝宝出生了」改成和我的收藏同款列表行;踢腿音效改到手机喇叭能放的频段+绕开 iPhone 静音键+每次触摸恢复声音

This commit is contained in:
deploy
2026-11-05 03:43:54 +00:00
parent c441dae002
commit 9b18cec9b5
28 changed files with 147 additions and 120 deletions

View File

@@ -124,28 +124,55 @@
· 浏览器规定用户碰过页面后才能出声 → 第一次点 / 摸屏幕时才建 AudioContext,之前的踢腿不响。
· 页面在后台 / 首页不在前台时本来就不动,也就不响;系统「减少动态效果」整个文件不跑。
· URL 带 kicksound=0 关掉(调试用)。 */
let ac = null;
/* 11-05 用户:「音效我打开了,但是没有声音」→ 三处原因一起修:
① 原来是 60~150Hz 的低音,手机外放喇叭基本放不出 → 改到 180~420Hz 的「咚」+ 一点低通噪声当「闷擦」,音量略加;
② iPhone 静音键开着时 WebAudio 会被静音 → 有 navigator.audioSession 就设成 playback,没有就在手势里放一段极短的静音 <audio> 把会话切到「播放」类;
③ 原来只在第一次触摸建一次 AudioContext,没起来或切后台被挂起后就再也不响 → 每次触摸都检查,挂起了就 resume。 */
let ac = null, nb = null, primed = false;
const soundOff = /[?&#]kicksound=0/.test(location.href);
function silentWav() { // 0.05 秒静音 wav(8kHz 单声道 8bit)
const n = 400, buf = new ArrayBuffer(44 + n), v = new DataView(buf), w = (o, str) => { for (let i = 0; i < str.length; i++) v.setUint8(o + i, str.charCodeAt(i)); };
w(0, 'RIFF'); v.setUint32(4, 36 + n, true); w(8, 'WAVEfmt '); v.setUint32(16, 16, true); v.setUint16(20, 1, true); v.setUint16(22, 1, true);
v.setUint32(24, 8000, true); v.setUint32(28, 8000, true); v.setUint16(32, 1, true); v.setUint16(34, 8, true); w(36, 'data'); v.setUint32(40, n, true);
for (let i = 0; i < n; i++) v.setUint8(44 + i, 128);
return URL.createObjectURL(new Blob([buf], { type: 'audio/wav' }));
}
const unlock = () => {
if (ac || soundOff) return;
try { const A = window.AudioContext || window.webkitAudioContext; if (A) { ac = new A(); if (ac.state === 'suspended') ac.resume(); } } catch (e) { ac = null; }
if (soundOff) return;
try {
if (!primed) {
primed = true;
if (navigator.audioSession) navigator.audioSession.type = 'playback';
else { const au = new Audio(silentWav()); au.setAttribute('playsinline', ''); au.volume = 0.01; const pr = au.play(); if (pr && pr.catch) pr.catch(() => {}); }
}
if (!ac) { const A = window.AudioContext || window.webkitAudioContext; if (A) ac = new A(); }
if (ac && ac.state !== 'running') ac.resume();
if (ac && !nb) { // 0.25 秒白噪声,做「闷擦」用
nb = ac.createBuffer(1, Math.floor(ac.sampleRate * .25), ac.sampleRate);
const d = nb.getChannelData(0); for (let i = 0; i < d.length; i++) d[i] = Math.random() * 2 - 1;
}
} catch (e) {}
};
['touchstart', 'pointerdown', 'click'].forEach(t => addEventListener(t, unlock, { passive: true, once: false, capture: true }));
['touchstart', 'touchend', 'pointerdown', 'click'].forEach(t => addEventListener(t, unlock, { passive: true, capture: true }));
function kickSound(delay) {
if (!ac || ac.state !== 'running') return;
try { if (localStorage.getItem('mb_kick_snd') === '0') return; } catch (e) {} // 11-05 「我的 → 隐私与权限 → 宝宝踢腿音效」关了就不响
const t = ac.currentTime + delay / 1000;
// 闷响:正弦 150→60Hz 快速下滑,低通滤掉高频,像隔着肚皮
const o = ac.createOscillator(), g = ac.createGain(), lp = ac.createBiquadFilter();
o.type = 'sine'; o.frequency.setValueAtTime(150, t); o.frequency.exponentialRampToValueAtTime(60, t + .16);
lp.type = 'lowpass'; lp.frequency.value = 420;
g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(0.16, t + .012); g.gain.exponentialRampToValueAtTime(0.0001, t + .2);
o.connect(lp); lp.connect(g); g.connect(ac.destination); o.start(t); o.stop(t + .22);
// 小气泡:很轻的上滑「啵」,晚一点点
const b = ac.createOscillator(), bg = ac.createGain(), t2 = t + .05;
b.type = 'sine'; b.frequency.setValueAtTime(520, t2); b.frequency.exponentialRampToValueAtTime(900, t2 + .07);
bg.gain.setValueAtTime(0.0001, t2); bg.gain.exponentialRampToValueAtTime(0.025, t2 + .01); bg.gain.exponentialRampToValueAtTime(0.0001, t2 + .09);
b.connect(bg); bg.connect(ac.destination); b.start(t2); b.stop(t2 + .1);
try { if (localStorage.getItem('mb_kick_snd') === '0') return; } catch (e) {} // 「我的 → 隐私与权限 → 宝宝踢腿音效」关了就不响
const t = ac.currentTime + delay / 1000, out = ac.createGain();
out.gain.value = 0.9; out.connect(ac.destination);
// 咚:正弦 420→180Hz 快速下滑(手机喇叭放得出来),带一点二次谐波让它更「实」
[[1, .32], [2, .08]].forEach(([m, v]) => {
const o = ac.createOscillator(), g = ac.createGain();
o.type = 'sine'; o.frequency.setValueAtTime(420 * m, t); o.frequency.exponentialRampToValueAtTime(180 * m, t + .14);
g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(v, t + .008); g.gain.exponentialRampToValueAtTime(0.0001, t + .18);
o.connect(g); g.connect(out); o.start(t); o.stop(t + .2);
});
// 闷擦:低通噪声一小下,像隔着肚皮蹬了一脚
if (nb) {
const n = ac.createBufferSource(), lp = ac.createBiquadFilter(), ng = ac.createGain();
n.buffer = nb; lp.type = 'lowpass'; lp.frequency.value = 900;
ng.gain.setValueAtTime(0.0001, t); ng.gain.exponentialRampToValueAtTime(0.12, t + .006); ng.gain.exponentialRampToValueAtTime(0.0001, t + .09);
n.connect(lp); lp.connect(ng); ng.connect(out); n.start(t); n.stop(t + .1);
}
}
// 安排下一次「动一动」