Compare commits

..

1 Commits

View File

@ -12,13 +12,13 @@ body.justVideo {
body.justVideo #status, body.justVideo form { body.justVideo #status, body.justVideo form {
display: none; display: none;
} }
#qrcodelink { .qrcontainer {
position: sticky; position: sticky;
top: 0; top: 0;
right: 0; right: 0;
float: right; float: right;
} }
#qrcodelink svg path { .qrcontainer svg path {
min-width: 30vw; min-width: 30vw;
max-width: 100vw; max-width: 100vw;
max-height: 50vh; max-height: 50vh;
@ -57,15 +57,29 @@ form label {
} }
</style> </style>
<script type="text/javascript" src="static/js/qrcodegen.js"></script> <script type="text/javascript" src="static/js/qrcodegen.js"></script>
<script type="text/javascript" src="static/js/lzma_worker.js"></script>
<script type="text/javascript" src="static/js/prefixCompressor.js"></script>
</head> </head>
<body> <body>
<a id="qrcodelink" style="display:none;"> <a class="qrcontainer" id="qrcodelink" style="display:none;">
<svg xmlns="http://www.w3.org/2000/svg" id="qrcode" style="width:20em; height:20em;" stroke="none"> <svg xmlns="http://www.w3.org/2000/svg" id="qrcode" style="width:20em; height:20em;" stroke="none">
<rect width="100%" height="100%" fill="#FFFFFF"/> <rect width="100%" height="100%" fill="#FFFFFF"/>
<path d="" fill="#000000"/> <path d="" fill="#000000"/>
</svg> </svg>
</a> </a>
<div class="qrcontainer" id="qrcodeObjContainer" style="display:none;">
<svg xmlns="http://www.w3.org/2000/svg" id="qrcodeObj" style="width:20em; height:20em;" stroke="none">
<rect width="100%" height="100%" fill="#FFFFFF"/>
<path d="" fill="#000000"/>
</svg> <br/>
<textarea id="localOffer" readonly></textarea>
<button id="copyLocalOffer">Copy</button>
</div>
<form name="settings"> <form name="settings">
<label>
Serverless mode:
<input name="serverless" type="checkbox" value="true" />
</label>
<label> <label>
Recieve remote video: Recieve remote video:
<select name="client-video"> <select name="client-video">
@ -99,6 +113,12 @@ form label {
<input name="debug" type="checkbox" value="true" /> <input name="debug" type="checkbox" value="true" />
</label> </label>
</form> </form>
<form id="serverlessOffer" style="display: none;">
<label>
Paste offer here:
<textarea name="remoteOffer"></textarea>
</label>
</form>
<div id="status"></div> <div id="status"></div>
<div id="videos"> <div id="videos">
<button id="unmute" style="display:none;">Unmute</button> <button id="unmute" style="display:none;">Unmute</button>
@ -106,19 +126,36 @@ form label {
<video id="remoteView" width="100%" autoplay muted style="display:none;"></video> <video id="remoteView" width="100%" autoplay muted style="display:none;"></video>
<video id="selfView" width="200" height="150" autoplay muted style="display:none;"></video> <video id="selfView" width="200" height="150" autoplay muted style="display:none;"></video>
</div> </div>
<script > <script type="module" >
import * as b64 from "./static/js/base64.js";
async function initialize() {
const create = (container, type) => container.appendChild(document.createElement(type)); const create = (container, type) => container.appendChild(document.createElement(type));
const QRGen = qrcodegen.QrCode; const QRGen = qrcodegen.QrCode;
var compressor = null;
async function getCompressor() {
if (compressor == null) {
compressor = await loadLZMAPrefixCompressor('static/js/prefix');
}
return compressor;
}
const body = document.querySelector("body"); const body = document.querySelector("body");
const out = document.getElementById("status"); const out = document.getElementById("status");
const qrcode = document.getElementById("qrcode"); const qrcode = document.getElementById("qrcode");
const qrcodelink = document.getElementById("qrcodelink"); const qrcodelink = document.getElementById("qrcodelink");
const qrcodeObjContainer = document.getElementById("qrcodeObjContainer");
const qrcodeObj = document.getElementById("qrcodeObj");
const remoteView = document.getElementById("remoteView"); const remoteView = document.getElementById("remoteView");
const selfView = document.getElementById("selfView"); const selfView = document.getElementById("selfView");
const start = document.getElementById("start"); const start = document.getElementById("start");
const unmute = document.getElementById("unmute"); const unmute = document.getElementById("unmute");
const form = document.forms["settings"]; const form = document.forms["settings"];
const remoteOfferForm = document.forms["serverlessOffer"];
const remoteOffer = remoteOfferForm.elements["remoteOffer"];
const localOfferArea = document.getElementById("localOffer");
const localOfferCopyButton = document.getElementById("copyLocalOffer");
function _log(str, tag) { function _log(str, tag) {
const logEntry = document.createElement(tag); const logEntry = document.createElement(tag);
@ -157,19 +194,52 @@ form label {
}); });
} }
function isServerless() {
return settings && 'serverless' in settings && settings.serverless;
}
function withHash(hash) { function withHash(hash) {
return window.location.href.split('#')[0] + '#' + hash; return window.location.href.split('#')[0] + '#' + hash;
} }
function displayQRUrl(url) { function displayQR(qrcode, val) {
qrcodelink.href = url; const encodeFunc = val instanceof Uint8Array
const qr = QRGen.encodeText(url, QRGen.Ecc.MEDIUM); ? QRGen.encodeBinary
: QRGen.encodeText;
const qr = encodeFunc(val, QRGen.Ecc.MEDIUM);
const code = qr.toSvgString(1); const code = qr.toSvgString(1);
const viewBox = (/ viewBox="([^"]*)"/.exec(code))[1]; const viewBox = (/ viewBox="([^"]*)"/.exec(code))[1];
const pathD = (/ d="([^"]*)"/.exec(code))[1]; const pathD = (/ d="([^"]*)"/.exec(code))[1];
qrcode.setAttribute("viewBox", viewBox); qrcode.setAttribute("viewBox", viewBox);
qrcode.querySelector("path").setAttribute("d", pathD); qrcode.querySelector("path").setAttribute("d", pathD);
}
function displayQRUrl(url) {
qrcodelink.href = url;
displayQR(qrcode, url);
qrcodelink.style.display = ''; qrcodelink.style.display = '';
} }
function displayQRUrlForObj(obj) {
const json = JSON.stringify(obj);
log("Encoding message in QR code/link:");
logPre(json);
const compressed = compressor.compress(json);
const encoded = b64.bytesToBase64(compressed);
displayQRUrl(withHash('^' + encoded));
}
function displayQRCodeForObj(obj) {
const json = JSON.stringify(obj);
log("Encoding message in QR code for copy/paste:");
logPre(json);
const compressed = compressor.compress(json);
const encoded = b64.bytesToBase64(compressed);
displayQR(qrcodeObj, compressed);
localOfferArea.value = encoded;
localOfferCopyButton.onclick = _ => {
localOfferArea.focus();
localOfferArea.select();
document.execCommand('copy');
};
qrcodeObjContainer.style.display = '';
}
let roomName = window.location.hash; let roomName = window.location.hash;
let isHost = roomName === undefined || !roomName; let isHost = roomName === undefined || !roomName;
@ -195,6 +265,25 @@ form label {
log("Failed to read settings from hash: " + error); log("Failed to read settings from hash: " + error);
} }
} }
var firstMessage = undefined;
if (roomName && roomName.startsWith("#^")) {
roomName = undefined;
isHost = false;
try {
const hash = decodeURI(window.location.hash);
log("Reading first message from hash:");
logPre(hash);
log("Decoded base64:");
const decoded = (await getCompressor()).decompress(b64.base64ToBytes(hash.substring(2)));
logPre(decoded);
firstMessage = decoded;
settings = {serverless: true};
} catch (error) {
log("Failed to read message from hash: " + error);
}
}
if (isHost) { if (isHost) {
// From https://stackoverflow.com/a/1349426 // From https://stackoverflow.com/a/1349426
function makeid(length) { function makeid(length) {
@ -207,15 +296,23 @@ form label {
return result; return result;
} }
roomName = makeid(8); if (!isServerless()) {
displayQRUrl(withHash(roomName)); roomName = makeid(8);
displayQRUrl(withHash(roomName));
}
} else { } else {
roomName = roomName.substring(1); if (!isServerless()) {
roomName = roomName.substring(1);
}
qrcodelink.style.display = 'none'; qrcodelink.style.display = 'none';
form.style.display = 'none'; form.style.display = 'none';
} }
log("Room: " + roomName); if (isServerless()) {
log("In serverless mode.");
} else {
log("Room: " + roomName);
}
var webSocket = undefined; var webSocket = undefined;
@ -225,6 +322,10 @@ form label {
const method = dcReady ? "dataConnection" : "webSocket"; const method = dcReady ? "dataConnection" : "webSocket";
log("Sending message via " + method + "..."); log("Sending message via " + method + "...");
logPre(toSend); logPre(toSend);
if (isServerless() && !dcReady) {
log("ERROR: Attempted to use webSocket in serverless mode.");
return;
}
(dcReady ? dc : webSocket).send(toSend); (dcReady ? dc : webSocket).send(toSend);
} }
@ -233,7 +334,12 @@ form label {
function sendOffer() { function sendOffer() {
if (pc.iceGatheringState == "complete") { if (pc.iceGatheringState == "complete") {
sendJson({ const sendFunc = !isServerless() || dc && dc.readyState == "open"
? sendJson
: isHost
? displayQRUrlForObj
: displayQRCodeForObj;
sendFunc({
description: pc.localDescription description: pc.localDescription
}); });
} }
@ -284,6 +390,7 @@ form label {
receiveMessage({source: "dataChannel", data: e.data}); receiveMessage({source: "dataChannel", data: e.data});
} }
log('Data channel initialized.'); log('Data channel initialized.');
qrcodeObjContainer.style.display = 'none';
} }
if (isHost) { if (isHost) {
@ -292,6 +399,7 @@ form label {
receiveMessage({source: "dataChannel", data: e.data}); receiveMessage({source: "dataChannel", data: e.data});
} }
dc.onopen = e => { dc.onopen = e => {
remoteOffer.disabled = true;
log("Data channel open, sending settings...") log("Data channel open, sending settings...")
settings = readSettingsForm(true); settings = readSettingsForm(true);
sendJson({settings}); sendJson({settings});
@ -414,16 +522,32 @@ form label {
return webSocket; return webSocket;
} }
webSocket = createWebSocket(); if (!isServerless()) {
webSocket = createWebSocket();
if (!isHost) { if (!isHost) {
// To make serverless and server mode more similar, // To make serverless and server mode more similar,
// always make the first RTCPeerConnection offer from the host, // always make the first RTCPeerConnection offer from the host,
// so here just notify the host to start the process. // so here just notify the host to start the process.
webSocket.onopen = _ => sendJson({ready: true}); webSocket.onopen = _ => sendJson({ready: true});
}
} else if (isHost) {
await getCompressor();
pc = createRTCPeerConnection();
remoteOffer.value = '';
remoteOffer.onchange = _ => {
const decoded = b64.base64ToBytes(remoteOffer.value);
const decompressed = compressor.decompress(decoded);
receiveMessage({source: 'textarea', data: decompressed});
}
remoteOfferForm.style.display = '';
} else {
receiveMessage({source: 'URL', data: firstMessage});
} }
log("Finished <script> block."); log("Finished <script> block.");
}
initialize();
</script> </script>
</body> </body>
</html> </html>