let watchrooms = {};
module.exports = {
create: function(user_id, display_name, video_id) {
watchrooms[video_id] = {
// Lock to prevent multiple simultanious state updates
update_state_timeout: undefined,
play_invoked_at: undefined,
state: {
invoked_by: undefined,
paused: true,
currentTime: 0 // In seconds
},
host: user_id,
viewers: {
[user_id]: {
user_id: user_id,
display_name: display_name,
ready: false,
state: "Buffering...",
latency: 0
}
}
};
return watchrooms[video_id];
},
join: function(user_id, display_name, video_id) {
watchrooms[video_id].viewers[user_id] = {
user_id: user_id,
display_name: display_name,
ready: false,
state: "Buffering...",
latency: 0
};
},
leave: function(user_id, video_id) {
video_id = video_id || get_video_id_for_user_id(user_id);
// Gotta make sure that the watchroom exists for the video_id as well
if (video_id && watchrooms[video_id]) {
delete watchrooms[video_id].viewers[user_id];
if (!Object.keys(watchrooms[video_id].viewers).length) {
delete watchrooms[video_id];
}
}
return video_id;
},
update_user: function(user_id, video_id, state) {
return Object.assign(
watchrooms[
video_id || get_video_id_for_user_id(user_id)
].viewers[user_id],
state
);
},
get_invoke_delay: function(video_id) {
let max_latency = 0;
for (const viewer_state of Object.values(watchrooms[video_id].viewers)) {
if (viewer_state.latency > max_latency) {
max_latency = viewer_state.latency;
}
}
// Inflate by 2x to account for network fluctuations
return max_latency * 2;
},
update_video: function(video_id, state, invoke_at, oninvoked) {
//console.log("Before Update", watchrooms[video_id]);
if (watchrooms[video_id].update_state_timeout) {
return;
}
let update_state = state.invoked_by === "server";
// Can only toggle paused state if value will change.
if (Object.prototype.hasOwnProperty.call(state, "paused") && watchrooms[video_id].state.paused != state.paused) {
update_state = true;
if (state.paused) {
// Video is currently playing...
// The difference between when the video was last played and now is the duration played.
let duration_played = invoke_at - watchrooms[video_id].play_invoked_at;
// Increment the currentTime with the duration played. Subtract one
// second in order to account for positive client clock offset
watchrooms[video_id].state.currentTime += (duration_played / 1000) - 1;
if (watchrooms[video_id].state.currentTime < 0) {
watchrooms[video_id].state.currentTime = 0;
}
} else {
// Video is currently paused...
// Set the last played time so that when the video is
// paused the difference can be added to the currentTime
watchrooms[video_id].play_invoked_at = invoke_at;
}
watchrooms[video_id].state.paused = state.paused;
}
// Cannot set new current time unless video is paused.
if (Object.prototype.hasOwnProperty.call(state, "currentTime") && watchrooms[video_id].state.paused) {
update_state = true;
watchrooms[video_id].state.currentTime = Math.max(state.currentTime, 0);
}
if (update_state) {
watchrooms[video_id].state.invoked_by = state.invoked_by;
watchrooms[video_id].update_state_timeout = setTimeout(function() {
watchrooms[video_id].update_state_timeout = undefined;
oninvoked && oninvoked();
}, Math.max(invoke_at - Date.now(), 0));
//console.log("After Update", watchrooms[video_id]);
return watchrooms[video_id].state;
}
},
get: function(video_id) {
return watchrooms[video_id];
},
get_all: function() {
return watchrooms;
}
};
function get_video_id_for_user_id(user_id) {
for (const video_id of Object.keys(watchrooms)) {
if (watchrooms[video_id].viewers[user_id]) {
return video_id;
}
}
}