diff --git a/src/client.js b/src/client.js index 713df0e..e04d726 100644 --- a/src/client.js +++ b/src/client.js @@ -1,6 +1,106 @@ const vid = document.querySelector('video'); const wsProto = window.location.protocol === 'https:' ? 'wss' : 'ws'; -const socket = new WebSocket(`${wsProto}://${window.location.hostname}:${window.location.port}`); +let socket = null; // Initialize socket as null +let retryInterval = 1000; // Initial retry interval in milliseconds +const maxRetryInterval = 30000; // Maximum retry interval + +function createWebSocket() { + socket = new WebSocket(`${wsProto}://${window.location.hostname}:${window.location.port}`); + + // Connection opened + socket.addEventListener('open', function (event) { + console.log('Connected to WS Server'); + retryInterval = 1000; // Reset retry interval on successful connection + }); + + // Got message from the server + socket.addEventListener("message", (event) => { + + // Reload the page if the server sends a reload request + if (event.data.startsWith("reload_request")) { + console.log("Reloading the page as requested by the server"); + setTimeout(() => window.location.reload(), 1000); + return; + } + + // Time syncing backward server-response + if (event.data.startsWith("time_sync_response_backward")) { + let time_at_server = Number(event.data.slice("time_sync_response_backward".length + 1)); + let under_estimate_latest = time_at_server - get_global_time(0); + + under_estimates.push(under_estimate_latest); + under_estimate = median(under_estimates); + correction = (under_estimate + over_estimate) / 2; + + console.log(`%c Updated val for under_estimate is ${under_estimate}`, "color:green"); + console.log(`%c New correction time is ${correction} miliseconds`, 'color:red; font-size:12px'); + } + + // Time syncing forward server-response + if (event.data.startsWith("time_sync_response_forward")) { + let calculated_diff = Number(event.data.slice("time_sync_response_forward".length + 1)); + over_estimates.push(calculated_diff); + over_estimate = median(over_estimates); + correction = (under_estimate + over_estimate) / 2; + + console.log(`%c Updated val for over_estimate is ${over_estimate}`, "color:green"); + console.log(`%c New correction time is ${correction} miliseconds`, 'color:red; font-size:12px'); + } + + // Video state update from server + if (event.data.startsWith("state_update_from_server")) { + let state = JSON.parse(event.data.slice("state_update_from_server".length + 1)); + + // Whenever the client connects or reconnects + if (client_uid == null) { + client_uid = state.client_uid; + } + + // calculating the new timestamp for both cases - when the video is playing and when it is paused + let proposed_time = (state.playing) ? ((get_global_time(correction) - state.global_timestamp) / 1000 + state.video_timestamp) : (state.video_timestamp); + let gap = Math.abs(proposed_time - vid.currentTime); + + console.log(`%cGap was ${proposed_time - vid.currentTime}`, 'font-size:12px; color:purple'); + if (state.playing) { + // tolerance while the video is playing + if (gap > PLAYING_THRESH) { + vid.currentTime = proposed_time; + } + vid.play() + } else { + vid.pause() + // condition to prevent an unnecessary seek + if (gap > PAUSED_THRESH) { + vid.currentTime = proposed_time; + } + } + } + }); + + // Connection closed + socket.addEventListener('close', function (event) { + console.log('Disconnected from the WS Server'); + client_uid = null; + retryWebSocketConnection(); // Attempt to reconnect + }); + + // Connection error + socket.addEventListener('error', function (event) { + console.error('WebSocket error:', event); + socket.close(); // Ensure the socket is closed on error + }); +} + +function retryWebSocketConnection() { + console.log(`Attempting to reconnect in ${retryInterval / 1000} seconds...`); + setTimeout(() => { + retryInterval = Math.min(retryInterval * 2, maxRetryInterval); // Exponential backoff + createWebSocket(); // Recreate the WebSocket connection + }, retryInterval); +} + +// Initial WebSocket connection +createWebSocket(); // if the new tms is within this margin of the current tms, then the change is ignored for smoother viewing const PLAYING_THRESH = 1; @@ -19,78 +119,6 @@ let over_estimate = 0; let under_estimate = 0; let correction = 0; - -// Connection opened -socket.addEventListener('open', function (event) { - console.log('Connected to WS Server'); -}); - -// Got message from the server -socket.addEventListener("message", (event) => { - - // Time syncing backward server-response - if (event.data.startsWith("time_sync_response_backward")) { - let time_at_server = Number(event.data.slice("time_sync_response_backward".length + 1)); - let under_estimate_latest = time_at_server - get_global_time(0); - - under_estimates.push(under_estimate_latest); - under_estimate = median(under_estimates); - correction = (under_estimate + over_estimate) / 2; - - console.log(`%c Updated val for under_estimate is ${under_estimate}`, "color:green"); - console.log(`%c New correction time is ${correction} miliseconds`, 'color:red; font-size:12px'); - } - - // Time syncing forward server-response - if (event.data.startsWith("time_sync_response_forward")) { - let calculated_diff = Number(event.data.slice("time_sync_response_forward".length + 1)); - over_estimates.push(calculated_diff); - over_estimate = median(over_estimates); - correction = (under_estimate + over_estimate) / 2; - - console.log(`%c Updated val for over_estimate is ${over_estimate}`, "color:green"); - console.log(`%c New correction time is ${correction} miliseconds`, 'color:red; font-size:12px'); - } - - // Video state update from server - if (event.data.startsWith("state_update_from_server")) { - let state = JSON.parse(event.data.slice("state_update_from_server".length + 1)); - - // Whenever the client connects or reconnects - if (client_uid == null) { - client_uid = state.client_uid; - } - - // calculating the new timestamp for both cases - when the video is playing and when it is paused - let proposed_time = (state.playing) ? ((get_global_time(correction) - state.global_timestamp) / 1000 + state.video_timestamp) : (state.video_timestamp); - let gap = Math.abs(proposed_time - vid.currentTime); - - console.log(`%cGap was ${proposed_time - vid.currentTime}`, 'font-size:12px; color:purple'); - if (state.playing) { - // tolerance while the video is playing - if (gap > PLAYING_THRESH) { - vid.currentTime = proposed_time; - } - vid.play() - } else { - vid.pause() - // condition to prevent an unnecessary seek - if (gap > PAUSED_THRESH) { - vid.currentTime = proposed_time; - } - } - } -}); - - -// Connection closed -socket.addEventListener('close', function (event) { - console.log('Disconnected from the WS Server'); - client_uid = null; - setTimeout(() => window.location.reload(), 1000); -}); - - // Send video state update to the server // event: the video event (ex: seeking, pause play) function state_change_handler(event) { @@ -111,7 +139,9 @@ function state_change_handler(event) { client_uid: client_uid }; - socket.send(`state_update_from_client ${JSON.stringify(state_image)}`); + if (socket.readyState === WebSocket.OPEN) { + socket.send(`state_update_from_client ${JSON.stringify(state_image)}`); + } } // assigning event handlers @@ -213,3 +243,4 @@ document.addEventListener('DOMContentLoaded', function() { style.textContent = `::cue { font-size: ${size} !important; }`; }); }); + diff --git a/src/index.js b/src/index.js index 6ee0ae1..48f28a4 100644 --- a/src/index.js +++ b/src/index.js @@ -187,7 +187,10 @@ server.listen(settings.server_port, settings.server_ip, process.on('SIGINT', () => { console.log("Shutting down server..."); // wss.close(); - wss.clients.forEach(client => client.terminate()); + wss.clients.forEach(client => { + client.send("reload_request"); + client.terminate(); + }); server.close(() => { console.log("Server closed"); process.exit(0);