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>) => {
debugger;
if (renderedRef && event.target === renderedRef.current) {
return;
}

@ -46,7 +46,9 @@ import { loadDefaultRooms } from '../../opengroup/opengroupV2/ApiUtil';
import { ActionPanelOnionStatusLight } from '../dialog/OnionStatusPathDialog';
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 ourNumber = useSelector(getOurNumber);
const unreadMessageCount = useSelector(getUnreadMessageCount);
@ -288,7 +290,8 @@ export const ActionsPanel = () => {
<>
<ModalContainer />
<CallContainer />
<DraggableCallContainer />
<IncomingCallDialog />
<div className="module-left-pane__sections-container">
<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 Draggable from 'react-draggable';
import Draggable, { DraggableData, DraggableEvent } from 'react-draggable';
// tslint:disable-next-line: no-submodule-imports
import useMountedState from 'react-use/lib/useMountedState';
@ -8,66 +8,44 @@ import styled from 'styled-components';
import _ from 'underscore';
import { CallManager } from '../../../session/utils';
import {
getHasIncomingCall,
getHasIncomingCallFrom,
getHasOngoingCall,
getHasOngoingCallWith,
getSelectedConversationKey,
} from '../../../state/selectors/conversations';
import { SessionButton, SessionButtonColor } from '../SessionButton';
import { SessionWrapperModal } from '../SessionWrapperModal';
import { SessionButton } from '../SessionButton';
export const CallWindow = styled.div`
export const DraggableCallWindow = styled.div`
position: absolute;
z-index: 9;
padding: 1rem;
top: 50vh;
left: 50vw;
transform: translate(-50%, -50%);
box-shadow: var(--color-session-shadow);
max-height: 300px;
width: 300px;
display: flex;
flex-direction: column;
background-color: var(--color-modal-background);
border: var(--session-border);
`;
// similar styling to modal header
const CallWindowHeader = styled.div`
display: flex;
flex-direction: row;
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 StyledVideoElement = styled.video`
padding: 0 1rem;
height: 100%;
width: 100%;
`;
const VideoContainer = styled.div`
position: relative;
max-height: 60vh;
const StyledDraggableVideoElement = styled(StyledVideoElement)`
padding: 0 0;
`;
const VideoContainerRemote = styled.video`
max-height: inherit;
`;
const VideoContainerLocal = styled.video`
max-height: 45%;
max-width: 45%;
position: absolute;
bottom: 0;
right: 0;
const CallWindowControls = styled.div`
padding: 5px;
flex-shrink: 0;
`;
const CallWindowInner = styled.div`
text-align: center;
padding: 1rem;
`;
const DraggableCallWindowInner = styled.div``;
const CallWindowControls = styled.div`
padding: 5px;
const VideoContainer = styled.div`
height: 100%;
width: 50%;
`;
// TODO:
@ -75,94 +53,144 @@ const CallWindowControls = styled.div`
* Add mute input, deafen, end call, possibly add person to call
* duration - look at how duration calculated for recording.
*/
export const CallContainer = () => {
const hasIncomingCall = useSelector(getHasIncomingCall);
const incomingCallProps = useSelector(getHasIncomingCallFrom);
export const DraggableCallContainer = () => {
const ongoingCallProps = useSelector(getHasOngoingCallWith);
const selectedConversationKey = useSelector(getSelectedConversationKey);
const hasOngoingCall = useSelector(getHasOngoingCall);
const ongoingOrIncomingPubkey = ongoingCallProps?.id || incomingCallProps?.id;
const videoRefRemote = useRef<any>();
const videoRefLocal = useRef<any>();
const [positionX, setPositionX] = useState(0);
const [positionY, setPositionY] = useState(0);
const ongoingCallPubkey = ongoingCallProps?.id;
const videoRefRemote = useRef<any>(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(() => {
CallManager.setVideoEventsListener(
(localStream: MediaStream | null, remoteStream: MediaStream | null) => {
if (mountedState() && videoRefRemote?.current && videoRefLocal?.current) {
videoRefLocal.current.srcObject = localStream;
videoRefRemote.current.srcObject = remoteStream;
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]);
//#region input handlers
const handleAcceptIncomingCall = async () => {
if (incomingCallProps?.id) {
await CallManager.USER_acceptIncomingCallRequest(incomingCallProps.id);
const handleEndCall = async () => {
// call method to end call connection
if (ongoingCallPubkey) {
await CallManager.USER_rejectIncomingCallRequest(ongoingCallPubkey);
}
};
const handleDeclineIncomingCall = async () => {
// close the modal
if (incomingCallProps?.id) {
await CallManager.USER_rejectIncomingCallRequest(incomingCallProps.id);
if (!hasOngoingCall || !ongoingCallProps || ongoingCallPubkey === selectedConversationKey) {
return null;
}
console.warn('rendering with pos', positionX, positionY);
return (
<Draggable
handle=".dragHandle"
position={{ x: positionX, y: positionY }}
onStop={(_e: DraggableEvent, data: DraggableData) => {
console.warn('setting position ', { x: data.x, y: data.y });
setPositionX(data.x);
setPositionY(data.y);
}}
>
<DraggableCallWindow className="dragHandle">
<DraggableCallWindowInner>
<StyledDraggableVideoElement ref={videoRefRemote} autoPlay={true} />
</DraggableCallWindowInner>
<CallWindowControls>
<SessionButton text={window.i18n('endCall')} onClick={handleEndCall} />
</CallWindowControls>
</DraggableCallWindow>
</Draggable>
);
};
export const InConvoCallWindow = styled.div`
padding: 1rem;
display: flex;
height: 50%;
/* background-color: var(--color-background-primary); */
background: radial-gradient(black, #505050);
flex-shrink: 0;
min-height: 200px;
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;
}
}
);
}
};
return () => {
CallManager.setVideoEventsListener(null);
};
}, [ongoingCallPubkey, selectedConversationKey]);
const handleEndCall = async () => {
// call method to end call connection
if (ongoingOrIncomingPubkey) {
await CallManager.USER_rejectIncomingCallRequest(ongoingOrIncomingPubkey);
if (ongoingCallPubkey) {
await CallManager.USER_rejectIncomingCallRequest(ongoingCallPubkey);
}
};
//#endregion
if (!hasOngoingCall && !hasIncomingCall) {
if (!hasOngoingCall || !ongoingCallProps || ongoingCallPubkey !== selectedConversationKey) {
return null;
}
if (hasOngoingCall && ongoingCallProps) {
return (
<Draggable handle=".dragHandle">
<CallWindow className="dragHandle">
<CallWindowHeader>Call with: {ongoingCallProps.name}</CallWindowHeader>
<CallWindowInner>
<div>{hasIncomingCall}</div>
<VideoContainer>
<VideoContainerRemote ref={videoRefRemote} autoPlay={true} />
<VideoContainerLocal ref={videoRefLocal} autoPlay={true} />
</VideoContainer>
</CallWindowInner>
<CallWindowControls>
<SessionButton text={window.i18n('endCall')} onClick={handleEndCall} />
</CallWindowControls>
</CallWindow>
</Draggable>
);
}
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;
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 { updateConfirmModal } from '../../../state/ducks/modalDialog';
import { addStagedAttachmentsInConversation } from '../../../state/ducks/stagedAttachments';
import { InConversationCallContainer } from '../calling/CallContainer';
interface State {
showRecordingView: boolean;
@ -263,6 +264,7 @@ export class SessionConversation extends React.Component<Props, State> {
{lightBoxOptions?.media && this.renderLightBox(lightBoxOptions)}
<div className="conversation-messages">
<InConversationCallContainer />
<UnreadAboveIndicator />
<SessionMessagesListContainer messageContainerRef={this.messageContainerRef} />

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

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

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

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

Loading…
Cancel
Save