Files
hitstar/public/js/handlers.js
Elmar Kresse 17faca1f46
All checks were successful
Build and Push Docker Image / docker (push) Successful in 10s
feat: added playlist option
2025-10-12 15:32:53 +02:00

229 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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?.file) {
// Use track.file instead of track.id to include playlist folder prefix
const coverUrl = `/cover/${encodeURIComponent(track.file)}`;
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;
}
}