Compare commits
1 Commits
3664fbc593
...
83bc11bac6
Author | SHA1 | Date | |
---|---|---|---|
83bc11bac6 |
|
@ -14,7 +14,7 @@ body.justVideo {
|
|||
right: 0;
|
||||
float: right;
|
||||
}
|
||||
#qrcodelink img {
|
||||
#qrcodelink svg path {
|
||||
min-width: 30vw;
|
||||
max-width: 100vw;
|
||||
max-height: 50vh;
|
||||
|
@ -42,6 +42,9 @@ form label {
|
|||
#status {
|
||||
display: table;
|
||||
}
|
||||
body.justVideo #status {
|
||||
display: none;
|
||||
}
|
||||
#status .log-entry {
|
||||
display: block;
|
||||
background: lightblue;
|
||||
|
@ -52,11 +55,24 @@ form label {
|
|||
background: lightyellow;
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript" src="static/js/qrcode.js"></script>
|
||||
<script type="text/javascript" src="static/js/qrcodegen.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a id="qrcodelink" style="display:none;"><div id="qrcode"></div></a>
|
||||
<a id="qrcodelink" style="display:none;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="qrcode" style="width:30em; height:30em;" stroke="none">
|
||||
<rect width="100%" height="100%" fill="#FFFFFF"/>
|
||||
<path d="" fill="#000000"/>
|
||||
</svg>
|
||||
</a>
|
||||
<div id="qrcodemessage" style="display:none;">
|
||||
<!-- TODO Add copy button. -->
|
||||
<div id="qrcodemsg"></div>
|
||||
</div>
|
||||
<form name="settings">
|
||||
<label>
|
||||
Serverless mode:
|
||||
<input name="serverless" type="checkbox" value="true" />
|
||||
</label>
|
||||
<label>
|
||||
Recieve remote video:
|
||||
<select name="client-video">
|
||||
|
@ -95,11 +111,14 @@ form label {
|
|||
</div>
|
||||
<script >
|
||||
const create = (container, type) => container.appendChild(document.createElement(type));
|
||||
const QRGen = qrcodegen.QrCode;
|
||||
|
||||
const body = document.querySelector("body");
|
||||
const out = document.getElementById("status");
|
||||
const qrcode = document.getElementById("qrcode");
|
||||
const qrcodelink = document.getElementById("qrcodelink");
|
||||
const qrcodemsg = document.getElementById("qrcodemsg");
|
||||
const qrcodemessage = document.getElementById("qrcodemessage");
|
||||
const remoteView = document.getElementById("remoteView");
|
||||
const selfView = document.getElementById("selfView");
|
||||
const start = document.getElementById("start");
|
||||
|
@ -117,6 +136,10 @@ form label {
|
|||
}
|
||||
|
||||
function logPre(str) {
|
||||
if (!str) {
|
||||
_log('undefined', 'pre');
|
||||
return;
|
||||
}
|
||||
_log(str.split('\\r\\n').join('\r\n'), 'pre');
|
||||
}
|
||||
|
||||
|
@ -143,9 +166,30 @@ form label {
|
|||
});
|
||||
}
|
||||
|
||||
function isServerless() {
|
||||
return settings && 'serverless' in settings && settings.serverless;
|
||||
}
|
||||
|
||||
function withHash(hash) {
|
||||
return window.location.href.split('#')[0] + '#' + hash;
|
||||
}
|
||||
function displayQRUrl(url) {
|
||||
qrcodelink.href = url;
|
||||
const qr = QRGen.encodeText(url, QRGen.Ecc.MEDIUM);
|
||||
const code = qr.toSvgString(4);
|
||||
const viewBox = (/ viewBox="([^"]*)"/.exec(code))[1];
|
||||
const pathD = (/ d="([^"]*)"/.exec(code))[1];
|
||||
qrcode.setAttribute("viewBox", viewBox);
|
||||
qrcode.querySelector("path").setAttribute("d", pathD);
|
||||
qrcodelink.style.display = '';
|
||||
}
|
||||
function displayQRUrlForObj(obj) {
|
||||
displayQRUrl(withHash('^' + btoa(JSON.stringify(obj))))
|
||||
}
|
||||
|
||||
let roomName = window.location.hash;
|
||||
let isHost = roomName === undefined || !roomName;
|
||||
if (roomName.startsWith("#{")) {
|
||||
if (roomName && roomName.startsWith("#{")) {
|
||||
roomName = undefined;
|
||||
isHost = true;
|
||||
|
||||
|
@ -167,6 +211,25 @@ form label {
|
|||
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 = atob(hash.substring(2));
|
||||
logPre(decoded);
|
||||
firstMessage = JSON.parse(decoded);
|
||||
settings = firstMessage.settings;
|
||||
} catch (error) {
|
||||
log("Failed to read message from hash: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
if (isHost) {
|
||||
// From https://stackoverflow.com/a/1349426
|
||||
function makeid(length) {
|
||||
|
@ -179,12 +242,14 @@ form label {
|
|||
return result;
|
||||
}
|
||||
|
||||
roomName = makeid(8);
|
||||
qrcodelink.href = window.location.href.split('#')[0] + '#' + roomName;
|
||||
new QRCode(qrcode, qrcodelink.href);
|
||||
qrcodelink.style.display = '';
|
||||
if (!isServerless()) {
|
||||
roomName = makeid(8);
|
||||
displayQRUrl(withHash(roomName));
|
||||
}
|
||||
} else {
|
||||
roomName = roomName.substring(1);
|
||||
if (!isServerless()) {
|
||||
roomName = roomName.substring(1);
|
||||
}
|
||||
qrcodelink.style.display = 'none';
|
||||
form.style.display = 'none';
|
||||
}
|
||||
|
@ -197,10 +262,15 @@ form label {
|
|||
const toSend = JSON.stringify(data);
|
||||
log("Sending message...");
|
||||
logPre(toSend);
|
||||
webSocket.send(toSend);
|
||||
if (isServerless()) {
|
||||
|
||||
} else {
|
||||
webSocket.send(toSend);
|
||||
}
|
||||
}
|
||||
|
||||
var pc = undefined;
|
||||
var localDescriptionSet = false;
|
||||
function createRTCPeerConnection() {
|
||||
const pc = new RTCPeerConnection();
|
||||
log("Created RTCPeerConnection.");
|
||||
|
@ -208,17 +278,30 @@ form label {
|
|||
pc.addEventListener('icegatheringstatechange', e => {
|
||||
log("In pc.icegatheringstatechange... e.target.iceGatheringState="
|
||||
+ e.target.iceGatheringState);
|
||||
if (!startStreamingDone || !localDescriptionSet) return;
|
||||
// In serverless mode, host transmits first.
|
||||
// In server mode, client transmits first.
|
||||
if ((isServerless() != isHost) && !handledOffer) return;
|
||||
// Don't send offer until all ICE candidates are generated.
|
||||
if (e.target.iceGatheringState === 'complete') {
|
||||
sendJson({
|
||||
description: pc.localDescription
|
||||
});
|
||||
if (isHost && isServerless()) {
|
||||
displayQRUrlForObj({
|
||||
settings: settings,
|
||||
description: pc.localDescription
|
||||
});
|
||||
} else {
|
||||
sendJson({
|
||||
description: pc.localDescription
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pc.onnegotiationneeded = async function () {
|
||||
localDescriptionSet = false;
|
||||
log("In pc.onnegotiationneeded...");
|
||||
await pc.setLocalDescription(await pc.createOffer());
|
||||
localDescriptionSet = true;
|
||||
}
|
||||
|
||||
pc.ontrack = ({streams: [stream]}) => {
|
||||
|
@ -230,7 +313,6 @@ form label {
|
|||
|
||||
if (stream.getVideoTracks().length > 0) {
|
||||
remoteView.style.display = '';
|
||||
out.style.display = 'none';
|
||||
form.style.display = 'none';
|
||||
videos.style.display = '';
|
||||
body.classList.add('justVideo');
|
||||
|
@ -240,6 +322,8 @@ form label {
|
|||
return pc;
|
||||
}
|
||||
|
||||
var startStreamingDone = false;
|
||||
var remoteDesc = null;
|
||||
// get a local stream, show it in a self-view and add it to be sent
|
||||
async function startStreaming(fromButton) {
|
||||
const otherAudioSettings = isHost
|
||||
|
@ -264,7 +348,7 @@ form label {
|
|||
}
|
||||
start.style.display = 'none';
|
||||
|
||||
if (isHost) {
|
||||
if (isHost && !isServerless()) {
|
||||
sendJson({
|
||||
settings: settings
|
||||
});
|
||||
|
@ -279,31 +363,45 @@ form label {
|
|||
? true
|
||||
: { advanced: [{facingMode: videoSettings}] };
|
||||
log("Created videoConstraints.");
|
||||
if (!videoConstraints && !audioSettings) return;
|
||||
|
||||
const stream = videoSettings == 'screen'
|
||||
? await navigator.mediaDevices.getDisplayMedia({
|
||||
audio: audioSettings,
|
||||
video: true
|
||||
})
|
||||
: await navigator.mediaDevices.getUserMedia({
|
||||
audio: audioSettings,
|
||||
video: videoConstraints
|
||||
});
|
||||
log("Created stream.");
|
||||
if (videoConstraints) {
|
||||
selfView.srcObject = stream;
|
||||
selfView.style.display = '';
|
||||
if (videoConstraints || audioSettings) {
|
||||
const stream = videoSettings == 'screen'
|
||||
? await navigator.mediaDevices.getDisplayMedia({
|
||||
audio: audioSettings,
|
||||
video: true
|
||||
})
|
||||
: await navigator.mediaDevices.getUserMedia({
|
||||
audio: audioSettings,
|
||||
video: videoConstraints
|
||||
});
|
||||
log("Created stream.");
|
||||
if (videoConstraints) {
|
||||
selfView.srcObject = stream;
|
||||
selfView.style.display = '';
|
||||
}
|
||||
for (const track of stream.getTracks()) {
|
||||
log("Added track.");
|
||||
pc.addTrack(track, stream);
|
||||
}
|
||||
}
|
||||
for (const track of stream.getTracks()) {
|
||||
log("Added track.");
|
||||
pc.addTrack(track, stream);
|
||||
else if (isServerless() && isHost) {
|
||||
log("No audio/video host.");
|
||||
//sendJson({description: pc.localDescription});
|
||||
await pc.setLocalDescription(await pc.createAnswer());
|
||||
}
|
||||
startStreamingDone = true;
|
||||
}
|
||||
function startStartingWithErorrHandling(fromButton) {
|
||||
function startStartingWithErorrHandling(fromButton, description) {
|
||||
startStreaming(fromButton)
|
||||
.then(() => {
|
||||
log("startStreaming() finished.");
|
||||
if (!description && remoteDesc) {
|
||||
description = remoteDesc;
|
||||
remoteDesc = null;
|
||||
}
|
||||
if (description) {
|
||||
handleOffer(description);
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
log("startStreaming() errored: " + e.message);
|
||||
|
@ -314,6 +412,16 @@ form label {
|
|||
startStartingWithErorrHandling(true)
|
||||
});
|
||||
|
||||
var handledOffer = false;
|
||||
async function handleOffer(description) {
|
||||
await pc.setRemoteDescription(description);
|
||||
if (description.type == "offer") {
|
||||
log("Got an offer...");
|
||||
await pc.setLocalDescription(await pc.createAnswer());
|
||||
handledOffer = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function receiveMessage(e) {
|
||||
qrcode.style.display = 'none';
|
||||
log("In webSocket.onmessage...");
|
||||
|
@ -324,12 +432,16 @@ form label {
|
|||
startStartingWithErorrHandling(false);
|
||||
} else if (data.settings) {
|
||||
settings = data.settings;
|
||||
startStartingWithErorrHandling(false);
|
||||
if ('description' in data) {
|
||||
startStartingWithErorrHandling(false, data.description);
|
||||
} else {
|
||||
startStartingWithErorrHandling(false);
|
||||
}
|
||||
} else if (data.description) {
|
||||
await pc.setRemoteDescription(data.description);
|
||||
if (data.description.type == "offer") {
|
||||
log("Got an offer...");
|
||||
await pc.setLocalDescription(await pc.createAnswer());
|
||||
if (startStreamingDone) {
|
||||
handleOffer(data.description);
|
||||
} else {
|
||||
remoteDesc = data.description;
|
||||
}
|
||||
} else if (data.candidate) {
|
||||
log("Adding ice candidate...");
|
||||
|
@ -359,10 +471,14 @@ form label {
|
|||
return webSocket;
|
||||
}
|
||||
|
||||
webSocket = createWebSocket();
|
||||
if (!isServerless()) {
|
||||
webSocket = createWebSocket();
|
||||
|
||||
if (!isHost) {
|
||||
webSocket.onopen = _ => sendJson({requestSettings: true});
|
||||
if (!isHost) {
|
||||
webSocket.onopen = _ => sendJson({requestSettings: true});
|
||||
}
|
||||
} else if (isHost) {
|
||||
startStartingWithErorrHandling(false);
|
||||
}
|
||||
|
||||
log("Finished <script> block.");
|
||||
|
|
Loading…
Reference in New Issue
Block a user