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() { Log.write(rconsole.info, "gray", arguments); } static warn() { Log.write(rconsole.warn, "orange", arguments); } static log() { Log.write(rconsole.log, "black", arguments); } static error() { Log.write(rconsole.error, "red", arguments); } } class Preview { element = null; constructor(id, stream) { this.element = document.getElementById(id); this.element.srcObject = stream; } } class WS { constructor(address, cb) { Log.warn("address", address); WS.ws = new WebSocket(address); WS.ws.onmessage = cb; } } WS.ws = null; class Remote { element = null; constructor(id, stream) { document.getElementById("remote").innerHTML += ` `; this.element = document.getElementById(id); this.element.srcObject = stream; } destructor() { Log.log("destructor called"); this.element.remove(); } } class RTC { conn = null; streams = {}; // TODO issue: singleton for all feeds ever;; 1 peer = 1 server conn constructor(stream) { this.conn = new RTCPeerConnection(Config.iceConfig); this.conn.onicecandidate = (event) => { if (event.candidate != null) { WS.ws.send(JSON.stringify({'ice': event.candidate, 'uuid': Config.getUUID()})); } }; this.conn.ontrack = (event) => { event.streams.forEach((stream) => { this.streams[stream.id] = new Remote(stream.id, stream); }); }; this.conn.addStream(stream); } offer(cb) { this.conn .createOffer({ 'iceRestart': true, 'voiceActivityDetection': true, }) .then(cb) .catch(Log.error); } } class Entropy { static pageReady() { Entropy.local = Local.build("video"); } static start(isCaller) { Entropy.rtc = new RTC(Entropy.local.stream); if (isCaller) { Entropy.rtc.offer(Entropy.createdDescription); } } static gotMessageFromServer(message) { if (!Entropy.rtc || !Entropy.rtc.conn) Entropy.start(false); var signal = JSON.parse(message.data); // Ignore messages from ourself if (signal.uuid == Config.getUUID()) return; if (signal.joined) Log.info("a challenjour has appeared", JSON.stringify(signal)); if (signal.sdp) { Entropy.rtc.conn.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() { // Only create answers in response to offers if (signal.sdp.type == 'offer') { Entropy.rtc.conn.createAnswer().then(Entropy.createdDescription).catch(Log.error); } }).catch(Log.error); } else if (signal.ice) { Entropy.rtc.conn.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(Log.error); } } static createdDescription(description) { Log.log('got description'); Entropy.rtc.conn .setLocalDescription(description) .then(function() { WS.ws.send(JSON.stringify({ 'sdp': Entropy.rtc.conn.localDescription, 'uuid': Config.getUUID(), })); }) .catch(Log.error); } } Entropy.local = null; Entropy.rtc = 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: Log.error("unknown build", type); } } constructor() { var constraints = { video: this.gettype() == "video", audio: this.gettype() == "audio", }; if (!navigator.mediaDevices.getUserMedia) { Log.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(Log.error); } gettype() { Log.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] = Log[i]; } window.console = console; function pageReady() { //Config.newUUID(); var host = window.location.hostname; if (window.location.port) { host += ":" + window.location.port; } new WS('wss://' + host + '/ws/' + window.location.pathname.split("/").slice(-1).pop(), 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]; } }