Frontload the chat time display calculation

The chat time display calculation is also done in a worker to prevent stalling. Since we do the calculation on load, the stalling that is prevented would happen when loading the page.
pull/317/head
ElementalAlchemist 2 years ago committed by Mike Lang
parent e74d655ce5
commit 832a0264bb

@ -8,6 +8,7 @@
<script src="scripts/hls.min.js"></script> <script src="scripts/hls.min.js"></script>
<script src="scripts/luxon.min.js"></script> <script src="scripts/luxon.min.js"></script>
<script src="scripts/common-worker.js"></script>
<script src="scripts/common.js"></script> <script src="scripts/common.js"></script>
<script src="scripts/edit.js"></script> <script src="scripts/edit.js"></script>
<script src="scripts/keyboard-shortcuts.js"></script> <script src="scripts/keyboard-shortcuts.js"></script>

@ -8,6 +8,7 @@
<script src="scripts/hls.min.js"></script> <script src="scripts/hls.min.js"></script>
<script src="scripts/luxon.min.js"></script> <script src="scripts/luxon.min.js"></script>
<script src="scripts/common-worker.js"></script>
<script src="scripts/common.js"></script> <script src="scripts/common.js"></script>
<script src="scripts/stream.js"></script> <script src="scripts/stream.js"></script>
<script src="scripts/keyboard-shortcuts.js"></script> <script src="scripts/keyboard-shortcuts.js"></script>

@ -0,0 +1,49 @@
self.importScripts("luxon.min.js", "common-worker.js");
var DateTime = luxon.DateTime;
luxon.Settings.defaultZone = "utc";
self.onmessage = async (event) => {
const chatLoadData = event.data;
const segmentMetadata = chatLoadData.segmentMetadata;
for (const segmentData of segmentMetadata) {
segmentData.rawStart = DateTime.fromMillis(segmentData.rawStart);
segmentData.rawEnd = DateTime.fromMillis(segmentData.rawEnd);
}
const fetchURL = `/${chatLoadData.stream}/chat.json?start=${chatLoadData.start}&end=${chatLoadData.end}`;
const chatResponse = await fetch(fetchURL);
if (!chatResponse.ok) {
return;
}
const chatRawData = await chatResponse.json();
const chatData = [];
for (const chatLine of chatRawData) {
if (
chatLine.command !== "PRIVMSG" &&
chatLine.command !== "CLEARMSG" &&
chatLine.command !== "CLEARCHAT" &&
chatLine.command !== "USERNOTICE"
) {
continue;
}
const when = DateTime.fromSeconds(chatLine.time);
const displayWhen = videoHumanTimeFromDateTimeWithFragments(segmentMetadata, when);
// Here, we just push each line successively into the list. This assumes data is provided to us in chronological order.
chatData.push({ message: chatLine, when: when.toMillis(), displayWhen: displayWhen });
}
self.postMessage(chatData);
};
function videoHumanTimeFromDateTimeWithFragments(fragmentMetadata, dateTime) {
for (const segmentData of fragmentMetadata) {
if (dateTime >= segmentData.rawStart && dateTime <= segmentData.rawEnd) {
const playerTime =
segmentData.playerStart + dateTime.diff(segmentData.rawStart).as("seconds");
return videoHumanTimeFromVideoPlayerTime(playerTime);
}
}
return null;
}

@ -0,0 +1,21 @@
function videoHumanTimeFromVideoPlayerTime(videoPlayerTime) {
const hours = Math.floor(videoPlayerTime / 3600);
let minutes = Math.floor((videoPlayerTime % 3600) / 60);
let seconds = Math.floor(videoPlayerTime % 60);
let milliseconds = Math.floor((videoPlayerTime * 1000) % 1000);
while (minutes.toString().length < 2) {
minutes = `0${minutes}`;
}
while (seconds.toString().length < 2) {
seconds = `0${seconds}`;
}
while (milliseconds.toString().length < 3) {
milliseconds = `0${milliseconds}`;
}
if (hours > 0) {
return `${hours}:${minutes}:${seconds}.${milliseconds}`;
}
return `${minutes}:${seconds}.${milliseconds}`;
}

