Refactor: Remove audio processing and game state management modules
This commit is contained in:
228
src/server-deno/public/js/handlers.js
Normal file
228
src/server-deno/public/js/handlers.js
Normal file
@@ -0,0 +1,228 @@
|
||||
import {
|
||||
$answerResult,
|
||||
$audio,
|
||||
$guessArtist,
|
||||
$guessTitle,
|
||||
$npArtist,
|
||||
$npTitle,
|
||||
$npYear,
|
||||
} from './dom.js';
|
||||
import { state } from './state.js';
|
||||
import { cacheLastRoomId, cacheSessionId, sendMsg } from './ws.js';
|
||||
import { renderRoom } from './render.js';
|
||||
import { applySync } from './audio.js';
|
||||
|
||||
function updatePlayerIdFromRoom(r) {
|
||||
try {
|
||||
if (r?.players?.length === 1) {
|
||||
const only = r.players[0];
|
||||
if (only && only.id && only.id !== state.playerId) {
|
||||
state.playerId = only.id;
|
||||
try {
|
||||
localStorage.setItem('playerId', only.id);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function shortName(id) {
|
||||
if (!id) return '-';
|
||||
const p = state.room?.players.find((x) => x.id === id);
|
||||
return p ? p.name : id.slice(0, 4);
|
||||
}
|
||||
|
||||
export function handleConnected(msg) {
|
||||
state.playerId = msg.playerId;
|
||||
try {
|
||||
if (msg.playerId) localStorage.setItem('playerId', msg.playerId);
|
||||
} catch {}
|
||||
if (msg.sessionId) {
|
||||
const existing = localStorage.getItem('sessionId');
|
||||
if (!existing) cacheSessionId(msg.sessionId);
|
||||
}
|
||||
|
||||
// lazy import to avoid cycle
|
||||
import('./session.js').then(({ reusePlayerName, reconnectLastRoom }) => {
|
||||
reusePlayerName();
|
||||
reconnectLastRoom();
|
||||
});
|
||||
|
||||
if (state.room) {
|
||||
try {
|
||||
updatePlayerIdFromRoom(state.room);
|
||||
renderRoom(state.room);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
export function handleRoomUpdate(msg) {
|
||||
if (msg?.room?.id) cacheLastRoomId(msg.room.id);
|
||||
const r = msg.room;
|
||||
updatePlayerIdFromRoom(r);
|
||||
renderRoom(r);
|
||||
}
|
||||
|
||||
export function handlePlayTrack(msg) {
|
||||
const t = msg.track;
|
||||
state.lastTrack = t;
|
||||
state.revealed = false;
|
||||
$npTitle.textContent = '???';
|
||||
$npArtist.textContent = '';
|
||||
$npYear.textContent = '';
|
||||
if ($guessTitle) $guessTitle.value = '';
|
||||
if ($guessArtist) $guessArtist.value = '';
|
||||
if ($answerResult) {
|
||||
$answerResult.textContent = '';
|
||||
$answerResult.className = 'mt-1 text-sm';
|
||||
}
|
||||
try {
|
||||
$audio.preload = 'auto';
|
||||
} catch {}
|
||||
$audio.src = t.url;
|
||||
const pf = document.getElementById('progressFill');
|
||||
if (pf) pf.style.width = '0%';
|
||||
const rd = document.getElementById('recordDisc');
|
||||
if (rd) {
|
||||
rd.classList.remove('spin-record');
|
||||
rd.src = '/hitstar.png';
|
||||
}
|
||||
const { startAt, serverNow } = msg;
|
||||
const now = Date.now();
|
||||
const offsetMs = startAt - serverNow;
|
||||
const localStart = now + offsetMs;
|
||||
const delay = Math.max(0, localStart - now);
|
||||
setTimeout(() => {
|
||||
$audio.currentTime = 0;
|
||||
$audio.play().catch(() => {});
|
||||
const disc = document.getElementById('recordDisc');
|
||||
if (disc) disc.classList.add('spin-record');
|
||||
}, delay);
|
||||
if (state.room) renderRoom(state.room);
|
||||
}
|
||||
|
||||
export function handleSync(msg) {
|
||||
applySync(msg.startAt, msg.serverNow);
|
||||
}
|
||||
|
||||
export function handleControl(msg) {
|
||||
const { action, startAt, serverNow } = msg;
|
||||
if (action === 'pause') {
|
||||
$audio.pause();
|
||||
const disc = document.getElementById('recordDisc');
|
||||
if (disc) disc.classList.remove('spin-record');
|
||||
$audio.playbackRate = 1.0;
|
||||
} else if (action === 'play') {
|
||||
if (startAt && serverNow) {
|
||||
const now = Date.now();
|
||||
const elapsed = (now - startAt) / 1000;
|
||||
$audio.currentTime = Math.max(0, elapsed);
|
||||
}
|
||||
$audio.play().catch(() => {});
|
||||
const disc = document.getElementById('recordDisc');
|
||||
if (disc) disc.classList.add('spin-record');
|
||||
}
|
||||
}
|
||||
|
||||
export function handleReveal(msg) {
|
||||
const { result, track } = msg;
|
||||
$npTitle.textContent = track.title || track.id || 'Track';
|
||||
$npArtist.textContent = track.artist ? ` – ${track.artist}` : '';
|
||||
$npYear.textContent = track.year ? ` (${track.year})` : '';
|
||||
state.revealed = true;
|
||||
const $rb = document.getElementById('revealBanner');
|
||||
if ($rb) {
|
||||
if (result.correct) {
|
||||
$rb.textContent = 'Richtig!';
|
||||
$rb.className =
|
||||
'inline-block rounded-md bg-emerald-600 text-white px-3 py-1 text-sm font-medium';
|
||||
} else {
|
||||
$rb.textContent = 'Falsch!';
|
||||
$rb.className =
|
||||
'inline-block rounded-md bg-rose-600 text-white px-3 py-1 text-sm font-medium';
|
||||
}
|
||||
}
|
||||
// Note: placeArea visibility is now controlled by renderRoom() based on game phase
|
||||
const rd = document.getElementById('recordDisc');
|
||||
if (rd && track?.file) {
|
||||
// Use track.file instead of track.id to include playlist folder prefix
|
||||
const coverUrl = `/cover/${encodeURIComponent(track.file)}`;
|
||||
const coverUrlWithTimestamp = `${coverUrl}?t=${Date.now()}`;
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
rd.src = coverUrlWithTimestamp;
|
||||
};
|
||||
img.onerror = () => {
|
||||
/* keep default logo */
|
||||
};
|
||||
img.src = coverUrlWithTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
export function handleGameEnded(msg) {
|
||||
alert(`Gewinner: ${shortName(msg.winner)}`);
|
||||
}
|
||||
|
||||
export function onMessage(ev) {
|
||||
const msg = JSON.parse(ev.data);
|
||||
switch (msg.type) {
|
||||
case 'resume_result': {
|
||||
if (msg.ok) {
|
||||
if (msg.playerId) {
|
||||
state.playerId = msg.playerId;
|
||||
try {
|
||||
localStorage.setItem('playerId', msg.playerId);
|
||||
} catch {}
|
||||
}
|
||||
const code = msg.roomId || state.room?.id || localStorage.getItem('lastRoomId');
|
||||
if (code) sendMsg({ type: 'join_room', roomId: code });
|
||||
if (state.room) {
|
||||
try {
|
||||
renderRoom(state.room);
|
||||
} catch {}
|
||||
}
|
||||
} else {
|
||||
const code = state.room?.id || localStorage.getItem('lastRoomId');
|
||||
if (code) sendMsg({ type: 'join_room', roomId: code });
|
||||
}
|
||||
return;
|
||||
}
|
||||
case 'connected':
|
||||
return handleConnected(msg);
|
||||
case 'room_update':
|
||||
return handleRoomUpdate(msg);
|
||||
case 'play_track':
|
||||
return handlePlayTrack(msg);
|
||||
case 'sync':
|
||||
return handleSync(msg);
|
||||
case 'control':
|
||||
return handleControl(msg);
|
||||
case 'reveal':
|
||||
return handleReveal(msg);
|
||||
case 'game_ended':
|
||||
return handleGameEnded(msg);
|
||||
case 'answer_result': {
|
||||
if ($answerResult) {
|
||||
if (!msg.ok) {
|
||||
$answerResult.textContent = '⛔ Eingabe ungültig oder gerade nicht möglich';
|
||||
$answerResult.className = 'mt-1 text-sm text-rose-600';
|
||||
} else {
|
||||
const okBoth = !!(msg.correctTitle && msg.correctArtist);
|
||||
const parts = [];
|
||||
parts.push(msg.correctTitle ? 'Titel ✓' : 'Titel ✗');
|
||||
parts.push(msg.correctArtist ? 'Künstler ✓' : 'Künstler ✗');
|
||||
let coin = '';
|
||||
if (msg.awarded) coin = ' +1 Token';
|
||||
else if (msg.alreadyAwarded) coin = ' (bereits erhalten)';
|
||||
$answerResult.textContent = `${parts.join(' · ')}${coin}`;
|
||||
$answerResult.className = okBoth
|
||||
? 'mt-1 text-sm text-emerald-600'
|
||||
: 'mt-1 text-sm text-amber-600';
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user