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