feat: Integrate Howler.js for audio playback and remove native audio elements
All checks were successful
Build and Push Docker Image / docker (push) Successful in 9s
All checks were successful
Build and Push Docker Image / docker (push) Successful in 9s
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
$audio,
|
||||
$answerResult,
|
||||
$copyRoomCode,
|
||||
$createRoom,
|
||||
@@ -22,11 +21,11 @@ import {
|
||||
$playBtn,
|
||||
$volumeSlider,
|
||||
$saveName,
|
||||
} from './dom.js';
|
||||
import { state } from './state.js';
|
||||
import { initAudioUI, stopAudioPlayback } from './audio.js';
|
||||
import { sendMsg } from './ws.js';
|
||||
import { showToast, wire } from './utils.js';
|
||||
} from "./dom.js";
|
||||
import { state } from "./state.js";
|
||||
import { initAudioUI, stopAudioPlayback } from "./audio.js";
|
||||
import { sendMsg } from "./ws.js";
|
||||
import { showToast, wire } from "./utils.js";
|
||||
|
||||
export function wireUi() {
|
||||
initAudioUI();
|
||||
@@ -37,15 +36,15 @@ export function wireUi() {
|
||||
// Button was removed; no state management needed.
|
||||
|
||||
function saveNameIfChanged(raw) {
|
||||
const name = (raw || '').trim();
|
||||
const name = (raw || "").trim();
|
||||
if (!name) return;
|
||||
try {
|
||||
const prev = localStorage.getItem('playerName') || '';
|
||||
const prev = localStorage.getItem("playerName") || "";
|
||||
if (prev === name) return; // no-op
|
||||
localStorage.setItem('playerName', name);
|
||||
localStorage.setItem("playerName", name);
|
||||
if ($nameDisplay) $nameDisplay.textContent = name;
|
||||
sendMsg({ type: 'set_name', name });
|
||||
showToast('Name gespeichert!');
|
||||
sendMsg({ type: "set_name", name });
|
||||
showToast("Name gespeichert!");
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
@@ -53,19 +52,19 @@ export function wireUi() {
|
||||
|
||||
// Manual save button
|
||||
if ($saveName) {
|
||||
wire($saveName, 'click', () => {
|
||||
wire($saveName, "click", () => {
|
||||
if (nameDebounce) {
|
||||
clearTimeout(nameDebounce);
|
||||
nameDebounce = null;
|
||||
}
|
||||
const val = ($nameLobby?.value || '').trim();
|
||||
const val = ($nameLobby?.value || "").trim();
|
||||
if (!val) {
|
||||
showToast('⚠️ Bitte gib einen Namen ein!');
|
||||
showToast("⚠️ Bitte gib einen Namen ein!");
|
||||
return;
|
||||
}
|
||||
const prev = localStorage.getItem('playerName') || '';
|
||||
const prev = localStorage.getItem("playerName") || "";
|
||||
if (prev === val) {
|
||||
showToast('✓ Name bereits gespeichert!');
|
||||
showToast("✓ Name bereits gespeichert!");
|
||||
return;
|
||||
}
|
||||
saveNameIfChanged(val);
|
||||
@@ -74,9 +73,9 @@ export function wireUi() {
|
||||
|
||||
// Autosave on input with debounce
|
||||
if ($nameLobby) {
|
||||
wire($nameLobby, 'input', () => {
|
||||
wire($nameLobby, "input", () => {
|
||||
if (nameDebounce) clearTimeout(nameDebounce);
|
||||
const val = ($nameLobby.value || '').trim();
|
||||
const val = ($nameLobby.value || "").trim();
|
||||
if (!val) return;
|
||||
nameDebounce = setTimeout(() => {
|
||||
saveNameIfChanged($nameLobby.value);
|
||||
@@ -84,114 +83,107 @@ export function wireUi() {
|
||||
}, SAVE_DEBOUNCE_MS);
|
||||
});
|
||||
// Save immediately on blur
|
||||
wire($nameLobby, 'blur', () => {
|
||||
wire($nameLobby, "blur", () => {
|
||||
if (nameDebounce) {
|
||||
clearTimeout(nameDebounce);
|
||||
nameDebounce = null;
|
||||
}
|
||||
const val = ($nameLobby.value || '').trim();
|
||||
const val = ($nameLobby.value || "").trim();
|
||||
if (val) saveNameIfChanged(val);
|
||||
});
|
||||
// Save on Enter
|
||||
wire($nameLobby, 'keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
wire($nameLobby, "keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
if (nameDebounce) {
|
||||
clearTimeout(nameDebounce);
|
||||
nameDebounce = null;
|
||||
}
|
||||
const val = ($nameLobby.value || '').trim();
|
||||
const val = ($nameLobby.value || "").trim();
|
||||
if (val) saveNameIfChanged(val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
wire($createRoom, 'click', () => sendMsg({ type: 'create_room' }));
|
||||
wire($createRoom, "click", () => sendMsg({ type: "create_room" }));
|
||||
|
||||
wire($joinRoom, 'click', () => {
|
||||
wire($joinRoom, "click", () => {
|
||||
const code = $roomCode.value.trim();
|
||||
if (code) sendMsg({ type: 'join_room', roomId: code });
|
||||
if (code) sendMsg({ type: "join_room", roomId: code });
|
||||
});
|
||||
|
||||
wire($leaveRoom, 'click', () => {
|
||||
sendMsg({ type: 'leave_room' });
|
||||
wire($leaveRoom, "click", () => {
|
||||
sendMsg({ type: "leave_room" });
|
||||
try {
|
||||
localStorage.removeItem('playerId');
|
||||
localStorage.removeItem('sessionId');
|
||||
localStorage.removeItem('dashboardHintSeen');
|
||||
localStorage.removeItem('lastRoomId');
|
||||
localStorage.removeItem("playerId");
|
||||
localStorage.removeItem("sessionId");
|
||||
localStorage.removeItem("dashboardHintSeen");
|
||||
localStorage.removeItem("lastRoomId");
|
||||
} catch {}
|
||||
stopAudioPlayback();
|
||||
state.room = null;
|
||||
if ($nameLobby) {
|
||||
try {
|
||||
const storedName = localStorage.getItem('playerName') || '';
|
||||
const storedName = localStorage.getItem("playerName") || "";
|
||||
$nameLobby.value = storedName;
|
||||
} catch {
|
||||
$nameLobby.value = '';
|
||||
$nameLobby.value = "";
|
||||
}
|
||||
}
|
||||
if ($nameDisplay) $nameDisplay.textContent = '';
|
||||
if ($nameDisplay) $nameDisplay.textContent = "";
|
||||
if ($readyChk) {
|
||||
try {
|
||||
$readyChk.checked = false;
|
||||
} catch {}
|
||||
}
|
||||
$lobby.classList.remove('hidden');
|
||||
$room.classList.add('hidden');
|
||||
$lobby.classList.remove("hidden");
|
||||
$room.classList.add("hidden");
|
||||
});
|
||||
|
||||
wire($startGame, 'click', () => {
|
||||
wire($startGame, "click", () => {
|
||||
// Validate playlist selection before starting
|
||||
if (!state.room?.state?.playlist) {
|
||||
showToast('⚠️ Bitte wähle zuerst eine Playlist aus!');
|
||||
showToast("⚠️ Bitte wähle zuerst eine Playlist aus!");
|
||||
return;
|
||||
}
|
||||
sendMsg({ type: 'start_game' });
|
||||
sendMsg({ type: "start_game" });
|
||||
});
|
||||
|
||||
wire($readyChk, 'change', (e) => {
|
||||
wire($readyChk, "change", (e) => {
|
||||
const val = !!e.target.checked;
|
||||
state.pendingReady = val;
|
||||
sendMsg({ type: 'ready', ready: val });
|
||||
sendMsg({ type: "ready", ready: val });
|
||||
});
|
||||
|
||||
// Playlist selection
|
||||
const $playlistSelect = document.getElementById('playlistSelect');
|
||||
const $playlistSelect = document.getElementById("playlistSelect");
|
||||
if ($playlistSelect) {
|
||||
wire($playlistSelect, 'change', (e) => {
|
||||
wire($playlistSelect, "change", (e) => {
|
||||
const playlistId = e.target.value;
|
||||
sendMsg({ type: 'select_playlist', playlist: playlistId });
|
||||
sendMsg({ type: "select_playlist", playlist: playlistId });
|
||||
});
|
||||
}
|
||||
|
||||
wire($placeBtn, 'click', () => {
|
||||
wire($placeBtn, "click", () => {
|
||||
const slot = parseInt($slotSelect.value, 10);
|
||||
sendMsg({ type: 'place_guess', slot });
|
||||
sendMsg({ type: "place_guess", slot });
|
||||
});
|
||||
|
||||
wire($playBtn, 'click', () => sendMsg({ type: 'resume_play' }));
|
||||
wire($pauseBtn, 'click', () => sendMsg({ type: 'pause' }));
|
||||
wire($nextBtn, 'click', () => sendMsg({ type: 'skip_track' }));
|
||||
wire($playBtn, "click", () => sendMsg({ type: "resume_play" }));
|
||||
wire($pauseBtn, "click", () => sendMsg({ type: "pause" }));
|
||||
wire($nextBtn, "click", () => sendMsg({ type: "skip_track" }));
|
||||
|
||||
if ($volumeSlider && $audio) {
|
||||
try {
|
||||
$volumeSlider.value = String($audio.volume ?? 1);
|
||||
} catch {}
|
||||
$volumeSlider.addEventListener('input', () => {
|
||||
$audio.volume = parseFloat($volumeSlider.value);
|
||||
});
|
||||
}
|
||||
// Volume slider is now handled in audio.js initAudioUI()
|
||||
|
||||
if ($copyRoomCode) {
|
||||
$copyRoomCode.style.display = 'inline-block';
|
||||
wire($copyRoomCode, 'click', () => {
|
||||
$copyRoomCode.style.display = "inline-block";
|
||||
wire($copyRoomCode, "click", () => {
|
||||
if (state.room?.id) {
|
||||
navigator.clipboard.writeText(state.room.id).then(() => {
|
||||
$copyRoomCode.textContent = '✔️';
|
||||
showToast('Code kopiert!');
|
||||
$copyRoomCode.textContent = "✔️";
|
||||
showToast("Code kopiert!");
|
||||
setTimeout(() => {
|
||||
$copyRoomCode.textContent = '📋';
|
||||
$copyRoomCode.textContent = "📋";
|
||||
}, 1200);
|
||||
});
|
||||
}
|
||||
@@ -199,57 +191,57 @@ export function wireUi() {
|
||||
}
|
||||
|
||||
if ($roomId) {
|
||||
wire($roomId, 'click', () => {
|
||||
wire($roomId, "click", () => {
|
||||
if (state.room?.id) {
|
||||
navigator.clipboard.writeText(state.room.id).then(() => {
|
||||
$roomId.title = 'Kopiert!';
|
||||
showToast('Code kopiert!');
|
||||
$roomId.title = "Kopiert!";
|
||||
showToast("Code kopiert!");
|
||||
setTimeout(() => {
|
||||
$roomId.title = 'Klicken zum Kopieren';
|
||||
$roomId.title = "Klicken zum Kopieren";
|
||||
}, 1200);
|
||||
});
|
||||
}
|
||||
});
|
||||
$roomId.style.cursor = 'pointer';
|
||||
$roomId.style.cursor = "pointer";
|
||||
}
|
||||
|
||||
const form = document.getElementById('answerForm');
|
||||
const form = document.getElementById("answerForm");
|
||||
if (form) {
|
||||
form.addEventListener('submit', (e) => {
|
||||
form.addEventListener("submit", (e) => {
|
||||
e.preventDefault();
|
||||
const title = ($guessTitle?.value || '').trim();
|
||||
const artist = ($guessArtist?.value || '').trim();
|
||||
const title = ($guessTitle?.value || "").trim();
|
||||
const artist = ($guessArtist?.value || "").trim();
|
||||
if (!title || !artist) {
|
||||
if ($answerResult) {
|
||||
$answerResult.textContent = 'Bitte Titel und Künstler eingeben';
|
||||
$answerResult.className = 'mt-1 text-sm text-amber-600';
|
||||
$answerResult.textContent = "Bitte Titel und Künstler eingeben";
|
||||
$answerResult.className = "mt-1 text-sm text-amber-600";
|
||||
}
|
||||
return;
|
||||
}
|
||||
sendMsg({ type: 'submit_answer', guess: { title, artist } });
|
||||
sendMsg({ type: "submit_answer", guess: { title, artist } });
|
||||
});
|
||||
}
|
||||
|
||||
// Dashboard one-time hint
|
||||
const dashboard = document.getElementById('dashboard');
|
||||
const dashboardHint = document.getElementById('dashboardHint');
|
||||
const dashboard = document.getElementById("dashboard");
|
||||
const dashboardHint = document.getElementById("dashboardHint");
|
||||
if (dashboard && dashboardHint) {
|
||||
try {
|
||||
const seen = localStorage.getItem('dashboardHintSeen');
|
||||
const seen = localStorage.getItem("dashboardHintSeen");
|
||||
if (!seen) {
|
||||
dashboardHint.classList.remove('hidden');
|
||||
dashboardHint.classList.remove("hidden");
|
||||
const hide = () => {
|
||||
dashboardHint.classList.add('hidden');
|
||||
dashboardHint.classList.add("hidden");
|
||||
try {
|
||||
localStorage.setItem('dashboardHintSeen', '1');
|
||||
localStorage.setItem("dashboardHintSeen", "1");
|
||||
} catch {}
|
||||
dashboard.removeEventListener('toggle', hide);
|
||||
dashboard.removeEventListener('click', hide);
|
||||
dashboard.removeEventListener("toggle", hide);
|
||||
dashboard.removeEventListener("click", hide);
|
||||
};
|
||||
dashboard.addEventListener('toggle', hide);
|
||||
dashboard.addEventListener('click', hide, { once: true });
|
||||
dashboard.addEventListener("toggle", hide);
|
||||
dashboard.addEventListener("click", hide, { once: true });
|
||||
setTimeout(() => {
|
||||
if (!localStorage.getItem('dashboardHintSeen')) hide();
|
||||
if (!localStorage.getItem("dashboardHintSeen")) hide();
|
||||
}, 6000);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
Reference in New Issue
Block a user