From 1f8157cc5a4d938c8ea0d21597def728a99265f8 Mon Sep 17 00:00:00 2001 From: "D. Berge" Date: Sat, 10 Oct 2020 12:07:31 +0200 Subject: [PATCH] Add client-side RTC logic. It tries to grab the microphone and open a connection to every other user as soon as the websocket connection is made. It shows audio controls for every connection at the bottom of the page for debugging purposes. --- lib/www/client/source/src/lib/rtc.js | 194 +++++++++++++++++++++++++++ lib/www/client/source/src/main.js | 10 +- 2 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 lib/www/client/source/src/lib/rtc.js diff --git a/lib/www/client/source/src/lib/rtc.js b/lib/www/client/source/src/lib/rtc.js new file mode 100644 index 0000000..041d8b4 --- /dev/null +++ b/lib/www/client/source/src/lib/rtc.js @@ -0,0 +1,194 @@ + +let ws = null; +let peerId = null; +let peers = {}; +let stream = null; + +function init (socket) { + ws = socket; + ws.addEventListener("message", (ev) => { + try { + const payload = JSON.parse(ev.data); + if (payload.rtc === true) { + // Handle this message + handle (payload); + } + } catch (err) { + console.error("Invalid message", ev, err); + } + }); +} + +async function talk () { + try { + if (!stream) { + const constraints = { audio: true, video: false }; + stream = await navigator.mediaDevices.getUserMedia(constraints); + console.log("Grabbed stream", stream); + } + + if (peerId && Object.keys(peers).length) { + for (const track of stream.getTracks()) { + for (const peer in peers) { + peers[peer].addTrack(track, stream); + } + } + } + } catch (err) { + console.error("talk() error", err); + } +} + +class PeerConnection { + + constructor (otherPeerId) { + this.otherPeerId = otherPeerId; + this.makingOffer = false; + this.polite = this.otherPeerId > peerId; + this.start(); + } + + send (message) { + message.from = peerId; + message.to = this.otherPeerId; + send(message); + } + + async handle (message) { + try { + let ignoreOffer = false; + + if ("description" in message) { + const offerCollision = (message.description.type == "offer") && + (this.makingOffer || this.conn.signalingState != "stable"); + + ignoreOffer = !this.polite && offerCollision; + + if (ignoreOffer) { + return; + } + + await this.conn.setRemoteDescription(message.description); + if (message.description.type == "offer") { + await this.conn.setLocalDescription(); + this.send({ description: this.conn.localDescription }); + } + } + + if ("candidate" in message) { + try { + await this.conn.addIceCandidate(message.candidate); + } catch (err) { + if (!ignoreOffer) { + throw err; + } + } + } + } catch (err) { + console.error(err); + } + } + + start () { + const config = { iceServers: [] }; + this.conn = new RTCPeerConnection(config); + console.log("Have peer connection", this.conn); + + this.conn.ontrack = ({track, streams}) => { + // FIXME Need to remove these elements when done + if (!this.remoteAudio) { + this.remoteAudio = document.createElement("audio"); + this.remoteAudio.setAttribute("autoplay", "true"); + this.remoteAudio.controls = true; + document.getElementsByTagName("footer")[0].appendChild(this.remoteAudio); + console.log("Added