blob: 397bd83a54c2e26188bbf9559c23e5565330c25a [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';
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050023import { WithChildren } from '../utils/utils';
simon71d1c0a2022-11-24 15:28:33 -050024import { useAuthContext } from './AuthProvider';
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050025import { ConversationContext } from './ConversationProvider';
simonf353ef42022-11-28 23:14:53 -050026import { IWebSocketContext, WebSocketContext } from './WebSocketProvider';
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050027
28interface IWebRtcContext {
29 isConnected: boolean;
30
31 remoteStreams: readonly MediaStream[] | undefined;
32 webRtcConnection: RTCPeerConnection | undefined;
33
34 sendWebRtcOffer: (sdp: RTCSessionDescriptionInit) => Promise<void>;
35}
36
37const defaultWebRtcContext: IWebRtcContext = {
38 isConnected: false,
39 remoteStreams: undefined,
40 webRtcConnection: undefined,
41 sendWebRtcOffer: async () => {},
42};
43
44export const WebRtcContext = createContext<IWebRtcContext>(defaultWebRtcContext);
45
46export default ({ children }: WithChildren) => {
simon71d1c0a2022-11-24 15:28:33 -050047 const { account } = useAuthContext();
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050048 const [webRtcConnection, setWebRtcConnection] = useState<RTCPeerConnection | undefined>();
simonf353ef42022-11-28 23:14:53 -050049 const webSocket = useContext(WebSocketContext);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050050
51 useEffect(() => {
simon71d1c0a2022-11-24 15:28:33 -050052 if (!webRtcConnection && account) {
53 const iceServers: RTCIceServer[] = [];
54
55 if (account.getDetails()['TURN.enable'] === 'true') {
56 iceServers.push({
57 urls: 'turn:' + account.getDetails()['TURN.server'],
58 username: account.getDetails()['TURN.username'],
59 credential: account.getDetails()['TURN.password'],
60 });
61 }
62
63 if (account.getDetails()['STUN.enable'] === 'true') {
64 iceServers.push({
65 urls: 'stun:' + account.getDetails()['STUN.server'],
66 });
67 }
68
69 setWebRtcConnection(new RTCPeerConnection({ iceServers: iceServers }));
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050070 }
simon71d1c0a2022-11-24 15:28:33 -050071 }, [account, webRtcConnection]);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050072
simonf353ef42022-11-28 23:14:53 -050073 if (!webRtcConnection || !webSocket) {
74 return <LoadingPage />;
75 }
76
77 return (
78 <WebRtcProvider webRtcConnection={webRtcConnection} webSocket={webSocket}>
79 {children}
80 </WebRtcProvider>
81 );
82};
83
84const WebRtcProvider = ({
85 children,
86 webRtcConnection,
87 webSocket,
88}: WithChildren & {
89 webRtcConnection: RTCPeerConnection;
90 webSocket: IWebSocketContext;
91}) => {
92 const { conversation, conversationId } = useContext(ConversationContext);
93 const [remoteStreams, setRemoteStreams] = useState<readonly MediaStream[]>();
94 const [isConnected, setIsConnected] = useState(false);
95
96 // TODO: This logic will have to change to support multiple people in a call
97 const contactUri = useMemo(() => conversation.getFirstMember().contact.getUri(), [conversation]);
98
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -050099 const sendWebRtcOffer = useCallback(
100 async (sdp: RTCSessionDescriptionInit) => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500101 const webRtcOffer: WebRtcSdp = {
102 contactId: contactUri,
Charliec18d6402022-11-27 13:01:04 -0500103 conversationId: conversationId,
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500104 sdp,
105 };
106
simonfeaa1db2022-11-26 20:13:18 -0500107 await webRtcConnection.setLocalDescription(new RTCSessionDescription(sdp));
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500108 console.info('Sending WebRtcOffer', webRtcOffer);
109 webSocket.send(WebSocketMessageType.WebRtcOffer, webRtcOffer);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500110 },
Charliec18d6402022-11-27 13:01:04 -0500111 [webRtcConnection, webSocket, conversationId, contactUri]
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500112 );
113
114 const sendWebRtcAnswer = useCallback(
115 (sdp: RTCSessionDescriptionInit) => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500116 const webRtcAnswer: WebRtcSdp = {
117 contactId: contactUri,
Charliec18d6402022-11-27 13:01:04 -0500118 conversationId: conversationId,
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500119 sdp,
120 };
121
122 console.info('Sending WebRtcAnswer', webRtcAnswer);
123 webSocket.send(WebSocketMessageType.WebRtcAnswer, webRtcAnswer);
124 },
simonf353ef42022-11-28 23:14:53 -0500125 [contactUri, conversationId, webSocket]
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500126 );
127
simonf353ef42022-11-28 23:14:53 -0500128 /* WebSocket Listeners */
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500129
simonf353ef42022-11-28 23:14:53 -0500130 useEffect(() => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500131 const webRtcOfferListener = async (data: WebRtcSdp) => {
132 console.info('Received event on WebRtcOffer', data);
Charliec18d6402022-11-27 13:01:04 -0500133 if (data.conversationId !== conversationId) {
134 console.warn('Wrong incoming conversationId, ignoring action');
135 return;
136 }
137
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500138 await webRtcConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
139
140 const sdp = await webRtcConnection.createAnswer({
141 offerToReceiveAudio: true,
142 offerToReceiveVideo: true,
143 });
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500144 await webRtcConnection.setLocalDescription(new RTCSessionDescription(sdp));
simonfeaa1db2022-11-26 20:13:18 -0500145 sendWebRtcAnswer(sdp);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500146 };
147
148 const webRtcAnswerListener = async (data: WebRtcSdp) => {
149 console.info('Received event on WebRtcAnswer', data);
Charliec18d6402022-11-27 13:01:04 -0500150 if (data.conversationId !== conversationId) {
151 console.warn('Wrong incoming conversationId, ignoring action');
152 return;
153 }
154
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500155 await webRtcConnection.setRemoteDescription(new RTCSessionDescription(data.sdp));
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500156 };
simonf353ef42022-11-28 23:14:53 -0500157 webSocket.bind(WebSocketMessageType.WebRtcOffer, webRtcOfferListener);
158 webSocket.bind(WebSocketMessageType.WebRtcAnswer, webRtcAnswerListener);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500159
simonf353ef42022-11-28 23:14:53 -0500160 return () => {
161 webSocket.unbind(WebSocketMessageType.WebRtcOffer, webRtcOfferListener);
162 webSocket.unbind(WebSocketMessageType.WebRtcAnswer, webRtcAnswerListener);
163 };
164 }, [webSocket, webRtcConnection, sendWebRtcAnswer, conversationId]);
165
166 useEffect(() => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500167 const webRtcIceCandidateListener = async (data: WebRtcIceCandidate) => {
Charliec18d6402022-11-27 13:01:04 -0500168 if (data.conversationId !== conversationId) {
169 console.warn('Wrong incoming conversationId, ignoring action');
170 return;
171 }
172
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500173 await webRtcConnection.addIceCandidate(data.candidate);
174 };
175
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500176 webSocket.bind(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidateListener);
177
178 return () => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500179 webSocket.unbind(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidateListener);
180 };
simonf353ef42022-11-28 23:14:53 -0500181 }, [webRtcConnection, webSocket, conversationId]);
182
183 /* WebRTC Listeners */
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500184
185 useEffect(() => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500186 const iceCandidateEventListener = (event: RTCPeerConnectionIceEvent) => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500187 if (event.candidate) {
188 const webRtcIceCandidate: WebRtcIceCandidate = {
189 contactId: contactUri,
Charliec18d6402022-11-27 13:01:04 -0500190 conversationId: conversationId,
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500191 candidate: event.candidate,
192 };
193
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500194 webSocket.send(WebSocketMessageType.WebRtcIceCandidate, webRtcIceCandidate);
195 }
196 };
197
simonf353ef42022-11-28 23:14:53 -0500198 webRtcConnection.addEventListener('icecandidate', iceCandidateEventListener);
199
200 return () => {
201 webRtcConnection.removeEventListener('icecandidate', iceCandidateEventListener);
202 };
203 }, [webRtcConnection, webSocket, contactUri, conversationId]);
204
205 useEffect(() => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500206 const trackEventListener = (event: RTCTrackEvent) => {
207 console.info('Received WebRTC event on track', event);
208 setRemoteStreams(event.streams);
209 };
210
simonf353ef42022-11-28 23:14:53 -0500211 const iceConnectionStateChangeEventListener = (event: Event) => {
212 console.info(`Received WebRTC event on iceconnectionstatechange: ${webRtcConnection.iceConnectionState}`, event);
simonfeaa1db2022-11-26 20:13:18 -0500213 setIsConnected(
MichelleSS55164202022-11-25 18:36:14 -0500214 webRtcConnection.iceConnectionState === 'connected' || webRtcConnection.iceConnectionState === 'completed'
simonfeaa1db2022-11-26 20:13:18 -0500215 );
216 };
217
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500218 webRtcConnection.addEventListener('track', trackEventListener);
MichelleSS55164202022-11-25 18:36:14 -0500219 webRtcConnection.addEventListener('iceconnectionstatechange', iceConnectionStateChangeEventListener);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500220
221 return () => {
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500222 webRtcConnection.removeEventListener('track', trackEventListener);
MichelleSS55164202022-11-25 18:36:14 -0500223 webRtcConnection.removeEventListener('iceconnectionstatechange', iceConnectionStateChangeEventListener);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500224 };
simonf353ef42022-11-28 23:14:53 -0500225 }, [webRtcConnection]);
Misha Krieger-Raynauld20cf1c82022-11-23 20:26:50 -0500226
227 return (
228 <WebRtcContext.Provider
229 value={{
230 isConnected,
231 remoteStreams,
232 webRtcConnection,
233 sendWebRtcOffer,
234 }}
235 >
236 {children}
237 </WebRtcContext.Provider>
238 );
239};