Improve permission handling in call flow
Improve permission handling by asking the user to give mic and camera permissions before sending `CallBegin` or `CallAccept` for the caller and receiver respectively.
Followed the flow described here: https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Connectivity#session_descriptions
CallProvider:
- Change functions order to place listeners under the function that sends the corresponding WebSocket message.
- Replace `Default` CallStatus with `Loading` for when asking user permissions before sending the `CallBegin`/`CallAccept` message.
- Remove `localStream` and `remoteStream` from `CallContext`. They are now available only in `WebRtcContext`.
- Replace `setAudioStatus` and `setVideoStatus` with `setIsAudioOn` and `setIsVideoOn`. A `useEffect` is now used to disable the tracks when the audio/video status changes.
WebRtcProvider:
- Move WebRTC connection close logic to WebRtcProvider
- Remove `webRtcConnection` from `WebRtcContext`. `WebRtcProvider` is now in charge of setting everything related to the WebRTC Connection.
UI:
- Add `CallPermissionDenied` page for when permissions are denied.
- Rework `CallPending` to display `Loading...` when waiting for user permissions
Change-Id: I48153577cca4c73cdb9b81d2fa78cfdfe2e06d69
diff --git a/client/src/components/Button.tsx b/client/src/components/Button.tsx
index 29dac19..a27028a 100644
--- a/client/src/components/Button.tsx
+++ b/client/src/components/Button.tsx
@@ -97,7 +97,7 @@
icon?: ReactNode;
};
-type ExpandMenuRadioOption = {
+export type ExpandMenuRadioOption = {
options: {
key: string;
description: ReactNode;
diff --git a/client/src/components/CallButtons.tsx b/client/src/components/CallButtons.tsx
index f6f7275..d0c2c5e 100644
--- a/client/src/components/CallButtons.tsx
+++ b/client/src/components/CallButtons.tsx
@@ -22,7 +22,14 @@
import { useTranslation } from 'react-i18next';
import { CallContext } from '../contexts/CallProvider';
-import { ExpandableButton, ExpandableButtonProps, ShapedButtonProps, ToggleIconButton } from './Button';
+import { WebRtcContext } from '../contexts/WebRtcProvider';
+import {
+ ExpandableButton,
+ ExpandableButtonProps,
+ ExpandMenuRadioOption,
+ ShapedButtonProps,
+ ToggleIconButton,
+} from './Button';
import {
CallEndIcon,
ChatBubbleIcon,
@@ -170,10 +177,10 @@
);
};
-const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind) => {
- const { mediaDevices } = useContext(CallContext);
+const useMediaDeviceExpandMenuOptions = (kind: MediaDeviceKind): ExpandMenuRadioOption[] | undefined => {
+ const { mediaDevices } = useContext(WebRtcContext);
- return useMemo(
+ const options = useMemo(
() =>
mediaDevices[kind].map((device) => ({
key: device.deviceId,
@@ -181,23 +188,14 @@
})),
[mediaDevices, kind]
);
+
+ return options.length > 0 ? [{ options }] : undefined;
};
export const CallingVolumeButton = (props: ExpandableButtonProps) => {
const options = useMediaDeviceExpandMenuOptions('audiooutput');
- return (
- <CallButton
- aria-label="volume options"
- Icon={VolumeIcon}
- expandMenuOptions={[
- {
- options,
- },
- ]}
- {...props}
- />
- );
+ return <CallButton aria-label="volume options" Icon={VolumeIcon} expandMenuOptions={options} {...props} />;
};
export const CallingMicButton = (props: ExpandableButtonProps) => {
@@ -206,11 +204,7 @@
return (
<CallButton
aria-label="microphone options"
- expandMenuOptions={[
- {
- options,
- },
- ]}
+ expandMenuOptions={options}
IconButtonComp={ToggleAudioCameraIconButton}
{...props}
/>
@@ -218,13 +212,13 @@
};
const ToggleAudioCameraIconButton = (props: IconButtonProps) => {
- const { isAudioOn, setAudioStatus } = useContext(CallContext);
+ const { isAudioOn, setIsAudioOn } = useContext(CallContext);
return (
<ToggleIconButton
IconOn={MicroIcon}
IconOff={MicroOffIcon}
selected={isAudioOn}
- toggle={() => setAudioStatus(!isAudioOn)}
+ toggle={() => setIsAudioOn((v) => !v)}
{...props}
/>
);
@@ -236,11 +230,7 @@
return (
<CallButton
aria-label="camera options"
- expandMenuOptions={[
- {
- options,
- },
- ]}
+ expandMenuOptions={options}
IconButtonComp={ToggleVideoCameraIconButton}
{...props}
/>
@@ -248,13 +238,13 @@
};
const ToggleVideoCameraIconButton = (props: IconButtonProps) => {
- const { isVideoOn, setVideoStatus } = useContext(CallContext);
+ const { isVideoOn, setIsVideoOn } = useContext(CallContext);
return (
<ToggleIconButton
IconOn={VideoCameraIcon}
IconOff={VideoCameraOffIcon}
selected={isVideoOn}
- toggle={() => setVideoStatus(!isVideoOn)}
+ toggle={() => setIsVideoOn((v) => !v)}
{...props}
/>
);
diff --git a/client/src/components/ConversationView.tsx b/client/src/components/ConversationView.tsx
index 064efcf..e412cca 100644
--- a/client/src/components/ConversationView.tsx
+++ b/client/src/components/ConversationView.tsx
@@ -43,9 +43,8 @@
const ConversationHeader = () => {
const { account } = useAuthContext();
- const { conversation } = useContext(ConversationContext);
+ const { conversation, conversationId } = useContext(ConversationContext);
const { t } = useTranslation();
- const { conversationId } = useContext(ConversationContext);
const members = conversation.getMembers();
const adminTitle = conversation.infos.title as string;