blob: f1c9addaa95d7a7ae0bc652a0157c72790206fa8 [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
22import { WithChildren } from '../utils/utils';
simon71d1c0a2022-11-24 15:28:33 -050023import { useAuthContext } from './AuthProvider';
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050024import { ConversationContext } from './ConversationProvider';
25import { WebSocketContext } from './WebSocketProvider';
26
27interface IWebRtcContext {
28 isConnected: boolean;
29
30 remoteStreams: readonly MediaStream[] | undefined;
31 webRtcConnection: RTCPeerConnection | undefined;
32
33 sendWebRtcOffer: (sdp: RTCSessionDescriptionInit) => Promise<void>;
34}
35
36const defaultWebRtcContext: IWebRtcContext = {
37 isConnected: false,
38 remoteStreams: undefined,
39 webRtcConnection: undefined,
40 sendWebRtcOffer: async () => {},
41};
42
43export const WebRtcContext = createContext<IWebRtcContext>(defaultWebRtcContext);
44
45export default ({ children }: WithChildren) => {
simon71d1c0a2022-11-24 15:28:33 -050046 const { account } = useAuthContext();
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050047 const webSocket = useContext(WebSocketContext);
48 const { conversation } = useContext(ConversationContext);
49 const [webRtcConnection, setWebRtcConnection] = useState<RTCPeerConnection | undefined>();
50 const [remoteStreams, setRemoteStreams] = useState<readonly MediaStream[]>();
51 const [isConnected, setIsConnected] = useState(false);
52
53 // TODO: This logic will have to change to support multiple people in a call
54 const contactUri = useMemo(() => conversation.getFirstMember().contact.getUri(), [conversation]);
55
56 useEffect(() => {
simon71d1c0a2022-11-24 15:28:33 -050057 if (!webRtcConnection && account) {
58 const iceServers: RTCIceServer[] = [];
59
60 if (account.getDetails()['TURN.enable'] === 'true') {
61 iceServers.push({
62 urls: 'turn:' + account.getDetails()['TURN.server'],
63 username: account.getDetails()['TURN.username'],
64 credential: account.getDetails()['TURN.password'],
65 });
66 }
67
68 if (account.getDetails()['STUN.enable'] === 'true') {
69 iceServers.push({
70 urls: 'stun:' + account.getDetails()['STUN.server'],
71 });
72 }
73
74 setWebRtcConnection(new RTCPeerConnection({ iceServers: iceServers }));
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050075 }
simon71d1c0a2022-11-24 15:28:33 -050076 }, [account, webRtcConnection]);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050077
78 const sendWebRtcOffer = useCallback(
79 async (sdp: RTCSessionDescriptionInit) => {
80 if (!webRtcConnection || !webSocket) {
81 throw new Error('Could not send WebRTC offer');
82 }
83
84 const webRtcOffer: WebRtcSdp = {
85 contactId: contactUri,
86 sdp,
87 };
88
simonfeaa1db2022-11-26 20:13:18 -050089 await webRtcConnection.setLocalDescription(new RTCSessionDescription(sdp));
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050090 console.info('Sending WebRtcOffer', webRtcOffer);
91 webSocket.send(WebSocketMessageType.WebRtcOffer, webRtcOffer);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050092 },
93 [webRtcConnection, webSocket, contactUri]
94 );
95
96 const sendWebRtcAnswer = useCallback(
97 (sdp: RTCSessionDescriptionInit) => {
98 if (!webRtcConnection || !webSocket) {
99 throw new Error('Could not send WebRTC answer');
100 }
101
102 const webRtcAnswer: WebRtcSdp = {
103 contactId: contactUri,
104 sdp,
105 };
106
107 console.info('Sending WebRtcAnswer', webRtcAnswer);
108 webSocket.send(WebSocketMessageType.WebRtcAnswer, webRtcAnswer);
109 },
110 [contactUri, webRtcConnection, webSocket]
111 );
112
113 useEffect(() => {
114 if (!webSocket || !webRtcConnection) {
115 return;
116 }
117
118 const webRtcOfferListener = async (data: WebRtcSdp) => {
119 console.info('Received event on WebRtcOffer', data);
120 await webRtcConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
121
122 const sdp = await webRtcConnection.createAnswer({
123 offerToReceiveAudio: true,
124 offerToReceiveVideo: true,
125 });
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500126 await webRtcConnection.setLocalDescription(new RTCSessionDescription(sdp));
simonfeaa1db2022-11-26 20:13:18 -0500127 sendWebRtcAnswer(sdp);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500128 };
129
130 const webRtcAnswerListener = async (data: WebRtcSdp) => {
131 console.info('Received event on WebRtcAnswer', data);
132 await webRtcConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500133 };
134
135 const webRtcIceCandidateListener = async (data: WebRtcIceCandidate) => {
136 console.info('Received event on WebRtcIceCandidate', data);
137 await webRtcConnection.addIceCandidate(data.candidate);
138 };
139
140 webSocket.bind(WebSocketMessageType.WebRtcOffer, webRtcOfferListener);
141 webSocket.bind(WebSocketMessageType.WebRtcAnswer, webRtcAnswerListener);
142 webSocket.bind(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidateListener);
143
144 return () => {
145 webSocket.unbind(WebSocketMessageType.WebRtcOffer, webRtcOfferListener);
146 webSocket.unbind(WebSocketMessageType.WebRtcAnswer, webRtcAnswerListener);
147 webSocket.unbind(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidateListener);
148 };
149 }, [webSocket, webRtcConnection, sendWebRtcAnswer]);
150
151 useEffect(() => {
152 if (!webRtcConnection || !webSocket) {
153 return;
154 }
155
156 const iceCandidateEventListener = (event: RTCPeerConnectionIceEvent) => {
157 console.info('Received WebRTC event on icecandidate', event);
158 if (!contactUri) {
159 throw new Error('Could not handle WebRTC event on icecandidate: contactUri is not defined');
160 }
161
162 if (event.candidate) {
163 const webRtcIceCandidate: WebRtcIceCandidate = {
164 contactId: contactUri,
165 candidate: event.candidate,
166 };
167
168 console.info('Sending WebRtcIceCandidate', webRtcIceCandidate);
169 webSocket.send(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidate);
170 }
171 };
172
173 const trackEventListener = (event: RTCTrackEvent) => {
174 console.info('Received WebRTC event on track', event);
175 setRemoteStreams(event.streams);
176 };
177
MichelleSS55164202022-11-25 18:36:14 -0500178 const iceConnectionStateChangeEventListener = () => {
simonfeaa1db2022-11-26 20:13:18 -0500179 setIsConnected(
MichelleSS55164202022-11-25 18:36:14 -0500180 webRtcConnection.iceConnectionState === 'connected' || webRtcConnection.iceConnectionState === 'completed'
simonfeaa1db2022-11-26 20:13:18 -0500181 );
182 };
183
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500184 webRtcConnection.addEventListener('icecandidate', iceCandidateEventListener);
185 webRtcConnection.addEventListener('track', trackEventListener);
MichelleSS55164202022-11-25 18:36:14 -0500186 webRtcConnection.addEventListener('iceconnectionstatechange', iceConnectionStateChangeEventListener);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500187
188 return () => {
189 webRtcConnection.removeEventListener('icecandidate', iceCandidateEventListener);
190 webRtcConnection.removeEventListener('track', trackEventListener);
MichelleSS55164202022-11-25 18:36:14 -0500191 webRtcConnection.removeEventListener('iceconnectionstatechange', iceConnectionStateChangeEventListener);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500192 };
193 }, [webRtcConnection, webSocket, contactUri]);
194
195 return (
196 <WebRtcContext.Provider
197 value={{
198 isConnected,
199 remoteStreams,
200 webRtcConnection,
201 sendWebRtcOffer,
202 }}
203 >
204 {children}
205 </WebRtcContext.Provider>
206 );
207};