81 lines
2.9 KiB
JavaScript
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));
|
|
})();
|