added laplace binary
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
config.json
|
||||||
180
files/main.html
Normal file
180
files/main.html
Normal 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> </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
9
files/server.crt
Normal 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
9
files/server.key
Normal 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
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
105
files/static/main.css
Normal 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
565
files/static/main.js
Normal 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
1
files/static/qrcode.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user