added laplace binary

This commit is contained in:
2021-07-18 13:43:37 +04:00
parent 82dd278eec
commit 58cc8c5442
9 changed files with 877 additions and 0 deletions

180
files/main.html Normal file
View File

@@ -0,0 +1,180 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Laplace</title>
<link rel="stylesheet" href="/static/bootstrap.min.css">
<link rel="stylesheet" href="/static/main.css?v=0.0.5">
</head>
<body>
<nav class="navbar">
<a class="navbar-brand text-dark" href="/">Laplace</a>
<span class="navbar-brand header-room" id="room-text"></span>
</nav>
<div class="container">
<div class="panel" id="panel">
<p class="help-text">
<b>Laplace</b> is an open-source project to enable screen sharing directly via browser.
Made possible using WebRTC for low latency peer-to-peer connections,
and websocket implemented in golang for WebRTC signaling.
<a href="https://github.com/adamyordan/laplace">Read more</a>
</p>
<div class="separator"></div>
<h4>Start sharing your screen</h4>
<p class="help-text">
Click the button below to create a new streaming room,
where you can share your screen with your peers.
A RoomID will be generated, and you can share the RoomID to your peers.
</p>
<button type="button" class="btn btn-dark btn-block" id="btnStream">Start sharing</button>
<div class="separator"></div>
<h4>Join streaming room</h4>
<p class="help-text">
Enter a RoomID to join the streaming room.
You can get the RoomID from your peer that is doing a screen sharing.
</p>
<form id="joinForm">
<div class="form-group">
<input id="inputRoomID" type="text" class="form-control form-control-lg text-center" placeholder="Enter room ID" required>
</div>
<h4>Connect to Barrier KVM (Beta)</h4>
<p class="help-text">
This is a feature is still on beta testing. This feature will ensure that you can share the keyboard
and mouse of the server machine.
<br>
<span class="text-success font-weight-bold"> Note: This only works if the laplace server has barrierc installed and the room name you are connecting
too belongs to the server.</span>
</p>
<h6>Host Name: <span id="hostname"> Not detected </span></h6>
<div class="form-group">
<input id="barrierIP" type="text" class="form-control form-control-lg text-center" placeholder="IPV6 or IPV4 address">
</div>
<button type="submit" class="btn btn-dark btn-block" value="submit">Join</button>
</form>
</div>
</div>
<div class="container container-small" id="stream-serve-page-ui">
<div class="config-panel">
<div class="row">
<div class="col">
<h4>Screen sharing configuration</h4>
<p class="help-text">
For most people, using the default "balanced" preset is enough,
then click the "Start sharing" button to start.
For the curious, You may also choose configuration preset that will affect the performance and quality of the streaming.
For advance user, you can modify the options inside the textbox directly, please note that it may incur errors.
</p>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="form-group">
<label for=optionPreset>Preset</label>
<br>
<select name="optionPreset" id="inputOptionPreset" class="form-control"></select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for=displayMediaOption>DisplayMedia option</label>
<textarea name="displayMediaOption" id="inputDisplayMediaOption" cols="30" rows="5" class="form-control"></textarea>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for=RTPPeerConnection>RTPPeerConnection option</label>
<textarea name="RTPPeerConnectionOption" id="inputRTPPeerConnectionOption" cols="30" rows="5" class="form-control"></textarea>
</div>
</div>
</div>
<hr>
<button type="button" class="btn btn-dark btn-block-xs-only" id="btnStartStream">Start stream</button>
<span>&nbsp;</span>
<button type="button" class="btn btn-outline-dark btn-block-xs-only" id="btnStream" onclick="leaveRoom()">Cancel</button>
</div>
<br><br>
<p class="help-text">
<b>Some notes</b><br>
After clicking the "Start stream" button, if your device is supported, you may be asked to choose which screen you want to share.
<br>
As of now, mobile devices (Android & iOS) are usually not supported for screen sharing.
<br>
Sharing system audio is tested to be working only with latest Google Chrome running in Windows OS.
<br>
Remember to tick the "Share audio" checkbox.
<br>
In case the streaming does not work, reload the page, the WebRTC maybe unstable.
</p>
</div>
<div class="container-fluid" id="video-container">
<div id="video-wrapper">
<video id="mainVideo" autoplay playsinline controls>
</video>
</div>
</div>
<br><br>
<div class="container container-small" id="stream-page-ui">
<div class="row">
<div class="col-md-9">
<h4>How to Join this Room</h4>
<p class="help-text">
You can share the RoomID to your peers (it is on the top-right position on this page).
They can also join with the following link or QRCode:
</p>
<a href="#" id="join-link" target="_blank"></a>
<br><br>
</div>
<div class="col-md-3">
<div id="qrcode"></div>
</div>
</div>
<hr>
<div>
<h6>Status</h6>
<table class="table table-borderless table-sm table-meta">
<tr>
<td>Number of connections:</td>
<td id="statusNumConn">0</td>
</tr>
<tr>
<td>Connected peers:</td>
<td id="statusPeers"></td>
</tr>
<tr>
<td>Ping:</td>
<td id="statusPing">0 ms</td>
</tr>
</table>
<button type="button" class="btn btn-dark btn-block-xs-only" id="btnStream" onclick="leaveRoom()">Leave Room</button>
</div>
<hr>
<pre id="output"></pre>
</div>
<div class="container">
<hr>
<div class="text-center small" id="footer">
<span>Laplace Project by Adam Jordan.</span>
<a href="https://github.com/adamyordan/laplace">Source Code</a>
</div>
</div>
<script src="/static/qrcode.min.js"></script>
<script src="/static/main.js?v=0.0.5"></script>
</body>
</html>

