Squashed 'third_party/rawrtc/rawrtc/' content from commit aa3ae4b24

Change-Id: I38a655a4259b62f591334e90a1315bd4e7e4d8ec
git-subtree-dir: third_party/rawrtc/rawrtc
git-subtree-split: aa3ae4b247275cc6e69c30613b3a4ba7fdc82d1b
diff --git a/htdocs/webrtc/peerconnection.js b/htdocs/webrtc/peerconnection.js
new file mode 100644
index 0000000..9161966
--- /dev/null
+++ b/htdocs/webrtc/peerconnection.js
@@ -0,0 +1,95 @@
+'use strict';
+
+/**
+ * A WebRTC peer connection helper. Tightly coupled with the signaling
+ * class.
+ */
+class WebRTCPeerConnection {
+    constructor(signaling, offering, configuration = null) {
+        // Set default configuration (if none provided)
+        if (configuration === null) {
+            configuration = {
+                iceServers: [{
+                    urls: 'stun:stun.services.mozilla.com',
+                }],
+            };
+        }
+
+        // Create peer connection and bind events
+        const pc = new RTCPeerConnection(configuration);
+        pc._offering = offering; // Meh!
+        signaling.pc = pc;
+        pc.onnegotiationneeded = async () => {
+            console.log('Negotiation needed');
+
+            // Create offer (if required)
+            if (offering) {
+                console.log('Creating offer');
+                const description = await pc.createOffer();
+                await pc.setLocalDescription(description);
+                signaling.handleLocalDescription(description);
+            }
+        };
+        pc.signalingstatechange = () => {
+            console.log('Signaling state:', pc.signalingState);
+        };
+        pc.oniceconnectionstatechange = () => {
+            console.log('ICE connection state:', pc.iceConnectionState);
+        };
+        pc.onicegatheringstatechange = () => {
+            console.log('ICE gathering state:', pc.iceGatheringState);
+        };
+        pc.onconnectionstatechange = () => {
+            console.log('Connection state:', pc.connectionState);
+        };
+        pc.onicecandidate = (event) => {
+            signaling.handleLocalCandidate(event.candidate);
+        };
+        pc.onicecandidateerror = (event) => {
+            console.error('ICE candidate error:', event);
+        };
+        pc.ondatachannel = (event) => {
+            const dc = event.channel;
+            console.log('Incoming data channel:', dc.label);
+
+            // Bind events
+            this.bindDataChannelEvents(dc);
+        };
+
+        // Store configuration & signalling instance
+        this.pc = pc;
+        this.dcs = {};
+    }
+
+    createDataChannel(name, options = null) {
+        const pc = this.pc;
+
+        // Create data channel and bind events
+        const dc = pc.createDataChannel(name, options);
+        this.bindDataChannelEvents(dc);
+
+        // Store data channel and return
+        this.dcs[name] = dc;
+        return dc;
+    }
+
+    bindDataChannelEvents(dc) {
+        dc._name = dc.label; // Meh!
+        dc.onopen = () => {
+            console.log(dc._name, 'open');
+        };
+        dc.onclose = () => {
+            console.log(dc._name, 'closed');
+        };
+        dc.onerror = (event) => {
+            console.log(dc._name, 'error:', event);
+        };
+        dc.onbufferedamountlow = () => {
+            console.log(dc._name, 'buffered amount low:', dc.bufferedAmount);
+        };
+        dc.onmessage = (event) => {
+            const size = event.data.byteLength || event.data.size;
+            console.log(dc._name, 'incoming message (' + size + ' bytes)');
+        };
+    }
+}