diff --git a/src/components/constants.ts b/src/components/constants.ts index 36eae99..6758bbb 100644 --- a/src/components/constants.ts +++ b/src/components/constants.ts @@ -15,3 +15,8 @@ export const controlIds = { volumeHighButton: "volume-high-button", volumeLowButton: "volume-low-button", }; + +/** + * How often to emit currentTime updates. + */ +export const videoSyncInterval = 500; diff --git a/src/pages/room/RoomEvents/useVideoControls/usePlayPauseControl.ts b/src/pages/room/RoomEvents/useVideoControls/usePlayPauseControl.ts index ea6392b..cdcc10d 100644 --- a/src/pages/room/RoomEvents/useVideoControls/usePlayPauseControl.ts +++ b/src/pages/room/RoomEvents/useVideoControls/usePlayPauseControl.ts @@ -4,10 +4,11 @@ import { controls, videoElement } from "@components/elements"; import { useRoomContext } from "@connection/RoomContext"; import { useEmitOnClick } from "../useEmitOnClick"; +import { videoSyncInterval } from "@components/constants"; export const usePlayPauseControl = () => { const { originalRoomData, roomData } = useRoomContext(); - const { currentTime, playing } = roomData.get(); + const { playing } = roomData.get(); useEmitOnClick(controls.playPauseButton, (oldRoomData) => ({ currentTime: videoElement.currentTime, @@ -22,10 +23,13 @@ export const usePlayPauseControl = () => { } else { videoElement.pause(); } - }, [currentTime, playing]); + }, [playing]); useEffect(() => { - videoElement.currentTime = originalRoomData.currentTime; + // Video timing is, on average, behind by half of the sync interval. + // currentTime is also measured in seconds instead of milliseconds for some reason. + videoElement.currentTime = + originalRoomData.currentTime + videoSyncInterval / 2000; if (originalRoomData.playing) { videoElement.play(); diff --git a/src/pages/room/RoomEvents/useVideoControls/useTimeSynchronization.ts b/src/pages/room/RoomEvents/useVideoControls/useTimeSynchronization.ts index 60b0f35..92fd468 100644 --- a/src/pages/room/RoomEvents/useVideoControls/useTimeSynchronization.ts +++ b/src/pages/room/RoomEvents/useVideoControls/useTimeSynchronization.ts @@ -3,22 +3,20 @@ import { useInterval } from "react-use"; import { videoElement } from "@components/elements"; import { useRoomContext } from "@connection/RoomContext"; - -/** - * How often to emit currentTime updates. - */ -const videoElementSyncInterval = 1000; +import { videoSyncInterval } from "@components/constants"; export const useTimeSynchronization = () => { const { emitRoomData, roomData } = useRoomContext(); const { currentTime, playing } = roomData.get(); - // Constantly update the server on the current time of the video + // Constantly update the server on the current time of the video if playing useInterval(() => { - emitRoomData({ - currentTime: videoElement.currentTime, - }); - }, videoElementSyncInterval); + if (playing) { + emitRoomData({ + currentTime: videoElement.currentTime, + }); + } + }, videoSyncInterval); // If the video is paused, we can safely match its time to currentTime useEffect(() => { diff --git a/src/server/events/karaoke.ts b/src/server/events/karaoke.ts index 529697e..209261f 100644 --- a/src/server/events/karaoke.ts +++ b/src/server/events/karaoke.ts @@ -1,3 +1,5 @@ +import { isEqual } from "lodash"; + import { KaraokeEvent } from "@shared/events"; import { RoomDataUpdatedData, SetUsernameData } from "@shared/types"; @@ -29,7 +31,29 @@ export const karaokeEvents = ({ socket.on(KaraokeEvent.RoomDataUpdated, (data: RoomDataUpdatedData) => { log(`Updated jukebox: ${JSON.stringify(data)}`); - room.data = { ...room.data, ...data }; - io.in(room.name).emit(KaraokeEvent.RoomDataUpdated, room.data); + // We declare a new "player" for the room if they're saying to play while paused. + const newPlayer = + data.playing && !room.data.playing ? person.id : room.data.player; + + const newData = { + ...room.data, + ...data, + + // Ignore currentTime updates from any client who didn't click the play button. + // This is a somewhat arbitrary measure to stop "currentTime battles", wherein + // two clients go back and forth sending vastly different times to each other. + currentTime: + newPlayer === person.id + ? data.currentTime ?? room.data.currentTime + : room.data.currentTime, + player: newPlayer, + }; + + if (isEqual(room.data, newData)) { + return; + } + + room.data = newData; + io.in(room.name).emit(KaraokeEvent.RoomDataUpdated, newData); }); }; diff --git a/src/shared/types.ts b/src/shared/types.ts index c4c2a1c..1c4b119 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -38,6 +38,11 @@ export type RoomData = { */ environment: string; + /** + * Client ID of the last person to trigger the play button. + */ + player?: PersonId; + /** * Whether the current song is playing. */