9
files/server.crt Normal file
View File

@@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIBRjCBzQIJAO9zOPn7NhXdMAoGCCqGSM49BAMCMA0xCzAJBgNVBAYTAlNHMB4X
DTIwMDMyMjA3MTAwNFoXDTMwMDMyMDA3MTAwNFowDTELMAkGA1UEBhMCU0cwdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAARK0vXzgCXCkFXMhkNyW3bCPmVkpdmzhmKvh/5l
zVFXBX20bD5Nd/Ja6bmZ5tfpabAAJv9U9i+xh5zIMyENIu/WoYZqjbIfffYK2xkA
VoZLQraKM8yWCGJE7k7cw+cUYaEwCgYIKoZIzj0EAwIDaAAwZQIwUwmVA+sCyJ8d
tfxK/Yh7BMCduSLw1dPqyr2lF90b6OL6oetKa/xtTcKLPomgHEFuAjEA9Pv23QD6
xqwwedsfrxNA3Z8WDWA+rWOEmDcsxtrTSuW1t1HsCAqzFLxQn9mIoX+b
-----END CERTIFICATE-----

9
files/server.key Normal file
View File

@@ -0,0 +1,9 @@
-----BEGIN EC PARAMETERS-----
BgUrgQQAIg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDDVijLZH3+6qNVMntFz5xTbqiyp8IBqqS1+TidkLS0ZVqX4u6WTRhst
1yP67FyQ9HCgBwYFK4EEACKhZANiAARK0vXzgCXCkFXMhkNyW3bCPmVkpdmzhmKv
h/5lzVFXBX20bD5Nd/Ja6bmZ5tfpabAAJv9U9i+xh5zIMyENIu/WoYZqjbIfffYK
2xkAVoZLQraKM8yWCGJE7k7cw+cUYaE=
-----END EC PRIVATE KEY-----

7
files/static/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

105
files/static/main.css Normal file
View File

