Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore: VideoConference UX/UI Refactor 1st Interaction #26183

Merged
merged 17 commits into from
Jul 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2872c33
[IMPROVE] [VideoConference] Validate and respond to client notificati…
pierre-lehnen-rc Jul 8, 2022
72fbf92
[NEW] When ending a call, add a message saying that it has ended. (#2…
pierre-lehnen-rc Jul 8, 2022
8c1f3df
[NEW] Add "Ring other users when calling" permission (#26180)
pierre-lehnen-rc Jul 8, 2022
1fc2b34
Only send .end notifications to users that haven't joined the call
pierre-lehnen-rc Jul 8, 2022
f9bad24
Merge branch 'develop' into improve/videoconf-first-phase
debdutdeb Jul 8, 2022
5951ef1
[IMPROVE] Use the provider app's user to send videoconf messages (#26…
pierre-lehnen-rc Jul 8, 2022
a3e3399
Merge remote-tracking branch 'origin/develop' into improve/videoconf-…
pierre-lehnen-rc Jul 9, 2022
c08f9bc
[BREAK] Remove webRTC for channels/dm/groups (#26222)
dougfabris Jul 11, 2022
94d26f2
Merge branch 'develop' into improve/videoconf-first-phase
dougfabris Jul 12, 2022
ee49c32
[IMPROVE] [VideoConference] VideoConfPopup visual improves (#26164)
dougfabris Jul 13, 2022
15cbb86
improve: add dial sfx
dougfabris Jul 13, 2022
2638a63
Merge remote-tracking branch 'origin/develop' into improve/videoconf-…
ggazzo Jul 14, 2022
7bca1fa
fix: review
dougfabris Jul 14, 2022
05121c1
Merge remote-tracking branch 'origin/develop' into improve/videoconf-…
ggazzo Jul 18, 2022
22ed6e9
Merge branch 'develop' into improve/videoconf-first-phase
ggazzo Jul 19, 2022
a437539
Merge branch 'develop' into improve/videoconf-first-phase
dougfabris Jul 19, 2022
8d14d5e
fix: migration
dougfabris Jul 19, 2022
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
7 changes: 5 additions & 2 deletions apps/meteor/app/api/server/v1/videoConference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {

import { API } from '../api';
import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { VideoConf } from '../../../../server/sdk';
import { videoConfProviders } from '../../../../server/lib/videoConfProviders';
import { availabilityErrors } from '../../../../lib/videoConference/constants';
Expand All @@ -18,7 +19,7 @@ API.v1.addRoute(
{ authRequired: true, validateParams: isVideoConfStartProps, rateLimiterOptions: { numRequestsAllowed: 3, intervalTimeInMS: 60000 } },
{
async post() {
const { roomId, title, allowRinging } = this.bodyParams;
const { roomId, title, allowRinging: requestRinging } = this.bodyParams;
const { userId } = this;
if (!userId || !(await canAccessRoomIdAsync(roomId, userId))) {
return API.v1.failure('invalid-params');
Expand All @@ -31,9 +32,11 @@ API.v1.addRoute(
throw new Error(availabilityErrors.NOT_ACTIVE);
}

const allowRinging = Boolean(requestRinging) && (await hasPermissionAsync(userId, 'videoconf-ring-users'));

return API.v1.success({
data: {
...(await VideoConf.start(userId, roomId, { title, allowRinging: Boolean(allowRinging) })),
...(await VideoConf.start(userId, roomId, { title, allowRinging })),
providerName,
},
});
Expand Down
5 changes: 3 additions & 2 deletions apps/meteor/app/apps/server/bridges/videoConferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ export class AppVideoConferenceBridge extends VideoConferenceBridge {
}
}

protected async registerProvider(info: IVideoConfProvider): Promise<void> {
videoConfProviders.registerProvider(info.name, info.capabilities || {});
protected async registerProvider(info: IVideoConfProvider, appId: string): Promise<void> {
this.orch.debugLog(`The App ${appId} is registering a video conference provider.`);
videoConfProviders.registerProvider(info.name, info.capabilities || {}, appId);
}

protected async unRegisterProvider(info: IVideoConfProvider): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export const upsertPermissions = async (): Promise<void> => {
{ _id: 'remove-slackbridge-links', roles: ['admin'] },
{ _id: 'view-import-operations', roles: ['admin'] },
{ _id: 'clear-oembed-cache', roles: ['admin'] },
{ _id: 'videoconf-ring-users', roles: ['admin', 'owner', 'moderator', 'user'] },
];

for await (const permission of permissions) {
Expand Down
3 changes: 2 additions & 1 deletion apps/meteor/app/custom-sounds/client/lib/CustomSounds.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const getCustomSoundId = (sound) => `custom-sound-${sound}`;
class CustomSoundsClass {
constructor() {
this.list = new ReactiveVar({});
this.add({ _id: 'calling', name: 'Calling', extension: 'mp3', src: getURL('sounds/calling.mp3') });
this.add({ _id: 'chime', name: 'Chime', extension: 'mp3', src: getURL('sounds/chime.mp3') });
this.add({ _id: 'door', name: 'Door', extension: 'mp3', src: getURL('sounds/door.mp3') });
this.add({ _id: 'beep', name: 'Beep', extension: 'mp3', src: getURL('sounds/beep.mp3') });
Expand Down Expand Up @@ -52,6 +51,8 @@ class CustomSoundsClass {
extension: 'mp3',
src: getURL('sounds/call-ended.mp3'),
});
this.add({ _id: 'dialtone', name: 'Dialtone', extension: 'mp3', src: getURL('sounds/dialtone.mp3') });
this.add({ _id: 'ringtone', name: 'Ringtone', extension: 'mp3', src: getURL('sounds/ringtone.mp3') });
}

add(sound) {
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/models/server/models/Users.js
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ export class Users extends Base {
return this.find(query, options);
}

findOneByAppId(appId, options) {
findOneByAppId(appId, options = {}) {
const query = { appId };

return this.findOne(query, options);
Expand Down
36 changes: 36 additions & 0 deletions apps/meteor/client/lib/VideoConfManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter<Vide
this.hookNotification('video-conference.rejected', (params: DirectCallParams) => this.onDirectCallRejected(params));
this.hookNotification('video-conference.confirmed', (params: DirectCallParams) => this.onDirectCallConfirmed(params));
this.hookNotification('video-conference.join', (params: DirectCallParams) => this.onDirectCallJoined(params));
this.hookNotification('video-conference.end', (params: DirectCallParams) => this.onDirectCallEnded(params));
}

private abortIncomingCall(callId: string): void {
Expand Down Expand Up @@ -658,6 +659,41 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter<Vide
this.onDirectCallAccepted(params, true);
}

private onDirectCallEnded(params: DirectCallParams): void {
if (!params.callId) {
debug && console.log(`[VideoConf] Invalid 'video-conference.end' event received: ${params.callId}, ${params.uid}.`);
return;
}

const callData = this.incomingDirectCalls.get(params.callId);
if (callData) {
debug && console.log(`[VideoConf] Incoming call ended by the server: ${params.callId}.`);
if (callData.acceptTimeout) {
clearTimeout(callData.acceptTimeout);
this.setIncomingCallAttribute(params.callId, 'acceptTimeout', undefined);
}

this.loseIncomingCall(params.callId);
return;
}

if (this.currentCallData?.callId !== params.callId) {
debug && console.log(`[VideoConf] Server sent a call ended event for a call we're not aware of: ${params.callId}.`);
return;
}

debug && console.log(`[VideoConf] Outgoing call ended by the server: ${params.callId}.`);

// Stop ringing
this.currentCallData = undefined;
if (this.currentCallHandler) {
clearInterval(this.currentCallHandler);
this.currentCallHandler = undefined;
this.emit('calling/changed');
this.emit('direct/stopped', params);
}
}

private onDirectCallRejected(params: DirectCallParams): void {
if (!params.callId || params.callId !== this.currentCallData?.callId) {
debug && console.log(`[VideoConf] User ${params.uid} has rejected a call ${params.callId} from us, but we're not calling.`);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IRoom } from '@rocket.chat/core-typings';
import { Box, Skeleton } from '@rocket.chat/fuselage';
import { Skeleton } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation, useUser } from '@rocket.chat/ui-contexts';
import { useTranslation } from '@rocket.chat/ui-contexts';
import {
VideoConfPopup,
VideoConfPopupContent,
Expand All @@ -12,36 +12,26 @@ import {
VideoConfPopupFooter,
VideoConfPopupFooterButtons,
VideoConfPopupTitle,
VideoConfPopupIndicators,
VideoConfPopupClose,
VideoConfPopupUsername,
VideoConfPopupHeader,
} from '@rocket.chat/ui-video-conf';
import React, { ReactElement, useMemo } from 'react';

import ReactiveUserStatus from '../../../../../../components/UserStatus/ReactiveUserStatus';
import RoomAvatar from '../../../../../../components/avatar/RoomAvatar';
import { useVideoConfSetPreferences } from '../../../../../../contexts/VideoConfContext';
import { AsyncStatePhase } from '../../../../../../hooks/useAsyncState';
import { useEndpointData } from '../../../../../../hooks/useEndpointData';
import VideoConfPopupRoomInfo from './VideoConfPopupRoomInfo';

type ReceivingPopupProps = {
type IncomingPopupProps = {
id: string;
room: IRoom;
position: number;
current: number;
total: number;
onClose: (id: string) => void;
onMute: (id: string) => void;
onConfirm: () => void;
};

const ReceivingPopup = ({ id, room, position, current, total, onClose, onMute, onConfirm }: ReceivingPopupProps): ReactElement => {
const IncomingPopup = ({ id, room, position, onClose, onMute, onConfirm }: IncomingPopupProps): ReactElement => {
const t = useTranslation();
const user = useUser();
const userId = user?._id;
const directUserId = room.uids?.filter((uid) => uid !== userId).shift();
const [directUsername] = room.usernames?.filter((username) => username !== user?.username) || [];

const { controllersConfig, handleToggleMic, handleToggleCam } = useVideoConfControllers();
const setPreferences = useVideoConfSetPreferences();

Expand All @@ -57,55 +47,48 @@ const ReceivingPopup = ({ id, room, position, current, total, onClose, onMute, o

return (
<VideoConfPopup position={position}>
<VideoConfPopupContent>
<VideoConfPopupClose title={t('Close')} onClick={(): void => onMute(id)} />
<RoomAvatar room={room} size='x40' />
{current && total ? <VideoConfPopupIndicators current={current} total={total} /> : null}
<VideoConfPopupTitle text='Incoming call from' icon='phone-in' />
{directUserId && (
<Box display='flex' alignItems='center' mbs='x8'>
<ReactiveUserStatus uid={directUserId} />
{directUsername && <VideoConfPopupUsername username={directUsername} />}
</Box>
)}
<VideoConfPopupHeader>
<VideoConfPopupTitle text={t('Incoming_call_from')} />
{phase === AsyncStatePhase.LOADING && <Skeleton />}
{phase === AsyncStatePhase.RESOLVED && (showMic || showCam) && (
<VideoConfPopupControllers>
{showMic && (
<VideoConfController
active={controllersConfig.mic}
text={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
onClick={handleToggleMic}
/>
)}
{showCam && (
<VideoConfController
active={controllersConfig.cam}
text={controllersConfig.cam ? t('Cam_on') : t('Cam_off')}
title={controllersConfig.cam ? t('Cam_on') : t('Cam_off')}
icon={controllersConfig.cam ? 'video' : 'video-off'}
onClick={handleToggleCam}
/>
)}
{showMic && (
<VideoConfController
active={controllersConfig.mic}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
onClick={handleToggleMic}
/>
)}
</VideoConfPopupControllers>
)}
</VideoConfPopupHeader>
<VideoConfPopupContent>
<VideoConfPopupRoomInfo room={room} />
</VideoConfPopupContent>
<VideoConfPopupFooter>
<VideoConfPopupFooterButtons>
<VideoConfButton primary onClick={handleJoinCall}>
{t('Accept')}
</VideoConfButton>
{onClose && (
<VideoConfButton danger onClick={(): void => onClose(id)}>
<VideoConfButton danger secondary onClick={(): void => onClose(id)}>
{t('Decline')}
</VideoConfButton>
)}
<VideoConfController small={false} secondary title={t('Mute_and_dismiss')} icon='cross' onClick={(): void => onMute(id)} />
</VideoConfPopupFooterButtons>
</VideoConfPopupFooter>
</VideoConfPopup>
);
};

export default ReceivingPopup;
export default IncomingPopup;
Loading