diff --git a/static/protocol.js b/static/protocol.js index 1ac0261..46bec13 100644 --- a/static/protocol.js +++ b/static/protocol.js @@ -919,29 +919,59 @@ Stream.prototype.updateStats = async function() { let transceivers = c.pc.getTransceivers(); for(let i = 0; i < transceivers.length; i++) { let t = transceivers[i]; - let tid = t.sender.track && t.sender.track.id; - if(!tid) - continue; + let stid = t.sender.track && t.sender.track.id; + let rtid = t.receiver.track && t.receiver.track.id; - let report; - try { - report = await t.sender.getStats(); - } catch(e) { - continue; + let report = null; + if(stid) { + try { + report = await t.sender.getStats(); + } catch(e) { + } } - stats[tid] = {}; + if(report) { + for(let r of report.values()) { + if(stid && r.type === 'outbound-rtp') { + if(!('bytesSent' in r)) + continue; + if(!stats[stid]) + stats[stid] = {}; + stats[stid][r.type] = {}; + stats[stid][r.type].timestamp = r.timestamp; + stats[stid][r.type].bytesSent = r.bytesSent; + if(old[stid] && old[stid][r.type]) + stats[stid][r.type].rate = + ((r.bytesSent - old[stid][r.type].bytesSent) * 1000 / + (r.timestamp - old[stid][r.type].timestamp)) * 8; + } + } + } - for(let r of report.values()) { - if(r.type !== 'outbound-rtp') - continue; + report = null; + if(rtid) { + try { + report = await t.receiver.getStats(); + } catch(e) { + console.error(e); + } + } - stats[tid].timestamp = r.timestamp; - stats[tid].bytesSent = r.bytesSent; - if(old[tid] && old[tid].timestamp) { - stats[tid].rate = - ((r.bytesSent - old[tid].bytesSent) * 1000 / - (r.timestamp - old[tid].timestamp)) * 8; + if(report) { + for(let r of report.values()) { + if(rtid && r.type === 'track') { + if(!('totalAudioEnergy' in r)) + continue; + if(!stats[rtid]) + stats[rtid] = {}; + stats[rtid][r.type] = {}; + stats[rtid][r.type].timestamp = r.timestamp; + stats[rtid][r.type].totalAudioEnergy = r.totalAudioEnergy; + if(old[rtid] && old[rtid][r.type]) + stats[rtid][r.type].audioEnergy = + (r.totalAudioEnergy - old[rtid][r.type].totalAudioEnergy) * 1000 / + (r.timestamp - old[rtid][r.type].timestamp); + } } } } diff --git a/static/sfu.css b/static/sfu.css index c08afb8..e222010 100644 --- a/static/sfu.css +++ b/static/sfu.css @@ -610,6 +610,11 @@ h1 { margin-top: auto; margin-bottom: auto; position: relative; + border: 2px solid rgba(0,0,0,0); +} + +.peer-active { + border: 2px solid #610a86; } .media { @@ -670,6 +675,11 @@ h1 { margin-top: 15px; } +.sidenav form{ + display: block; + margin-top: 15px; +} + .sidenav-header h2{ color: #fff; padding: 10px; diff --git a/static/sfu.html b/static/sfu.html index 8295ba5..74b2cc4 100644 --- a/static/sfu.html +++ b/static/sfu.html @@ -172,6 +172,10 @@ + +
diff --git a/static/sfu.js b/static/sfu.js index eaf3370..d99e6aa 100644 --- a/static/sfu.js +++ b/static/sfu.js @@ -142,6 +142,9 @@ function gotDownStream(c) { c.onstatus = function(status) { setMediaStatus(c); } + c.onstats = gotDownStats; + if(document.getElementById('activitybox').value) + c.setStatsInterval(activityDetectionInterval); } // Store current browser viewport height in css variable @@ -278,7 +281,23 @@ document.getElementById('requestselect').onchange = function(e) { serverConnection.request(this.value); }; -function displayStats(stats) { +const activityDetectionInterval = 100; +const activityDetectionPeriod = 700; +const activityDetectionThreshold = 0.2; + +document.getElementById('activitybox').onchange = function(e) { + for(let id in serverConnection.down) { + let c = serverConnection.down[id]; + if(this.value) + c.setStatsInterval(activityDetectionInterval); + else { + setActive(c, false); + c.setStatsInterval(0); + } + } +} + +function gotUpStats(stats) { let c = this; let text = ''; @@ -286,16 +305,57 @@ function displayStats(stats) { c.pc.getSenders().forEach(s => { let tid = s.track && s.track.id; let stats = tid && c.stats[tid]; - if(stats && stats.rate > 0) { + let rate = stats && stats['outbound-rtp'] && stats['outbound-rtp'].rate; + if(typeof rate === 'number') { if(text) text = text + ' + '; - text = text + Math.round(stats.rate / 1000) + 'kbps'; + text = text + Math.round(rate / 1000) + 'kbps'; } }); setLabel(c, text); } +/** + * @param {Stream} c + * @param {boolean} value + */ +function setActive(c, value) { + let peer = document.getElementById('peer-' + c.id); + if(value) + peer.classList.add('peer-active'); + else + peer.classList.remove('peer-active'); +} + +function gotDownStats(stats) { + if(!document.getElementById('activitybox').value) + return; + + let c = this; + + let maxEnergy = 0; + + c.pc.getReceivers().forEach(r => { + let tid = r.track && r.track.id; + let s = tid && stats[tid]; + let energy = s && s['track'] && s['track'].audioEnergy; + if(typeof energy === 'number') + maxEnergy = Math.max(maxEnergy, energy); + }); + + // totalAudioEnergy is defined as the integral of the square of the + // volume, so square the threshold. + if(maxEnergy > activityDetectionThreshold * activityDetectionThreshold) { + c.userdata.lastVoiceActivity = Date.now(); + setActive(c, true); + } else { + let last = c.userdata.lastVoiceActivity; + if(!last || Date.now() - last > activityDetectionPeriod) + setActive(c, false); + } +} + function mapMediaOption(value) { console.assert(typeof(value) === 'string'); switch(value) { @@ -457,7 +517,7 @@ async function addLocalMedia(id) { let sender = c.pc.addTrack(t, stream); }); - c.onstats = displayStats; + c.onstats = gotUpStats; c.setStatsInterval(2000); await setMedia(c, true); setButtonsVisibility(); @@ -486,7 +546,7 @@ async function addShareMedia(setup) { }; c.labels[t.id] = 'screenshare'; }); - c.onstats = displayStats; + c.onstats = gotUpStats; c.setStatsInterval(2000); await setMedia(c, true); setButtonsVisibility()