Commit cbff5067 authored by Juliusz Chroboczek's avatar Juliusz Chroboczek

Implement sharing of multiple application windows at a time.

parent 9fde0299
...@@ -32,11 +32,7 @@ h1 { ...@@ -32,11 +32,7 @@ h1 {
display: inline display: inline
} }
.userform-invisible { .invisible {
display: none;
}
.disconnect-invisible {
display: none; display: none;
} }
...@@ -53,6 +49,12 @@ h1 { ...@@ -53,6 +49,12 @@ h1 {
margin-bottom: 4px; margin-bottom: 4px;
} }
#presentbutton, #unpresentbutton {
width: 8em;
white-space: nowrap;
margin-right: 0.4em;
}
#videoselect { #videoselect {
width: 8em; width: 8em;
text-align-last: center; text-align-last: center;
...@@ -65,6 +67,15 @@ h1 { ...@@ -65,6 +67,15 @@ h1 {
margin-right: 0.4em; margin-right: 0.4em;
} }
#sharebutton, #unsharebutton {
width: 8em;
white-space: nowrap;
}
#unsharebutton {
margin-right: 0.4em;
}
#requestselect { #requestselect {
width: 8em; width: 8em;
text-align-last: center; text-align-last: center;
......
...@@ -23,29 +23,31 @@ ...@@ -23,29 +23,31 @@
autocomplete="current-password"/> autocomplete="current-password"/>
<input id="connectbutton" type="submit" value="Connect" disabled/> <input id="connectbutton" type="submit" value="Connect" disabled/>
</form> </form>
<input id="disconnectbutton" class="disconnect-invisible" <input id="disconnectbutton" class="invisible"
type="submit" value="Disconnect"/> type="submit" value="Disconnect"/>
<span id="errspan"></span> <span id="errspan"></span>
</div> </div>
<div id="optionsdiv"> <div id="optionsdiv">
<label for="presenterbox">Present:</label> <button id="presentbutton" class="invisible">Present</button>
<input id="presenterbox" type="checkbox" disabled/> <button id="unpresentbutton" class="invisible">Stop presenting</button>
<label for="videoselect">Camera:</label> <span id="mediaoptions">
<select id="videoselect"> <label for="videoselect">Camera:</label>
<option>default</option> <select id="videoselect">
<option>off</option> <option>default</option>
</select> <option>off</option>
</select>
<label for="audioselect">Microphone:</label> <label for="audioselect">Microphone:</label>
<select id="audioselect"> <select id="audioselect">
<option>default</option> <option>default</option>
<option>off</option> <option>off</option>
</select> </select>
</span>
<label for="sharebox">Share screen:</label> <button id="sharebutton" class="invisible">Share screen</button>
<input id="sharebox" type="checkbox" disabled/> <button id="unsharebutton" class="invisible">Stop sharing</button>
<label for="requestselect">Receive:</label> <label for="requestselect">Receive:</label>
<select id="requestselect"> <select id="requestselect">
......
...@@ -36,6 +36,7 @@ function randomid() { ...@@ -36,6 +36,7 @@ function randomid() {
function Connection(id, pc) { function Connection(id, pc) {
this.id = id; this.id = id;
this.kind = null;
this.label = null; this.label = null;
this.pc = pc; this.pc = pc;
this.stream = null; this.stream = null;
...@@ -100,9 +101,9 @@ function setConnected(connected) { ...@@ -100,9 +101,9 @@ function setConnected(connected) {
statspan.textContent = 'Connected'; statspan.textContent = 'Connected';
statspan.classList.remove('disconnected'); statspan.classList.remove('disconnected');
statspan.classList.add('connected'); statspan.classList.add('connected');
userform.classList.add('userform-invisible'); userform.classList.add('invisible');
userform.classList.remove('userform'); userform.classList.remove('userform');
disconnectbutton.classList.remove('disconnect-invisible'); disconnectbutton.classList.remove('invisible');
displayUsername(); displayUsername();
} else { } else {
let userpass = getUserPass(); let userpass = getUserPass();
...@@ -114,33 +115,75 @@ function setConnected(connected) { ...@@ -114,33 +115,75 @@ function setConnected(connected) {
statspan.classList.remove('connected'); statspan.classList.remove('connected');
statspan.classList.add('disconnected'); statspan.classList.add('disconnected');
userform.classList.add('userform'); userform.classList.add('userform');
userform.classList.remove('userform-invisible'); userform.classList.remove('invisible');
disconnectbutton.classList.add('disconnect-invisible'); disconnectbutton.classList.add('invisible');
permissions={}; permissions={};
clearUsername(false); clearUsername(false);
} }
} }
document.getElementById('presenterbox').onchange = function(e) { document.getElementById('presentbutton').onclick = function(e) {
e.preventDefault(); e.preventDefault();
setLocalMedia(this.checked); addLocalMedia();
}; };
document.getElementById('unpresentbutton').onclick = function(e) {
e.preventDefault();
delUpMediaKind('local');
};
function changePresentation() {
let found = false;
for(let id in up) {
if(up[id].kind === 'local')
found = true;
}
delUpMediaKind('local');
if(found)
addLocalMedia();
}
function setVisibility(id, visible) {
let elt = document.getElementById(id);
if(visible)
elt.classList.remove('invisible');
else
elt.classList.add('invisible');
}
function setButtonsVisibility() {
let local = findUpMedia('local');
let share = findUpMedia('screenshare')
// don't allow multiple presentations
setVisibility('presentbutton', permissions.present && !local);
setVisibility('unpresentbutton', local);
// allow multiple shared documents
setVisibility('sharebutton', permissions.present);
setVisibility('unsharebutton', share);
setVisibility('mediaoptions', permissions.present);
}
document.getElementById('audioselect').onchange = function(e) { document.getElementById('audioselect').onchange = function(e) {
e.preventDefault(); e.preventDefault();
setLocalMedia(document.getElementById('presenterbox').checked); changePresentation();
}; };
document.getElementById('videoselect').onchange = function(e) { document.getElementById('videoselect').onchange = function(e) {
e.preventDefault(); e.preventDefault();
setLocalMedia(document.getElementById('presenterbox').checked); changePresentation();
}; };
document.getElementById('sharebox').onchange = function(e) { document.getElementById('sharebutton').onclick = function(e) {
e.preventDefault(); e.preventDefault();
setShareMedia(this.checked); addShareMedia();
}; };
document.getElementById('unsharebutton').onclick = function(e) {
e.preventDefault();
delUpMediaKind('screenshare');
}
document.getElementById('requestselect').onchange = function(e) { document.getElementById('requestselect').onchange = function(e) {
e.preventDefault(); e.preventDefault();
sendRequest(this.value); sendRequest(this.value);
...@@ -273,26 +316,13 @@ async function setMediaChoices() { ...@@ -273,26 +316,13 @@ async function setMediaChoices() {
mediaChoicesDone = true; mediaChoicesDone = true;
} }
let localMediaId = null; async function addLocalMedia() {
async function setLocalMedia(setup) {
if(!getUserPass()) if(!getUserPass())
return; return;
if(!setup) {
if(localMediaId) {
up[localMediaId].close(true);
delete(up[localMediaId]);
delMedia(localMediaId);
localMediaId = null;
}
return;
}
let audio = mapMediaOption(document.getElementById('audioselect').value); let audio = mapMediaOption(document.getElementById('audioselect').value);
let video = mapMediaOption(document.getElementById('videoselect').value); let video = mapMediaOption(document.getElementById('videoselect').value);
setLocalMedia(false);
if(!audio && !video) if(!audio && !video)
return; return;
...@@ -302,15 +332,14 @@ async function setLocalMedia(setup) { ...@@ -302,15 +332,14 @@ async function setLocalMedia(setup) {
stream = await navigator.mediaDevices.getUserMedia(constraints); stream = await navigator.mediaDevices.getUserMedia(constraints);
} catch(e) { } catch(e) {
console.error(e); console.error(e);
document.getElementById('presenterbox').checked = false;
await setLocalMedia(false);
return; return;
} }
setMediaChoices(); setMediaChoices();
localMediaId = await newUpStream(); let id = await newUpStream();
let c = up[localMediaId]; let c = up[id];
c.kind = 'local';
c.stream = stream; c.stream = stream;
stream.getTracks().forEach(t => { stream.getTracks().forEach(t => {
c.labels[t.id] = t.kind c.labels[t.id] = t.kind
...@@ -320,56 +349,77 @@ async function setLocalMedia(setup) { ...@@ -320,56 +349,77 @@ async function setLocalMedia(setup) {
}, 2000); }, 2000);
}); });
c.setInterval(() => { c.setInterval(() => {
displayStats(localMediaId); displayStats(id);
}, 2500); }, 2500);
await setMedia(localMediaId); await setMedia(id);
setButtonsVisibility()
} }
let shareMediaId = null; async function addShareMedia(setup) {
async function setShareMedia(setup) {
if(!getUserPass()) if(!getUserPass())
return; return;
if(!setup) { let stream = null;
if(shareMediaId) { try {
up[shareMediaId].close(true); stream = await navigator.mediaDevices.getDisplayMedia({});
delete(up[shareMediaId]); } catch(e) {
delMedia(shareMediaId) console.error(e);
shareMediaId = null;
}
return; return;
} }
if(!shareMediaId) {
let stream = null; let id = await newUpStream();
try { let c = up[id];
stream = await navigator.mediaDevices.getDisplayMedia({}); c.kind = 'screenshare';
} catch(e) { c.stream = stream;
console.error(e); stream.getTracks().forEach(t => {
document.getElementById('sharebox').checked = false; let sender = c.pc.addTrack(t, stream);
await setShareMedia(false); t.onended = e => {
return; delUpMedia(id);
} };
shareMediaId = await newUpStream(); c.labels[t.id] = 'screenshare';
let c = up[shareMediaId];
c.stream = stream;
stream.getTracks().forEach(t => {
let sender = c.pc.addTrack(t, stream);
t.onended = e => {
document.getElementById('sharebox').checked = false;
setShareMedia(false);
};
c.labels[t.id] = 'screenshare';
c.setInterval(() => {
updateStats(c, sender);
}, 2000);
});
c.setInterval(() => { c.setInterval(() => {
displayStats(shareMediaId); updateStats(c, sender);
}, 2500); }, 2000);
await setMedia(shareMediaId); });
c.setInterval(() => {
displayStats(id);
}, 2500);
await setMedia(id);
setButtonsVisibility()
}
function delUpMedia(id) {
let c = up[id];
if(!c) {
console.error("Deleting unknown up media");
return;
}
c.close(true);
delMedia(id);
delete(up[id]);
setButtonsVisibility()
}
function delUpMediaKind(kind) {
for(let id in up) {
let c = up[id];
if(c.kind != kind)
continue
c.close(true);
delMedia(id);
delete(up[id]);
} }
setButtonsVisibility()
}
function findUpMedia(kind) {
for(let id in up) {
if(up[id].kind === kind)
return true;
}
return false;
} }
function setMedia(id) { function setMedia(id) {
...@@ -493,12 +543,8 @@ function serverConnect() { ...@@ -493,12 +543,8 @@ function serverConnect() {
}; };
socket.onclose = function(e) { socket.onclose = function(e) {
setConnected(false); setConnected(false);
document.getElementById('presenterbox').checked = false; delUpMediaKind('local');
document.getElementById('presenterbox').disabled = true; delUpMediaKind('screenshare');
setLocalMedia(false);
document.getElementById('sharebox').checked = false;
document.getElementById('sharebox').disabled = true;
setShareMedia(false);
for(let id in down) { for(let id in down) {
let c = down[id]; let c = down[id];
delete(down[id]); delete(down[id]);
...@@ -648,21 +694,7 @@ function gotClose(id) { ...@@ -648,21 +694,7 @@ function gotClose(id) {
} }
function gotAbort(id) { function gotAbort(id) {
let c = up[id]; delUpMedia(id);
if(!c)
throw new Error('unknown up stream in abort');
if(id === localMediaId) {
document.getElementById('presenterbox').checked = false;
setLocalMedia(false);
} else if(id === shareMediaId) {
document.getElementById('sharebox').checked = false;
setShareMedia(false);
} else {
console.error('Strange stream in abort');
delMedia(id);
c.pc.close();
delete(up[id]);
}
} }
async function gotICE(id, candidate) { async function gotICE(id, candidate) {
...@@ -753,9 +785,8 @@ function clearUsername() { ...@@ -753,9 +785,8 @@ function clearUsername() {
function gotPermissions(perm) { function gotPermissions(perm) {
permissions = perm; permissions = perm;
document.getElementById('presenterbox').disabled = !perm.present;
document.getElementById('sharebox').disabled = !perm.present;
displayUsername(); displayUsername();
setButtonsVisibility();
} }
const urlRegexp = /https?:\/\/[-a-zA-Z0-9@:%/._\\+~#=?]+[-a-zA-Z0-9@:%/_\\+~#=]/g; const urlRegexp = /https?:\/\/[-a-zA-Z0-9@:%/._\\+~#=?]+[-a-zA-Z0-9@:%/_\\+~#=]/g;
...@@ -1088,18 +1119,12 @@ async function getIceServers() { ...@@ -1088,18 +1119,12 @@ async function getIceServers() {
iceServers = servers; iceServers = servers;
} }
async function doConnect() {
await serverConnect();
await setLocalMedia(document.getElementById('presenterbox').checked);
await setShareMedia(document.getElementById('sharebox').checked);
}
document.getElementById('userform').onsubmit = async function(e) { document.getElementById('userform').onsubmit = async function(e) {
e.preventDefault(); e.preventDefault();
let username = document.getElementById('username').value.trim(); let username = document.getElementById('username').value.trim();
let password = document.getElementById('password').value; let password = document.getElementById('password').value;
setUserPass(username, password); setUserPass(username, password);
await doConnect(); await serverConnect();
}; };
document.getElementById('disconnectbutton').onclick = function(e) { document.getElementById('disconnectbutton').onclick = function(e) {
...@@ -1121,7 +1146,7 @@ function start() { ...@@ -1121,7 +1146,7 @@ function start() {
}).then(c => { }).then(c => {
let userpass = getUserPass(); let userpass = getUserPass();
if(userpass) if(userpass)
doConnect(); return serverConnect();
}); });
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment