forked from lavender/watch-party
		
	Prepare for adding the chat box
This commit is contained in:
		
							parent
							
								
									7796d8e5f0
								
							
						
					
					
						commit
						d48771e921
					
				
					 7 changed files with 123 additions and 28 deletions
				
			
		|  | @ -16,6 +16,14 @@ | |||
|       <form id="join-session-form"> | ||||
|         <h2>Join a session</h2> | ||||
| 
 | ||||
|         <label for="nickname">Nickname:</label> | ||||
|         <input | ||||
|           type="text" | ||||
|           id="join-session-nickname" | ||||
|           placeholder="Nickname" | ||||
|           required | ||||
|         /> | ||||
| 
 | ||||
|         <label for="session-id">Session ID:</label> | ||||
|         <input | ||||
|           type="text" | ||||
|  | @ -31,6 +39,9 @@ | |||
|       </p> | ||||
|     </div> | ||||
| 
 | ||||
|     <div id="video-container"></div> | ||||
|     <div id="chatbox-container"></div> | ||||
| 
 | ||||
|     <script src="/main.js"></script> | ||||
|   </body> | ||||
| </html> | ||||
|  |  | |||
|  | @ -3,8 +3,6 @@ | |||
|  * @param {{name: string, url: string}[]} subtitles | ||||
|  */ | ||||
| const createVideoElement = (videoUrl, subtitles) => { | ||||
|   document.querySelector("#pre-join-controls").style["display"] = "none"; | ||||
| 
 | ||||
|   const video = document.createElement("video"); | ||||
|   video.controls = true; | ||||
|   video.autoplay = false; | ||||
|  | @ -35,6 +33,19 @@ const createVideoElement = (videoUrl, subtitles) => { | |||
| let outgoingDebounce = false; | ||||
| let outgoingDebounceCallbackId = null; | ||||
| 
 | ||||
| const setDebounce = () => { | ||||
|   outgoingDebounce = true; | ||||
| 
 | ||||
|   if (outgoingDebounceCallbackId) { | ||||
|     cancelIdleCallback(outgoingDebounceCallbackId); | ||||
|     outgoingDebounceCallbackId = null; | ||||
|   } | ||||
| 
 | ||||
|   outgoingDebounceCallbackId = setTimeout(() => { | ||||
|     outgoingDebounce = false; | ||||
|   }, 500); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * @param {WebSocket} socket | ||||
|  * @param {HTMLVideoElement} video | ||||
|  | @ -53,10 +64,10 @@ const setupSocketEvents = (socket, video) => { | |||
|       const event = JSON.parse(messageEvent.data); | ||||
|       console.log(event); | ||||
| 
 | ||||
|       outgoingDebounce = true; | ||||
| 
 | ||||
|       switch (event.op) { | ||||
|         case "SetPlaying": | ||||
|           setDebounce(); | ||||
| 
 | ||||
|           if (event.data.playing) { | ||||
|             await video.play(); | ||||
|           } else { | ||||
|  | @ -67,21 +78,15 @@ const setupSocketEvents = (socket, video) => { | |||
| 
 | ||||
|           break; | ||||
|         case "SetTime": | ||||
|           setDebounce(); | ||||
|           setVideoTime(event.data); | ||||
| 
 | ||||
|           break; | ||||
| 
 | ||||
|         // TODO: UserJoin, UserLeave, ChatMessage
 | ||||
|       } | ||||
|     } catch (_err) { | ||||
|     } | ||||
| 
 | ||||
|     if (outgoingDebounceCallbackId) { | ||||
|       cancelIdleCallback(outgoingDebounceCallbackId); | ||||
|       outgoingDebounceCallbackId = null; | ||||
|     } | ||||
| 
 | ||||
|     outgoingDebounceCallbackId = setTimeout(() => { | ||||
|       outgoingDebounce = false; | ||||
|     }, 500); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
|  | @ -148,8 +153,9 @@ const setupVideoEvents = (sessionId, video, socket) => { | |||
|  * @param {WebSocket} socket | ||||
|  */ | ||||
| const setupVideo = async (sessionId, videoUrl, subtitles, currentTime, playing, socket) => { | ||||
|   document.querySelector("#pre-join-controls").style["display"] = "none"; | ||||
|   const video = createVideoElement(videoUrl, subtitles); | ||||
|   document.body.appendChild(video); | ||||
|   document.querySelector("#video-container").appendChild(video); | ||||
| 
 | ||||
|   video.currentTime = (currentTime / 1000.0); | ||||
| 
 | ||||
|  | @ -167,8 +173,20 @@ const setupVideo = async (sessionId, videoUrl, subtitles, currentTime, playing, | |||
|   setupVideoEvents(sessionId, video, socket); | ||||
| } | ||||
| 
 | ||||
| /** @param {string} sessionId */ | ||||
| const joinSession = async (sessionId) => { | ||||
| /** | ||||
|  * @param {string} sessionId | ||||
|  * @param {WebSocket} socket | ||||
|  */ | ||||
| const setupChat = async (sessionId, socket) => { | ||||
|   document.querySelector("#chatbox-container").style["display"] = "initial"; | ||||
|   // TODO
 | ||||
| } | ||||
| 
 | ||||
| /**  | ||||
|  * @param {string} nickname | ||||
|  * @param {string} sessionId | ||||
|  */ | ||||
| const joinSession = async (nickname, sessionId) => { | ||||
|   try { | ||||
|     window.location.hash = sessionId; | ||||
| 
 | ||||
|  | @ -177,11 +195,14 @@ const joinSession = async (sessionId) => { | |||
|       current_time_ms, is_playing | ||||
|     } = await fetch(`/sess/${sessionId}`).then(r => r.json()); | ||||
| 
 | ||||
|     const wsUrl = new URL(`/sess/${sessionId}/subscribe`, window.location.href); | ||||
|     const wsUrl = new URL(`/sess/${sessionId}/subscribe?nickname=${encodeURIComponent(nickname)}`, window.location.href); | ||||
|     wsUrl.protocol = { "http:": "ws:", "https:": "wss:" }[wsUrl.protocol]; | ||||
|     const socket = new WebSocket(wsUrl.toString()); | ||||
| 
 | ||||
|     socket.addEventListener("open", () => { | ||||
|       setupVideo(sessionId, video_url, subtitle_tracks, current_time_ms, is_playing, socket); | ||||
|       setupChat(sessionId, socket); | ||||
|     }); | ||||
|   } catch (err) { | ||||
|     // TODO: Show an error on the screen
 | ||||
|     console.error(err); | ||||
|  | @ -189,11 +210,16 @@ const joinSession = async (sessionId) => { | |||
| } | ||||
| 
 | ||||
| const main = () => { | ||||
|   document.querySelector("#join-session-nickname").value = localStorage.getItem("watch-party-nickname"); | ||||
| 
 | ||||
|   document.querySelector("#join-session-form").addEventListener("submit", event => { | ||||
|     event.preventDefault(); | ||||
| 
 | ||||
|     const nickname = document.querySelector("#join-session-nickname").value; | ||||
|     const sessionId = document.querySelector("#join-session-id").value; | ||||
|     joinSession(sessionId); | ||||
| 
 | ||||
|     localStorage.setItem("watch-party-nickname", nickname); | ||||
|     joinSession(nickname, sessionId); | ||||
|   }); | ||||
| 
 | ||||
|   if (window.location.hash.match(/#[0-9a-f\-]+/)) { | ||||
|  |  | |||
|  | @ -9,6 +9,15 @@ html { | |||
|   color: var(--fg); | ||||
|   font-size: 1.125rem; | ||||
|   font-family: sans-serif; | ||||
| 
 | ||||
|   overflow-y: scroll; | ||||
|   scrollbar-width: none; | ||||
|   -ms-overflow-style: none; | ||||
| } | ||||
| 
 | ||||
| ::-webkit-scrollbar { | ||||
|   width: 0; | ||||
|   background: transparent; | ||||
| } | ||||
| 
 | ||||
| html, | ||||
|  | @ -17,8 +26,13 @@ body { | |||
| } | ||||
| 
 | ||||
| video { | ||||
|   display: block; | ||||
| 
 | ||||
|   width: 100vw; | ||||
|   height: auto; | ||||
| 
 | ||||
|   max-width: auto; | ||||
|   max-height: 100vh; | ||||
| } | ||||
| 
 | ||||
| a { | ||||
|  | @ -99,3 +113,7 @@ button.small-button { | |||
| #join-session-form { | ||||
|   margin-bottom: 4em; | ||||
| } | ||||
| 
 | ||||
| #chatbox-container { | ||||
|   display: none; | ||||
| } | ||||
|  |  | |||
|  | @ -3,6 +3,17 @@ use serde::{Deserialize, Serialize}; | |||
| #[derive(Clone, Serialize, Deserialize)] | ||||
| #[serde(tag = "op", content = "data")] | ||||
| pub enum WatchEvent { | ||||
|     SetPlaying { playing: bool, time: u64 }, | ||||
|     SetPlaying { | ||||
|         playing: bool, | ||||
|         time: u64, | ||||
|     }, | ||||
|     SetTime(u64), | ||||
| 
 | ||||
|     UserJoin(String), | ||||
|     UserLeave(String), | ||||
|     ChatMessage { | ||||
|         #[serde(default = "String::new")] | ||||
|         user: String, | ||||
|         message: String, | ||||
|     }, | ||||
| } | ||||
|  |  | |||
							
								
								
									
										18
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/main.rs
									
									
									
									
									
								
							|  | @ -19,9 +19,14 @@ use crate::{ | |||
| 
 | ||||
| #[derive(Deserialize)] | ||||
| struct StartSessionBody { | ||||
|     pub video_url: String, | ||||
|     video_url: String, | ||||
|     #[serde(default = "Vec::new")] | ||||
|     pub subtitle_tracks: Vec<SubtitleTrack>, | ||||
|     subtitle_tracks: Vec<SubtitleTrack>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
| struct SubscribeQuery { | ||||
|     nickname: String, | ||||
| } | ||||
| 
 | ||||
| #[tokio::main] | ||||
|  | @ -112,12 +117,13 @@ async fn main() { | |||
|         ); | ||||
| 
 | ||||
|     let ws_subscribe_route = get_running_session | ||||
|         .and(warp::path!("subscribe")) | ||||
|         .and(warp::ws()) | ||||
|         .and(warb::path!("subscribe")) | ||||
|         .and(warb::query()) | ||||
|         .and(warb::ws()) | ||||
|         .map( | ||||
|             |requested_session, ws: warb::ws::Ws| match requested_session { | ||||
|             |requested_session, query: SubscribeQuery, ws: warb::ws::Ws| match requested_session { | ||||
|                 RequestedSession::Session(uuid, _) => ws | ||||
|                     .on_upgrade(move |ws| ws_subscribe(uuid, ws)) | ||||
|                     .on_upgrade(move |ws| ws_subscribe(uuid, query.nickname, ws)) | ||||
|                     .into_response(), | ||||
|                 RequestedSession::Error(error_response) => error_response.into_response(), | ||||
|             }, | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ pub struct ConnectedViewer { | |||
|     pub tx: UnboundedSender<WatchEvent>, | ||||
| } | ||||
| 
 | ||||
| pub async fn ws_subscribe(session_uuid: Uuid, ws: WebSocket) { | ||||
| pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, ws: WebSocket) { | ||||
|     let viewer_id = NEXT_VIEWER_ID.fetch_add(1, Ordering::Relaxed); | ||||
|     let (mut viewer_ws_tx, mut viewer_ws_rx) = ws.split(); | ||||
| 
 | ||||
|  | @ -56,8 +56,10 @@ pub async fn ws_subscribe(session_uuid: Uuid, ws: WebSocket) { | |||
|         }, | ||||
|     ); | ||||
| 
 | ||||
|     ws_publish(session_uuid, None, WatchEvent::UserJoin(nickname.clone())).await; | ||||
| 
 | ||||
|     while let Some(Ok(message)) = viewer_ws_rx.next().await { | ||||
|         let event: WatchEvent = match message | ||||
|         let mut event: WatchEvent = match message | ||||
|             .to_str() | ||||
|             .ok() | ||||
|             .and_then(|s| serde_json::from_str(s).ok()) | ||||
|  | @ -66,6 +68,23 @@ pub async fn ws_subscribe(session_uuid: Uuid, ws: WebSocket) { | |||
|             None => continue, | ||||
|         }; | ||||
| 
 | ||||
|         // Make sure people don't spoof their nicknames to pretend to be others
 | ||||
|         // If a nickname change is required, I guess reconnect idk
 | ||||
|         if let WatchEvent::ChatMessage { user: _, message } = event { | ||||
|             event = WatchEvent::ChatMessage { | ||||
|                 user: nickname.clone(), | ||||
|                 message, | ||||
|             }; | ||||
| 
 | ||||
|             // Don't pass through the viewer_id because we want the chat message
 | ||||
|             // to be reflected to the user.
 | ||||
|             ws_publish(session_uuid, None, event).await; | ||||
| 
 | ||||
|             // We don't need to handle() chat messages,
 | ||||
|             // and we are already publishing them ourselves.
 | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         handle_watch_event( | ||||
|             session_uuid, | ||||
|             &mut get_session(session_uuid).unwrap(), | ||||
|  | @ -75,6 +94,8 @@ pub async fn ws_subscribe(session_uuid: Uuid, ws: WebSocket) { | |||
|         ws_publish(session_uuid, Some(viewer_id), event).await; | ||||
|     } | ||||
| 
 | ||||
|     ws_publish(session_uuid, None, WatchEvent::UserLeave(nickname.clone())).await; | ||||
| 
 | ||||
|     CONNECTED_VIEWERS.write().await.remove(&viewer_id); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -84,6 +84,8 @@ pub fn handle_watch_event(uuid: Uuid, watch_session: &mut WatchSession, event: W | |||
|         WatchEvent::SetTime(time) => { | ||||
|             watch_session.set_time_ms(time); | ||||
|         } | ||||
| 
 | ||||
|         _ => {} | ||||
|     }; | ||||
| 
 | ||||
|     let _ = SESSIONS.lock().unwrap().insert(uuid, watch_session.clone()); | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue