forked from lavender/watch-party
		
	Big changes: All events are reported to chat, new layout options
This commit is contained in:
		
							parent
							
								
									8da286fad9
								
							
						
					
					
						commit
						be5a05e0fd
					
				
					 10 changed files with 268 additions and 117 deletions
				
			
		|  | @ -47,6 +47,6 @@ | ||||||
|       </form> |       </form> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     <script type="module" src="/main.mjs?v=1"></script> |     <script type="module" src="/main.mjs?v=2"></script> | ||||||
|   </body> |   </body> | ||||||
| </html> | </html> | ||||||
|  |  | ||||||
|  | @ -15,9 +15,7 @@ const setupChatboxEvents = (socket) => { | ||||||
|       socket.send( |       socket.send( | ||||||
|         JSON.stringify({ |         JSON.stringify({ | ||||||
|           op: "ChatMessage", |           op: "ChatMessage", | ||||||
|           data: { |           data: content, | ||||||
|             message: content, |  | ||||||
|           }, |  | ||||||
|         }) |         }) | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  | @ -51,51 +49,134 @@ export const setupChat = async (socket) => { | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const printToChat = (elem) => { | const addToChat = (node) => { | ||||||
|   const chatbox = document.querySelector("#chatbox"); |   const chatbox = document.querySelector("#chatbox"); | ||||||
|   chatbox.appendChild(elem); |   chatbox.appendChild(node); | ||||||
|   chatbox.scrollTop = chatbox.scrollHeight; |   chatbox.scrollTop = chatbox.scrollHeight; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const handleChatEvent = (event) => { | let lastTimeMs = null; | ||||||
|   switch (event.op) { | let lastPlaying = false; | ||||||
|     case "UserJoin": { | 
 | ||||||
|       // print something to the chat
 | const checkDebounce = (event) => { | ||||||
|  |   let timeMs = null; | ||||||
|  |   let playing = null; | ||||||
|  |   if (event.op == "SetTime") { | ||||||
|  |     timeMs = event.data; | ||||||
|  |   } else if (event.op == "SetPlaying") { | ||||||
|  |     timeMs = event.data.time; | ||||||
|  |     playing = event.data.playing; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   let shouldIgnore = false; | ||||||
|  | 
 | ||||||
|  |   if (timeMs != null) { | ||||||
|  |     if (lastTimeMs && Math.abs(lastTimeMs - timeMs) < 500) { | ||||||
|  |       shouldIgnore = true; | ||||||
|  |     } | ||||||
|  |     lastTimeMs = timeMs; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   if (playing != null) { | ||||||
|  |     if (lastPlaying != playing) { | ||||||
|  |       shouldIgnore = false; | ||||||
|  |     } | ||||||
|  |     lastPlaying = playing; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return shouldIgnore; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * @param {string} eventType | ||||||
|  |  * @param {string?} user | ||||||
|  |  * @param {Node?} content | ||||||
|  |  */ | ||||||
|  | const printChatMessage = (eventType, user, content) => { | ||||||
|   const chatMessage = document.createElement("div"); |   const chatMessage = document.createElement("div"); | ||||||
|   chatMessage.classList.add("chat-message"); |   chatMessage.classList.add("chat-message"); | ||||||
|       chatMessage.classList.add("user-join"); |   chatMessage.classList.add(eventType); | ||||||
|       const userName = document.createElement("strong"); |  | ||||||
|       userName.textContent = event.data; |  | ||||||
|       chatMessage.appendChild(userName); |  | ||||||
|       chatMessage.appendChild(document.createTextNode(" joined")); |  | ||||||
|       printToChat(chatMessage); |  | ||||||
| 
 | 
 | ||||||
|  |   if (user != null) { | ||||||
|  |     const userName = document.createElement("strong"); | ||||||
|  |     userName.textContent = user; | ||||||
|  |     chatMessage.appendChild(userName); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   chatMessage.appendChild(document.createTextNode(" ")); | ||||||
|  | 
 | ||||||
|  |   if (content != null) { | ||||||
|  |     chatMessage.appendChild(content); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   addToChat(chatMessage); | ||||||
|  | 
 | ||||||
|  |   return chatMessage; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const formatTime = (ms) => { | ||||||
|  |   const seconds = Math.floor((ms / 1000) % 60); | ||||||
|  |   const minutes = Math.floor((ms / (60 * 1000)) % 60); | ||||||
|  |   const hours = Math.floor((ms / (3600 * 1000)) % 3600); | ||||||
|  |   return `${hours < 10 ? "0" + hours : hours}:${ | ||||||
|  |     minutes < 10 ? "0" + minutes : minutes | ||||||
|  |   }:${seconds < 10 ? "0" + seconds : seconds}`;
 | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const logEventToChat = (event) => { | ||||||
|  |   if (checkDebounce(event)) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   switch (event.op) { | ||||||
|  |     case "UserJoin": { | ||||||
|  |       printChatMessage( | ||||||
|  |         "user-join", | ||||||
|  |         event.user, | ||||||
|  |         document.createTextNode("joined") | ||||||
|  |       ); | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case "UserLeave": { |     case "UserLeave": { | ||||||
|       const chatMessage = document.createElement("div"); |       printChatMessage( | ||||||
|       chatMessage.classList.add("chat-message"); |         "user-leave", | ||||||
|       chatMessage.classList.add("user-leave"); |         event.user, | ||||||
|       const userName = document.createElement("strong"); |         document.createTextNode("left") | ||||||
|       userName.textContent = event.data; |       ); | ||||||
|       chatMessage.appendChild(userName); |  | ||||||
|       chatMessage.appendChild(document.createTextNode(" left")); |  | ||||||
|       printToChat(chatMessage); |  | ||||||
| 
 |  | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case "ChatMessage": { |     case "ChatMessage": { | ||||||
|       const chatMessage = document.createElement("div"); |  | ||||||
|       chatMessage.classList.add("chat-message"); |  | ||||||
|       const userName = document.createElement("strong"); |  | ||||||
|       userName.innerText = event.data.user; |  | ||||||
|       chatMessage.appendChild(userName); |  | ||||||
|       chatMessage.appendChild(document.createTextNode(" ")); |  | ||||||
|       const messageContent = document.createElement("span"); |       const messageContent = document.createElement("span"); | ||||||
|       messageContent.classList.add("message-content"); |       messageContent.classList.add("message-content"); | ||||||
|       messageContent.textContent = event.data.message; |       messageContent.textContent = event.data; | ||||||
|       chatMessage.appendChild(messageContent); |       printChatMessage("chat-message", event.user, messageContent); | ||||||
|       printToChat(chatMessage); |       break; | ||||||
|  |     } | ||||||
|  |     case "SetTime": { | ||||||
|  |       const messageContent = document.createElement("span"); | ||||||
|  |       messageContent.appendChild(document.createTextNode("set the time to ")); | ||||||
|  | 
 | ||||||
|  |       messageContent.appendChild( | ||||||
|  |         document.createTextNode(formatTime(event.data)) | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |       printChatMessage("set-time", event.user, messageContent); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     case "SetPlaying": { | ||||||
|  |       const messageContent = document.createElement("span"); | ||||||
|  |       messageContent.appendChild( | ||||||
|  |         document.createTextNode( | ||||||
|  |           event.data.playing ? "started playing" : "paused" | ||||||
|  |         ) | ||||||
|  |       ); | ||||||
|  |       messageContent.appendChild(document.createTextNode(" at ")); | ||||||
|  |       messageContent.appendChild( | ||||||
|  |         document.createTextNode(formatTime(event.data.time)) | ||||||
|  |       ); | ||||||
|  | 
 | ||||||
|  |       printChatMessage("set-playing", event.user, messageContent); | ||||||
|  | 
 | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { joinSession } from "./watch-session.mjs"; | import { joinSession } from "./watch-session.mjs?v=2"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {HTMLInputElement} field |  * @param {HTMLInputElement} field | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import { setupVideo } from "./video.mjs"; | import { setupVideo } from "./video.mjs?v=2"; | ||||||
| import { setupChat, handleChatEvent } from "./chat.mjs"; | import { setupChat, logEventToChat } from "./chat.mjs?v=2"; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param {string} sessionId |  * @param {string} sessionId | ||||||
|  | @ -50,8 +50,8 @@ const setupIncomingEvents = (video, socket) => { | ||||||
|   socket.addEventListener("message", async (messageEvent) => { |   socket.addEventListener("message", async (messageEvent) => { | ||||||
|     try { |     try { | ||||||
|       const event = JSON.parse(messageEvent.data); |       const event = JSON.parse(messageEvent.data); | ||||||
|       // console.log(event);
 |  | ||||||
| 
 | 
 | ||||||
|  |       if (!event.reflected) { | ||||||
|         switch (event.op) { |         switch (event.op) { | ||||||
|           case "SetPlaying": |           case "SetPlaying": | ||||||
|             setDebounce(); |             setDebounce(); | ||||||
|  | @ -69,12 +69,10 @@ const setupIncomingEvents = (video, socket) => { | ||||||
|             setDebounce(); |             setDebounce(); | ||||||
|             setVideoTime(event.data); |             setVideoTime(event.data); | ||||||
|             break; |             break; | ||||||
|         case "UserJoin": |  | ||||||
|         case "UserLeave": |  | ||||||
|         case "ChatMessage": |  | ||||||
|           handleChatEvent(event); |  | ||||||
|           break; |  | ||||||
|         } |         } | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       logEventToChat(event); | ||||||
|     } catch (_err) {} |     } catch (_err) {} | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import { setupJoinSessionForm } from "./lib/join-session.mjs"; | import { setupJoinSessionForm } from "./lib/join-session.mjs?v=2"; | ||||||
| 
 | 
 | ||||||
| const main = () => { | const main = () => { | ||||||
|   setupJoinSessionForm(); |   setupJoinSessionForm(); | ||||||
|  |  | ||||||
|  | @ -61,6 +61,7 @@ input[type="text"] { | ||||||
|   font-family: sans-serif; |   font-family: sans-serif; | ||||||
|   font-size: 1em; |   font-size: 1em; | ||||||
|   width: 500px; |   width: 500px; | ||||||
|  |   max-width: 100%; | ||||||
| 
 | 
 | ||||||
|   resize: none; |   resize: none; | ||||||
|   overflow-x: wrap; |   overflow-x: wrap; | ||||||
|  | @ -82,6 +83,7 @@ button { | ||||||
|   font-family: sans-serif; |   font-family: sans-serif; | ||||||
|   font-size: 1em; |   font-size: 1em; | ||||||
|   width: 500px; |   width: 500px; | ||||||
|  |   max-width: 100%; | ||||||
| 
 | 
 | ||||||
|   user-select: none; |   user-select: none; | ||||||
|   border: 1px solid rgba(0, 0, 0, 0); |   border: 1px solid rgba(0, 0, 0, 0); | ||||||
|  | @ -118,13 +120,25 @@ button.small-button { | ||||||
|   display: none; |   display: none; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .user-join, | .chat-message > strong { | ||||||
| .user-leave { |   color: rgb(126, 208, 255); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .chat-message.user-join, | ||||||
|  | .chat-message.user-leave { | ||||||
|   font-style: italic; |   font-style: italic; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .chat-message > strong { | .chat-message.set-time, | ||||||
|   color: rgb(126, 208, 255); | .chat-message.set-playing { | ||||||
|  |   font-style: italic; | ||||||
|  |   text-align: right; | ||||||
|  |   font-size: 0.85em; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .chat-message.set-time > strong, | ||||||
|  | .chat-message.set-playing > strong { | ||||||
|  |   color: unset; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #chatbox { | #chatbox { | ||||||
|  | @ -144,4 +158,30 @@ button.small-button { | ||||||
| 
 | 
 | ||||||
| #chatbox-send > input { | #chatbox-send > input { | ||||||
|   font-size: 0.75em; |   font-size: 0.75em; | ||||||
|  |   width: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @media (min-aspect-ratio: 4/3) { | ||||||
|  |   #video-container video { | ||||||
|  |     width: calc(100vw - 400px); | ||||||
|  |     position: absolute; | ||||||
|  |     height: 100vh; | ||||||
|  |     background-color: black; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #video-container { | ||||||
|  |     float: left; | ||||||
|  |     height: 100vh; | ||||||
|  |     position: relative; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #chatbox-container { | ||||||
|  |     float: right; | ||||||
|  |     width: 400px; | ||||||
|  |     height: 100vh !important; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #chatbox { | ||||||
|  |     height: calc(100vh - 5em) !important; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,18 +2,31 @@ use serde::{Deserialize, Serialize}; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Serialize, Deserialize)] | #[derive(Clone, Serialize, Deserialize)] | ||||||
| #[serde(tag = "op", content = "data")] | #[serde(tag = "op", content = "data")] | ||||||
| pub enum WatchEvent { | pub enum WatchEventData { | ||||||
|     SetPlaying { |     SetPlaying { playing: bool, time: u64 }, | ||||||
|         playing: bool, |  | ||||||
|         time: u64, |  | ||||||
|     }, |  | ||||||
|     SetTime(u64), |     SetTime(u64), | ||||||
| 
 | 
 | ||||||
|     UserJoin(String), |     UserJoin, | ||||||
|     UserLeave(String), |     UserLeave, | ||||||
|     ChatMessage { |     ChatMessage(String), | ||||||
|         #[serde(default = "String::new")] | } | ||||||
|         user: String, | 
 | ||||||
|         message: String, | #[derive(Clone, Serialize, Deserialize)] | ||||||
|     }, | pub struct WatchEvent { | ||||||
|  |     #[serde(default, skip_serializing_if = "Option::is_none")] | ||||||
|  |     pub user: Option<String>, | ||||||
|  |     #[serde(flatten)] | ||||||
|  |     pub data: WatchEventData, | ||||||
|  |     #[serde(default)] | ||||||
|  |     pub reflected: bool, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl WatchEvent { | ||||||
|  |     pub fn new(user: String, data: WatchEventData) -> Self { | ||||||
|  |         WatchEvent { | ||||||
|  |             user: Some(user), | ||||||
|  |             data, | ||||||
|  |             reflected: false, | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										32
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								src/main.rs
									
									
									
									
									
								
							|  | @ -12,9 +12,9 @@ mod watch_session; | ||||||
| use serde::Deserialize; | use serde::Deserialize; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     events::WatchEvent, |     events::{WatchEvent, WatchEventData}, | ||||||
|     viewer_connection::{ws_publish, ws_subscribe}, |     viewer_connection::{ws_publish, ws_subscribe}, | ||||||
|     watch_session::{get_session, handle_watch_event, SubtitleTrack, WatchSession, SESSIONS}, |     watch_session::{get_session, handle_watch_event_data, SubtitleTrack, WatchSession, SESSIONS}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[derive(Deserialize)] | #[derive(Deserialize)] | ||||||
|  | @ -85,13 +85,21 @@ async fn main() { | ||||||
|         .and(warb::body::json()) |         .and(warb::body::json()) | ||||||
|         .map(|requested_session, playing: bool| match requested_session { |         .map(|requested_session, playing: bool| match requested_session { | ||||||
|             RequestedSession::Session(uuid, mut sess) => { |             RequestedSession::Session(uuid, mut sess) => { | ||||||
|                 let event = WatchEvent::SetPlaying { |                 let data = WatchEventData::SetPlaying { | ||||||
|                     playing, |                     playing, | ||||||
|                     time: sess.get_time_ms(), |                     time: sess.get_time_ms(), | ||||||
|                 }; |                 }; | ||||||
| 
 | 
 | ||||||
|                 handle_watch_event(uuid, &mut sess, event.clone()); |                 handle_watch_event_data(uuid, &mut sess, data.clone()); | ||||||
|                 tokio::spawn(ws_publish(uuid, None, event)); |                 tokio::spawn(ws_publish( | ||||||
|  |                     uuid, | ||||||
|  |                     None, | ||||||
|  |                     WatchEvent { | ||||||
|  |                         user: None, | ||||||
|  |                         data, | ||||||
|  |                         reflected: false, | ||||||
|  |                     }, | ||||||
|  |                 )); | ||||||
| 
 | 
 | ||||||
|                 warb::reply::with_status(warb::reply::json(&sess.view()), StatusCode::OK) |                 warb::reply::with_status(warb::reply::json(&sess.view()), StatusCode::OK) | ||||||
|             } |             } | ||||||
|  | @ -105,10 +113,18 @@ async fn main() { | ||||||
|         .map( |         .map( | ||||||
|             |requested_session, current_time_ms: u64| match requested_session { |             |requested_session, current_time_ms: u64| match requested_session { | ||||||
|                 RequestedSession::Session(uuid, mut sess) => { |                 RequestedSession::Session(uuid, mut sess) => { | ||||||
|                     let event = WatchEvent::SetTime(current_time_ms); |                     let data = WatchEventData::SetTime(current_time_ms); | ||||||
| 
 | 
 | ||||||
|                     handle_watch_event(uuid, &mut sess, event.clone()); |                     handle_watch_event_data(uuid, &mut sess, data.clone()); | ||||||
|                     tokio::spawn(ws_publish(uuid, None, event)); |                     tokio::spawn(ws_publish( | ||||||
|  |                         uuid, | ||||||
|  |                         None, | ||||||
|  |                         WatchEvent { | ||||||
|  |                             user: None, | ||||||
|  |                             data, | ||||||
|  |                             reflected: false, | ||||||
|  |                         }, | ||||||
|  |                     )); | ||||||
| 
 | 
 | ||||||
|                     warb::reply::with_status(warb::reply::json(&sess.view()), StatusCode::OK) |                     warb::reply::with_status(warb::reply::json(&sess.view()), StatusCode::OK) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -15,8 +15,8 @@ use uuid::Uuid; | ||||||
| use warp::ws::{Message, WebSocket}; | use warp::ws::{Message, WebSocket}; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     events::WatchEvent, |     events::{WatchEvent, WatchEventData}, | ||||||
|     watch_session::{get_session, handle_watch_event}, |     watch_session::{get_session, handle_watch_event_data}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| static CONNECTED_VIEWERS: Lazy<RwLock<HashMap<usize, ConnectedViewer>>> = | static CONNECTED_VIEWERS: Lazy<RwLock<HashMap<usize, ConnectedViewer>>> = | ||||||
|  | @ -27,6 +27,7 @@ pub struct ConnectedViewer { | ||||||
|     pub session: Uuid, |     pub session: Uuid, | ||||||
|     pub viewer_id: usize, |     pub viewer_id: usize, | ||||||
|     pub tx: UnboundedSender<WatchEvent>, |     pub tx: UnboundedSender<WatchEvent>, | ||||||
|  |     pub nickname: Option<String>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, ws: WebSocket) { | pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, ws: WebSocket) { | ||||||
|  | @ -53,13 +54,19 @@ pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, ws: WebSocket) { | ||||||
|             viewer_id, |             viewer_id, | ||||||
|             session: session_uuid, |             session: session_uuid, | ||||||
|             tx, |             tx, | ||||||
|  |             nickname: Some(nickname.clone()), | ||||||
|         }, |         }, | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     ws_publish(session_uuid, None, WatchEvent::UserJoin(nickname.clone())).await; |     ws_publish( | ||||||
|  |         session_uuid, | ||||||
|  |         None, | ||||||
|  |         WatchEvent::new(nickname.clone(), WatchEventData::UserJoin), | ||||||
|  |     ) | ||||||
|  |     .await; | ||||||
| 
 | 
 | ||||||
|     while let Some(Ok(message)) = viewer_ws_rx.next().await { |     while let Some(Ok(message)) = viewer_ws_rx.next().await { | ||||||
|         let mut event: WatchEvent = match message |         let event: WatchEventData = match message | ||||||
|             .to_str() |             .to_str() | ||||||
|             .ok() |             .ok() | ||||||
|             .and_then(|s| serde_json::from_str(s).ok()) |             .and_then(|s| serde_json::from_str(s).ok()) | ||||||
|  | @ -68,47 +75,39 @@ pub async fn ws_subscribe(session_uuid: Uuid, nickname: String, ws: WebSocket) { | ||||||
|             None => continue, |             None => continue, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         // Make sure people don't spoof their nicknames to pretend to be others
 |         handle_watch_event_data( | ||||||
|         // 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, |             session_uuid, | ||||||
|             &mut get_session(session_uuid).unwrap(), |             &mut get_session(session_uuid).unwrap(), | ||||||
|             event.clone(), |             event.clone(), | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         ws_publish(session_uuid, Some(viewer_id), event).await; |         ws_publish( | ||||||
|  |             session_uuid, | ||||||
|  |             Some(viewer_id), | ||||||
|  |             WatchEvent::new(nickname.clone(), event), | ||||||
|  |         ) | ||||||
|  |         .await; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     ws_publish(session_uuid, None, WatchEvent::UserLeave(nickname.clone())).await; |     ws_publish( | ||||||
|  |         session_uuid, | ||||||
|  |         None, | ||||||
|  |         WatchEvent::new(nickname.clone(), WatchEventData::UserLeave), | ||||||
|  |     ) | ||||||
|  |     .await; | ||||||
| 
 | 
 | ||||||
|     CONNECTED_VIEWERS.write().await.remove(&viewer_id); |     CONNECTED_VIEWERS.write().await.remove(&viewer_id); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub async fn ws_publish(session_uuid: Uuid, viewer_id: Option<usize>, event: WatchEvent) { | pub async fn ws_publish(session_uuid: Uuid, skip_viewer_id: Option<usize>, event: WatchEvent) { | ||||||
|     for viewer in CONNECTED_VIEWERS.read().await.values() { |     for viewer in CONNECTED_VIEWERS.read().await.values() { | ||||||
|         if viewer_id == Some(viewer.viewer_id) { |  | ||||||
|             continue; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if viewer.session != session_uuid { |         if viewer.session != session_uuid { | ||||||
|             continue; |             continue; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let _ = viewer.tx.send(event.clone()); |         let _ = viewer.tx.send(WatchEvent { | ||||||
|  |             reflected: skip_viewer_id == Some(viewer.viewer_id), | ||||||
|  |             ..event.clone() | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; | ||||||
| use std::{collections::HashMap, sync::Mutex, time::Instant}; | use std::{collections::HashMap, sync::Mutex, time::Instant}; | ||||||
| use uuid::Uuid; | use uuid::Uuid; | ||||||
| 
 | 
 | ||||||
| use crate::events::WatchEvent; | use crate::events::WatchEventData; | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize, Deserialize, Clone)] | #[derive(Serialize, Deserialize, Clone)] | ||||||
| pub struct SubtitleTrack { | pub struct SubtitleTrack { | ||||||
|  | @ -75,13 +75,17 @@ pub fn get_session(uuid: Uuid) -> Option<WatchSession> { | ||||||
|     SESSIONS.lock().unwrap().get(&uuid).cloned() |     SESSIONS.lock().unwrap().get(&uuid).cloned() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn handle_watch_event(uuid: Uuid, watch_session: &mut WatchSession, event: WatchEvent) { | pub fn handle_watch_event_data( | ||||||
|  |     uuid: Uuid, | ||||||
|  |     watch_session: &mut WatchSession, | ||||||
|  |     event: WatchEventData, | ||||||
|  | ) { | ||||||
|     match event { |     match event { | ||||||
|         WatchEvent::SetPlaying { playing, time } => { |         WatchEventData::SetPlaying { playing, time } => { | ||||||
|             watch_session.set_playing(playing, time); |             watch_session.set_playing(playing, time); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         WatchEvent::SetTime(time) => { |         WatchEventData::SetTime(time) => { | ||||||
|             watch_session.set_time_ms(time); |             watch_session.set_time_ms(time); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue