mirror of
https://github.com/jech/galene.git
synced 2024-11-22 16:45:58 +01:00
Implement sharing of multiple application windows at a time.
This commit is contained in:
parent
9fde029912
commit
cbff5067b2
3 changed files with 156 additions and 118 deletions
|
@ -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 {
|
||||||
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 {
|
||||||
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,15 +23,16 @@
|
||||||
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>
|
||||||
|
|
||||||
|
<span id="mediaoptions">
|
||||||
<label for="videoselect">Camera:</label>
|
<label for="videoselect">Camera:</label>
|
||||||
<select id="videoselect">
|
<select id="videoselect">
|
||||||
<option>default</option>
|
<option>default</option>
|
||||||
|
@ -43,9 +44,10 @@
|
||||||
<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">
|
||||||
|
|
189
static/sfu.js
189
static/sfu.js
|
@ -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) {
|
||||||
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) {
|
||||||
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() {
|
||||||
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) {
|
||||||
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,45 +349,32 @@ 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) {
|
|
||||||
if(shareMediaId) {
|
|
||||||
up[shareMediaId].close(true);
|
|
||||||
delete(up[shareMediaId]);
|
|
||||||
delMedia(shareMediaId)
|
|
||||||
shareMediaId = null;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if(!shareMediaId) {
|
|
||||||
let stream = null;
|
let stream = null;
|
||||||
try {
|
try {
|
||||||
stream = await navigator.mediaDevices.getDisplayMedia({});
|
stream = await navigator.mediaDevices.getDisplayMedia({});
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
document.getElementById('sharebox').checked = false;
|
|
||||||
await setShareMedia(false);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
shareMediaId = await newUpStream();
|
|
||||||
|
|
||||||
let c = up[shareMediaId];
|
let id = await newUpStream();
|
||||||
|
let c = up[id];
|
||||||
|
c.kind = 'screenshare';
|
||||||
c.stream = stream;
|
c.stream = stream;
|
||||||
stream.getTracks().forEach(t => {
|
stream.getTracks().forEach(t => {
|
||||||
let sender = c.pc.addTrack(t, stream);
|
let sender = c.pc.addTrack(t, stream);
|
||||||
t.onended = e => {
|
t.onended = e => {
|
||||||
document.getElementById('sharebox').checked = false;
|
delUpMedia(id);
|
||||||
setShareMedia(false);
|
|
||||||
};
|
};
|
||||||
c.labels[t.id] = 'screenshare';
|
c.labels[t.id] = 'screenshare';
|
||||||
c.setInterval(() => {
|
c.setInterval(() => {
|
||||||
|
@ -366,10 +382,44 @@ async function setShareMedia(setup) {
|
||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
c.setInterval(() => {
|
c.setInterval(() => {
|
||||||
displayStats(shareMediaId);
|
displayStats(id);
|
||||||
}, 2500);
|
}, 2500);
|
||||||
await setMedia(shareMediaId);
|
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() {
|
||||||
};
|
};
|
||||||
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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
||||||
|
|
||||||
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() {
|
||||||
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() {
|
||||||
}).then(c => {
|
}).then(c => {
|
||||||
let userpass = getUserPass();
|
let userpass = getUserPass();
|
||||||
if(userpass)
|
if(userpass)
|
||||||
doConnect();
|
return serverConnect();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue