const internals = { peers: {}, createAudioElement(source) { const audioElement = document.createElement("audio"); audioElement.setAttribute("class", "junction-call-audio"); audioElement.autoplay = "autoplay"; audioElement.srcObject = source; document.querySelector("body").appendChild(audioElement); return audioElement; }, }; export function addPeer({ peerId, shouldCreateOffer, mediaStream, onOffer, socket, }) { const peerConnection = new RTCPeerConnection( { iceServers: internals.kIceServers }, { optional: [{ DtlsSrtpKeyAgreement: true }] }, ); internals.peers[peerId] = peerConnection; mediaStream.getTracks().forEach((track) => { peerConnection.addTrack(track, mediaStream); }); peerConnection.onicecandidate = (event) => { if (event.candidate) { socket.emit("relayICECandidate", { peerId: peerId, candidate: event.candidate, }); } }; const remoteStream = new MediaStream(); peerConnection.ontrack = (event) => { remoteStream.addTrack(event.track); const remoteAudioElement = new Audio(); remoteAudioElement.srcObject = remoteStream; remoteAudioElement.play(); }; peerConnection.onnegotiationneeded = async () => { if (shouldCreateOffer) { console.debug("Creating RTC offer to ", peerId); const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer); onOffer({ peerId, offer }); } }; console.info(`There are now ${countPeers()} participants`); } export function removePeer({ peerId }) { delete internals.peers[peerId]; console.info(`There are now ${countPeers()} participants`); } export async function answerPeerOffer({ peerId, offer }) { console.info(`Answering peer ${peerId}`); const peerConnection = internals.peers[peerId]; const remoteDescription = new RTCSessionDescription(offer); await peerConnection.setRemoteDescription(remoteDescription); const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer); return { peerId, answer }; } export async function processPeerAnswer({ peerId, answer }) { console.info(`Processing answer for peer ${peerId}`); const peerConnection = internals.peers[peerId]; const remoteDescription = new RTCSessionDescription(answer); await peerConnection.setRemoteDescription(remoteDescription); } export async function addIceCandidate({ peerId, candidate }) { console.info(`Adding ICE candidate for peer ${peerId}`); const peerConnection = internals.peers[peerId]; console.info(peerConnection.signalingState); const iceCandidate = new RTCIceCandidate(candidate); await peerConnection.addIceCandidate(iceCandidate); } export function countPeers() { return Object.keys(internals.peers).length; } export function resetPeers() { for (const connection of Object.values(internals.peers)) { connection.close(); } internals.peers = {}; document .querySelectorAll(".junction-call-audio") .forEach((audioElement) => audioElement.remove()); }