Files

81 lines
2.9 KiB
JavaScript

'use strict';
(function () {
const $ = id => document.getElementById(id);
const roomId = decodeURIComponent((location.pathname.match(/^\/watch\/([A-Za-z0-9_-]+)\/?$/) || [])[1] || '');
const maxLines = Math.min(5, Math.max(1, parseInt(new URLSearchParams(location.search).get('maxlines'), 10) || 3));
if (new URLSearchParams(location.search).has('plain')) document.body.classList.add('plain');
const finals = [];
let interim = null;
let viewer = null;
function status(message, error) {
$('status').textContent = message;
$('status').classList.toggle('error', !!error);
$('status').hidden = false;
}
function render() {
const lines = finals.slice(-maxLines);
if (interim) lines.push(interim);
const container = $('captions');
container.replaceChildren();
for (const item of lines) {
const line = document.createElement('div');
line.className = 'line' + (item.interim ? ' interim' : '');
line.textContent = item.text;
container.append(line);
}
if (viewer && viewer.getCursor) {
const cursor = viewer.getCursor();
if (cursor) $('status').hidden = true;
}
}
function receive(data) {
const text = String(data.final || data.interm || '').trim();
if (!text) return;
if (data.interm) {
interim = {text};
} else {
interim = null;
finals.push({text});
if (finals.length > 50) finals.shift();
}
render();
}
async function boot() {
if (!roomId) { status('This caption link is invalid.', true); return; }
let response;
try {
response = await fetch('/api/public/rooms/' + encodeURIComponent(roomId), {
credentials: 'same-origin', signal: AbortSignal.timeout(10000)
});
} catch (_) { status('The caption service could not be reached.', true); return; }
if (!response.ok) {
status(response.status === 404 ? 'This caption room has ended.' : 'Captions are unavailable.', true);
return;
}
let config;
try { config = await response.json(); } catch (_) { status('Captions are unavailable.', true); return; }
try {
CaptionRelay.configure(config.address);
} catch (error) {
status('Captions are misconfigured: ' + error.message, true);
return;
}
viewer = CaptionRelay.subscribe({
room: config.room,
token: config.readToken,
onCaption: receive,
onState: state => {
if (state === 'connected') { if (!finals.length && !interim) status('Connected. Waiting for captions…'); }
else if (state === 'connected-with-gap') status('Reconnected; some captions may be missing.', true);
else if (state === 'denied') status('This caption room link is no longer valid.', true);
else if (state === 'reconnecting') status('Reconnecting to captions…');
},
onError: error => status('Caption relay: ' + (error.message || error), true),
onGap: () => status('Some captions may be missing.', true)
});
window.addEventListener('beforeunload', () => viewer.disconnect());
}
boot().catch(() => status('Captions are unavailable.', true));
})();