class Config { static peerConnectionConfig = { 'iceServers': [ {'urls': 'stun:stun.stunprotocol.org:3478'}, {'urls': 'stun:stun.l.google.com:19302'}, ] }; static getCookie(cname) { var name = cname + "="; var decodedCookie = decodeURIComponent(document.cookie); var ca = decodedCookie.split(';'); for(var i = 0; i ' + msg + ''; } static info() { View.write(rconsole.info, "gray", arguments); } static warn() { View.write(rconsole.warn, "orange", arguments); } static log() { View.write(rconsole.log, "black", arguments); } static error() { View.write(rconsole.error, "red", arguments); } } class Remote { element = null; constructor(id, stream) { this.element = document.getElementById(id); this.element.srcObject = stream; } } class Preview { element = null; constructor(id, stream) { this.element = document.getElementById(id); this.element.srcObject = stream; } } class Controller { static serverConnection; constructor(address, cb) { Controller.serverConnection = new WebSocket(address); Controller.serverConnection.onmessage = cb; } } class Entropy { static preview = null; static localStream = null; static peerConnection = null; static remote = null; static pageReady() { var constraints = { video: true, audio: false, }; if(navigator.mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia(constraints) .then((stream) => { new Preview('preview', stream); Entropy.localStream = stream; }) .catch(View.error); } else { alert('Your browser does not support getUserMedia API'); } } static start(isCaller) { Entropy.peerConnection = new RTCPeerConnection(Config.peerConnectionConfig); Entropy.peerConnection.onicecandidate = Entropy.gotIceCandidate; Entropy.peerConnection.ontrack = (event) => new Remote('remoteVideo', event.streams[0]); Entropy.peerConnection.addStream(Entropy.localStream); if(isCaller) { Entropy.peerConnection .createOffer({ 'iceRestart': true, 'voiceActivityDetection': true, }) .then(Entropy.createdDescription) .catch(View.error); } } static gotMessageFromServer(message) { if(!Entropy.peerConnection) Entropy.start(false); var signal = JSON.parse(message.data); // Ignore messages from ourself if(signal.uuid == Config.getUUID()) return; if(signal.sdp) { Entropy.peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() { // Only create answers in response to offers if(signal.sdp.type == 'offer') { Entropy.peerConnection.createAnswer().then(Entropy.createdDescription).catch(View.error); } }).catch(View.error); } else if(signal.ice) { Entropy.peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(View.error); } } static gotIceCandidate(event) { if(event.candidate != null) { Controller.serverConnection.send(JSON.stringify({'ice': event.candidate, 'uuid': Config.getUUID()})); } } static createdDescription(description) { View.log('got description'); Entropy.peerConnection .setLocalDescription(description) .then(function() { Controller.serverConnection.send(JSON.stringify({ 'sdp': Entropy.peerConnection.localDescription, 'uuid': Config.getUUID(), })); }) .catch(View.error); } } class Local { type = null; constructor(type) { this.type = type; } start() {} stop() {} } class Audio extends Local { } class Video extends Local { } class Text extends Local { } var streams = {} var rconsole = console; var console = {} for (var i in ["log", "info", "warn", "error"]) { console[i] = View[i]; } window.console = console; function pageReady() { Config.getUUID(); new Controller('wss://' + window.location.hostname + '/abc', Entropy.gotMessageFromServer); Entropy.pageReady(); } function start(b, type = null) { Entropy.start(b); } function toggle(type, caller) { //start(true, type); var exists = Object.keys(streams); if (caller.checked && ! (type in exists)) { switch(type) { case "audio": streams[type] = new Audio(); break; case "video": streams[type] = new Video(); break; case "text": streams[type] = new Text(); break; default: View.error("unknown type", type); return; } streams[type].start(); } else if (type in exists) { streams[type].stop(); delete streams[type]; } }