Add remote video overlay
Fix remote audio not working when viewing another conversation.
Add a remote video overlay when in a call, but viewing a different page.
New components:
- VideoOverlay
- RemoteVideoOverlay: Video overlay for the remote stream when in a
different page. Clicking it will redirect to the current call page.
- VideoStream: renders a video from a MediaStream and can
output audio to the device with id `audioOutDeviceId`
Misc changes:
- Can click local video to make bigger
Change-Id: If1bb0b10c137944c405d540f041ebfe8005f9251
diff --git a/client/src/pages/CallInterface.tsx b/client/src/pages/CallInterface.tsx
index 4feecc2..3c98784 100644
--- a/client/src/pages/CallInterface.tsx
+++ b/client/src/pages/CallInterface.tsx
@@ -29,7 +29,6 @@
useRef,
useState,
} from 'react';
-import Draggable from 'react-draggable';
import { ExpandableButtonProps } from '../components/Button';
import {
@@ -46,6 +45,8 @@
CallingVolumeButton,
} from '../components/CallButtons';
import CallChatDrawer from '../components/CallChatDrawer';
+import VideoOverlay from '../components/VideoOverlay';
+import VideoStream from '../components/VideoStream';
import { CallContext, CallStatus, VideoStatus } from '../contexts/CallProvider';
import { useConversationContext } from '../contexts/ConversationProvider';
import { WebRtcContext } from '../contexts/WebRtcProvider';
@@ -89,58 +90,51 @@
}
const CallInterface = () => {
- const { remoteStreams } = useContext(WebRtcContext);
+ const { localStream, screenShareLocalStream, remoteStreams } = useContext(WebRtcContext);
const {
currentMediaDeviceIds: {
audiooutput: { id: audioOutDeviceId },
},
+ videoStatus,
} = useContext(CallContext);
const remoteVideoRef = useRef<VideoElementWithSinkId | null>(null);
const gridItemRef = useRef<HTMLDivElement | null>(null);
+ const [isLocalVideoZoomed, setIsLocalVideoZoomed] = useState(false);
- useEffect(() => {
- // TODO: For now, `remoteStream` is the first remote stream in the array.
- // There should only be one in the array, but we should make sure this is right.
- const remoteStream = remoteStreams?.at(0);
- if (remoteStream && remoteVideoRef.current) {
- remoteVideoRef.current.srcObject = remoteStream;
+ const stream = useMemo(() => {
+ switch (videoStatus) {
+ case VideoStatus.Camera:
+ return localStream;
+ case VideoStatus.ScreenShare:
+ return screenShareLocalStream;
}
- }, [remoteStreams, remoteVideoRef]);
-
- useEffect(() => {
- if (!audioOutDeviceId) {
- return;
- }
-
- if (remoteVideoRef.current?.setSinkId) {
- // This only work on chrome and other browsers that support `setSinkId`
- // https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId#browser_compatibility
- remoteVideoRef.current.setSinkId(audioOutDeviceId);
- }
- }, [audioOutDeviceId, remoteVideoRef]);
+ }, [videoStatus, localStream, screenShareLocalStream]);
const hasSetSinkId = remoteVideoRef.current?.setSinkId != null;
+ // TODO: For now, `remoteStream` is the first remote stream in the array.
+ // There should only be one in the array, but we should make sure this is right.
+ const remoteStream = remoteStreams?.at(0);
+
return (
<Box display="flex" flexGrow={1}>
- <video
+ <VideoStream
ref={remoteVideoRef}
- autoPlay
+ stream={remoteStream}
+ audioOutDeviceId={audioOutDeviceId}
style={{ zIndex: -1, backgroundColor: 'black', position: 'absolute', height: '100%', width: '100%' }}
/>
<Box flexGrow={1} margin={2} display="flex" flexDirection="column">
{/* Guest video, takes the whole screen */}
<CallInterfaceInformation />
<Box flexGrow={1} marginY={2} position="relative">
- <Box
- sx={{
- position: 'absolute',
- width: '100%',
- height: '100%',
- }}
- >
- <LocalVideo />
- </Box>
+ <VideoOverlay
+ stream={stream}
+ hidden={!stream}
+ muted
+ size={isLocalVideoZoomed ? 'large' : 'medium'}
+ onClick={() => setIsLocalVideoZoomed((v) => !v)}
+ />
</Box>
<Grid container>
<Grid item xs />
@@ -158,45 +152,6 @@
);
};
-const LocalVideo = () => {
- const { localStream, screenShareLocalStream } = useContext(WebRtcContext);
- const { videoStatus } = useContext(CallContext);
- const videoRef = useRef<VideoElementWithSinkId | null>(null);
-
- const stream = useMemo(() => {
- switch (videoStatus) {
- case VideoStatus.Camera:
- return localStream;
- case VideoStatus.ScreenShare:
- return screenShareLocalStream;
- }
- }, [videoStatus, localStream, screenShareLocalStream]);
-
- useEffect(() => {
- if (stream && videoRef.current) {
- videoRef.current.srcObject = stream;
- }
- }, [stream, videoRef]);
-
- return (
- <Draggable bounds="parent" nodeRef={videoRef ?? undefined}>
- <video
- ref={videoRef}
- autoPlay
- muted
- style={{
- position: 'absolute',
- borderRadius: '12px',
- maxHeight: '50%',
- maxWidth: '50%',
- right: 0,
- visibility: stream ? 'visible' : 'hidden',
- }}
- />
- </Draggable>
- );
-};
-
const formatElapsedSeconds = (elapsedSeconds: number): string => {
const seconds = Math.floor(elapsedSeconds % 60);
elapsedSeconds = Math.floor(elapsedSeconds / 60);