1
Fork 0
This commit is contained in:
Juliusz Chroboczek 2023-07-14 23:35:57 +02:00
parent 5722270cc7
commit 300d6e2e9d
2 changed files with 97 additions and 97 deletions

View File

@ -214,16 +214,19 @@
<option value="lowest">lowest</option> <option value="lowest">lowest</option>
<option value="low">low</option> <option value="low">low</option>
<option value="normal" selected>normal</option> <option value="normal" selected>normal</option>
<option value="unlimited">unlimited</option> <option value="high">high</option>
<option value="highest">highest</option>
</select> </select>
</form> </form>
<form id="simulcastform"> <form id="scalabilityform">
<label for="simulcastselect" class="sidenav-label-first">Simulcast:</label> <label for="scalabilityselect" class="sidenav-label-first">Scalability mode:</label>
<select id="simulcastselect" class="select select-inline"> <select id="scalabilityselect" class="select select-inline">
<option value="off">off</option> <option value="off">off</option>
<option value="auto" selected>auto</option> <option value="auto" selected>auto</option>
<option value="on">on</option> <option value="svc">SVC</option>
<option value="simulcast">simulcast</option>
<option value="both">both</option>
</select> </select>
</form> </form>

View File

@ -40,7 +40,7 @@ let connectingAgain = false;
* @property {boolean} [localMute] * @property {boolean} [localMute]
* @property {string} [video] * @property {string} [video]
* @property {string} [audio] * @property {string} [audio]
* @property {string} [simulcast] * @property {string} [scalability]
* @property {string} [send] * @property {string} [send]
* @property {string} [request] * @property {string} [request]
* @property {boolean} [activityDetection] * @property {boolean} [activityDetection]
@ -195,10 +195,10 @@ function reflectSettings() {
store = true; store = true;
} }
if(settings.hasOwnProperty('simulcast')) { if(settings.hasOwnProperty('scalability')) {
getSelectElement('simulcastselect').value = settings.simulcast getSelectElement('scalabilityselect').value = settings.scalability;
} else { } else {
settings.simulcast = getSelectElement('simulcastselect').value; settings.scalability = getSelectElement('scalabilityselect').value;
store = true; store = true;
} }
@ -280,11 +280,6 @@ function isSafari() {
return ua.indexOf('safari') >= 0 && ua.indexOf('chrome') < 0; return ua.indexOf('safari') >= 0 && ua.indexOf('chrome') < 0;
} }
function isFirefox() {
let ua = navigator.userAgent.toLowerCase();
return ua.indexOf('firefox') >= 0;
}
/** @type {MediaStream} */ /** @type {MediaStream} */
let safariStream = null; let safariStream = null;
@ -496,7 +491,7 @@ function setButtonsVisibility() {
setVisibility('mediaoptions', present); setVisibility('mediaoptions', present);
setVisibility('sendform', present); setVisibility('sendform', present);
setVisibility('simulcastform', present); setVisibility('scalabilityform', present);
setVisibility('collapse-video', mediacount && mobilelayout); setVisibility('collapse-video', mediacount && mobilelayout);
} }
@ -598,24 +593,6 @@ getSelectElement('filterselect').onchange = async function(e) {
} }
}; };
/** @returns {number} */
function getMaxVideoThroughput() {
let v = getSettings().send;
switch(v) {
case 'lowest':
return 150000;
case 'low':
return 300000;
case 'normal':
return 700000;
case 'unlimited':
return null;
default:
console.error('Unknown video quality', v);
return 700000;
}
}
getSelectElement('sendselect').onchange = async function(e) { getSelectElement('sendselect').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');
@ -623,10 +600,10 @@ getSelectElement('sendselect').onchange = async function(e) {
await reconsiderSendParameters(); await reconsiderSendParameters();
}; };
getSelectElement('simulcastselect').onchange = async function(e) { getSelectElement('scalabilityselect').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({simulcast: this.value}); updateSettings({scalability: this.value});
await reconsiderSendParameters(); await reconsiderSendParameters();
}; };
@ -908,14 +885,78 @@ function newUpStream(localId) {
return c; return c;
} }
const simulcastRate = 100000;
const hqAudioRate = 128000;
const videoRates = {
lowest: 150000,
low: 300000,
normal: 700000,
high: 1500000,
highest: 3000000,
};
/** /**
* Sets an up stream's video throughput and simulcast parameters. * @typedef {Object} sendParameters
* @property {number} maxBitrate
* @property {string} scalabilityMode
* @property {boolean} simulcast
*/
/**
* @returns {sendParameters}
*/
function getSendParameters() {
let bps = videoRates[getSettings().send];
if(!bps) {
console.error('Unknown video quality');
bps = 700000;
}
/** @type{sendParameters} */
let parms = {
maxBitrate: bps,
scalabilityMode: 'L1T1',
simulcast: false,
}
switch(getSettings().scalability) {
case 'off':
return parms;
case 'svc':
parms.scalabilityMode = 'L3T3';
return parms;
case 'simulcast':
parms.simulcast = true;
return parms;
case 'both':
parms.scalabilityMode = 'L1T3';
parms.simulcast = true;
return parms;
default:
let count = 0;
for(let n in serverConnection.users) {
if(!serverConnection.users[n].permissions["system"]) {
count++;
if(count > 2)
break;
}
}
if(count <= 2)
return parms;
if(!bps || bps < 2 * simulcastRate)
return parms;
parms.scalabilityMode = 'L3T3';
return parms;
}
}
/**
* Sets an up stream's video throughput and scalability parameters.
* *
* @param {Stream} c * @param {Stream} c
* @param {number} bps * @param {sendParameters} parms
* @param {boolean} simulcast
*/ */
async function setSendParameters(c, bps, simulcast) { async function setSendParameters(c, parms) {
if(!c.up) if(!c.up)
throw new Error('Setting throughput of down stream'); throw new Error('Setting throughput of down stream');
let senders = c.pc.getSenders(); let senders = c.pc.getSenders();
@ -925,14 +966,15 @@ async function setSendParameters(c, bps, simulcast) {
continue; continue;
let p = s.getParameters(); let p = s.getParameters();
if((!p.encodings || if((!p.encodings ||
!simulcast && p.encodings.length != 1) || !parms.simulcast && p.encodings.length != 1) ||
(simulcast && p.encodings.length != 2)) { (parms.simulcast && p.encodings.length < 2)) {
await replaceUpStream(c); await replaceUpStream(c);
return; return;
} }
p.encodings.forEach(e => { p.encodings.forEach(e => {
if(!e.rid || e.rid === 'h') if(!e.rid || e.rid === 'h')
e.maxBitrate = bps || unlimitedRate; e.maxBitrate = parms.maxBitrate;
e.scalabilityMode = parms.scalabilityMode;
}); });
await s.setParameters(p); await s.setParameters(p);
} }
@ -945,12 +987,11 @@ let reconsiderParametersTimer = null;
*/ */
async function reconsiderSendParameters() { async function reconsiderSendParameters() {
cancelReconsiderParameters(); cancelReconsiderParameters();
let t = getMaxVideoThroughput(); let parms = getSendParameters();
let s = doSimulcast();
let promises = []; let promises = [];
for(let id in serverConnection.up) { for(let id in serverConnection.up) {
let c = serverConnection.up[id]; let c = serverConnection.up[id];
promises.push(setSendParameters(c, t, s)); promises.push(setSendParameters(c, parms));
} }
await Promise.all(promises); await Promise.all(promises);
} }
@ -1167,37 +1208,6 @@ function addFilters() {
} }
} }
const unlimitedRate = 1000000000;
const simulcastRate = 100000;
const hqAudioRate = 128000;
/**
* Decide whether we want to send simulcast.
*
* @returns {boolean}
*/
function doSimulcast() {
switch(getSettings().simulcast) {
case 'on':
return true;
case 'off':
return false;
default:
let count = 0;
for(let n in serverConnection.users) {
if(!serverConnection.users[n].permissions["system"]) {
count++;
if(count > 2)
break;
}
}
if(count <= 2)
return false;
let bps = getMaxVideoThroughput();
return bps <= 0 || bps >= 2 * simulcastRate;
}
}
/** /**
* Sets up c to send the given stream. Some extra parameters are stored * Sets up c to send the given stream. Some extra parameters are stored
* in c.userdata. * in c.userdata.
@ -1250,24 +1260,26 @@ function setUpStream(c, stream) {
}; };
let encodings = []; let encodings = [];
let simulcast = doSimulcast(); let parms = getSendParameters();
if(t.kind === 'video') { if(t.kind === 'video') {
let bps = getMaxVideoThroughput();
// Firefox doesn't like us setting the RID if we're not // Firefox doesn't like us setting the RID if we're not
// simulcasting. // simulcasting.
if(simulcast && c.label !== 'screenshare') { if(parms.simulcast) {
encodings.push({ encodings.push({
rid: 'h', rid: 'h',
maxBitrate: bps || unlimitedRate, maxBitrate: parms.maxBitrate,
scalabilityMode: parms.scalabilityMode,
}); });
encodings.push({ encodings.push({
rid: 'l', rid: 'l',
scaleResolutionDownBy: 2, scaleResolutionDownBy: 2,
maxBitrate: simulcastRate, maxBitrate: simulcastRate,
scalabilityMode: parms.scalabilityMode,
}); });
} else { } else {
encodings.push({ encodings.push({
maxBitrate: bps || unlimitedRate, maxBitrate: parms.maxBitrate,
scalabilityMode: parms.scalabilityMode,
}); });
} }
} else { } else {
@ -1277,22 +1289,11 @@ function setUpStream(c, stream) {
}); });
} }
} }
let tr = c.pc.addTransceiver(t, { c.pc.addTransceiver(t, {
direction: 'sendonly', direction: 'sendonly',
streams: [stream], streams: [stream],
sendEncodings: encodings, sendEncodings: encodings,
}); });
// Firefox before 110 does not implement sendEncodings, and
// requires this hack, which throws an exception on Chromium.
try {
let p = tr.sender.getParameters();
if(!p.encodings) {
p.encodings = encodings;
tr.sender.setParameters(p);
}
} catch(e) {
}
} }
// c.stream might be different from stream if there's a filter // c.stream might be different from stream if there's a filter
@ -3923,10 +3924,6 @@ async function start() {
); );
} }
// Disable simulcast on Firefox by default, it's buggy.
if(isFirefox())
getSelectElement('simulcastselect').value = 'off';
let parms = new URLSearchParams(window.location.search); let parms = new URLSearchParams(window.location.search);
if(window.location.search) if(window.location.search)
window.history.replaceState(null, '', window.location.pathname); window.history.replaceState(null, '', window.location.pathname);