blob: 04cc61a38a8fc43949282199e06eb40f305510b0 [file] [log] [blame]
Charliec2c012f2022-10-05 14:09:28 -04001/*
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
simonf929a362022-11-18 16:53:45 -050019import { AccountTextMessage, WebRTCIceCandidate, WebRtcSdp, WebSocketMessageType } from 'jami-web-common';
20import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
Charliec2c012f2022-10-05 14:09:28 -040021
22import { WithChildren } from '../utils/utils';
Charlie461805e2022-11-09 10:40:15 -050023import { useAuthContext } from './AuthProvider';
simonf929a362022-11-18 16:53:45 -050024import { ConversationContext } from './ConversationProvider';
Charlie461805e2022-11-09 10:40:15 -050025import { WebSocketContext } from './WebSocketProvider';
Charliec2c012f2022-10-05 14:09:28 -040026
27interface IWebRTCContext {
simonf929a362022-11-18 16:53:45 -050028 isConnected: boolean;
Charlie461805e2022-11-09 10:40:15 -050029
simonf929a362022-11-18 16:53:45 -050030 remoteStreams: readonly MediaStream[] | undefined;
31 webRTCConnection: RTCPeerConnection | undefined;
simon9a8fe202022-11-15 18:25:49 -050032
simonf929a362022-11-18 16:53:45 -050033 sendWebRTCOffer: (sdp: RTCSessionDescriptionInit) => Promise<void>;
Charliec2c012f2022-10-05 14:09:28 -040034}
35
simonce2c0c42022-11-02 17:39:31 -040036const defaultWebRTCContext: IWebRTCContext = {
simonf929a362022-11-18 16:53:45 -050037 isConnected: false,
38 remoteStreams: undefined,
39 webRTCConnection: undefined,
40 sendWebRTCOffer: async () => {},
Charliec2c012f2022-10-05 14:09:28 -040041};
42
simonce2c0c42022-11-02 17:39:31 -040043export const WebRTCContext = createContext<IWebRTCContext>(defaultWebRTCContext);
Charliec2c012f2022-10-05 14:09:28 -040044
simonf929a362022-11-18 16:53:45 -050045export default ({ children }: WithChildren) => {
46 const { accountId } = useAuthContext();
Issam E. Maghni0432cb72022-11-12 06:09:26 +000047 const webSocket = useContext(WebSocketContext);
simonf929a362022-11-18 16:53:45 -050048 const { conversation } = useContext(ConversationContext);
49 const [webRTCConnection, setWebRTCConnection] = useState<RTCPeerConnection | undefined>();
50 const [remoteStreams, setRemoteStreams] = useState<readonly MediaStream[]>();
51 const [isConnected, setIsConnected] = useState(false);
simon9a8fe202022-11-15 18:25:49 -050052
simonf929a362022-11-18 16:53:45 -050053 // TODO: This logic will have to change to support multiple people in a call
54 const contactUri = useMemo(() => conversation.getFirstMember().contact.getUri(), [conversation]);
Charliec2c012f2022-10-05 14:09:28 -040055
simonce2c0c42022-11-02 17:39:31 -040056 useEffect(() => {
57 if (!webRTCConnection) {
58 // TODO use SFL iceServers
59 const iceConfig = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };
60 setWebRTCConnection(new RTCPeerConnection(iceConfig));
61 }
62 }, [webRTCConnection]);
Charliec2c012f2022-10-05 14:09:28 -040063
simonf929a362022-11-18 16:53:45 -050064 const sendWebRTCOffer = useCallback(
65 async (sdp: RTCSessionDescriptionInit) => {
66 if (!webRTCConnection || !webSocket) {
67 throw new Error('Could not send WebRTC offer');
68 }
69 const webRTCOffer: AccountTextMessage<WebRtcSdp> = {
70 from: accountId,
71 to: contactUri,
72 message: {
73 sdp,
74 },
75 };
76
77 console.info('Sending WebRTCOffer', webRTCOffer);
78 webSocket.send(WebSocketMessageType.WebRTCOffer, webRTCOffer);
79 await webRTCConnection.setLocalDescription(new RTCSessionDescription(sdp));
80 },
81 [accountId, webRTCConnection, webSocket, contactUri]
82 );
83
84 const sendWebRTCAnswer = useCallback(
85 (sdp: RTCSessionDescriptionInit) => {
86 if (!webRTCConnection || !webSocket) {
87 throw new Error('Could not send WebRTC answer');
88 }
89
90 const webRTCAnswer: AccountTextMessage<WebRtcSdp> = {
91 from: accountId,
92 to: contactUri,
93 message: {
94 sdp,
95 },
96 };
97
98 console.info('Sending WebRTCAnswer', webRTCAnswer);
99 webSocket.send(WebSocketMessageType.WebRTCAnswer, webRTCAnswer);
100 },
101 [accountId, contactUri, webRTCConnection, webSocket]
102 );
103
simonce2c0c42022-11-02 17:39:31 -0400104 useEffect(() => {
simonf929a362022-11-18 16:53:45 -0500105 if (!webSocket || !webRTCConnection) {
simonce2c0c42022-11-02 17:39:31 -0400106 return;
Charliec2c012f2022-10-05 14:09:28 -0400107 }
108
simonf929a362022-11-18 16:53:45 -0500109 const webRTCOfferListener = async (data: AccountTextMessage<WebRtcSdp>) => {
110 console.info('Received event on WebRTCOffer', data);
111 await webRTCConnection.setRemoteDescription(new RTCSessionDescription(data.message.sdp));
simonce2c0c42022-11-02 17:39:31 -0400112
simonf929a362022-11-18 16:53:45 -0500113 const sdp = await webRTCConnection.createAnswer({
114 offerToReceiveAudio: true,
115 offerToReceiveVideo: true,
116 });
117 sendWebRTCAnswer(sdp);
118 await webRTCConnection.setLocalDescription(new RTCSessionDescription(sdp));
119 setIsConnected(true);
120 };
121
122 const webRTCAnswerListener = async (data: AccountTextMessage<WebRtcSdp>) => {
123 console.info('Received event on WebRTCAnswer', data);
124 await webRTCConnection.setRemoteDescription(new RTCSessionDescription(data.message.sdp));
125 setIsConnected(true);
126 };
127
128 const webRTCIceCandidateListener = async (data: AccountTextMessage<WebRTCIceCandidate>) => {
129 console.info('Received event on WebRTCIceCandidate', data);
130 await webRTCConnection.addIceCandidate(data.message.candidate);
131 };
132
133 webSocket.bind(WebSocketMessageType.WebRTCOffer, webRTCOfferListener);
134 webSocket.bind(WebSocketMessageType.WebRTCAnswer, webRTCAnswerListener);
135 webSocket.bind(WebSocketMessageType.IceCandidate, webRTCIceCandidateListener);
136
137 return () => {
138 webSocket.unbind(WebSocketMessageType.WebRTCOffer, webRTCOfferListener);
139 webSocket.unbind(WebSocketMessageType.WebRTCAnswer, webRTCAnswerListener);
140 webSocket.unbind(WebSocketMessageType.IceCandidate, webRTCIceCandidateListener);
141 };
142 }, [webSocket, webRTCConnection, sendWebRTCAnswer]);
143
144 useEffect(() => {
145 if (!webRTCConnection || !webSocket) {
146 return;
simonce2c0c42022-11-02 17:39:31 -0400147 }
148
149 const icecandidateEventListener = (event: RTCPeerConnectionIceEvent) => {
simonf929a362022-11-18 16:53:45 -0500150 console.info('Received WebRTC event on icecandidate', event);
151 if (!contactUri) {
152 throw new Error('Could not handle WebRTC event on icecandidate: contactUri is not defined');
153 }
154 if (event.candidate) {
155 const iceCandidateMessageData: AccountTextMessage<WebRTCIceCandidate> = {
156 from: accountId,
157 to: contactUri,
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000158 message: {
159 candidate: event.candidate,
Charlie461805e2022-11-09 10:40:15 -0500160 },
simonf929a362022-11-18 16:53:45 -0500161 };
162
163 console.info('Sending IceCandidate', iceCandidateMessageData);
164 webSocket.send(WebSocketMessageType.IceCandidate, iceCandidateMessageData);
Charliec2c012f2022-10-05 14:09:28 -0400165 }
simonce2c0c42022-11-02 17:39:31 -0400166 };
simonce2c0c42022-11-02 17:39:31 -0400167 const trackEventListener = (event: RTCTrackEvent) => {
simonf929a362022-11-18 16:53:45 -0500168 console.info('Received WebRTC event on track', event);
169 setRemoteStreams(event.streams);
simonce2c0c42022-11-02 17:39:31 -0400170 };
171
172 webRTCConnection.addEventListener('icecandidate', icecandidateEventListener);
173 webRTCConnection.addEventListener('track', trackEventListener);
174
175 return () => {
176 webRTCConnection.removeEventListener('icecandidate', icecandidateEventListener);
177 webRTCConnection.removeEventListener('track', trackEventListener);
178 };
simonf929a362022-11-18 16:53:45 -0500179 }, [accountId, webRTCConnection, webSocket, contactUri]);
Charliec2c012f2022-10-05 14:09:28 -0400180
181 return (
182 <WebRTCContext.Provider
183 value={{
simonf929a362022-11-18 16:53:45 -0500184 isConnected,
185 remoteStreams,
186 webRTCConnection,
simonce2c0c42022-11-02 17:39:31 -0400187 sendWebRTCOffer,
Charliec2c012f2022-10-05 14:09:28 -0400188 }}
189 >
190 {children}
191 </WebRTCContext.Provider>
192 );
193};