forked from lavender/watch-party
		
	Compare commits
	
		
			4 commits
		
	
	
		
			options-pa
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e6d09b14c5 | |||
| 4b61c44d6a | |||
| f3ee2ecc83 | |||
| 1bd7071cec | 
					 22 changed files with 2324 additions and 2500 deletions
				
			
		|  | @ -3,7 +3,7 @@ root = true | |||
| [*] | ||||
| indent_style = space | ||||
| indent_size = 2 | ||||
| end_of_line = crlf | ||||
| end_of_line = lf | ||||
| charset = utf-8 | ||||
| trim_trailing_whitespace = false | ||||
| insert_final_newline = true | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
|   <head> | ||||
|     <meta charset="utf-8" /> | ||||
|     <title>watch party :D</title> | ||||
|     <link rel="stylesheet" href="/styles.css?v=bfdcf2" /> | ||||
|     <link rel="stylesheet" href="/styles.css?v=4b61c4" /> | ||||
|   </head> | ||||
| 
 | ||||
|   <body> | ||||
|  | @ -47,6 +47,6 @@ | |||
|       </form> | ||||
|     </div> | ||||
| 
 | ||||
|     <script type="module" src="/create.mjs?v=bfdcf2"></script> | ||||
|     <script type="module" src="/create.mjs?v=4b61c4"></script> | ||||
|   </body> | ||||
| </html> | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { setupCreateSessionForm } from "./lib/create-session.mjs?v=bfdcf2"; | ||||
| import { setupCreateSessionForm } from "./lib/create-session.mjs?v=4b61c4"; | ||||
| 
 | ||||
