class Config { 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 Preview { element = null; constructor(id, stream) { this.element = document.getElementById(id); this.element.srcObject = stream; } } class Server { server = null; constructor(address, cb) { Server.server = new WebSocket(address); Server.server.onmessage = cb; } } class Remote { element = null; constructor(id, stream) { document.getElementById("remote").innerHTML += ` `; this.element = document.getElementById(id); this.element.srcObject = stream; } destructor() { View.log("destructor called"); this.element.remove(); } } class Peer { peer = null; streams = {}; constructor(stream) { this.peer = new RTCPeerConnection(Config.iceConfig); this.peer.onicecandidate = (event) => { if(event.candidate != null) { Server.server.send(JSON.stringify({'ice': event.candidate, 'uuid': Config.getUUID()})); } }; this.peer.ontrack = (event) => { event.streams.forEach((stream) => { this.streams[stream.id] = new Remote(stream.id, stream); }); }; this.peer.oniceconnectionstatechange = (event) => { switch (this.peer.iceConnectionState) { case "disconnected": case "failed": case "closed": for (var k in this.streams) { this.streams[k].destructor(); delete this.streams[k]; } } }; this.peer.addStream(stream); } offer(cb) { this.peer .createOffer({ 'iceRestart': true, 'voiceActivityDetection': true, }) .then(cb) .catch(View.error); } destructor() { this.peer.close(); } } class Entropy { static pageReady() { Entropy.local = Local.build("video"); } static start(isCaller) { Entropy.peer = new Peer(Entropy.local.stream); if(isCaller) { Entropy.peer.offer(Entropy.createdDescription); } } static gotMessageFromServer(message) { if(!Entropy.peer || !Entropy.peer.peer) Entropy.start(false); var signal = JSON.parse(message.data); // Ignore messages from ourself if(signal.uuid == Config.getUUID()) return; if(signal.sdp) { Entropy.peer.peer.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() { // Only create answers in response to offers if(signal.sdp.type == 'offer') { Entropy.peer.peer.createAnswer().then(Entropy.createdDescription).catch(View.error); } }).catch(View.error); } else if(signal.ice) { Entropy.peer.peer.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(View.error); } } static createdDescription(description) { View.log('got description'); Entropy.peer.peer .setLocalDescription(description) .then(function() { Server.server.send(JSON.stringify({ 'sdp': Entropy.peer.peer.localDescription, 'uuid': Config.getUUID(), })); }) .catch(View.error); } } Entropy.local = null; Entropy.peer = null; 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(); var host = window.location.hostname; if (window.location.port) { host += ":" + window.location.port; } new Server('wss://' + host + '/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)) { streams[type] = Local.build(type); streams[type].start(); } else if (type in exists) { streams[type].stop(); delete streams[type]; } }