v_1.7
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { PdfViewer } from './pdf-viewer.js';
|
||||
|
||||
const $ = (s) => document.querySelector(s);
|
||||
const sessionId = location.pathname.split('/').filter(Boolean).pop();
|
||||
const accessCode = new URLSearchParams(location.search).get('k') || '';
|
||||
|
||||
const socket = io();
|
||||
let kind = null, built = false, mode = 'synced', following = true;
|
||||
let viewer = null, videoEl = null, joinedAudio = false;
|
||||
let localPage = 1, numPages = 1, lastVideo = null;
|
||||
|
||||
function toast(msg) {
|
||||
const t = $('#toast'); t.textContent = msg; t.classList.add('show');
|
||||
clearTimeout(toast._t); toast._t = setTimeout(() => t.classList.remove('show'), 2000);
|
||||
}
|
||||
function stageMsg(html) {
|
||||
$('#stage').querySelector('.msg')?.remove();
|
||||
const d = document.createElement('div'); d.className = 'msg'; d.innerHTML = html;
|
||||
$('#stage').prepend(d);
|
||||
}
|
||||
function clearMsg() { $('#stage').querySelector('.msg')?.remove(); }
|
||||
function setStatus(text, live) { $('#statusText').textContent = text; $('#lamp').classList.toggle('on', !!live); }
|
||||
|
||||
socket.on('connect', () => { setStatus('Connecting…', false); socket.emit('join', { sessionId, role: 'viewer', k: accessCode }); });
|
||||
socket.on('disconnect', () => setStatus('Reconnecting…', false));
|
||||
socket.on('error:join', (m) => stageMsg(m));
|
||||
socket.on('ended', () => { stageMsg('The presenter ended this session.'); setStatus('Ended', false); });
|
||||
socket.on('state', onState);
|
||||
|
||||
async function onState(s) {
|
||||
if (!s.meta) return;
|
||||
$('#deckTitle').textContent = s.meta.title || 'Presentation';
|
||||
if (s.meta.status === 'processing') { stageMsg('<div class="spinner"></div>The presenter is preparing this. Hang tight…'); setStatus('Preparing', false); return; }
|
||||
if (s.meta.status === 'error') { stageMsg('This presentation could not be prepared.'); setStatus('Unavailable', false); return; }
|
||||
if (!built) { await build(s.meta); built = true; }
|
||||
mode = s.mode;
|
||||
setStatus(mode === 'free' ? 'Browse freely' : (following ? 'Live · in sync' : 'Live · you moved away'), true);
|
||||
if (kind === 'pdf') {
|
||||
updateFollowBar();
|
||||
if (mode === 'synced' && following) { localPage = s.currentPage || 1; viewer.render(localPage); updatePageInd(); }
|
||||
$('#controls').style.display = 'flex';
|
||||
} else if (kind === 'video') {
|
||||
lastVideo = s.video; applyVideo(s.video);
|
||||
}
|
||||
}
|
||||
|
||||
async function build(meta) {
|
||||
kind = meta.kind; clearMsg();
|
||||
const src = `/s/${meta.id}/${meta.viewerFile}?k=${encodeURIComponent(accessCode)}`;
|
||||
if (kind === 'pdf') {
|
||||
viewer = new PdfViewer($('#stage'));
|
||||
numPages = await viewer.load(src); updatePageInd();
|
||||
} else if (kind === 'video') {
|
||||
videoEl = document.createElement('video');
|
||||
videoEl.src = src; videoEl.playsInline = true; videoEl.preload = 'auto'; videoEl.muted = true;
|
||||
videoEl.setAttribute('webkit-playsinline', '');
|
||||
$('#stage').appendChild(videoEl);
|
||||
$('#tapjoin').classList.add('show');
|
||||
} else if (kind === 'image') {
|
||||
const img = document.createElement('img'); img.src = src; img.alt = meta.title || 'Image';
|
||||
$('#stage').appendChild(img);
|
||||
}
|
||||
}
|
||||
|
||||
// --- PDF: audience self-navigation (with first / last) --------------------
|
||||
function updatePageInd() { $('#pageInd').textContent = `${localPage} / ${numPages}`; }
|
||||
function updateFollowBar() { $('#followbar').classList.toggle('show', mode === 'synced' && !following); }
|
||||
function localGoto(n) {
|
||||
if (kind !== 'pdf') return;
|
||||
localPage = Math.max(1, Math.min(numPages, n));
|
||||
viewer.render(localPage); updatePageInd();
|
||||
if (mode === 'synced') { following = false; updateFollowBar(); setStatus('Live · you moved away', true); }
|
||||
}
|
||||
$('#first').addEventListener('click', () => localGoto(1));
|
||||
$('#prev').addEventListener('click', () => localGoto(localPage - 1));
|
||||
$('#next').addEventListener('click', () => localGoto(localPage + 1));
|
||||
$('#last').addEventListener('click', () => localGoto(numPages));
|
||||
$('#followBtn').addEventListener('click', () => {
|
||||
following = true; updateFollowBar();
|
||||
socket.emit('join', { sessionId, role: 'viewer', k: accessCode });
|
||||
setStatus('Live · in sync', true);
|
||||
});
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (kind !== 'pdf') return;
|
||||
if (e.key === 'ArrowRight') localGoto(localPage + 1);
|
||||
if (e.key === 'ArrowLeft') localGoto(localPage - 1);
|
||||
if (e.key === 'Home') localGoto(1);
|
||||
if (e.key === 'End') localGoto(numPages);
|
||||
});
|
||||
|
||||
// --- video following -------------------------------------------------------
|
||||
$('#tapBtn').addEventListener('click', () => {
|
||||
joinedAudio = true;
|
||||
if (videoEl) videoEl.muted = false;
|
||||
$('#tapjoin').classList.remove('show');
|
||||
if (lastVideo) applyVideo(lastVideo);
|
||||
});
|
||||
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;
|
||||
videoEl.play().catch(() => { if (!joinedAudio) $('#tapjoin').classList.add('show'); });
|
||||
} else {
|
||||
videoEl.pause();
|
||||
if (typeof v.time === 'number' && Math.abs(videoEl.currentTime - v.time) > 0.4) videoEl.currentTime = v.time;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user