blob: eb52e6be15aab4e8a5d3743927ddbe7a7ed78f65 [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
Issam E. Maghni0432cb72022-11-12 06:09:26 +000019import { WebSocketMessageType } from 'jami-web-common';
Charlie461805e2022-11-09 10:40:15 -050020import React, { createContext, useCallback, useContext, useEffect, useRef, 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';
24import { WebSocketContext } from './WebSocketProvider';
Charliec2c012f2022-10-05 14:09:28 -040025
26interface IWebRTCContext {
27 localVideoRef: React.RefObject<HTMLVideoElement> | null;
28 remoteVideoRef: React.RefObject<HTMLVideoElement> | null;
Charlie461805e2022-11-09 10:40:15 -050029
30 contactId: string;
simonce2c0c42022-11-02 17:39:31 -040031
32 isAudioOn: boolean;
33 setAudioStatus: (isOn: boolean) => void;
34 isVideoOn: boolean;
35 setVideoStatus: (isOn: boolean) => void;
36 sendWebRTCOffer: () => void;
Charliec2c012f2022-10-05 14:09:28 -040037}
38
simonce2c0c42022-11-02 17:39:31 -040039const defaultWebRTCContext: IWebRTCContext = {
Charliec2c012f2022-10-05 14:09:28 -040040 localVideoRef: null,
41 remoteVideoRef: null,
Charlie461805e2022-11-09 10:40:15 -050042
43 contactId: '',
simonce2c0c42022-11-02 17:39:31 -040044
45 isAudioOn: false,
46 setAudioStatus: () => {},
47 isVideoOn: false,
48 setVideoStatus: () => {},
49
Charliec2c012f2022-10-05 14:09:28 -040050 sendWebRTCOffer: () => {},
Charliec2c012f2022-10-05 14:09:28 -040051};
52
simonce2c0c42022-11-02 17:39:31 -040053export const WebRTCContext = createContext<IWebRTCContext>(defaultWebRTCContext);
Charliec2c012f2022-10-05 14:09:28 -040054
simonce2c0c42022-11-02 17:39:31 -040055type WebRTCProviderProps = WithChildren & {
Charlie461805e2022-11-09 10:40:15 -050056 contactId: string;
simonce2c0c42022-11-02 17:39:31 -040057 isAudioOn?: boolean;
58 isVideoOn?: boolean;
59};
60
61// TODO: This is a WIP. The calling logic will be improved in other CRs
62export default ({
63 children,
64 isAudioOn: _isAudioOn = defaultWebRTCContext.isAudioOn,
65 isVideoOn: _isVideoOn = defaultWebRTCContext.isVideoOn,
Charlie461805e2022-11-09 10:40:15 -050066 contactId: _contactId = defaultWebRTCContext.contactId,
simonce2c0c42022-11-02 17:39:31 -040067}: 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);
Charlie461805e2022-11-09 10:40:15 -050072 const { account } = useAuthContext();
73 const contactId = _contactId;
simonce2c0c42022-11-02 17:39:31 -040074 const [webRTCConnection, setWebRTCConnection] = useState<RTCPeerConnection | undefined>();
75 const localStreamRef = useRef<MediaStream>();
Issam E. Maghni0432cb72022-11-12 06:09:26 +000076 const webSocket = useContext(WebSocketContext);
Charliec2c012f2022-10-05 14:09:28 -040077
simonce2c0c42022-11-02 17:39:31 -040078 useEffect(() => {
79 if (!webRTCConnection) {
80 // TODO use SFL iceServers
81 const iceConfig = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };
82 setWebRTCConnection(new RTCPeerConnection(iceConfig));
83 }
84 }, [webRTCConnection]);
Charliec2c012f2022-10-05 14:09:28 -040085
simonce2c0c42022-11-02 17:39:31 -040086 useEffect(() => {
87 if (!webRTCConnection) {
88 return;
Charliec2c012f2022-10-05 14:09:28 -040089 }
90
simonce2c0c42022-11-02 17:39:31 -040091 if (isVideoOn || isAudioOn) {
92 try {
93 // TODO: When toggling mute on/off, the camera flickers
94 // https://git.jami.net/savoirfairelinux/jami-web/-/issues/90
95 navigator.mediaDevices
96 .getUserMedia({
97 audio: true,
98 video: true,
99 })
100 .then((stream) => {
101 if (localVideoRef.current) {
102 localVideoRef.current.srcObject = stream;
103 }
104
105 stream.getTracks().forEach((track) => {
106 if (track.kind === 'audio') {
107 track.enabled = isAudioOn;
108 } else if (track.kind === 'video') {
109 track.enabled = isVideoOn;
110 }
111 webRTCConnection.addTrack(track, stream);
112 });
113 localStreamRef.current = stream;
114 });
115 } catch (e) {
116 console.error('Could not get media devices: ', e);
Charliec2c012f2022-10-05 14:09:28 -0400117 }
simonce2c0c42022-11-02 17:39:31 -0400118 }
119
120 const icecandidateEventListener = (event: RTCPeerConnectionIceEvent) => {
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000121 if (event.candidate && webSocket) {
Charliec2c012f2022-10-05 14:09:28 -0400122 console.log('webRTCConnection : onicecandidate');
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000123 webSocket.send(WebSocketMessageType.IceCandidate, {
124 from: account.getId(),
125 to: contactId,
126 message: {
127 candidate: event.candidate,
Charlie461805e2022-11-09 10:40:15 -0500128 },
129 });
Charliec2c012f2022-10-05 14:09:28 -0400130 }
simonce2c0c42022-11-02 17:39:31 -0400131 };
132
133 const trackEventListener = (event: RTCTrackEvent) => {
134 console.log('remote TrackEvent');
Charliec2c012f2022-10-05 14:09:28 -0400135 if (remoteVideoRef.current) {
136 remoteVideoRef.current.srcObject = event.streams[0];
137 console.log('webRTCConnection : add remotetrack success');
138 }
simonce2c0c42022-11-02 17:39:31 -0400139 };
140
141 webRTCConnection.addEventListener('icecandidate', icecandidateEventListener);
142 webRTCConnection.addEventListener('track', trackEventListener);
143
144 return () => {
145 webRTCConnection.removeEventListener('icecandidate', icecandidateEventListener);
146 webRTCConnection.removeEventListener('track', trackEventListener);
147 };
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000148 }, [webRTCConnection, isVideoOn, isAudioOn, webSocket, contactId, account]);
simonce2c0c42022-11-02 17:39:31 -0400149
150 useEffect(() => {
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000151 if (!webRTCConnection || !webSocket) {
simonce2c0c42022-11-02 17:39:31 -0400152 return;
153 }
154
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000155 webSocket.bind(WebSocketMessageType.WebRTCOffer, async (data) => {
156 if (webRTCConnection) {
157 await webRTCConnection.setRemoteDescription(new RTCSessionDescription(data.message.sdp));
Charlie461805e2022-11-09 10:40:15 -0500158 const mySdp = await webRTCConnection.createAnswer({
159 offerToReceiveAudio: true,
160 offerToReceiveVideo: true,
161 });
162 await webRTCConnection.setLocalDescription(new RTCSessionDescription(mySdp));
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000163 webSocket.send(WebSocketMessageType.WebRTCAnswer, {
164 from: account.getId(),
165 to: contactId,
166 message: {
167 sdp: mySdp,
Charlie461805e2022-11-09 10:40:15 -0500168 },
169 });
Charlie461805e2022-11-09 10:40:15 -0500170 }
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000171 });
Charlie461805e2022-11-09 10:40:15 -0500172
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000173 webSocket.bind(WebSocketMessageType.WebRTCAnswer, async (data) => {
174 await webRTCConnection.setRemoteDescription(new RTCSessionDescription(data.message.sdp));
simonce2c0c42022-11-02 17:39:31 -0400175 console.log('get answer');
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000176 });
Charlie461805e2022-11-09 10:40:15 -0500177
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000178 webSocket.bind(WebSocketMessageType.IceCandidate, async (data) => {
179 await webRTCConnection.addIceCandidate(new RTCIceCandidate(data.message.candidate));
Charlie461805e2022-11-09 10:40:15 -0500180 console.log('webRTCConnection : candidate add success');
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000181 });
182 }, [account, contactId, webSocket, webRTCConnection]);
simonce2c0c42022-11-02 17:39:31 -0400183
184 const setAudioStatus = useCallback((isOn: boolean) => {
185 setIsAudioOn(isOn);
186 localStreamRef.current?.getAudioTracks().forEach((track) => {
187 track.enabled = isOn;
188 });
189 }, []);
190
191 const setVideoStatus = useCallback((isOn: boolean) => {
192 setIsVideoOn(isOn);
193 localStreamRef.current?.getVideoTracks().forEach((track) => {
194 track.enabled = isOn;
195 });
196 }, []);
Charliec2c012f2022-10-05 14:09:28 -0400197
198 const sendWebRTCOffer = useCallback(async () => {
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000199 if (webRTCConnection && webSocket) {
simon94fe53e2022-11-10 12:51:58 -0500200 const sdp = await webRTCConnection.createOffer({
201 offerToReceiveAudio: true,
202 offerToReceiveVideo: true,
203 });
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000204 webSocket.send(WebSocketMessageType.WebRTCOffer, {
205 from: account.getId(),
206 to: contactId,
207 message: {
208 sdp,
simon94fe53e2022-11-10 12:51:58 -0500209 },
210 });
211 await webRTCConnection.setLocalDescription(new RTCSessionDescription(sdp));
Charliec2c012f2022-10-05 14:09:28 -0400212 }
Issam E. Maghni0432cb72022-11-12 06:09:26 +0000213 }, [account, contactId, webSocket, webRTCConnection]);
Charliec2c012f2022-10-05 14:09:28 -0400214
215 return (
216 <WebRTCContext.Provider
217 value={{
218 localVideoRef,
219 remoteVideoRef,
Charlie461805e2022-11-09 10:40:15 -0500220 contactId,
simonce2c0c42022-11-02 17:39:31 -0400221 isAudioOn,
222 setAudioStatus,
223 isVideoOn,
224 setVideoStatus,
225 sendWebRTCOffer,
Charliec2c012f2022-10-05 14:09:28 -0400226 }}
227 >
228 {children}
229 </WebRTCContext.Provider>
230 );
231};