create a hook for listening for video call events
+ wip fullscreen video calls
This commit is contained in:
parent
a45f5f520a
commit
6a1f575c46
|
@ -48,6 +48,7 @@ import { ActionPanelOnionStatusLight } from '../dialog/OnionStatusPathDialog';
|
|||
import { switchHtmlToDarkTheme, switchHtmlToLightTheme } from '../../state/ducks/SessionTheme';
|
||||
import { DraggableCallContainer } from './calling/CallContainer';
|
||||
import { IncomingCallDialog } from './calling/IncomingCallDialog';
|
||||
import { CallInFullScreenContainer } from './calling/CallInFullScreenContainer';
|
||||
|
||||
const Section = (props: { type: SectionType; avatarPath?: string | null }) => {
|
||||
const ourNumber = useSelector(getOurNumber);
|
||||
|
@ -232,6 +233,16 @@ const doAppStartUp = () => {
|
|||
void getSwarmPollingInstance().start();
|
||||
};
|
||||
|
||||
const CallContainer = () => {
|
||||
return (
|
||||
<>
|
||||
<DraggableCallContainer />
|
||||
<IncomingCallDialog />
|
||||
<CallInFullScreenContainer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* ActionsPanel is the far left banner (not the left pane).
|
||||
* The panel with buttons to switch between the message/contact/settings/theme views
|
||||
|
@ -290,9 +301,7 @@ export const ActionsPanel = () => {
|
|||
<>
|
||||
<ModalContainer />
|
||||
|
||||
<DraggableCallContainer />
|
||||
<IncomingCallDialog />
|
||||
|
||||
<CallContainer />
|
||||
<div className="module-left-pane__sections-container">
|
||||
<Section type={SectionType.Profile} avatarPath={ourPrimaryConversation.avatarPath} />
|
||||
<Section type={SectionType.Message} />
|
||||
|
|
|
@ -2,11 +2,8 @@ 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 } from '../../../session/utils';
|
||||
import {
|
||||
getHasOngoingCall,
|
||||
getHasOngoingCallWith,
|
||||
|
@ -15,7 +12,7 @@ import {
|
|||
import { openConversationWithMessages } from '../../../state/ducks/conversations';
|
||||
import { Avatar, AvatarSize } from '../../Avatar';
|
||||
import { getConversationController } from '../../../session/conversations';
|
||||
import { CallManagerOptionsType } from '../../../session/utils/CallManager';
|
||||
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener';
|
||||
|
||||
export const DraggableCallWindow = styled.div`
|
||||
position: absolute;
|
||||
|
@ -74,11 +71,12 @@ export const DraggableCallContainer = () => {
|
|||
const [positionY, setPositionY] = useState(window.innerHeight / 2);
|
||||
const [lastPositionX, setLastPositionX] = useState(0);
|
||||
const [lastPositionY, setLastPositionY] = useState(0);
|
||||
const [isRemoteVideoMuted, setIsRemoteVideoMuted] = useState(true);
|
||||
|
||||
const ongoingCallPubkey = ongoingCallProps?.id;
|
||||
const { remoteStreamVideoIsMuted, remoteStream } = useVideoCallEventsListener(
|
||||
'DraggableCallContainer'
|
||||
);
|
||||
const videoRefRemote = useRef<any>(undefined);
|
||||
const mountedState = useMountedState();
|
||||
|
||||
function onWindowResize() {
|
||||
if (positionY + 50 > window.innerHeight || positionX + 50 > window.innerWidth) {
|
||||
|
@ -95,22 +93,9 @@ export const DraggableCallContainer = () => {
|
|||
};
|
||||
}, [positionX, positionY]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ongoingCallPubkey !== selectedConversationKey) {
|
||||
CallManager.setVideoEventsListener(
|
||||
({ isRemoteVideoStreamMuted, remoteStream }: CallManagerOptionsType) => {
|
||||
if (mountedState() && videoRefRemote?.current) {
|
||||
videoRefRemote.current.srcObject = remoteStream;
|
||||
setIsRemoteVideoMuted(isRemoteVideoStreamMuted);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return () => {
|
||||
CallManager.setVideoEventsListener(null);
|
||||
};
|
||||
}, [ongoingCallPubkey, selectedConversationKey]);
|
||||
if (videoRefRemote?.current?.srcObject && remoteStream) {
|
||||
videoRefRemote.current.srcObject = remoteStream;
|
||||
}
|
||||
|
||||
const openCallingConversation = useCallback(() => {
|
||||
if (ongoingCallPubkey && ongoingCallPubkey !== selectedConversationKey) {
|
||||
|
@ -152,9 +137,9 @@ export const DraggableCallContainer = () => {
|
|||
<StyledDraggableVideoElement
|
||||
ref={videoRefRemote}
|
||||
autoPlay={true}
|
||||
isVideoMuted={isRemoteVideoMuted}
|
||||
isVideoMuted={remoteStreamVideoIsMuted}
|
||||
/>
|
||||
{isRemoteVideoMuted && (
|
||||
{remoteStreamVideoIsMuted && (
|
||||
<CenteredAvatarInDraggable>
|
||||
<Avatar
|
||||
size={AvatarSize.XL}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
import { setFullScreenCall } from '../../../state/ducks/conversations';
|
||||
import {
|
||||
getCallIsInFullScreen,
|
||||
getHasOngoingCall,
|
||||
getHasOngoingCallWith,
|
||||
} from '../../../state/selectors/conversations';
|
||||
|
||||
const CallInFullScreenVisible = styled.div`
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--color-modal-background);
|
||||
border: var(--session-border);
|
||||
opacity: 0.9;
|
||||
`;
|
||||
|
||||
export const CallInFullScreenContainer = () => {
|
||||
const dispatch = useDispatch();
|
||||
const ongoingCallProps = useSelector(getHasOngoingCallWith);
|
||||
// const selectedConversationKey = useSelector(getSelectedConversationKey);
|
||||
const hasOngoingCall = useSelector(getHasOngoingCall);
|
||||
const hasOngoingCallFullScreen = useSelector(getCallIsInFullScreen);
|
||||
|
||||
// const ongoingCallPubkey = ongoingCallProps?.id;
|
||||
// const ongoingCallUsername = ongoingCallProps?.profileName || ongoingCallProps?.name;
|
||||
// const videoRefRemote = useRef<any>();
|
||||
// const videoRefLocal = useRef<any>();
|
||||
// const mountedState = useMountedState();
|
||||
|
||||
function toggleFullScreenOFF() {
|
||||
dispatch(setFullScreenCall(false));
|
||||
}
|
||||
|
||||
if (!hasOngoingCall || !ongoingCallProps || !hasOngoingCallFullScreen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <CallInFullScreenVisible onClick={toggleFullScreenOFF} />;
|
||||
};
|
|
@ -1,23 +1,24 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import React, { useRef } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
// 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, UserUtils } from '../../../session/utils';
|
||||
import {
|
||||
getHasOngoingCall,
|
||||
getHasOngoingCallWith,
|
||||
getHasOngoingCallWithPubkey,
|
||||
getSelectedConversationKey,
|
||||
} from '../../../state/selectors/conversations';
|
||||
import { SessionIconButton } from '../icon';
|
||||
import { animation, contextMenu, Item, Menu } from 'react-contexify';
|
||||
import { CallManagerOptionsType, InputItem } from '../../../session/utils/CallManager';
|
||||
import { InputItem } from '../../../session/utils/CallManager';
|
||||
import { DropDownAndToggleButton } from '../icon/DropDownAndToggleButton';
|
||||
import { StyledVideoElement } from './CallContainer';
|
||||
import { Avatar, AvatarSize } from '../../Avatar';
|
||||
import { getConversationController } from '../../../session/conversations';
|
||||
import { setFullScreenCall } from '../../../state/ducks/conversations';
|
||||
import { useVideoCallEventsListener } from '../../../hooks/useVideoEventListener';
|
||||
|
||||
const VideoContainer = styled.div`
|
||||
height: 100%;
|
||||
|
@ -70,10 +71,8 @@ const RelativeCallWindow = styled.div`
|
|||
const VideoInputMenu = ({
|
||||
triggerId,
|
||||
camerasList,
|
||||
onUnmute,
|
||||
}: {
|
||||
triggerId: string;
|
||||
onUnmute: () => void;
|
||||
camerasList: Array<InputItem>;
|
||||
}) => {
|
||||
return (
|
||||
|
@ -83,7 +82,6 @@ const VideoInputMenu = ({
|
|||
<Item
|
||||
key={m.deviceId}
|
||||
onClick={() => {
|
||||
onUnmute();
|
||||
void CallManager.selectCameraByDeviceId(m.deviceId);
|
||||
}}
|
||||
>
|
||||
|
@ -98,11 +96,9 @@ const VideoInputMenu = ({
|
|||
const AudioInputMenu = ({
|
||||
triggerId,
|
||||
audioInputsList,
|
||||
onUnmute,
|
||||
}: {
|
||||
triggerId: string;
|
||||
audioInputsList: Array<InputItem>;
|
||||
onUnmute: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<Menu id={triggerId} animation={animation.fade}>
|
||||
|
@ -111,7 +107,6 @@ const AudioInputMenu = ({
|
|||
<Item
|
||||
key={m.deviceId}
|
||||
onClick={() => {
|
||||
onUnmute();
|
||||
void CallManager.selectAudioInputByDeviceId(m.deviceId);
|
||||
}}
|
||||
>
|
||||
|
@ -136,29 +131,129 @@ const CenteredAvatarInConversation = styled.div`
|
|||
align-items: center;
|
||||
`;
|
||||
|
||||
const videoTriggerId = 'video-menu-trigger-id';
|
||||
const audioTriggerId = 'audio-menu-trigger-id';
|
||||
|
||||
const ShowInFullScreenButton = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const showInFullScreen = () => {
|
||||
dispatch(setFullScreenCall(true));
|
||||
};
|
||||
|
||||
return (
|
||||
<SessionIconButton
|
||||
iconSize={60}
|
||||
iconPadding="20px"
|
||||
iconType="fullscreen"
|
||||
backgroundColor="white"
|
||||
borderRadius="50%"
|
||||
onClick={showInFullScreen}
|
||||
iconColor="black"
|
||||
margin="10px"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const HangUpButton = () => {
|
||||
const ongoingCallPubkey = useSelector(getHasOngoingCallWithPubkey);
|
||||
|
||||
const handleEndCall = async () => {
|
||||
// call method to end call connection
|
||||
if (ongoingCallPubkey) {
|
||||
await CallManager.USER_rejectIncomingCallRequest(ongoingCallPubkey);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SessionIconButton
|
||||
iconSize={60}
|
||||
iconPadding="20px"
|
||||
iconType="hangup"
|
||||
backgroundColor="white"
|
||||
borderRadius="50%"
|
||||
onClick={handleEndCall}
|
||||
iconColor="red"
|
||||
margin="10px"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const showAudioInputMenu = (
|
||||
currentConnectedAudioInputs: Array<any>,
|
||||
e: React.MouseEvent<HTMLDivElement>
|
||||
) => {
|
||||
if (currentConnectedAudioInputs.length === 0) {
|
||||
ToastUtils.pushNoAudioInputFound();
|
||||
return;
|
||||
}
|
||||
contextMenu.show({
|
||||
id: audioTriggerId,
|
||||
event: e,
|
||||
});
|
||||
};
|
||||
|
||||
const showVideoInputMenu = (
|
||||
currentConnectedCameras: Array<any>,
|
||||
e: React.MouseEvent<HTMLDivElement>
|
||||
) => {
|
||||
if (currentConnectedCameras.length === 0) {
|
||||
ToastUtils.pushNoCameraFound();
|
||||
return;
|
||||
}
|
||||
contextMenu.show({
|
||||
id: videoTriggerId,
|
||||
event: e,
|
||||
});
|
||||
};
|
||||
|
||||
const handleCameraToggle = async (
|
||||
currentConnectedCameras: Array<InputItem>,
|
||||
localStreamVideoIsMuted: boolean
|
||||
) => {
|
||||
if (!currentConnectedCameras.length) {
|
||||
ToastUtils.pushNoCameraFound();
|
||||
|
||||
return;
|
||||
}
|
||||
if (localStreamVideoIsMuted) {
|
||||
// select the first one
|
||||
await CallManager.selectCameraByDeviceId(currentConnectedCameras[0].deviceId);
|
||||
} else {
|
||||
await CallManager.selectCameraByDeviceId(CallManager.INPUT_DISABLED_DEVICE_ID);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMicrophoneToggle = async (
|
||||
currentConnectedAudioInputs: Array<InputItem>,
|
||||
isAudioMuted: boolean
|
||||
) => {
|
||||
if (!currentConnectedAudioInputs.length) {
|
||||
ToastUtils.pushNoAudioInputFound();
|
||||
|
||||
return;
|
||||
}
|
||||
console.warn('onclick', isAudioMuted);
|
||||
if (isAudioMuted) {
|
||||
// selects the first one
|
||||
await CallManager.selectAudioInputByDeviceId(currentConnectedAudioInputs[0].deviceId);
|
||||
} else {
|
||||
console.warn('onclick was not muted so muting it now');
|
||||
|
||||
await CallManager.selectAudioInputByDeviceId(CallManager.INPUT_DISABLED_DEVICE_ID);
|
||||
}
|
||||
};
|
||||
|
||||
// tslint:disable-next-line: max-func-body-length
|
||||
export const InConversationCallContainer = () => {
|
||||
const ongoingCallProps = useSelector(getHasOngoingCallWith);
|
||||
const selectedConversationKey = useSelector(getSelectedConversationKey);
|
||||
const hasOngoingCall = useSelector(getHasOngoingCall);
|
||||
const [currentConnectedCameras, setCurrentConnectedCameras] = useState<Array<InputItem>>([]);
|
||||
const [currentConnectedAudioInputs, setCurrentConnectedAudioInputs] = useState<Array<InputItem>>(
|
||||
[]
|
||||
);
|
||||
|
||||
const ongoingCallPubkey = ongoingCallProps?.id;
|
||||
const ongoingCallPubkey = useSelector(getHasOngoingCallWithPubkey);
|
||||
const ongoingCallUsername = ongoingCallProps?.profileName || ongoingCallProps?.name;
|
||||
const videoRefRemote = useRef<any>();
|
||||
const videoRefLocal = useRef<any>();
|
||||
const mountedState = useMountedState();
|
||||
|
||||
const [isLocalVideoMuted, setLocalVideoMuted] = useState(true);
|
||||
const [isRemoteVideoMuted, setIsRemoteVideoMuted] = useState(true);
|
||||
|
||||
const [isAudioMuted, setAudioMuted] = useState(false);
|
||||
|
||||
const videoTriggerId = 'video-menu-trigger-id';
|
||||
const audioTriggerId = 'audio-menu-trigger-id';
|
||||
|
||||
const remoteAvatarPath = ongoingCallPubkey
|
||||
? getConversationController()
|
||||
|
@ -175,94 +270,20 @@ export const InConversationCallContainer = () => {
|
|||
.get(ourPubkey)
|
||||
.getAvatarPath();
|
||||
|
||||
useEffect(() => {
|
||||
if (ongoingCallPubkey === selectedConversationKey) {
|
||||
CallManager.setVideoEventsListener((options: CallManagerOptionsType) => {
|
||||
const {
|
||||
audioInputsList,
|
||||
camerasList,
|
||||
isLocalVideoStreamMuted,
|
||||
isRemoteVideoStreamMuted,
|
||||
localStream,
|
||||
remoteStream,
|
||||
} = options;
|
||||
if (mountedState() && videoRefRemote?.current && videoRefLocal?.current) {
|
||||
videoRefLocal.current.srcObject = localStream;
|
||||
setIsRemoteVideoMuted(isRemoteVideoStreamMuted);
|
||||
setLocalVideoMuted(isLocalVideoStreamMuted);
|
||||
videoRefRemote.current.srcObject = remoteStream;
|
||||
const {
|
||||
currentConnectedAudioInputs,
|
||||
currentConnectedCameras,
|
||||
localStream,
|
||||
localStreamVideoIsMuted,
|
||||
remoteStream,
|
||||
remoteStreamVideoIsMuted,
|
||||
isAudioMuted,
|
||||
} = useVideoCallEventsListener('InConversationCallContainer');
|
||||
|
||||
setCurrentConnectedCameras(camerasList);
|
||||
setCurrentConnectedAudioInputs(audioInputsList);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
CallManager.setVideoEventsListener(null);
|
||||
};
|
||||
}, [ongoingCallPubkey, selectedConversationKey]);
|
||||
|
||||
const handleEndCall = async () => {
|
||||
// call method to end call connection
|
||||
if (ongoingCallPubkey) {
|
||||
await CallManager.USER_rejectIncomingCallRequest(ongoingCallPubkey);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCameraToggle = async () => {
|
||||
if (!currentConnectedCameras.length) {
|
||||
ToastUtils.pushNoCameraFound();
|
||||
|
||||
return;
|
||||
}
|
||||
if (isLocalVideoMuted) {
|
||||
// select the first one
|
||||
await CallManager.selectCameraByDeviceId(currentConnectedCameras[0].deviceId);
|
||||
} else {
|
||||
await CallManager.selectCameraByDeviceId(CallManager.INPUT_DISABLED_DEVICE_ID);
|
||||
}
|
||||
|
||||
setLocalVideoMuted(!isLocalVideoMuted);
|
||||
};
|
||||
|
||||
const handleMicrophoneToggle = async () => {
|
||||
if (!currentConnectedAudioInputs.length) {
|
||||
ToastUtils.pushNoAudioInputFound();
|
||||
|
||||
return;
|
||||
}
|
||||
if (isAudioMuted) {
|
||||
// select the first one
|
||||
await CallManager.selectAudioInputByDeviceId(currentConnectedAudioInputs[0].deviceId);
|
||||
} else {
|
||||
await CallManager.selectAudioInputByDeviceId(CallManager.INPUT_DISABLED_DEVICE_ID);
|
||||
}
|
||||
|
||||
setAudioMuted(!isAudioMuted);
|
||||
};
|
||||
|
||||
const showAudioInputMenu = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (currentConnectedAudioInputs.length === 0) {
|
||||
ToastUtils.pushNoAudioInputFound();
|
||||
return;
|
||||
}
|
||||
contextMenu.show({
|
||||
id: audioTriggerId,
|
||||
event: e,
|
||||
});
|
||||
};
|
||||
|
||||
const showVideoInputMenu = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (currentConnectedCameras.length === 0) {
|
||||
ToastUtils.pushNoCameraFound();
|
||||
return;
|
||||
}
|
||||
contextMenu.show({
|
||||
id: videoTriggerId,
|
||||
event: e,
|
||||
});
|
||||
};
|
||||
if (videoRefRemote?.current && videoRefLocal?.current) {
|
||||
videoRefRemote.current.srcObject = remoteStream;
|
||||
videoRefLocal.current.srcObject = localStream;
|
||||
}
|
||||
|
||||
if (!hasOngoingCall || !ongoingCallProps || ongoingCallPubkey !== selectedConversationKey) {
|
||||
return null;
|
||||
|
@ -275,9 +296,9 @@ export const InConversationCallContainer = () => {
|
|||
<StyledVideoElement
|
||||
ref={videoRefRemote}
|
||||
autoPlay={true}
|
||||
isVideoMuted={isRemoteVideoMuted}
|
||||
isVideoMuted={remoteStreamVideoIsMuted}
|
||||
/>
|
||||
{isRemoteVideoMuted && (
|
||||
{remoteStreamVideoIsMuted && (
|
||||
<CenteredAvatarInConversation>
|
||||
<Avatar
|
||||
size={AvatarSize.XL}
|
||||
|
@ -293,9 +314,9 @@ export const InConversationCallContainer = () => {
|
|||
ref={videoRefLocal}
|
||||
autoPlay={true}
|
||||
muted={true}
|
||||
isVideoMuted={isLocalVideoMuted}
|
||||
isVideoMuted={localStreamVideoIsMuted}
|
||||
/>
|
||||
{isLocalVideoMuted && (
|
||||
{localStreamVideoIsMuted && (
|
||||
<CenteredAvatarInConversation>
|
||||
<Avatar
|
||||
size={AvatarSize.XL}
|
||||
|
@ -308,43 +329,31 @@ export const InConversationCallContainer = () => {
|
|||
</VideoContainer>
|
||||
|
||||
<InConvoCallWindowControls>
|
||||
<SessionIconButton
|
||||
iconSize={60}
|
||||
iconPadding="20px"
|
||||
iconType="hangup"
|
||||
backgroundColor="white"
|
||||
borderRadius="50%"
|
||||
onClick={handleEndCall}
|
||||
iconColor="red"
|
||||
margin="10px"
|
||||
/>
|
||||
<HangUpButton />
|
||||
<DropDownAndToggleButton
|
||||
iconType="camera"
|
||||
isMuted={isLocalVideoMuted}
|
||||
onMainButtonClick={handleCameraToggle}
|
||||
onArrowClick={showVideoInputMenu}
|
||||
isMuted={localStreamVideoIsMuted}
|
||||
onMainButtonClick={() => {
|
||||
void handleCameraToggle(currentConnectedCameras, localStreamVideoIsMuted);
|
||||
}}
|
||||
onArrowClick={e => {
|
||||
showVideoInputMenu(currentConnectedCameras, e);
|
||||
}}
|
||||
/>
|
||||
<DropDownAndToggleButton
|
||||
iconType="microphone"
|
||||
isMuted={isAudioMuted}
|
||||
onMainButtonClick={handleMicrophoneToggle}
|
||||
onArrowClick={showAudioInputMenu}
|
||||
onMainButtonClick={() => {
|
||||
void handleMicrophoneToggle(currentConnectedAudioInputs, isAudioMuted);
|
||||
}}
|
||||
onArrowClick={e => {
|
||||
showAudioInputMenu(currentConnectedAudioInputs, e);
|
||||
}}
|
||||
/>
|
||||
<ShowInFullScreenButton />
|
||||
</InConvoCallWindowControls>
|
||||
<VideoInputMenu
|
||||
triggerId={videoTriggerId}
|
||||
onUnmute={() => {
|
||||
setLocalVideoMuted(false);
|
||||
}}
|
||||
camerasList={currentConnectedCameras}
|
||||
/>
|
||||
<AudioInputMenu
|
||||
triggerId={audioTriggerId}
|
||||
onUnmute={() => {
|
||||
setAudioMuted(false);
|
||||
}}
|
||||
audioInputsList={currentConnectedAudioInputs}
|
||||
/>
|
||||
<VideoInputMenu triggerId={videoTriggerId} camerasList={currentConnectedCameras} />
|
||||
<AudioInputMenu triggerId={audioTriggerId} audioInputsList={currentConnectedAudioInputs} />
|
||||
</RelativeCallWindow>
|
||||
</InConvoCallWindow>
|
||||
);
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,68 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
// tslint:disable-next-line: no-submodule-imports
|
||||
import useMountedState from 'react-use/lib/useMountedState';
|
||||
import { CallManager } from '../session/utils';
|
||||
import { CallManagerOptionsType, InputItem } from '../session/utils/CallManager';
|
||||
import {
|
||||
getCallIsInFullScreen,
|
||||
getHasOngoingCallWithPubkey,
|
||||
getSelectedConversationKey,
|
||||
} from '../state/selectors/conversations';
|
||||
|
||||
export function useVideoCallEventsListener(uniqueId: string) {
|
||||
const selectedConversationKey = useSelector(getSelectedConversationKey);
|
||||
const ongoingCallPubkey = useSelector(getHasOngoingCallWithPubkey);
|
||||
const isFullScreen = useSelector(getCallIsInFullScreen);
|
||||
|
||||
const [localStream, setLocalStream] = useState<MediaStream | null>(null);
|
||||
const [remoteStream, setRemoteStream] = useState<MediaStream | null>(null);
|
||||
const [localStreamVideoIsMuted, setLocalStreamVideoIsMuted] = useState(true);
|
||||
const [ourAudioIsMuted, setOurAudioIsMuted] = useState(false);
|
||||
const [remoteStreamVideoIsMuted, setRemoteStreamVideoIsMuted] = useState(true);
|
||||
const mountedState = useMountedState();
|
||||
|
||||
const [currentConnectedCameras, setCurrentConnectedCameras] = useState<Array<InputItem>>([]);
|
||||
const [currentConnectedAudioInputs, setCurrentConnectedAudioInputs] = useState<Array<InputItem>>(
|
||||
[]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (ongoingCallPubkey === selectedConversationKey) {
|
||||
CallManager.addVideoEventsListener(uniqueId, (options: CallManagerOptionsType) => {
|
||||
const {
|
||||
audioInputsList,
|
||||
camerasList,
|
||||
isLocalVideoStreamMuted,
|
||||
isRemoteVideoStreamMuted,
|
||||
localStream: lLocalStream,
|
||||
remoteStream: lRemoteStream,
|
||||
isAudioMuted,
|
||||
} = options;
|
||||
if (mountedState()) {
|
||||
setLocalStream(lLocalStream);
|
||||
setRemoteStream(lRemoteStream);
|
||||
setRemoteStreamVideoIsMuted(isRemoteVideoStreamMuted);
|
||||
setLocalStreamVideoIsMuted(isLocalVideoStreamMuted);
|
||||
setOurAudioIsMuted(isAudioMuted);
|
||||
|
||||
setCurrentConnectedCameras(camerasList);
|
||||
setCurrentConnectedAudioInputs(audioInputsList);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
CallManager.removeVideoEventsListener(uniqueId);
|
||||
};
|
||||
}, [ongoingCallPubkey, selectedConversationKey, isFullScreen]);
|
||||
|
||||
return {
|
||||
currentConnectedAudioInputs,
|
||||
currentConnectedCameras,
|
||||
localStreamVideoIsMuted,
|
||||
remoteStreamVideoIsMuted,
|
||||
localStream,
|
||||
remoteStream,
|
||||
isAudioMuted: ourAudioIsMuted,
|
||||
};
|
||||
}
|
|
@ -9,6 +9,7 @@ import {
|
|||
callConnected,
|
||||
endCall,
|
||||
incomingCall,
|
||||
setFullScreenCall,
|
||||
startingCallWith,
|
||||
} from '../../state/ducks/conversations';
|
||||
import { getConversationController } from '../conversations';
|
||||
|
@ -29,27 +30,44 @@ export type CallManagerOptionsType = {
|
|||
audioInputsList: Array<InputItem>;
|
||||
isLocalVideoStreamMuted: boolean;
|
||||
isRemoteVideoStreamMuted: boolean;
|
||||
isAudioMuted: boolean;
|
||||
};
|
||||
|
||||
export type CallManagerListener = ((options: CallManagerOptionsType) => void) | null;
|
||||
let videoEventsListener: CallManagerListener;
|
||||
const videoEventsListeners: Array<{ id: string; listener: CallManagerListener }> = [];
|
||||
|
||||
function callVideoListener() {
|
||||
if (videoEventsListener) {
|
||||
videoEventsListener({
|
||||
localStream: mediaDevices,
|
||||
remoteStream,
|
||||
camerasList,
|
||||
audioInputsList,
|
||||
isRemoteVideoStreamMuted: remoteVideoStreamIsMuted,
|
||||
isLocalVideoStreamMuted: selectedCameraId === INPUT_DISABLED_DEVICE_ID,
|
||||
function callVideoListeners() {
|
||||
if (videoEventsListeners.length) {
|
||||
videoEventsListeners.forEach(item => {
|
||||
item.listener?.({
|
||||
localStream: mediaDevices,
|
||||
remoteStream,
|
||||
camerasList,
|
||||
audioInputsList,
|
||||
isRemoteVideoStreamMuted: remoteVideoStreamIsMuted,
|
||||
isLocalVideoStreamMuted: selectedCameraId === INPUT_DISABLED_DEVICE_ID,
|
||||
isAudioMuted: selectedAudioInputId === INPUT_DISABLED_DEVICE_ID,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function setVideoEventsListener(listener: CallManagerListener) {
|
||||
videoEventsListener = listener;
|
||||
callVideoListener();
|
||||
export function addVideoEventsListener(uniqueId: string, listener: CallManagerListener) {
|
||||
const indexFound = videoEventsListeners.findIndex(m => m.id === uniqueId);
|
||||
if (indexFound === -1) {
|
||||
videoEventsListeners.push({ id: uniqueId, listener });
|
||||
} else {
|
||||
videoEventsListeners[indexFound].listener = listener;
|
||||
}
|
||||
callVideoListeners();
|
||||
}
|
||||
|
||||
export function removeVideoEventsListener(uniqueId: string) {
|
||||
const indexFound = videoEventsListeners.findIndex(m => m.id === uniqueId);
|
||||
if (indexFound !== -1) {
|
||||
videoEventsListeners.splice(indexFound);
|
||||
}
|
||||
callVideoListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,7 +100,7 @@ const configuration: RTCConfiguration = {
|
|||
};
|
||||
|
||||
let selectedCameraId: string = INPUT_DISABLED_DEVICE_ID;
|
||||
let selectedAudioInputId: string | undefined;
|
||||
let selectedAudioInputId: string = INPUT_DISABLED_DEVICE_ID;
|
||||
let camerasList: Array<InputItem> = [];
|
||||
let audioInputsList: Array<InputItem> = [];
|
||||
|
||||
|
@ -96,7 +114,7 @@ async function getConnectedDevices(type: 'videoinput' | 'audioinput') {
|
|||
if (typeof navigator !== 'undefined') {
|
||||
navigator.mediaDevices.addEventListener('devicechange', async () => {
|
||||
await updateInputLists();
|
||||
callVideoListener();
|
||||
callVideoListeners();
|
||||
});
|
||||
}
|
||||
async function updateInputLists() {
|
||||
|
@ -137,7 +155,7 @@ export async function selectCameraByDeviceId(cameraDeviceId: string) {
|
|||
sender.track.enabled = false;
|
||||
}
|
||||
sendVideoStatusViaDataChannel();
|
||||
callVideoListener();
|
||||
callVideoListeners();
|
||||
return;
|
||||
}
|
||||
if (camerasList.some(m => m.deviceId === cameraDeviceId)) {
|
||||
|
@ -168,13 +186,13 @@ export async function selectCameraByDeviceId(cameraDeviceId: string) {
|
|||
mediaDevices?.addTrack(videoTrack);
|
||||
|
||||
sendVideoStatusViaDataChannel();
|
||||
callVideoListener();
|
||||
callVideoListeners();
|
||||
} else {
|
||||
throw new Error('Failed to get sender for selectCameraByDeviceId ');
|
||||
}
|
||||
} catch (e) {
|
||||
window.log.warn('selectCameraByDeviceId failed with', e.message);
|
||||
callVideoListener();
|
||||
callVideoListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -188,6 +206,7 @@ export async function selectAudioInputByDeviceId(audioInputDeviceId: string) {
|
|||
if (sender?.track) {
|
||||
sender.track.enabled = false;
|
||||
}
|
||||
callVideoListeners();
|
||||
return;
|
||||
}
|
||||
if (audioInputsList.some(m => m.deviceId === audioInputDeviceId)) {
|
||||
|
@ -218,6 +237,8 @@ export async function selectAudioInputByDeviceId(audioInputDeviceId: string) {
|
|||
} catch (e) {
|
||||
window.log.warn('selectAudioInputByDeviceId failed with', e.message);
|
||||
}
|
||||
|
||||
callVideoListeners();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,20 +298,20 @@ async function openMediaDevicesAndAddTracks() {
|
|||
return;
|
||||
}
|
||||
|
||||
const firstAudio = audioInputsList[0].deviceId;
|
||||
const firstVideo = camerasList[0].deviceId;
|
||||
selectedAudioInputId = audioInputsList[0].deviceId;
|
||||
selectedCameraId = INPUT_DISABLED_DEVICE_ID;
|
||||
window.log.info(
|
||||
`openMediaDevices videoDevice:${firstVideo}:${camerasList[0].label} audioDevice:${firstAudio}`
|
||||
`openMediaDevices videoDevice:${selectedCameraId}:${camerasList[0].label} audioDevice:${selectedAudioInputId}`
|
||||
);
|
||||
|
||||
const devicesConfig = {
|
||||
audio: {
|
||||
deviceId: firstAudio,
|
||||
deviceId: selectedAudioInputId,
|
||||
|
||||
echoCancellation: true,
|
||||
},
|
||||
video: {
|
||||
deviceId: firstVideo,
|
||||
deviceId: selectedCameraId,
|
||||
// width: VIDEO_WIDTH,
|
||||
// height: Math.floor(VIDEO_WIDTH * VIDEO_RATIO),
|
||||
},
|
||||
|
@ -309,7 +330,7 @@ async function openMediaDevicesAndAddTracks() {
|
|||
ToastUtils.pushVideoCallPermissionNeeded();
|
||||
closeVideoCall();
|
||||
}
|
||||
callVideoListener();
|
||||
callVideoListeners();
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: function-name
|
||||
|
@ -426,16 +447,9 @@ function closeVideoCall() {
|
|||
mediaDevices = null;
|
||||
remoteStream = null;
|
||||
selectedCameraId = INPUT_DISABLED_DEVICE_ID;
|
||||
if (videoEventsListener) {
|
||||
videoEventsListener({
|
||||
audioInputsList: [],
|
||||
camerasList: [],
|
||||
isLocalVideoStreamMuted: true,
|
||||
isRemoteVideoStreamMuted: true,
|
||||
localStream: null,
|
||||
remoteStream: null,
|
||||
});
|
||||
}
|
||||
selectedAudioInputId = INPUT_DISABLED_DEVICE_ID;
|
||||
callVideoListeners();
|
||||
window.inboxStore?.dispatch(setFullScreenCall(false));
|
||||
}
|
||||
|
||||
function onDataChannelReceivedMessage(ev: MessageEvent<string>) {
|
||||
|
@ -445,7 +459,7 @@ function onDataChannelReceivedMessage(ev: MessageEvent<string>) {
|
|||
if (parsed.video !== undefined) {
|
||||
remoteVideoStreamIsMuted = !Boolean(parsed.video);
|
||||
}
|
||||
callVideoListener();
|
||||
callVideoListeners();
|
||||
} catch (e) {
|
||||
window.log.warn('onDataChannelReceivedMessage Could not parse data in event', ev);
|
||||
}
|
||||
|
@ -489,11 +503,11 @@ function createOrGetPeerConnection(withPubkey: string, createDataChannel: boolea
|
|||
peerConnection.ontrack = event => {
|
||||
event.track.onunmute = () => {
|
||||
remoteStream?.addTrack(event.track);
|
||||
callVideoListener();
|
||||
callVideoListeners();
|
||||
};
|
||||
event.track.onmute = () => {
|
||||
remoteStream?.removeTrack(event.track);
|
||||
callVideoListener();
|
||||
callVideoListeners();
|
||||
};
|
||||
};
|
||||
peerConnection.onconnectionstatechange = () => {
|
||||
|
@ -604,16 +618,7 @@ export function handleCallTypeEndCall(sender: string) {
|
|||
// we just got a end call event from whoever we are in a call with
|
||||
if (callingConvos.length === 1 && callingConvos[0].id === sender) {
|
||||
closeVideoCall();
|
||||
if (videoEventsListener) {
|
||||
videoEventsListener({
|
||||
audioInputsList: [],
|
||||
camerasList: [],
|
||||
isLocalVideoStreamMuted: true,
|
||||
isRemoteVideoStreamMuted: true,
|
||||
localStream: null,
|
||||
remoteStream: null,
|
||||
});
|
||||
}
|
||||
|
||||
window.inboxStore?.dispatch(endCall({ pubkey: sender }));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -277,6 +277,7 @@ export type ConversationsStateType = {
|
|||
quotedMessage?: ReplyingToMessageProps;
|
||||
areMoreMessagesBeingFetched: boolean;
|
||||
haveDoneFirstScroll: boolean;
|
||||
callIsInFullScreen: boolean;
|
||||
|
||||
showScrollButton: boolean;
|
||||
animateQuotedMessageId?: string;
|
||||
|
@ -371,6 +372,7 @@ export function getEmptyConversationState(): ConversationsStateType {
|
|||
mentionMembers: [],
|
||||
firstUnreadMessageId: undefined,
|
||||
haveDoneFirstScroll: false,
|
||||
callIsInFullScreen: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -696,6 +698,8 @@ const conversationsSlice = createSlice({
|
|||
|
||||
return {
|
||||
conversationLookup: state.conversationLookup,
|
||||
callIsInFullScreen: state.callIsInFullScreen,
|
||||
|
||||
selectedConversation: action.payload.id,
|
||||
areMoreMessagesBeingFetched: false,
|
||||
messages: action.payload.initialMessages,
|
||||
|
@ -850,6 +854,10 @@ const conversationsSlice = createSlice({
|
|||
void foundConvo.commit();
|
||||
return state;
|
||||
},
|
||||
setFullScreenCall(state: ConversationsStateType, action: PayloadAction<boolean>) {
|
||||
state.callIsInFullScreen = action.payload;
|
||||
return state;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder: any) => {
|
||||
// Add reducers for additional action types here, and handle loading state as needed
|
||||
|
@ -915,6 +923,7 @@ export const {
|
|||
answerCall,
|
||||
callConnected,
|
||||
startingCallWith,
|
||||
setFullScreenCall,
|
||||
} = actions;
|
||||
|
||||
export async function openConversationWithMessages(args: {
|
||||
|
|
|
@ -122,6 +122,16 @@ export const getHasOngoingCall = createSelector(
|
|||
(withConvo: ReduxConversationType | undefined): boolean => !!withConvo
|
||||
);
|
||||
|
||||
export const getHasOngoingCallWithPubkey = createSelector(
|
||||
getHasOngoingCallWith,
|
||||
(withConvo: ReduxConversationType | undefined): string | undefined => withConvo?.id
|
||||
);
|
||||
|
||||
export const getCallIsInFullScreen = createSelector(
|
||||
getConversations,
|
||||
(state: ConversationsStateType): boolean => state.callIsInFullScreen
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns true if the current conversation selected is a group conversation.
|
||||
* Returns false if the current conversation selected is not a group conversation, or none are selected
|
||||
|
|
Loading…
Reference in New Issue