175 lines
7.6 KiB
JavaScript
175 lines
7.6 KiB
JavaScript
/* Justice — Sound Manager (BGM + SFX) · โหลดร่วมทุกหน้า (Main-Menu / room-lobby / play)
|
|
window.jdSound = { playBgm(scene), stopBgm(), playSfx(id[,scene]), setMusic(on), setSfx(on),
|
|
isMusicOn(), isSfxOn(), notifySpeaking(), duck(ms) }
|
|
- ประสาน UI ปุ่ม Music/SFX (profile popup) ผ่าน localStorage (jd_music_on / jd_sfx_on)
|
|
- ไม่ยุ่งกับ mic/WebRTC เลย — BGM/SFX เป็น HTMLAudio แยกจากเสียงพูด
|
|
- duck: หรี่ BGM อัตโนมัติตอนมีคนพูด (notifySpeaking) → เสียงคนไม่โดนกลบ
|
|
- volume master/music/sfx ปรับใน Admin ได้ (GET /Game/api/sound-settings) */
|
|
(function () {
|
|
'use strict';
|
|
if (window.jdSound) return;
|
|
|
|
var scriptBase = (function () {
|
|
try {
|
|
var s = (document.currentScript && document.currentScript.src) || '';
|
|
if (s) return s.replace(/\/js\/sound\.js.*$/, '');
|
|
} catch (e) { /* ignore */ }
|
|
/* fallback: หา <script src=".../js/sound.js"> */
|
|
try {
|
|
var els = document.getElementsByTagName('script');
|
|
for (var i = 0; i < els.length; i++) {
|
|
var src = els[i].src || '';
|
|
if (src.indexOf('/js/sound.js') >= 0) return src.replace(/\/js\/sound\.js.*$/, '');
|
|
}
|
|
} catch (e2) { /* ignore */ }
|
|
return '';
|
|
})();
|
|
function aurl(p) { return scriptBase + '/audio/' + p; }
|
|
|
|
var LS_MUSIC = 'jd_music_on', LS_SFX = 'jd_sfx_on';
|
|
function readBool(k, def) { try { var v = localStorage.getItem(k); return v == null ? def : (v === '1'); } catch (e) { return def; } }
|
|
function writeBool(k, v) { try { localStorage.setItem(k, v ? '1' : '0'); } catch (e) { /* ignore */ } }
|
|
|
|
var state = {
|
|
musicOn: readBool(LS_MUSIC, true),
|
|
sfxOn: readBool(LS_SFX, true),
|
|
masterVol: 1.0,
|
|
musicVol: 0.35,
|
|
sfxVol: 0.75,
|
|
curScene: null,
|
|
duckUntil: 0,
|
|
unlocked: false
|
|
};
|
|
|
|
/* ไฟล์ที่มีจริงต่อฉาก (กัน 404 — playSfx no-op ถ้าไม่มี) */
|
|
var MANIFEST = {
|
|
lobby: { bgm: 1 },
|
|
mg1: { bgm: 1, button: 1, correct: 1, endgame: 1, time: 1, wrong: 1 },
|
|
mg2: { bgm: 1, button: 1, gameover: 1, jump: 1, time: 1, winner: 1 },
|
|
mg3: { bgm: 1, button: 1, 'drop-clear': 1, 'drop-fail': 1, 'drop-hard': 1, endgame: 1, gameover: 1, time: 1, timeup: 1 },
|
|
mg4: { bgm: 1, button: 1, correct: 1, endgame: 1, gameover: 1, time: 1, timeup: 1, wrong: 1 },
|
|
mg5: { bgm: 1, button: 1, endgame: 1, gameover: 1, jump: 1, time: 1, timeup: 1 },
|
|
mg6: { bgm: 1, bomb: 1, button: 1, crash: 1, endgame: 1, gameover: 1, laser: 1, time: 1, timeup: 1 },
|
|
mg7: { bgm: 1, 'boss-elim': 1, button: 1, endgame: 1, gameover: 1, laser: 1, time: 1 }
|
|
};
|
|
var EXT = { button: 'wav' }; /* ที่เหลือ .mp3 */
|
|
function fileFor(scene, id) {
|
|
var ext = EXT[id] || 'mp3';
|
|
return scene + '/' + id + '.' + ext;
|
|
}
|
|
/* alias สำหรับ win/lose ให้เลือกไฟล์ที่มีในฉากนั้น */
|
|
function resolveSfx(scene, id) {
|
|
var m = MANIFEST[scene] || {};
|
|
if (id === 'win') { id = m.winner ? 'winner' : (m.endgame ? 'endgame' : null); }
|
|
else if (id === 'lose') { id = m.gameover ? 'gameover' : null; }
|
|
if (!id) return null;
|
|
if (m[id]) return fileFor(scene, id);
|
|
/* button ใช้ตัวกลางที่ audio root ถ้าฉากไม่มี */
|
|
if (id === 'button') return 'button.wav';
|
|
return null;
|
|
}
|
|
|
|
/* ===== BGM ===== */
|
|
var bgm = null;
|
|
function ensureBgm() {
|
|
if (!bgm) { bgm = new Audio(); bgm.loop = true; bgm.preload = 'auto'; bgm.setAttribute('playsinline', ''); }
|
|
return bgm;
|
|
}
|
|
function effBgmVol() {
|
|
var v = state.masterVol * state.musicVol;
|
|
if (Date.now() < state.duckUntil) v *= 0.22; /* หรี่ตอนมีคนพูด */
|
|
return Math.max(0, Math.min(1, v));
|
|
}
|
|
function applyBgmVol() { if (bgm) bgm.volume = state.musicOn ? effBgmVol() : 0; }
|
|
|
|
function playBgm(scene) {
|
|
if (!scene) return;
|
|
var man = MANIFEST[scene];
|
|
if (!man || !man.bgm) { state.curScene = scene; return; } /* ฉากไม่มี bgm (แต่จำ scene ไว้ให้ sfx) */
|
|
var a = ensureBgm();
|
|
var want = aurl(scene + '/bgm.mp3');
|
|
if (state.curScene !== scene || (a.src && a.src.indexOf(scene + '/bgm.mp3') < 0)) {
|
|
try { a.pause(); } catch (e) { /* ignore */ }
|
|
a.src = want;
|
|
try { a.currentTime = 0; } catch (e) { /* ignore */ }
|
|
}
|
|
state.curScene = scene;
|
|
applyBgmVol();
|
|
if (state.musicOn) { var p = a.play(); if (p && p.catch) p.catch(function () { /* รอ gesture ปลดล็อก */ }); }
|
|
}
|
|
function stopBgm() { if (bgm) { try { bgm.pause(); } catch (e) { /* ignore */ } } }
|
|
|
|
/* ===== SFX (pool กัน overlap) ===== */
|
|
var pool = [];
|
|
function grab() {
|
|
for (var i = 0; i < pool.length; i++) { if (pool[i].ended || pool[i].paused) return pool[i]; }
|
|
if (pool.length < 12) { var a = new Audio(); a.preload = 'auto'; a.setAttribute('playsinline', ''); pool.push(a); return a; }
|
|
return pool[0];
|
|
}
|
|
function playSfx(id, scene) {
|
|
if (!state.sfxOn || !id) return;
|
|
scene = scene || state.curScene || 'lobby';
|
|
var rel = resolveSfx(scene, id);
|
|
if (!rel) return;
|
|
try {
|
|
var a = grab();
|
|
a.src = aurl(rel);
|
|
a.volume = Math.max(0, Math.min(1, state.masterVol * state.sfxVol));
|
|
a.currentTime = 0;
|
|
var p = a.play(); if (p && p.catch) p.catch(function () { /* ignore */ });
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
|
|
/* ===== toggles ===== */
|
|
function setMusic(on) {
|
|
state.musicOn = !!on; writeBool(LS_MUSIC, state.musicOn);
|
|
if (!state.musicOn) { stopBgm(); }
|
|
else { applyBgmVol(); if (state.curScene) playBgm(state.curScene); }
|
|
return state.musicOn;
|
|
}
|
|
function setSfx(on) { state.sfxOn = !!on; writeBool(LS_SFX, state.sfxOn); return state.sfxOn; }
|
|
|
|
/* ===== duck (เสียงพูด) ===== */
|
|
function duck(ms) { state.duckUntil = Date.now() + (ms || 600); applyBgmVol(); }
|
|
function notifySpeaking() { duck(700); }
|
|
setInterval(applyBgmVol, 220); /* คลาย duck อัตโนมัติ */
|
|
|
|
/* ===== unlock audio ครั้งแรกที่ user gesture (autoplay policy) ===== */
|
|
function unlock() {
|
|
if (state.unlocked) return;
|
|
state.unlocked = true;
|
|
if (state.musicOn && state.curScene) playBgm(state.curScene);
|
|
}
|
|
['pointerdown', 'keydown', 'touchstart', 'click'].forEach(function (ev) {
|
|
window.addEventListener(ev, unlock, { once: false, passive: true });
|
|
});
|
|
|
|
/* ===== config จาก Admin (ไม่มีก็ใช้ default) ===== */
|
|
(function loadSettings() {
|
|
try {
|
|
fetch(scriptBase + '/api/sound-settings', { cache: 'no-store' })
|
|
.then(function (r) { return r.ok ? r.json() : null; })
|
|
.then(function (d) {
|
|
if (!d) return;
|
|
if (typeof d.masterVol === 'number') state.masterVol = Math.max(0, Math.min(1, d.masterVol));
|
|
if (typeof d.musicVol === 'number') state.musicVol = Math.max(0, Math.min(1, d.musicVol));
|
|
if (typeof d.sfxVol === 'number') state.sfxVol = Math.max(0, Math.min(1, d.sfxVol));
|
|
applyBgmVol();
|
|
}).catch(function () { /* ignore */ });
|
|
} catch (e) { /* ignore */ }
|
|
})();
|
|
|
|
window.jdSound = {
|
|
playBgm: playBgm,
|
|
stopBgm: stopBgm,
|
|
playSfx: playSfx,
|
|
setMusic: setMusic,
|
|
setSfx: setSfx,
|
|
isMusicOn: function () { return state.musicOn; },
|
|
isSfxOn: function () { return state.sfxOn; },
|
|
notifySpeaking: notifySpeaking,
|
|
duck: duck,
|
|
_state: state
|
|
};
|
|
})();
|