289 lines
6.7 KiB
JavaScript
Executable File
289 lines
6.7 KiB
JavaScript
Executable File
class Config {
|
|
static getCookie(cname) {
|
|
var name = cname + "=";
|
|
var decodedCookie = decodeURIComponent(document.cookie);
|
|
var ca = decodedCookie.split(';');
|
|
for(var i = 0; i <ca.length; i++) {
|
|
var c = ca[i];
|
|
while (c.charAt(0) == ' ') {
|
|
c = c.substring(1);
|
|
}
|
|
if (c.indexOf(name) == 0) {
|
|
return c.substring(name.length, c.length);
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
static setCookie(cname, cvalue) {
|
|
var d = new Date();
|
|
d.setTime(d.getTime() + (1*24*60*60*1000));
|
|
var expires = "expires="+ d.toUTCString();
|
|
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
|
}
|
|
|
|
static getUUID() {
|
|
var uuid = Config.getCookie('uuid');
|
|
if (!uuid) {
|
|
Config.newUUID();
|
|
}
|
|
Log.info("uuid", uuid);
|
|
return uuid;
|
|
}
|
|
|
|
static newUUID() {
|
|
var uuid = Config.createUUID();
|
|
Config.setCookie('uuid', uuid);
|
|
}
|
|
|
|
static createUUID() {
|
|
function s4() {
|
|
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
|
|
}
|
|
|
|
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
|
|
}
|
|
}
|
|
Config.iceConfig = {
|
|
'iceServers': [
|
|
{
|
|
'urls': 'turn:turn.home.blapointe.com:5349',
|
|
'credentialType': 'password',
|
|
'username': 'user',
|
|
'credential': 'pass',
|
|
},
|
|
],
|
|
'iceTransportPolicy': 'relay',
|
|
};
|
|
|
|
class Log {
|
|
static write(foo, color) {
|
|
var msg = [].slice.call(arguments[2]).join(" ");
|
|
foo(msg);
|
|
document.getElementById("log").innerHTML +=
|
|
'<br><span style="color: ' + color + ';">' + msg + '</span>';
|
|
}
|
|
|
|
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 += `
|
|
<video id="${id}" autoplay ></video>
|
|
`;
|
|
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];
|
|
}
|
|
}
|
|
|