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 (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
ws websocket.Conn
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
type Pool struct {
|
type Pool struct {
|
||||||
conns *sync.Map //map[string]*websocket.Conn
|
lock *sync.RWMutex
|
||||||
|
conns *sync.Map //map[string]*Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPool() *Pool {
|
func NewPool() *Pool {
|
||||||
return &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 {
|
func (p *Pool) Broadcast(mt int, r io.Reader) error {
|
||||||
|
p.lock.RLock()
|
||||||
|
defer p.lock.RUnlock()
|
||||||
// io.MultiWriter exists but I like this
|
// io.MultiWriter exists but I like this
|
||||||
b, err := ioutil.ReadAll(r)
|
b, err := ioutil.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -28,9 +45,13 @@ func (p *Pool) Broadcast(mt int, r io.Reader) error {
|
|||||||
cnt := 0
|
cnt := 0
|
||||||
p.conns.Range(func(k, v interface{}) bool {
|
p.conns.Range(func(k, v interface{}) bool {
|
||||||
k = k.(string)
|
k = k.(string)
|
||||||
conn := v.(*websocket.Conn)
|
conn := v.(*Conn)
|
||||||
|
lock := &conn.lock
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
ws := &conn.ws
|
||||||
cnt += 1
|
cnt += 1
|
||||||
w, err := conn.NextWriter(mt)
|
w, err := ws.NextWriter(mt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.conns.Delete(k)
|
p.conns.Delete(k)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,25 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<!--
|
|
||||||
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
|
||||||
-->
|
|
||||||
<script src="webrtc.js"></script>
|
<script src="webrtc.js"></script>
|
||||||
<link rel="stylesheet" type="text/css" href="style.css">
|
<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>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -27,18 +10,16 @@
|
|||||||
<div>Audio<label class="switch"><input onclick="toggle('audio', this)" type="checkbox"><span class="slider round"></span></label></div>
|
<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>
|
<video id="preview" autoplay muted ></video>
|
||||||
<div id="remote">
|
<div id="remote"></div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<input type="button" id="start" onclick="start(true)" value="Start Video"></input>
|
<input type="button" id="start" onclick="start(true)" value="Start Video"></input>
|
||||||
|
|
||||||
|
<div id="log"></div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
pageReady();
|
pageReady();
|
||||||
</script>
|
</script>
|
||||||
|
</footer>
|
||||||
<div id="log">
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
21
public/style.css
Normal file → Executable file
21
public/style.css
Normal file → Executable file
@@ -60,3 +60,24 @@ input:checked + .slider:before {
|
|||||||
.slider.round:before {
|
.slider.round:before {
|
||||||
border-radius: 50%;
|
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() {
|
static getUUID() {
|
||||||
var uuid = Config.getCookie('uuid');
|
var uuid = Config.getCookie('uuid');
|
||||||
if (!uuid) {
|
if (!uuid) {
|
||||||
uuid = Config.createUUID();
|
Config.newUUID();
|
||||||
Config.setCookie('uuid', uuid);
|
|
||||||
}
|
}
|
||||||
|
Log.info("uuid", uuid);
|
||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static newUUID() {
|
||||||
|
var uuid = Config.createUUID();
|
||||||
|
Config.setCookie('uuid', uuid);
|
||||||
|
}
|
||||||
|
|
||||||
static createUUID() {
|
static createUUID() {
|
||||||
return "" + new Date();
|
|
||||||
function s4() {
|
function s4() {
|
||||||
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
|
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
|
||||||
}
|
}
|
||||||
@@ -52,7 +56,7 @@ Config.iceConfig = {
|
|||||||
'iceTransportPolicy': 'relay',
|
'iceTransportPolicy': 'relay',
|
||||||
};
|
};
|
||||||
|
|
||||||
class View {
|
class Log {
|
||||||
static write(foo, color) {
|
static write(foo, color) {
|
||||||
var msg = [].slice.call(arguments[2]).join(" ");
|
var msg = [].slice.call(arguments[2]).join(" ");
|
||||||
foo(msg);
|
foo(msg);
|
||||||
@@ -61,32 +65,19 @@ class View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static info() {
|
static info() {
|
||||||
View.write(rconsole.info, "gray", arguments);
|
Log.write(rconsole.info, "gray", arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
static warn() {
|
static warn() {
|
||||||
View.write(rconsole.warn, "orange", arguments);
|
Log.write(rconsole.warn, "orange", arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
static log() {
|
static log() {
|
||||||
View.write(rconsole.log, "black", arguments);
|
Log.write(rconsole.log, "black", arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
static error() {
|
static error() {
|
||||||
View.write(rconsole.error, "red", arguments);
|
Log.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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,62 +88,61 @@ class Preview {
|
|||||||
this.element = document.getElementById(id);
|
this.element = document.getElementById(id);
|
||||||
this.element.srcObject = stream;
|
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() {
|
destructor() {
|
||||||
|
Log.log("destructor called");
|
||||||
this.element.remove();
|
this.element.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Server {
|
class RTC {
|
||||||
server = null;
|
conn = null;
|
||||||
|
|
||||||
constructor(address, cb) {
|
|
||||||
Server.server = new WebSocket(address);
|
|
||||||
Server.server.onmessage = cb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Peer {
|
|
||||||
peer = null;
|
|
||||||
streams = {};
|
streams = {};
|
||||||
|
// TODO issue: singleton for all feeds ever;; 1 peer = 1 server conn
|
||||||
constructor(stream) {
|
constructor(stream) {
|
||||||
this.peer = new RTCPeerConnection(Config.iceConfig);
|
this.conn = new RTCPeerConnection(Config.iceConfig);
|
||||||
this.peer.onicecandidate = (event) => {
|
this.conn.onicecandidate = (event) => {
|
||||||
if(event.candidate != null) {
|
if (event.candidate != null) {
|
||||||
Server.server.send(JSON.stringify({'ice': event.candidate, 'uuid': Config.getUUID()}));
|
WS.ws.send(JSON.stringify({'ice': event.candidate, 'uuid': Config.getUUID()}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.peer.ontrack = (event) => {
|
this.conn.ontrack = (event) => {
|
||||||
event.streams.forEach((stream) => {
|
event.streams.forEach((stream) => {
|
||||||
this.streams[stream.id] = new Remote(stream.id, stream);
|
this.streams[stream.id] = new Remote(stream.id, stream);
|
||||||
});
|
});
|
||||||
View.log("/ontrack:", this.streams);
|
|
||||||
};
|
};
|
||||||
this.peer.onremovestream = (event) => {
|
this.conn.addStream(stream);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
offer(cb) {
|
offer(cb) {
|
||||||
this.peer
|
this.conn
|
||||||
.createOffer({
|
.createOffer({
|
||||||
'iceRestart': true,
|
'iceRestart': true,
|
||||||
'voiceActivityDetection': true,
|
'voiceActivityDetection': true,
|
||||||
})
|
})
|
||||||
.then(cb)
|
.then(cb)
|
||||||
.catch(View.error);
|
.catch(Log.error);
|
||||||
}
|
|
||||||
|
|
||||||
destructor() {
|
|
||||||
this.peer.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,48 +152,51 @@ class Entropy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static start(isCaller) {
|
static start(isCaller) {
|
||||||
Entropy.peer = new Peer(Entropy.local.stream);
|
Entropy.rtc = new RTC(Entropy.local.stream);
|
||||||
if(isCaller) {
|
if (isCaller) {
|
||||||
Entropy.peer.offer(Entropy.createdDescription);
|
Entropy.rtc.offer(Entropy.createdDescription);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static gotMessageFromServer(message) {
|
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);
|
var signal = JSON.parse(message.data);
|
||||||
|
|
||||||
// Ignore messages from ourself
|
// Ignore messages from ourself
|
||||||
if(signal.uuid == Config.getUUID()) return;
|
if (signal.uuid == Config.getUUID()) return;
|
||||||
|
|
||||||
if(signal.sdp) {
|
if (signal.joined)
|
||||||
Entropy.peer.peer.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() {
|
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
|
// Only create answers in response to offers
|
||||||
if(signal.sdp.type == 'offer') {
|
if (signal.sdp.type == 'offer') {
|
||||||
Entropy.peer.peer.createAnswer().then(Entropy.createdDescription).catch(View.error);
|
Entropy.rtc.conn.createAnswer().then(Entropy.createdDescription).catch(Log.error);
|
||||||
}
|
}
|
||||||
}).catch(View.error);
|
}).catch(Log.error);
|
||||||
} else if(signal.ice) {
|
} else if (signal.ice) {
|
||||||
Entropy.peer.peer.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(View.error);
|
Entropy.rtc.conn.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(Log.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static createdDescription(description) {
|
static createdDescription(description) {
|
||||||
View.log('got description');
|
Log.log('got description');
|
||||||
|
|
||||||
Entropy.peer.peer
|
Entropy.rtc.conn
|
||||||
.setLocalDescription(description)
|
.setLocalDescription(description)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
Server.server.send(JSON.stringify({
|
WS.ws.send(JSON.stringify({
|
||||||
'sdp': Entropy.peer.peer.localDescription,
|
'sdp': Entropy.rtc.conn.localDescription,
|
||||||
'uuid': Config.getUUID(),
|
'uuid': Config.getUUID(),
|
||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
.catch(View.error);
|
.catch(Log.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Entropy.local = null;
|
Entropy.local = null;
|
||||||
Entropy.peer = null;
|
Entropy.rtc = null;
|
||||||
|
|
||||||
class Local {
|
class Local {
|
||||||
stream = null;
|
stream = null;
|
||||||
@@ -213,7 +206,7 @@ class Local {
|
|||||||
case "audio": return new Audio();
|
case "audio": return new Audio();
|
||||||
case "video": return new Video();
|
case "video": return new Video();
|
||||||
case "text": return new Text();
|
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",
|
video: this.gettype() == "video",
|
||||||
audio: this.gettype() == "audio",
|
audio: this.gettype() == "audio",
|
||||||
};
|
};
|
||||||
if(!navigator.mediaDevices.getUserMedia) {
|
if (!navigator.mediaDevices.getUserMedia) {
|
||||||
View.error('Your browser does not support getUserMedia API');
|
Log.error('Your browser does not support getUserMedia API');
|
||||||
}
|
}
|
||||||
navigator.mediaDevices.getUserMedia(constraints)
|
navigator.mediaDevices.getUserMedia(constraints)
|
||||||
.then((stream) => {
|
.then((stream) => {
|
||||||
@@ -232,11 +225,11 @@ class Local {
|
|||||||
}
|
}
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
})
|
})
|
||||||
.catch(View.error);
|
.catch(Log.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
gettype() {
|
gettype() {
|
||||||
View.error("not impl");
|
Log.error("not impl");
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {}
|
start() {}
|
||||||
@@ -263,17 +256,17 @@ var streams = {}
|
|||||||
var rconsole = console;
|
var rconsole = console;
|
||||||
var console = {}
|
var console = {}
|
||||||
for (var i in ["log", "info", "warn", "error"]) {
|
for (var i in ["log", "info", "warn", "error"]) {
|
||||||
console[i] = View[i];
|
console[i] = Log[i];
|
||||||
}
|
}
|
||||||
window.console = console;
|
window.console = console;
|
||||||
|
|
||||||
function pageReady() {
|
function pageReady() {
|
||||||
Config.getUUID();
|
//Config.newUUID();
|
||||||
var host = window.location.hostname;
|
var host = window.location.hostname;
|
||||||
if (window.location.port) {
|
if (window.location.port) {
|
||||||
host += ":" + 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();
|
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) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Println("base", path.Base(r.URL.Path))
|
log.Println("base", path.Base(r.URL.Path))
|
||||||
|
log.Println("dir", path.Dir(r.URL.Path))
|
||||||
if !s.Authorize(w, r) {
|
if !s.Authorize(w, r) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if path.Ext(r.URL.Path) != "" {
|
if path.Dir(r.URL.Path) == "/ws" {
|
||||||
s.fs.ServeHTTP(w, r)
|
|
||||||
} else if _, err := os.Stat(path.Join(config().GetString("d"), r.URL.Path[1:])); os.IsNotExist(err) {
|
|
||||||
s.ws.ServeHTTP(w, r)
|
s.ws.ServeHTTP(w, r)
|
||||||
} else {
|
} 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
ws.go
Normal file → Executable file
2
ws.go
Normal file → Executable file
@@ -49,7 +49,7 @@ func (ws *WS) serveHTTP(w http.ResponseWriter, r *http.Request) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pool.conns.Store(id, conn)
|
pool.Push(id, conn) // conns.Store(id, conn)
|
||||||
for {
|
for {
|
||||||
mt, reader, err := conn.NextReader()
|
mt, reader, err := conn.NextReader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user