anonstream/static/platform.js

164 行
5.6 KiB
JavaScript

const t0 = Date.now() / 1000;
const playbackTimeout = 20.0; // seconds until playback should have begun
const segmentDuration = 8.0; // seconds per segment
const latencyThreshold = 180; // notify the viewer once they cross this threshold
const segmentThreshold = latencyThreshold / segmentDuration;
let token, streamTitle, viewerCount, streamStatus, streamLight, refreshButton;
let streamAbsoluteStart, streamRelativeStart, streamTimer, streamTimerLastUpdated;
// ensure only one heartbeat is sent at a time
let heartIsBeating = false;
let streamInfoFrame = window.frames['stream-info'];
streamInfoFrame.addEventListener("load", function() {
console.log("stream info iframe loaded");
const streamInfo = streamInfoFrame.contentDocument
streamTitle = streamInfo.getElementById("stream-title");
viewerCount = streamInfo.getElementById("viewer-count");
streamStatus = streamInfo.getElementById("stream-status");
streamLight = streamInfo.getElementById("stream-light");
refreshButton = streamInfo.getElementById("refresh-button");
refreshButton.onclick = function() { return window.location.reload(true); };
streamTimer = streamInfo.getElementById("uptime");
streamAbsoluteStart = streamInfoFrame.contentWindow.streamAbsoluteStart;
streamRelativeStart = streamInfoFrame.contentWindow.streamRelativeStart;
streamTimerLastUpdated = Date.now() / 1000;
// this viewer's token
token = document.getElementById("token").value;
// get stream info every 20 seconds
setInterval(heartbeat, 20000);
// update stream timer every second
setInterval(updateStreamTimer, 1000);
});
function currentSegment() {
try {
let player = videojs.players.vjs_video_3;
if ( player == null ) {
player = videojs.players.videojs;
}
const tracks = player.textTracks();
const cues = tracks[0].cues;
const uri = cues[cues.length - 1].value.uri;
return parseInt(uri.split("/")[3].slice(6));
} catch ( error ) {
return null;
}
}
function updateStreamStatus(msg, color, showRefreshButton) {
streamStatus.innerHTML = msg;
streamLight.style.color = color;
if ( showRefreshButton ) {
refreshButton.style.display = "";
} else {
refreshButton.style.display = "none";
}
}
function updateStreamTimer() {
if ( streamTimer == null ) {
return;
}
const diff = streamRelativeStart + Math.floor(Date.now() / 1000 - streamTimerLastUpdated);
if ( Number.isInteger(diff) ) {
const hours = Math.floor(diff / 3600);
const minutes = Math.floor((diff % 3600) / 60);
const seconds = diff % 60;
const mm = ("0" + minutes).slice(-2);
const ss = ("0" + seconds).slice(-2);
if ( hours == 0 ) {
streamTimer.innerHTML = `${mm}:${ss}`;
} else if ( hours < 1000 ) {
streamTimer.innerHTML = `${hours}:${mm}:${ss}`;
} else {
streamTimer.innerHTML = "1000+ hours";
}
} else {
streamTimer.innerHTML = "";
}
}
// get stream info from the server (viewer count, current segment, if stream is online, etc.)
function heartbeat() {
if ( heartIsBeating ) {
return;
} else {
heartIsBeating = true;
}
// prepare a request to /heartbeat
const xhr = new XMLHttpRequest();
xhr.open("GET", `/heartbeat?token=${token}`);
xhr.timeout = 18000; // timeout in ms, 18 seconds
xhr.onerror = function(e) {
heartIsBeating = false;
console.log(e);
updateStreamStatus("The stream was unreachable. Try refreshing the page.", "yellow", true);
}
xhr.ontimeout = xhr.onerror;
xhr.onload = function(e) {
heartIsBeating = false;
if ( xhr.status != 200 ) {
return xhr.onerror(xhr);
}
response = JSON.parse(xhr.responseText)
// update viewer count
viewerCount.innerHTML = response.viewers;
// update stream title
streamTitle.innerHTML = response.title;
// update stream start time (for the timer)
const oldStreamAbsoluteStart = streamAbsoluteStart;
streamAbsoluteStart = response.start_abs;
streamRelativeStart = response.start_rel;
streamTimerLastUpdated = Date.now() / 1000;
// update stream status
if ( !response.online ) {
return updateStreamStatus("The stream has ended.", "red", false);
}
const serverSegment = response.current_segment;
if ( !Number.isInteger(serverSegment) ) {
return updateStreamStatus("The stream has ended.", "red", false);
}
if ( oldStreamAbsoluteStart != response.start_abs ) {
return updateStreamStatus("The stream restarted. Refresh the page.", "yellow", true);
}
// when the page is first loaded clientSegment may be null
const clientSegment = currentSegment();
if ( Number.isInteger(clientSegment) ) {
const diff = serverSegment - clientSegment;
if ( diff >= segmentThreshold ) {
return updateStreamStatus(`You're more than ${latencyThreshold} seconds behind the stream. Refresh the page.`, "yellow", true);
} else if ( diff < 0 ) {
return updateStreamStatus("The stream restarted. Refresh the page.", "yellow", true);
}
} else if (Date.now() / 1000 - t0 >= playbackTimeout) {
return updateStreamStatus("The stream is online but you're not receiving it. Try refreshing the page.", "yellow", true);
}
// otherwise
return updateStreamStatus("The stream is online.", "green", false);
}
xhr.send();
}