@ -12,6 +12,7 @@ var globalSetUpControls = false;
var globalSeekTimer = null; var globalSeekTimer = null;
var globalChatData = []; var globalChatData = [];
var globalLoadChatWorker = null;
Hls.DefaultConfig.maxBufferHole = 600; Hls.DefaultConfig.maxBufferHole = 600;
@ -25,6 +26,8 @@ function commonPageSetup() {
"Your browser doesn't support MediaSource extensions. Video playback and editing won't work." "Your browser doesn't support MediaSource extensions. Video playback and editing won't work."
); );
} }
globalLoadChatWorker = new Worker("scripts/chat-load.js");
} }
function addError(errorText) { function addError(errorText) {
@ -50,6 +53,7 @@ async function loadVideoPlayer(playlistURL) {
videoElement.addEventListener("loadedmetadata", (_event) => { videoElement.addEventListener("loadedmetadata", (_event) => {
setUpVideoControls(); setUpVideoControls();
sendChatLogLoadData();
}); });
videoElement.addEventListener("loadeddata", (_event) => { videoElement.addEventListener("loadeddata", (_event) => {
@ -419,12 +423,12 @@ function dateTimeFromVideoPlayerTime(videoPlayerTime) {
} }
function videoPlayerTimeFromDateTime(dateTime) { function videoPlayerTimeFromDateTime(dateTime) {
const segmentList = getSegmentList(); const segmentTimes = getSegmentTimes();
for (const segment of segmentList) { for (const segmentData of segmentTimes) {
const segmentStart = DateTime.fromISO(segment.rawProgramDateTime); const segmentStart = segmentData.rawStart;
const segmentEnd = segmentStart.plus({ seconds: segment.duration }); const segmentEnd = segmentData.rawEnd;
if (dateTime >= segmentStart && dateTime <= segmentEnd) { if (dateTime >= segmentStart && dateTime <= segmentEnd) {
return segment.start + dateTime.diff(segmentStart).as("seconds"); return segmentData.playerStart + dateTime.diff(segmentStart).as("seconds");
} }
} }
return null; return null;
@ -478,6 +482,17 @@ function hasSegmentList() {
return false; return false;
} }
function getSegmentTimes() {
const segmentList = getSegmentList();
const segmentTimes = [];
for (const segment of segmentList) {
const segmentStart = DateTime.fromISO(segment.rawProgramDateTime);
const segmentEnd = segmentStart.plus({ seconds: segment.duration });
segmentTimes.push({ rawStart: segmentStart, rawEnd: segmentEnd, playerStart: segment.start });
}
return segmentTimes;
}
function downloadFrame() { function downloadFrame() {
const videoElement = document.getElementById("video"); const videoElement = document.getElementById("video");
const dateTime = dateTimeFromVideoPlayerTime(videoElement.currentTime); const dateTime = dateTimeFromVideoPlayerTime(videoElement.currentTime);
@ -500,27 +515,32 @@ function triggerDownload(url, filename) {
document.body.removeChild(link); document.body.removeChild(link);
} }
async function getChatLog(startWubloaderTime, endWubloaderTime) { function sendChatLogLoadData() {
globalChatData = []; const startTime = globalStartTimeString;
const url = `/${globalStreamName}/chat.json?start=${startWubloaderTime}&end=${endWubloaderTime}`; const endTime = globalEndTimeString;
const response = await fetch(url); if (!startTime || !endTime) {
if (!response.ok) {
return; return;
} }
const chatLogData = await response.json(); const segmentMetadata = getSegmentTimes();
for (const chatLine of chatLogData) { for (const segmentData of segmentMetadata) {
if ( segmentData.rawStart = segmentData.rawStart.toMillis();
chatLine.command !== "PRIVMSG" && segmentData.rawEnd = segmentData.rawEnd.toMillis();
chatLine.command !== "CLEARMSG" &&
chatLine.command !== "CLEARCHAT" &&
chatLine.command !== "USERNOTICE"
) {
continue;
} }
const when = DateTime.fromSeconds(chatLine.time);
// Here, we just push each line successively into the list. This assumes data is provided to us in chronological order. const message = {
globalChatData.push({ message: chatLine, when: when }); stream: globalStreamName,
start: startTime,
end: endTime,
segmentMetadata: segmentMetadata,
};
globalLoadChatWorker.postMessage(message);
}
function updateChatDataFromWorkerResponse(chatData) {
for (const chatLine of chatData) {
chatLine.when = DateTime.fromMillis(chatLine.when);
} }
globalChatData = chatData;
} }
function renderChatMessage(chatMessageData) { function renderChatMessage(chatMessageData) {
@ -531,7 +551,7 @@ function renderChatMessage(chatMessageData) {
const sendTimeElement = document.createElement("div"); const sendTimeElement = document.createElement("div");
sendTimeElement.classList.add("chat-replay-message-time"); sendTimeElement.classList.add("chat-replay-message-time");
sendTimeElement.innerText = videoHumanTimeFromDateTime(chatMessageData.when); sendTimeElement.innerText = chatMessageData.displayWhen;
const senderNameElement = createMessageSenderElement(chatMessageData); const senderNameElement = createMessageSenderElement(chatMessageData);
@ -579,7 +599,7 @@ function renderSystemMessages(chatMessageData) {
const sendTimeElement = document.createElement("div"); const sendTimeElement = document.createElement("div");
sendTimeElement.classList.add("chat-replay-message-time"); sendTimeElement.classList.add("chat-replay-message-time");
sendTimeElement.innerText = videoHumanTimeFromDateTime(chatMessageData.when); sendTimeElement.innerText = chatMessageData.displayWhen;
const systemTextElement = document.createElement("div"); const systemTextElement = document.createElement("div");
systemTextElement.classList.add("chat-replay-message-text"); systemTextElement.classList.add("chat-replay-message-text");

@ -7,6 +7,10 @@ const CHAPTER_MARKER_DELIMITER_PARTIAL = "==========";
window.addEventListener("DOMContentLoaded", async (event) => { window.addEventListener("DOMContentLoaded", async (event) => {
commonPageSetup(); commonPageSetup();
globalLoadChatWorker.onmessage = (event) => {
updateChatDataFromWorkerResponse(event.data);
renderChatLog();
};
const timeUpdateForm = document.getElementById("stream-time-settings"); const timeUpdateForm = document.getElementById("stream-time-settings");
timeUpdateForm.addEventListener("submit", async (event) => { timeUpdateForm.addEventListener("submit", async (event) => {
@ -338,20 +342,6 @@ window.addEventListener("DOMContentLoaded", async (event) => {
document.getElementById("google-auth-sign-out").addEventListener("click", (_event) => { document.getElementById("google-auth-sign-out").addEventListener("click", (_event) => {
googleSignOut(); googleSignOut();
}); });
await loadEditorChatData();
if (globalChatData) {
if (hasSegmentList()) {
renderChatLog();
} else {
const videoPlayer = document.getElementById("video");
const initialChatLogRender = (_event) => {
renderChatLog();
videoPlayer.removeEventListener("loadedmetadata", initialChatLogRender);
};
videoPlayer.addEventListener("loadedmetadata", initialChatLogRender);
}
}
}); });
async function loadVideoInfo() { async function loadVideoInfo() {
@ -1540,10 +1530,6 @@ async function rangeDataUpdated() {
} }
} }
updateDownloadLink(); updateDownloadLink();
await getChatLog(globalStartTimeString, globalEndTimeString);
document.getElementById("chat-replay").innerHTML = "";
renderChatLog();
} }
function setCurrentRangeStartToVideoTime() { function setCurrentRangeStartToVideoTime() {
@ -1591,13 +1577,6 @@ function changeEnableChaptersHandler() {
} }
} }
async function loadEditorChatData() {
if (!globalStartTimeString || !globalEndTimeString) {
return [];
}
return getChatLog(globalStartTimeString, globalEndTimeString);
}
function renderChatLog() { function renderChatLog() {
const chatReplayParent = document.getElementById("chat-replay"); const chatReplayParent = document.getElementById("chat-replay");
chatReplayParent.innerHTML = ""; chatReplayParent.innerHTML = "";

@ -8,6 +8,10 @@ var globalChatPreviousRenderTime = null;
window.addEventListener("DOMContentLoaded", async (event) => { window.addEventListener("DOMContentLoaded", async (event) => {
commonPageSetup(); commonPageSetup();
globalLoadChatWorker.onmessage = (event) => {
updateChatDataFromWorkerResponse(event.data);
initialChatRender();
};
const queryParams = new URLSearchParams(window.location.search); const queryParams = new URLSearchParams(window.location.search);
if (queryParams.has("start")) { if (queryParams.has("start")) {
@ -124,8 +128,6 @@ async function updateTimeSettings() {
queryParts.push(`end=${wubloaderTimeFromDateTime(endTime)}`); queryParts.push(`end=${wubloaderTimeFromDateTime(endTime)}`);
} }
document.getElementById("stream-time-link").href = `?${queryParts.join("&")}`; document.getElementById("stream-time-link").href = `?${queryParts.join("&")}`;
await getStreamChatLog();
} }
function generateDownloadURL(startTime, endTime, downloadType, allowHoles, quality) { function generateDownloadURL(startTime, endTime, downloadType, allowHoles, quality) {
@ -230,15 +232,6 @@ function convertEnteredTimes() {
} }
} }
async function getStreamChatLog() {
const startTime = getStartTime();
const endTime = getEndTime();
if (!startTime || !endTime) {
return;
}
return getChatLog(wubloaderTimeFromDateTime(startTime), wubloaderTimeFromDateTime(endTime));
}
function initialChatRender() { function initialChatRender() {
if (!globalChatData || globalChatData.length === 0) { if (!globalChatData || globalChatData.length === 0) {
return; return;
@ -261,7 +254,7 @@ function initialChatRender() {
} }
function updateChatRender() { function updateChatRender() {
if (!globalChatData || globalChatData === 0) { if (!globalChatData || globalChatData.length === 0) {
return; return;
} }
if (!hasSegmentList()) { if (!hasSegmentList()) {

Loading…
Cancel
Save