blob: bb99717364e3c320151c2d5a32724e269aa60139 [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';
20import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
21
simonf353ef42022-11-28 23:14:53 -050022import LoadingPage from '../components/Loading';
simone35acc22022-12-02 16:51:12 -050023import { Conversation } from '../models/conversation';
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';
simonf353ef42022-11-28 23:14:53 -050027import { IWebSocketContext, WebSocketContext } from './WebSocketProvider';
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050028
simon492e8402022-11-29 16:48:37 -050029export type MediaDevicesInfo = Record<MediaDeviceKind, MediaDeviceInfo[]>;
30export type MediaInputKind = 'audio' | 'video';
31export type MediaInputIds = Record<MediaInputKind, string | false | undefined>;
32
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050033interface IWebRtcContext {
Charlieb837e8f2022-11-28 19:18:46 -050034 iceConnectionState: RTCIceConnectionState | undefined;
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050035
simon9076a9a2022-11-29 17:13:01 -050036 localStream: MediaStream | undefined;
simon1e2bf342022-12-02 12:19:40 -050037 screenShareLocalStream: MediaStream | undefined;
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050038 remoteStreams: readonly MediaStream[] | undefined;
simon492e8402022-11-29 16:48:37 -050039 getMediaDevices: () => Promise<MediaDevicesInfo>;
40 updateLocalStream: (mediaDeviceIds?: MediaInputIds) => Promise<void>;
simon1e2bf342022-12-02 12:19:40 -050041 updateScreenShare: (active: boolean) => Promise<MediaStream | undefined>;
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050042
simon9076a9a2022-11-29 17:13:01 -050043 sendWebRtcOffer: () => Promise<void>;
44 closeConnection: () => void;
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050045}
46
47const defaultWebRtcContext: IWebRtcContext = {
Charlieb837e8f2022-11-28 19:18:46 -050048 iceConnectionState: undefined,
simon9076a9a2022-11-29 17:13:01 -050049 localStream: undefined,
simon1e2bf342022-12-02 12:19:40 -050050 screenShareLocalStream: undefined,
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050051 remoteStreams: undefined,
simon492e8402022-11-29 16:48:37 -050052 getMediaDevices: async () => Promise.reject(),
53 updateLocalStream: async () => Promise.reject(),
simon1e2bf342022-12-02 12:19:40 -050054 updateScreenShare: async () => Promise.reject(),
simon492e8402022-11-29 16:48:37 -050055 sendWebRtcOffer: async () => Promise.reject(),
simon9076a9a2022-11-29 17:13:01 -050056 closeConnection: () => {},
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050057};
58
59export const WebRtcContext = createContext<IWebRtcContext>(defaultWebRtcContext);
60
61export default ({ children }: WithChildren) => {
simon71d1c0a2022-11-24 15:28:33 -050062 const { account } = useAuthContext();
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050063 const [webRtcConnection, setWebRtcConnection] = useState<RTCPeerConnection | undefined>();
simonf353ef42022-11-28 23:14:53 -050064 const webSocket = useContext(WebSocketContext);
simone35acc22022-12-02 16:51:12 -050065 const { callConversation, callData } = useContext(CallManagerContext);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050066
67 useEffect(() => {
simon71d1c0a2022-11-24 15:28:33 -050068 if (!webRtcConnection && account) {
69 const iceServers: RTCIceServer[] = [];
70
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050071 if (account.details['TURN.enable'] === 'true') {
simon71d1c0a2022-11-24 15:28:33 -050072 iceServers.push({
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050073 urls: 'turn:' + account.details['TURN.server'],
74 username: account.details['TURN.username'],
75 credential: account.details['TURN.password'],
simon71d1c0a2022-11-24 15:28:33 -050076 });
77 }
78
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050079 if (account.details['STUN.enable'] === 'true') {
simon71d1c0a2022-11-24 15:28:33 -050080 iceServers.push({
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -050081 urls: 'stun:' + account.details['STUN.server'],
simon71d1c0a2022-11-24 15:28:33 -050082 });
83 }
84
simon9076a9a2022-11-29 17:13:01 -050085 setWebRtcConnection(new RTCPeerConnection({ iceServers }));
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050086 }
simon71d1c0a2022-11-24 15:28:33 -050087 }, [account, webRtcConnection]);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050088
simone35acc22022-12-02 16:51:12 -050089 if (!webRtcConnection || !webSocket || !callConversation || !callData?.conversationId) {
simonf353ef42022-11-28 23:14:53 -050090 return <LoadingPage />;
91 }
92
93 return (
simone35acc22022-12-02 16:51:12 -050094 <WebRtcProvider
95 webRtcConnection={webRtcConnection}
96 webSocket={webSocket}
97 conversation={callConversation}
98 conversationId={callData.conversationId}
99 >
simonf353ef42022-11-28 23:14:53 -0500100 {children}
101 </WebRtcProvider>
102 );
103};
104
105const WebRtcProvider = ({
106 children,
simone35acc22022-12-02 16:51:12 -0500107 conversation,
108 conversationId,
simonf353ef42022-11-28 23:14:53 -0500109 webRtcConnection,
110 webSocket,
111}: WithChildren & {
112 webRtcConnection: RTCPeerConnection;
113 webSocket: IWebSocketContext;
simone35acc22022-12-02 16:51:12 -0500114 conversation: Conversation;
115 conversationId: string;
simonf353ef42022-11-28 23:14:53 -0500116}) => {
simon9076a9a2022-11-29 17:13:01 -0500117 const [localStream, setLocalStream] = useState<MediaStream>();
simon1e2bf342022-12-02 12:19:40 -0500118 const [screenShareLocalStream, setScreenShareLocalStream] = useState<MediaStream>();
simonf353ef42022-11-28 23:14:53 -0500119 const [remoteStreams, setRemoteStreams] = useState<readonly MediaStream[]>();
Charlieb837e8f2022-11-28 19:18:46 -0500120 const [iceConnectionState, setIceConnectionState] = useState<RTCIceConnectionState | undefined>();
simon492e8402022-11-29 16:48:37 -0500121
122 const [audioRtcRtpSenders, setAudioRtcRtpSenders] = useState<RTCRtpSender[]>();
123 const [videoRtcRtpSenders, setVideoRtcRtpSenders] = useState<RTCRtpSender[]>();
simonf353ef42022-11-28 23:14:53 -0500124
simon25bfee82022-11-28 19:41:24 -0500125 // TODO: The ICE candidate queue is used to cache candidates that were received before `setRemoteDescription` was
126 // called. This is currently necessary, because the jami-daemon is unreliable as a WebRTC signaling channel,
127 // because messages can be received with a delay or out of order. This queue is a temporary workaround that
128 // should be replaced if there is a better way to send messages with the daemon.
129 // Relevant links:
130 // - https://github.com/w3c/webrtc-pc/issues/2519#issuecomment-622055440
131 // - https://stackoverflow.com/questions/57256828/how-to-fix-invalidstateerror-cannot-add-ice-candidate-when-there-is-no-remote-s
132 const [isReadyForIceCandidates, setIsReadyForIceCandidates] = useState(false);
133 const [iceCandidateQueue, setIceCandidateQueue] = useState<RTCIceCandidate[]>([]);
134
simonf353ef42022-11-28 23:14:53 -0500135 // TODO: This logic will have to change to support multiple people in a call
Misha Krieger-Raynauldcfa44302022-11-30 18:36:36 -0500136 const contactUri = useMemo(() => conversation.getFirstMember().contact.uri, [conversation]);
simonf353ef42022-11-28 23:14:53 -0500137
simon492e8402022-11-29 16:48:37 -0500138 const getMediaDevices = useCallback(async (): Promise<MediaDevicesInfo> => {
simon9076a9a2022-11-29 17:13:01 -0500139 try {
140 const devices = await navigator.mediaDevices.enumerateDevices();
simon492e8402022-11-29 16:48:37 -0500141
142 // TODO: On Firefox, some devices can sometime be duplicated (2 devices can share the same deviceId). Using a map
143 // and then converting it to an array makes it so that there is no duplicate. If we find a way to prevent
144 // Firefox from listing 2 devices with the same deviceId, we can remove this logic.
145 const newMediaDevices: Record<MediaDeviceKind, Record<string, MediaDeviceInfo>> = {
146 audioinput: {},
147 audiooutput: {},
148 videoinput: {},
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500149 };
150
simon9076a9a2022-11-29 17:13:01 -0500151 for (const device of devices) {
simon492e8402022-11-29 16:48:37 -0500152 newMediaDevices[device.kind][device.deviceId] = device;
simon9076a9a2022-11-29 17:13:01 -0500153 }
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500154
simon492e8402022-11-29 16:48:37 -0500155 return {
156 audioinput: Object.values(newMediaDevices.audioinput),
157 audiooutput: Object.values(newMediaDevices.audiooutput),
158 videoinput: Object.values(newMediaDevices.videoinput),
159 };
simon9076a9a2022-11-29 17:13:01 -0500160 } catch (e) {
161 throw new Error('Could not get media devices', { cause: e });
162 }
163 }, []);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500164
simon492e8402022-11-29 16:48:37 -0500165 const updateLocalStream = useCallback(
166 async (mediaDeviceIds?: MediaInputIds) => {
167 const devices = await getMediaDevices();
simon9076a9a2022-11-29 17:13:01 -0500168
simon492e8402022-11-29 16:48:37 -0500169 let audioConstraint: MediaTrackConstraints | boolean = devices.audioinput.length !== 0;
170 let videoConstraint: MediaTrackConstraints | boolean = devices.videoinput.length !== 0;
171
172 if (!audioConstraint && !videoConstraint) {
173 return;
174 }
175
176 if (mediaDeviceIds?.audio !== undefined) {
177 audioConstraint = mediaDeviceIds.audio !== false ? { deviceId: mediaDeviceIds.audio } : false;
178 }
179 if (mediaDeviceIds?.video !== undefined) {
180 videoConstraint = mediaDeviceIds.video !== false ? { deviceId: mediaDeviceIds.video } : false;
181 }
182
simon9076a9a2022-11-29 17:13:01 -0500183 try {
simon492e8402022-11-29 16:48:37 -0500184 const stream = await navigator.mediaDevices.getUserMedia({
185 audio: audioConstraint,
186 video: videoConstraint,
187 });
188
189 for (const track of stream.getTracks()) {
190 track.enabled = false;
191 }
192
193 setLocalStream(stream);
simon9076a9a2022-11-29 17:13:01 -0500194 } catch (e) {
simon492e8402022-11-29 16:48:37 -0500195 throw new Error('Could not get media devices', { cause: e });
simon9076a9a2022-11-29 17:13:01 -0500196 }
simon492e8402022-11-29 16:48:37 -0500197 },
198 [getMediaDevices]
199 );
simon9076a9a2022-11-29 17:13:01 -0500200
simon1e2bf342022-12-02 12:19:40 -0500201 const updateScreenShare = useCallback(
202 async (isOn: boolean) => {
203 if (isOn) {
204 const stream = await navigator.mediaDevices.getDisplayMedia({
205 video: true,
206 audio: false,
207 });
208
209 setScreenShareLocalStream(stream);
210 return stream;
211 } else {
212 if (screenShareLocalStream) {
213 for (const track of screenShareLocalStream.getTracks()) {
214 track.stop();
215 }
216 }
217
218 setScreenShareLocalStream(undefined);
219 }
220 },
221 [screenShareLocalStream]
222 );
223
simon492e8402022-11-29 16:48:37 -0500224 useEffect(() => {
simon1e2bf342022-12-02 12:19:40 -0500225 if ((!localStream && !screenShareLocalStream) || !webRtcConnection) {
simon9076a9a2022-11-29 17:13:01 -0500226 return;
227 }
228
simon1e2bf342022-12-02 12:19:40 -0500229 const updateTracks = async (stream: MediaStream, kind: 'audio' | 'video') => {
simon492e8402022-11-29 16:48:37 -0500230 const senders = kind === 'audio' ? audioRtcRtpSenders : videoRtcRtpSenders;
simon1e2bf342022-12-02 12:19:40 -0500231 const tracks = kind === 'audio' ? stream.getAudioTracks() : stream.getVideoTracks();
simon492e8402022-11-29 16:48:37 -0500232 if (senders) {
233 const promises: Promise<void>[] = [];
234 for (let i = 0; i < senders.length; i++) {
235 // TODO: There is a bug where calling multiple times `addTrack` when changing an input device doesn't work.
236 // Calling `addTrack` doesn't trigger the `track` event listener for the other user.
237 // This workaround makes it possible to replace a track, but it could be improved by figuring out the
238 // proper way of changing a track.
239 promises.push(
240 senders[i].replaceTrack(tracks[i]).catch((e) => {
241 console.error('Error replacing track:', e);
242 })
243 );
244 }
245 return Promise.all(promises);
simon9076a9a2022-11-29 17:13:01 -0500246 }
247
simon492e8402022-11-29 16:48:37 -0500248 // TODO: Currently, we do not support adding new devices. To enable this feature, we would need to implement
249 // the "Perfect negotiation" pattern to renegotiate after `addTrack`.
250 // https://blog.mozilla.org/webrtc/perfect-negotiation-in-webrtc/
simon1e2bf342022-12-02 12:19:40 -0500251 const newSenders = tracks.map((track) => webRtcConnection.addTrack(track, stream));
simon492e8402022-11-29 16:48:37 -0500252 if (kind === 'audio') {
253 setAudioRtcRtpSenders(newSenders);
254 } else {
255 setVideoRtcRtpSenders(newSenders);
256 }
257 };
258
simon1e2bf342022-12-02 12:19:40 -0500259 if (localStream) {
260 updateTracks(localStream, 'audio');
261 updateTracks(localStream, 'video');
262 }
263
264 if (screenShareLocalStream) {
265 updateTracks(screenShareLocalStream, 'video');
266 }
267 }, [localStream, screenShareLocalStream, webRtcConnection, audioRtcRtpSenders, videoRtcRtpSenders]);
simon9076a9a2022-11-29 17:13:01 -0500268
269 const sendWebRtcOffer = useCallback(async () => {
270 const sdp = await webRtcConnection.createOffer({
271 offerToReceiveAudio: true,
272 offerToReceiveVideo: true,
273 });
274
275 const webRtcOffer: WebRtcSdp = {
276 contactId: contactUri,
277 conversationId: conversationId,
278 sdp,
279 };
280
281 await webRtcConnection.setLocalDescription(new RTCSessionDescription(sdp));
282 console.info('Sending WebRtcOffer', webRtcOffer);
283 webSocket.send(WebSocketMessageType.WebRtcOffer, webRtcOffer);
284 }, [webRtcConnection, webSocket, conversationId, contactUri]);
285
286 const sendWebRtcAnswer = useCallback(async () => {
287 const sdp = await webRtcConnection.createAnswer({
288 offerToReceiveAudio: true,
289 offerToReceiveVideo: true,
290 });
291
292 const webRtcAnswer: WebRtcSdp = {
293 contactId: contactUri,
294 conversationId: conversationId,
295 sdp,
296 };
297
298 await webRtcConnection.setLocalDescription(new RTCSessionDescription(sdp));
299 console.info('Sending WebRtcAnswer', webRtcAnswer);
300 webSocket.send(WebSocketMessageType.WebRtcAnswer, webRtcAnswer);
301 }, [contactUri, conversationId, webRtcConnection, webSocket]);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500302
simonf353ef42022-11-28 23:14:53 -0500303 /* WebSocket Listeners */
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500304
simonf353ef42022-11-28 23:14:53 -0500305 useEffect(() => {
simon25bfee82022-11-28 19:41:24 -0500306 const addQueuedIceCandidates = async () => {
307 console.info('WebRTC remote description has been set. Ready to receive ICE candidates');
308 setIsReadyForIceCandidates(true);
309 if (iceCandidateQueue.length !== 0) {
simon492e8402022-11-29 16:48:37 -0500310 console.warn(
311 'Found queued ICE candidates that were added before `setRemoteDescription` was called. ' +
312 'Adding queued ICE candidates...',
313 iceCandidateQueue
314 );
simon25bfee82022-11-28 19:41:24 -0500315
316 await Promise.all(iceCandidateQueue.map((iceCandidate) => webRtcConnection.addIceCandidate(iceCandidate)));
317 }
318 };
319
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500320 const webRtcOfferListener = async (data: WebRtcSdp) => {
321 console.info('Received event on WebRtcOffer', data);
Charliec18d6402022-11-27 13:01:04 -0500322 if (data.conversationId !== conversationId) {
323 console.warn('Wrong incoming conversationId, ignoring action');
324 return;
325 }
326
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500327 await webRtcConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
simon9076a9a2022-11-29 17:13:01 -0500328 await sendWebRtcAnswer();
simon25bfee82022-11-28 19:41:24 -0500329 await addQueuedIceCandidates();
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500330 };
331
332 const webRtcAnswerListener = async (data: WebRtcSdp) => {
333 console.info('Received event on WebRtcAnswer', data);
Charliec18d6402022-11-27 13:01:04 -0500334 if (data.conversationId !== conversationId) {
335 console.warn('Wrong incoming conversationId, ignoring action');
336 return;
337 }
338
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500339 await webRtcConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
simon25bfee82022-11-28 19:41:24 -0500340 await addQueuedIceCandidates();
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500341 };
simon9076a9a2022-11-29 17:13:01 -0500342
simonf353ef42022-11-28 23:14:53 -0500343 webSocket.bind(WebSocketMessageType.WebRtcOffer, webRtcOfferListener);
344 webSocket.bind(WebSocketMessageType.WebRtcAnswer, webRtcAnswerListener);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500345
simonf353ef42022-11-28 23:14:53 -0500346 return () => {
347 webSocket.unbind(WebSocketMessageType.WebRtcOffer, webRtcOfferListener);
348 webSocket.unbind(WebSocketMessageType.WebRtcAnswer, webRtcAnswerListener);
349 };
simon25bfee82022-11-28 19:41:24 -0500350 }, [webSocket, webRtcConnection, sendWebRtcAnswer, conversationId, iceCandidateQueue]);
simonf353ef42022-11-28 23:14:53 -0500351
352 useEffect(() => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500353 const webRtcIceCandidateListener = async (data: WebRtcIceCandidate) => {
Charliec18d6402022-11-27 13:01:04 -0500354 if (data.conversationId !== conversationId) {
355 console.warn('Wrong incoming conversationId, ignoring action');
356 return;
357 }
358
simon25bfee82022-11-28 19:41:24 -0500359 if (!data.candidate) {
360 return;
361 }
362
363 if (isReadyForIceCandidates) {
364 await webRtcConnection.addIceCandidate(data.candidate);
365 } else {
simon25bfee82022-11-28 19:41:24 -0500366 setIceCandidateQueue((v) => {
367 v.push(data.candidate);
368 return v;
369 });
370 }
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500371 };
372
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500373 webSocket.bind(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidateListener);
374
375 return () => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500376 webSocket.unbind(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidateListener);
377 };
simon25bfee82022-11-28 19:41:24 -0500378 }, [webRtcConnection, webSocket, conversationId, isReadyForIceCandidates]);
simonf353ef42022-11-28 23:14:53 -0500379
380 /* WebRTC Listeners */
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500381
382 useEffect(() => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500383 const iceCandidateEventListener = (event: RTCPeerConnectionIceEvent) => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500384 if (event.candidate) {
385 const webRtcIceCandidate: WebRtcIceCandidate = {
386 contactId: contactUri,
Charliec18d6402022-11-27 13:01:04 -0500387 conversationId: conversationId,
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500388 candidate: event.candidate,
389 };
390
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500391 webSocket.send(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidate);
392 }
393 };
simonf353ef42022-11-28 23:14:53 -0500394 webRtcConnection.addEventListener('icecandidate', iceCandidateEventListener);
395
396 return () => {
397 webRtcConnection.removeEventListener('icecandidate', iceCandidateEventListener);
398 };
399 }, [webRtcConnection, webSocket, contactUri, conversationId]);
400
401 useEffect(() => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500402 const trackEventListener = (event: RTCTrackEvent) => {
403 console.info('Received WebRTC event on track', event);
404 setRemoteStreams(event.streams);
405 };
406
simon9076a9a2022-11-29 17:13:01 -0500407 const iceConnectionStateChangeEventListener = (event: Event) => {
408 console.info(`Received WebRTC event on iceconnectionstatechange: ${webRtcConnection.iceConnectionState}`, event);
Charlieb837e8f2022-11-28 19:18:46 -0500409 setIceConnectionState(webRtcConnection.iceConnectionState);
simonfeaa1db2022-11-26 20:13:18 -0500410 };
411
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500412 webRtcConnection.addEventListener('track', trackEventListener);
MichelleSS55164202022-11-25 18:36:14 -0500413 webRtcConnection.addEventListener('iceconnectionstatechange', iceConnectionStateChangeEventListener);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500414
415 return () => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500416 webRtcConnection.removeEventListener('track', trackEventListener);
MichelleSS55164202022-11-25 18:36:14 -0500417 webRtcConnection.removeEventListener('iceconnectionstatechange', iceConnectionStateChangeEventListener);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500418 };
simonf353ef42022-11-28 23:14:53 -0500419 }, [webRtcConnection]);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500420
simon9076a9a2022-11-29 17:13:01 -0500421 const closeConnection = useCallback(() => {
simon1e2bf342022-12-02 12:19:40 -0500422 const stopStream = (stream: MediaStream) => {
423 const localTracks = stream.getTracks();
424 if (localTracks) {
425 for (const track of localTracks) {
426 track.stop();
427 }
simon9076a9a2022-11-29 17:13:01 -0500428 }
simon1e2bf342022-12-02 12:19:40 -0500429 };
430
431 if (localStream) {
432 stopStream(localStream);
433 }
434 if (screenShareLocalStream) {
435 stopStream(screenShareLocalStream);
simon9076a9a2022-11-29 17:13:01 -0500436 }
437
438 webRtcConnection.close();
simon1e2bf342022-12-02 12:19:40 -0500439 }, [webRtcConnection, localStream, screenShareLocalStream]);
simon9076a9a2022-11-29 17:13:01 -0500440
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500441 return (
442 <WebRtcContext.Provider
443 value={{
Charlieb837e8f2022-11-28 19:18:46 -0500444 iceConnectionState,
simon9076a9a2022-11-29 17:13:01 -0500445 localStream,
simon1e2bf342022-12-02 12:19:40 -0500446 screenShareLocalStream,
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500447 remoteStreams,
simon492e8402022-11-29 16:48:37 -0500448 getMediaDevices,
449 updateLocalStream,
simon1e2bf342022-12-02 12:19:40 -0500450 updateScreenShare,
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500451 sendWebRtcOffer,
simon9076a9a2022-11-29 17:13:01 -0500452 closeConnection,
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500453 }}
454 >
455 {children}
456 </WebRtcContext.Provider>
457 );
458};