Skip to content
This repository has been archived by the owner on Jun 30, 2022. It is now read-only.

[NEW] Audio and Video calling in Livechat using WebRTC #646

Merged
merged 13 commits into from
Nov 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .loki/reference/chrome_Components_Icons_all.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .loki/reference/chrome_Components_Icons_phone.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added .loki/reference/chrome_Components_Icons_video.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
},
"dependencies": {
"@kossnocorp/desvg": "^0.2.0",
"@rocket.chat/sdk": "^1.0.0-alpha.41",
"@rocket.chat/sdk": "^1.0.0-alpha.42",
"@rocket.chat/ui-kit": "^0.14.1",
"css-vars-ponyfill": "^2.3.2",
"date-fns": "^2.15.0",
Expand Down
32 changes: 32 additions & 0 deletions src/components/Calls/CallIFrame.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { h } from 'preact';

import { Livechat } from '../../api';
import store from '../../store';
import { createClassName } from '../helpers';
import { CallStatus } from './CallStatus';
import styles from './styles.scss';


export const CallIframe = () => {
const { token, room, incomingCallAlert, ongoingCall } = store.state;
const url = `${ Livechat.client.host }/meet/${ room._id }?token=${ token }&layout=embedded`;
window.handleIframeClose = () => store.setState({ incomingCallAlert: { ...incomingCallAlert, show: false } });
window.expandCall = () => {
window.open(
`${ Livechat.client.host }/meet/${ room._id }?token=${ token }`,
room._id,
);
return store.setState({
incomingCallAlert: { ...incomingCallAlert, show: false },
ongoingCall: {
...ongoingCall,
callStatus: CallStatus.IN_PROGRESS_DIFFERENT_TAB,
},
});
};
return (
<div className={createClassName(styles, 'call-iframe')}>
<iframe className={createClassName(styles, 'call-iframe__content')} allow='camera;microphone' src={url} />
</div>
);
};
111 changes: 111 additions & 0 deletions src/components/Calls/CallNotification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { h } from 'preact';
import { useState } from 'preact/compat';

import { Livechat } from '../../api';
import I18n from '../../i18n';
import PhoneAccept from '../../icons/phone.svg';
import PhoneDecline from '../../icons/phoneOff.svg';
import constants from '../../lib/constants';
import store from '../../store';
import { Avatar } from '../Avatar';
import { Button } from '../Button';
import { createClassName, getAvatarUrl, isMobileDevice } from '../helpers';
import { CallStatus } from './CallStatus';
import styles from './styles.scss';


export const CallNotification = ({
callProvider,
callerUsername,
url,
dispatch,
time,
rid,
callId,
}) => {
const [show, setShow] = useState(true);

const callInNewTab = async () => {
const { token } = store.state;
const url = `${ Livechat.client.host }/meet/${ rid }?token=${ token }`;
await dispatch({
ongoingCall: {
callStatus: CallStatus.IN_PROGRESS_DIFFERENT_TAB,
time: { time },
},
incomingCallAlert: {
show: false,
callProvider,
},
});
window.open(url, rid);
};

const acceptClick = async () => {
setShow(!{ show });
murtaza98 marked this conversation as resolved.
Show resolved Hide resolved
switch (callProvider) {
case constants.jitsiCallStartedMessageType: {
window.open(url, rid);
await dispatch({
incomingCallAlert: { show: false, url, callProvider },
ongoingCall: {
callStatus: CallStatus.IN_PROGRESS_DIFFERENT_TAB,
time: { time },
},
});
break;
}
case constants.webRTCCallStartedMessageType: {
await Livechat.updateCallStatus(CallStatus.IN_PROGRESS, rid, callId);
if (isMobileDevice()) {
callInNewTab();
break;
}
await dispatch({ ongoingCall: { callStatus: CallStatus.IN_PROGRESS_SAME_TAB, time: { time } } });
break;
}
}
};

const declineClick = async () => {
await Livechat.updateCallStatus(CallStatus.DECLINED, rid, callId);
await Livechat.notifyCallDeclined(rid);
await dispatch({
incomingCallAlert: null,
ongoingCall: {
callStatus: CallStatus.DECLINED,
time: { time },
},
});
};

return (
<div className={createClassName(styles, 'call-notification')}>
{
show && (
<div className = { createClassName(styles, 'call-notification__content') }>
<div className = { createClassName(styles, 'call-notification__content-avatar') }>
<Avatar src = { getAvatarUrl(callerUsername) } large />
</div>
<div className = { createClassName(styles, 'call-notification__content-message') }>
{ I18n.t('Incoming video Call') }
</div>
<div className = { createClassName(styles, 'call-notification__content-actions') }>
<Button
onClick = { declineClick }
className = { createClassName(styles, 'call-notification__content-actions-decline') }>
<PhoneDecline width = { 20 } height = { 20 } />
<span style='margin-left:5px'> {I18n.t('Decline')} </span >
</Button>
<Button onClick = { acceptClick }
className = {createClassName(styles, 'call-notification__content-actions-accept') }>
<PhoneAccept width = { 20 } height = { 20} />
<span style='margin-left:5px'> {I18n.t('Accept')} </span >
</Button>
</div>
</div>
)
}
</div>
);
};
12 changes: 12 additions & 0 deletions src/components/Calls/CallStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const CallStatus = {
RINGING: 'ringing',
DECLINED: 'declined',
IN_PROGRESS: 'inProgress', // although on Livechat we only use "IN_PROGRESS_SAME_TAB" and "IN_PROGRESS_DIFFERENT_TAB", we still need this status since on Rocket.Chat core, this is the status of ongoing calls
IN_PROGRESS_SAME_TAB: 'inProgressSameTab',
IN_PROGRESS_DIFFERENT_TAB: 'inProgressDifferentTab',
ENDED: 'ended',
};

export const isCallOngoing = (callStatus) => callStatus === CallStatus.IN_PROGRESS
|| callStatus === CallStatus.IN_PROGRESS_DIFFERENT_TAB
|| callStatus === CallStatus.IN_PROGRESS_SAME_TAB;
50 changes: 50 additions & 0 deletions src/components/Calls/JoinCallButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { h } from 'preact';

import { Livechat } from '../../api';
import I18n from '../../i18n';
import VideoIcon from '../../icons/video.svg';
import constants from '../../lib/constants';
import store from '../../store';
import { Button } from '../Button';
import { createClassName } from '../helpers';
import { isCallOngoing } from './CallStatus';
import styles from './styles.scss';


export const JoinCallButton = (props) => {
const { token, room } = store.state;

const clickJoinCall = () => {
switch (props.callProvider) {
case constants.jitsiCallStartedMessageType: {
window.open(props.url, room._id);
break;
}
case constants.webRTCCallStartedMessageType: {
window.open(`${ Livechat.client.host }/meet/${ room._id }?token=${ token }`, room._id);
break;
}
}
};
return (
<div className={createClassName(styles, 'joinCall')}>
{
isCallOngoing(props.callStatus)
&& (
<div>
<div className={createClassName(styles, 'joinCall__content')} >
<div className={createClassName(styles, 'joinCall__content-videoIcon')} >
<VideoIcon width={20} height={20} />
</div>
{ I18n.t('Join my room to start the video call') }
</div>
<Button onClick={clickJoinCall} className={createClassName(styles, 'joinCall__content-action')}>
<VideoIcon width={20} height={20} />
{I18n.t('Join Call')}
</Button>
</div>
)
}
</div>
);
};
128 changes: 128 additions & 0 deletions src/components/Calls/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
@import '../../styles/colors';
@import '../../styles/variables';

.call-notification {
position: relative;

display: flex;

width: 100%;
height: 50%;

&__content {
display: flex;
flex-direction: column;

width: 100%;
height: 100%;

background: #1f2329;

font-weight: 600;
justify-content: space-evenly;

&-avatar {
display: flex;

margin: 0 auto;
align-self: flex-end;
}

&-message {
margin: 0 auto;

color: #ffffff;
}

&-actions {
display: flex;
flex-direction: row;

margin: 0 auto;
margin-bottom: 15px;

color: white;

align-items: flex-end;

> button {
margin-bottom: 0;
margin-left: 10px;
}

&-accept {
border-color: green;
background-color: #2de0a5;
}

&-decline {
border-color: red;
background-color: #f5455c;
}
}
}
}

.call-iframe {
position: absolute;
top: 0;

width: 100%;
height: 41%;

&__content {
width: 100%;
height: 100%;
}
}

.joinCall {
width: 300px;
margin: 15px;

padding: 5px;

border: 1px solid #e4e7ea;

&__content {
display: flex;
flex-direction: row;

padding: 15px;

line-height: 16px;
justify-content: space-around;

&-videoIcon {
display: flex;

height: 7%;
margin-right: 10px;

padding: 5px;

border: 1px solid white;
background-color: #d1ebfe;
}

&-action {
display: block;

width: 120px;
margin-top: 0;
margin-bottom: 3%;
margin-left: 20%;
padding: 5px;

color: white;
border: 1px solid blue;
background-color: #1d74f5;
}
}
}

@media screen and (min-width: 410px) {
.joinCall {
margin-left: 3%;
}
}
Loading