2022-02-18 11:24:29 -08:00
|
|
|
import { setupVideo } from "./video.mjs?v=048af96";
|
2022-02-15 21:53:54 -08:00
|
|
|
import {
|
|
|
|
setupChat,
|
|
|
|
logEventToChat,
|
|
|
|
updateViewerList,
|
2022-03-30 04:17:44 -07:00
|
|
|
printChatMessage,
|
2022-02-18 11:24:29 -08:00
|
|
|
} from "./chat.mjs?v=048af96";
|
2022-02-15 16:30:22 -08:00
|
|
|
import ReconnectingWebSocket from "./reconnecting-web-socket.mjs";
|
2021-11-09 05:21:14 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {string} sessionId
|
|
|
|
* @param {string} nickname
|
2022-02-15 14:19:48 -08:00
|
|
|
* @returns {ReconnectingWebSocket}
|
2021-11-09 05:21:14 -08:00
|
|
|
*/
|
2022-01-18 03:42:55 -08:00
|
|
|
const createWebSocket = (sessionId, nickname, colour) => {
|
2021-11-09 05:21:14 -08:00
|
|
|
const wsUrl = new URL(
|
|
|
|
`/sess/${sessionId}/subscribe` +
|
2022-01-18 03:42:55 -08:00
|
|
|
`?nickname=${encodeURIComponent(nickname)}` +
|
|
|
|
`&colour=${encodeURIComponent(colour)}`,
|
2021-11-09 05:21:14 -08:00
|
|
|
window.location.href
|
|
|
|
);
|
2022-02-15 14:19:48 -08:00
|
|
|
wsUrl.protocol = "ws" + window.location.protocol.slice(4);
|
|
|
|
const socket = new ReconnectingWebSocket(wsUrl);
|
2021-11-09 05:21:14 -08:00
|
|
|
|
|
|
|
return socket;
|
|
|
|
};
|
|
|
|
|
|
|
|
let outgoingDebounce = false;
|
|
|
|
let outgoingDebounceCallbackId = null;
|
|
|
|
|
2022-02-14 06:30:42 -08:00
|
|
|
export const setDebounce = () => {
|
2021-11-09 05:21:14 -08:00
|
|
|
outgoingDebounce = true;
|
|
|
|
|
|
|
|
if (outgoingDebounceCallbackId) {
|
|
|
|
cancelIdleCallback(outgoingDebounceCallbackId);
|
|
|
|
outgoingDebounceCallbackId = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
outgoingDebounceCallbackId = setTimeout(() => {
|
|
|
|
outgoingDebounce = false;
|
|
|
|
}, 500);
|
|
|
|
};
|
|
|
|
|
2022-02-14 06:30:42 -08:00
|
|
|
export const setVideoTime = (time, video = null) => {
|
|
|
|
if (video == null) {
|
|
|
|
video = document.querySelector("video");
|
|
|
|
}
|
|
|
|
|
|
|
|
const timeSecs = time / 1000.0;
|
|
|
|
if (Math.abs(video.currentTime - timeSecs) > 0.5) {
|
|
|
|
video.currentTime = timeSecs;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const setPlaying = async (playing, video = null) => {
|
|
|
|
if (video == null) {
|
|
|
|
video = document.querySelector("video");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (playing) {
|
|
|
|
await video.play();
|
|
|
|
} else {
|
|
|
|
video.pause();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-11-09 05:21:14 -08:00
|
|
|
/**
|
|
|
|
* @param {HTMLVideoElement} video
|
2022-02-15 14:19:48 -08:00
|
|
|
* @param {ReconnectingWebSocket} socket
|
2021-11-09 05:21:14 -08:00
|
|
|
*/
|
|
|
|
const setupIncomingEvents = (video, socket) => {
|
|
|
|
socket.addEventListener("message", async (messageEvent) => {
|
|
|
|
try {
|
|
|
|
const event = JSON.parse(messageEvent.data);
|
2021-11-10 06:29:52 -08:00
|
|
|
if (!event.reflected) {
|
|
|
|
switch (event.op) {
|
|
|
|
case "SetPlaying":
|
|
|
|
setDebounce();
|
2021-11-09 05:21:14 -08:00
|
|
|
|
2021-11-10 06:29:52 -08:00
|
|
|
if (event.data.playing) {
|
|
|
|
await video.play();
|
|
|
|
} else {
|
|
|
|
video.pause();
|
|
|
|
}
|
2021-11-09 05:21:14 -08:00
|
|
|
|
2022-02-14 06:30:42 -08:00
|
|
|
setVideoTime(event.data.time, video);
|
2021-11-10 06:29:52 -08:00
|
|
|
break;
|
|
|
|
case "SetTime":
|
|
|
|
setDebounce();
|
2022-02-14 06:30:42 -08:00
|
|
|
setVideoTime(event.data, video);
|
2021-11-10 06:29:52 -08:00
|
|
|
break;
|
2022-02-13 09:23:20 -08:00
|
|
|
case "UpdateViewerList":
|
|
|
|
updateViewerList(event.data);
|
|
|
|
break;
|
2021-11-10 06:29:52 -08:00
|
|
|
}
|
2021-11-09 05:21:14 -08:00
|
|
|
}
|
2021-11-10 06:29:52 -08:00
|
|
|
|
|
|
|
logEventToChat(event);
|
2022-01-18 03:42:55 -08:00
|
|
|
} catch (_err) {}
|
2021-11-09 05:21:14 -08:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {HTMLVideoElement} video
|
2022-02-15 14:19:48 -08:00
|
|
|
* @param {ReconnectingWebSocket} socket
|
2021-11-09 05:21:14 -08:00
|
|
|
*/
|
|
|
|
const setupOutgoingEvents = (video, socket) => {
|
|
|
|
const currentVideoTime = () => (video.currentTime * 1000) | 0;
|
|
|
|
|
|
|
|
video.addEventListener("pause", async (event) => {
|
2021-11-19 12:29:49 -08:00
|
|
|
if (outgoingDebounce || !video.controls) {
|
2021-11-09 05:21:14 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-22 17:22:19 -08:00
|
|
|
// don't send a pause event for the video ending
|
|
|
|
if (video.currentTime == video.duration) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-09 05:21:14 -08:00
|
|
|
socket.send(
|
|
|
|
JSON.stringify({
|
|
|
|
op: "SetPlaying",
|
|
|
|
data: {
|
|
|
|
playing: false,
|
|
|
|
time: currentVideoTime(),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
video.addEventListener("play", (event) => {
|
2021-11-19 12:29:49 -08:00
|
|
|
if (outgoingDebounce || !video.controls) {
|
2021-11-09 05:21:14 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
socket.send(
|
|
|
|
JSON.stringify({
|
|
|
|
op: "SetPlaying",
|
|
|
|
data: {
|
|
|
|
playing: true,
|
|
|
|
time: currentVideoTime(),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
let firstSeekComplete = false;
|
|
|
|
video.addEventListener("seeked", async (event) => {
|
|
|
|
if (!firstSeekComplete) {
|
|
|
|
// The first seeked event is performed by the browser when the video is loading
|
|
|
|
firstSeekComplete = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-11-19 12:29:49 -08:00
|
|
|
if (outgoingDebounce || !video.controls) {
|
2021-11-09 05:21:14 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
socket.send(
|
|
|
|
JSON.stringify({
|
|
|
|
op: "SetTime",
|
2022-02-13 08:58:31 -08:00
|
|
|
data: {
|
|
|
|
to: currentVideoTime(),
|
|
|
|
},
|
2021-11-09 05:21:14 -08:00
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-03-30 04:17:44 -07:00
|
|
|
let socket = null;
|
|
|
|
let video = null;
|
|
|
|
|
|
|
|
export const joinNewSession = async (sessionId) => {
|
|
|
|
const messageContent = document.createElement("span");
|
|
|
|
messageContent.appendChild(document.createTextNode("joining new session "));
|
|
|
|
messageContent.appendChild(document.createTextNode(sessionId));
|
|
|
|
|
|
|
|
printChatMessage("join-session", "watch-party", "#fffff", messageContent);
|
|
|
|
|
|
|
|
// clean up previous session
|
|
|
|
// TODO: this most likely isnt properly working yet when using the /join command to join a new session
|
|
|
|
if (socket != null) {
|
|
|
|
socket.close();
|
|
|
|
socket = null;
|
|
|
|
}
|
|
|
|
if (video != null) {
|
|
|
|
video.remove();
|
|
|
|
video = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
joinSession(window.nickname, sessionId, sColour);
|
|
|
|
};
|
|
|
|
|
2021-11-09 05:21:14 -08:00
|
|
|
/**
|
|
|
|
* @param {string} nickname
|
|
|
|
* @param {string} sessionId
|
2022-03-30 04:17:44 -07:00
|
|
|
* @param {string} colour
|
2021-11-09 05:21:14 -08:00
|
|
|
*/
|
2022-01-18 03:42:55 -08:00
|
|
|
export const joinSession = async (nickname, sessionId, colour) => {
|
2022-03-30 04:17:44 -07:00
|
|
|
// TODO: we are getting to a point with our features where some kind of
|
|
|
|
// state store for the various info that is needed in a lot of places would probably make sense
|
|
|
|
window.nickname = nickname;
|
|
|
|
window.colour = colour;
|
|
|
|
|
2022-02-15 14:19:48 -08:00
|
|
|
// try { // we are handling errors in the join form.
|
2022-02-15 16:30:22 -08:00
|
|
|
const genericConnectionError = new Error(
|
|
|
|
"There was an issue getting the session information."
|
|
|
|
);
|
2022-02-15 14:19:48 -08:00
|
|
|
window.location.hash = sessionId;
|
|
|
|
let response, video_url, subtitle_tracks, current_time_ms, is_playing;
|
2021-11-09 05:21:14 -08:00
|
|
|
try {
|
2022-02-15 14:19:48 -08:00
|
|
|
response = await fetch(`/sess/${sessionId}`);
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
throw genericConnectionError;
|
|
|
|
}
|
2022-02-15 16:30:22 -08:00
|
|
|
if (!response.ok) {
|
2022-02-15 14:19:48 -08:00
|
|
|
let error;
|
|
|
|
try {
|
|
|
|
({ error } = await response.json());
|
2022-02-15 16:30:22 -08:00
|
|
|
if (!error) throw new Error();
|
2022-02-15 14:19:48 -08:00
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
throw genericConnectionError;
|
|
|
|
}
|
2022-02-15 16:30:22 -08:00
|
|
|
throw new Error(error);
|
2022-02-15 14:19:48 -08:00
|
|
|
}
|
|
|
|
try {
|
2022-02-15 16:30:22 -08:00
|
|
|
({ video_url, subtitle_tracks, current_time_ms, is_playing } =
|
|
|
|
await response.json());
|
2022-02-15 14:19:48 -08:00
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
throw genericConnectionError;
|
2021-11-09 05:21:14 -08:00
|
|
|
}
|
2022-02-15 14:19:48 -08:00
|
|
|
|
2022-03-30 04:17:44 -07:00
|
|
|
socket = createWebSocket(sessionId, nickname, colour);
|
2022-02-15 14:19:48 -08:00
|
|
|
socket.addEventListener("open", async () => {
|
2022-03-30 04:17:44 -07:00
|
|
|
video = await setupVideo(
|
2022-02-15 14:19:48 -08:00
|
|
|
video_url,
|
|
|
|
subtitle_tracks,
|
|
|
|
current_time_ms,
|
|
|
|
is_playing
|
|
|
|
);
|
|
|
|
|
|
|
|
// By default, we should disable video controls if the video is already playing.
|
|
|
|
// This solves an issue where Safari users join and seek to 00:00:00 because of
|
|
|
|
// outgoing events.
|
|
|
|
if (current_time_ms != 0) {
|
|
|
|
video.controls = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
setupOutgoingEvents(video, socket);
|
|
|
|
setupIncomingEvents(video, socket);
|
|
|
|
setupChat(socket);
|
|
|
|
});
|
2022-02-15 16:30:22 -08:00
|
|
|
socket.addEventListener("reconnecting", (e) => {
|
2022-02-15 14:19:48 -08:00
|
|
|
console.log("Reconnecting...");
|
|
|
|
});
|
2022-02-15 16:30:22 -08:00
|
|
|
socket.addEventListener("reconnected", (e) => {
|
2022-02-15 14:19:48 -08:00
|
|
|
console.log("Reconnected.");
|
|
|
|
});
|
|
|
|
//} catch (e) {
|
|
|
|
// alert(e.message)
|
|
|
|
//}
|
2021-11-09 05:21:14 -08:00
|
|
|
};
|
2021-11-11 09:26:30 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {string} videoUrl
|
|
|
|
* @param {Array} subtitleTracks
|
|
|
|
*/
|
|
|
|
export const createSession = async (videoUrl, subtitleTracks) => {
|
|
|
|
const { id } = await fetch("/start_session", {
|
|
|
|
method: "POST",
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
body: JSON.stringify({
|
|
|
|
video_url: videoUrl,
|
|
|
|
subtitle_tracks: subtitleTracks,
|
|
|
|
}),
|
|
|
|
}).then((r) => r.json());
|
|
|
|
|
|
|
|
window.location = `/?created=true#${id}`;
|
|
|
|
};
|