make a draggable or in conversation call container

pull/1969/head
Audric Ackermann 4 years ago
parent b05afc7c3f
commit b85425ff83
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4

@ -279,7 +279,6 @@ export const Lightbox = (props: Props) => {
}; };
const onContainerClick = (event: React.MouseEvent<HTMLDivElement>) => { const onContainerClick = (event: React.MouseEvent<HTMLDivElement>) => {
debugger;
if (renderedRef && event.target === renderedRef.current) { if (renderedRef && event.target === renderedRef.current) {
return; return;
} }

@ -46,7 +46,9 @@ import { loadDefaultRooms } from '../../opengroup/opengroupV2/ApiUtil';
import { ActionPanelOnionStatusLight } from '../dialog/OnionStatusPathDialog'; import { ActionPanelOnionStatusLight } from '../dialog/OnionStatusPathDialog';
import { switchHtmlToDarkTheme, switchHtmlToLightTheme } from '../../state/ducks/SessionTheme'; import { switchHtmlToDarkTheme, switchHtmlToLightTheme } from '../../state/ducks/SessionTheme';
import { CallContainer } from './calling/CallContainer'; import { DraggableCallContainer } from './calling/CallContainer';
import { IncomingCallDialog } from './calling/IncomingCallDialog';
const Section = (props: { type: SectionType; avatarPath?: string | null }) => { const Section = (props: { type: SectionType; avatarPath?: string | null }) => {
const ourNumber = useSelector(getOurNumber); const ourNumber = useSelector(getOurNumber);
const unreadMessageCount = useSelector(getUnreadMessageCount); const unreadMessageCount = useSelector(getUnreadMessageCount);
@ -288,7 +290,8 @@ export const ActionsPanel = () => {
<> <>
<ModalContainer /> <ModalContainer />
<CallContainer /> <DraggableCallContainer />
<IncomingCallDialog />
<div className="module-left-pane__sections-container"> <div className="module-left-pane__sections-container">
<Section type={SectionType.Profile} avatarPath={ourPrimaryConversation.avatarPath} /> <Section type={SectionType.Profile} avatarPath={ourPrimaryConversation.avatarPath} />

@ -1,6 +1,6 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import Draggable from 'react-draggable'; import Draggable, { DraggableData, DraggableEvent } from 'react-draggable';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import useMountedState from 'react-use/lib/useMountedState'; import useMountedState from 'react-use/lib/useMountedState';
@ -8,66 +8,44 @@ import styled from 'styled-components';
import _ from 'underscore'; import _ from 'underscore';
import { CallManager } from '../../../session/utils'; import { CallManager } from '../../../session/utils';
import { import {
getHasIncomingCall,
getHasIncomingCallFrom,
getHasOngoingCall, getHasOngoingCall,
getHasOngoingCallWith, getHasOngoingCallWith,
getSelectedConversationKey,
} from '../../../state/selectors/conversations'; } from '../../../state/selectors/conversations';
import { SessionButton, SessionButtonColor } from '../SessionButton'; import { SessionButton } from '../SessionButton';
import { SessionWrapperModal } from '../SessionWrapperModal';
export const CallWindow = styled.div` export const DraggableCallWindow = styled.div`
position: absolute; position: absolute;
z-index: 9; z-index: 9;
padding: 1rem; box-shadow: var(--color-session-shadow);
top: 50vh; max-height: 300px;
left: 50vw; width: 300px;
transform: translate(-50%, -50%);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: var(--color-modal-background); background-color: var(--color-modal-background);
border: var(--session-border); border: var(--session-border);
`; `;
// similar styling to modal header const StyledVideoElement = styled.video`
const CallWindowHeader = styled.div` padding: 0 1rem;
display: flex; height: 100%;
flex-direction: row; width: 100%;
justify-content: space-between;
align-self: center;
padding: $session-margin-lg;
font-family: $session-font-default;
text-align: center;
line-height: 18px;
font-size: $session-font-md;
font-weight: 700;
`; `;
const VideoContainer = styled.div` const StyledDraggableVideoElement = styled(StyledVideoElement)`
position: relative; padding: 0 0;
max-height: 60vh;
`; `;
const VideoContainerRemote = styled.video` const CallWindowControls = styled.div`
max-height: inherit; padding: 5px;
`; flex-shrink: 0;
const VideoContainerLocal = styled.video`
max-height: 45%;
max-width: 45%;
position: absolute;
bottom: 0;
right: 0;
`; `;
const CallWindowInner = styled.div` const DraggableCallWindowInner = styled.div``;
text-align: center;
padding: 1rem;
`;
const CallWindowControls = styled.div` const VideoContainer = styled.div`
padding: 5px; height: 100%;
width: 50%;
`; `;
// TODO: // TODO:
@ -75,94 +53,144 @@ const CallWindowControls = styled.div`
* Add mute input, deafen, end call, possibly add person to call * Add mute input, deafen, end call, possibly add person to call
* duration - look at how duration calculated for recording. * duration - look at how duration calculated for recording.
*/ */
export const CallContainer = () => { export const DraggableCallContainer = () => {
const hasIncomingCall = useSelector(getHasIncomingCall);
const incomingCallProps = useSelector(getHasIncomingCallFrom);
const ongoingCallProps = useSelector(getHasOngoingCallWith); const ongoingCallProps = useSelector(getHasOngoingCallWith);
const selectedConversationKey = useSelector(getSelectedConversationKey);
const hasOngoingCall = useSelector(getHasOngoingCall); const hasOngoingCall = useSelector(getHasOngoingCall);
const ongoingOrIncomingPubkey = ongoingCallProps?.id || incomingCallProps?.id; const [positionX, setPositionX] = useState(0);
const videoRefRemote = useRef<any>(); const [positionY, setPositionY] = useState(0);
const videoRefLocal = useRef<any>();
const ongoingCallPubkey = ongoingCallProps?.id;
const videoRefRemote = useRef<any>(undefined);
const mountedState = useMountedState(); 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(() => { useEffect(() => {
if (ongoingCallPubkey !== selectedConversationKey) {
CallManager.setVideoEventsListener( CallManager.setVideoEventsListener(
(localStream: MediaStream | null, remoteStream: MediaStream | null) => { (_localStream: MediaStream | null, remoteStream: MediaStream | null) => {
if (mountedState() && videoRefRemote?.current && videoRefLocal?.current) { if (mountedState() && videoRefRemote?.current) {
videoRefLocal.current.srcObject = localStream;
videoRefRemote.current.srcObject = remoteStream; videoRefRemote.current.srcObject = remoteStream;
} }
} }
); );
}
return () => { return () => {
CallManager.setVideoEventsListener(null); CallManager.setVideoEventsListener(null);
}; };
}, []); }, [ongoingCallPubkey, selectedConversationKey]);
//#region input handlers
const handleAcceptIncomingCall = async () => {
if (incomingCallProps?.id) {
await CallManager.USER_acceptIncomingCallRequest(incomingCallProps.id);
}
};
const handleDeclineIncomingCall = async () => {
// close the modal
if (incomingCallProps?.id) {
await CallManager.USER_rejectIncomingCallRequest(incomingCallProps.id);
}
};
const handleEndCall = async () => { const handleEndCall = async () => {
// call method to end call connection // call method to end call connection
if (ongoingOrIncomingPubkey) { if (ongoingCallPubkey) {
await CallManager.USER_rejectIncomingCallRequest(ongoingOrIncomingPubkey); await CallManager.USER_rejectIncomingCallRequest(ongoingCallPubkey);
} }
}; };
//#endregion if (!hasOngoingCall || !ongoingCallProps || ongoingCallPubkey === selectedConversationKey) {
if (!hasOngoingCall && !hasIncomingCall) {
return null; return null;
} }
if (hasOngoingCall && ongoingCallProps) { console.warn('rendering with pos', positionX, positionY);
return (
<Draggable handle=".dragHandle">
<CallWindow className="dragHandle">
<CallWindowHeader>Call with: {ongoingCallProps.name}</CallWindowHeader>
<CallWindowInner> return (
<div>{hasIncomingCall}</div> <Draggable
<VideoContainer> handle=".dragHandle"
<VideoContainerRemote ref={videoRefRemote} autoPlay={true} /> position={{ x: positionX, y: positionY }}
<VideoContainerLocal ref={videoRefLocal} autoPlay={true} /> onStop={(_e: DraggableEvent, data: DraggableData) => {
</VideoContainer> console.warn('setting position ', { x: data.x, y: data.y });
</CallWindowInner> setPositionX(data.x);
setPositionY(data.y);
}}
>
<DraggableCallWindow className="dragHandle">
<DraggableCallWindowInner>
<StyledDraggableVideoElement ref={videoRefRemote} autoPlay={true} />
</DraggableCallWindowInner>
<CallWindowControls> <CallWindowControls>
<SessionButton text={window.i18n('endCall')} onClick={handleEndCall} /> <SessionButton text={window.i18n('endCall')} onClick={handleEndCall} />
</CallWindowControls> </CallWindowControls>
</CallWindow> </DraggableCallWindow>
</Draggable> </Draggable>
); );
} };
if (hasIncomingCall) { export const InConvoCallWindow = styled.div`
return ( padding: 1rem;
<SessionWrapperModal title={window.i18n('incomingCall')}> display: flex;
<div className="session-modal__button-group"> height: 50%;
<SessionButton text={window.i18n('decline')} onClick={handleDeclineIncomingCall} />
<SessionButton /* background-color: var(--color-background-primary); */
text={window.i18n('accept')}
onClick={handleAcceptIncomingCall} background: radial-gradient(black, #505050);
buttonColor={SessionButtonColor.Green}
/> flex-shrink: 0;
</div> min-height: 200px;
</SessionWrapperModal> align-items: center;
`;
export const InConversationCallContainer = () => {
const ongoingCallProps = useSelector(getHasOngoingCallWith);
const selectedConversationKey = useSelector(getSelectedConversationKey);
const hasOngoingCall = useSelector(getHasOngoingCall);
const ongoingCallPubkey = ongoingCallProps?.id;
const videoRefRemote = useRef<any>();
const videoRefLocal = useRef<any>();
const mountedState = useMountedState();
useEffect(() => {
if (ongoingCallPubkey === selectedConversationKey) {
CallManager.setVideoEventsListener(
(localStream: MediaStream | null, remoteStream: MediaStream | null) => {
if (mountedState() && videoRefRemote?.current && videoRefLocal?.current) {
videoRefLocal.current.srcObject = localStream;
videoRefRemote.current.srcObject = remoteStream;
}
}
); );
} }
// display spinner while connecting
return () => {
CallManager.setVideoEventsListener(null);
};
}, [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 null;
}
return (
<InConvoCallWindow>
<VideoContainer>
<StyledVideoElement ref={videoRefRemote} autoPlay={true} />
</VideoContainer>
<VideoContainer>
<StyledVideoElement ref={videoRefLocal} autoPlay={true} />
</VideoContainer>
</InConvoCallWindow>
);
}; };

@ -0,0 +1,67 @@
import React from 'react';
import { useSelector } from 'react-redux';
import styled from 'styled-components';
import _ from 'underscore';
import { CallManager } from '../../../session/utils';
import { getHasIncomingCall, getHasIncomingCallFrom } from '../../../state/selectors/conversations';
import { SessionButton, SessionButtonColor } from '../SessionButton';
import { SessionWrapperModal } from '../SessionWrapperModal';
export const CallWindow = styled.div`
position: absolute;
z-index: 9;
padding: 1rem;
top: 50vh;
left: 50vw;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
background-color: var(--color-modal-background);
border: var(--session-border);
`;
// TODO:
/**
* Add mute input, deafen, end call, possibly add person to call
* duration - look at how duration calculated for recording.
*/
export const IncomingCallDialog = () => {
const hasIncomingCall = useSelector(getHasIncomingCall);
const incomingCallProps = useSelector(getHasIncomingCallFrom);
//#region input handlers
const handleAcceptIncomingCall = async () => {
if (incomingCallProps?.id) {
await CallManager.USER_acceptIncomingCallRequest(incomingCallProps.id);
}
};
const handleDeclineIncomingCall = async () => {
// close the modal
if (incomingCallProps?.id) {
await CallManager.USER_rejectIncomingCallRequest(incomingCallProps.id);
}
};
if (!hasIncomingCall) {
return null;
}
if (hasIncomingCall) {
return (
<SessionWrapperModal title={window.i18n('incomingCall')}>
<div className="session-modal__button-group">
<SessionButton text={window.i18n('decline')} onClick={handleDeclineIncomingCall} />
<SessionButton
text={window.i18n('accept')}
onClick={handleAcceptIncomingCall}
buttonColor={SessionButtonColor.Green}
/>
</div>
</SessionWrapperModal>
);
}
// display spinner while connecting
return null;
};

@ -43,6 +43,7 @@ import {
import { SessionButtonColor } from '../SessionButton'; import { SessionButtonColor } from '../SessionButton';
import { updateConfirmModal } from '../../../state/ducks/modalDialog'; import { updateConfirmModal } from '../../../state/ducks/modalDialog';
import { addStagedAttachmentsInConversation } from '../../../state/ducks/stagedAttachments'; import { addStagedAttachmentsInConversation } from '../../../state/ducks/stagedAttachments';
import { InConversationCallContainer } from '../calling/CallContainer';
interface State { interface State {
showRecordingView: boolean; showRecordingView: boolean;
@ -263,6 +264,7 @@ export class SessionConversation extends React.Component<Props, State> {
{lightBoxOptions?.media && this.renderLightBox(lightBoxOptions)} {lightBoxOptions?.media && this.renderLightBox(lightBoxOptions)}
<div className="conversation-messages"> <div className="conversation-messages">
<InConversationCallContainer />
<UnreadAboveIndicator /> <UnreadAboveIndicator />
<SessionMessagesListContainer messageContainerRef={this.messageContainerRef} /> <SessionMessagesListContainer messageContainerRef={this.messageContainerRef} />

@ -19,7 +19,7 @@ const QuotedMessageCompositionReply = styled.div`
background: var(--color-quote-bottom-bar-background); background: var(--color-quote-bottom-bar-background);
border-radius: var(--margins-sm); border-radius: var(--margins-sm);
padding: var(--margins-xs); padding: var(--margins-xs);
box-shadow: --color-session-shadow; box-shadow: var(--color-session-shadow);
margin: var(--margins-xs); margin: var(--margins-xs);
`; `;

@ -324,7 +324,9 @@ export function getMarkAllReadMenuItem(conversationId: string): JSX.Element | nu
export function getStartCallMenuItem(conversationId: string): JSX.Element | null { export function getStartCallMenuItem(conversationId: string): JSX.Element | null {
if (window?.lokiFeatureFlags.useCallMessage) { if (window?.lokiFeatureFlags.useCallMessage) {
const canCall = !(useSelector(getHasIncomingCall) || useSelector(getHasOngoingCall)); const hasIncomingCall = useSelector(getHasIncomingCall);
const hasOngoingCall = useSelector(getHasOngoingCall);
const canCall = !(hasIncomingCall || hasOngoingCall);
return ( return (
<Item <Item
onClick={async () => { onClick={async () => {

@ -1,7 +1,7 @@
import { isNumber, omit } from 'lodash'; import { filter, isNumber, omit } from 'lodash';
import _ from 'lodash';
// tslint:disable-next-line: no-submodule-imports // tslint:disable-next-line: no-submodule-imports
import { default as getGuid } from 'uuid/v4'; import { default as getGuid } from 'uuid/v4';
import * as Constants from '../constants';
import { import {
getMessageById, getMessageById,
getNextAttachmentDownloadJobs, getNextAttachmentDownloadJobs,
@ -17,16 +17,13 @@ import { downloadAttachment, downloadAttachmentOpenGroupV2 } from '../../receive
// this cause issues if we increment that value to > 1. // this cause issues if we increment that value to > 1.
const MAX_ATTACHMENT_JOB_PARALLELISM = 3; const MAX_ATTACHMENT_JOB_PARALLELISM = 3;
const SECOND = 1000; const TICK_INTERVAL = Constants.DURATION.MINUTES;
const MINUTE = SECOND * 60;
const HOUR = MINUTE * 60;
const TICK_INTERVAL = MINUTE;
// tslint:disable: function-name // tslint:disable: function-name
const RETRY_BACKOFF = { const RETRY_BACKOFF = {
1: SECOND * 30, 1: Constants.DURATION.SECONDS * 30,
2: MINUTE * 30, 2: Constants.DURATION.MINUTES * 30,
3: HOUR * 6, 3: Constants.DURATION.HOURS * 6,
}; };
let enabled = false; let enabled = false;
@ -113,7 +110,7 @@ async function _maybeStartJob() {
return; return;
} }
const nextJobsWithoutCurrentlyRunning = _.filter( const nextJobsWithoutCurrentlyRunning = filter(
nextJobs, nextJobs,
j => _activeAttachmentDownloadJobs[j.id] === undefined j => _activeAttachmentDownloadJobs[j.id] === undefined
); );

@ -25,6 +25,9 @@ let videoEventsListener: CallManagerListener;
export function setVideoEventsListener(listener: CallManagerListener) { export function setVideoEventsListener(listener: CallManagerListener) {
videoEventsListener = listener; videoEventsListener = listener;
if (videoEventsListener) {
videoEventsListener(mediaDevices, remoteStream);
}
} }
/** /**
@ -33,6 +36,8 @@ export function setVideoEventsListener(listener: CallManagerListener) {
const callCache = new Map<string, Array<SignalService.CallMessage>>(); const callCache = new Map<string, Array<SignalService.CallMessage>>();
let peerConnection: RTCPeerConnection | null; let peerConnection: RTCPeerConnection | null;
let remoteStream: MediaStream | null;
let mediaDevices: MediaStream | null;
const ENABLE_VIDEO = true; const ENABLE_VIDEO = true;
@ -66,12 +71,13 @@ export async function USER_callRecipient(recipient: string) {
} }
peerConnection = new RTCPeerConnection(configuration); peerConnection = new RTCPeerConnection(configuration);
let mediaDevices: any;
try { try {
mediaDevices = await openMediaDevices(); mediaDevices = await openMediaDevices();
mediaDevices.getTracks().map((track: any) => { mediaDevices.getTracks().map((track: any) => {
window.log.info('USER_callRecipient adding track: ', track); window.log.info('USER_callRecipient adding track: ', track);
if (mediaDevices) {
peerConnection?.addTrack(track, mediaDevices); peerConnection?.addTrack(track, mediaDevices);
}
}); });
} catch (err) { } catch (err) {
ToastUtils.pushMicAndCameraPermissionNeeded(() => { ToastUtils.pushMicAndCameraPermissionNeeded(() => {
@ -131,7 +137,7 @@ export async function USER_callRecipient(recipient: string) {
} }
}; };
const remoteStream = new MediaStream(); remoteStream = new MediaStream();
if (videoEventsListener) { if (videoEventsListener) {
videoEventsListener(mediaDevices, remoteStream); videoEventsListener(mediaDevices, remoteStream);
@ -141,7 +147,9 @@ export async function USER_callRecipient(recipient: string) {
if (videoEventsListener) { if (videoEventsListener) {
videoEventsListener(mediaDevices, remoteStream); videoEventsListener(mediaDevices, remoteStream);
} }
if (remoteStream) {
remoteStream.addTrack(event.track); remoteStream.addTrack(event.track);
}
}); });
const offerDescription = await peerConnection.createOffer({ const offerDescription = await peerConnection.createOffer({
@ -254,12 +262,14 @@ export async function USER_acceptIncomingCallRequest(fromSender: string) {
peerConnection = null; peerConnection = null;
} }
peerConnection = new RTCPeerConnection(configuration); peerConnection = new RTCPeerConnection(configuration);
const mediaDevices = await openMediaDevices(); mediaDevices = await openMediaDevices();
mediaDevices.getTracks().map(track => { mediaDevices.getTracks().map(track => {
// window.log.info('USER_acceptIncomingCallRequest adding track ', track); // window.log.info('USER_acceptIncomingCallRequest adding track ', track);
if (mediaDevices) {
peerConnection?.addTrack(track, mediaDevices); peerConnection?.addTrack(track, mediaDevices);
}
}); });
const remoteStream = new MediaStream(); remoteStream = new MediaStream();
peerConnection.addEventListener('icecandidate', event => { peerConnection.addEventListener('icecandidate', event => {
window.log?.warn('icecandidateerror:', event); window.log?.warn('icecandidateerror:', event);
@ -279,7 +289,7 @@ export async function USER_acceptIncomingCallRequest(fromSender: string) {
if (videoEventsListener) { if (videoEventsListener) {
videoEventsListener(mediaDevices, remoteStream); videoEventsListener(mediaDevices, remoteStream);
} }
remoteStream.addTrack(event.track); remoteStream?.addTrack(event.track);
}); });
peerConnection.addEventListener('connectionstatechange', _event => { peerConnection.addEventListener('connectionstatechange', _event => {
window.log.info( window.log.info(
@ -363,6 +373,8 @@ export function handleEndCallMessage(sender: string) {
if (videoEventsListener) { if (videoEventsListener) {
videoEventsListener(null, null); videoEventsListener(null, null);
} }
mediaDevices = null;
remoteStream = null;
// //
// FIXME audric trigger UI cleanup // FIXME audric trigger UI cleanup
window.inboxStore?.dispatch(endCall({ pubkey: sender })); window.inboxStore?.dispatch(endCall({ pubkey: sender }));

Loading…
Cancel
Save