diff --git a/src/client.js b/src/client.js index e903d8d..f1ef897 100644 --- a/src/client.js +++ b/src/client.js @@ -20,93 +20,88 @@ let correction = 0 // Connection opened socket.addEventListener('open', function (event) { - console.log('Connected to WS Server'); + console.log('Connected to WS Server'); }); // Got message from the server socket.addEventListener("message", (event) => { - 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); + 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; + 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'); - } - 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 under_estimate is ${under_estimate}`, "color:green"); + console.log(`%c New correction time is ${correction} miliseconds`, 'color:red; font-size:12px'); + } + 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'); - } - if (event.data.startsWith("state_update_from_server")) - { - let state = JSON.parse(event.data.slice("state_update_from_server".length + 1)); + 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'); + } + 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; - } + // 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) + // 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 - } - } - } + 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 + console.log('Disconnected from the WS Server'); + client_uid = null }); -function state_change_handler(event) -{ - if (event !== null && event !== undefined){ +function state_change_handler(event) { + if (event !== null && event !== undefined) { if (event.type === 'pause') video_playing = false; else if (event.type === 'play') video_playing = true; } - last_updated = get_global_time(correction); + last_updated = get_global_time(correction); state_image = { video_timestamp: vid.currentTime, last_updated: last_updated, playing: video_playing, global_timestamp: get_global_time(correction), - client_uid: client_uid + client_uid: client_uid } - socket.send(`state_update_from_client ${JSON.stringify(state_image)}`) + socket.send(`state_update_from_client ${JSON.stringify(state_image)}`) } // assigning event handlers @@ -130,17 +125,21 @@ function get_global_time(delta = 0) { } async function get_settings() { - let s = null; - await fetch('settings.json') - .then((response)=>response.json()) - .then((responseJson)=>{s = responseJson}); - return s; + let s = null; + await fetch('settings.json') + .then((response) => response.json()) + .then((responseJson) => { + s = responseJson + }); + return s; } function median(values) { - if(values.length === 0) { return 0; } + if (values.length === 0) { + return 0; + } - values.sort((x,y) => (x-y)); + values.sort((x, y) => (x - y)); let half = Math.floor(values.length / 2); if (values.length % 2) { return values[half]; @@ -149,21 +148,22 @@ function median(values) { } function timeout(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise(resolve => setTimeout(resolve, ms)); } function do_time_sync_one_cycle_backward() { - socket.send("time_sync_request_backward"); + socket.send("time_sync_request_backward"); } + function do_time_sync_one_cycle_forward() { - socket.send(`time_sync_request_forward ${get_global_time()}`); + socket.send(`time_sync_request_forward ${get_global_time()}`); } // time requests are made every second async function do_time_sync() { - for(let i = 0; i < num_time_sync_cycles; i++){ + for (let i = 0; i < num_time_sync_cycles; i++) { await timeout(500); do_time_sync_one_cycle_backward(); await timeout(500); diff --git a/src/index.js b/src/index.js index fab080a..f64cc4d 100644 --- a/src/index.js +++ b/src/index.js @@ -6,140 +6,142 @@ const WebSocket = require('ws'); const app = express(); const server = require('http').createServer(app); -const wss = new WebSocket.Server({ server:server }); +const wss = new WebSocket.Server({ + server: server +}); const settings = JSON.parse(fs.readFileSync("settings.json")); const THRESH_IGNORANCE = 250; let users_amount = 0; let unique_id = 0; let state = { - video_timestamp: 0, - last_updated: get_time(), - playing: false, - global_timestamp: 0, - client_uid: null + video_timestamp: 0, + last_updated: get_time(), + playing: false, + global_timestamp: 0, + client_uid: null }; wss.on('connection', function connection(ws) { - users_amount += 1; - console.log('A new client Connected. Amount of users: ', users_amount); - state.client_uid = unique_id; - unique_id +=1 ; - ws.send(`state_update_from_server ${JSON.stringify(state)}`); + users_amount += 1; + console.log('A new client Connected. Amount of users: ', users_amount); + state.client_uid = unique_id; + unique_id += 1; + ws.send(`state_update_from_server ${JSON.stringify(state)}`); - ws.on('error', console.error); + ws.on('error', console.error); - ws.on('message', function message(data) { - data = data.toString(); + ws.on('message', function message(data) { + data = data.toString(); - if(data.startsWith("time_sync_request_backward")) - { - ws.send(`time_sync_response_backward ${get_time()}`); - } - if(data.startsWith("time_sync_request_forward")) - { - let client_time = Number(data.slice("time_sync_request_forward".length + 1)); - ws.send(`time_sync_response_forward ${get_time() - client_time}`); - } - if(data.startsWith("state_update_from_client")) - { - let new_state = JSON.parse(data.slice("state_update_from_client".length + 1)); - let too_soon = (get_time() - state.last_updated) < THRESH_IGNORANCE; - let other_ip = (new_state.client_uid != state.client_uid); - let stale = (new_state.last_updated < state.last_updated) - - if (!stale && !(too_soon && other_ip)) - { - state = new_state; - - wss.clients.forEach(function each(client) { - if (client !== ws && client.readyState === WebSocket.OPEN) { - client.send(`state_update_from_server ${JSON.stringify(state)}`); - } - }); - } - } - }); - - ws.on('close', function close() { - users_amount -= 1; - console.log('Client diconnected. Amount of users: ', users_amount); - }); + if (data.startsWith("time_sync_request_backward")) { + ws.send(`time_sync_response_backward ${get_time()}`); + } + if (data.startsWith("time_sync_request_forward")) { + let client_time = Number(data.slice("time_sync_request_forward".length + 1)); + ws.send(`time_sync_response_forward ${get_time() - client_time}`); + } + if (data.startsWith("state_update_from_client")) { + let new_state = JSON.parse(data.slice("state_update_from_client".length + 1)); + let too_soon = (get_time() - state.last_updated) < THRESH_IGNORANCE; + let other_ip = (new_state.client_uid != state.client_uid); + let stale = (new_state.last_updated < state.last_updated) + + if (!stale && !(too_soon && other_ip)) { + state = new_state; + + wss.clients.forEach(function each(client) { + if (client !== ws && client.readyState === WebSocket.OPEN) { + client.send(`state_update_from_server ${JSON.stringify(state)}`); + } + }); + } + } + }); + + ws.on('close', function close() { + users_amount -= 1; + console.log('Client diconnected. Amount of users: ', users_amount); + }); }); app.use('/', express.static(__dirname)); -app.use(bodyParser.urlencoded({ extended: true })); +app.use(bodyParser.urlencoded({ + extended: true +})); app.use(bodyParser.json()); app.use(session({ - secret: 'secret key', - resave: false, - saveUninitialized: false, - logged: false + secret: 'secret key', + resave: false, + saveUninitialized: false, + logged: false })); app.get("/", function (req, res) { - if (req.session.logged) - res.sendFile(__dirname + "/main.html"); - else - res.sendFile(__dirname + "/login.html"); + if (req.session.logged) + res.sendFile(__dirname + "/main.html"); + else + res.sendFile(__dirname + "/login.html"); }); -app.post("/login", function (req, res) -{ - const data = req.body; - if(!data) - res.sendStatus(400); +app.post("/login", function (req, res) { + const data = req.body; + if (!data) + res.sendStatus(400); - if(data.password == settings.password) - req.session.logged = true; - else - req.session.logged = false; + if (data.password == settings.password) + req.session.logged = true; + else + req.session.logged = false; - res.redirect("/"); + res.redirect("/"); }); app.get("/video", function (req, res) { - // Ensure there is a range given for the video - const range = req.headers.range; - if (!range) { - res.status(400).send("Requires Range header"); - } - - // get video stats (about 61MB) - const videoPath = settings.video_path; - const videoSize = fs.statSync(videoPath).size; - - // Parse Range - // Example: "bytes=32324-" - const CHUNK_SIZE = 10 ** 6; // 1MB - const start = Number(range.replace(/\D/g, "")); - const end = Math.min(start + CHUNK_SIZE, videoSize - 1); - - // Create headers - const contentLength = end - start + 1; - const headers = { - "Content-Range": `bytes ${start}-${end}/${videoSize}`, - "Accept-Ranges": "bytes", - "Content-Length": contentLength, - "Content-Type": "video/mp4", - }; - - // HTTP Status 206 for Partial Content - res.writeHead(206, headers); - - // create video read stream for this particular chunk - const videoStream = fs.createReadStream(videoPath, { start, end }); - - // Stream the video chunk to the client - videoStream.pipe(res); + // Ensure there is a range given for the video + const range = req.headers.range; + if (!range) { + res.status(400).send("Requires Range header"); + } + + // get video stats (about 61MB) + const videoPath = settings.video_path; + const videoSize = fs.statSync(videoPath).size; + + // Parse Range + // Example: "bytes=32324-" + const CHUNK_SIZE = 10 ** 6; // 1MB + const start = Number(range.replace(/\D/g, "")); + const end = Math.min(start + CHUNK_SIZE, videoSize - 1); + + // Create headers + const contentLength = end - start + 1; + const headers = { + "Content-Range": `bytes ${start}-${end}/${videoSize}`, + "Accept-Ranges": "bytes", + "Content-Length": contentLength, + "Content-Type": "video/mp4", + }; + + // HTTP Status 206 for Partial Content + res.writeHead(206, headers); + + // create video read stream for this particular chunk + const videoStream = fs.createReadStream(videoPath, { + start, + end + }); + + // Stream the video chunk to the client + videoStream.pipe(res); }); -server.listen(settings.server_port, settings.server_ip, - () => console.log(`Server started at ${settings.server_ip}:${settings.server_port}`)); +server.listen(settings.server_port, settings.server_ip, + () => console.log(`Server started at ${settings.server_ip}:${settings.server_port}`)); -function get_time(){ +function get_time() { let d = new Date(); return d.getTime(); } \ No newline at end of file diff --git a/src/login.html b/src/login.html index 3b66bab..7b4f859 100644 --- a/src/login.html +++ b/src/login.html @@ -1,21 +1,21 @@ - - - - - Login Page - - -
-

Login To Watch-Together Server

-
- -
-
- + + + + + Login Page + + +
+

Login To Watch-Together Server

+
+ +
+
+ diff --git a/src/main.html b/src/main.html index bbd43fe..001f4e6 100644 --- a/src/main.html +++ b/src/main.html @@ -1,22 +1,22 @@ - - - - - - Client - - -
-
-

Watch-Together

-
-
- -
-
- + + + + + + Client + + +
+
+

Watch-Together

+
+
+ +
+
+