]>
Commit | Line | Data |
---|---|---|
1 | const internals = { | |
2 | peers: {}, | |
3 | ||
4 | createAudioElement(source) { | |
5 | const audioElement = document.createElement("audio"); | |
6 | audioElement.setAttribute("class", "junction-call-audio"); | |
7 | audioElement.autoplay = "autoplay"; | |
8 | audioElement.srcObject = source; | |
9 | ||
10 | document.querySelector("body").appendChild(audioElement); | |
11 | ||
12 | return audioElement; | |
13 | }, | |
14 | }; | |
15 | ||
16 | export function addPeer({ | |
17 | peerId, | |
18 | shouldCreateOffer, | |
19 | mediaStream, | |
20 | onOffer, | |
21 | socket, | |
22 | }) { | |
23 | const peerConnection = new RTCPeerConnection( | |
24 | { iceServers: internals.kIceServers }, | |
25 | { optional: [{ DtlsSrtpKeyAgreement: true }] }, | |
26 | ); | |
27 | ||
28 | internals.peers[peerId] = peerConnection; | |
29 | mediaStream.getTracks().forEach((track) => { | |
30 | peerConnection.addTrack(track, mediaStream); | |
31 | }); | |
32 | ||
33 | peerConnection.onicecandidate = (event) => { | |
34 | if (event.candidate) { | |
35 | socket.emit("relayICECandidate", { | |
36 | peerId: peerId, | |
37 | candidate: event.candidate, | |
38 | }); | |
39 | } | |
40 | }; | |
41 | ||
42 | const remoteStream = new MediaStream(); | |
43 | peerConnection.ontrack = (event) => { | |
44 | remoteStream.addTrack(event.track); | |
45 | const remoteAudioElement = new Audio(); | |
46 | remoteAudioElement.srcObject = remoteStream; | |
47 | remoteAudioElement.play(); | |
48 | }; | |
49 | ||
50 | peerConnection.onnegotiationneeded = async () => { | |
51 | if (shouldCreateOffer) { | |
52 | console.debug("Creating RTC offer to ", peerId); | |
53 | const offer = await peerConnection.createOffer(); | |
54 | await peerConnection.setLocalDescription(offer); | |
55 | ||
56 | onOffer({ peerId, offer }); | |
57 | } | |
58 | }; | |
59 | ||
60 | console.info(`There are now ${countPeers()} participants`); | |
61 | } | |
62 | ||
63 | export function removePeer({ peerId }) { | |
64 | delete internals.peers[peerId]; | |
65 | console.info(`There are now ${countPeers()} participants`); | |
66 | } | |
67 | ||
68 | export async function answerPeerOffer({ peerId, offer }) { | |
69 | console.info(`Answering peer ${peerId}`); | |
70 | const peerConnection = internals.peers[peerId]; | |
71 | ||
72 | const remoteDescription = new RTCSessionDescription(offer); | |
73 | await peerConnection.setRemoteDescription(remoteDescription); | |
74 | ||
75 | const answer = await peerConnection.createAnswer(); | |
76 | await peerConnection.setLocalDescription(answer); | |
77 | ||
78 | return { peerId, answer }; | |
79 | } | |
80 | ||
81 | export async function processPeerAnswer({ peerId, answer }) { | |
82 | console.info(`Processing answer for peer ${peerId}`); | |
83 | const peerConnection = internals.peers[peerId]; | |
84 | const remoteDescription = new RTCSessionDescription(answer); | |
85 | await peerConnection.setRemoteDescription(remoteDescription); | |
86 | } | |
87 | ||
88 | export async function addIceCandidate({ peerId, candidate }) { | |
89 | console.info(`Adding ICE candidate for peer ${peerId}`); | |
90 | const peerConnection = internals.peers[peerId]; | |
91 | console.info(peerConnection.signalingState); | |
92 | const iceCandidate = new RTCIceCandidate(candidate); | |
93 | await peerConnection.addIceCandidate(iceCandidate); | |
94 | } | |
95 | ||
96 | export function countPeers() { | |
97 | return Object.keys(internals.peers).length; | |
98 | } | |
99 | ||
100 | export function resetPeers() { | |
101 | for (const connection of Object.values(internals.peers)) { | |
102 | connection.close(); | |
103 | } | |
104 | internals.peers = {}; | |
105 | document | |
106 | .querySelectorAll(".junction-call-audio") | |
107 | .forEach((audioElement) => audioElement.remove()); | |
108 | } |