blob: e35df0cc0ef12d6e4772ace79b8e6d76049ef737 [file] [log] [blame]
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -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 */
18
19import { WebRtcIceCandidate, WebRtcSdp, WebSocketMessageType } from 'jami-web-common';
simon5c677962022-12-02 16:51:54 -050020import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050021
simon5c677962022-12-02 16:51:54 -050022import { createOptionalContext } from '../hooks/createOptionalContext';
idillon07d31cc2022-12-06 22:40:14 -050023import { ConversationMember } from '../models/conversation-member';
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050024import { WithChildren } from '../utils/utils';
simon71d1c0a2022-11-24 15:28:33 -050025import { useAuthContext } from './AuthProvider';
simone35acc22022-12-02 16:51:12 -050026import { CallManagerContext } from './CallManagerProvider';
simon5c677962022-12-02 16:51:54 -050027import ConditionalContextProvider from './ConditionalContextProvider';
simonf353ef42022-11-28 23:14:53 -050028import { IWebSocketContext, WebSocketContext } from './WebSocketProvider';
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050029
simon492e8402022-11-29 16:48:37 -050030export type MediaDevicesInfo = Record<MediaDeviceKind, MediaDeviceInfo[]>;
31export type MediaInputKind = 'audio' | 'video';
32export type MediaInputIds = Record<MediaInputKind, string | false | undefined>;
33
simon5c677962022-12-02 16:51:54 -050034export interface IWebRtcContext {
Charlieb837e8f2022-11-28 19:18:46 -050035 iceConnectionState: RTCIceConnectionState | undefined;
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050036
simon9076a9a2022-11-29 17:13:01 -050037 localStream: MediaStream | undefined;
simon1e2bf342022-12-02 12:19:40 -050038 screenShareLocalStream: MediaStream | undefined;
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050039 remoteStreams: readonly MediaStream[] | undefined;
simon492e8402022-11-29 16:48:37 -050040 getMediaDevices: () => Promise<MediaDevicesInfo>;
41 updateLocalStream: (mediaDeviceIds?: MediaInputIds) => Promise<void>;
simon1e2bf342022-12-02 12:19:40 -050042 updateScreenShare: (active: boolean) => Promise<MediaStream | undefined>;
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050043
simon9076a9a2022-11-29 17:13:01 -050044 sendWebRtcOffer: () => Promise<void>;
45 closeConnection: () => void;
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050046}
47
simon5c677962022-12-02 16:51:54 -050048const optionalWebRtcContext = createOptionalContext<IWebRtcContext>('WebRtcContext');
49export const useWebRtcContext = optionalWebRtcContext.useOptionalContext;
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050050
51export default ({ children }: WithChildren) => {
simon71d1c0a2022-11-24 15:28:33 -050052 const { account } = useAuthContext();
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050053 const [webRtcConnection, setWebRtcConnection] = useState<RTCPeerConnection | undefined>();
simonf353ef42022-11-28 23:14:53 -050054 const webSocket = useContext(WebSocketContext);
idillon07d31cc2022-12-06 22:40:14 -050055 const { callConversationInfos, callMembers, callData } = useContext(CallManagerContext);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050056
57 useEffect(() => {
simon5c677962022-12-02 16:51:54 -050058 if (webRtcConnection && !callData) {
59 setWebRtcConnection(undefined);
60 return;
61 }
62
63 if (!webRtcConnection && account && callData) {
simon71d1c0a2022-11-24 15:28:33 -050064 const iceServers: RTCIceServer[] = [];
65
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050066 if (account.details['TURN.enable'] === 'true') {
simon71d1c0a2022-11-24 15:28:33 -050067 iceServers.push({
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050068 urls: 'turn:' + account.details['TURN.server'],
69 username: account.details['TURN.username'],
70 credential: account.details['TURN.password'],
simon71d1c0a2022-11-24 15:28:33 -050071 });
72 }
73
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050074 if (account.details['STUN.enable'] === 'true') {
simon71d1c0a2022-11-24 15:28:33 -050075 iceServers.push({
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050076 urls: 'stun:' + account.details['STUN.server'],
simon71d1c0a2022-11-24 15:28:33 -050077 });
78 }
79
simon9076a9a2022-11-29 17:13:01 -050080 setWebRtcConnection(new RTCPeerConnection({ iceServers }));
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050081 }
simon5c677962022-12-02 16:51:54 -050082 }, [account, webRtcConnection, callData]);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050083
simon5c677962022-12-02 16:51:54 -050084 const dependencies = useMemo(
85 () => ({
86 webRtcConnection,
87 webSocket,
idillon07d31cc2022-12-06 22:40:14 -050088 conversationInfos: callConversationInfos,
89 members: callMembers,
simon5c677962022-12-02 16:51:54 -050090 conversationId: callData?.conversationId,
91 }),
idillon07d31cc2022-12-06 22:40:14 -050092 [webRtcConnection, webSocket, callConversationInfos, callMembers, callData?.conversationId]
simon5c677962022-12-02 16:51:54 -050093 );
simonf353ef42022-11-28 23:14:53 -050094
95 return (
simon5c677962022-12-02 16:51:54 -050096 <ConditionalContextProvider
97 Context={optionalWebRtcContext.Context}
98 initialValue={undefined}
99 dependencies={dependencies}
100 useProviderValue={useWebRtcContextValue}
simone35acc22022-12-02 16:51:12 -0500101 >
simonf353ef42022-11-28 23:14:53 -0500102 {children}
simon5c677962022-12-02 16:51:54 -0500103 </ConditionalContextProvider>
simonf353ef42022-11-28 23:14:53 -0500104 );
105};
106
simon5c677962022-12-02 16:51:54 -0500107const useWebRtcContextValue = ({
idillon07d31cc2022-12-06 22:40:14 -0500108 members,
simone35acc22022-12-02 16:51:12 -0500109 conversationId,
simonf353ef42022-11-28 23:14:53 -0500110 webRtcConnection,
111 webSocket,
simon5c677962022-12-02 16:51:54 -0500112}: {
simonf353ef42022-11-28 23:14:53 -0500113 webRtcConnection: RTCPeerConnection;
114 webSocket: IWebSocketContext;
idillon07d31cc2022-12-06 22:40:14 -0500115 members: ConversationMember[];
simone35acc22022-12-02 16:51:12 -0500116 conversationId: string;
simonf353ef42022-11-28 23:14:53 -0500117}) => {
simon9076a9a2022-11-29 17:13:01 -0500118 const [localStream, setLocalStream] = useState<MediaStream>();
simon1e2bf342022-12-02 12:19:40 -0500119 const [screenShareLocalStream, setScreenShareLocalStream] = useState<MediaStream>();
simonf353ef42022-11-28 23:14:53 -0500120 const [remoteStreams, setRemoteStreams] = useState<readonly MediaStream[]>();
Charlieb837e8f2022-11-28 19:18:46 -0500121 const [iceConnectionState, setIceConnectionState] = useState<RTCIceConnectionState | undefined>();
simon492e8402022-11-29 16:48:37 -0500122
123 const [audioRtcRtpSenders, setAudioRtcRtpSenders] = useState<RTCRtpSender[]>();
124 const [videoRtcRtpSenders, setVideoRtcRtpSenders] = useState<RTCRtpSender[]>();
simonf353ef42022-11-28 23:14:53 -0500125
simon25bfee82022-11-28 19:41:24 -0500126 // TODO: The ICE candidate queue is used to cache candidates that were received before `setRemoteDescription` was
127 // called. This is currently necessary, because the jami-daemon is unreliable as a WebRTC signaling channel,
128 // because messages can be received with a delay or out of order. This queue is a temporary workaround that
129 // should be replaced if there is a better way to send messages with the daemon.
130 // Relevant links:
131 // - https://github.com/w3c/webrtc-pc/issues/2519#issuecomment-622055440
132 // - https://stackoverflow.com/questions/57256828/how-to-fix-invalidstateerror-cannot-add-ice-candidate-when-there-is-no-remote-s
133 const [isReadyForIceCandidates, setIsReadyForIceCandidates] = useState(false);
134 const [iceCandidateQueue, setIceCandidateQueue] = useState<RTCIceCandidate[]>([]);
135
simonf353ef42022-11-28 23:14:53 -0500136 // TODO: This logic will have to change to support multiple people in a call
idillon07d31cc2022-12-06 22:40:14 -0500137 const contactUri = useMemo(() => members[0]?.contact.uri, [members]);
simonf353ef42022-11-28 23:14:53 -0500138
simon492e8402022-11-29 16:48:37 -0500139 const getMediaDevices = useCallback(async (): Promise<MediaDevicesInfo> => {
simon9076a9a2022-11-29 17:13:01 -0500140 try {
141 const devices = await navigator.mediaDevices.enumerateDevices();
simon492e8402022-11-29 16:48:37 -0500142
143 // TODO: On Firefox, some devices can sometime be duplicated (2 devices can share the same deviceId). Using a map
144 // and then converting it to an array makes it so that there is no duplicate. If we find a way to prevent
145 // Firefox from listing 2 devices with the same deviceId, we can remove this logic.
146 const newMediaDevices: Record<MediaDeviceKind, Record<string, MediaDeviceInfo>> = {
147 audioinput: {},
148 audiooutput: {},
149 videoinput: {},
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500150 };
151
simon9076a9a2022-11-29 17:13:01 -0500152 for (const device of devices) {
simon492e8402022-11-29 16:48:37 -0500153 newMediaDevices[device.kind][device.deviceId] = device;
simon9076a9a2022-11-29 17:13:01 -0500154 }
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500155
simon492e8402022-11-29 16:48:37 -0500156 return {
157 audioinput: Object.values(newMediaDevices.audioinput),
158 audiooutput: Object.values(newMediaDevices.audiooutput),
159 videoinput: Object.values(newMediaDevices.videoinput),
160 };
simon9076a9a2022-11-29 17:13:01 -0500161 } catch (e) {
162 throw new Error('Could not get media devices', { cause: e });
163 }
164 }, []);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500165
simon492e8402022-11-29 16:48:37 -0500166 const updateLocalStream = useCallback(
167 async (mediaDeviceIds?: MediaInputIds) => {
168 const devices = await getMediaDevices();
simon9076a9a2022-11-29 17:13:01 -0500169
simon492e8402022-11-29 16:48:37 -0500170 let audioConstraint: MediaTrackConstraints | boolean = devices.audioinput.length !== 0;
171 let videoConstraint: MediaTrackConstraints | boolean = devices.videoinput.length !== 0;
172
173 if (!audioConstraint && !videoConstraint) {
174 return;
175 }
176
177 if (mediaDeviceIds?.audio !== undefined) {
178 audioConstraint = mediaDeviceIds.audio !== false ? { deviceId: mediaDeviceIds.audio } : false;
179 }
180 if (mediaDeviceIds?.video !== undefined) {
181 videoConstraint = mediaDeviceIds.video !== false ? { deviceId: mediaDeviceIds.video } : false;
182 }
183
simon9076a9a2022-11-29 17:13:01 -0500184 try {
simon492e8402022-11-29 16:48:37 -0500185 const stream = await navigator.mediaDevices.getUserMedia({
186 audio: audioConstraint,
187 video: videoConstraint,
188 });
189
190 for (const track of stream.getTracks()) {
191 track.enabled = false;
192 }
193
194 setLocalStream(stream);
simon9076a9a2022-11-29 17:13:01 -0500195 } catch (e) {
simon492e8402022-11-29 16:48:37 -0500196 throw new Error('Could not get media devices', { cause: e });
simon9076a9a2022-11-29 17:13:01 -0500197 }
simon492e8402022-11-29 16:48:37 -0500198 },
199 [getMediaDevices]
200 );
simon9076a9a2022-11-29 17:13:01 -0500201
simon1e2bf342022-12-02 12:19:40 -0500202 const updateScreenShare = useCallback(
203 async (isOn: boolean) => {
204 if (isOn) {
205 const stream = await navigator.mediaDevices.getDisplayMedia({
206 video: true,
207 audio: false,
208 });
209
210 setScreenShareLocalStream(stream);
211 return stream;
212 } else {
213 if (screenShareLocalStream) {
214 for (const track of screenShareLocalStream.getTracks()) {
215 track.stop();
216 }
217 }
218
219 setScreenShareLocalStream(undefined);
220 }
221 },
222 [screenShareLocalStream]
223 );
224
simon492e8402022-11-29 16:48:37 -0500225 useEffect(() => {
simon1e2bf342022-12-02 12:19:40 -0500226 if ((!localStream && !screenShareLocalStream) || !webRtcConnection) {
simon9076a9a2022-11-29 17:13:01 -0500227 return;
228 }
229
simon1e2bf342022-12-02 12:19:40 -0500230 const updateTracks = async (stream: MediaStream, kind: 'audio' | 'video') => {
simon492e8402022-11-29 16:48:37 -0500231 const senders = kind === 'audio' ? audioRtcRtpSenders : videoRtcRtpSenders;
simon1e2bf342022-12-02 12:19:40 -0500232 const tracks = kind === 'audio' ? stream.getAudioTracks() : stream.getVideoTracks();
simon492e8402022-11-29 16:48:37 -0500233 if (senders) {
234 const promises: Promise<void>[] = [];
235 for (let i = 0; i < senders.length; i++) {
236 // TODO: There is a bug where calling multiple times `addTrack` when changing an input device doesn't work.
237 // Calling `addTrack` doesn't trigger the `track` event listener for the other user.
238 // This workaround makes it possible to replace a track, but it could be improved by figuring out the
239 // proper way of changing a track.
240 promises.push(
241 senders[i].replaceTrack(tracks[i]).catch((e) => {
242 console.error('Error replacing track:', e);
243 })
244 );
245 }
246 return Promise.all(promises);
simon9076a9a2022-11-29 17:13:01 -0500247 }
248
simon492e8402022-11-29 16:48:37 -0500249 // TODO: Currently, we do not support adding new devices. To enable this feature, we would need to implement
250 // the "Perfect negotiation" pattern to renegotiate after `addTrack`.
251 // https://blog.mozilla.org/webrtc/perfect-negotiation-in-webrtc/
simon1e2bf342022-12-02 12:19:40 -0500252 const newSenders = tracks.map((track) => webRtcConnection.addTrack(track, stream));
simon492e8402022-11-29 16:48:37 -0500253 if (kind === 'audio') {
254 setAudioRtcRtpSenders(newSenders);
255 } else {
256 setVideoRtcRtpSenders(newSenders);
257 }
258 };
259
simon1e2bf342022-12-02 12:19:40 -0500260 if (localStream) {
261 updateTracks(localStream, 'audio');
262 updateTracks(localStream, 'video');
263 }
264
265 if (screenShareLocalStream) {
266 updateTracks(screenShareLocalStream, 'video');
267 }
268 }, [localStream, screenShareLocalStream, webRtcConnection, audioRtcRtpSenders, videoRtcRtpSenders]);
simon9076a9a2022-11-29 17:13:01 -0500269
270 const sendWebRtcOffer = useCallback(async () => {
271 const sdp = await webRtcConnection.createOffer({
272 offerToReceiveAudio: true,
273 offerToReceiveVideo: true,
274 });
275
276 const webRtcOffer: WebRtcSdp = {
277 contactId: contactUri,
278 conversationId: conversationId,
279 sdp,
280 };
281
282 await webRtcConnection.setLocalDescription(new RTCSessionDescription(sdp));
283 console.info('Sending WebRtcOffer', webRtcOffer);
284 webSocket.send(WebSocketMessageType.WebRtcOffer, webRtcOffer);
285 }, [webRtcConnection, webSocket, conversationId, contactUri]);
286
287 const sendWebRtcAnswer = useCallback(async () => {
288 const sdp = await webRtcConnection.createAnswer({
289 offerToReceiveAudio: true,
290 offerToReceiveVideo: true,
291 });
292
293 const webRtcAnswer: WebRtcSdp = {
294 contactId: contactUri,
295 conversationId: conversationId,
296 sdp,
297 };
298
299 await webRtcConnection.setLocalDescription(new RTCSessionDescription(sdp));
300 console.info('Sending WebRtcAnswer', webRtcAnswer);
301 webSocket.send(WebSocketMessageType.WebRtcAnswer, webRtcAnswer);
302 }, [contactUri, conversationId, webRtcConnection, webSocket]);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500303
simonf353ef42022-11-28 23:14:53 -0500304 /* WebSocket Listeners */
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500305
simonf353ef42022-11-28 23:14:53 -0500306 useEffect(() => {
simon25bfee82022-11-28 19:41:24 -0500307 const addQueuedIceCandidates = async () => {
308 console.info('WebRTC remote description has been set. Ready to receive ICE candidates');
309 setIsReadyForIceCandidates(true);
310 if (iceCandidateQueue.length !== 0) {
simon492e8402022-11-29 16:48:37 -0500311 console.warn(
312 'Found queued ICE candidates that were added before `setRemoteDescription` was called. ' +
313 'Adding queued ICE candidates...',
314 iceCandidateQueue
315 );
simon25bfee82022-11-28 19:41:24 -0500316
317 await Promise.all(iceCandidateQueue.map((iceCandidate) => webRtcConnection.addIceCandidate(iceCandidate)));
318 }
319 };
320
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500321 const webRtcOfferListener = async (data: WebRtcSdp) => {
322 console.info('Received event on WebRtcOffer', data);
Charliec18d6402022-11-27 13:01:04 -0500323 if (data.conversationId !== conversationId) {
324 console.warn('Wrong incoming conversationId, ignoring action');
325 return;
326 }
327
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500328 await webRtcConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
simon9076a9a2022-11-29 17:13:01 -0500329 await sendWebRtcAnswer();
simon25bfee82022-11-28 19:41:24 -0500330 await addQueuedIceCandidates();
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500331 };
332
333 const webRtcAnswerListener = async (data: WebRtcSdp) => {
334 console.info('Received event on WebRtcAnswer', data);
Charliec18d6402022-11-27 13:01:04 -0500335 if (data.conversationId !== conversationId) {
336 console.warn('Wrong incoming conversationId, ignoring action');
337 return;
338 }
339
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500340 await webRtcConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
simon25bfee82022-11-28 19:41:24 -0500341 await addQueuedIceCandidates();
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500342 };
simon9076a9a2022-11-29 17:13:01 -0500343
simonf353ef42022-11-28 23:14:53 -0500344 webSocket.bind(WebSocketMessageType.WebRtcOffer, webRtcOfferListener);
345 webSocket.bind(WebSocketMessageType.WebRtcAnswer, webRtcAnswerListener);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500346
simonf353ef42022-11-28 23:14:53 -0500347 return () => {
348 webSocket.unbind(WebSocketMessageType.WebRtcOffer, webRtcOfferListener);
349 webSocket.unbind(WebSocketMessageType.WebRtcAnswer, webRtcAnswerListener);
350 };
simon25bfee82022-11-28 19:41:24 -0500351 }, [webSocket, webRtcConnection, sendWebRtcAnswer, conversationId, iceCandidateQueue]);
simonf353ef42022-11-28 23:14:53 -0500352
353 useEffect(() => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500354 const webRtcIceCandidateListener = async (data: WebRtcIceCandidate) => {
Charliec18d6402022-11-27 13:01:04 -0500355 if (data.conversationId !== conversationId) {
356 console.warn('Wrong incoming conversationId, ignoring action');
357 return;
358 }
359
simon25bfee82022-11-28 19:41:24 -0500360 if (!data.candidate) {
361 return;
362 }
363
364 if (isReadyForIceCandidates) {
365 await webRtcConnection.addIceCandidate(data.candidate);
366 } else {
simon25bfee82022-11-28 19:41:24 -0500367 setIceCandidateQueue((v) => {
368 v.push(data.candidate);
369 return v;
370 });
371 }
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500372 };
373
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500374 webSocket.bind(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidateListener);
375
376 return () => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500377 webSocket.unbind(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidateListener);
378 };
simon25bfee82022-11-28 19:41:24 -0500379 }, [webRtcConnection, webSocket, conversationId, isReadyForIceCandidates]);
simonf353ef42022-11-28 23:14:53 -0500380
381 /* WebRTC Listeners */
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500382
383 useEffect(() => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500384 const iceCandidateEventListener = (event: RTCPeerConnectionIceEvent) => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500385 if (event.candidate) {
386 const webRtcIceCandidate: WebRtcIceCandidate = {
387 contactId: contactUri,
Charliec18d6402022-11-27 13:01:04 -0500388 conversationId: conversationId,
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500389 candidate: event.candidate,
390 };
391
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500392 webSocket.send(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidate);
393 }
394 };
simonf353ef42022-11-28 23:14:53 -0500395 webRtcConnection.addEventListener('icecandidate', iceCandidateEventListener);
396
397 return () => {
398 webRtcConnection.removeEventListener('icecandidate', iceCandidateEventListener);
399 };
400 }, [webRtcConnection, webSocket, contactUri, conversationId]);
401
402 useEffect(() => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500403 const trackEventListener = (event: RTCTrackEvent) => {
404 console.info('Received WebRTC event on track', event);
405 setRemoteStreams(event.streams);
406 };
407
simon9076a9a2022-11-29 17:13:01 -0500408 const iceConnectionStateChangeEventListener = (event: Event) => {
409 console.info(`Received WebRTC event on iceconnectionstatechange: ${webRtcConnection.iceConnectionState}`, event);
Charlieb837e8f2022-11-28 19:18:46 -0500410 setIceConnectionState(webRtcConnection.iceConnectionState);
simonfeaa1db2022-11-26 20:13:18 -0500411 };
412
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500413 webRtcConnection.addEventListener('track', trackEventListener);
MichelleSS55164202022-11-25 18:36:14 -0500414 webRtcConnection.addEventListener('iceconnectionstatechange', iceConnectionStateChangeEventListener);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500415
416 return () => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500417 webRtcConnection.removeEventListener('track', trackEventListener);
MichelleSS55164202022-11-25 18:36:14 -0500418 webRtcConnection.removeEventListener('iceconnectionstatechange', iceConnectionStateChangeEventListener);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500419 };
simonf353ef42022-11-28 23:14:53 -0500420 }, [webRtcConnection]);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500421
simon9076a9a2022-11-29 17:13:01 -0500422 const closeConnection = useCallback(() => {
simon1e2bf342022-12-02 12:19:40 -0500423 const stopStream = (stream: MediaStream) => {
424 const localTracks = stream.getTracks();
425 if (localTracks) {
426 for (const track of localTracks) {
427 track.stop();
428 }
simon9076a9a2022-11-29 17:13:01 -0500429 }
simon1e2bf342022-12-02 12:19:40 -0500430 };
431
432 if (localStream) {
433 stopStream(localStream);
434 }
435 if (screenShareLocalStream) {
436 stopStream(screenShareLocalStream);
simon9076a9a2022-11-29 17:13:01 -0500437 }
438
439 webRtcConnection.close();
simon1e2bf342022-12-02 12:19:40 -0500440 }, [webRtcConnection, localStream, screenShareLocalStream]);
simon9076a9a2022-11-29 17:13:01 -0500441
simon5c677962022-12-02 16:51:54 -0500442 return useMemo(
443 () => ({
444 iceConnectionState,
445 localStream,
446 screenShareLocalStream,
447 remoteStreams,
448 getMediaDevices,
449 updateLocalStream,
450 updateScreenShare,
451 sendWebRtcOffer,
452 closeConnection,
453 }),
454 [
455 iceConnectionState,
456 localStream,
457 screenShareLocalStream,
458 remoteStreams,
459 getMediaDevices,
460 updateLocalStream,
461 updateScreenShare,
462 sendWebRtcOffer,
463 closeConnection,
464 ]
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500465 );
466};