blob: 400140cbabe35f9d8e56d3a343018fcdcdd82eca [file] [log] [blame]
simonf929a362022-11-18 16:53:45 -05001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
MichelleSS55164202022-11-25 18:36:14 -050018import { CallAction, CallBegin, WebSocketMessageType } from 'jami-web-common';
simon5c677962022-12-02 16:51:54 -050019import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
simonf929a362022-11-18 16:53:45 -050020
simon5c677962022-12-02 16:51:54 -050021import { createOptionalContext } from '../hooks/createOptionalContext';
idillon07d31cc2022-12-06 22:40:14 -050022import { ConversationMember } from '../models/conversation-member';
MichelleSS55164202022-11-25 18:36:14 -050023import { callTimeoutMs } from '../utils/constants';
simon1e2bf342022-12-02 12:19:40 -050024import { AsyncSetState, SetState, WithChildren } from '../utils/utils';
idilloncf42c652023-01-31 18:56:17 -050025import { useWebRtcManager } from '../webrtc/WebRtcManager';
26import { useAuthContext } from './AuthProvider';
simon5c677962022-12-02 16:51:54 -050027import { CallData, CallManagerContext } from './CallManagerProvider';
28import ConditionalContextProvider from './ConditionalContextProvider';
idillona4b96ab2023-02-01 15:30:12 -050029import { IWebSocketContext, useWebSocketContext } from './WebSocketProvider';
simonf929a362022-11-18 16:53:45 -050030
31export type CallRole = 'caller' | 'receiver';
32
33export enum CallStatus {
simonff1cb352022-11-24 15:15:26 -050034 Default,
simon9076a9a2022-11-29 17:13:01 -050035 Loading,
simonf929a362022-11-18 16:53:45 -050036 Ringing,
37 Connecting,
38 InCall,
simon9076a9a2022-11-29 17:13:01 -050039 PermissionsDenied,
simonf929a362022-11-18 16:53:45 -050040}
41
simon1e2bf342022-12-02 12:19:40 -050042export enum VideoStatus {
43 Off,
44 Camera,
45 ScreenShare,
46}
47
simon492e8402022-11-29 16:48:37 -050048type MediaDeviceIdState = {
49 id: string | undefined;
50 setId: (id: string | undefined) => void | Promise<void>;
51};
52type CurrentMediaDeviceIds = Record<MediaDeviceKind, MediaDeviceIdState>;
53
idilloncf42c652023-01-31 18:56:17 -050054export type MediaDevicesInfo = Record<MediaDeviceKind, MediaDeviceInfo[]>;
55export type MediaInputKind = 'audio' | 'video';
56export type MediaInputIds = Record<MediaInputKind, string | false | undefined>;
57
simonf929a362022-11-18 16:53:45 -050058export interface ICallContext {
idilloncf42c652023-01-31 18:56:17 -050059 localStream: MediaStream | undefined;
60 screenShareLocalStream: MediaStream | undefined;
61 remoteStreams: readonly MediaStream[];
62
simon492e8402022-11-29 16:48:37 -050063 mediaDevices: MediaDevicesInfo;
64 currentMediaDeviceIds: CurrentMediaDeviceIds;
65
simonf929a362022-11-18 16:53:45 -050066 isAudioOn: boolean;
simon9076a9a2022-11-29 17:13:01 -050067 setIsAudioOn: SetState<boolean>;
simon1e2bf342022-12-02 12:19:40 -050068 videoStatus: VideoStatus;
69 updateVideoStatus: AsyncSetState<VideoStatus>;
simonf9d78f22022-11-25 15:47:15 -050070 isChatShown: boolean;
71 setIsChatShown: SetState<boolean>;
simon2a5cf142022-11-25 15:47:35 -050072 isFullscreen: boolean;
73 setIsFullscreen: SetState<boolean>;
simonf929a362022-11-18 16:53:45 -050074 callRole: CallRole;
75 callStatus: CallStatus;
Misha Krieger-Raynauldd0cc3e32022-11-29 19:59:31 -050076 callStartTime: number | undefined;
simonf929a362022-11-18 16:53:45 -050077
MichelleSS55164202022-11-25 18:36:14 -050078 acceptCall: (withVideoOn: boolean) => void;
simonaccd8022022-11-24 15:04:53 -050079 endCall: () => void;
simonf929a362022-11-18 16:53:45 -050080}
81
simon5c677962022-12-02 16:51:54 -050082const optionalCallContext = createOptionalContext<ICallContext>('CallContext');
83export const useCallContext = optionalCallContext.useOptionalContext;
simonf929a362022-11-18 16:53:45 -050084
85export default ({ children }: WithChildren) => {
idillona4b96ab2023-02-01 15:30:12 -050086 const webSocket = useWebSocketContext();
idillon07d31cc2022-12-06 22:40:14 -050087 const { callMembers, callData, exitCall } = useContext(CallManagerContext);
simonf353ef42022-11-28 23:14:53 -050088
simon5c677962022-12-02 16:51:54 -050089 const dependencies = useMemo(
90 () => ({
91 webSocket,
idillon07d31cc2022-12-06 22:40:14 -050092 callMembers,
simon5c677962022-12-02 16:51:54 -050093 callData,
94 exitCall,
95 conversationId: callData?.conversationId,
96 }),
idilloncf42c652023-01-31 18:56:17 -050097 [webSocket, callMembers, callData, exitCall]
simon5c677962022-12-02 16:51:54 -050098 );
simonf353ef42022-11-28 23:14:53 -050099
simone35acc22022-12-02 16:51:12 -0500100 return (
simon5c677962022-12-02 16:51:54 -0500101 <ConditionalContextProvider
102 Context={optionalCallContext.Context}
103 initialValue={undefined}
104 dependencies={dependencies}
105 useProviderValue={CallProvider}
106 >
simone35acc22022-12-02 16:51:12 -0500107 {children}
simon5c677962022-12-02 16:51:54 -0500108 </ConditionalContextProvider>
simone35acc22022-12-02 16:51:12 -0500109 );
simonf353ef42022-11-28 23:14:53 -0500110};
111
112const CallProvider = ({
idillon07d31cc2022-12-06 22:40:14 -0500113 callMembers,
simon5c677962022-12-02 16:51:54 -0500114 callData,
115 exitCall,
simone35acc22022-12-02 16:51:12 -0500116 conversationId,
simonf353ef42022-11-28 23:14:53 -0500117 webSocket,
simon5c677962022-12-02 16:51:54 -0500118}: {
simonf353ef42022-11-28 23:14:53 -0500119 webSocket: IWebSocketContext;
idillon07d31cc2022-12-06 22:40:14 -0500120 callMembers: ConversationMember[];
simon5c677962022-12-02 16:51:54 -0500121 callData: CallData;
122 exitCall: () => void;
simone35acc22022-12-02 16:51:12 -0500123 conversationId: string;
simon5c677962022-12-02 16:51:54 -0500124}): ICallContext => {
idilloncf42c652023-01-31 18:56:17 -0500125 const [localStream, setLocalStream] = useState<MediaStream>();
126 const [screenShareLocalStream, setScreenShareLocalStream] = useState<MediaStream>();
127 const { account } = useAuthContext();
128 const webRtcManager = useWebRtcManager();
129
130 // TODO: This logic will have to change to support multiple people in a call. Could we move this logic to the server?
131 // The client could make a single request with the conversationId, and the server would be tasked with sending
132 // all the individual requests to the members of the conversation.
133 const contactUri = callMembers[0]?.contact.uri;
134 const connectionInfos = webRtcManager.connectionsInfos[contactUri];
135 const remoteStreams = connectionInfos?.remoteStreams;
136 const iceConnectionState = connectionInfos?.iceConnectionState;
simonf929a362022-11-18 16:53:45 -0500137
simon5c677962022-12-02 16:51:54 -0500138 const [mediaDevices, setMediaDevices] = useState<MediaDevicesInfo>({
139 audioinput: [],
140 audiooutput: [],
141 videoinput: [],
142 });
simon492e8402022-11-29 16:48:37 -0500143 const [audioInputDeviceId, setAudioInputDeviceId] = useState<string>();
144 const [audioOutputDeviceId, setAudioOutputDeviceId] = useState<string>();
145 const [videoDeviceId, setVideoDeviceId] = useState<string>();
146
simonf929a362022-11-18 16:53:45 -0500147 const [isAudioOn, setIsAudioOn] = useState(false);
simon1e2bf342022-12-02 12:19:40 -0500148 const [videoStatus, setVideoStatus] = useState(VideoStatus.Off);
simonf9d78f22022-11-25 15:47:15 -0500149 const [isChatShown, setIsChatShown] = useState(false);
simon2a5cf142022-11-25 15:47:35 -0500150 const [isFullscreen, setIsFullscreen] = useState(false);
simone35acc22022-12-02 16:51:12 -0500151 const [callStatus, setCallStatus] = useState(CallStatus.Default);
152 const [callRole] = useState(callData?.role);
Misha Krieger-Raynauldd0cc3e32022-11-29 19:59:31 -0500153 const [callStartTime, setCallStartTime] = useState<number | undefined>(undefined);
simonf929a362022-11-18 16:53:45 -0500154
idilloncf42c652023-01-31 18:56:17 -0500155 // TODO: Replace this by a callback
156 useEffect(() => {
157 if (callData.role === 'receiver' && contactUri && localStream) {
158 webRtcManager.addConnection(webSocket, account, contactUri, callData, localStream, screenShareLocalStream);
159 }
160 }, [account, callData, contactUri, localStream, screenShareLocalStream, webRtcManager, webSocket]);
161
162 const getMediaDevices = useCallback(async (): Promise<MediaDevicesInfo> => {
163 try {
164 const devices = await navigator.mediaDevices.enumerateDevices();
165
166 // TODO: On Firefox, some devices can sometime be duplicated (2 devices can share the same deviceId). Using a map
167 // and then converting it to an array makes it so that there is no duplicate. If we find a way to prevent
168 // Firefox from listing 2 devices with the same deviceId, we can remove this logic.
169 const newMediaDevices: Record<MediaDeviceKind, Record<string, MediaDeviceInfo>> = {
170 audioinput: {},
171 audiooutput: {},
172 videoinput: {},
173 };
174
175 for (const device of devices) {
176 newMediaDevices[device.kind][device.deviceId] = device;
177 }
178
179 return {
180 audioinput: Object.values(newMediaDevices.audioinput),
181 audiooutput: Object.values(newMediaDevices.audiooutput),
182 videoinput: Object.values(newMediaDevices.videoinput),
183 };
184 } catch (e) {
185 throw new Error('Could not get media devices', { cause: e });
186 }
187 }, []);
188
189 const updateLocalStream = useCallback(
190 async (mediaDeviceIds?: MediaInputIds) => {
191 const devices = await getMediaDevices();
192
193 let audioConstraint: MediaTrackConstraints | boolean = devices.audioinput.length !== 0;
194 let videoConstraint: MediaTrackConstraints | boolean = devices.videoinput.length !== 0;
195
196 if (!audioConstraint && !videoConstraint) {
197 return;
198 }
199
200 if (mediaDeviceIds?.audio !== undefined) {
201 audioConstraint = mediaDeviceIds.audio !== false ? { deviceId: mediaDeviceIds.audio } : false;
202 }
203 if (mediaDeviceIds?.video !== undefined) {
204 videoConstraint = mediaDeviceIds.video !== false ? { deviceId: mediaDeviceIds.video } : false;
205 }
206
207 try {
208 const stream = await navigator.mediaDevices.getUserMedia({
209 audio: audioConstraint,
210 video: videoConstraint,
211 });
212
213 for (const track of stream.getTracks()) {
214 track.enabled = false;
215 }
216
217 setLocalStream(stream);
218 } catch (e) {
219 throw new Error('Could not get media devices', { cause: e });
220 }
221 },
222 [getMediaDevices]
223 );
224
225 const updateScreenShare = useCallback(
226 async (isOn: boolean) => {
227 if (isOn) {
228 const stream = await navigator.mediaDevices.getDisplayMedia({
229 video: true,
230 audio: false,
231 });
232
233 setScreenShareLocalStream(stream);
234 return stream;
235 } else {
236 if (screenShareLocalStream) {
237 for (const track of screenShareLocalStream.getTracks()) {
238 track.stop();
239 }
240 }
241
242 setScreenShareLocalStream(undefined);
243 }
244 },
245 [screenShareLocalStream]
246 );
247
248 // TODO: Transform the effect into a callback
249 const updateLocalStreams = webRtcManager.updateLocalStreams;
250 useEffect(() => {
251 if ((!localStream && !screenShareLocalStream) || !updateLocalStreams) {
252 return;
253 }
254
255 updateLocalStreams(localStream, screenShareLocalStream);
256 }, [localStream, screenShareLocalStream, updateLocalStreams]);
257
258 const sendWebRtcOffer = useCallback(async () => {
259 if (contactUri) {
260 webRtcManager.addConnection(webSocket, account, contactUri, callData, localStream, screenShareLocalStream);
261 }
262 }, [account, callData, contactUri, localStream, screenShareLocalStream, webRtcManager, webSocket]);
263
264 const closeConnection = useCallback(() => {
265 const stopStream = (stream: MediaStream) => {
266 const localTracks = stream.getTracks();
267 if (localTracks) {
268 for (const track of localTracks) {
269 track.stop();
270 }
271 }
272 };
273
274 if (localStream) {
275 stopStream(localStream);
276 }
277 if (screenShareLocalStream) {
278 stopStream(screenShareLocalStream);
279 }
280
281 webRtcManager.clean();
282 }, [localStream, screenShareLocalStream, webRtcManager]);
simonf929a362022-11-18 16:53:45 -0500283
284 useEffect(() => {
simon492e8402022-11-29 16:48:37 -0500285 if (callStatus !== CallStatus.InCall) {
286 return;
287 }
288
289 const updateMediaDevices = async () => {
290 try {
291 const newMediaDevices = await getMediaDevices();
292
293 if (newMediaDevices.audiooutput.length !== 0 && !audioOutputDeviceId) {
294 setAudioOutputDeviceId(newMediaDevices.audiooutput[0].deviceId);
295 }
296
297 setMediaDevices(newMediaDevices);
298 } catch (e) {
299 console.error('Could not update media devices:', e);
300 }
301 };
302
303 navigator.mediaDevices.addEventListener('devicechange', updateMediaDevices);
304 updateMediaDevices();
305
306 return () => {
307 navigator.mediaDevices.removeEventListener('devicechange', updateMediaDevices);
308 };
309 }, [callStatus, getMediaDevices, audioOutputDeviceId]);
310
311 useEffect(() => {
simon9076a9a2022-11-29 17:13:01 -0500312 if (localStream) {
313 for (const track of localStream.getAudioTracks()) {
314 track.enabled = isAudioOn;
simon492e8402022-11-29 16:48:37 -0500315 const deviceId = track.getSettings().deviceId;
316 if (deviceId) {
317 setAudioInputDeviceId(deviceId);
318 }
simon9076a9a2022-11-29 17:13:01 -0500319 }
simonfeaa1db2022-11-26 20:13:18 -0500320 }
simon9076a9a2022-11-29 17:13:01 -0500321 }, [isAudioOn, localStream]);
simonf929a362022-11-18 16:53:45 -0500322
323 useEffect(() => {
simonf353ef42022-11-28 23:14:53 -0500324 if (localStream) {
MichelleSS55164202022-11-25 18:36:14 -0500325 for (const track of localStream.getVideoTracks()) {
simon1e2bf342022-12-02 12:19:40 -0500326 track.enabled = videoStatus === VideoStatus.Camera;
simon492e8402022-11-29 16:48:37 -0500327 const deviceId = track.getSettings().deviceId;
328 if (deviceId) {
329 setVideoDeviceId(deviceId);
330 }
MichelleSS55164202022-11-25 18:36:14 -0500331 }
simonff1cb352022-11-24 15:15:26 -0500332 }
simon1e2bf342022-12-02 12:19:40 -0500333 }, [videoStatus, localStream]);
334
335 const updateVideoStatus = useCallback(
336 async (newStatus: ((prevState: VideoStatus) => VideoStatus) | VideoStatus) => {
337 if (typeof newStatus === 'function') {
338 newStatus = newStatus(videoStatus);
339 }
340
341 const stream = await updateScreenShare(newStatus === VideoStatus.ScreenShare);
342 if (stream) {
343 for (const track of stream.getTracks()) {
344 track.addEventListener('ended', () => {
345 console.warn('Browser ended screen sharing');
346 updateVideoStatus(VideoStatus.Off);
347 });
348 }
349 }
350
351 setVideoStatus(newStatus);
352 },
353 [videoStatus, updateScreenShare]
354 );
simonf929a362022-11-18 16:53:45 -0500355
356 useEffect(() => {
simon2a5cf142022-11-25 15:47:35 -0500357 const onFullscreenChange = () => {
358 setIsFullscreen(document.fullscreenElement !== null);
359 };
360
361 document.addEventListener('fullscreenchange', onFullscreenChange);
362 return () => {
363 document.removeEventListener('fullscreenchange', onFullscreenChange);
364 };
365 }, []);
366
367 useEffect(() => {
simon9076a9a2022-11-29 17:13:01 -0500368 if (callRole === 'caller' && callStatus === CallStatus.Default) {
simone35acc22022-12-02 16:51:12 -0500369 const withVideoOn = callData?.withVideoOn ?? false;
simon9076a9a2022-11-29 17:13:01 -0500370 setCallStatus(CallStatus.Loading);
simon492e8402022-11-29 16:48:37 -0500371 updateLocalStream()
simon9076a9a2022-11-29 17:13:01 -0500372 .then(() => {
373 const callBegin: CallBegin = {
374 contactId: contactUri,
375 conversationId,
simon492e8402022-11-29 16:48:37 -0500376 withVideoOn,
simon9076a9a2022-11-29 17:13:01 -0500377 };
378
379 setCallStatus(CallStatus.Ringing);
simon1e2bf342022-12-02 12:19:40 -0500380 setVideoStatus(withVideoOn ? VideoStatus.Camera : VideoStatus.Off);
simon9076a9a2022-11-29 17:13:01 -0500381 console.info('Sending CallBegin', callBegin);
382 webSocket.send(WebSocketMessageType.CallBegin, callBegin);
383 })
384 .catch((e) => {
385 console.error(e);
386 setCallStatus(CallStatus.PermissionsDenied);
387 });
388 }
simone35acc22022-12-02 16:51:12 -0500389 }, [webSocket, updateLocalStream, callRole, callStatus, contactUri, conversationId, callData]);
simon9076a9a2022-11-29 17:13:01 -0500390
391 const acceptCall = useCallback(
392 (withVideoOn: boolean) => {
393 setCallStatus(CallStatus.Loading);
simon492e8402022-11-29 16:48:37 -0500394 updateLocalStream()
simon9076a9a2022-11-29 17:13:01 -0500395 .then(() => {
396 const callAccept: CallAction = {
397 contactId: contactUri,
398 conversationId,
399 };
400
simon1e2bf342022-12-02 12:19:40 -0500401 setVideoStatus(withVideoOn ? VideoStatus.Camera : VideoStatus.Off);
simon9076a9a2022-11-29 17:13:01 -0500402 setCallStatus(CallStatus.Connecting);
403 console.info('Sending CallAccept', callAccept);
404 webSocket.send(WebSocketMessageType.CallAccept, callAccept);
405 })
406 .catch((e) => {
407 console.error(e);
408 setCallStatus(CallStatus.PermissionsDenied);
409 });
410 },
simon492e8402022-11-29 16:48:37 -0500411 [webSocket, updateLocalStream, contactUri, conversationId]
simon9076a9a2022-11-29 17:13:01 -0500412 );
413
414 useEffect(() => {
simonf929a362022-11-18 16:53:45 -0500415 if (callRole === 'caller' && callStatus === CallStatus.Ringing) {
simonaccd8022022-11-24 15:04:53 -0500416 const callAcceptListener = (data: CallAction) => {
417 console.info('Received event on CallAccept', data);
Charliec18d6402022-11-27 13:01:04 -0500418 if (data.conversationId !== conversationId) {
419 console.warn('Wrong incoming conversationId, ignoring action');
420 return;
421 }
422
simonf929a362022-11-18 16:53:45 -0500423 setCallStatus(CallStatus.Connecting);
424
simon9076a9a2022-11-29 17:13:01 -0500425 sendWebRtcOffer();
simonf929a362022-11-18 16:53:45 -0500426 };
427
428 webSocket.bind(WebSocketMessageType.CallAccept, callAcceptListener);
429
430 return () => {
431 webSocket.unbind(WebSocketMessageType.CallAccept, callAcceptListener);
432 };
433 }
simon9076a9a2022-11-29 17:13:01 -0500434 }, [callRole, webSocket, sendWebRtcOffer, callStatus, conversationId]);
simonf929a362022-11-18 16:53:45 -0500435
simon9076a9a2022-11-29 17:13:01 -0500436 const endCall = useCallback(() => {
437 const callEnd: CallAction = {
438 contactId: contactUri,
439 conversationId,
440 };
MichelleSS55164202022-11-25 18:36:14 -0500441
simon9076a9a2022-11-29 17:13:01 -0500442 console.info('Sending CallEnd', callEnd);
443 closeConnection();
444 webSocket.send(WebSocketMessageType.CallEnd, callEnd);
simone35acc22022-12-02 16:51:12 -0500445 exitCall();
simon9076a9a2022-11-29 17:13:01 -0500446 // TODO: write in chat that the call ended
simone35acc22022-12-02 16:51:12 -0500447 }, [webSocket, contactUri, conversationId, closeConnection, exitCall]);
simonaccd8022022-11-24 15:04:53 -0500448
449 useEffect(() => {
simonaccd8022022-11-24 15:04:53 -0500450 const callEndListener = (data: CallAction) => {
451 console.info('Received event on CallEnd', data);
Charliec18d6402022-11-27 13:01:04 -0500452 if (data.conversationId !== conversationId) {
453 console.warn('Wrong incoming conversationId, ignoring action');
454 return;
455 }
456
simon9076a9a2022-11-29 17:13:01 -0500457 closeConnection();
simone35acc22022-12-02 16:51:12 -0500458 exitCall();
simonaccd8022022-11-24 15:04:53 -0500459 // TODO: write in chat that the call ended
460 };
461
462 webSocket.bind(WebSocketMessageType.CallEnd, callEndListener);
463 return () => {
464 webSocket.unbind(WebSocketMessageType.CallEnd, callEndListener);
465 };
simone35acc22022-12-02 16:51:12 -0500466 }, [webSocket, exitCall, conversationId, closeConnection]);
simonaccd8022022-11-24 15:04:53 -0500467
simonf929a362022-11-18 16:53:45 -0500468 useEffect(() => {
Charlieb837e8f2022-11-28 19:18:46 -0500469 if (
470 callStatus === CallStatus.Connecting &&
471 (iceConnectionState === 'connected' || iceConnectionState === 'completed')
472 ) {
simonf929a362022-11-18 16:53:45 -0500473 console.info('Changing call status to InCall');
474 setCallStatus(CallStatus.InCall);
Misha Krieger-Raynauldd0cc3e32022-11-29 19:59:31 -0500475 setCallStartTime(Date.now());
simonf929a362022-11-18 16:53:45 -0500476 }
simon9076a9a2022-11-29 17:13:01 -0500477 }, [iceConnectionState, callStatus]);
simonaccd8022022-11-24 15:04:53 -0500478
MichelleSS55164202022-11-25 18:36:14 -0500479 useEffect(() => {
Charlie380dc5e2022-11-29 16:51:42 -0500480 if (iceConnectionState === 'disconnected' || iceConnectionState === 'failed') {
481 console.info('ICE connection disconnected or failed, ending call');
Charlieb837e8f2022-11-28 19:18:46 -0500482 endCall();
483 }
simon1e2bf342022-12-02 12:19:40 -0500484 }, [iceConnectionState, callStatus, videoStatus, endCall]);
Charlieb837e8f2022-11-28 19:18:46 -0500485
486 useEffect(() => {
MichelleSS55164202022-11-25 18:36:14 -0500487 const checkStatusTimeout = () => {
488 if (callStatus !== CallStatus.InCall) {
489 endCall();
simonff1cb352022-11-24 15:15:26 -0500490 }
MichelleSS55164202022-11-25 18:36:14 -0500491 };
492 const timeoutId = setTimeout(checkStatusTimeout, callTimeoutMs);
simonff1cb352022-11-24 15:15:26 -0500493
MichelleSS55164202022-11-25 18:36:14 -0500494 return () => {
495 clearTimeout(timeoutId);
496 };
497 }, [callStatus, endCall]);
simonff1cb352022-11-24 15:15:26 -0500498
simon492e8402022-11-29 16:48:37 -0500499 const currentMediaDeviceIds: CurrentMediaDeviceIds = useMemo(() => {
500 const createSetIdForDeviceKind = (mediaInputKind: MediaInputKind) => async (id: string | undefined) => {
501 const mediaDeviceIds = {
502 audio: audioInputDeviceId,
503 video: videoDeviceId,
504 };
505
506 mediaDeviceIds[mediaInputKind] = id;
507
508 await updateLocalStream(mediaDeviceIds);
509 };
510
511 return {
512 audioinput: {
513 id: audioInputDeviceId,
514 setId: createSetIdForDeviceKind('audio'),
515 },
516 audiooutput: {
517 id: audioOutputDeviceId,
518 setId: setAudioOutputDeviceId,
519 },
520 videoinput: {
521 id: videoDeviceId,
522 setId: createSetIdForDeviceKind('video'),
523 },
524 };
525 }, [updateLocalStream, audioInputDeviceId, audioOutputDeviceId, videoDeviceId]);
526
simon5c677962022-12-02 16:51:54 -0500527 return useMemo(
528 () => ({
idilloncf42c652023-01-31 18:56:17 -0500529 localStream,
530 screenShareLocalStream,
531 remoteStreams,
simon5c677962022-12-02 16:51:54 -0500532 mediaDevices,
533 currentMediaDeviceIds,
534 isAudioOn,
535 setIsAudioOn,
536 videoStatus,
537 updateVideoStatus,
538 isChatShown,
539 setIsChatShown,
540 isFullscreen,
541 setIsFullscreen,
542 callRole,
543 callStatus,
544 callStartTime,
545 acceptCall,
546 endCall,
547 }),
548 [
idilloncf42c652023-01-31 18:56:17 -0500549 localStream,
550 screenShareLocalStream,
551 remoteStreams,
simon5c677962022-12-02 16:51:54 -0500552 mediaDevices,
553 currentMediaDeviceIds,
554 isAudioOn,
simon5c677962022-12-02 16:51:54 -0500555 videoStatus,
556 updateVideoStatus,
557 isChatShown,
simon5c677962022-12-02 16:51:54 -0500558 isFullscreen,
simon5c677962022-12-02 16:51:54 -0500559 callRole,
560 callStatus,
561 callStartTime,
562 acceptCall,
563 endCall,
564 ]
simonf929a362022-11-18 16:53:45 -0500565 );
566};