244 lines
9.0 KiB
JavaScript
244 lines
9.0 KiB
JavaScript
/* ==========================================
|
|
2048 AUDIO - SOUND MANAGEMENT SYSTEM
|
|
==========================================
|
|
fitur:
|
|
1. 3 Audio objects (bg, pop, merge)
|
|
2. Volume control terpisah untuk setiap audio
|
|
3. Icon dinamis sesuai level volume
|
|
4. Panel volume dengan inputLock integration
|
|
========================================== */
|
|
|
|
/* ==========================================
|
|
AUDIO OBJECTS
|
|
========================================== */
|
|
const audio = {
|
|
bg: new Audio("Background_Music.mp3"), // Background music
|
|
pop: new Audio("Pop.mp3"), // Sound saat tile spawn
|
|
merge: new Audio("Merge.mp3") // Sound saat tile merge
|
|
};
|
|
|
|
audio.bg.loop = true; // Background music loop terus
|
|
|
|
/* ==========================================
|
|
UPDATE VOLUME DARI STATE & SLIDERS
|
|
========================================== */
|
|
function updateAudioVolumes() {
|
|
// Formula: volume = enabled ? (slider / 100) : 0
|
|
audio.bg.volume = soundState.bg ? (volumeState.music / 100) : 0;
|
|
audio.pop.volume = soundState.pop ? (volumeState.pop / 100) : 0;
|
|
audio.merge.volume = soundState.merge ? (volumeState.merge / 100) : 0;
|
|
}
|
|
|
|
/* ==========================================
|
|
PLAY BACKGROUND MUSIC (dengan unlock)
|
|
========================================== */
|
|
function tryPlayBg() {
|
|
if (!soundState.bg || volumeState.music === 0) return;
|
|
|
|
// Coba play, kalau di-block browser (autoplay policy)
|
|
audio.bg.play().catch(() => {
|
|
// Setup unlock: tunggu user interaction
|
|
const unlock = () => {
|
|
if (soundState.bg && volumeState.music > 0) audio.bg.play().catch(()=>{});
|
|
window.removeEventListener("keydown", unlock);
|
|
window.removeEventListener("click", unlock);
|
|
};
|
|
window.addEventListener("keydown", unlock, { once: true });
|
|
window.addEventListener("click", unlock, { once: true });
|
|
});
|
|
}
|
|
|
|
/* ==========================================
|
|
PLAY SOUND (dengan mute check)
|
|
========================================== */
|
|
function playSound(soundObj) {
|
|
try {
|
|
// Guard: cek mute state sebelum play
|
|
if (soundObj === audio.pop && (!soundState.pop || volumeState.pop === 0)) return;
|
|
if (soundObj === audio.merge && (!soundState.merge || volumeState.merge === 0)) return;
|
|
if (soundObj === audio.bg && (!soundState.bg || volumeState.music === 0)) return;
|
|
|
|
soundObj.currentTime = 0; // Restart dari awal
|
|
soundObj.play().catch(() => {}); // Play with error handling
|
|
} catch (e) {}
|
|
}
|
|
|
|
/* ==========================================
|
|
INIT VOLUME SLIDERS - Setup Event Listeners
|
|
========================================== */
|
|
function initVolumeControl() {
|
|
updateAudioVolumes();
|
|
|
|
const musicSlider = document.getElementById('vol-music');
|
|
const popSlider = document.getElementById('vol-pop');
|
|
const mergeSlider = document.getElementById('vol-merge');
|
|
|
|
/* --- MUSIC SLIDER --- */
|
|
if (musicSlider) {
|
|
// Load nilai dari localStorage
|
|
musicSlider.value = volumeState.music;
|
|
updateSliderFill(musicSlider, volumeState.music);
|
|
document.getElementById('vol-music-display').textContent = volumeState.music + '%';
|
|
|
|
// Event listener saat slider digeser
|
|
musicSlider.addEventListener('input', (e) => {
|
|
const val = parseInt(e.target.value);
|
|
volumeState.music = val; // Update state
|
|
audio.bg.volume = val / 100; // Set volume langsung
|
|
localStorage.setItem('vol_music', val); // Save ke browser
|
|
document.getElementById('vol-music-display').textContent = val + '%';
|
|
updateSliderFill(e.target, val); // Update visual fill
|
|
updateMainSoundIcon(); // Update icon
|
|
|
|
// Auto play/pause background music
|
|
if (val > 0 && audio.bg.paused && soundState.bg) {
|
|
tryPlayBg(); // Play kalau volume > 0
|
|
} else if (val === 0) {
|
|
audio.bg.pause(); // Pause kalau volume = 0 (muted)
|
|
}
|
|
});
|
|
}
|
|
|
|
/* --- POP SLIDER (sama seperti music) --- */
|
|
if (popSlider) {
|
|
popSlider.value = volumeState.pop;
|
|
updateSliderFill(popSlider, volumeState.pop);
|
|
document.getElementById('vol-pop-display').textContent = volumeState.pop + '%';
|
|
|
|
popSlider.addEventListener('input', (e) => {
|
|
const val = parseInt(e.target.value);
|
|
volumeState.pop = val;
|
|
audio.pop.volume = val / 100;
|
|
localStorage.setItem('vol_pop', val);
|
|
document.getElementById('vol-pop-display').textContent = val + '%';
|
|
updateSliderFill(e.target, val);
|
|
updateMainSoundIcon();
|
|
});
|
|
}
|
|
|
|
/* --- MERGE SLIDER (sama seperti music) --- */
|
|
if (mergeSlider) {
|
|
mergeSlider.value = volumeState.merge;
|
|
updateSliderFill(mergeSlider, volumeState.merge);
|
|
document.getElementById('vol-merge-display').textContent = volumeState.merge + '%';
|
|
|
|
mergeSlider.addEventListener('input', (e) => {
|
|
const val = parseInt(e.target.value);
|
|
volumeState.merge = val;
|
|
audio.merge.volume = val / 100;
|
|
localStorage.setItem('vol_merge', val);
|
|
document.getElementById('vol-merge-display').textContent = val + '%';
|
|
updateSliderFill(e.target, val);
|
|
updateMainSoundIcon();
|
|
});
|
|
}
|
|
|
|
updateMainSoundIcon();
|
|
setupVolumePanelEvents(); // Setup panel interactions
|
|
}
|
|
|
|
/* ==========================================
|
|
SETUP PANEL EVENTS - Open/Close Logic
|
|
==========================================
|
|
Ini yang nge-link dengan inputLocked di Controls
|
|
========================================== */
|
|
function setupVolumePanelEvents() {
|
|
const btnSoundMain = document.getElementById('btn-sound-main');
|
|
const volumePanel = document.getElementById('volume-panel');
|
|
const volumeBackdrop = document.getElementById('volume-backdrop');
|
|
|
|
if (btnSoundMain && volumePanel) {
|
|
// Event: Klik tombol sound main
|
|
btnSoundMain.addEventListener('click', (e) => {
|
|
e.stopPropagation(); // Jangan trigger event di parent
|
|
const isActive = volumePanel.classList.contains('active');
|
|
|
|
if (isActive) {
|
|
// TUTUP PANEL
|
|
volumePanel.classList.remove('active');
|
|
if (volumeBackdrop) volumeBackdrop.classList.remove('active');
|
|
inputLocked = false; // UNLOCK - game input aktif lagi
|
|
} else {
|
|
// BUKA PANEL
|
|
volumePanel.classList.add('active');
|
|
if (volumeBackdrop) volumeBackdrop.classList.add('active');
|
|
inputLocked = true; // LOCK - blokir swipe & keyboard
|
|
}
|
|
});
|
|
|
|
// Event: Klik backdrop (tutup panel)
|
|
volumeBackdrop.addEventListener('click', () => {
|
|
volumePanel.classList.remove('active');
|
|
volumeBackdrop.classList.remove('active');
|
|
inputLocked = false; // UNLOCK
|
|
});
|
|
|
|
// Event: Klik di luar panel (desktop)
|
|
document.addEventListener('click', (e) => {
|
|
if (!volumePanel.contains(e.target) &&
|
|
!btnSoundMain.contains(e.target) &&
|
|
(!volumeBackdrop || !volumeBackdrop.contains(e.target))) {
|
|
volumePanel.classList.remove('active');
|
|
if (volumeBackdrop) volumeBackdrop.classList.remove('active');
|
|
inputLocked = false; // UNLOCK
|
|
}
|
|
});
|
|
|
|
// Event: Klik di dalam panel (jangan tutup)
|
|
volumePanel.addEventListener('click', (e) => {
|
|
e.stopPropagation(); // Prevent close
|
|
});
|
|
}
|
|
}
|
|
|
|
/* ==========================================
|
|
UPDATE VISUAL FILL SLIDER
|
|
========================================== */
|
|
function updateSliderFill(slider, value) {
|
|
// Set CSS custom property untuk animasi fill
|
|
// Dipakai di CSS: background: linear-gradient(...)
|
|
slider.style.setProperty('--value', value + '%');
|
|
}
|
|
|
|
/* ==========================================
|
|
UPDATE ICON DINAMIS - Sesuai Volume Level
|
|
========================================== */
|
|
function updateMainSoundIcon() {
|
|
const btnMain = document.getElementById('btn-sound-main');
|
|
if (!btnMain) return;
|
|
|
|
// Ambil semua icon
|
|
const iconFull = btnMain.querySelector('.sound-full');
|
|
const iconMedium = btnMain.querySelector('.sound-medium');
|
|
const iconLow = btnMain.querySelector('.sound-low');
|
|
const iconMuted = btnMain.querySelector('.sound-muted');
|
|
|
|
// Hitung rata-rata volume dari 3 slider
|
|
const totalVolume = volumeState.music + volumeState.pop + volumeState.merge;
|
|
const avgVolume = totalVolume / 3;
|
|
|
|
// Hide semua icon dulu
|
|
if (iconFull) iconFull.style.display = 'none';
|
|
if (iconMedium) iconMedium.style.display = 'none';
|
|
if (iconLow) iconLow.style.display = 'none';
|
|
if (iconMuted) iconMuted.style.display = 'none';
|
|
|
|
// Show icon yang sesuai dengan level volume
|
|
if (totalVolume === 0) {
|
|
// Semua muted
|
|
if (iconMuted) iconMuted.style.display = 'block';
|
|
btnMain.classList.add('all-muted');
|
|
} else {
|
|
btnMain.classList.remove('all-muted');
|
|
if (avgVolume >= 60) {
|
|
// Volume tinggi (≥60%)
|
|
if (iconFull) iconFull.style.display = 'block';
|
|
} else if (avgVolume >= 30) {
|
|
// Volume sedang (30-59%)
|
|
if (iconMedium) iconMedium.style.display = 'block';
|
|
} else {
|
|
// Volume rendah (1-29%)
|
|
if (iconLow) iconLow.style.display = 'block';
|
|
}
|
|
}
|
|
} |