blob: afae8a091d2491776e322c6db6ff66ecc38e00f2 [file] [log] [blame]
idillon989d6542023-01-30 15:49:58 -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 */
18import { useMemo, useRef, useSyncExternalStore } from 'react';
19
20import { CallData } from '../contexts/CallManagerProvider';
21import { IWebSocketContext } from '../contexts/WebSocketProvider';
22import { Account } from '../models/account';
23import { Listener } from '../utils/utils';
24import { RTCPeerConnectionHandler, RTCPeerConnectionInfos } from './RtcPeerConnectionHandler';
25
26export const useWebRtcManager = () => {
27 const webRtcManagerRef = useRef(new WebRtcManager());
28 const connectionsInfos = useSyncExternalStore(
29 webRtcManagerRef.current.subscribe.bind(webRtcManagerRef.current),
30 webRtcManagerRef.current.getSnapshot.bind(webRtcManagerRef.current)
31 );
32
33 return useMemo(
34 () => ({
35 addConnection: webRtcManagerRef.current.addConnection.bind(webRtcManagerRef.current),
36 removeConnection: webRtcManagerRef.current.removeConnection.bind(webRtcManagerRef.current),
37 updateLocalStreams: webRtcManagerRef.current.updateLocalStreams.bind(webRtcManagerRef.current),
38 clean: webRtcManagerRef.current.clean.bind(webRtcManagerRef.current),
39 connectionsInfos: connectionsInfos,
40 }),
41 [connectionsInfos]
42 );
43};
44
45class WebRtcManager {
46 private connections: Record<string, RTCPeerConnectionHandler> = {}; // key is contactUri
47
48 private listeners: Listener[] = [];
49 private snapshot: Record<string, RTCPeerConnectionInfos> = {}; // key is contactUri
50
51 addConnection(
52 webSocket: IWebSocketContext,
53 account: Account,
54 contactUri: string,
55 callData: CallData,
56 localStream: MediaStream | undefined,
57 screenShareLocalStream: MediaStream | undefined
58 ) {
59 if (this.connections[contactUri]) {
60 console.debug('Attempted to establish an WebRTC connection with the same peer more than once');
61 return;
62 }
63
64 const connection = new RTCPeerConnectionHandler(
65 webSocket,
66 account,
67 contactUri,
68 callData,
69 localStream,
70 screenShareLocalStream,
71 this.emitChange.bind(this)
72 );
73 this.connections[contactUri] = connection;
74 }
75
76 removeConnection(contactUri: string) {
77 const connection = this.connections[contactUri];
78 connection.disconnect();
79 delete this.connections[contactUri];
80 }
81
82 updateLocalStreams(localStream: MediaStream | undefined, screenShareLocalStream: MediaStream | undefined) {
83 Object.values(this.connections).forEach((connection) =>
84 connection.updateLocalStreams(localStream, screenShareLocalStream)
85 );
86 }
87
88 subscribe(listener: Listener) {
89 this.listeners.push(listener);
90 return () => {
91 this.listeners.filter((otherListener) => otherListener !== listener);
92 };
93 }
94
95 getSnapshot(): Record<string, RTCPeerConnectionInfos> {
96 return this.snapshot;
97 }
98
99 emitChange() {
100 this.snapshot = Object.entries(this.connections).reduce((acc, [contactUri, connection]) => {
101 acc[contactUri] = connection.getInfos();
102 return acc;
103 }, {} as Record<string, RTCPeerConnectionInfos>);
104
105 this.listeners.forEach((listener) => listener());
106 }
107
108 clean() {
109 Object.values(this.connections).forEach((connection) => connection.disconnect());
110 }
111}