@@ -0,0 +1,105 @@
#video-wrapper {
text-align: center;
padding-top: 10px;
padding-bottom: 10px;
}
#video-wrapper video {
max-width: 90vw;
max-height: 70vh;
box-shadow: 0 10px 25px 0 rgba(0, 0, 0, 0.50);
outline: none;
}
#stream-page-ui {
display: none;
}
#stream-serve-page-ui {
display: none;
}
#video-container {
display: none;
}
#output {
padding: 20px 0;
overflow: scroll;
font-size: 10px;
margin: 0;
max-height: 400px;
}
#output::-webkit-scrollbar {
width: 0;
background: transparent;
}
#qrcode {
width: 128px;
margin: 0 auto;
}
#footer {
margin-bottom: 20px;
}
.header-room {
font-size: 1rem;
}
.panel {
max-width: 400px;
padding: 20px;
margin: 20px auto;
}
.panel .separator {
height: 50px;
}
.help-text {
font-size: 0.8rem;
}
.container-small {
max-width: 760px;
}
.config-panel {
border: 1px solid #e2e3e5;
padding: 20px;
border-radius: 10px;
margin-top: 40px;
margin-bottom: 40px;
}
.config-panel label {
font-size: 0.8rem;
margin: 2px;
}
.config-panel select {
font-size: 0.8rem;
}
.config-panel input, .config-panel textarea {
font-size: 0.8rem;
font-family: Consolas, monospace;
}
.table-meta {
font-size: 12px;
}
.table-meta td:first-child {
width: 150px;
}
@media (max-width: 576px) {
.btn-block-xs-only {
display: block;
width: 100%;
}
}

565
files/static/main.js Normal file
View File

