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 peerConnection = null; static local = null; static pageReady() { Entropy.local = Local.build("video"); } 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.local.stream); 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 { stream = null; static build(type) { switch(type) { case "audio": return new Audio(); case "video": return new Video(); case "text": return new Text(); default: View.error("unknown build", type); } } constructor() { var constraints = { video: this.gettype() == "video", audio: this.gettype() == "audio", }; if(!navigator.mediaDevices.getUserMedia) { View.error('Your browser does not support getUserMedia API'); } navigator.mediaDevices.getUserMedia(constraints) .then((stream) => { if (this.gettype() == "video") { new Preview('preview', stream); } this.stream = stream; }) .catch(View.error); } gettype() { View.error("not impl"); } start() {} stop() {} } class Audio extends Local { gettype() { return "audio"; } } class Video extends Local { gettype() { return "video"; } } 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]; } }