@observertc/client-monitor-js
Version:
ObserveRTC Client Integration Javascript Library
1 lines • 6.85 kB
JavaScript
export class DefaultScoreCalculator{clientMonitor;static MAX_SCORE=5;static MIN_SCORE=0;static lastNScoresMaxLength=10;static lastNScoresMinLength=5;static TARGET_AUDIO_BITRATE=32e3;static MIN_AUDIO_BITRATE=6e3;static NORMALIZATION_FACTOR=Math.log10(this.TARGET_AUDIO_BITRATE/this.MIN_AUDIO_BITRATE);currentReasons={};totalReasons={};constructor(t){this.clientMonitor=t}update(){for(const t of this.clientMonitor.mappedPeerConnections.values())this._calculatePeerConnectionStabilityScore(t);for(const t of this.clientMonitor.tracks)this._calculateTrackScore(t);this._calculateClientMonitorScore()}encodeScoreReasons(t){return JSON.stringify(t??"{}")}_calculateClientMonitorScore(){const e=this.clientMonitor;let a=0,c=0;this.currentReasons={};for(const o of e.peerConnections){if(void 0===o.calculatedStabilityScore.value)continue;let e=0,r=0,l=!0;for(const a of o.tracks){const c=a.calculatedScore;void 0!==c.value&&(e+=c.value*c.weight,r+=c.weight,l=!1,t(this.currentReasons,c.reasons??{}))}a+=(l?DefaultScoreCalculator.MAX_SCORE:e/Math.max(r,1))*(Math.max(DefaultScoreCalculator.MIN_SCORE,o.calculatedStabilityScore.value)/DefaultScoreCalculator.MAX_SCORE)*o.calculatedStabilityScore.weight,c+=o.calculatedStabilityScore.weight,t(this.currentReasons,o.scoreReasons??{})}const o=a/Math.max(c,1);e.setScore(o,this.currentReasons),t(this.totalReasons,this.currentReasons)}_calculatePeerConnectionStabilityScore(t){const e=t.calculatedStabilityScore,a=1e3*(t.avgRttInSec??0),c=t.inboundRtps.reduce(((t,e)=>t+(e.deltaFractionLost??0)),0)+t.remoteInboundRtps.reduce(((t,e)=>t+(e.fractionLost??0)),0);let o=5,r=e.appData;const l={};r||(r={lastNScores:[]},e.appData=r),e.reasons=l,300<a?l["very-high-rtt"]=2:150<a&&(l["high-rtt"]=1),.01<c&&(l["high-packetloss"]=c<.05?1:c<.2?2:5),o=Math.max(DefaultScoreCalculator.MIN_SCORE,DefaultScoreCalculator.MAX_SCORE-this._getTotalSubtraction(l)),r.lastNScores.push(o);const i=this._calculateFinalScore(r.lastNScores);e.value=void 0!==i?this._getRoundedScore(i):void 0}_calculateTrackScore(t){switch(t.direction){case"inbound":switch(t.kind){case"audio":this._calculateInboundAudioTrackScore(t);break;case"video":this._calculateInboundVideoTrackScore(t)}break;case"outbound":switch(t.kind){case"audio":this._calculateOutboundAudioTrackScore(t);break;case"video":this._calculateOutboundVideoTrackScore(t)}}}_calculateInboundVideoTrackScore(t){if(!t.track.enabled||t.track.muted)return t.calculatedScore.appData&&(t.calculatedScore.appData=void 0),void(t.calculatedScore.value=void 0);const e=t.getInboundRtp();if(!e)return void(t.calculatedScore.value=void 0);let a=t.calculatedScore.appData;const c={};if(a||(a={lastNScores:[]},t.calculatedScore.appData=a),t.calculatedScore.reasons=c,e.framesPerSecond&&e.ewmaFps&&e.lastNFramesPerSec.length>=2){const t=e.lastNFramesPerSec.length,a=e.lastNFramesPerSec.reduce(((t,e)=>t+e),0)/t,o=e.lastNFramesPerSec.reduce(((t,e)=>t+Math.pow(e-a,2)),0)/t,r=Math.sqrt(o)/e.ewmaFps;.1<r&&r<.2?c["volatile-fps"]=1:.2<=r&&(c["volatile-fps"]=2)}if(e.framesDropped&&e.framesRendered){const t=e.framesDropped/(e.framesDropped+e.framesRendered);.1<t&&t<.2?c["dropped-video-frames"]=1:.2<t&&(c["dropped-video-frames"]=2)}e.deltaCorruptionProbability&&(c["video-frame-corruptions"]=2*e.deltaCorruptionProbability);const o=Math.max(DefaultScoreCalculator.MIN_SCORE,DefaultScoreCalculator.MAX_SCORE-this._getTotalSubtraction(c));a.lastNScores.push(o);const r=this._calculateFinalScore(a.lastNScores);t.calculatedScore.value=void 0!==r?this._getRoundedScore(r):void 0}_calculateOutboundVideoTrackScore(t){if(!t.track.enabled||t.track.muted)return t.calculatedScore.appData&&(t.calculatedScore.appData=void 0),void(t.calculatedScore.value=void 0);const e=t.getHighestLayer();if(!e)return void(t.calculatedScore.value=void 0);const a=t.calculatedScore;let c=a.appData;const o={};if(c||(c={lastNScores:[],diffBitrateSquares:[]},a.appData=c),a.reasons=o,"cpu"===e.qualityLimitationReason&&(o["cpu-limitation"]=2),"screen"!==t.track.contentHint){if(e.targetBitrate){const a=[...t.mappedOutboundRtps.values()].reduce(((t,e)=>t+(e.payloadBitrate??0)),0);if(a){const t=e.targetBitrate-a,c=t/e.targetBitrate,r=Math.max(2e4,.05*e.targetBitrate);0<t&&r<t&&(.05<=c&&c<.15?o["high-deviation-from-target-bitrate"]=1:.15<=c&&(o["high-deviation-from-target-bitrate"]=2))}}if(e.bitrate){if(c.ewmaBitrate?c.ewmaBitrate=.9*c.ewmaBitrate+.1*e.bitrate:c.ewmaBitrate=e.bitrate,c.lastBitrate){const t=Math.abs(c.lastBitrate-e.bitrate);for(c.diffBitrateSquares.push(t*t);c.diffBitrateSquares.length>10;)c.diffBitrateSquares.shift()}if(c.diffBitrateSquares.length>3){const t=c.diffBitrateSquares.reduce(((t,e)=>t+e),0)/c.diffBitrateSquares.length,e=Math.sqrt(t)/c.ewmaBitrate;.1<e&&e<.2?o["high-volatile-bitrate"]=1:.2<e&&(o["high-volatile-bitrate"]=2)}c.lastBitrate=e.bitrate}}const r=Math.max(DefaultScoreCalculator.MIN_SCORE,DefaultScoreCalculator.MAX_SCORE-this._getTotalSubtraction(o));c.lastNScores.push(r),a.value=this._calculateFinalScore(c.lastNScores)}_calculateInboundAudioTrackScore(t){if(!t.track.enabled||t.track.muted)return t.calculatedScore.appData&&(t.calculatedScore.appData=void 0),void(t.calculatedScore.value=void 0);const e=t.bitrate,a=t.getInboundRtp().deltaPacketsLost??0;if(!e)return void(t.calculatedScore.value=void 0);const c=Math.log10(Math.max(e,DefaultScoreCalculator.MIN_AUDIO_BITRATE)/DefaultScoreCalculator.MIN_AUDIO_BITRATE)/DefaultScoreCalculator.NORMALIZATION_FACTOR,o=Math.exp(-a/2),r=Math.max(DefaultScoreCalculator.MIN_SCORE,Math.min(DefaultScoreCalculator.MAX_SCORE,5*c*o));t.calculatedScore.value=this._getRoundedScore(r)}_calculateOutboundAudioTrackScore(t){if(!t.track.enabled||t.track.muted)return t.calculatedScore.appData&&(t.calculatedScore.appData=void 0),void(t.calculatedScore.value=void 0);const e=t.getOutboundRtps()?.[0];if(!e||void 0===e.bitrate)return void(t.calculatedScore.value=void 0);const a=e.getMediaSource()?.audioLevel;if(void 0!==a&&a<.01)return void(t.calculatedScore.value=void 0);const c=Math.log10(Math.max(e.bitrate,DefaultScoreCalculator.MIN_AUDIO_BITRATE)/DefaultScoreCalculator.MIN_AUDIO_BITRATE)/DefaultScoreCalculator.NORMALIZATION_FACTOR,o=Math.exp(-(e.getRemoteInboundRtp()?.deltaPacketsLost??0)/2),r=Math.max(DefaultScoreCalculator.MIN_SCORE,Math.min(DefaultScoreCalculator.MAX_SCORE,5*c*o));t.calculatedScore.value=this._getRoundedScore(r)}_calculateFinalScore(t){let e=0,a=0,c=0;if(DefaultScoreCalculator.lastNScoresMaxLength<t.length)t.shift();else if(t.length<DefaultScoreCalculator.lastNScoresMinLength)return;for(const o of t)a+=1,e+=a,c+=a*o;return c/e}_getRoundedScore(t){return Math.round(100*t)/100}_getTotalSubtraction(t){let e=0;for(const a of Object.keys(t)){const c=t[a];"number"==typeof c&&(e+=c)}return e}}function t(t,e){for(const[a,c]of Object.entries(e)){if("number"!=typeof c)continue;const e=a;t[e]=(t[e]??0)+c}return t}