import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import Draggable, { DraggableData, DraggableEvent } from 'react-draggable'; // tslint:disable-next-line: no-submodule-imports import useMountedState from 'react-use/lib/useMountedState'; import styled from 'styled-components'; import _ from 'underscore'; import { CallManager, ToastUtils } from '../../../session/utils'; import { getHasOngoingCall, getHasOngoingCallWith, getSelectedConversationKey, } from '../../../state/selectors/conversations'; import { openConversationWithMessages } from '../../../state/ducks/conversations'; import { SessionIconButton } from '../icon'; import { animation, contextMenu, Item, Menu } from 'react-contexify'; import { InputItem } from '../../../session/utils/CallManager'; export const DraggableCallWindow = styled.div` position: absolute; z-index: 9; box-shadow: var(--color-session-shadow); max-height: 300px; width: 12vw; display: flex; flex-direction: column; background-color: var(--color-modal-background); border: var(--session-border); `; const StyledVideoElement = styled.video` padding: 0 1rem; height: 100%; width: 100%; `; const StyledDraggableVideoElement = styled(StyledVideoElement)` padding: 0 0; `; const DraggableCallWindowInner = styled.div` cursor: pointer; `; const VideoContainer = styled.div` height: 100%; width: 50%; `; export const InConvoCallWindow = styled.div` padding: 1rem; display: flex; height: 50%; background: radial-gradient(black, #505050); flex-shrink: 0; min-height: 200px; align-items: center; `; const InConvoCallWindowControls = styled.div` position: absolute; bottom: 0px; width: fit-content; padding: 10px; border-radius: 10px; height: 45px; margin-left: auto; margin-right: auto; left: 0; right: 0; transition: all 0.25s ease-in-out; display: flex; background-color: white; align-items: center; justify-content: center; opacity: 0.3; &:hover { opacity: 1; } `; const RelativeCallWindow = styled.div` position: relative; height: 100%; display: flex; flex-grow: 1; `; // TODO: /** * Add mute input, deafen, end call, possibly add person to call * duration - look at how duration calculated for recording. */ export const DraggableCallContainer = () => { const ongoingCallProps = useSelector(getHasOngoingCallWith); const selectedConversationKey = useSelector(getSelectedConversationKey); const hasOngoingCall = useSelector(getHasOngoingCall); const [positionX, setPositionX] = useState(window.innerWidth / 2); const [positionY, setPositionY] = useState(window.innerHeight / 2); const [lastPositionX, setLastPositionX] = useState(0); const [lastPositionY, setLastPositionY] = useState(0); const ongoingCallPubkey = ongoingCallProps?.id; const videoRefRemote = useRef(undefined); const mountedState = useMountedState(); function onWindowResize() { if (positionY + 50 > window.innerHeight || positionX + 50 > window.innerWidth) { setPositionX(window.innerWidth / 2); setPositionY(window.innerHeight / 2); } } useEffect(() => { window.addEventListener('resize', onWindowResize); return () => { window.removeEventListener('resize', onWindowResize); }; }, [positionX, positionY]); useEffect(() => { if (ongoingCallPubkey !== selectedConversationKey) { CallManager.setVideoEventsListener( (_localStream: MediaStream | null, remoteStream: MediaStream | null) => { if (mountedState() && videoRefRemote?.current) { videoRefRemote.current.srcObject = remoteStream; } } ); } return () => { CallManager.setVideoEventsListener(null); }; }, [ongoingCallPubkey, selectedConversationKey]); const openCallingConversation = useCallback(() => { if (ongoingCallPubkey && ongoingCallPubkey !== selectedConversationKey) { void openConversationWithMessages({ conversationKey: ongoingCallPubkey }); } }, [ongoingCallPubkey, selectedConversationKey]); if (!hasOngoingCall || !ongoingCallProps || ongoingCallPubkey === selectedConversationKey) { return null; } return ( { setLastPositionX(data.x); setLastPositionY(data.y); }} onStop={(e: DraggableEvent, data: DraggableData) => { e.stopPropagation(); if (data.x === lastPositionX && data.y === lastPositionY) { // drag did not change anything. Consider this to be a click openCallingConversation(); } setPositionX(data.x); setPositionY(data.y); }} > ); }; const VideoInputMenu = ({ triggerId, camerasList, }: { triggerId: string; camerasList: Array; }) => { return ( {camerasList.map(m => { return ( { void CallManager.selectCameraByDeviceId(m.deviceId); }} > {m.label.substr(0, 40)} ); })} ); }; const AudioInputMenu = ({ triggerId, audioInputsList, }: { triggerId: string; audioInputsList: Array; }) => { return ( {audioInputsList.map(m => { return ( { void CallManager.selectAudioInputByDeviceId(m.deviceId); }} > {m.label.substr(0, 40)} ); })} ); }; export const InConversationCallContainer = () => { const ongoingCallProps = useSelector(getHasOngoingCallWith); const selectedConversationKey = useSelector(getSelectedConversationKey); const hasOngoingCall = useSelector(getHasOngoingCall); const [currentConnectedCameras, setCurrentConnectedCameras] = useState>([]); const [currentConnectedAudioInputs, setCurrentConnectedAudioInputs] = useState>( [] ); const ongoingCallPubkey = ongoingCallProps?.id; const videoRefRemote = useRef(); const videoRefLocal = useRef(); const mountedState = useMountedState(); const videoTriggerId = 'video-menu-trigger-id'; const audioTriggerId = 'audio-menu-trigger-id'; useEffect(() => { if (ongoingCallPubkey === selectedConversationKey) { CallManager.setVideoEventsListener( ( localStream: MediaStream | null, remoteStream: MediaStream | null, camerasList: Array, audioInputList: Array ) => { if (mountedState() && videoRefRemote?.current && videoRefLocal?.current) { videoRefLocal.current.srcObject = localStream; videoRefRemote.current.srcObject = remoteStream; setCurrentConnectedCameras(camerasList); setCurrentConnectedAudioInputs(audioInputList); } } ); } return () => { CallManager.setVideoEventsListener(null); setCurrentConnectedCameras([]); setCurrentConnectedAudioInputs([]); }; }, [ongoingCallPubkey, selectedConversationKey]); const handleEndCall = async () => { // call method to end call connection if (ongoingCallPubkey) { await CallManager.USER_rejectIncomingCallRequest(ongoingCallPubkey); } }; if (!hasOngoingCall || !ongoingCallProps || ongoingCallPubkey !== selectedConversationKey) { return null; } return ( ) => { if (currentConnectedCameras.length === 0) { ToastUtils.pushNoCameraFound(); return; } contextMenu.show({ id: videoTriggerId, event: e, }); }} iconColor="black" /> ) => { if (currentConnectedAudioInputs.length === 0) { ToastUtils.pushNoAudioInputFound(); return; } contextMenu.show({ id: audioTriggerId, event: e, }); }} /> ); };