@@ -0,0 +1,565 @@
"use strict";
// todo: deprecate
const iceConfig = {
iceServers: [{
urls: [
'stun:stun1.l.google.com:19302',
'stun:stun2.l.google.com:19302',
],
}],
iceCandidatePoolSize: 10,
};
const displayMediaOptions = {
noConstraint: {
video: true,
audio: true,
},
v720p30: {
video: {
height: 720,
frameRate: 30,
},
audio: true,
},
v480p60: {
video: {
height: 480,
frameRate: 60,
},
audio: true,
},
};
const rtpPeerConnectionOptions = {
stunGoogle: {
iceServers: [{
urls: [
'stun:stun1.l.google.com:19302',
'stun:stun2.l.google.com:19302',
],
}],
iceCandidatePoolSize: 10,
},
noStun: {
iceServers: [],
}
};
const preset = {
balanced: {
displayMediaOption: displayMediaOptions.v720p30,
rtpPeerConnectionOption: rtpPeerConnectionOptions.stunGoogle,
},
performance: {
displayMediaOption: displayMediaOptions.v480p60,
rtpPeerConnectionOption: rtpPeerConnectionOptions.stunGoogle,
},
highQuality: {
displayMediaOption: displayMediaOptions.noConstraint,
rtpPeerConnectionOption: rtpPeerConnectionOptions.stunGoogle,
},
balancedLanOnly: {
displayMediaOption: displayMediaOptions.v720p30,
rtpPeerConnectionOption: rtpPeerConnectionOptions.noStun,
},
performanceLanOnly: {
displayMediaOption: displayMediaOptions.v480p60,
rtpPeerConnectionOption: rtpPeerConnectionOptions.noStun,
},
highQualityLanOnly: {
displayMediaOption: displayMediaOptions.noConstraint,
rtpPeerConnectionOption: rtpPeerConnectionOptions.noStun,
},
};
const LaplaceVar = {
ui: {},
};
function avg(arr) {
return (arr.reduce((a, b) => a + b, 0) / arr.length) | 0
}
function print(s) {
LaplaceVar.ui.output.innerHTML += s + '\n';
}
function getBaseUrl() {
return `${window.location.protocol}//${window.location.host}`
}
function getStreamUrl() {
return `${getBaseUrl()}/?stream=1`;
}
function getJoinUrl(roomID,BarrierIP="") {
if(BarrierIP == ""){
return `${getBaseUrl()}/?id=${roomID}`;
}
return `${getBaseUrl()}/?id=${roomID}&barrierip=${BarrierIP}`;
}
function updateRoomUI() {
LaplaceVar.ui.panel.style.display = 'none';
LaplaceVar.ui.videoContainer.style.display = 'block';
LaplaceVar.ui.streamPageUI.style.display = 'block';
if (LaplaceVar.roomID) {
const joinUrl = getJoinUrl(LaplaceVar.roomID);
LaplaceVar.ui.qrcodeObj = new QRCode(LaplaceVar.ui.qrcode, {
text: joinUrl,
width: 128,
height: 128,
});
LaplaceVar.ui.roomText.innerHTML = '<b>RoomID:</b> #' + LaplaceVar.roomID;
LaplaceVar.ui.joinLinkText.innerHTML = joinUrl;
LaplaceVar.ui.joinLinkText.href = joinUrl;
}
}
function initUI() {
LaplaceVar.ui.btnStream = document.getElementById('btnStream');
LaplaceVar.ui.btnStartStream = document.getElementById('btnStartStream');
LaplaceVar.ui.inputRoomID = document.getElementById('inputRoomID');
LaplaceVar.ui.inputDisplayMediaOption = document.getElementById('inputDisplayMediaOption');
LaplaceVar.ui.inputRTPPeerConnectionOption = document.getElementById('inputRTPPeerConnectionOption');
LaplaceVar.ui.joinLinkText = document.getElementById("join-link");
LaplaceVar.ui.joinForm = document.getElementById('joinForm');
LaplaceVar.ui.output = document.getElementById('output');
LaplaceVar.ui.qrcode = document.getElementById("qrcode");
LaplaceVar.ui.panel = document.getElementById('panel');
LaplaceVar.ui.roomText = document.getElementById('room-text');
LaplaceVar.ui.statusNumConn = document.getElementById('statusNumConn');
LaplaceVar.ui.statusPeers = document.getElementById('statusPeers');
LaplaceVar.ui.statusPing = document.getElementById('statusPing');
LaplaceVar.ui.selectOptionPreset = document.getElementById('inputOptionPreset');
LaplaceVar.ui.streamPageUI = document.getElementById('stream-page-ui');
LaplaceVar.ui.streamServePageUI = document.getElementById('stream-serve-page-ui');
LaplaceVar.ui.video = document.getElementById('mainVideo');
LaplaceVar.ui.videoContainer = document.getElementById('video-container');
LaplaceVar.ui.barrierIP = document.getElementById('barrierIP');
LaplaceVar.ui.barrierhostname = document.getElementById('hostname');
LaplaceVar.ui.joinForm.onsubmit = async e => {
e.preventDefault();
LaplaceVar.roomID = LaplaceVar.ui.inputRoomID.value;
LaplaceVar.barrierIP = LaplaceVar.ui.barrierIP.value;
// Check if the ip address for barrier is given
if(LaplaceVar.barrierIP == "") {
window.history.pushState('', '', getJoinUrl(LaplaceVar.roomID));
}
else {
window.history.pushState('', '', getJoinUrl(LaplaceVar.roomID,LaplaceVar.barrierIP));
}
await doJoin(LaplaceVar.roomID,LaplaceVar.barrierIP);
};
LaplaceVar.ui.btnStream.onclick = async () => {
window.history.pushState('', '', getStreamUrl());
await doStream();
};
LaplaceVar.ui.btnStartStream.onclick = () => {
LaplaceVar.ui.streamServePageUI.style.display = 'none';
const mediaOption = JSON.parse(LaplaceVar.ui.inputDisplayMediaOption.value);
const pcOption = JSON.parse(LaplaceVar.ui.inputRTPPeerConnectionOption.value);
return startStream(mediaOption, pcOption);
};
for (const presetName of Object.keys(preset)) {
const optionElement = document.createElement('option');
optionElement.appendChild(document.createTextNode(presetName));
optionElement.value = presetName;
LaplaceVar.ui.selectOptionPreset.appendChild(optionElement);
}
LaplaceVar.ui.selectOptionPreset.onchange = () => {
const v = LaplaceVar.ui.selectOptionPreset.value;
if (preset[v] != null) {
LaplaceVar.ui.inputDisplayMediaOption.value = JSON.stringify(preset[v].displayMediaOption, null, 1);
LaplaceVar.ui.inputRTPPeerConnectionOption.value = JSON.stringify(preset[v].rtpPeerConnectionOption, null, 1);
}
};
const defaultPresetValue = Object.keys(preset)[0];
LaplaceVar.ui.inputDisplayMediaOption.value = JSON.stringify(preset[defaultPresetValue].displayMediaOption, null, 1);
LaplaceVar.ui.inputRTPPeerConnectionOption.value = JSON.stringify(preset[defaultPresetValue].rtpPeerConnectionOption, null, 1);
//getting server hostname
getServerHostName()
print("Logs:");
print("[+] Page loaded");
}
// Gets host name of the server
// This is so that user can
// add the host name to connect
// to laplace
function getServerHostName() {
// Source https://stackoverflow.com/questions/247483/http-get-request-in-javascript
var xmlHttp = new XMLHttpRequest()
xmlHttp.open( "GET", getHttpUrl() + "/hostname", false ); // false for synchronous request
xmlHttp.send( null );
LaplaceVar.ui.barrierhostname.innerHTML = xmlHttp.responseText
}
function updateStatusUIStream() {
LaplaceVar.status.peers = Object.keys(LaplaceVar.pcs).map(s => s.split('$')[1]);
LaplaceVar.status.numConn = LaplaceVar.status.peers.length;
LaplaceVar.ui.statusPeers.innerHTML = LaplaceVar.status.peers
.map(s => `${s} (${LaplaceVar.pings[LaplaceVar.roomID + '$' + s]} ms)`).join(', ');
LaplaceVar.ui.statusNumConn.innerHTML = LaplaceVar.status.numConn;
}
function updateStatusUIJoin() {
LaplaceVar.ui.statusNumConn.innerHTML = LaplaceVar.status.numConn;
LaplaceVar.ui.statusPeers.innerHTML = LaplaceVar.status.peers.map(s => LaplaceVar.sessionID.endsWith(s) ? s + ' (you)' : s).join(', ');
}
function getWebsocketUrl() {
if (window.location.protocol === "https:") {
return `wss://${window.location.host}`
} else {
return `ws://${window.location.host}`
}
}
function getHttpUrl() {
//if (window.location.protocol === "https:") {
return `${window.location.href}`
// } else {
// return `${window.location.href}`
// }
}
async function newRoom(rID) {
print("[+] Get room ID: " + rID);
LaplaceVar.roomID = rID;
updateRoomUI();
}
async function newSessionStream(sessionID, pcOption) {
print('[+] New session: ' + sessionID);
LaplaceVar.pcs[sessionID] = new RTCPeerConnection(pcOption);
LaplaceVar.pcs[sessionID].onicecandidate = e => {
print('[+] Debug onicecandidate: ' + JSON.stringify(e.candidate));
if (!e.candidate) {
print('[+] Debug onicecandidate: got final candidate!');
return;
}
print('[+] Send addCallerIceCandidate to websocket: ' + JSON.stringify(e.candidate));
LaplaceVar.socket.send(JSON.stringify({
Type: "addCallerIceCandidate",
SessionID: sessionID,
Value: JSON.stringify(e.candidate),
}))
};
LaplaceVar.pcs[sessionID].oniceconnectionstatechange = () => {
print('[+] Debug oniceconnectionstatechange ' + LaplaceVar.pcs[sessionID].iceConnectionState);
if (LaplaceVar.pcs[sessionID].iceConnectionState === 'disconnected') {
print("[-] Disconnected with a Peer " + sessionID);
LaplaceVar.pcs[sessionID].close();
delete LaplaceVar.pcs[sessionID];
delete LaplaceVar.dataChannels[sessionID];
delete LaplaceVar.pings[sessionID];
delete LaplaceVar.pingHistories[sessionID];
updateStatusUIStream();
}
};
updateStatusUIStream();
LaplaceVar.dataChannels[sessionID] = LaplaceVar.pcs[sessionID].createDataChannel('ping');
LaplaceVar.dataChannels[sessionID].addEventListener('open', () => {
print('[+] Start ping interval: ', sessionID);
LaplaceVar.pingHistories[sessionID] = [];
LaplaceVar.pingIntervals[sessionID] = window.setInterval(() => {
const now = new Date().getTime();
LaplaceVar.dataChannels[sessionID].send('ping ' + now.toString());
LaplaceVar.dataChannels[sessionID].send('status ' + JSON.stringify(LaplaceVar.status));
}, 5000);
});
LaplaceVar.dataChannels[sessionID].addEventListener('close', () => {
print('[+] Clear ping interval: ', sessionID);
window.clearInterval(LaplaceVar.pingIntervals[sessionID]);
});
LaplaceVar.dataChannels[sessionID].addEventListener('message', e => {
if (e.data.startsWith('ping')) {
LaplaceVar.dataChannels[sessionID].send('pong' + e.data.slice(4));
} else if (e.data.startsWith('pong')) {
const now = new Date().getTime();
const then = parseInt(e.data.slice(4));
if (!isNaN(then)) {
LaplaceVar.pingHistories[sessionID].push(now - then);
if (LaplaceVar.pingHistories[sessionID].length > 3) {
LaplaceVar.pingHistories[sessionID].shift()
}
LaplaceVar.pings[sessionID] = avg(LaplaceVar.pingHistories[sessionID]);
updateStatusUIStream();
}
}
});
LaplaceVar.mediaStream.getTracks().forEach(track => {
LaplaceVar.pcs[sessionID].addTrack(track, LaplaceVar.mediaStream);
});
print('[+] Creating offer');
const offer = await LaplaceVar.pcs[sessionID].createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true,
});
await LaplaceVar.pcs[sessionID].setLocalDescription(offer);
print('[+] Send offer to websocket: ' + JSON.stringify(offer));
LaplaceVar.socket.send(JSON.stringify({
Type: "gotOffer",
SessionID: sessionID,
Value: JSON.stringify(offer),
}));
}
async function addCalleeIceCandidate(sessionID, v) {
print('[+] Debug addCalleeIceCandidate ' + sessionID + ' ' + JSON.stringify(v));
return LaplaceVar.pcs[sessionID].addIceCandidate(v);
}
async function gotAnswer(sessionID, v) {
print('[+] Debug gotAnswer ' + sessionID + ' ' + JSON.stringify(v));
return LaplaceVar.pcs[sessionID].setRemoteDescription(new RTCSessionDescription(v));
}
async function doStream() {
LaplaceVar.ui.panel.style.display = 'none';
LaplaceVar.ui.streamServePageUI.style.display = 'block';
}
async function startStream(displayMediaOption, pcOption) {
LaplaceVar.pcs = {}; // contains RTCPeerConnections
LaplaceVar.dataChannels = {};
LaplaceVar.pings = {};
LaplaceVar.pingHistories = {};
LaplaceVar.pingIntervals = {};
LaplaceVar.status = {
numConn: 0,
peers: [],
};
updateRoomUI();
print('[+] Initiate media: capture display media');
try {
// noinspection JSUnresolvedFunction
LaplaceVar.mediaStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOption);
} catch {
alert('Streaming from this device is not supported. \n\nGoogle reference: getDisplayMedia');
leaveRoom()
}
LaplaceVar.ui.video.srcObject = LaplaceVar.mediaStream;
LaplaceVar.ui.video.muted = true; // prevent duplicate sound played
print('[+] Initiate websocket');
LaplaceVar.socket = new WebSocket(getWebsocketUrl() + '/ws_serve');
LaplaceVar.socket.onerror = () => {
alert('WebSocket error');
leaveRoom();
};
LaplaceVar.socket.onopen = async function () {
print("[+] Connected to websocket");
};
LaplaceVar.socket.onmessage = async function (e) {
try {
const jsonData = JSON.parse(e.data);
if (jsonData.Type !== 'beat') {
print("[+] Received websocket message: " + JSON.stringify(e.data));
}
if (jsonData.Type === "newRoom") {
await newRoom(jsonData.Value);
} else if (jsonData.Type === "newSession") {
await newSessionStream(jsonData.SessionID, pcOption);
} else if (jsonData.Type === "addCalleeIceCandidate") {
await addCalleeIceCandidate(jsonData.SessionID, JSON.parse(jsonData.Value));
} else if (jsonData.Type === "gotAnswer") {
await gotAnswer(jsonData.SessionID, JSON.parse(jsonData.Value));
}
} catch (e) {
print("[!] ERROR: " + e);
console.error(e);
}
};
}
async function newSessionJoin(sID) {
print('[+] New session: ' + sID);
LaplaceVar.sessionID = sID;
LaplaceVar.pc = new RTCPeerConnection(iceConfig);
LaplaceVar.pc.onicecandidate = e => {
print('[+] Debug onicecandidate: ' + JSON.stringify(e.candidate));
if (!e.candidate) {
print('[+] Debug onicecandidate: got final candidate!');
return;
}
print('[+] Send addCalleeIceCandidate to websocket: ' + JSON.stringify(e.candidate));
LaplaceVar.socket.send(JSON.stringify({
Type: "addCalleeIceCandidate",
SessionID: LaplaceVar.sessionID,
Value: JSON.stringify(e.candidate),
}))
};
LaplaceVar.pc.oniceconnectionstatechange = () => {
print('[+] pc.oniceconnectionstatechange ' + LaplaceVar.pc.iceConnectionState);
if (LaplaceVar.pc.iceConnectionState === 'disconnected') {
print("[-] Disconnected with Peer");
LaplaceVar.pc.close();
LaplaceVar.pc = null;
}
};
LaplaceVar.pc.ontrack = event => {
// print('[+] Debug pc.ontrack: ' + JSON.stringify(event.track));
// /* does not work on safari */
// event.streams[0].getTracks().forEach(track => {
// print('[+] Debug addTrack ' + JSON.stringify(track));
// LaplaceVar.mediaStream.addTrack(track)
// });
LaplaceVar.mediaStream.addTrack(event.track);
LaplaceVar.ui.video.srcObject = LaplaceVar.mediaStream;
try {
LaplaceVar.ui.video.play();
} catch {}
};
LaplaceVar.pc.addEventListener('datachannel', e => {
LaplaceVar.dataChannel = e.channel;
LaplaceVar.dataChannel.addEventListener('open', () => {
print('[+] Start ping interval');
LaplaceVar.pingHistory = [];
LaplaceVar.pingInterval = window.setInterval(() => {
const now = new Date().getTime();
LaplaceVar.dataChannel.send('ping ' + now.toString());
}, 1000);
});
LaplaceVar.dataChannel.addEventListener('close', () => {
print('[+] Clear ping interval');
clearInterval(LaplaceVar.pingInterval);
});
LaplaceVar.dataChannel.addEventListener('message', e => {
if (e.data.startsWith('ping')) {
LaplaceVar.dataChannel.send('pong' + e.data.slice(4));
} else if (e.data.startsWith('pong')) {
const now = new Date().getTime();
const then = parseInt(e.data.slice(4));
if (!isNaN(then)) {
LaplaceVar.pingHistory.push(now - then);
if (LaplaceVar.pingHistory.length > 3) {
LaplaceVar.pingHistory.shift()
}
LaplaceVar.ping = avg(LaplaceVar.pingHistory);
LaplaceVar.ui.statusPing.innerHTML = LaplaceVar.ping + ' ms';
}
} else if (e.data.startsWith('status')) {
LaplaceVar.status = JSON.parse(e.data.slice(7));
updateStatusUIJoin();
}
});
});
}
async function addCallerIceCandidate(sID, v) {
print('[+] Debug addCallerIceCandidate ' + sID + ' ' + JSON.stringify(v));
if (LaplaceVar.sessionID !== sID) return;
return LaplaceVar.pc.addIceCandidate(v);
}
async function gotOffer(sID, v) {
print('[+] Debug gotOffer ' + sID + ' ' + JSON.stringify(v));
if (LaplaceVar.sessionID !== sID) return;
await LaplaceVar.pc.setRemoteDescription(new RTCSessionDescription(v));
print('[+] Create answer');
const answer = await LaplaceVar.pc.createAnswer();
await LaplaceVar.pc.setLocalDescription(answer);
print('[+] Send answer to websocket: ' + JSON.stringify(answer));
LaplaceVar.socket.send(JSON.stringify({
Type: "gotAnswer",
SessionID: LaplaceVar.sessionID,
Value: JSON.stringify(answer),
}))
}
async function doJoin(roomID,BarrierIP = "") {
if (roomID) {
LaplaceVar.roomID = roomID;
} else {
return alert('roomID is not provided');
}
// normalize roomID starting with #
LaplaceVar.roomID = LaplaceVar.roomID.startsWith('#') ? LaplaceVar.roomID.slice(1) : LaplaceVar.roomID;
LaplaceVar.status = {
numConn: 0,
peers: [],
};
updateRoomUI();
print('[+] Initiate media: set remote source');
LaplaceVar.mediaStream = new MediaStream();
LaplaceVar.ui.video.srcObject = LaplaceVar.mediaStream;
print('[+] Initiate websocket');
if (BarrierIP == "") {
LaplaceVar.socket = new WebSocket(getWebsocketUrl() + "/ws_connect?id=" + LaplaceVar.roomID);
} else {
LaplaceVar.socket = new WebSocket(getWebsocketUrl() + "/ws_connect?id=" + LaplaceVar.roomID + "&barrierip="+BarrierIP);
}
LaplaceVar.socket.onerror = () => {
alert('WebSocket error');
leaveRoom();
};
LaplaceVar.socket.onopen = async function () {
print("[+] Connected to websocket");
};
LaplaceVar.socket.onmessage = async function (e) {
try {
const jsonData = JSON.parse(e.data);
if (jsonData.Type !== 'beat') {
print("[+] Received websocket message: " + JSON.stringify(e.data));
}
if (jsonData.Type === "newSession") {
await newSessionJoin(jsonData.SessionID);
} else if (jsonData.Type === "addCallerIceCandidate") {
await addCallerIceCandidate(jsonData.SessionID, JSON.parse(jsonData.Value));
} else if (jsonData.Type === "gotOffer") {
await gotOffer(jsonData.SessionID, JSON.parse(jsonData.Value));
} else if (jsonData.Type === 'roomNotFound') {
alert('Room not found');
leaveRoom();
} else if (jsonData.Type === 'roomClosed') {
alert('Room closed');
}
} catch (e) {
print("[!] ERROR: " + e);
console.error(e)
}
};
}
function routeByUrl() {
const u = new URL(window.location);
const paramId = u.searchParams.get('id');
if (paramId && paramId.length > 0) {
return doJoin(paramId);
}
const paramStream = u.searchParams.get('stream');
if (paramStream && paramStream.length > 0) {
return doStream();
}
}
function leaveRoom() {
window.location.href = getBaseUrl();
}
initUI();
routeByUrl();

1
files/static/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long