Compare commits
10 Commits
b762ce2401
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ca9686bee | ||
|
|
fab2ab97d7 | ||
|
|
d6ea697f57 | ||
|
|
d2cebde4dc | ||
|
|
c9872c7725 | ||
|
|
4145bea3ba | ||
|
|
816c793c5a | ||
|
|
d610aebc8f | ||
|
|
24eb2bc3de | ||
|
|
5c924ad154 |
54
BRAINDUMP.md
Executable file
54
BRAINDUMP.md
Executable file
@@ -0,0 +1,54 @@
|
||||
# Classes
|
||||
|
||||
## Config - helpers
|
||||
|
||||
### Static
|
||||
|
||||
* [gs]etCookie
|
||||
* createUUID
|
||||
* getUUID
|
||||
|
||||
## Log - logger
|
||||
|
||||
### Static
|
||||
|
||||
* info
|
||||
* warn
|
||||
* error
|
||||
* log
|
||||
|
||||
## Preview - self-video feed element
|
||||
|
||||
* element
|
||||
|
||||
## WS - websocket wrapper
|
||||
|
||||
### Static
|
||||
|
||||
* ws
|
||||
|
||||
## RTC - RTC connection to TURN
|
||||
|
||||
* conn (rtc conection)
|
||||
* streams {id: RTCMediaStream}
|
||||
|
||||
## Remote - local display of stream
|
||||
|
||||
* element
|
||||
|
||||
## Local - local stream out
|
||||
|
||||
### Text, Video, Audio
|
||||
|
||||
* stream (MediaStream)
|
||||
|
||||
## Entropy - monolith
|
||||
|
||||
### Static
|
||||
|
||||
* pageReady - init Local preview video
|
||||
* start - new RTC conn, sends offer message
|
||||
* gotMessageFromServer - listens for events from server, starts stream if called, responds to offer messages, responds to TURN connect
|
||||
* createdDescription
|
||||
* local - Local
|
||||
* rtc - RTC
|
||||
19
errors.log
Normal file
19
errors.log
Normal file
@@ -0,0 +1,19 @@
|
||||
address wss://dev2.home.blapointe.com/ws/
|
||||
got description
|
||||
uuid
|
||||
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
|
||||
got description
|
||||
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
|
||||
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
|
||||
InvalidStateError: Cannot set remote answer in state stable
|
||||
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
|
||||
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
|
||||
OperationError: Unknown ufrag (fa5a1736)
|
||||
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
|
||||
OperationError: Unknown ufrag (fa5a1736)
|
||||
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
|
||||
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
|
||||
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
|
||||
uuid 172c8038-8ec0-d3a9-99c0-72798dded8b2
|
||||
|
||||
https://stackoverflow.com/questions/30109011/webrtc-failed-to-add-a-third-peer-cannot-set-remote-answer-in-state-stable
|
||||
29
pool.go
Normal file → Executable file
29
pool.go
Normal file → Executable file
@@ -3,22 +3,39 @@ package main
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type Conn struct {
|
||||
ws websocket.Conn
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
type Pool struct {
|
||||
conns *sync.Map //map[string]*websocket.Conn
|
||||
lock *sync.RWMutex
|
||||
conns *sync.Map //map[string]*Conn
|
||||
}
|
||||
|
||||
func NewPool() *Pool {
|
||||
return &Pool{
|
||||
conns: &sync.Map{}, //map[string]*websocket.Conn{},
|
||||
conns: &sync.Map{},
|
||||
lock: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) Push(id string, conn *websocket.Conn) {
|
||||
p.lock.Lock()
|
||||
p.conns.Store(id, &Conn{ws: *conn})
|
||||
p.lock.Unlock()
|
||||
p.Broadcast(websocket.TextMessage, strings.NewReader(`{"joined":"`+id+`", "uuid":"`+id+`"}`))
|
||||
}
|
||||
|
||||
func (p *Pool) Broadcast(mt int, r io.Reader) error {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
// io.MultiWriter exists but I like this
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
@@ -28,9 +45,13 @@ func (p *Pool) Broadcast(mt int, r io.Reader) error {
|
||||
cnt := 0
|
||||
p.conns.Range(func(k, v interface{}) bool {
|
||||
k = k.(string)
|
||||
conn := v.(*websocket.Conn)
|
||||
conn := v.(*Conn)
|
||||
lock := &conn.lock
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
ws := &conn.ws
|
||||
cnt += 1
|
||||
w, err := conn.NextWriter(mt)
|
||||
w, err := ws.NextWriter(mt)
|
||||
if err != nil {
|
||||
p.conns.Delete(k)
|
||||
return true
|
||||
|
||||
@@ -1,25 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!--
|
||||
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
||||
-->
|
||||
<script src="webrtc.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<style>
|
||||
video {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
/*
|
||||
max-width: 400px;
|
||||
min-width: 400px;
|
||||
*/
|
||||
border: 1px solid black;
|
||||
display: inline-block;
|
||||
}
|
||||
#preview { display: block; max-width: 150px; position: absolute; top: 0; right: 0; z-index: 1; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -27,18 +10,16 @@
|
||||
<div>Audio<label class="switch"><input onclick="toggle('audio', this)" type="checkbox"><span class="slider round"></span></label></div>
|
||||
|
||||
<video id="preview" autoplay muted ></video>
|
||||
<div id="remote">
|
||||
</div>
|
||||
|
||||
<div id="remote"></div>
|
||||
<br />
|
||||
|
||||
<input type="button" id="start" onclick="start(true)" value="Start Video"></input>
|
||||
|
||||
<div id="log"></div>
|
||||
</body>
|
||||
|
||||
<footer>
|
||||
<script type="text/javascript">
|
||||
pageReady();
|
||||
</script>
|
||||
|
||||
<div id="log">
|
||||
</div>
|
||||
</body>
|
||||
</footer>
|
||||
</html>
|
||||
|
||||
23
public/style.css
Normal file → Executable file
23
public/style.css
Normal file → Executable file
@@ -59,4 +59,25 @@ input:checked + .slider:before {
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
max-height: 90vh;
|
||||
/*
|
||||
max-width: 400px;
|
||||
min-width: 400px;
|
||||
*/
|
||||
border: 1px solid black;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#preview {
|
||||
display: block;
|
||||
max-width: 150px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
155
public/webrtc.js
155
public/webrtc.js
@@ -25,14 +25,18 @@ class Config {
|
||||
static getUUID() {
|
||||
var uuid = Config.getCookie('uuid');
|
||||
if (!uuid) {
|
||||
uuid = Config.createUUID();
|
||||
Config.setCookie('uuid', uuid);
|
||||
Config.newUUID();
|
||||
}
|
||||
Log.info("uuid", uuid);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
static newUUID() {
|
||||
var uuid = Config.createUUID();
|
||||
Config.setCookie('uuid', uuid);
|
||||
}
|
||||
|
||||
static createUUID() {
|
||||
return "" + new Date();
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
|
||||
}
|
||||
@@ -52,7 +56,7 @@ Config.iceConfig = {
|
||||
'iceTransportPolicy': 'relay',
|
||||
};
|
||||
|
||||
class View {
|
||||
class Log {
|
||||
static write(foo, color) {
|
||||
var msg = [].slice.call(arguments[2]).join(" ");
|
||||
foo(msg);
|
||||
@@ -61,32 +65,19 @@ class View {
|
||||
}
|
||||
|
||||
static info() {
|
||||
View.write(rconsole.info, "gray", arguments);
|
||||
Log.write(rconsole.info, "gray", arguments);
|
||||
}
|
||||
|
||||
static warn() {
|
||||
View.write(rconsole.warn, "orange", arguments);
|
||||
Log.write(rconsole.warn, "orange", arguments);
|
||||
}
|
||||
|
||||
static log() {
|
||||
View.write(rconsole.log, "black", arguments);
|
||||
Log.write(rconsole.log, "black", arguments);
|
||||
}
|
||||
|
||||
static error() {
|
||||
View.write(rconsole.error, "red", arguments);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
View.log(this.element);
|
||||
Log.write(rconsole.error, "red", arguments);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,62 +88,61 @@ class Preview {
|
||||
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 Server {
|
||||
server = null;
|
||||
|
||||
constructor(address, cb) {
|
||||
Server.server = new WebSocket(address);
|
||||
Server.server.onmessage = cb;
|
||||
}
|
||||
}
|
||||
|
||||
class Peer {
|
||||
peer = null;
|
||||
class RTC {
|
||||
conn = null;
|
||||
streams = {};
|
||||
// TODO issue: singleton for all feeds ever;; 1 peer = 1 server conn
|
||||
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.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.peer.ontrack = (event) => {
|
||||
this.conn.ontrack = (event) => {
|
||||
event.streams.forEach((stream) => {
|
||||
this.streams[stream.id] = new Remote(stream.id, stream);
|
||||
});
|
||||
View.log("/ontrack:", this.streams);
|
||||
};
|
||||
this.peer.onremovestream = (event) => {
|
||||
event.streams.forEach((stream) => {
|
||||
this.streams[stream.id].destructor();
|
||||
delete this.streams[stream.id];
|
||||
});
|
||||
View.log("/onremovestream:", this.streams);
|
||||
};
|
||||
this.peer.onconnectionstatechange = (event) => {
|
||||
View.log("/onconnectionstatechange:", this.streams, this.peer.connectionState);
|
||||
};
|
||||
this.peer.addStream(stream);
|
||||
this.conn.addStream(stream);
|
||||
}
|
||||
|
||||
offer(cb) {
|
||||
this.peer
|
||||
this.conn
|
||||
.createOffer({
|
||||
'iceRestart': true,
|
||||
'voiceActivityDetection': true,
|
||||
})
|
||||
.then(cb)
|
||||
.catch(View.error);
|
||||
}
|
||||
|
||||
destructor() {
|
||||
this.peer.close();
|
||||
.catch(Log.error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,48 +152,51 @@ class Entropy {
|
||||
}
|
||||
|
||||
static start(isCaller) {
|
||||
Entropy.peer = new Peer(Entropy.local.stream);
|
||||
if(isCaller) {
|
||||
Entropy.peer.offer(Entropy.createdDescription);
|
||||
Entropy.rtc = new RTC(Entropy.local.stream);
|
||||
if (isCaller) {
|
||||
Entropy.rtc.offer(Entropy.createdDescription);
|
||||
}
|
||||
}
|
||||
|
||||
static gotMessageFromServer(message) {
|
||||
if(!Entropy.peer || !Entropy.peer.peer) Entropy.start(false);
|
||||
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.uuid == Config.getUUID()) return;
|
||||
|
||||
if(signal.sdp) {
|
||||
Entropy.peer.peer.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() {
|
||||
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.peer.peer.createAnswer().then(Entropy.createdDescription).catch(View.error);
|
||||
if (signal.sdp.type == 'offer') {
|
||||
Entropy.rtc.conn.createAnswer().then(Entropy.createdDescription).catch(Log.error);
|
||||
}
|
||||
}).catch(View.error);
|
||||
} else if(signal.ice) {
|
||||
Entropy.peer.peer.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(View.error);
|
||||
}).catch(Log.error);
|
||||
} else if (signal.ice) {
|
||||
Entropy.rtc.conn.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(Log.error);
|
||||
}
|
||||
}
|
||||
|
||||
static createdDescription(description) {
|
||||
View.log('got description');
|
||||
Log.log('got description');
|
||||
|
||||
Entropy.peer.peer
|
||||
Entropy.rtc.conn
|
||||
.setLocalDescription(description)
|
||||
.then(function() {
|
||||
Server.server.send(JSON.stringify({
|
||||
'sdp': Entropy.peer.peer.localDescription,
|
||||
WS.ws.send(JSON.stringify({
|
||||
'sdp': Entropy.rtc.conn.localDescription,
|
||||
'uuid': Config.getUUID(),
|
||||
}));
|
||||
})
|
||||
.catch(View.error);
|
||||
.catch(Log.error);
|
||||
}
|
||||
}
|
||||
Entropy.local = null;
|
||||
Entropy.peer = null;
|
||||
Entropy.rtc = null;
|
||||
|
||||
class Local {
|
||||
stream = null;
|
||||
@@ -213,7 +206,7 @@ class Local {
|
||||
case "audio": return new Audio();
|
||||
case "video": return new Video();
|
||||
case "text": return new Text();
|
||||
default: View.error("unknown build", type);
|
||||
default: Log.error("unknown build", type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,8 +215,8 @@ class Local {
|
||||
video: this.gettype() == "video",
|
||||
audio: this.gettype() == "audio",
|
||||
};
|
||||
if(!navigator.mediaDevices.getUserMedia) {
|
||||
View.error('Your browser does not support getUserMedia API');
|
||||
if (!navigator.mediaDevices.getUserMedia) {
|
||||
Log.error('Your browser does not support getUserMedia API');
|
||||
}
|
||||
navigator.mediaDevices.getUserMedia(constraints)
|
||||
.then((stream) => {
|
||||
@@ -232,11 +225,11 @@ class Local {
|
||||
}
|
||||
this.stream = stream;
|
||||
})
|
||||
.catch(View.error);
|
||||
.catch(Log.error);
|
||||
}
|
||||
|
||||
gettype() {
|
||||
View.error("not impl");
|
||||
Log.error("not impl");
|
||||
}
|
||||
|
||||
start() {}
|
||||
@@ -263,17 +256,17 @@ var streams = {}
|
||||
var rconsole = console;
|
||||
var console = {}
|
||||
for (var i in ["log", "info", "warn", "error"]) {
|
||||
console[i] = View[i];
|
||||
console[i] = Log[i];
|
||||
}
|
||||
window.console = console;
|
||||
|
||||
function pageReady() {
|
||||
Config.getUUID();
|
||||
//Config.newUUID();
|
||||
var host = window.location.hostname;
|
||||
if (window.location.port) {
|
||||
host += ":" + window.location.port;
|
||||
}
|
||||
new Server('wss://' + host + '/abc', Entropy.gotMessageFromServer);
|
||||
new WS('wss://' + host + '/ws/' + window.location.pathname.split("/").slice(-1).pop(), Entropy.gotMessageFromServer);
|
||||
Entropy.pageReady();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
var localVideo;
|
||||
var localStream;
|
||||
var remoteVideo;
|
||||
var peerConnection;
|
||||
var uuid;
|
||||
var serverConnection;
|
||||
|
||||
var peerConnectionConfig = {
|
||||
'iceServers': [
|
||||
{'urls': 'stun:stun.stunprotocol.org:3478'},
|
||||
{'urls': 'stun:stun.l.google.com:19302'},
|
||||
]
|
||||
};
|
||||
|
||||
function 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 "";
|
||||
}
|
||||
|
||||
function 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=/";
|
||||
}
|
||||
|
||||
function pageReady() {
|
||||
uuid = getCookie("uuid");
|
||||
if (!uuid) {
|
||||
uuid = createUUID();
|
||||
setCookie("uuid", uuid);
|
||||
}
|
||||
|
||||
localVideo = document.getElementById('localVideo');
|
||||
remoteVideo = document.getElementById('remoteVideo');
|
||||
|
||||
serverConnection = new WebSocket('wss://' + window.location.hostname + '/abc?uuid=' + uuid);
|
||||
serverConnection.onmessage = gotMessageFromServer;
|
||||
|
||||
var constraints = {
|
||||
video: true,
|
||||
audio: true,
|
||||
};
|
||||
|
||||
if(navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices.getUserMedia(constraints).then(getUserMediaSuccess).catch(errorHandler);
|
||||
} else {
|
||||
alert('Your browser does not support getUserMedia API');
|
||||
}
|
||||
}
|
||||
|
||||
function getUserMediaSuccess(stream) {
|
||||
localStream = stream;
|
||||
localVideo.srcObject = stream;
|
||||
}
|
||||
|
||||
function start(isCaller) {
|
||||
peerConnection = new RTCPeerConnection(peerConnectionConfig);
|
||||
peerConnection.onicecandidate = gotIceCandidate;
|
||||
peerConnection.ontrack = gotRemoteStream;
|
||||
peerConnection.addStream(localStream);
|
||||
|
||||
if(isCaller) {
|
||||
peerConnection
|
||||
.createOffer({
|
||||
'iceRestart': true,
|
||||
'voiceActivityDetection': true,
|
||||
})
|
||||
.then(createdDescription)
|
||||
.catch(errorHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function gotMessageFromServer(message) {
|
||||
if(!peerConnection) start(false);
|
||||
|
||||
var signal = JSON.parse(message.data);
|
||||
|
||||
// Ignore messages from ourself
|
||||
if(signal.uuid == uuid) return;
|
||||
|
||||
if(signal.sdp) {
|
||||
peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() {
|
||||
// Only create answers in response to offers
|
||||
if(signal.sdp.type == 'offer') {
|
||||
peerConnection.createAnswer().then(createdDescription).catch(errorHandler);
|
||||
}
|
||||
}).catch(errorHandler);
|
||||
} else if(signal.ice) {
|
||||
peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(errorHandler);
|
||||
}
|
||||
}
|
||||
|
||||
function gotIceCandidate(event) {
|
||||
if(event.candidate != null) {
|
||||
serverConnection.send(JSON.stringify({'ice': event.candidate, 'uuid': uuid}));
|
||||
}
|
||||
}
|
||||
|
||||
function createdDescription(description) {
|
||||
console.log('got description');
|
||||
|
||||
peerConnection
|
||||
.setLocalDescription(description)
|
||||
.then(function() {
|
||||
serverConnection.send(JSON.stringify({
|
||||
'sdp': peerConnection.localDescription,
|
||||
'uuid': uuid,
|
||||
}));
|
||||
})
|
||||
.catch(errorHandler);
|
||||
}
|
||||
|
||||
function gotRemoteStream(event) {
|
||||
console.log('got remote stream');
|
||||
remoteVideo.srcObject = event.streams[0];
|
||||
}
|
||||
|
||||
function errorHandler(error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
// Taken from http://stackoverflow.com/a/105074/515584
|
||||
// Strictly speaking, it's not a real UUID, but it gets the job done here
|
||||
function createUUID() {
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
|
||||
}
|
||||
|
||||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
class Self {
|
||||
uuid = null
|
||||
connections = []
|
||||
|
||||
constructor() {
|
||||
this.connections.push(new Stream(this.id(), {audio: true, video: true}))
|
||||
}
|
||||
|
||||
start(isCaller) {
|
||||
for(var i of this.connections)
|
||||
i.start(isCaller)
|
||||
}
|
||||
|
||||
id() {
|
||||
this.uuid = this.get("uuid")
|
||||
if (!this.uuid) {
|
||||
s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)
|
||||
this.uuid = s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
|
||||
this.set("uuid", this.uuid)
|
||||
}
|
||||
return this.uuid
|
||||
}
|
||||
|
||||
get(k) {
|
||||
var name = k + "=";
|
||||
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 "";
|
||||
}
|
||||
|
||||
set(k, v) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + (1*24*60*60*1000));
|
||||
var expires = "expires="+ d.toUTCString();
|
||||
document.cookie = k + "=" + v + ";" + expires + ";path=/";
|
||||
}
|
||||
}
|
||||
|
||||
class Stream {
|
||||
serverConnection = null
|
||||
localVideo = null
|
||||
remoteVideo = null
|
||||
localStream = null
|
||||
id = null
|
||||
|
||||
constructor(id, config) {
|
||||
if (config.audio) {
|
||||
id += "a"
|
||||
}
|
||||
if (config.video) {
|
||||
id += "v"
|
||||
}
|
||||
this.id = id
|
||||
|
||||
this.serverConnection = new WebSocket('wss://' + window.location.hostname + '/abc?uuid=' + this.id)
|
||||
this.serverConnection.onmessage = (m) => this.gotMessageFromServer(m)
|
||||
|
||||
if(navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia(config)
|
||||
.then((s) => this.getUserMediaSuccess(s))
|
||||
.catch((e) => this.errorHandler(e))
|
||||
} else {
|
||||
throw new Exception('Your browser does not support getUserMedia API')
|
||||
}
|
||||
}
|
||||
|
||||
start(isCaller) {
|
||||
this.peerConnection = new RTCPeerConnection({
|
||||
'iceServers': [
|
||||
{'urls': 'stun:stun.stunprotocol.org:3478'},
|
||||
{'urls': 'stun:stun.l.google.com:19302'},
|
||||
]
|
||||
});
|
||||
|
||||
this.peerConnection.onicecandidate = (e) => this.gotIceCandidate(e);
|
||||
this.peerConnection.ontrack = (e) => this.gotRemoteStream(e);
|
||||
this.peerConnection.addStream(this.localStream);
|
||||
|
||||
if(isCaller) {
|
||||
this.peerConnection
|
||||
.createOffer({
|
||||
'iceRestart': true,
|
||||
'voiceActivityDetection': true,
|
||||
})
|
||||
.then((d) => this.createdDescription(d))
|
||||
.catch((m) => this.errorHandler(m));
|
||||
}
|
||||
}
|
||||
|
||||
getUserMediaSuccess(stream) {
|
||||
this.localVideo = document.getElementById('localVideo');
|
||||
this.remoteVideo = document.getElementById('remoteVideo');
|
||||
|
||||
this.localStream = stream;
|
||||
this.localVideo.srcObject = stream;
|
||||
}
|
||||
|
||||
gotMessageFromServer(message) {
|
||||
if(!this.peerConnection) start(false);
|
||||
|
||||
var signal = JSON.parse(message.data);
|
||||
|
||||
// Ignore messages from ourself
|
||||
if(signal.uuid == this.id) return;
|
||||
|
||||
if(signal.sdp) {
|
||||
this.peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() {
|
||||
// Only create answers in response to offers
|
||||
if(signal.sdp.type == 'offer') {
|
||||
this.peerConnection
|
||||
.createAnswer()
|
||||
.then((d) => this.createdDescription(d))
|
||||
.catch((e) => this.errorHandler(e));
|
||||
}
|
||||
}).catch((e) => this.errorHandler(e));
|
||||
} else if(signal.ice) {
|
||||
this.peerConnection
|
||||
.addIceCandidate(new RTCIceCandidate(signal.ice))
|
||||
.catch((e) => this.errorHandler(e));
|
||||
}
|
||||
}
|
||||
|
||||
gotIceCandidate(event) {
|
||||
if(event.candidate != null) {
|
||||
this.serverConnection
|
||||
.send(JSON.stringify({
|
||||
'ice': event.candidate,
|
||||
'uuid': this.id,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
createdDescription(description) {
|
||||
console.log('got description');
|
||||
|
||||
this.peerConnection
|
||||
.setLocalDescription(description)
|
||||
.then(() => {
|
||||
this.serverConnection.send(JSON.stringify({
|
||||
'sdp': this.peerConnection.localDescription,
|
||||
'uuid': this.id,
|
||||
}));
|
||||
})
|
||||
.catch((e) => this.errorHandler(e));
|
||||
}
|
||||
|
||||
gotRemoteStream(event) {
|
||||
console.log('got remote stream');
|
||||
this.remoteVideo.srcObject = event.streams[0];
|
||||
}
|
||||
|
||||
errorHandler(error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
var self
|
||||
|
||||
function pageReady() {
|
||||
self = new Self()
|
||||
}
|
||||
|
||||
function start(isCaller) {
|
||||
self.start(isCaller)
|
||||
}
|
||||
13
server.go
Normal file → Executable file
13
server.go
Normal file → Executable file
@@ -31,15 +31,20 @@ func New() *Server {
|
||||
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println("base", path.Base(r.URL.Path))
|
||||
log.Println("dir", path.Dir(r.URL.Path))
|
||||
if !s.Authorize(w, r) {
|
||||
return
|
||||
}
|
||||
if path.Ext(r.URL.Path) != "" {
|
||||
s.fs.ServeHTTP(w, r)
|
||||
} else if _, err := os.Stat(path.Join(config().GetString("d"), r.URL.Path[1:])); os.IsNotExist(err) {
|
||||
if path.Dir(r.URL.Path) == "/ws" {
|
||||
s.ws.ServeHTTP(w, r)
|
||||
} else {
|
||||
s.fs.ServeHTTP(w, r)
|
||||
r.URL.Path = "/" + path.Base(r.URL.Path)
|
||||
if _, err := os.Stat(path.Join(config().GetString("d"), path.Base(r.URL.Path))); os.IsNotExist(err) {
|
||||
r.URL.Path = "/"
|
||||
s.fs.ServeHTTP(w, r)
|
||||
} else {
|
||||
s.fs.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user