mirror of
https://github.com/jech/galene.git
synced 2024-11-22 16:45:58 +01:00
Factor out stream creation, implement replaceUpStream.
This avoids code duplication in the various addUpMedia functions, and makes it possible to replace a stream without reopening the stream's source. This will be required in order to change the simulcast envelope.
This commit is contained in:
parent
ed88a7a4b5
commit
f1a15f07db
1 changed files with 209 additions and 157 deletions
366
static/galene.js
366
static/galene.js
|
@ -392,12 +392,6 @@ getButtonElement('unpresentbutton').onclick = function(e) {
|
||||||
resizePeers();
|
resizePeers();
|
||||||
};
|
};
|
||||||
|
|
||||||
function changePresentation() {
|
|
||||||
let c = findUpMedia('camera');
|
|
||||||
if(c)
|
|
||||||
addLocalMedia(c.localId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* @param {boolean} visible
|
* @param {boolean} visible
|
||||||
|
@ -466,7 +460,7 @@ getSelectElement('videoselect').onchange = function(e) {
|
||||||
if(!(this instanceof HTMLSelectElement))
|
if(!(this instanceof HTMLSelectElement))
|
||||||
throw new Error('Unexpected type for this');
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({video: this.value});
|
updateSettings({video: this.value});
|
||||||
changePresentation();
|
replaceCameraStream();
|
||||||
};
|
};
|
||||||
|
|
||||||
getSelectElement('audioselect').onchange = function(e) {
|
getSelectElement('audioselect').onchange = function(e) {
|
||||||
|
@ -474,7 +468,7 @@ getSelectElement('audioselect').onchange = function(e) {
|
||||||
if(!(this instanceof HTMLSelectElement))
|
if(!(this instanceof HTMLSelectElement))
|
||||||
throw new Error('Unexpected type for this');
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({audio: this.value});
|
updateSettings({audio: this.value});
|
||||||
changePresentation();
|
replaceCameraStream();
|
||||||
};
|
};
|
||||||
|
|
||||||
getInputElement('mirrorbox').onchange = function(e) {
|
getInputElement('mirrorbox').onchange = function(e) {
|
||||||
|
@ -482,7 +476,8 @@ getInputElement('mirrorbox').onchange = function(e) {
|
||||||
if(!(this instanceof HTMLInputElement))
|
if(!(this instanceof HTMLInputElement))
|
||||||
throw new Error('Unexpected type for this');
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({mirrorView: this.checked});
|
updateSettings({mirrorView: this.checked});
|
||||||
changePresentation();
|
// no need to reopen the camera
|
||||||
|
replaceUpStreams('camera');
|
||||||
};
|
};
|
||||||
|
|
||||||
getInputElement('blackboardbox').onchange = function(e) {
|
getInputElement('blackboardbox').onchange = function(e) {
|
||||||
|
@ -490,7 +485,7 @@ getInputElement('blackboardbox').onchange = function(e) {
|
||||||
if(!(this instanceof HTMLInputElement))
|
if(!(this instanceof HTMLInputElement))
|
||||||
throw new Error('Unexpected type for this');
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({blackboardMode: this.checked});
|
updateSettings({blackboardMode: this.checked});
|
||||||
changePresentation();
|
replaceCameraStream();
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('mutebutton').onclick = function(e) {
|
document.getElementById('mutebutton').onclick = function(e) {
|
||||||
|
@ -509,7 +504,8 @@ getSelectElement('filterselect').onchange = async function(e) {
|
||||||
if(!(this instanceof HTMLSelectElement))
|
if(!(this instanceof HTMLSelectElement))
|
||||||
throw new Error('Unexpected type for this');
|
throw new Error('Unexpected type for this');
|
||||||
updateSettings({filter: this.value});
|
updateSettings({filter: this.value});
|
||||||
changePresentation();
|
// no need to reopen the camera
|
||||||
|
replaceUpStreams('camera');
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @returns {number} */
|
/** @returns {number} */
|
||||||
|
@ -944,31 +940,37 @@ Filter.prototype.stop = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Removes any filter set on c.
|
||||||
|
*
|
||||||
* @param {Stream} c
|
* @param {Stream} c
|
||||||
* @param {Filter} f
|
|
||||||
*/
|
*/
|
||||||
function setFilter(c, f) {
|
function removeFilter(c) {
|
||||||
if(!f) {
|
let old = c.userdata.filter;
|
||||||
let filter = c.userdata.filter;
|
if(!old)
|
||||||
if(!filter)
|
return;
|
||||||
return null;
|
|
||||||
if(!(filter instanceof Filter))
|
|
||||||
throw new Error('userdata.filter is not a filter');
|
|
||||||
if(c.userdata.filter) {
|
|
||||||
c.stream = c.userdata.filter.inputStream;
|
|
||||||
c.userdata.filter.stop();
|
|
||||||
c.userdata.filter = null;
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if(c.userdata.filter)
|
if(!(old instanceof Filter))
|
||||||
setFilter(c, null);
|
throw new Error('userdata.filter is not a filter');
|
||||||
|
|
||||||
if(f.inputStream != c.stream)
|
c.stream = old.inputStream;
|
||||||
throw new Error('Setting filter for wrong stream');
|
old.stop();
|
||||||
c.stream = f.outputStream;
|
c.userdata.filter = null;
|
||||||
c.userdata.filter = f;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the filter described by c.userdata.filterDefinition on c.
|
||||||
|
*
|
||||||
|
* @param {Stream} c
|
||||||
|
*/
|
||||||
|
function setFilter(c) {
|
||||||
|
removeFilter(c);
|
||||||
|
|
||||||
|
if(!c.userdata.filterDefinition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let filter = new Filter(c.stream, c.userdata.filterDefinition);
|
||||||
|
c.stream = filter.outputStream;
|
||||||
|
c.userdata.filter = filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1021,22 +1023,155 @@ function isSafari() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Sets up c to send the given stream. Some extra parameters are stored
|
||||||
|
* in c.userdata.
|
||||||
|
*
|
||||||
* @param {Stream} c
|
* @param {Stream} c
|
||||||
* @param {MediaStreamTrack} t
|
|
||||||
* @param {MediaStream} stream
|
* @param {MediaStream} stream
|
||||||
*/
|
*/
|
||||||
function addUpTrack(c, t, stream) {
|
function setUpStream(c, stream) {
|
||||||
let encodings = [{}];
|
if(c.stream != null)
|
||||||
if(t.kind === 'video') {
|
throw new Error("Setting nonempty stream");
|
||||||
let bps = getMaxVideoThroughput();
|
|
||||||
if(bps > 0)
|
c.stream = stream;
|
||||||
encodings[0].maxBitrate = bps;
|
|
||||||
|
try {
|
||||||
|
setFilter(c);
|
||||||
|
} catch(e) {
|
||||||
|
displayWarning("Couldn't set filter: " + e);
|
||||||
}
|
}
|
||||||
c.pc.addTransceiver(t, {
|
|
||||||
direction: 'sendonly',
|
c.onclose = replace => {
|
||||||
streams: [stream],
|
removeFilter(c);
|
||||||
sendEncodings: encodings,
|
if(!replace) {
|
||||||
});
|
stopStream(c.stream);
|
||||||
|
if(c.userdata.onclose)
|
||||||
|
c.userdata.onclose.call(c);
|
||||||
|
delMedia(c.localId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {MediaStreamTrack} t
|
||||||
|
*/
|
||||||
|
function addUpTrack(t) {
|
||||||
|
if(c.label === 'camera') {
|
||||||
|
let settings = getSettings();
|
||||||
|
if(t.kind == 'audio') {
|
||||||
|
if(settings.localMute)
|
||||||
|
t.enabled = false;
|
||||||
|
} else if(t.kind == 'video') {
|
||||||
|
if(settings.blackboardMode) {
|
||||||
|
/** @ts-ignore */
|
||||||
|
t.contentHint = 'detail';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.onended = e => {
|
||||||
|
stream.onaddtrack = null;
|
||||||
|
stream.onremovetrack = null;
|
||||||
|
c.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
let encodings = [{}];
|
||||||
|
if(t.kind === 'video') {
|
||||||
|
let bps = getMaxVideoThroughput();
|
||||||
|
if(bps > 0)
|
||||||
|
encodings[0].maxBitrate = bps;
|
||||||
|
}
|
||||||
|
c.pc.addTransceiver(t, {
|
||||||
|
direction: 'sendonly',
|
||||||
|
streams: [stream],
|
||||||
|
sendEncodings: encodings,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// c.stream might be different from stream if there's a filter
|
||||||
|
c.stream.getTracks().forEach(addUpTrack);
|
||||||
|
|
||||||
|
stream.onaddtrack = function(e) {
|
||||||
|
addUpTrack(e.track);
|
||||||
|
};
|
||||||
|
|
||||||
|
stream.onremovetrack = function(e) {
|
||||||
|
let t = e.track;
|
||||||
|
|
||||||
|
/** @type {RTCRtpSender} */
|
||||||
|
let sender;
|
||||||
|
c.pc.getSenders().forEach(s => {
|
||||||
|
if(s.track === t)
|
||||||
|
sender = s;
|
||||||
|
});
|
||||||
|
if(sender) {
|
||||||
|
c.pc.removeTrack(sender);
|
||||||
|
} else {
|
||||||
|
console.warn('Removing unknown track');
|
||||||
|
}
|
||||||
|
|
||||||
|
let found = false;
|
||||||
|
c.pc.getSenders().forEach(s => {
|
||||||
|
if(s.track)
|
||||||
|
found = true;
|
||||||
|
});
|
||||||
|
if(!found) {
|
||||||
|
stream.onaddtrack = null;
|
||||||
|
stream.onremovetrack = null;
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
c.onstats = gotUpStats;
|
||||||
|
c.setStatsInterval(2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces c with a freshly created stream, duplicating any relevant
|
||||||
|
* parameters in c.userdata.
|
||||||
|
*
|
||||||
|
* @param {Stream} c
|
||||||
|
* @returns {Promise<Stream>}
|
||||||
|
*/
|
||||||
|
async function replaceUpStream(c) {
|
||||||
|
removeFilter(c);
|
||||||
|
let cn = newUpStream(c.localId);
|
||||||
|
cn.label = c.label;
|
||||||
|
if(c.userdata.filterDefinition)
|
||||||
|
cn.userdata.filterDefinition = c.userdata.filterDefinition;
|
||||||
|
if(c.userdata.onclose)
|
||||||
|
cn.userdata.onclose = c.userdata.onclose;
|
||||||
|
let media = /** @type{HTMLVideoElement} */
|
||||||
|
(document.getElementById('media-' + c.localId));
|
||||||
|
setUpStream(cn, c.stream);
|
||||||
|
await setMedia(c, true,
|
||||||
|
c.label == 'camera' && getSettings().mirrorView,
|
||||||
|
c.label == 'video' && media);
|
||||||
|
return cn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces all up streams with the given label. If label is null,
|
||||||
|
* replaces all up stream.
|
||||||
|
*
|
||||||
|
* @param {string} label
|
||||||
|
*/
|
||||||
|
async function replaceUpStreams(label) {
|
||||||
|
let promises = [];
|
||||||
|
for(let id in serverConnection.up) {
|
||||||
|
let c = serverConnection.up[id];
|
||||||
|
if(label && c.label !== label)
|
||||||
|
continue
|
||||||
|
promises.push(replaceUpStream(c));
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes and reopens the camera then replaces the camera stream.
|
||||||
|
*/
|
||||||
|
function replaceCameraStream() {
|
||||||
|
let c = findUpMedia('camera');
|
||||||
|
if(c)
|
||||||
|
addLocalMedia(c.localId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1048,14 +1183,6 @@ async function addLocalMedia(localId) {
|
||||||
let audio = settings.audio ? {deviceId: settings.audio} : false;
|
let audio = settings.audio ? {deviceId: settings.audio} : false;
|
||||||
let video = settings.video ? {deviceId: settings.video} : false;
|
let video = settings.video ? {deviceId: settings.video} : false;
|
||||||
|
|
||||||
let filter = null;
|
|
||||||
if(settings.filter) {
|
|
||||||
filter = filters[settings.filter];
|
|
||||||
if(!filter) {
|
|
||||||
displayWarning(`Unknown filter ${settings.filter}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(video) {
|
if(video) {
|
||||||
let resolution = settings.resolution;
|
let resolution = settings.resolution;
|
||||||
if(resolution) {
|
if(resolution) {
|
||||||
|
@ -1095,51 +1222,17 @@ async function addLocalMedia(localId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
c.stream = stream;
|
|
||||||
c.label = 'camera';
|
c.label = 'camera';
|
||||||
|
|
||||||
if(filter) {
|
if(settings.filter) {
|
||||||
try {
|
let filter = filters[settings.filter];
|
||||||
let f = new Filter(stream, filter);
|
if(filter)
|
||||||
setFilter(c, f);
|
c.userdata.filterDefinition = filter;
|
||||||
c.onclose = replace => {
|
else
|
||||||
stopStream(stream);
|
displayWarning(`Unknown filter ${settings.filter}`);
|
||||||
setFilter(c, null);
|
|
||||||
if(!replace)
|
|
||||||
delMedia(c.localId);
|
|
||||||
}
|
|
||||||
} catch(e) {
|
|
||||||
displayWarning(e);
|
|
||||||
c.onclose = replace => {
|
|
||||||
stopStream(c.stream);
|
|
||||||
if(!replace)
|
|
||||||
delMedia(c.localId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
c.onclose = replace => {
|
|
||||||
stopStream(c.stream);
|
|
||||||
if(!replace)
|
|
||||||
delMedia(c.localId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mute = getSettings().localMute;
|
setUpStream(c, stream);
|
||||||
c.stream.getTracks().forEach(t => {
|
|
||||||
if(t.kind == 'audio') {
|
|
||||||
if(mute)
|
|
||||||
t.enabled = false;
|
|
||||||
} else if(t.kind == 'video') {
|
|
||||||
if(settings.blackboardMode) {
|
|
||||||
/** @ts-ignore */
|
|
||||||
t.contentHint = 'detail';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addUpTrack(c, t, stream);
|
|
||||||
});
|
|
||||||
|
|
||||||
c.onstats = gotUpStats;
|
|
||||||
c.setStatsInterval(2000);
|
|
||||||
await setMedia(c, true, settings.mirrorView);
|
await setMedia(c, true, settings.mirrorView);
|
||||||
setButtonsVisibility();
|
setButtonsVisibility();
|
||||||
}
|
}
|
||||||
|
@ -1168,19 +1261,8 @@ async function addShareMedia() {
|
||||||
}
|
}
|
||||||
|
|
||||||
let c = newUpStream();
|
let c = newUpStream();
|
||||||
c.stream = stream;
|
|
||||||
c.label = 'screenshare';
|
c.label = 'screenshare';
|
||||||
c.onclose = replace => {
|
setUpStream(c, stream);
|
||||||
stopStream(stream);
|
|
||||||
if(!replace)
|
|
||||||
delMedia(c.localId);
|
|
||||||
}
|
|
||||||
stream.getTracks().forEach(t => {
|
|
||||||
addUpTrack(c, t, stream)
|
|
||||||
t.onended = e => c.close();
|
|
||||||
});
|
|
||||||
c.onstats = gotUpStats;
|
|
||||||
c.setStatsInterval(2000);
|
|
||||||
await setMedia(c, true);
|
await setMedia(c, true);
|
||||||
setButtonsVisibility();
|
setButtonsVisibility();
|
||||||
}
|
}
|
||||||
|
@ -1208,60 +1290,24 @@ async function addFileMedia(file) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let c = newUpStream();
|
let c = newUpStream();
|
||||||
c.stream = stream;
|
|
||||||
c.label = 'video';
|
c.label = 'video';
|
||||||
c.onclose = function(replace) {
|
c.userdata.onclose = function() {
|
||||||
stopStream(c.stream);
|
|
||||||
let media = /** @type{HTMLVideoElement} */
|
let media = /** @type{HTMLVideoElement} */
|
||||||
(document.getElementById('media-' + this.localId));
|
(document.getElementById('media-' + this.localId));
|
||||||
if(media && media.src) {
|
if(media && media.src) {
|
||||||
URL.revokeObjectURL(media.src);
|
URL.revokeObjectURL(media.src);
|
||||||
media.src = null;
|
media.src = null;
|
||||||
}
|
}
|
||||||
if(!replace)
|
|
||||||
delMedia(c.localId);
|
|
||||||
};
|
};
|
||||||
|
await setUpStream(c, stream);
|
||||||
|
|
||||||
stream.onaddtrack = function(e) {
|
let presenting = !!findUpMedia('camera');
|
||||||
let t = e.track;
|
let muted = getSettings().localMute;
|
||||||
if(t.kind === 'audio') {
|
if(presenting && !muted) {
|
||||||
let presenting = !!findUpMedia('camera');
|
setLocalMute(true, true);
|
||||||
let muted = getSettings().localMute;
|
displayWarning('You have been muted');
|
||||||
if(presenting && !muted) {
|
}
|
||||||
setLocalMute(true, true);
|
|
||||||
displayWarning('You have been muted');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addUpTrack(c, t, stream);
|
|
||||||
c.onstats = gotUpStats;
|
|
||||||
c.setStatsInterval(2000);
|
|
||||||
};
|
|
||||||
stream.onremovetrack = function(e) {
|
|
||||||
let t = e.track;
|
|
||||||
|
|
||||||
/** @type {RTCRtpSender} */
|
|
||||||
let sender;
|
|
||||||
c.pc.getSenders().forEach(s => {
|
|
||||||
if(s.track === t)
|
|
||||||
sender = s;
|
|
||||||
});
|
|
||||||
if(sender) {
|
|
||||||
c.pc.removeTrack(sender);
|
|
||||||
} else {
|
|
||||||
console.warn('Removing unknown track');
|
|
||||||
}
|
|
||||||
|
|
||||||
let found = false;
|
|
||||||
c.pc.getSenders().forEach(s => {
|
|
||||||
if(s.track)
|
|
||||||
found = true;
|
|
||||||
});
|
|
||||||
if(!found) {
|
|
||||||
stream.onaddtrack = null;
|
|
||||||
stream.onremovetrack = null;
|
|
||||||
c.close();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
await setMedia(c, true, false, video);
|
await setMedia(c, true, false, video);
|
||||||
c.userdata.play = true;
|
c.userdata.play = true;
|
||||||
setButtonsVisibility();
|
setButtonsVisibility();
|
||||||
|
@ -1352,11 +1398,7 @@ async function setMedia(c, isUp, mirror, video) {
|
||||||
|
|
||||||
let media = /** @type {HTMLVideoElement} */
|
let media = /** @type {HTMLVideoElement} */
|
||||||
(document.getElementById('media-' + c.localId));
|
(document.getElementById('media-' + c.localId));
|
||||||
if(media) {
|
if(!media) {
|
||||||
if(video) {
|
|
||||||
throw new Error("Duplicate video");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(video) {
|
if(video) {
|
||||||
media = video;
|
media = video;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1464,7 +1506,7 @@ function addCustomControls(media, container, c, toponly) {
|
||||||
topcontrols.id = 'topcontrols-' + c.localId;
|
topcontrols.id = 'topcontrols-' + c.localId;
|
||||||
container.appendChild(topcontrols);
|
container.appendChild(topcontrols);
|
||||||
}
|
}
|
||||||
registerControlHandlers(media, container, c);
|
registerControlHandlers(c.localId, media, container);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1495,11 +1537,11 @@ function setVolumeButton(muted, button, slider) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {string} localId
|
||||||
* @param {HTMLVideoElement} media
|
* @param {HTMLVideoElement} media
|
||||||
* @param {HTMLElement} container
|
* @param {HTMLElement} container
|
||||||
* @param {Stream} c
|
|
||||||
*/
|
*/
|
||||||
function registerControlHandlers(media, container, c) {
|
function registerControlHandlers(localId, media, container) {
|
||||||
let play = getVideoButton(container, 'video-play');
|
let play = getVideoButton(container, 'video-play');
|
||||||
if(play) {
|
if(play) {
|
||||||
play.onclick = function(event) {
|
play.onclick = function(event) {
|
||||||
|
@ -1513,6 +1555,9 @@ function registerControlHandlers(media, container, c) {
|
||||||
stop.onclick = function(event) {
|
stop.onclick = function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
try {
|
try {
|
||||||
|
let c = serverConnection.findByLocalId(localId);
|
||||||
|
if(!c)
|
||||||
|
throw new Error('Closing unknown stream');
|
||||||
c.close();
|
c.close();
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -2243,6 +2288,13 @@ commands.renegotiate = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
commands.replace = {
|
||||||
|
f: (c, r) => {
|
||||||
|
replaceUpStreams(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* parseCommand splits a string into two space-separated parts. The first
|
* parseCommand splits a string into two space-separated parts. The first
|
||||||
* part may be quoted and may include backslash escapes.
|
* part may be quoted and may include backslash escapes.
|
||||||
|
|
Loading…
Reference in a new issue