import { PdfViewer } from './pdf-viewer.js'; const $ = (s) => document.querySelector(s); const parts = location.pathname.split('/').filter(Boolean); const sessionId = parts[parts.length - 1]; const params = new URLSearchParams(location.search); const token = params.get('token') || ''; // presenter token: mirror the console even after code rotation const accessCode = params.get('k') || ''; // fallback for asset access const socket = io(); let kind = null, built = false, viewer = null, videoEl = null; function msg(text) { $('#stage').innerHTML = `
${text}
`; } // Join as presenter (display-only — it never sends control) so it stays in sync // with the console and isn't counted as an audience viewer. socket.on('connect', () => socket.emit('join', { sessionId, role: 'presenter', token, k: accessCode })); socket.on('error:join', (m) => msg(m)); socket.on('ended', () => msg('The presentation has ended.')); socket.on('state', onState); async function onState(s) { if (!s.meta) return; document.title = (s.meta.title || 'Presentation') + ' — MPS'; if (s.meta.status === 'processing') { msg('Preparing…'); return; } if (s.meta.status === 'error') { msg('This presentation could not be prepared.'); return; } if (!built) { await build(s.meta); built = true; } if (kind === 'pdf') viewer.render(s.currentPage || 1); else if (kind === 'video') applyVideo(s.video); } async function build(meta) { kind = meta.kind; const stage = $('#stage'); stage.innerHTML = ''; const src = `/s/${meta.id}/${meta.viewerFile}${accessCode ? `?k=${encodeURIComponent(accessCode)}` : ''}`; if (kind === 'pdf') { viewer = new PdfViewer(stage); await viewer.load(src); } else if (kind === 'video') { videoEl = document.createElement('video'); videoEl.src = src; videoEl.playsInline = true; videoEl.preload = 'auto'; videoEl.setAttribute('webkit-playsinline', ''); stage.appendChild(videoEl); } else if (kind === 'image') { const img = document.createElement('img'); img.src = src; img.alt = meta.title || ''; stage.appendChild(img); } } function applyVideo(v) { if (!videoEl || !v) return; const rate = v.rate || 1; if (v.playing) { const elapsed = (Date.now() - (v.updatedAt || Date.now())) / 1000; const expected = (v.time || 0) + elapsed * rate; if (Math.abs(videoEl.currentTime - expected) > 0.8) videoEl.currentTime = expected; videoEl.playbackRate = rate; // Play with sound where allowed; fall back to muted so the image still tracks. videoEl.play().catch(() => { videoEl.muted = true; videoEl.play().catch(() => {}); }); } else { videoEl.pause(); if (typeof v.time === 'number' && Math.abs(videoEl.currentTime - v.time) > 0.4) videoEl.currentTime = v.time; } }