blob: e8ee1d2436102777e7d380bd1fdf15af031b362c [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
simonce2c0c42022-11-02 17:39:31 -040019import React, { createContext, useCallback, useEffect, useRef, useState } from 'react';
Charliec2c012f2022-10-05 14:09:28 -040020import { connect, Socket } from 'socket.io-client';
21
22import { WithChildren } from '../utils/utils';
23
24/*
25 * TODO: This socket is temporary, it will be replaced by the real socket
26 * for communication with webrtc
27 * */
simonce2c0c42022-11-02 17:39:31 -040028const socket = connect(import.meta.env.VITE_SOCKET_URL, { transports: ['websocket'] });
Charliec2c012f2022-10-05 14:09:28 -040029
30interface IWebRTCContext {
31 localVideoRef: React.RefObject<HTMLVideoElement> | null;
32 remoteVideoRef: React.RefObject<HTMLVideoElement> | null;
Charliec2c012f2022-10-05 14:09:28 -040033 socket: Socket;
simonce2c0c42022-11-02 17:39:31 -040034
35 isAudioOn: boolean;
36 setAudioStatus: (isOn: boolean) => void;
37 isVideoOn: boolean;
38 setVideoStatus: (isOn: boolean) => void;
39 sendWebRTCOffer: () => void;
Charliec2c012f2022-10-05 14:09:28 -040040}
41
simonce2c0c42022-11-02 17:39:31 -040042const defaultWebRTCContext: IWebRTCContext = {
Charliec2c012f2022-10-05 14:09:28 -040043 localVideoRef: null,
44 remoteVideoRef: null,
simonce2c0c42022-11-02 17:39:31 -040045 socket,
46
47 isAudioOn: false,
48 setAudioStatus: () => {},
49 isVideoOn: false,
50 setVideoStatus: () => {},
51
Charliec2c012f2022-10-05 14:09:28 -040052 sendWebRTCOffer: () => {},
Charliec2c012f2022-10-05 14:09:28 -040053};
54
simonce2c0c42022-11-02 17:39:31 -040055export const WebRTCContext = createContext<IWebRTCContext>(defaultWebRTCContext);
Charliec2c012f2022-10-05 14:09:28 -040056
simonce2c0c42022-11-02 17:39:31 -040057type WebRTCProviderProps = WithChildren & {
58 isAudioOn?: boolean;
59 isVideoOn?: boolean;
60};
61
62// TODO: This is a WIP. The calling logic will be improved in other CRs
63export default ({
64 children,
65 isAudioOn: _isAudioOn = defaultWebRTCContext.isAudioOn,
66 isVideoOn: _isVideoOn = defaultWebRTCContext.isVideoOn,
67}: WebRTCProviderProps) => {
68 const [isAudioOn, setIsAudioOn] = useState(_isAudioOn);
69 const [isVideoOn, setIsVideoOn] = useState(_isVideoOn);
Charliec2c012f2022-10-05 14:09:28 -040070 const localVideoRef = useRef<HTMLVideoElement>(null);
71 const remoteVideoRef = useRef<HTMLVideoElement>(null);
simonce2c0c42022-11-02 17:39:31 -040072 const [webRTCConnection, setWebRTCConnection] = useState<RTCPeerConnection | undefined>();
73 const localStreamRef = useRef<MediaStream>();
Charliec2c012f2022-10-05 14:09:28 -040074
simonce2c0c42022-11-02 17:39:31 -040075 useEffect(() => {
76 if (!webRTCConnection) {
77 // TODO use SFL iceServers
78 const iceConfig = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };
79 setWebRTCConnection(new RTCPeerConnection(iceConfig));
80 }
81 }, [webRTCConnection]);
Charliec2c012f2022-10-05 14:09:28 -040082
simonce2c0c42022-11-02 17:39:31 -040083 useEffect(() => {
84 if (!webRTCConnection) {
85 return;
Charliec2c012f2022-10-05 14:09:28 -040086 }
87
simonce2c0c42022-11-02 17:39:31 -040088 if (isVideoOn || isAudioOn) {
89 try {
90 // TODO: When toggling mute on/off, the camera flickers
91 // https://git.jami.net/savoirfairelinux/jami-web/-/issues/90
92 navigator.mediaDevices
93 .getUserMedia({
94 audio: true,
95 video: true,
96 })
97 .then((stream) => {
98 if (localVideoRef.current) {
99 localVideoRef.current.srcObject = stream;
100 }
101
102 stream.getTracks().forEach((track) => {
103 if (track.kind === 'audio') {
104 track.enabled = isAudioOn;
105 } else if (track.kind === 'video') {
106 track.enabled = isVideoOn;
107 }
108 webRTCConnection.addTrack(track, stream);
109 });
110 localStreamRef.current = stream;
111 });
112 } catch (e) {
113 console.error('Could not get media devices: ', e);
Charliec2c012f2022-10-05 14:09:28 -0400114 }
simonce2c0c42022-11-02 17:39:31 -0400115 }
116
117 const icecandidateEventListener = (event: RTCPeerConnectionIceEvent) => {
118 if (event.candidate) {
Charliec2c012f2022-10-05 14:09:28 -0400119 console.log('webRTCConnection : onicecandidate');
120 socket.emit('candidate', event.candidate);
121 }
simonce2c0c42022-11-02 17:39:31 -0400122 };
123
124 const trackEventListener = (event: RTCTrackEvent) => {
125 console.log('remote TrackEvent');
Charliec2c012f2022-10-05 14:09:28 -0400126 if (remoteVideoRef.current) {
127 remoteVideoRef.current.srcObject = event.streams[0];
128 console.log('webRTCConnection : add remotetrack success');
129 }
simonce2c0c42022-11-02 17:39:31 -0400130 };
131
132 webRTCConnection.addEventListener('icecandidate', icecandidateEventListener);
133 webRTCConnection.addEventListener('track', trackEventListener);
134
135 return () => {
136 webRTCConnection.removeEventListener('icecandidate', icecandidateEventListener);
137 webRTCConnection.removeEventListener('track', trackEventListener);
138 };
139 }, [webRTCConnection, isVideoOn, isAudioOn]);
140
141 useEffect(() => {
142 if (!webRTCConnection) {
143 return;
144 }
145
146 const sendWebRTCAnswer = async (remoteSdp: RTCSessionDescriptionInit) => {
147 await webRTCConnection.setRemoteDescription(new RTCSessionDescription(remoteSdp));
148 const mySdp = await webRTCConnection.createAnswer({
149 offerToReceiveAudio: true,
150 offerToReceiveVideo: true,
151 });
152 await webRTCConnection.setLocalDescription(new RTCSessionDescription(mySdp));
153 socket.emit('answer', mySdp);
154 };
155
156 const handleWebRTCAnswer = async (remoteSdp: RTCSessionDescriptionInit) => {
157 await webRTCConnection.setRemoteDescription(new RTCSessionDescription(remoteSdp));
158 };
159
160 const addIceCandidate = async (candidate: RTCIceCandidateInit) => {
161 await webRTCConnection.addIceCandidate(new RTCIceCandidate(candidate));
162 };
163
164 socket.on('getOffer', (remoteSdp: RTCSessionDescription) => {
165 sendWebRTCAnswer(remoteSdp);
166 console.log('get offer and aswering');
Charliec2c012f2022-10-05 14:09:28 -0400167 });
simonce2c0c42022-11-02 17:39:31 -0400168
169 socket.on('getAnswer', (remoteSdp: RTCSessionDescription) => {
170 handleWebRTCAnswer(remoteSdp);
171 console.log('get answer');
172 });
173
174 socket.on('getCandidate', (candidate: RTCIceCandidateInit) => {
175 addIceCandidate(candidate);
176 console.log('webRTCConnection : candidate add success');
177 });
178
179 return () => {
180 socket.off('getOffer');
181 socket.off('getAnswer');
182 socket.off('getCandidate');
183 };
184 }, [webRTCConnection]);
185
186 const setAudioStatus = useCallback((isOn: boolean) => {
187 setIsAudioOn(isOn);
188 localStreamRef.current?.getAudioTracks().forEach((track) => {
189 track.enabled = isOn;
190 });
191 }, []);
192
193 const setVideoStatus = useCallback((isOn: boolean) => {
194 setIsVideoOn(isOn);
195 localStreamRef.current?.getVideoTracks().forEach((track) => {
196 track.enabled = isOn;
197 });
198 }, []);
Charliec2c012f2022-10-05 14:09:28 -0400199
200 const sendWebRTCOffer = useCallback(async () => {
simonce2c0c42022-11-02 17:39:31 -0400201 if (webRTCConnection) {
202 webRTCConnection
203 .createOffer({
Charliec2c012f2022-10-05 14:09:28 -0400204 offerToReceiveAudio: true,
205 offerToReceiveVideo: true,
simonce2c0c42022-11-02 17:39:31 -0400206 })
207 .then((sdp) => {
208 socket.emit('offer', sdp);
209 webRTCConnection.setLocalDescription(new RTCSessionDescription(sdp));
Charliec2c012f2022-10-05 14:09:28 -0400210 });
Charliec2c012f2022-10-05 14:09:28 -0400211 }
simonce2c0c42022-11-02 17:39:31 -0400212 }, [webRTCConnection]);
Charliec2c012f2022-10-05 14:09:28 -0400213
214 return (
215 <WebRTCContext.Provider
216 value={{
217 localVideoRef,
218 remoteVideoRef,
Charliec2c012f2022-10-05 14:09:28 -0400219 socket,
simonce2c0c42022-11-02 17:39:31 -0400220 isAudioOn,
221 setAudioStatus,
222 isVideoOn,
223 setVideoStatus,
224 sendWebRTCOffer,
Charliec2c012f2022-10-05 14:09:28 -0400225 }}
226 >
227 {children}
228 </WebRTCContext.Provider>
229 );
230};