| const main = () => { | ||||
|   setupCreateSessionForm(); | ||||
|  |  | |||
|  | @ -3,7 +3,8 @@ | |||
|   <head> | ||||
|     <meta charset="utf-8" /> | ||||
|     <title>watch party :D</title> | ||||
|     <link rel="stylesheet" href="/styles.css?v=bfdcf2" /> | ||||
|     <link rel="stylesheet" href="/lib/plyr-3.7.3.css" /> | ||||
|     <link rel="stylesheet" href="/styles.css?v=4b61c4" /> | ||||
|   </head> | ||||
| 
 | ||||
|   <body> | ||||
|  | @ -53,7 +54,6 @@ | |||
| 
 | ||||
|     <div id="video-container"></div> | ||||
|     <div id="chatbox-container"> | ||||
|       <section id="viewing"> | ||||
|       <div id="viewer-list"></div> | ||||
|       <div id="chatbox"></div> | ||||
|       <form id="chatbox-send"> | ||||
|  | @ -65,43 +65,9 @@ | |||
|         <div id="emoji-autocomplete"></div> | ||||
|         <!-- DO NOT ADD SPACING INSIDE THE TAG IT WILL BREAK THE CSS kthxbye --> | ||||
|       </form> | ||||
|       </section> | ||||
|       <section id="options"> | ||||
|         <h2>settings</h2> | ||||
|         <hr /> | ||||
|         <form id="options-form"> | ||||
|           <label for="plingVolume" | ||||
|             ><input | ||||
|               type="range" | ||||
|               min="0" | ||||
|               max="100" | ||||
|               value="100" | ||||
|               id="plingVolume" | ||||
|               onchange="handlePlingVolume(this)" | ||||
|             /> | ||||
|             ping volume</label | ||||
|           > | ||||
|           <label | ||||
|           ><input | ||||
|             id="playerControlsShown" | ||||
|             type="checkbox" | ||||
|             onchange="togglePlayerControlsShown(this)" | ||||
|           />hide controls when loading video player</label | ||||
|         > | ||||
|         </form> | ||||
|       </section> | ||||
|       <div id="options-toggle"> | ||||
|         <button | ||||
|           aria-label="settings" | ||||
|           id="options-icon" | ||||
|           onclick="toggleOptionPane(event, this)" | ||||
|         > | ||||
|           ⚙️ | ||||
|         </button> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <script type="module" src="/main.mjs?v=bfdcf2"></script> | ||||
|     <script type="module" src="/main.mjs?v=4b61c4"></script> | ||||
|     <script> | ||||
|       const updateColourLabel = () => { | ||||
|         const colour = document.querySelector("#join-session-colour").value; | ||||
|  |  | |||
|  | @ -2,11 +2,12 @@ import { | |||
|   setDebounce, | ||||
|   setVideoTime, | ||||
|   setPlaying, | ||||
| } from "./watch-session.mjs?v=bfdcf2"; | ||||
| import { emojify, findEmojis } from "./emojis.mjs?v=bfdcf2"; | ||||
| import { linkify } from "./links.mjs?v=bfdcf2"; | ||||
| import { joinSession } from "./watch-session.mjs?v=bfdcf2"; | ||||
| import { pling } from "./pling.mjs?v=bfdcf2"; | ||||
|   sync, | ||||
| } from "./watch-session.mjs?v=4b61c4"; | ||||
| import { emojify, findEmojis } from "./emojis.mjs?v=4b61c4"; | ||||
| import { linkify } from "./links.mjs?v=4b61c4"; | ||||
| import { joinSession } from "./watch-session.mjs?v=4b61c4"; | ||||
| import { pling } from "./pling.mjs?v=4b61c4"; | ||||
| import { state } from "./state.mjs"; | ||||
| 
 | ||||
| function setCaretPosition(elem, caretPos) { | ||||
|  | @ -164,14 +165,7 @@ const setupChatboxEvents = (socket) => { | |||
|             handled = true; | ||||
|             break; | ||||
|           case "/sync": | ||||
|             const sessionId = window.location.hash.slice(1); | ||||
|             const { current_time_ms, is_playing } = await fetch( | ||||
|               `/sess/${sessionId}` | ||||
|             ).then((r) => r.json()); | ||||
| 
 | ||||
|             setDebounce(); | ||||
|             setPlaying(is_playing); | ||||
|             setVideoTime(current_time_ms); | ||||
|             await sync(); | ||||
| 
 | ||||
|             const syncMessageContent = document.createElement("span"); | ||||
|             syncMessageContent.appendChild( | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { createSession } from "./watch-session.mjs?v=bfdcf2"; | ||||
| import { createSession } from "./watch-session.mjs?v=4b61c4"; | ||||
| 
 | ||||
| export const setupCreateSessionForm = () => { | ||||
|   const form = document.querySelector("#create-session-form"); | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { joinSession } from "./watch-session.mjs?v=bfdcf2"; | ||||
| import { joinSession } from "./watch-session.mjs?v=4b61c4"; | ||||
| import { state } from "./state.mjs"; | ||||
| 
 | ||||
| /** | ||||
|  | @ -54,11 +54,13 @@ const displayPostCreateMessage = () => { | |||
|   if (params.get("created") == "true") { | ||||
|     document.querySelector("#post-create-message").style["display"] = "block"; | ||||
|     window.history.replaceState({}, document.title, `/${window.location.hash}`); | ||||
|     return true; | ||||
|   } | ||||
|   return false; | ||||
| }; | ||||
| 
 | ||||
| export const setupJoinSessionForm = () => { | ||||
|   displayPostCreateMessage(); | ||||
|   const created = displayPostCreateMessage(); | ||||
| 
 | ||||
|   const form = document.querySelector("#join-session-form"); | ||||
|   const nickname = form.querySelector("#join-session-nickname"); | ||||
|  | @ -84,7 +86,7 @@ export const setupJoinSessionForm = () => { | |||
|       state().nickname = nickname.value; | ||||
|       state().sessionId = sessionId.value; | ||||
|       state().colour = colour.value.replace(/^#/, ""); | ||||
|       await joinSession(); | ||||
|       await joinSession(created); | ||||
|     } catch (e) { | ||||
|       alert(e.message); | ||||
|       button.disabled = false; | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { joinSession } from "./watch-session.mjs?v=bfdcf2"; | ||||
| import { joinSession } from "./watch-session.mjs?v=4b61c4"; | ||||
| import { state } from "./state.mjs"; | ||||
| 
 | ||||
| export async function linkify( | ||||
|  |  | |||
|  | @ -1,43 +0,0 @@ | |||
| export const toggleOptionPane = (event, element) => { | ||||
|   event.preventDefault(); | ||||
|   // show options
 | ||||
|   if ( | ||||
|     !document.querySelector("#options").style.display || | ||||
|     document.querySelector("#options").style.display === "none" | ||||
|   ) { | ||||
|     // using this to do any potential init logic for the fields too
 | ||||
|     loadPlayerControlsShown(document.querySelector("#playerControlsShown")) | ||||
|     loadPlingVolume(document.querySelector("#plingVolume")) | ||||
| 
 | ||||
|     element.innerText = "❌"; | ||||
|     document.querySelector("#options").style.display = "block"; | ||||
|     return (document.querySelector("#viewing").style.display = "none"); | ||||
|   } | ||||
|   // hide options
 | ||||
|   element.innerText = "⚙️"; | ||||
|   document.querySelector("#options").style.display = "none"; | ||||
|   document.querySelector("#viewing").style.display = "block"; | ||||
| }; | ||||
| 
 | ||||
| const getPlayerControlsShown = () =>  localStorage.getItem("watch-party-default-allow-controls") || false | ||||
| // delete from storage on false to prevent weird js boolean parsing (Boolean('false') === True)
 | ||||
| const setPlayerControlShown = (boolean) => !boolean  | ||||
| ? localStorage.removeItem("watch-party-default-allow-controls") | ||||
| : localStorage.setItem("watch-party-default-allow-controls", boolean) | ||||
| export const togglePlayerControlsShown = (element) => { | ||||
|   const isShown = element.checked | ||||
|   setPlayerControlShown(!isShown) | ||||
| } | ||||
| const loadPlayerControlsShown = (element) => { | ||||
|   const isShown = getPlayerControlsShown() | ||||
|   element.checked = !isShown | ||||
| } | ||||
| 
 | ||||
| const getPlingVolume = () =>  localStorage.getItem("watch-party-pling-volume") || 100 | ||||
| const setPlingVolume = (value) => localStorage.setItem("watch-party-pling-volume", value) | ||||
| export const handlePlingVolume = (element) => { | ||||
|   setPlingVolume(element.value) | ||||
| } | ||||
| const loadPlingVolume = (element) => { | ||||
|   element.value = getPlingVolume() | ||||
| } | ||||
|  | @ -1,8 +1,5 @@ | |||
| export const pling = () => { | ||||
|   // technically volume 0 breaks it but its the wanted outcome i guess?
 | ||||
|   const maxGain = | ||||
|     (Number(localStorage.getItem("watch-party-pling-volume")) / 100 ?? 1) * 0.3; | ||||
| 
 | ||||
|   const maxGain = 0.3; | ||||
|   const duration = 0.22; | ||||
|   const fadeDuration = 0.1; | ||||
|   const secondBeepOffset = 0.05; | ||||
|  |  | |||
							
								
								
									
										1
									
								
								frontend/lib/plyr-3.7.3.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								frontend/lib/plyr-3.7.3.css
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										1
									
								
								frontend/lib/plyr-3.7.3.min.esm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								frontend/lib/plyr-3.7.3.min.esm.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							|  | @ -1,110 +1,56 @@ | |||
| const loadVolume = () => { | ||||
|   try { | ||||
|     const savedVolume = localStorage.getItem("watch-party-volume"); | ||||
|     if (savedVolume != null && savedVolume != "") { | ||||
|       return +savedVolume; | ||||
|     } | ||||
|   } catch (_err) { | ||||
|     // Sometimes localStorage is blocked from use
 | ||||
|   } | ||||
|   // default
 | ||||
|   return 0.5; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * @param {number} volume | ||||
|  */ | ||||
| const saveVolume = (volume) => { | ||||
|   try { | ||||
|     localStorage.setItem("watch-party-volume", volume); | ||||
|   } catch (_err) { | ||||
|     // see loadVolume
 | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| const loadCaptionTrack = () => { | ||||
|   try { | ||||
|     const savedTrack = localStorage.getItem("watch-party-captions"); | ||||
|     if (savedTrack != null && savedTrack != "") { | ||||
|       return +savedTrack; | ||||
|     } | ||||
|   } catch (_err) { | ||||
|     // Sometimes localStorage is blocked from use
 | ||||
|   } | ||||
|   // default
 | ||||
|   return -1; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * @param {number} track | ||||
|  */ | ||||
| const saveCaptionsTrack = (track) => { | ||||
|   try { | ||||
|     localStorage.setItem("watch-party-captions", track); | ||||
|   } catch (_err) { | ||||
|     // see loadCaptionsTrack
 | ||||
|   } | ||||
| }; | ||||
| import Plyr from "./plyr-3.7.3.min.esm.js"; | ||||
| 
 | ||||
| /** | ||||
|  * @param {string} videoUrl | ||||
|  * @param {{name: string, url: string}[]} subtitles | ||||
|  */ | ||||
| const createVideoElement = (videoUrl, subtitles) => { | ||||
|   const oldVideo = document.getElementById("video"); | ||||
| const createVideoElement = (videoUrl, subtitles, created) => { | ||||
|   const oldVideo = document.getElementById(".plyr"); | ||||
|   if (oldVideo) { | ||||
|     oldVideo.remove(); | ||||
|   } | ||||
|   const video = document.createElement("video"); | ||||
|   video.id = "video"; | ||||
|   video.controls = true; | ||||
|   video.autoplay = false; | ||||
|   video.volume = loadVolume(); | ||||
|   video.crossOrigin = "anonymous"; | ||||
| 
 | ||||
|   video.addEventListener("volumechange", async () => { | ||||
|     saveVolume(video.volume); | ||||
|   }); | ||||
| 
 | ||||
|   const source = document.createElement("source"); | ||||
|   source.src = videoUrl; | ||||
| 
 | ||||
|   video.appendChild(source); | ||||
| 
 | ||||
|   const storedTrack = loadCaptionTrack(); | ||||
|   let id = 0; | ||||
|   for (const { name, url } of subtitles) { | ||||
|     const track = document.createElement("track"); | ||||
|     track.label = name; | ||||
|     track.srclang = "xx-" + name.toLowerCase(); | ||||
|     track.src = url; | ||||
|     track.kind = "captions"; | ||||
| 
 | ||||
|     if (id == storedTrack || storedTrack == -1) { | ||||
|       track.default = true; | ||||
|     } | ||||
| 
 | ||||
|     video.appendChild(track); | ||||
|     id++; | ||||
|   } | ||||
| 
 | ||||
|   video.textTracks.addEventListener("change", async () => { | ||||
|     let id = 0; | ||||
|     for (const track of video.textTracks) { | ||||
|       if (track.mode != "disabled") { | ||||
|         saveCaptionsTrack(id); | ||||
|         return; | ||||
|       } | ||||
|       id++; | ||||
|     } | ||||
|     saveCaptionsTrack(-1); | ||||
|   const videoContainer = document.querySelector("#video-container"); | ||||
|   videoContainer.style.display = "block"; | ||||
|   videoContainer.appendChild(video); | ||||
| 
 | ||||
|   const player = new Plyr(video, { | ||||
|     clickToPlay: false, | ||||
|     settings: ["captions", "quality"], | ||||
|     autopause: false, | ||||
|   }); | ||||
| 
 | ||||
|   // watch for attribute changes on the video object to detect hiding/showing of controls
 | ||||
|   // as far as i can tell this is the least hacky solutions to get control visibility change events
 | ||||
|   const observer = new MutationObserver(async (mutations) => { | ||||
|     for (const mutation of mutations) { | ||||
|       if (mutation.attributeName == "controls") { | ||||
|         if (video.controls) { | ||||
|   player.elements.controls.insertAdjacentHTML( | ||||
|     "afterbegin", | ||||
|     `<button type="button" aria-pressed="false" class="plyr__controls__item plyr__control lock-controls"><svg aria-hidden="true" focusable="false" viewBox="0 0 24 24"><path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zM9 8V6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9z"></path></svg><span class="label--pressed plyr__sr-only">Unlock controls</span><span class="label--not-pressed plyr__sr-only">Lock controls</span></button>` | ||||
|   ); | ||||
|   const lockButton = player.elements.controls.children[0]; | ||||
|   let controlsEnabled = created; | ||||
|   const setControlsEnabled = (enabled) => { | ||||
|     controlsEnabled = enabled; | ||||
|     lockButton.setAttribute("aria-pressed", enabled); | ||||
|     lockButton.classList.toggle("plyr__control--pressed", enabled); | ||||
|     player.elements.buttons.play[0].disabled = | ||||
|       player.elements.buttons.play[1].disabled = | ||||
|       player.elements.inputs.seek.disabled = | ||||
|         !enabled; | ||||
|     if (!enabled) { | ||||
|       // enable media button support
 | ||||
|       navigator.mediaSession.setActionHandler("play", null); | ||||
|       navigator.mediaSession.setActionHandler("pause", null); | ||||
|  | @ -125,13 +71,14 @@ const createVideoElement = (videoUrl, subtitles) => { | |||
|       navigator.mediaSession.setActionHandler("previoustrack", () => {}); | ||||
|       navigator.mediaSession.setActionHandler("nexttrack", () => {}); | ||||
|     } | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|   observer.observe(video, { attributes: true }); | ||||
|   }; | ||||
|   setControlsEnabled(controlsEnabled); | ||||
|   lockButton.addEventListener("click", () => | ||||
|     setControlsEnabled(!controlsEnabled) | ||||
|   ); | ||||
|   window.__plyr = player; | ||||
| 
 | ||||
|   return video; | ||||
|   return player; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  | @ -140,24 +87,26 @@ const createVideoElement = (videoUrl, subtitles) => { | |||
|  * @param {number} currentTime | ||||
|  * @param {boolean} playing | ||||
|  */ | ||||
| export const setupVideo = async (videoUrl, subtitles, currentTime, playing) => { | ||||
| export const setupVideo = async ( | ||||
|   videoUrl, | ||||
|   subtitles, | ||||
|   currentTime, | ||||
|   playing, | ||||
|   created | ||||
| ) => { | ||||
|   document.querySelector("#pre-join-controls").style["display"] = "none"; | ||||
|   const video = createVideoElement(videoUrl, subtitles); | ||||
|   const videoContainer = document.querySelector("#video-container"); | ||||
|   videoContainer.style.display = "block"; | ||||
|   videoContainer.appendChild(video); | ||||
| 
 | ||||
|   video.currentTime = currentTime / 1000.0; | ||||
|   const player = createVideoElement(videoUrl, subtitles, created); | ||||
|   player.currentTime = currentTime / 1000.0; | ||||
| 
 | ||||
|   try { | ||||
|     if (playing) { | ||||
|       await video.play(); | ||||
|       player.play(); | ||||
|     } else { | ||||
|       video.pause(); | ||||
|       player.pause(); | ||||
|     } | ||||
|   } catch (err) { | ||||
|     // Auto-play is probably disabled, we should uhhhhhhh do something about it
 | ||||
|   } | ||||
| 
 | ||||
|   return video; | ||||
|   return player; | ||||
| }; | ||||
|  |  | |||
|  | @ -1,13 +1,13 @@ | |||
| import { setupVideo } from "./video.mjs?v=bfdcf2"; | ||||
| import { setupVideo } from "./video.mjs?v=4b61c4"; | ||||
| import { | ||||
|   setupChat, | ||||
|   logEventToChat, | ||||
|   updateViewerList, | ||||
|   printChatMessage, | ||||
| } from "./chat.mjs?v=bfdcf2"; | ||||
| } from "./chat.mjs?v=4b61c4"; | ||||
| import ReconnectingWebSocket from "./reconnecting-web-socket.mjs"; | ||||
| import { state } from "./state.mjs"; | ||||
| 
 | ||||
| let player; | ||||
| /** | ||||
|  * @param {string} sessionId | ||||
|  * @param {string} nickname | ||||
|  | @ -42,26 +42,18 @@ export const setDebounce = () => { | |||
|   }, 500); | ||||
| }; | ||||
| 
 | ||||
| export const setVideoTime = (time, video = null) => { | ||||
|   if (video == null) { | ||||
|     video = document.querySelector("video"); | ||||
|   } | ||||
| 
 | ||||
| export const setVideoTime = (time) => { | ||||
|   const timeSecs = time / 1000.0; | ||||
|   if (Math.abs(video.currentTime - timeSecs) > 0.5) { | ||||
|     video.currentTime = timeSecs; | ||||
|   if (Math.abs(player.currentTime - timeSecs) > 0.5) { | ||||
|     player.currentTime = timeSecs; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| export const setPlaying = async (playing, video = null) => { | ||||
|   if (video == null) { | ||||
|     video = document.querySelector("video"); | ||||
|   } | ||||
| 
 | ||||
| export const setPlaying = async (playing) => { | ||||
|   if (playing) { | ||||
|     await video.play(); | ||||
|     await player.play(); | ||||
|   } else { | ||||
|     video.pause(); | ||||
|     player.pause(); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
|  | @ -69,7 +61,7 @@ export const setPlaying = async (playing, video = null) => { | |||
|  * @param {HTMLVideoElement} video | ||||
|  * @param {ReconnectingWebSocket} socket | ||||
|  */ | ||||
| const setupIncomingEvents = (video, socket) => { | ||||
| const setupIncomingEvents = (player, socket) => { | ||||
|   socket.addEventListener("message", async (messageEvent) => { | ||||
|     try { | ||||
|       const event = JSON.parse(messageEvent.data); | ||||
|  | @ -79,16 +71,16 @@ const setupIncomingEvents = (video, socket) => { | |||
|             setDebounce(); | ||||
| 
 | ||||
|             if (event.data.playing) { | ||||
|               await video.play(); | ||||
|               await player.play(); | ||||
|             } else { | ||||
|               video.pause(); | ||||
|               player.pause(); | ||||
|             } | ||||
| 
 | ||||
|             setVideoTime(event.data.time, video); | ||||
|             setVideoTime(event.data.time); | ||||
|             break; | ||||
|           case "SetTime": | ||||
|             setDebounce(); | ||||
|             setVideoTime(event.data, video); | ||||
|             setVideoTime(event.data); | ||||
|             break; | ||||
|           case "UpdateViewerList": | ||||
|             updateViewerList(event.data); | ||||
|  | @ -102,19 +94,19 @@ const setupIncomingEvents = (video, socket) => { | |||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * @param {HTMLVideoElement} video | ||||
|  * @param {Plyr} player | ||||
|  * @param {ReconnectingWebSocket} socket | ||||
|  */ | ||||
| const setupOutgoingEvents = (video, socket) => { | ||||
|   const currentVideoTime = () => (video.currentTime * 1000) | 0; | ||||
| const setupOutgoingEvents = (player, socket) => { | ||||
|   const currentVideoTime = () => (player.currentTime * 1000) | 0; | ||||
| 
 | ||||
|   video.addEventListener("pause", async (event) => { | ||||
|     if (outgoingDebounce || !video.controls) { | ||||
|   player.on("pause", async () => { | ||||
|     if (outgoingDebounce || player.elements.inputs.seek.disabled) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // don't send a pause event for the video ending
 | ||||
|     if (video.currentTime == video.duration) { | ||||
|     if (player.currentTime == player.duration) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|  | @ -129,8 +121,8 @@ const setupOutgoingEvents = (video, socket) => { | |||
|     ); | ||||
|   }); | ||||
| 
 | ||||
|   video.addEventListener("play", (event) => { | ||||
|     if (outgoingDebounce || !video.controls) { | ||||
|   player.on("play", () => { | ||||
|     if (outgoingDebounce || player.elements.inputs.seek.disabled) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|  | @ -146,14 +138,14 @@ const setupOutgoingEvents = (video, socket) => { | |||
|   }); | ||||
| 
 | ||||
|   let firstSeekComplete = false; | ||||
|   video.addEventListener("seeked", async (event) => { | ||||
|   player.on("seeked", async (event) => { | ||||
|     if (!firstSeekComplete) { | ||||
|       // The first seeked event is performed by the browser when the video is loading
 | ||||
|       firstSeekComplete = true; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (outgoingDebounce || !video.controls) { | ||||
|     if (outgoingDebounce || player.elements.inputs.seek.disabled) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|  | @ -168,7 +160,7 @@ const setupOutgoingEvents = (video, socket) => { | |||
|   }); | ||||
| }; | ||||
| 
 | ||||
| export const joinSession = async () => { | ||||
| export const joinSession = async (created) => { | ||||
|   if (state().activeSession) { | ||||
|     if (state().activeSession === state().sessionId) { | ||||
|       // we are already in this session, dont rejoin
 | ||||
|  | @ -221,29 +213,20 @@ export const joinSession = async () => { | |||
|   const socket = createWebSocket(); | ||||
|   state().socket = socket; | ||||
|   socket.addEventListener("open", async () => { | ||||
|     const video = await setupVideo( | ||||
|     player = await setupVideo( | ||||
|       video_url, | ||||
|       subtitle_tracks, | ||||
|       current_time_ms, | ||||
|       is_playing | ||||
|       is_playing, | ||||
|       created | ||||
|     ); | ||||
| 
 | ||||
|     let defaultAllowControls = false; | ||||
|     try { | ||||
|       defaultAllowControls = localStorage.getItem( | ||||
|         "watch-party-default-allow-controls" | ||||
|       ); | ||||
|     } catch (_err) {} | ||||
|     player.on("canplay", () => { | ||||
|       sync(); | ||||
|     }); | ||||
| 
 | ||||
|     // 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 || !defaultAllowControls) { | ||||
|       video.controls = false; | ||||
|     } | ||||
| 
 | ||||
|     setupOutgoingEvents(video, socket); | ||||
|     setupIncomingEvents(video, socket); | ||||
|     setupOutgoingEvents(player, socket); | ||||
|     setupIncomingEvents(player, socket); | ||||
|     setupChat(socket); | ||||
|   }); | ||||
|   socket.addEventListener("reconnecting", (e) => { | ||||
|  | @ -273,3 +256,15 @@ export const createSession = async (videoUrl, subtitleTracks) => { | |||
| 
 | ||||
|   window.location = `/?created=true#${id}`; | ||||
| }; | ||||
| 
 | ||||
| export const sync = async () => { | ||||
|   setDebounce(); | ||||
|   await setPlaying(false); | ||||
|   const { current_time_ms, is_playing } = await fetch( | ||||
|     `/sess/${state().sessionId}` | ||||
|   ).then((r) => r.json()); | ||||
| 
 | ||||
|   setDebounce(); | ||||
|   setVideoTime(current_time_ms); | ||||
|   if (is_playing) await setPlaying(is_playing); | ||||
| }; | ||||
|  |  | |||
|  | @ -1,15 +1,7 @@ | |||
| import { setupJoinSessionForm } from "./lib/join-session.mjs?v=bfdcf2"; | ||||
| import { | ||||
|   toggleOptionPane, | ||||
|   togglePlayerControlsShown, | ||||
|   handlePlingVolume | ||||
| } from "./lib/options-pane.mjs?v=bfdcf2"; | ||||
| import { setupJoinSessionForm } from "./lib/join-session.mjs?v=4b61c4"; | ||||
| 
 | ||||
| const main = () => { | ||||
|   setupJoinSessionForm(); | ||||
|   window.toggleOptionPane = toggleOptionPane; | ||||
|   window.togglePlayerControlsShown = togglePlayerControlsShown; | ||||
|   window.handlePlingVolume = handlePlingVolume | ||||
| }; | ||||
| 
 | ||||
| if (document.readyState === "complete") { | ||||
|  |  | |||
|  | @ -8,8 +8,6 @@ | |||
|   --bg-rgb: 28, 23, 36; | ||||
|   --fg-rgb: 234, 234, 248; | ||||
|   --accent-rgb: 181, 127, 220; | ||||
|   --accent-darker: rgb(95, 37, 136); /*50% darker*/ | ||||
|   --accent-darkest: rgb(47, 19, 68); /*75% darker*/ | ||||
|   --fg: rgb(var(--fg-rgb)); | ||||
|   --bg: rgb(var(--bg-rgb)); | ||||
|   --default-user-color: rgb(126, 208, 255); | ||||
|  | @ -27,6 +25,14 @@ | |||
|     ), | ||||
|     linear-gradient(var(--bg), var(--bg)); | ||||
|   --accent-transparent: rgba(var(--accent-rgb), 0.25); | ||||
|   --plyr-color-main: var(--accent); | ||||
|   --plyr-control-radius: 6px; | ||||
|   --plyr-menu-radius: 6px; | ||||
|   --plyr-menu-background: var(--autocomplete-bg); | ||||
|   --plyr-menu-color: var(--fg); | ||||
|   --plyr-menu-arrow-color: var(--fg); | ||||
|   --plyr-menu-back-border-color: var(--fg-transparent); | ||||
|   --plyr-menu-back-border-shadow-color: transparent; | ||||
| } | ||||
| 
 | ||||
| html { | ||||
|  | @ -51,11 +57,41 @@ body { | |||
|   flex-direction: column; | ||||
| } | ||||
| 
 | ||||
| video { | ||||
|   display: block; | ||||
| .lock-controls.plyr__control--pressed svg { | ||||
|   opacity: 0.5; | ||||
| } | ||||
| 
 | ||||
| .plyr { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   object-fit: contain; | ||||
| } | ||||
| 
 | ||||
| .plyr__menu__container { | ||||
|   --plyr-video-control-background-hover: var(--fg-transparent); | ||||
|   --plyr-video-control-color-hover: var(--fg); | ||||
|   --plyr-control-radius: 4px; | ||||
|   --plyr-control-spacing: calc(0.25rem / 0.7); | ||||
|   --plyr-font-size-menu: 0.75rem; | ||||
|   --plyr-menu-arrow-size: 0; | ||||
|   margin-bottom: 0.48rem; | ||||
|   max-height: 27vmin; | ||||
|   clip-path: inset(0 0 0 0 round 4px); | ||||
|   scrollbar-width: thin; | ||||
| } | ||||
| 
 | ||||
| .plyr__menu__container .plyr__control[role="menuitemradio"]::after { | ||||
|   left: 10px; | ||||
| } | ||||
| 
 | ||||
| .plyr__menu__container | ||||
|   .plyr__control[role="menuitemradio"][aria-checked="true"].plyr__tab-focus::before, | ||||
| .plyr__menu__container | ||||
|   .plyr__control[role="menuitemradio"][aria-checked="true"]:hover::before { | ||||
|   background: var(--accent); | ||||
| } | ||||
| 
 | ||||
| [data-plyr="language"] .plyr__menu__value { | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| #video-container { | ||||
|  | @ -133,7 +169,7 @@ input[type="text"] { | |||
|   overflow-y: scroll; | ||||
| } | ||||
| 
 | ||||
| button { | ||||
| button:not(.plyr button) { | ||||
|   background-color: var(--accent); | ||||
|   border: var(--accent); | ||||
|   border-radius: 6px; | ||||
|  | @ -281,6 +317,7 @@ button.small-button { | |||
| 
 | ||||
| #chatbox-send { | ||||
|   padding: 0 1em; | ||||
|   padding-bottom: 0.5em; | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
|  | @ -304,7 +341,7 @@ button.small-button { | |||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .emoji-option { | ||||
| .emoji-option:not(:root) { | ||||
|   background: transparent; | ||||
|   font-size: 0.75rem; | ||||
|   text-align: left; | ||||
|  | @ -360,73 +397,6 @@ button.small-button { | |||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| #options-toggle { | ||||
|   padding: 0 1em 1em; | ||||
|   position: relative; | ||||
|   text-align: right; | ||||
|   margin-top: auto; | ||||
| } | ||||
| 
 | ||||
| #options-toggle #options-icon { | ||||
|   padding: 3px 10px; | ||||
|   font-size: 1em; | ||||
|   max-width: 3em; | ||||
| 
 | ||||
|   color: transparent; | ||||
|   text-shadow: 0 0 0 white; | ||||
|   border: none; | ||||
|   box-shadow:0px 0px 0px 2px var(--accent-darkest) inset; | ||||
|    | ||||
|   transform-style: preserve-3d; | ||||
|   transition: cubic-bezier(0, 0, 0.58, 1), cubic-bezier(0, 0, 0.58, 1); | ||||
|   transition-duration: 150ms; | ||||
| } | ||||
| 
 | ||||
| #options-toggle #options-icon::before { | ||||
|   content: ""; | ||||
| 
 | ||||
|   position: absolute; | ||||
|    | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   bottom: 0; | ||||
| 
 | ||||
|   background-color: var(--accent-darker); | ||||
|   border-radius: inherit; | ||||
|   border: none; | ||||
|   box-shadow:0px 0px 0px 2px var(--accent-darkest) inset; | ||||
| 
 | ||||
|   transform: translate3d(0, 0.5em, -1em); | ||||
|   transition: cubic-bezier(0, 0, 0.58, 1), cubic-bezier(0, 0, 0.58, 1); | ||||
|   transition-duration: 150ms; | ||||
| } | ||||
| 
 | ||||
| #options-toggle #options-icon:hover { | ||||
|   transform: translate(0, 0.15em); | ||||
|   background-color: rgb(173, 113, 216); /*5% darker accent*/ | ||||
| } | ||||
| 
 | ||||
| #options-toggle #options-icon:hover::before { | ||||
|   transform: translate3d(0, 0.35em, -1em); | ||||
| } | ||||
| 
 | ||||
| #options-toggle #options-icon:active { | ||||
|   transform: translate(0em, 0.5em); | ||||
|   background-color: rgb(165, 100, 213); /*10% darker accent*/ | ||||
| } | ||||
| 
 | ||||
| #options-toggle #options-icon:active::before { | ||||
|   transform: translate3d(0, 0, -1em); | ||||
| } | ||||
| 
 | ||||
| #options { | ||||
|   display: none; /* default for sections is block */ | ||||
|   padding: 01em; | ||||
| } | ||||
| 
 | ||||
| input[type="color"]::-moz-color-swatch { | ||||
|   border: none; | ||||
|   margin: 0; | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue