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'; } } const $placeArea = document.getElementById('placeArea'); if ($placeArea) $placeArea.classList.add('hidden'); const rd = document.getElementById('recordDisc'); if (rd && track?.id) { const coverUrl = `/cover/${encodeURIComponent(track.id)}`; const img = new Image(); img.onload = () => { rd.src = coverUrl; }; img.onerror = () => { /* keep default logo */ }; img.src = `${coverUrl}?t=${Date.now()}`; } } 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', code }); if (state.room) { try { renderRoom(state.room); } catch {} } } else { const code = state.room?.id || localStorage.getItem('lastRoomId'); if (code) sendMsg({ type: 'join_room', 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; } }