New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

webrtc-issue-detector

Package Overview
Dependencies
Maintainers
0
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

webrtc-issue-detector - npm Package Compare versions

Comparing version 1.14.0-debug-detectors.1 to 1.14.0-debug-detectors.2

2

dist/bundle-cjs.js

@@ -1,1 +0,1 @@

"use strict";var e,t,s,o;function r(){}function n(){n.init.call(this)}function i(e){return void 0===e._maxListeners?n.defaultMaxListeners:e._maxListeners}function a(e,t,s){if(t)e.call(s);else for(var o=e.length,r=m(e,o),n=0;n<o;++n)r[n].call(s)}function c(e,t,s,o){if(t)e.call(s,o);else for(var r=e.length,n=m(e,r),i=0;i<r;++i)n[i].call(s,o)}function u(e,t,s,o,r){if(t)e.call(s,o,r);else for(var n=e.length,i=m(e,n),a=0;a<n;++a)i[a].call(s,o,r)}function d(e,t,s,o,r,n){if(t)e.call(s,o,r,n);else for(var i=e.length,a=m(e,i),c=0;c<i;++c)a[c].call(s,o,r,n)}function h(e,t,s,o){if(t)e.apply(s,o);else for(var r=e.length,n=m(e,r),i=0;i<r;++i)n[i].apply(s,o)}function p(e,t,s,o){var n,a,c,u;if("function"!=typeof s)throw new TypeError('"listener" argument must be a function');if((a=e._events)?(a.newListener&&(e.emit("newListener",t,s.listener?s.listener:s),a=e._events),c=a[t]):(a=e._events=new r,e._eventsCount=0),c){if("function"==typeof c?c=a[t]=o?[s,c]:[c,s]:o?c.unshift(s):c.push(s),!c.warned&&(n=i(e))&&n>0&&c.length>n){c.warned=!0;var d=new Error("Possible EventEmitter memory leak detected. "+c.length+" "+t+" listeners added. Use emitter.setMaxListeners() to increase limit");d.name="MaxListenersExceededWarning",d.emitter=e,d.type=t,d.count=c.length,u=d,"function"==typeof console.warn?console.warn(u):console.log(u)}}else c=a[t]=s,++e._eventsCount;return e}function l(e,t,s){var o=!1;function r(){e.removeListener(t,r),o||(o=!0,s.apply(e,arguments))}return r.listener=s,r}function f(e){var t=this._events;if(t){var s=t[e];if("function"==typeof s)return 1;if(s)return s.length}return 0}function m(e,t){for(var s=new Array(t);t--;)s[t]=e[t];return s}Object.defineProperty(exports,"__esModule",{value:!0}),r.prototype=Object.create(null),n.EventEmitter=n,n.usingDomains=!1,n.prototype.domain=void 0,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.init=function(){this.domain=null,n.usingDomains&&undefined.active,this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=new r,this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},n.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||isNaN(e))throw new TypeError('"n" argument must be a positive number');return this._maxListeners=e,this},n.prototype.getMaxListeners=function(){return i(this)},n.prototype.emit=function(e){var t,s,o,r,n,i,p,l="error"===e;if(i=this._events)l=l&&null==i.error;else if(!l)return!1;if(p=this.domain,l){if(t=arguments[1],!p){if(t instanceof Error)throw t;var f=new Error('Uncaught, unspecified "error" event. ('+t+")");throw f.context=t,f}return t||(t=new Error('Uncaught, unspecified "error" event')),t.domainEmitter=this,t.domain=p,t.domainThrown=!1,p.emit("error",t),!1}if(!(s=i[e]))return!1;var m="function"==typeof s;switch(o=arguments.length){case 1:a(s,m,this);break;case 2:c(s,m,this,arguments[1]);break;case 3:u(s,m,this,arguments[1],arguments[2]);break;case 4:d(s,m,this,arguments[1],arguments[2],arguments[3]);break;default:for(r=new Array(o-1),n=1;n<o;n++)r[n-1]=arguments[n];h(s,m,this,r)}return!0},n.prototype.addListener=function(e,t){return p(this,e,t,!1)},n.prototype.on=n.prototype.addListener,n.prototype.prependListener=function(e,t){return p(this,e,t,!0)},n.prototype.once=function(e,t){if("function"!=typeof t)throw new TypeError('"listener" argument must be a function');return this.on(e,l(this,e,t)),this},n.prototype.prependOnceListener=function(e,t){if("function"!=typeof t)throw new TypeError('"listener" argument must be a function');return this.prependListener(e,l(this,e,t)),this},n.prototype.removeListener=function(e,t){var s,o,n,i,a;if("function"!=typeof t)throw new TypeError('"listener" argument must be a function');if(!(o=this._events))return this;if(!(s=o[e]))return this;if(s===t||s.listener&&s.listener===t)0==--this._eventsCount?this._events=new r:(delete o[e],o.removeListener&&this.emit("removeListener",e,s.listener||t));else if("function"!=typeof s){for(n=-1,i=s.length;i-- >0;)if(s[i]===t||s[i].listener&&s[i].listener===t){a=s[i].listener,n=i;break}if(n<0)return this;if(1===s.length){if(s[0]=void 0,0==--this._eventsCount)return this._events=new r,this;delete o[e]}else!function(e,t){for(var s=t,o=s+1,r=e.length;o<r;s+=1,o+=1)e[s]=e[o];e.pop()}(s,n);o.removeListener&&this.emit("removeListener",e,a||t)}return this},n.prototype.off=function(e,t){return this.removeListener(e,t)},n.prototype.removeAllListeners=function(e){var t,s;if(!(s=this._events))return this;if(!s.removeListener)return 0===arguments.length?(this._events=new r,this._eventsCount=0):s[e]&&(0==--this._eventsCount?this._events=new r:delete s[e]),this;if(0===arguments.length){for(var o,n=Object.keys(s),i=0;i<n.length;++i)"removeListener"!==(o=n[i])&&this.removeAllListeners(o);return this.removeAllListeners("removeListener"),this._events=new r,this._eventsCount=0,this}if("function"==typeof(t=s[e]))this.removeListener(e,t);else if(t)do{this.removeListener(e,t[t.length-1])}while(t[0]);return this},n.prototype.listeners=function(e){var t,s=this._events;return s&&(t=s[e])?"function"==typeof t?[t.listener||t]:function(e){for(var t=new Array(e.length),s=0;s<t.length;++s)t[s]=e[s].listener||e[s];return t}(t):[]},n.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):f.call(e,t)},n.prototype.listenerCount=f,n.prototype.eventNames=function(){return this._eventsCount>0?Reflect.ownKeys(this._events):[]};class g extends n{}exports.EventType=void 0,(e=exports.EventType||(exports.EventType={})).Issue="issue",e.NetworkScoresUpdated="network-scores-updated",e.StatsParsingFinished="stats-parsing-finished",exports.IssueType=void 0,(t=exports.IssueType||(exports.IssueType={})).Network="network",t.CPU="cpu",t.Server="server",t.Stream="stream",exports.IssueReason=void 0,(s=exports.IssueReason||(exports.IssueReason={})).OutboundNetworkQuality="outbound-network-quality",s.InboundNetworkQuality="inbound-network-quality",s.OutboundNetworkMediaLatency="outbound-network-media-latency",s.InboundNetworkMediaLatency="inbound-network-media-latency",s.NetworkMediaSyncFailure="network-media-sync-failure",s.OutboundNetworkThroughput="outbound-network-throughput",s.InboundNetworkThroughput="inbound-network-throughput",s.EncoderCPUThrottling="encoder-cpu-throttling",s.DecoderCPUThrottling="decoder-cpu-throttling",s.ServerIssue="server-issue",s.UnknownVideoDecoderIssue="unknown-video-decoder",s.LowInboundMOS="low-inbound-mean-opinion-score",s.LowOutboundMOS="low-outbound-mean-opinion-score",s.FrozenVideoTrack="frozen-video-track",exports.MosQuality=void 0,(o=exports.MosQuality||(exports.MosQuality={}))[o.BAD=2.1]="BAD",o[o.POOR=2.6]="POOR",o[o.FAIR=3.1]="FAIR",o[o.GOOD=3.8]="GOOD",o[o.EXCELLENT=4.3]="EXCELLENT";class S extends n{static STATS_REPORT_READY_EVENT="stats-report-ready";static STATS_REPORTS_PARSED="stats-reports-parsed";isStopped=!1;reportTimer;getStatsInterval;compositeStatsParser;constructor(e){super(),this.compositeStatsParser=e.compositeStatsParser,this.getStatsInterval=e.getStatsInterval??1e4}get isRunning(){return!!this.reportTimer&&!this.isStopped}startReporting(){if(this.reportTimer)return;const e=()=>setTimeout((()=>{this.isStopped?this.reportTimer=void 0:this.parseReports().finally((()=>{this.reportTimer=e()}))}),this.getStatsInterval);this.isStopped=!1,this.reportTimer=e()}stopReporting(){this.isStopped=!0,this.reportTimer&&(clearTimeout(this.reportTimer),this.reportTimer=void 0)}async parseReports(){const e=Date.now(),t=await this.compositeStatsParser.parse(),s=Date.now()-e;this.emit(S.STATS_REPORTS_PARSED,{timeTaken:s,reportItems:t}),t.forEach((e=>{this.emit(S.STATS_REPORT_READY_EVENT,e)}))}}const v=(()=>{const e=new Map;return t=>{const{taskId:s,delayMs:o,maxJitterMs:r,callback:n}=t,i=Math.ceil(Math.random()*(r||0)),a=e.get(s);a&&clearTimeout(a);const c=setTimeout((()=>{n(),e.delete(s)}),o+i);e.set(s,c)}})();class k{#e={};calculate(e){const{connection:{id:t}}=e,{mos:s,stats:o}=this.calculateOutboundScore(e)||{},{mos:r,stats:n}=this.calculateInboundScore(e)||{};return this.#e[t]=e,v({taskId:t,delayMs:35e3,callback:()=>delete this.#e[t]}),{outbound:s,inbound:r,connectionId:t,statsSamples:{inboundStatsSample:n,outboundStatsSample:o}}}calculateOutboundScore(e){const t=[...e.remote?.audio.inbound||[],...e.remote?.video.inbound||[]];if(!t.length)return;const s=this.#e[e.connection.id];if(!s)return;const o=[...s.remote?.audio.inbound||[],...s.remote?.video.inbound||[]],{packetsSent:r}=e.connection,n=s.connection.packetsSent,i=t.reduce(((e,t)=>{const s=o.find((e=>e.ssrc===t.ssrc));return{sumJitter:e.sumJitter+t.jitter,packetsLost:e.packetsLost+t.packetsLost,lastPacketsLost:e.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,packetsLost:0,lastPacketsLost:0}),a=1e3*e.connection.currentRoundTripTime||0,{sumJitter:c}=i,u=c/t.length,d=r-n,h=i.packetsLost-i.lastPacketsLost,p=d&&h?Math.round(100*h/(d+h)):0;return{mos:this.calculateMOS({avgJitter:u,rtt:a,packetsLoss:p}),stats:{avgJitter:u,rtt:a,packetsLoss:p}}}calculateInboundScore(e){const t=[...e.audio?.inbound,...e.video?.inbound];if(!t.length)return;const s=this.#e[e.connection.id];if(!s)return;const o=[...s.video?.inbound,...s.audio?.inbound],{packetsReceived:r}=e.connection,n=s.connection.packetsReceived,i=t.reduce(((e,t)=>{const s=o.find((e=>e.ssrc===t.ssrc));return{sumJitter:e.sumJitter+t.jitter,packetsLost:e.packetsLost+t.packetsLost,lastPacketsLost:e.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,packetsLost:0,lastPacketsLost:0}),a=1e3*e.connection.currentRoundTripTime||0,{sumJitter:c}=i,u=c/t.length,d=r-n,h=i.packetsLost-i.lastPacketsLost,p=d&&h?Math.round(100*h/(d+h)):0;return{mos:this.calculateMOS({avgJitter:u,rtt:a,packetsLoss:p}),stats:{avgJitter:u,rtt:a,packetsLoss:p}}}calculateMOS({avgJitter:e,rtt:t,packetsLoss:s}){const o=t+2*e+10;let r=o<160?93.2-o/40:93.2-o/120-10;return r-=2.5*s,1+.035*r+7e-6*r*(r-60)*(100-r)}}class y{#t=new Map;#s;#o;constructor(e={}){this.#s=e.statsCleanupTtlMs??35e3,this.#o=e.maxParsedStatsStorageSize??5}detect(e,t){const s={...e,networkScores:{...t,statsSamples:t?.statsSamples||{}}},o=this.performDetection(s);return this.setLastProcessedStats(e.connection.id,s),this.performPrevStatsCleanup({connectionId:e.connection.id}),o}performPrevStatsCleanup(e){const{connectionId:t,cleanupCallback:s}=e;this.#t.has(t)&&v({taskId:t,delayMs:this.#s,callback:()=>{this.deleteLastProcessedStats(t),"function"==typeof s&&s()}})}setLastProcessedStats(e,t){if(!e||t.connection.id!==e)return;const s=this.#t.get(e)??[];s.push(t),s.length>this.#o&&s.shift(),this.#t.set(e,s)}getLastProcessedStats(e){const t=this.#t.get(e);return t?.[t.length-1]}getAllLastProcessedStats(e){return this.#t.get(e)??[]}deleteLastProcessedStats(e){this.#t.delete(e)}}class T extends y{#r;constructor(e={}){super(e),this.#r=e.availableOutgoingBitrateThreshold??1e5}performDetection(e){const t=[],{availableOutgoingBitrate:s}=e.connection;if(void 0===s)return t;const o=e.audio.outbound.reduce(((e,t)=>e+t.targetBitrate),0),r=e.video.outbound.reduce(((e,t)=>e+t.bitrate),0);if(!o&&!r)return t;const n={availableOutgoingBitrate:s,videoStreamsTotalBitrate:r,audioStreamsTotalTargetBitrate:o};return o>s||r>0&&s<this.#r?(t.push({statsSample:n,type:exports.IssueType.Network,reason:exports.IssueReason.OutboundNetworkThroughput}),t):t}}class w extends y{#n;#i;#a;#c;constructor(e={}){super(),this.#n=e.highPacketLossThresholdPct??5,this.#i=e.highJitterThreshold??200,this.#a=e.highJitterBufferDelayThresholdMs??500,this.#c=e.highRttThresholdMs??250}performDetection(e){return this.processData(e)}processData(e){const t=[],s=[...e.audio?.inbound,...e.video?.inbound];if(!s.length)return t;const o=this.getLastProcessedStats(e.connection.id);if(!o)return t;const r=[...o.video?.inbound,...o.audio?.inbound],{packetsReceived:n}=e.connection,i=o.connection.packetsReceived,a=s.reduce(((e,t)=>{const s=r.find((e=>e.ssrc===t.ssrc)),o=s?.jitterBufferDelay||0,n=s?.jitterBufferEmittedCount||0,i=t.jitterBufferDelay-o,a=t.jitterBufferEmittedCount-n,c=i&&a?1e3*i/a:0;return{sumJitter:e.sumJitter+t.jitter,sumJitterBufferDelayMs:e.sumJitterBufferDelayMs+c,packetsLost:e.packetsLost+t.packetsLost,lastPacketsLost:e.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,sumJitterBufferDelayMs:0,packetsLost:0,lastPacketsLost:0}),c=1e3*e.connection.currentRoundTripTime||0,{sumJitter:u,sumJitterBufferDelayMs:d}=a,h=u/s.length,p=d/s.length,l=n-i,f=a.packetsLost-a.lastPacketsLost,m=l&&f?Math.round(100*f/(l+f)):0,g=m>this.#n,S=h>=this.#i,v=c>=this.#c,k=p>this.#a,y=v&&!S&&!g,T=g&&S,w=S&&k,b={rtt:c,packetLossPct:m,avgJitter:h,avgJitterBufferDelay:p};return(S||g)&&t.push({statsSample:b,type:exports.IssueType.Network,reason:exports.IssueReason.InboundNetworkQuality,iceCandidate:e.connection.local.id}),y&&t.push({statsSample:b,type:exports.IssueType.Server,reason:exports.IssueReason.ServerIssue,iceCandidate:e.connection.remote.id}),T&&t.push({statsSample:b,type:exports.IssueType.Network,reason:exports.IssueReason.InboundNetworkMediaLatency,iceCandidate:e.connection.local.id}),w&&t.push({statsSample:b,type:exports.IssueType.Network,reason:exports.IssueReason.NetworkMediaSyncFailure,iceCandidate:e.connection.local.id}),t}}class b extends y{#u;constructor(e={}){super(),this.#u=e.correctedSamplesThresholdPct??5}performDetection(e){return this.processData(e)}processData(e){const t=e.audio.inbound,s=[],o=this.getLastProcessedStats(e.connection.id)?.audio.inbound;return o?(t.forEach((e=>{const t=o.find((t=>t.ssrc===e.ssrc));if(!t)return;const r=e.track.insertedSamplesForDeceleration+e.track.removedSamplesForAcceleration,n=t.track.insertedSamplesForDeceleration+t.track.removedSamplesForAcceleration;if(r===n)return;const i=e.track.totalSamplesReceived-t.track.totalSamplesReceived,a=r-n,c=Math.round(100*a/i),u={correctedSamplesPct:c};c>this.#u&&s.push({statsSample:u,type:exports.IssueType.Network,reason:exports.IssueReason.NetworkMediaSyncFailure,ssrc:e.ssrc})})),s):s}}class P extends y{#n;#i;constructor(e={}){super(),this.#n=e.highPacketLossThresholdPct??5,this.#i=e.highJitterThreshold??200}performDetection(e){return this.processData(e)}processData(e){const t=[],s=[...e.remote?.audio.inbound||[],...e.remote?.video.inbound||[]];if(!s.length)return t;const o=this.getLastProcessedStats(e.connection.id);if(!o)return t;const r=[...o.remote?.audio.inbound||[],...o.remote?.video.inbound||[]],{packetsSent:n}=e.connection,i=o.connection.packetsSent,a=s.reduce(((e,t)=>{const s=r.find((e=>e.ssrc===t.ssrc));return{sumJitter:e.sumJitter+t.jitter,packetsLost:e.packetsLost+t.packetsLost,lastPacketsLost:e.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,packetsLost:0,lastPacketsLost:0}),c=1e3*e.connection.currentRoundTripTime||0,{sumJitter:u}=a,d=u/s.length,h=n-i,p=a.packetsLost-a.lastPacketsLost,l=h&&p?Math.round(100*p/(h+p)):0,f=l>this.#n,m=d>=this.#i,g=!f&&m||m||f,S={rtt:c,avgJitter:d,packetLossPct:l};return f&&m&&t.push({statsSample:S,type:exports.IssueType.Network,reason:exports.IssueReason.OutboundNetworkMediaLatency,iceCandidate:e.connection.local.id}),g&&t.push({statsSample:S,type:exports.IssueType.Network,reason:exports.IssueReason.OutboundNetworkQuality,iceCandidate:e.connection.local.id}),t}}class L extends y{performDetection(e){return this.processData(e)}processData(e){const t=e.video.outbound.filter((e=>"none"!==e.qualityLimitationReason)),s=[],o=this.getLastProcessedStats(e.connection.id)?.video.outbound;return o?(t.forEach((e=>{const t=o.find((t=>t.ssrc===e.ssrc));if(!t)return;const r={qualityLimitationReason:e.qualityLimitationReason};e.framesSent>t.framesSent||("cpu"===e.qualityLimitationReason&&s.push({statsSample:r,type:exports.IssueType.CPU,reason:exports.IssueReason.EncoderCPUThrottling,ssrc:e.ssrc}),"bandwidth"===e.qualityLimitationReason&&s.push({statsSample:r,type:exports.IssueType.Network,reason:exports.IssueReason.OutboundNetworkThroughput,ssrc:e.ssrc}))})),s):s}}class I extends y{UNKNOWN_DECODER="unknown";#d={};performDetection(e){return this.processData(e)}performPrevStatsCleanup(e){const{connectionId:t,cleanupCallback:s}=e;super.performPrevStatsCleanup({...e,cleanupCallback:()=>{delete this.#d[t],"function"==typeof s&&s()}})}processData(e){const t=[],{id:s}=e.connection,o=this.getLastProcessedStats(s)?.video.inbound;return e.video.inbound.forEach((e=>{const{decoderImplementation:r,ssrc:n}=e,i=o?.find((e=>e.ssrc===n));if(i)if(r===this.UNKNOWN_DECODER){if(!this.hadLastDecoderWithIssue(s,n)){this.setLastDecoderWithIssue(s,n,this.UNKNOWN_DECODER);const o={mimeType:e.mimeType,decoderImplementation:r};t.push({ssrc:n,statsSample:o,type:exports.IssueType.Stream,reason:exports.IssueReason.UnknownVideoDecoderIssue,trackIdentifier:e.track.trackIdentifier})}}else this.setLastDecoderWithIssue(s,n,void 0)})),t}setLastDecoderWithIssue(e,t,s){const o=this.#d[e]??{};void 0===s?delete o[t]:o[t]=s,this.#d[e]=o}hadLastDecoderWithIssue(e,t){const s=this.#d[e];return(s&&s[t])===this.UNKNOWN_DECODER}}const D=(e,t)=>{for(let s=1;s<t.length;s+=1){const o=t[s].video.inbound.find((t=>t.ssrc===e));if(!o)continue;const r=t[s-1].video.inbound.find((t=>t.ssrc===e)),n=o.frameWidth!==r?.frameWidth,i=o.frameHeight!==r?.frameHeight;if(n||i)return!0}return!1};class R extends y{#h;#p;constructor(e={}){super(),this.#h=e.avgFreezeDurationThresholdMs??1e3,this.#p=e.frozenDurationThresholdPct??30}performDetection(e){const t=e.networkScores.inbound;return void 0!==t&&t<=exports.MosQuality.BAD?[]:this.processData(e)}processData(e){const t=[],s=this.getAllLastProcessedStats(e.connection.id);if(0===s.length)return[];const o=e.video.inbound.map((t=>{const o=s[s.length-1].video.inbound.find((e=>e.ssrc===t.ssrc));if(!o)return;if(D(t.ssrc,[s[s.length-1],e]))return;const r=t.freezeCount-(o.freezeCount??0),n=1e3*(t.totalFreezesDuration-(o.totalFreezesDuration??0)),i=r>0?n/r:0,a=n/(t.timestamp-o.timestamp)*100;return a>this.#p||i>this.#h?{ssrc:t.ssrc,avgFreezeDurationMs:i,frozenDurationPct:a}:void 0})).filter((e=>void 0!==e));return o.length>0&&t.push({type:exports.IssueType.Stream,reason:exports.IssueReason.FrozenVideoTrack,statsSample:{ssrcs:o.map((e=>e.ssrc))}}),t}}class C extends y{#l;#f;constructor(e={}){super(e),this.#l=e.volatilityThreshold??1.5,this.#f=e.affectedStreamsPercentThreshold??50}performDetection(e){return[...this.getAllLastProcessedStats(e.connection.id),e].find((e=>void 0!==e.networkScores.inbound&&e.networkScores.inbound<=exports.MosQuality.BAD))?[]:this.processData(e)}processData(e){const t=[],s=[...this.getAllLastProcessedStats(e.connection.id),e],o=e.video.inbound.map((e=>{const t=[];if(D(e.ssrc,s))return;if(s.length<5)return;for(let o=1;o<s.length;o+=1){let r=0,n=0,i=0;const a=s[o].video.inbound.find((t=>t.ssrc===e.ssrc));if(!a)continue;const c=s[o-1].video.inbound.find((t=>t.ssrc===e.ssrc));c&&(r=a.framesDecoded-c.framesDecoded,n=a.totalDecodeTime-c.totalDecodeTime),n>0&&r>0&&(i=1e3*n/r),t.push(i)}const o=t.reduce(((e,t)=>e+t),0)/t.length,r=t.map((e=>(e-o)**2)),n=r.reduce(((e,t)=>e+t),0)/r.length,i=Math.sqrt(n),a=t.every(((e,s)=>0===s||e>t[s-1]));return console.log("THROTTLE",{isDecodeTimePerFrameIncrease:a,volatility:i,mean:o}),i>this.#l&&a?(console.log("THROTTLE DETECTED"),{ssrc:e.ssrc,allDecodeTimePerFrame:t,volatility:i}):void 0})).filter((e=>Boolean(e))),r=o.length/(e.video.inbound.length/100);return r>this.#f&&(t.push({type:exports.IssueType.CPU,reason:exports.IssueReason.DecoderCPUThrottling,statsSample:{affectedStreamsPercent:r,throtthedStreams:o}}),this.deleteLastProcessedStats(e.connection.id)),t}}const x=e=>"closed"===e.iceConnectionState||"closed"===e.connectionState,E=(e,t,s)=>8*((e,t,s)=>{if(!t)return 0;const o=e[s],r=t[s];if(null==o||null==r)return 0;const n=Math.floor(e.timestamp)-Math.floor(t.timestamp);return 0===n?0:(Number(o)-Number(r))/n*1e3})(e,t,s);class M{connections=[];statsParser;constructor(e){this.statsParser=e.statsParser}listConnections(){return[...this.connections]}addPeerConnection(e){this.connections.push({id:e.id??String(Date.now()+Math.random().toString(32)),pc:e.pc})}removePeerConnection(e){const t=this.connections.findIndex((({pc:t})=>t===e.pc));t>=0&&this.removeConnectionsByIndexes([t])}async parse(){const e=[],t=this.connections.map((async(t,s)=>{if(!x(t.pc))return this.statsParser.parse(t);e.unshift(s)}));e.length&&this.removeConnectionsByIndexes(e);return(await Promise.all(t)).filter((e=>void 0!==e))}removeConnectionsByIndexes(e){e.forEach((e=>{this.connections.splice(e,1)}))}}class N{prevStats=new Map;allowedReportTypes=new Set(["candidate-pair","inbound-rtp","outbound-rtp","remote-outbound-rtp","remote-inbound-rtp","track","transport"]);ignoreSSRCList;logger;constructor(e){this.ignoreSSRCList=e.ignoreSSRCList??[],this.logger=e.logger}get previouslyParsedStatsConnectionsIds(){return[...this.prevStats.keys()]}async parse(e){if(!x(e.pc))return this.getConnectionStats(e);this.logger.debug("Skip stats parsing. Connection is closed.",{connection:e})}async getConnectionStats(e){const{pc:t,id:s}=e;try{const o=Date.now(),r=t.getReceivers().filter((e=>e.track?.enabled)),n=t.getSenders().filter((e=>e.track?.enabled)),i=await Promise.all(r.map((e=>e.getStats()))),a=await Promise.all(n.map((e=>e.getStats())));return{id:s,stats:this.mapReportsStats([...i,...a],e),timeTaken:Date.now()-o}}catch(e){return void this.logger.error("Failed to get stats for PC",{id:s,pc:t,error:e})}}mapReportsStats(e,t){const s={audio:{inbound:[],outbound:[]},video:{inbound:[],outbound:[]},connection:{},remote:{video:{inbound:[],outbound:[]},audio:{inbound:[],outbound:[]}}};e.forEach((e=>{e.forEach((t=>{this.allowedReportTypes.has(t.type)&&this.updateMappedStatsWithReportItemData(t,s,e)}))}));const{id:o}=t,r=this.prevStats.get(o);return r&&this.propagateStatsWithRateValues(s,r.stats),this.prevStats.set(o,{stats:s,ts:Date.now()}),v({taskId:o,delayMs:35e3,callback:()=>this.prevStats.delete(o)}),s}updateMappedStatsWithReportItemData(e,t,s){const o=e.type;if("candidate-pair"===o&&"succeeded"===e.state&&e.nominated)return void(t.connection=this.prepareConnectionStats(e,s));const r=this.getMediaType(e);if(!r)return;const n=e.ssrc;if(!n||!this.ignoreSSRCList.includes(n))if("outbound-rtp"!==o)if("inbound-rtp"!==o)"remote-outbound-rtp"!==o?"remote-inbound-rtp"===o&&(this.mapConnectionStatsIfNecessary(t,e,s),t.remote[r].inbound.push({...e})):t.remote[r].outbound.push({...e});else{const o=s.get(e.trackId)||s.get(e.mediaSourceId)||{};this.mapConnectionStatsIfNecessary(t,e,s);const n={...e,track:{...o}};t[r].inbound.push(n)}else{const o=s.get(e.trackId)||s.get(e.mediaSourceId)||{},n={...e,track:{...o}};t[r].outbound.push(n)}}getMediaType(e){const t=e.mediaType||e.kind;if(!["audio","video"].includes(t)){const{id:t}=e;if(!t)return;return String(t).includes("Video")?"video":String(t).includes("Audio")?"audio":void 0}return t}propagateStatsWithRateValues(e,t){e.audio.inbound.forEach((e=>{const s=t.audio.inbound.find((({id:t})=>t===e.id));e.bitrate=E(e,s,"bytesReceived"),e.packetRate=E(e,s,"packetsReceived")})),e.audio.outbound.forEach((e=>{const s=t.audio.outbound.find((({id:t})=>t===e.id));e.bitrate=E(e,s,"bytesSent"),e.packetRate=E(e,s,"packetsSent")})),e.video.inbound.forEach((e=>{const s=t.video.inbound.find((({id:t})=>t===e.id));e.bitrate=E(e,s,"bytesReceived"),e.packetRate=E(e,s,"packetsReceived")})),e.video.outbound.forEach((e=>{const s=t.video.outbound.find((({id:t})=>t===e.id));e.bitrate=E(e,s,"bytesSent"),e.packetRate=E(e,s,"packetsSent")}))}mapConnectionStatsIfNecessary(e,t,s){if(e.connection.id||!t.transportId)return;const o=s.get(t.transportId);if(o&&o.selectedCandidatePairId){const t=s.get(o.selectedCandidatePairId);e.connection=this.prepareConnectionStats(t,s)}}prepareConnectionStats(e,t){if(!e||!t)return{};const s={...e};if(s.remoteCandidateId){const e=t.get(s.remoteCandidateId);s.remote={...e}}if(s.localCandidateId){const e=t.get(s.localCandidateId);s.local={...e}}return s}}exports.AvailableOutgoingBitrateIssueDetector=T,exports.BaseIssueDetector=y,exports.CompositeRTCStatsParser=M,exports.FrozenVideoTrackDetector=R,exports.InboundNetworkIssueDetector=w,exports.NetworkMediaSyncIssueDetector=b,exports.NetworkScoresCalculator=k,exports.OutboundNetworkIssueDetector=P,exports.PeriodicWebRTCStatsReporter=S,exports.QualityLimitationsIssueDetector=L,exports.RTCStatsParser=N,exports.UnknownVideoDecoderImplementationDetector=I,exports.VideoDecoderIssueDetector=C,exports.WebRTCIssueEmitter=g,exports.default=class{eventEmitter;#m=!1;detectors=[];networkScoresCalculator;statsReporter;compositeStatsParser;logger;autoAddPeerConnections;constructor(e){this.logger=e.logger??{debug:()=>{},info:()=>{},warn:()=>{},error:()=>{}},this.eventEmitter=e.issueEmitter??new g,e.onIssues&&this.eventEmitter.on(exports.EventType.Issue,e.onIssues),e.onNetworkScoresUpdated&&this.eventEmitter.on(exports.EventType.NetworkScoresUpdated,e.onNetworkScoresUpdated),this.detectors=e.detectors??[new L,new w,new P,new b,new T,new I,new R,new C],this.networkScoresCalculator=e.networkScoresCalculator??new k,this.compositeStatsParser=e.compositeStatsParser??new M({statsParser:new N({ignoreSSRCList:e.ignoreSSRCList,logger:this.logger})}),this.statsReporter=e.statsReporter??new S({compositeStatsParser:this.compositeStatsParser,getStatsInterval:e.getStatsInterval??5e3}),window.wid=this,this.autoAddPeerConnections=e.autoAddPeerConnections??!0,this.autoAddPeerConnections&&this.wrapRTCPeerConnection(),this.statsReporter.on(S.STATS_REPORT_READY_EVENT,(e=>{const t=this.calculateNetworkScores(e.stats);this.detectIssues({data:e.stats},t)})),this.statsReporter.on(S.STATS_REPORTS_PARSED,(t=>{const s={timeTaken:t.timeTaken,ts:Date.now()};e.onStats&&e.onStats(t.reportItems),this.eventEmitter.emit(exports.EventType.StatsParsingFinished,s)}))}watchNewPeerConnections(){if(!this.autoAddPeerConnections)throw new Error("Auto add peer connections was disabled in the constructor.");this.#m?this.logger.warn("WebRTCIssueDetector is already started. Skip processing"):(this.logger.info("Start watching peer connections"),this.#m=!0,this.statsReporter.startReporting())}stopWatchingNewPeerConnections(){this.#m?(this.logger.info("Stop watching peer connections"),this.#m=!1,this.statsReporter.stopReporting()):this.logger.warn("WebRTCIssueDetector is already stopped. Skip processing")}handleNewPeerConnection(e,t){this.#m||!this.autoAddPeerConnections?(this.#m||!1!==this.autoAddPeerConnections||(this.logger.info("Starting stats reporting for new peer connection"),this.#m=!0,this.statsReporter.startReporting()),this.logger.debug("Handling new peer connection",e),this.compositeStatsParser.addPeerConnection({pc:e,id:t})):this.logger.debug("Skip handling new peer connection. Detector is not running",e)}emitIssues(e){this.eventEmitter.emit(exports.EventType.Issue,e)}detectIssues({data:e},t){const s=this.detectors.reduce(((s,o)=>[...s,...o.detect(e,t)]),[]);s.length>0&&this.emitIssues(s)}calculateNetworkScores(e){const t=this.networkScoresCalculator.calculate(e);return this.eventEmitter.emit(exports.EventType.NetworkScoresUpdated,t),t}wrapRTCPeerConnection(){if(!window.RTCPeerConnection)return void this.logger.warn("No RTCPeerConnection found in browser window. Skipping");const e=window.RTCPeerConnection,t=e=>this.handleNewPeerConnection(e);function s(s){const o=new e(s);return t(o),o}s.prototype=e.prototype,window.RTCPeerConnection=s}};
"use strict";var e,t,s,o;function r(){}function n(){n.init.call(this)}function i(e){return void 0===e._maxListeners?n.defaultMaxListeners:e._maxListeners}function a(e,t,s){if(t)e.call(s);else for(var o=e.length,r=m(e,o),n=0;n<o;++n)r[n].call(s)}function c(e,t,s,o){if(t)e.call(s,o);else for(var r=e.length,n=m(e,r),i=0;i<r;++i)n[i].call(s,o)}function u(e,t,s,o,r){if(t)e.call(s,o,r);else for(var n=e.length,i=m(e,n),a=0;a<n;++a)i[a].call(s,o,r)}function d(e,t,s,o,r,n){if(t)e.call(s,o,r,n);else for(var i=e.length,a=m(e,i),c=0;c<i;++c)a[c].call(s,o,r,n)}function h(e,t,s,o){if(t)e.apply(s,o);else for(var r=e.length,n=m(e,r),i=0;i<r;++i)n[i].apply(s,o)}function p(e,t,s,o){var n,a,c,u;if("function"!=typeof s)throw new TypeError('"listener" argument must be a function');if((a=e._events)?(a.newListener&&(e.emit("newListener",t,s.listener?s.listener:s),a=e._events),c=a[t]):(a=e._events=new r,e._eventsCount=0),c){if("function"==typeof c?c=a[t]=o?[s,c]:[c,s]:o?c.unshift(s):c.push(s),!c.warned&&(n=i(e))&&n>0&&c.length>n){c.warned=!0;var d=new Error("Possible EventEmitter memory leak detected. "+c.length+" "+t+" listeners added. Use emitter.setMaxListeners() to increase limit");d.name="MaxListenersExceededWarning",d.emitter=e,d.type=t,d.count=c.length,u=d,"function"==typeof console.warn?console.warn(u):console.log(u)}}else c=a[t]=s,++e._eventsCount;return e}function l(e,t,s){var o=!1;function r(){e.removeListener(t,r),o||(o=!0,s.apply(e,arguments))}return r.listener=s,r}function f(e){var t=this._events;if(t){var s=t[e];if("function"==typeof s)return 1;if(s)return s.length}return 0}function m(e,t){for(var s=new Array(t);t--;)s[t]=e[t];return s}Object.defineProperty(exports,"__esModule",{value:!0}),r.prototype=Object.create(null),n.EventEmitter=n,n.usingDomains=!1,n.prototype.domain=void 0,n.prototype._events=void 0,n.prototype._maxListeners=void 0,n.defaultMaxListeners=10,n.init=function(){this.domain=null,n.usingDomains&&undefined.active,this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=new r,this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},n.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||isNaN(e))throw new TypeError('"n" argument must be a positive number');return this._maxListeners=e,this},n.prototype.getMaxListeners=function(){return i(this)},n.prototype.emit=function(e){var t,s,o,r,n,i,p,l="error"===e;if(i=this._events)l=l&&null==i.error;else if(!l)return!1;if(p=this.domain,l){if(t=arguments[1],!p){if(t instanceof Error)throw t;var f=new Error('Uncaught, unspecified "error" event. ('+t+")");throw f.context=t,f}return t||(t=new Error('Uncaught, unspecified "error" event')),t.domainEmitter=this,t.domain=p,t.domainThrown=!1,p.emit("error",t),!1}if(!(s=i[e]))return!1;var m="function"==typeof s;switch(o=arguments.length){case 1:a(s,m,this);break;case 2:c(s,m,this,arguments[1]);break;case 3:u(s,m,this,arguments[1],arguments[2]);break;case 4:d(s,m,this,arguments[1],arguments[2],arguments[3]);break;default:for(r=new Array(o-1),n=1;n<o;n++)r[n-1]=arguments[n];h(s,m,this,r)}return!0},n.prototype.addListener=function(e,t){return p(this,e,t,!1)},n.prototype.on=n.prototype.addListener,n.prototype.prependListener=function(e,t){return p(this,e,t,!0)},n.prototype.once=function(e,t){if("function"!=typeof t)throw new TypeError('"listener" argument must be a function');return this.on(e,l(this,e,t)),this},n.prototype.prependOnceListener=function(e,t){if("function"!=typeof t)throw new TypeError('"listener" argument must be a function');return this.prependListener(e,l(this,e,t)),this},n.prototype.removeListener=function(e,t){var s,o,n,i,a;if("function"!=typeof t)throw new TypeError('"listener" argument must be a function');if(!(o=this._events))return this;if(!(s=o[e]))return this;if(s===t||s.listener&&s.listener===t)0==--this._eventsCount?this._events=new r:(delete o[e],o.removeListener&&this.emit("removeListener",e,s.listener||t));else if("function"!=typeof s){for(n=-1,i=s.length;i-- >0;)if(s[i]===t||s[i].listener&&s[i].listener===t){a=s[i].listener,n=i;break}if(n<0)return this;if(1===s.length){if(s[0]=void 0,0==--this._eventsCount)return this._events=new r,this;delete o[e]}else!function(e,t){for(var s=t,o=s+1,r=e.length;o<r;s+=1,o+=1)e[s]=e[o];e.pop()}(s,n);o.removeListener&&this.emit("removeListener",e,a||t)}return this},n.prototype.off=function(e,t){return this.removeListener(e,t)},n.prototype.removeAllListeners=function(e){var t,s;if(!(s=this._events))return this;if(!s.removeListener)return 0===arguments.length?(this._events=new r,this._eventsCount=0):s[e]&&(0==--this._eventsCount?this._events=new r:delete s[e]),this;if(0===arguments.length){for(var o,n=Object.keys(s),i=0;i<n.length;++i)"removeListener"!==(o=n[i])&&this.removeAllListeners(o);return this.removeAllListeners("removeListener"),this._events=new r,this._eventsCount=0,this}if("function"==typeof(t=s[e]))this.removeListener(e,t);else if(t)do{this.removeListener(e,t[t.length-1])}while(t[0]);return this},n.prototype.listeners=function(e){var t,s=this._events;return s&&(t=s[e])?"function"==typeof t?[t.listener||t]:function(e){for(var t=new Array(e.length),s=0;s<t.length;++s)t[s]=e[s].listener||e[s];return t}(t):[]},n.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):f.call(e,t)},n.prototype.listenerCount=f,n.prototype.eventNames=function(){return this._eventsCount>0?Reflect.ownKeys(this._events):[]};class g extends n{}exports.EventType=void 0,(e=exports.EventType||(exports.EventType={})).Issue="issue",e.NetworkScoresUpdated="network-scores-updated",e.StatsParsingFinished="stats-parsing-finished",exports.IssueType=void 0,(t=exports.IssueType||(exports.IssueType={})).Network="network",t.CPU="cpu",t.Server="server",t.Stream="stream",exports.IssueReason=void 0,(s=exports.IssueReason||(exports.IssueReason={})).OutboundNetworkQuality="outbound-network-quality",s.InboundNetworkQuality="inbound-network-quality",s.OutboundNetworkMediaLatency="outbound-network-media-latency",s.InboundNetworkMediaLatency="inbound-network-media-latency",s.NetworkMediaSyncFailure="network-media-sync-failure",s.OutboundNetworkThroughput="outbound-network-throughput",s.InboundNetworkThroughput="inbound-network-throughput",s.EncoderCPUThrottling="encoder-cpu-throttling",s.DecoderCPUThrottling="decoder-cpu-throttling",s.ServerIssue="server-issue",s.UnknownVideoDecoderIssue="unknown-video-decoder",s.LowInboundMOS="low-inbound-mean-opinion-score",s.LowOutboundMOS="low-outbound-mean-opinion-score",s.FrozenVideoTrack="frozen-video-track",exports.MosQuality=void 0,(o=exports.MosQuality||(exports.MosQuality={}))[o.BAD=2.1]="BAD",o[o.POOR=2.6]="POOR",o[o.FAIR=3.1]="FAIR",o[o.GOOD=3.8]="GOOD",o[o.EXCELLENT=4.3]="EXCELLENT";class S extends n{static STATS_REPORT_READY_EVENT="stats-report-ready";static STATS_REPORTS_PARSED="stats-reports-parsed";isStopped=!1;reportTimer;getStatsInterval;compositeStatsParser;constructor(e){super(),this.compositeStatsParser=e.compositeStatsParser,this.getStatsInterval=e.getStatsInterval??1e4}get isRunning(){return!!this.reportTimer&&!this.isStopped}startReporting(){if(this.reportTimer)return;const e=()=>setTimeout((()=>{this.isStopped?this.reportTimer=void 0:this.parseReports().finally((()=>{this.reportTimer=e()}))}),this.getStatsInterval);this.isStopped=!1,this.reportTimer=e()}stopReporting(){this.isStopped=!0,this.reportTimer&&(clearTimeout(this.reportTimer),this.reportTimer=void 0)}async parseReports(){const e=Date.now(),t=await this.compositeStatsParser.parse(),s=Date.now()-e;this.emit(S.STATS_REPORTS_PARSED,{timeTaken:s,reportItems:t}),t.forEach((e=>{this.emit(S.STATS_REPORT_READY_EVENT,e)}))}}const v=(()=>{const e=new Map;return t=>{const{taskId:s,delayMs:o,maxJitterMs:r,callback:n}=t,i=Math.ceil(Math.random()*(r||0)),a=e.get(s);a&&clearTimeout(a);const c=setTimeout((()=>{n(),e.delete(s)}),o+i);e.set(s,c)}})();class k{#e={};calculate(e){const{connection:{id:t}}=e,{mos:s,stats:o}=this.calculateOutboundScore(e)||{},{mos:r,stats:n}=this.calculateInboundScore(e)||{};return this.#e[t]=e,v({taskId:t,delayMs:35e3,callback:()=>delete this.#e[t]}),{outbound:s,inbound:r,connectionId:t,statsSamples:{inboundStatsSample:n,outboundStatsSample:o}}}calculateOutboundScore(e){const t=[...e.remote?.audio.inbound||[],...e.remote?.video.inbound||[]];if(!t.length)return;const s=this.#e[e.connection.id];if(!s)return;const o=[...s.remote?.audio.inbound||[],...s.remote?.video.inbound||[]],{packetsSent:r}=e.connection,n=s.connection.packetsSent,i=t.reduce(((e,t)=>{const s=o.find((e=>e.ssrc===t.ssrc));return{sumJitter:e.sumJitter+t.jitter,packetsLost:e.packetsLost+t.packetsLost,lastPacketsLost:e.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,packetsLost:0,lastPacketsLost:0}),a=1e3*e.connection.currentRoundTripTime||0,{sumJitter:c}=i,u=c/t.length,d=r-n,h=i.packetsLost-i.lastPacketsLost,p=d&&h?Math.round(100*h/(d+h)):0;return{mos:this.calculateMOS({avgJitter:u,rtt:a,packetsLoss:p}),stats:{avgJitter:u,rtt:a,packetsLoss:p}}}calculateInboundScore(e){const t=[...e.audio?.inbound,...e.video?.inbound];if(!t.length)return;const s=this.#e[e.connection.id];if(!s)return;const o=[...s.video?.inbound,...s.audio?.inbound],{packetsReceived:r}=e.connection,n=s.connection.packetsReceived,i=t.reduce(((e,t)=>{const s=o.find((e=>e.ssrc===t.ssrc));return{sumJitter:e.sumJitter+t.jitter,packetsLost:e.packetsLost+t.packetsLost,lastPacketsLost:e.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,packetsLost:0,lastPacketsLost:0}),a=1e3*e.connection.currentRoundTripTime||0,{sumJitter:c}=i,u=c/t.length,d=r-n,h=i.packetsLost-i.lastPacketsLost,p=d&&h?Math.round(100*h/(d+h)):0;return{mos:this.calculateMOS({avgJitter:u,rtt:a,packetsLoss:p}),stats:{avgJitter:u,rtt:a,packetsLoss:p}}}calculateMOS({avgJitter:e,rtt:t,packetsLoss:s}){const o=t+2*e+10;let r=o<160?93.2-o/40:93.2-o/120-10;return r-=2.5*s,1+.035*r+7e-6*r*(r-60)*(100-r)}}class y{#t=new Map;#s;#o;constructor(e={}){this.#s=e.statsCleanupTtlMs??35e3,this.#o=e.maxParsedStatsStorageSize??5}detect(e,t){const s={...e,networkScores:{...t,statsSamples:t?.statsSamples||{}}},o=this.performDetection(s);return this.setLastProcessedStats(e.connection.id,s),this.performPrevStatsCleanup({connectionId:e.connection.id}),o}performPrevStatsCleanup(e){const{connectionId:t,cleanupCallback:s}=e;this.#t.has(t)&&v({taskId:t,delayMs:this.#s,callback:()=>{this.deleteLastProcessedStats(t),"function"==typeof s&&s()}})}setLastProcessedStats(e,t){if(!e||t.connection.id!==e)return;const s=this.#t.get(e)??[];s.push(t),s.length>this.#o&&s.shift(),this.#t.set(e,s)}getLastProcessedStats(e){const t=this.#t.get(e);return t?.[t.length-1]}getAllLastProcessedStats(e){return this.#t.get(e)??[]}deleteLastProcessedStats(e){this.#t.delete(e)}}class T extends y{#r;constructor(e={}){super(e),this.#r=e.availableOutgoingBitrateThreshold??1e5}performDetection(e){const t=[],{availableOutgoingBitrate:s}=e.connection;if(void 0===s)return t;const o=e.audio.outbound.reduce(((e,t)=>e+t.targetBitrate),0),r=e.video.outbound.reduce(((e,t)=>e+t.bitrate),0);if(!o&&!r)return t;const n={availableOutgoingBitrate:s,videoStreamsTotalBitrate:r,audioStreamsTotalTargetBitrate:o};return o>s||r>0&&s<this.#r?(t.push({statsSample:n,type:exports.IssueType.Network,reason:exports.IssueReason.OutboundNetworkThroughput}),t):t}}class w extends y{#n;#i;#a;#c;constructor(e={}){super(),this.#n=e.highPacketLossThresholdPct??5,this.#i=e.highJitterThreshold??200,this.#a=e.highJitterBufferDelayThresholdMs??500,this.#c=e.highRttThresholdMs??250}performDetection(e){return this.processData(e)}processData(e){const t=[],s=[...e.audio?.inbound,...e.video?.inbound];if(!s.length)return t;const o=this.getLastProcessedStats(e.connection.id);if(!o)return t;const r=[...o.video?.inbound,...o.audio?.inbound],{packetsReceived:n}=e.connection,i=o.connection.packetsReceived,a=s.reduce(((e,t)=>{const s=r.find((e=>e.ssrc===t.ssrc)),o=s?.jitterBufferDelay||0,n=s?.jitterBufferEmittedCount||0,i=t.jitterBufferDelay-o,a=t.jitterBufferEmittedCount-n,c=i&&a?1e3*i/a:0;return{sumJitter:e.sumJitter+t.jitter,sumJitterBufferDelayMs:e.sumJitterBufferDelayMs+c,packetsLost:e.packetsLost+t.packetsLost,lastPacketsLost:e.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,sumJitterBufferDelayMs:0,packetsLost:0,lastPacketsLost:0}),c=1e3*e.connection.currentRoundTripTime||0,{sumJitter:u,sumJitterBufferDelayMs:d}=a,h=u/s.length,p=d/s.length,l=n-i,f=a.packetsLost-a.lastPacketsLost,m=l&&f?Math.round(100*f/(l+f)):0,g=m>this.#n,S=h>=this.#i,v=c>=this.#c,k=p>this.#a,y=v&&!S&&!g,T=g&&S,w=S&&k,b={rtt:c,packetLossPct:m,avgJitter:h,avgJitterBufferDelay:p};return(S||g)&&t.push({statsSample:b,type:exports.IssueType.Network,reason:exports.IssueReason.InboundNetworkQuality,iceCandidate:e.connection.local.id}),y&&t.push({statsSample:b,type:exports.IssueType.Server,reason:exports.IssueReason.ServerIssue,iceCandidate:e.connection.remote.id}),T&&t.push({statsSample:b,type:exports.IssueType.Network,reason:exports.IssueReason.InboundNetworkMediaLatency,iceCandidate:e.connection.local.id}),w&&t.push({statsSample:b,type:exports.IssueType.Network,reason:exports.IssueReason.NetworkMediaSyncFailure,iceCandidate:e.connection.local.id}),t}}class b extends y{#u;constructor(e={}){super(),this.#u=e.correctedSamplesThresholdPct??5}performDetection(e){return this.processData(e)}processData(e){const t=e.audio.inbound,s=[],o=this.getLastProcessedStats(e.connection.id)?.audio.inbound;return o?(t.forEach((e=>{const t=o.find((t=>t.ssrc===e.ssrc));if(!t)return;const r=e.track.insertedSamplesForDeceleration+e.track.removedSamplesForAcceleration,n=t.track.insertedSamplesForDeceleration+t.track.removedSamplesForAcceleration;if(r===n)return;const i=e.track.totalSamplesReceived-t.track.totalSamplesReceived,a=r-n,c=Math.round(100*a/i),u={correctedSamplesPct:c};c>this.#u&&s.push({statsSample:u,type:exports.IssueType.Network,reason:exports.IssueReason.NetworkMediaSyncFailure,ssrc:e.ssrc})})),s):s}}class P extends y{#n;#i;constructor(e={}){super(),this.#n=e.highPacketLossThresholdPct??5,this.#i=e.highJitterThreshold??200}performDetection(e){return this.processData(e)}processData(e){const t=[],s=[...e.remote?.audio.inbound||[],...e.remote?.video.inbound||[]];if(!s.length)return t;const o=this.getLastProcessedStats(e.connection.id);if(!o)return t;const r=[...o.remote?.audio.inbound||[],...o.remote?.video.inbound||[]],{packetsSent:n}=e.connection,i=o.connection.packetsSent,a=s.reduce(((e,t)=>{const s=r.find((e=>e.ssrc===t.ssrc));return{sumJitter:e.sumJitter+t.jitter,packetsLost:e.packetsLost+t.packetsLost,lastPacketsLost:e.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,packetsLost:0,lastPacketsLost:0}),c=1e3*e.connection.currentRoundTripTime||0,{sumJitter:u}=a,d=u/s.length,h=n-i,p=a.packetsLost-a.lastPacketsLost,l=h&&p?Math.round(100*p/(h+p)):0,f=l>this.#n,m=d>=this.#i,g=!f&&m||m||f,S={rtt:c,avgJitter:d,packetLossPct:l};return f&&m&&t.push({statsSample:S,type:exports.IssueType.Network,reason:exports.IssueReason.OutboundNetworkMediaLatency,iceCandidate:e.connection.local.id}),g&&t.push({statsSample:S,type:exports.IssueType.Network,reason:exports.IssueReason.OutboundNetworkQuality,iceCandidate:e.connection.local.id}),t}}class L extends y{performDetection(e){return this.processData(e)}processData(e){const t=e.video.outbound.filter((e=>"none"!==e.qualityLimitationReason)),s=[],o=this.getLastProcessedStats(e.connection.id)?.video.outbound;return o?(t.forEach((e=>{const t=o.find((t=>t.ssrc===e.ssrc));if(!t)return;const r={qualityLimitationReason:e.qualityLimitationReason};e.framesSent>t.framesSent||("cpu"===e.qualityLimitationReason&&s.push({statsSample:r,type:exports.IssueType.CPU,reason:exports.IssueReason.EncoderCPUThrottling,ssrc:e.ssrc}),"bandwidth"===e.qualityLimitationReason&&s.push({statsSample:r,type:exports.IssueType.Network,reason:exports.IssueReason.OutboundNetworkThroughput,ssrc:e.ssrc}))})),s):s}}class I extends y{UNKNOWN_DECODER="unknown";#d={};performDetection(e){return this.processData(e)}performPrevStatsCleanup(e){const{connectionId:t,cleanupCallback:s}=e;super.performPrevStatsCleanup({...e,cleanupCallback:()=>{delete this.#d[t],"function"==typeof s&&s()}})}processData(e){const t=[],{id:s}=e.connection,o=this.getLastProcessedStats(s)?.video.inbound;return e.video.inbound.forEach((e=>{const{decoderImplementation:r,ssrc:n}=e,i=o?.find((e=>e.ssrc===n));if(i)if(r===this.UNKNOWN_DECODER){if(!this.hadLastDecoderWithIssue(s,n)){this.setLastDecoderWithIssue(s,n,this.UNKNOWN_DECODER);const o={mimeType:e.mimeType,decoderImplementation:r};t.push({ssrc:n,statsSample:o,type:exports.IssueType.Stream,reason:exports.IssueReason.UnknownVideoDecoderIssue,trackIdentifier:e.track.trackIdentifier})}}else this.setLastDecoderWithIssue(s,n,void 0)})),t}setLastDecoderWithIssue(e,t,s){const o=this.#d[e]??{};void 0===s?delete o[t]:o[t]=s,this.#d[e]=o}hadLastDecoderWithIssue(e,t){const s=this.#d[e];return(s&&s[t])===this.UNKNOWN_DECODER}}const D=(e,t)=>{for(let s=1;s<t.length;s+=1){const o=t[s].video.inbound.find((t=>t.ssrc===e));if(!o)continue;const r=t[s-1].video.inbound.find((t=>t.ssrc===e)),n=o.frameWidth!==r?.frameWidth,i=o.frameHeight!==r?.frameHeight;if(n||i)return!0}return!1};class R extends y{#h;#p;constructor(e={}){super(),this.#h=e.avgFreezeDurationThresholdMs??1e3,this.#p=e.frozenDurationThresholdPct??30}performDetection(e){const t=e.networkScores.inbound;return void 0!==t&&t<=exports.MosQuality.BAD?[]:this.processData(e)}processData(e){const t=[],s=this.getAllLastProcessedStats(e.connection.id);if(0===s.length)return[];const o=e.video.inbound.map((t=>{const o=s[s.length-1].video.inbound.find((e=>e.ssrc===t.ssrc));if(!o)return;if(D(t.ssrc,[s[s.length-1],e]))return;const r=t.freezeCount-(o.freezeCount??0),n=1e3*(t.totalFreezesDuration-(o.totalFreezesDuration??0)),i=r>0?n/r:0,a=n/(t.timestamp-o.timestamp)*100;return a>this.#p||i>this.#h?{ssrc:t.ssrc,avgFreezeDurationMs:i,frozenDurationPct:a}:void 0})).filter((e=>void 0!==e));return o.length>0&&t.push({type:exports.IssueType.Stream,reason:exports.IssueReason.FrozenVideoTrack,statsSample:{ssrcs:o.map((e=>e.ssrc))}}),t}}class C extends y{#l;#f;constructor(e={}){super(e),this.#l=e.volatilityThreshold??1.5,this.#f=e.affectedStreamsPercentThreshold??50}performDetection(e){return[...this.getAllLastProcessedStats(e.connection.id),e].find((e=>void 0!==e.networkScores.inbound&&e.networkScores.inbound<=exports.MosQuality.BAD))?[]:this.processData(e)}processData(e){const t=[],s=[...this.getAllLastProcessedStats(e.connection.id),e],o=e.video.inbound.map((e=>{const t=[],o=[];if(D(e.ssrc,s))return;if(s.length<5)return;for(let r=1;r<s.length;r+=1){let n=0,i=0,a=0;const c=s[r].video.inbound.find((t=>t.ssrc===e.ssrc));if(!c)continue;const u=s[r-1].video.inbound.find((t=>t.ssrc===e.ssrc));u&&(n=c.framesDecoded-u.framesDecoded,i=c.totalDecodeTime-u.totalDecodeTime),i>0&&n>0&&(a=1e3*i/n),t.push(a),o.push(c.framesPerSecond)}const r=t.reduce(((e,t)=>e+t),0)/t.length,n=t.map((e=>(e-r)**2)),i=n.reduce(((e,t)=>e+t),0)/n.length,a=Math.sqrt(i),c=t.every(((e,s)=>0===s||e>t[s-1])),u=o.reduce(((e,t)=>e+t),0)/t.length,d=o.map((e=>(e-u)**2)),h=d.reduce(((e,t)=>e+t),0)/d.length,p=Math.sqrt(h);return console.log("THROTTLE",{volatilityFps:p,allFpss:o}),a>this.#l&&c?(console.log("THROTTLE DETECTED"),{ssrc:e.ssrc,allDecodeTimePerFrame:t,volatility:a}):void 0})).filter((e=>Boolean(e))),r=o.length/(e.video.inbound.length/100);return r>this.#f&&(t.push({type:exports.IssueType.CPU,reason:exports.IssueReason.DecoderCPUThrottling,statsSample:{affectedStreamsPercent:r,throtthedStreams:o}}),this.deleteLastProcessedStats(e.connection.id)),t}}const x=e=>"closed"===e.iceConnectionState||"closed"===e.connectionState,E=(e,t,s)=>8*((e,t,s)=>{if(!t)return 0;const o=e[s],r=t[s];if(null==o||null==r)return 0;const n=Math.floor(e.timestamp)-Math.floor(t.timestamp);return 0===n?0:(Number(o)-Number(r))/n*1e3})(e,t,s);class M{connections=[];statsParser;constructor(e){this.statsParser=e.statsParser}listConnections(){return[...this.connections]}addPeerConnection(e){this.connections.push({id:e.id??String(Date.now()+Math.random().toString(32)),pc:e.pc})}removePeerConnection(e){const t=this.connections.findIndex((({pc:t})=>t===e.pc));t>=0&&this.removeConnectionsByIndexes([t])}async parse(){const e=[],t=this.connections.map((async(t,s)=>{if(!x(t.pc))return this.statsParser.parse(t);e.unshift(s)}));e.length&&this.removeConnectionsByIndexes(e);return(await Promise.all(t)).filter((e=>void 0!==e))}removeConnectionsByIndexes(e){e.forEach((e=>{this.connections.splice(e,1)}))}}class N{prevStats=new Map;allowedReportTypes=new Set(["candidate-pair","inbound-rtp","outbound-rtp","remote-outbound-rtp","remote-inbound-rtp","track","transport"]);ignoreSSRCList;logger;constructor(e){this.ignoreSSRCList=e.ignoreSSRCList??[],this.logger=e.logger}get previouslyParsedStatsConnectionsIds(){return[...this.prevStats.keys()]}async parse(e){if(!x(e.pc))return this.getConnectionStats(e);this.logger.debug("Skip stats parsing. Connection is closed.",{connection:e})}async getConnectionStats(e){const{pc:t,id:s}=e;try{const o=Date.now(),r=t.getReceivers().filter((e=>e.track?.enabled)),n=t.getSenders().filter((e=>e.track?.enabled)),i=await Promise.all(r.map((e=>e.getStats()))),a=await Promise.all(n.map((e=>e.getStats())));return{id:s,stats:this.mapReportsStats([...i,...a],e),timeTaken:Date.now()-o}}catch(e){return void this.logger.error("Failed to get stats for PC",{id:s,pc:t,error:e})}}mapReportsStats(e,t){const s={audio:{inbound:[],outbound:[]},video:{inbound:[],outbound:[]},connection:{},remote:{video:{inbound:[],outbound:[]},audio:{inbound:[],outbound:[]}}};e.forEach((e=>{e.forEach((t=>{this.allowedReportTypes.has(t.type)&&this.updateMappedStatsWithReportItemData(t,s,e)}))}));const{id:o}=t,r=this.prevStats.get(o);return r&&this.propagateStatsWithRateValues(s,r.stats),this.prevStats.set(o,{stats:s,ts:Date.now()}),v({taskId:o,delayMs:35e3,callback:()=>this.prevStats.delete(o)}),s}updateMappedStatsWithReportItemData(e,t,s){const o=e.type;if("candidate-pair"===o&&"succeeded"===e.state&&e.nominated)return void(t.connection=this.prepareConnectionStats(e,s));const r=this.getMediaType(e);if(!r)return;const n=e.ssrc;if(!n||!this.ignoreSSRCList.includes(n))if("outbound-rtp"!==o)if("inbound-rtp"!==o)"remote-outbound-rtp"!==o?"remote-inbound-rtp"===o&&(this.mapConnectionStatsIfNecessary(t,e,s),t.remote[r].inbound.push({...e})):t.remote[r].outbound.push({...e});else{const o=s.get(e.trackId)||s.get(e.mediaSourceId)||{};this.mapConnectionStatsIfNecessary(t,e,s);const n={...e,track:{...o}};t[r].inbound.push(n)}else{const o=s.get(e.trackId)||s.get(e.mediaSourceId)||{},n={...e,track:{...o}};t[r].outbound.push(n)}}getMediaType(e){const t=e.mediaType||e.kind;if(!["audio","video"].includes(t)){const{id:t}=e;if(!t)return;return String(t).includes("Video")?"video":String(t).includes("Audio")?"audio":void 0}return t}propagateStatsWithRateValues(e,t){e.audio.inbound.forEach((e=>{const s=t.audio.inbound.find((({id:t})=>t===e.id));e.bitrate=E(e,s,"bytesReceived"),e.packetRate=E(e,s,"packetsReceived")})),e.audio.outbound.forEach((e=>{const s=t.audio.outbound.find((({id:t})=>t===e.id));e.bitrate=E(e,s,"bytesSent"),e.packetRate=E(e,s,"packetsSent")})),e.video.inbound.forEach((e=>{const s=t.video.inbound.find((({id:t})=>t===e.id));e.bitrate=E(e,s,"bytesReceived"),e.packetRate=E(e,s,"packetsReceived")})),e.video.outbound.forEach((e=>{const s=t.video.outbound.find((({id:t})=>t===e.id));e.bitrate=E(e,s,"bytesSent"),e.packetRate=E(e,s,"packetsSent")}))}mapConnectionStatsIfNecessary(e,t,s){if(e.connection.id||!t.transportId)return;const o=s.get(t.transportId);if(o&&o.selectedCandidatePairId){const t=s.get(o.selectedCandidatePairId);e.connection=this.prepareConnectionStats(t,s)}}prepareConnectionStats(e,t){if(!e||!t)return{};const s={...e};if(s.remoteCandidateId){const e=t.get(s.remoteCandidateId);s.remote={...e}}if(s.localCandidateId){const e=t.get(s.localCandidateId);s.local={...e}}return s}}exports.AvailableOutgoingBitrateIssueDetector=T,exports.BaseIssueDetector=y,exports.CompositeRTCStatsParser=M,exports.FrozenVideoTrackDetector=R,exports.InboundNetworkIssueDetector=w,exports.NetworkMediaSyncIssueDetector=b,exports.NetworkScoresCalculator=k,exports.OutboundNetworkIssueDetector=P,exports.PeriodicWebRTCStatsReporter=S,exports.QualityLimitationsIssueDetector=L,exports.RTCStatsParser=N,exports.UnknownVideoDecoderImplementationDetector=I,exports.VideoDecoderIssueDetector=C,exports.WebRTCIssueEmitter=g,exports.default=class{eventEmitter;#m=!1;detectors=[];networkScoresCalculator;statsReporter;compositeStatsParser;logger;autoAddPeerConnections;constructor(e){this.logger=e.logger??{debug:()=>{},info:()=>{},warn:()=>{},error:()=>{}},this.eventEmitter=e.issueEmitter??new g,e.onIssues&&this.eventEmitter.on(exports.EventType.Issue,e.onIssues),e.onNetworkScoresUpdated&&this.eventEmitter.on(exports.EventType.NetworkScoresUpdated,e.onNetworkScoresUpdated),this.detectors=e.detectors??[new L,new w,new P,new b,new T,new I,new R,new C],this.networkScoresCalculator=e.networkScoresCalculator??new k,this.compositeStatsParser=e.compositeStatsParser??new M({statsParser:new N({ignoreSSRCList:e.ignoreSSRCList,logger:this.logger})}),this.statsReporter=e.statsReporter??new S({compositeStatsParser:this.compositeStatsParser,getStatsInterval:e.getStatsInterval??5e3}),window.wid=this,this.autoAddPeerConnections=e.autoAddPeerConnections??!0,this.autoAddPeerConnections&&this.wrapRTCPeerConnection(),this.statsReporter.on(S.STATS_REPORT_READY_EVENT,(e=>{const t=this.calculateNetworkScores(e.stats);this.detectIssues({data:e.stats},t)})),this.statsReporter.on(S.STATS_REPORTS_PARSED,(t=>{const s={timeTaken:t.timeTaken,ts:Date.now()};e.onStats&&e.onStats(t.reportItems),this.eventEmitter.emit(exports.EventType.StatsParsingFinished,s)}))}watchNewPeerConnections(){if(!this.autoAddPeerConnections)throw new Error("Auto add peer connections was disabled in the constructor.");this.#m?this.logger.warn("WebRTCIssueDetector is already started. Skip processing"):(this.logger.info("Start watching peer connections"),this.#m=!0,this.statsReporter.startReporting())}stopWatchingNewPeerConnections(){this.#m?(this.logger.info("Stop watching peer connections"),this.#m=!1,this.statsReporter.stopReporting()):this.logger.warn("WebRTCIssueDetector is already stopped. Skip processing")}handleNewPeerConnection(e,t){this.#m||!this.autoAddPeerConnections?(this.#m||!1!==this.autoAddPeerConnections||(this.logger.info("Starting stats reporting for new peer connection"),this.#m=!0,this.statsReporter.startReporting()),this.logger.debug("Handling new peer connection",e),this.compositeStatsParser.addPeerConnection({pc:e,id:t})):this.logger.debug("Skip handling new peer connection. Detector is not running",e)}emitIssues(e){this.eventEmitter.emit(exports.EventType.Issue,e)}detectIssues({data:e},t){const s=this.detectors.reduce(((s,o)=>[...s,...o.detect(e,t)]),[]);s.length>0&&this.emitIssues(s)}calculateNetworkScores(e){const t=this.networkScoresCalculator.calculate(e);return this.eventEmitter.emit(exports.EventType.NetworkScoresUpdated,t),t}wrapRTCPeerConnection(){if(!window.RTCPeerConnection)return void this.logger.warn("No RTCPeerConnection found in browser window. Skipping");const e=window.RTCPeerConnection,t=e=>this.handleNewPeerConnection(e);function s(s){const o=new e(s);return t(o),o}s.prototype=e.prototype,window.RTCPeerConnection=s}};

@@ -1,1 +0,1 @@

var t,e,s,n;function o(){}function r(){r.init.call(this)}function i(t){return void 0===t._maxListeners?r.defaultMaxListeners:t._maxListeners}function a(t,e,s){if(e)t.call(s);else for(var n=t.length,o=m(t,n),r=0;r<n;++r)o[r].call(s)}function c(t,e,s,n){if(e)t.call(s,n);else for(var o=t.length,r=m(t,o),i=0;i<o;++i)r[i].call(s,n)}function d(t,e,s,n,o){if(e)t.call(s,n,o);else for(var r=t.length,i=m(t,r),a=0;a<r;++a)i[a].call(s,n,o)}function u(t,e,s,n,o,r){if(e)t.call(s,n,o,r);else for(var i=t.length,a=m(t,i),c=0;c<i;++c)a[c].call(s,n,o,r)}function h(t,e,s,n){if(e)t.apply(s,n);else for(var o=t.length,r=m(t,o),i=0;i<o;++i)r[i].apply(s,n)}function l(t,e,s,n){var r,a,c,d;if("function"!=typeof s)throw new TypeError('"listener" argument must be a function');if((a=t._events)?(a.newListener&&(t.emit("newListener",e,s.listener?s.listener:s),a=t._events),c=a[e]):(a=t._events=new o,t._eventsCount=0),c){if("function"==typeof c?c=a[e]=n?[s,c]:[c,s]:n?c.unshift(s):c.push(s),!c.warned&&(r=i(t))&&r>0&&c.length>r){c.warned=!0;var u=new Error("Possible EventEmitter memory leak detected. "+c.length+" "+e+" listeners added. Use emitter.setMaxListeners() to increase limit");u.name="MaxListenersExceededWarning",u.emitter=t,u.type=e,u.count=c.length,d=u,"function"==typeof console.warn?console.warn(d):console.log(d)}}else c=a[e]=s,++t._eventsCount;return t}function p(t,e,s){var n=!1;function o(){t.removeListener(e,o),n||(n=!0,s.apply(t,arguments))}return o.listener=s,o}function f(t){var e=this._events;if(e){var s=e[t];if("function"==typeof s)return 1;if(s)return s.length}return 0}function m(t,e){for(var s=new Array(e);e--;)s[e]=t[e];return s}o.prototype=Object.create(null),r.EventEmitter=r,r.usingDomains=!1,r.prototype.domain=void 0,r.prototype._events=void 0,r.prototype._maxListeners=void 0,r.defaultMaxListeners=10,r.init=function(){this.domain=null,r.usingDomains&&undefined.active,this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=new o,this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},r.prototype.setMaxListeners=function(t){if("number"!=typeof t||t<0||isNaN(t))throw new TypeError('"n" argument must be a positive number');return this._maxListeners=t,this},r.prototype.getMaxListeners=function(){return i(this)},r.prototype.emit=function(t){var e,s,n,o,r,i,l,p="error"===t;if(i=this._events)p=p&&null==i.error;else if(!p)return!1;if(l=this.domain,p){if(e=arguments[1],!l){if(e instanceof Error)throw e;var f=new Error('Uncaught, unspecified "error" event. ('+e+")");throw f.context=e,f}return e||(e=new Error('Uncaught, unspecified "error" event')),e.domainEmitter=this,e.domain=l,e.domainThrown=!1,l.emit("error",e),!1}if(!(s=i[t]))return!1;var m="function"==typeof s;switch(n=arguments.length){case 1:a(s,m,this);break;case 2:c(s,m,this,arguments[1]);break;case 3:d(s,m,this,arguments[1],arguments[2]);break;case 4:u(s,m,this,arguments[1],arguments[2],arguments[3]);break;default:for(o=new Array(n-1),r=1;r<n;r++)o[r-1]=arguments[r];h(s,m,this,o)}return!0},r.prototype.addListener=function(t,e){return l(this,t,e,!1)},r.prototype.on=r.prototype.addListener,r.prototype.prependListener=function(t,e){return l(this,t,e,!0)},r.prototype.once=function(t,e){if("function"!=typeof e)throw new TypeError('"listener" argument must be a function');return this.on(t,p(this,t,e)),this},r.prototype.prependOnceListener=function(t,e){if("function"!=typeof e)throw new TypeError('"listener" argument must be a function');return this.prependListener(t,p(this,t,e)),this},r.prototype.removeListener=function(t,e){var s,n,r,i,a;if("function"!=typeof e)throw new TypeError('"listener" argument must be a function');if(!(n=this._events))return this;if(!(s=n[t]))return this;if(s===e||s.listener&&s.listener===e)0==--this._eventsCount?this._events=new o:(delete n[t],n.removeListener&&this.emit("removeListener",t,s.listener||e));else if("function"!=typeof s){for(r=-1,i=s.length;i-- >0;)if(s[i]===e||s[i].listener&&s[i].listener===e){a=s[i].listener,r=i;break}if(r<0)return this;if(1===s.length){if(s[0]=void 0,0==--this._eventsCount)return this._events=new o,this;delete n[t]}else!function(t,e){for(var s=e,n=s+1,o=t.length;n<o;s+=1,n+=1)t[s]=t[n];t.pop()}(s,r);n.removeListener&&this.emit("removeListener",t,a||e)}return this},r.prototype.off=function(t,e){return this.removeListener(t,e)},r.prototype.removeAllListeners=function(t){var e,s;if(!(s=this._events))return this;if(!s.removeListener)return 0===arguments.length?(this._events=new o,this._eventsCount=0):s[t]&&(0==--this._eventsCount?this._events=new o:delete s[t]),this;if(0===arguments.length){for(var n,r=Object.keys(s),i=0;i<r.length;++i)"removeListener"!==(n=r[i])&&this.removeAllListeners(n);return this.removeAllListeners("removeListener"),this._events=new o,this._eventsCount=0,this}if("function"==typeof(e=s[t]))this.removeListener(t,e);else if(e)do{this.removeListener(t,e[e.length-1])}while(e[0]);return this},r.prototype.listeners=function(t){var e,s=this._events;return s&&(e=s[t])?"function"==typeof e?[e.listener||e]:function(t){for(var e=new Array(t.length),s=0;s<e.length;++s)e[s]=t[s].listener||t[s];return e}(e):[]},r.listenerCount=function(t,e){return"function"==typeof t.listenerCount?t.listenerCount(e):f.call(t,e)},r.prototype.listenerCount=f,r.prototype.eventNames=function(){return this._eventsCount>0?Reflect.ownKeys(this._events):[]};class g extends r{}!function(t){t.Issue="issue",t.NetworkScoresUpdated="network-scores-updated",t.StatsParsingFinished="stats-parsing-finished"}(t||(t={})),function(t){t.Network="network",t.CPU="cpu",t.Server="server",t.Stream="stream"}(e||(e={})),function(t){t.OutboundNetworkQuality="outbound-network-quality",t.InboundNetworkQuality="inbound-network-quality",t.OutboundNetworkMediaLatency="outbound-network-media-latency",t.InboundNetworkMediaLatency="inbound-network-media-latency",t.NetworkMediaSyncFailure="network-media-sync-failure",t.OutboundNetworkThroughput="outbound-network-throughput",t.InboundNetworkThroughput="inbound-network-throughput",t.EncoderCPUThrottling="encoder-cpu-throttling",t.DecoderCPUThrottling="decoder-cpu-throttling",t.ServerIssue="server-issue",t.UnknownVideoDecoderIssue="unknown-video-decoder",t.LowInboundMOS="low-inbound-mean-opinion-score",t.LowOutboundMOS="low-outbound-mean-opinion-score",t.FrozenVideoTrack="frozen-video-track"}(s||(s={})),function(t){t[t.BAD=2.1]="BAD",t[t.POOR=2.6]="POOR",t[t.FAIR=3.1]="FAIR",t[t.GOOD=3.8]="GOOD",t[t.EXCELLENT=4.3]="EXCELLENT"}(n||(n={}));class S extends r{static STATS_REPORT_READY_EVENT="stats-report-ready";static STATS_REPORTS_PARSED="stats-reports-parsed";isStopped=!1;reportTimer;getStatsInterval;compositeStatsParser;constructor(t){super(),this.compositeStatsParser=t.compositeStatsParser,this.getStatsInterval=t.getStatsInterval??1e4}get isRunning(){return!!this.reportTimer&&!this.isStopped}startReporting(){if(this.reportTimer)return;const t=()=>setTimeout((()=>{this.isStopped?this.reportTimer=void 0:this.parseReports().finally((()=>{this.reportTimer=t()}))}),this.getStatsInterval);this.isStopped=!1,this.reportTimer=t()}stopReporting(){this.isStopped=!0,this.reportTimer&&(clearTimeout(this.reportTimer),this.reportTimer=void 0)}async parseReports(){const t=Date.now(),e=await this.compositeStatsParser.parse(),s=Date.now()-t;this.emit(S.STATS_REPORTS_PARSED,{timeTaken:s,reportItems:e}),e.forEach((t=>{this.emit(S.STATS_REPORT_READY_EVENT,t)}))}}const v=(()=>{const t=new Map;return e=>{const{taskId:s,delayMs:n,maxJitterMs:o,callback:r}=e,i=Math.ceil(Math.random()*(o||0)),a=t.get(s);a&&clearTimeout(a);const c=setTimeout((()=>{r(),t.delete(s)}),n+i);t.set(s,c)}})();class k{#t={};calculate(t){const{connection:{id:e}}=t,{mos:s,stats:n}=this.calculateOutboundScore(t)||{},{mos:o,stats:r}=this.calculateInboundScore(t)||{};return this.#t[e]=t,v({taskId:e,delayMs:35e3,callback:()=>delete this.#t[e]}),{outbound:s,inbound:o,connectionId:e,statsSamples:{inboundStatsSample:r,outboundStatsSample:n}}}calculateOutboundScore(t){const e=[...t.remote?.audio.inbound||[],...t.remote?.video.inbound||[]];if(!e.length)return;const s=this.#t[t.connection.id];if(!s)return;const n=[...s.remote?.audio.inbound||[],...s.remote?.video.inbound||[]],{packetsSent:o}=t.connection,r=s.connection.packetsSent,i=e.reduce(((t,e)=>{const s=n.find((t=>t.ssrc===e.ssrc));return{sumJitter:t.sumJitter+e.jitter,packetsLost:t.packetsLost+e.packetsLost,lastPacketsLost:t.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,packetsLost:0,lastPacketsLost:0}),a=1e3*t.connection.currentRoundTripTime||0,{sumJitter:c}=i,d=c/e.length,u=o-r,h=i.packetsLost-i.lastPacketsLost,l=u&&h?Math.round(100*h/(u+h)):0;return{mos:this.calculateMOS({avgJitter:d,rtt:a,packetsLoss:l}),stats:{avgJitter:d,rtt:a,packetsLoss:l}}}calculateInboundScore(t){const e=[...t.audio?.inbound,...t.video?.inbound];if(!e.length)return;const s=this.#t[t.connection.id];if(!s)return;const n=[...s.video?.inbound,...s.audio?.inbound],{packetsReceived:o}=t.connection,r=s.connection.packetsReceived,i=e.reduce(((t,e)=>{const s=n.find((t=>t.ssrc===e.ssrc));return{sumJitter:t.sumJitter+e.jitter,packetsLost:t.packetsLost+e.packetsLost,lastPacketsLost:t.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,packetsLost:0,lastPacketsLost:0}),a=1e3*t.connection.currentRoundTripTime||0,{sumJitter:c}=i,d=c/e.length,u=o-r,h=i.packetsLost-i.lastPacketsLost,l=u&&h?Math.round(100*h/(u+h)):0;return{mos:this.calculateMOS({avgJitter:d,rtt:a,packetsLoss:l}),stats:{avgJitter:d,rtt:a,packetsLoss:l}}}calculateMOS({avgJitter:t,rtt:e,packetsLoss:s}){const n=e+2*t+10;let o=n<160?93.2-n/40:93.2-n/120-10;return o-=2.5*s,1+.035*o+7e-6*o*(o-60)*(100-o)}}class w{#e=new Map;#s;#n;constructor(t={}){this.#s=t.statsCleanupTtlMs??35e3,this.#n=t.maxParsedStatsStorageSize??5}detect(t,e){const s={...t,networkScores:{...e,statsSamples:e?.statsSamples||{}}},n=this.performDetection(s);return this.setLastProcessedStats(t.connection.id,s),this.performPrevStatsCleanup({connectionId:t.connection.id}),n}performPrevStatsCleanup(t){const{connectionId:e,cleanupCallback:s}=t;this.#e.has(e)&&v({taskId:e,delayMs:this.#s,callback:()=>{this.deleteLastProcessedStats(e),"function"==typeof s&&s()}})}setLastProcessedStats(t,e){if(!t||e.connection.id!==t)return;const s=this.#e.get(t)??[];s.push(e),s.length>this.#n&&s.shift(),this.#e.set(t,s)}getLastProcessedStats(t){const e=this.#e.get(t);return e?.[e.length-1]}getAllLastProcessedStats(t){return this.#e.get(t)??[]}deleteLastProcessedStats(t){this.#e.delete(t)}}class b extends w{#o;constructor(t={}){super(t),this.#o=t.availableOutgoingBitrateThreshold??1e5}performDetection(t){const n=[],{availableOutgoingBitrate:o}=t.connection;if(void 0===o)return n;const r=t.audio.outbound.reduce(((t,e)=>t+e.targetBitrate),0),i=t.video.outbound.reduce(((t,e)=>t+e.bitrate),0);if(!r&&!i)return n;const a={availableOutgoingBitrate:o,videoStreamsTotalBitrate:i,audioStreamsTotalTargetBitrate:r};return r>o||i>0&&o<this.#o?(n.push({statsSample:a,type:e.Network,reason:s.OutboundNetworkThroughput}),n):n}}class P extends w{#r;#i;#a;#c;constructor(t={}){super(),this.#r=t.highPacketLossThresholdPct??5,this.#i=t.highJitterThreshold??200,this.#a=t.highJitterBufferDelayThresholdMs??500,this.#c=t.highRttThresholdMs??250}performDetection(t){return this.processData(t)}processData(t){const n=[],o=[...t.audio?.inbound,...t.video?.inbound];if(!o.length)return n;const r=this.getLastProcessedStats(t.connection.id);if(!r)return n;const i=[...r.video?.inbound,...r.audio?.inbound],{packetsReceived:a}=t.connection,c=r.connection.packetsReceived,d=o.reduce(((t,e)=>{const s=i.find((t=>t.ssrc===e.ssrc)),n=s?.jitterBufferDelay||0,o=s?.jitterBufferEmittedCount||0,r=e.jitterBufferDelay-n,a=e.jitterBufferEmittedCount-o,c=r&&a?1e3*r/a:0;return{sumJitter:t.sumJitter+e.jitter,sumJitterBufferDelayMs:t.sumJitterBufferDelayMs+c,packetsLost:t.packetsLost+e.packetsLost,lastPacketsLost:t.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,sumJitterBufferDelayMs:0,packetsLost:0,lastPacketsLost:0}),u=1e3*t.connection.currentRoundTripTime||0,{sumJitter:h,sumJitterBufferDelayMs:l}=d,p=h/o.length,f=l/o.length,m=a-c,g=d.packetsLost-d.lastPacketsLost,S=m&&g?Math.round(100*g/(m+g)):0,v=S>this.#r,k=p>=this.#i,w=u>=this.#c,b=f>this.#a,P=w&&!k&&!v,T=v&&k,y=k&&b,L={rtt:u,packetLossPct:S,avgJitter:p,avgJitterBufferDelay:f};return(k||v)&&n.push({statsSample:L,type:e.Network,reason:s.InboundNetworkQuality,iceCandidate:t.connection.local.id}),P&&n.push({statsSample:L,type:e.Server,reason:s.ServerIssue,iceCandidate:t.connection.remote.id}),T&&n.push({statsSample:L,type:e.Network,reason:s.InboundNetworkMediaLatency,iceCandidate:t.connection.local.id}),y&&n.push({statsSample:L,type:e.Network,reason:s.NetworkMediaSyncFailure,iceCandidate:t.connection.local.id}),n}}class T extends w{#d;constructor(t={}){super(),this.#d=t.correctedSamplesThresholdPct??5}performDetection(t){return this.processData(t)}processData(t){const n=t.audio.inbound,o=[],r=this.getLastProcessedStats(t.connection.id)?.audio.inbound;return r?(n.forEach((t=>{const n=r.find((e=>e.ssrc===t.ssrc));if(!n)return;const i=t.track.insertedSamplesForDeceleration+t.track.removedSamplesForAcceleration,a=n.track.insertedSamplesForDeceleration+n.track.removedSamplesForAcceleration;if(i===a)return;const c=t.track.totalSamplesReceived-n.track.totalSamplesReceived,d=i-a,u=Math.round(100*d/c),h={correctedSamplesPct:u};u>this.#d&&o.push({statsSample:h,type:e.Network,reason:s.NetworkMediaSyncFailure,ssrc:t.ssrc})})),o):o}}class y extends w{#r;#i;constructor(t={}){super(),this.#r=t.highPacketLossThresholdPct??5,this.#i=t.highJitterThreshold??200}performDetection(t){return this.processData(t)}processData(t){const n=[],o=[...t.remote?.audio.inbound||[],...t.remote?.video.inbound||[]];if(!o.length)return n;const r=this.getLastProcessedStats(t.connection.id);if(!r)return n;const i=[...r.remote?.audio.inbound||[],...r.remote?.video.inbound||[]],{packetsSent:a}=t.connection,c=r.connection.packetsSent,d=o.reduce(((t,e)=>{const s=i.find((t=>t.ssrc===e.ssrc));return{sumJitter:t.sumJitter+e.jitter,packetsLost:t.packetsLost+e.packetsLost,lastPacketsLost:t.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,packetsLost:0,lastPacketsLost:0}),u=1e3*t.connection.currentRoundTripTime||0,{sumJitter:h}=d,l=h/o.length,p=a-c,f=d.packetsLost-d.lastPacketsLost,m=p&&f?Math.round(100*f/(p+f)):0,g=m>this.#r,S=l>=this.#i,v=!g&&S||S||g,k={rtt:u,avgJitter:l,packetLossPct:m};return g&&S&&n.push({statsSample:k,type:e.Network,reason:s.OutboundNetworkMediaLatency,iceCandidate:t.connection.local.id}),v&&n.push({statsSample:k,type:e.Network,reason:s.OutboundNetworkQuality,iceCandidate:t.connection.local.id}),n}}class L extends w{performDetection(t){return this.processData(t)}processData(t){const n=t.video.outbound.filter((t=>"none"!==t.qualityLimitationReason)),o=[],r=this.getLastProcessedStats(t.connection.id)?.video.outbound;return r?(n.forEach((t=>{const n=r.find((e=>e.ssrc===t.ssrc));if(!n)return;const i={qualityLimitationReason:t.qualityLimitationReason};t.framesSent>n.framesSent||("cpu"===t.qualityLimitationReason&&o.push({statsSample:i,type:e.CPU,reason:s.EncoderCPUThrottling,ssrc:t.ssrc}),"bandwidth"===t.qualityLimitationReason&&o.push({statsSample:i,type:e.Network,reason:s.OutboundNetworkThroughput,ssrc:t.ssrc}))})),o):o}}class C extends w{UNKNOWN_DECODER="unknown";#u={};performDetection(t){return this.processData(t)}performPrevStatsCleanup(t){const{connectionId:e,cleanupCallback:s}=t;super.performPrevStatsCleanup({...t,cleanupCallback:()=>{delete this.#u[e],"function"==typeof s&&s()}})}processData(t){const n=[],{id:o}=t.connection,r=this.getLastProcessedStats(o)?.video.inbound;return t.video.inbound.forEach((t=>{const{decoderImplementation:i,ssrc:a}=t,c=r?.find((t=>t.ssrc===a));if(c)if(i===this.UNKNOWN_DECODER){if(!this.hadLastDecoderWithIssue(o,a)){this.setLastDecoderWithIssue(o,a,this.UNKNOWN_DECODER);const r={mimeType:t.mimeType,decoderImplementation:i};n.push({ssrc:a,statsSample:r,type:e.Stream,reason:s.UnknownVideoDecoderIssue,trackIdentifier:t.track.trackIdentifier})}}else this.setLastDecoderWithIssue(o,a,void 0)})),n}setLastDecoderWithIssue(t,e,s){const n=this.#u[t]??{};void 0===s?delete n[e]:n[e]=s,this.#u[t]=n}hadLastDecoderWithIssue(t,e){const s=this.#u[t];return(s&&s[e])===this.UNKNOWN_DECODER}}const D=(t,e)=>{for(let s=1;s<e.length;s+=1){const n=e[s].video.inbound.find((e=>e.ssrc===t));if(!n)continue;const o=e[s-1].video.inbound.find((e=>e.ssrc===t)),r=n.frameWidth!==o?.frameWidth,i=n.frameHeight!==o?.frameHeight;if(r||i)return!0}return!1};class R extends w{#h;#l;constructor(t={}){super(),this.#h=t.avgFreezeDurationThresholdMs??1e3,this.#l=t.frozenDurationThresholdPct??30}performDetection(t){const e=t.networkScores.inbound;return void 0!==e&&e<=n.BAD?[]:this.processData(t)}processData(t){const n=[],o=this.getAllLastProcessedStats(t.connection.id);if(0===o.length)return[];const r=t.video.inbound.map((e=>{const s=o[o.length-1].video.inbound.find((t=>t.ssrc===e.ssrc));if(!s)return;if(D(e.ssrc,[o[o.length-1],t]))return;const n=e.freezeCount-(s.freezeCount??0),r=1e3*(e.totalFreezesDuration-(s.totalFreezesDuration??0)),i=n>0?r/n:0,a=r/(e.timestamp-s.timestamp)*100;return a>this.#l||i>this.#h?{ssrc:e.ssrc,avgFreezeDurationMs:i,frozenDurationPct:a}:void 0})).filter((t=>void 0!==t));return r.length>0&&n.push({type:e.Stream,reason:s.FrozenVideoTrack,statsSample:{ssrcs:r.map((t=>t.ssrc))}}),n}}class I extends w{#p;#f;constructor(t={}){super(t),this.#p=t.volatilityThreshold??1.5,this.#f=t.affectedStreamsPercentThreshold??50}performDetection(t){return[...this.getAllLastProcessedStats(t.connection.id),t].find((t=>void 0!==t.networkScores.inbound&&t.networkScores.inbound<=n.BAD))?[]:this.processData(t)}processData(t){const n=[],o=[...this.getAllLastProcessedStats(t.connection.id),t],r=t.video.inbound.map((t=>{const e=[];if(D(t.ssrc,o))return;if(o.length<5)return;for(let s=1;s<o.length;s+=1){let n=0,r=0,i=0;const a=o[s].video.inbound.find((e=>e.ssrc===t.ssrc));if(!a)continue;const c=o[s-1].video.inbound.find((e=>e.ssrc===t.ssrc));c&&(n=a.framesDecoded-c.framesDecoded,r=a.totalDecodeTime-c.totalDecodeTime),r>0&&n>0&&(i=1e3*r/n),e.push(i)}const s=e.reduce(((t,e)=>t+e),0)/e.length,n=e.map((t=>(t-s)**2)),r=n.reduce(((t,e)=>t+e),0)/n.length,i=Math.sqrt(r),a=e.every(((t,s)=>0===s||t>e[s-1]));return console.log("THROTTLE",{isDecodeTimePerFrameIncrease:a,volatility:i,mean:s}),i>this.#p&&a?(console.log("THROTTLE DETECTED"),{ssrc:t.ssrc,allDecodeTimePerFrame:e,volatility:i}):void 0})).filter((t=>Boolean(t))),i=r.length/(t.video.inbound.length/100);return i>this.#f&&(n.push({type:e.CPU,reason:s.DecoderCPUThrottling,statsSample:{affectedStreamsPercent:i,throtthedStreams:r}}),this.deleteLastProcessedStats(t.connection.id)),n}}const E=t=>"closed"===t.iceConnectionState||"closed"===t.connectionState,M=(t,e,s)=>8*((t,e,s)=>{if(!e)return 0;const n=t[s],o=e[s];if(null==n||null==o)return 0;const r=Math.floor(t.timestamp)-Math.floor(e.timestamp);return 0===r?0:(Number(n)-Number(o))/r*1e3})(t,e,s);class N{connections=[];statsParser;constructor(t){this.statsParser=t.statsParser}listConnections(){return[...this.connections]}addPeerConnection(t){this.connections.push({id:t.id??String(Date.now()+Math.random().toString(32)),pc:t.pc})}removePeerConnection(t){const e=this.connections.findIndex((({pc:e})=>e===t.pc));e>=0&&this.removeConnectionsByIndexes([e])}async parse(){const t=[],e=this.connections.map((async(e,s)=>{if(!E(e.pc))return this.statsParser.parse(e);t.unshift(s)}));t.length&&this.removeConnectionsByIndexes(t);return(await Promise.all(e)).filter((t=>void 0!==t))}removeConnectionsByIndexes(t){t.forEach((t=>{this.connections.splice(t,1)}))}}class _{prevStats=new Map;allowedReportTypes=new Set(["candidate-pair","inbound-rtp","outbound-rtp","remote-outbound-rtp","remote-inbound-rtp","track","transport"]);ignoreSSRCList;logger;constructor(t){this.ignoreSSRCList=t.ignoreSSRCList??[],this.logger=t.logger}get previouslyParsedStatsConnectionsIds(){return[...this.prevStats.keys()]}async parse(t){if(!E(t.pc))return this.getConnectionStats(t);this.logger.debug("Skip stats parsing. Connection is closed.",{connection:t})}async getConnectionStats(t){const{pc:e,id:s}=t;try{const n=Date.now(),o=e.getReceivers().filter((t=>t.track?.enabled)),r=e.getSenders().filter((t=>t.track?.enabled)),i=await Promise.all(o.map((t=>t.getStats()))),a=await Promise.all(r.map((t=>t.getStats())));return{id:s,stats:this.mapReportsStats([...i,...a],t),timeTaken:Date.now()-n}}catch(t){return void this.logger.error("Failed to get stats for PC",{id:s,pc:e,error:t})}}mapReportsStats(t,e){const s={audio:{inbound:[],outbound:[]},video:{inbound:[],outbound:[]},connection:{},remote:{video:{inbound:[],outbound:[]},audio:{inbound:[],outbound:[]}}};t.forEach((t=>{t.forEach((e=>{this.allowedReportTypes.has(e.type)&&this.updateMappedStatsWithReportItemData(e,s,t)}))}));const{id:n}=e,o=this.prevStats.get(n);return o&&this.propagateStatsWithRateValues(s,o.stats),this.prevStats.set(n,{stats:s,ts:Date.now()}),v({taskId:n,delayMs:35e3,callback:()=>this.prevStats.delete(n)}),s}updateMappedStatsWithReportItemData(t,e,s){const n=t.type;if("candidate-pair"===n&&"succeeded"===t.state&&t.nominated)return void(e.connection=this.prepareConnectionStats(t,s));const o=this.getMediaType(t);if(!o)return;const r=t.ssrc;if(!r||!this.ignoreSSRCList.includes(r))if("outbound-rtp"!==n)if("inbound-rtp"!==n)"remote-outbound-rtp"!==n?"remote-inbound-rtp"===n&&(this.mapConnectionStatsIfNecessary(e,t,s),e.remote[o].inbound.push({...t})):e.remote[o].outbound.push({...t});else{const n=s.get(t.trackId)||s.get(t.mediaSourceId)||{};this.mapConnectionStatsIfNecessary(e,t,s);const r={...t,track:{...n}};e[o].inbound.push(r)}else{const n=s.get(t.trackId)||s.get(t.mediaSourceId)||{},r={...t,track:{...n}};e[o].outbound.push(r)}}getMediaType(t){const e=t.mediaType||t.kind;if(!["audio","video"].includes(e)){const{id:e}=t;if(!e)return;return String(e).includes("Video")?"video":String(e).includes("Audio")?"audio":void 0}return e}propagateStatsWithRateValues(t,e){t.audio.inbound.forEach((t=>{const s=e.audio.inbound.find((({id:e})=>e===t.id));t.bitrate=M(t,s,"bytesReceived"),t.packetRate=M(t,s,"packetsReceived")})),t.audio.outbound.forEach((t=>{const s=e.audio.outbound.find((({id:e})=>e===t.id));t.bitrate=M(t,s,"bytesSent"),t.packetRate=M(t,s,"packetsSent")})),t.video.inbound.forEach((t=>{const s=e.video.inbound.find((({id:e})=>e===t.id));t.bitrate=M(t,s,"bytesReceived"),t.packetRate=M(t,s,"packetsReceived")})),t.video.outbound.forEach((t=>{const s=e.video.outbound.find((({id:e})=>e===t.id));t.bitrate=M(t,s,"bytesSent"),t.packetRate=M(t,s,"packetsSent")}))}mapConnectionStatsIfNecessary(t,e,s){if(t.connection.id||!e.transportId)return;const n=s.get(e.transportId);if(n&&n.selectedCandidatePairId){const e=s.get(n.selectedCandidatePairId);t.connection=this.prepareConnectionStats(e,s)}}prepareConnectionStats(t,e){if(!t||!e)return{};const s={...t};if(s.remoteCandidateId){const t=e.get(s.remoteCandidateId);s.remote={...t}}if(s.localCandidateId){const t=e.get(s.localCandidateId);s.local={...t}}return s}}class O{eventEmitter;#m=!1;detectors=[];networkScoresCalculator;statsReporter;compositeStatsParser;logger;autoAddPeerConnections;constructor(e){this.logger=e.logger??{debug:()=>{},info:()=>{},warn:()=>{},error:()=>{}},this.eventEmitter=e.issueEmitter??new g,e.onIssues&&this.eventEmitter.on(t.Issue,e.onIssues),e.onNetworkScoresUpdated&&this.eventEmitter.on(t.NetworkScoresUpdated,e.onNetworkScoresUpdated),this.detectors=e.detectors??[new L,new P,new y,new T,new b,new C,new R,new I],this.networkScoresCalculator=e.networkScoresCalculator??new k,this.compositeStatsParser=e.compositeStatsParser??new N({statsParser:new _({ignoreSSRCList:e.ignoreSSRCList,logger:this.logger})}),this.statsReporter=e.statsReporter??new S({compositeStatsParser:this.compositeStatsParser,getStatsInterval:e.getStatsInterval??5e3}),window.wid=this,this.autoAddPeerConnections=e.autoAddPeerConnections??!0,this.autoAddPeerConnections&&this.wrapRTCPeerConnection(),this.statsReporter.on(S.STATS_REPORT_READY_EVENT,(t=>{const e=this.calculateNetworkScores(t.stats);this.detectIssues({data:t.stats},e)})),this.statsReporter.on(S.STATS_REPORTS_PARSED,(s=>{const n={timeTaken:s.timeTaken,ts:Date.now()};e.onStats&&e.onStats(s.reportItems),this.eventEmitter.emit(t.StatsParsingFinished,n)}))}watchNewPeerConnections(){if(!this.autoAddPeerConnections)throw new Error("Auto add peer connections was disabled in the constructor.");this.#m?this.logger.warn("WebRTCIssueDetector is already started. Skip processing"):(this.logger.info("Start watching peer connections"),this.#m=!0,this.statsReporter.startReporting())}stopWatchingNewPeerConnections(){this.#m?(this.logger.info("Stop watching peer connections"),this.#m=!1,this.statsReporter.stopReporting()):this.logger.warn("WebRTCIssueDetector is already stopped. Skip processing")}handleNewPeerConnection(t,e){this.#m||!this.autoAddPeerConnections?(this.#m||!1!==this.autoAddPeerConnections||(this.logger.info("Starting stats reporting for new peer connection"),this.#m=!0,this.statsReporter.startReporting()),this.logger.debug("Handling new peer connection",t),this.compositeStatsParser.addPeerConnection({pc:t,id:e})):this.logger.debug("Skip handling new peer connection. Detector is not running",t)}emitIssues(e){this.eventEmitter.emit(t.Issue,e)}detectIssues({data:t},e){const s=this.detectors.reduce(((s,n)=>[...s,...n.detect(t,e)]),[]);s.length>0&&this.emitIssues(s)}calculateNetworkScores(e){const s=this.networkScoresCalculator.calculate(e);return this.eventEmitter.emit(t.NetworkScoresUpdated,s),s}wrapRTCPeerConnection(){if(!window.RTCPeerConnection)return void this.logger.warn("No RTCPeerConnection found in browser window. Skipping");const t=window.RTCPeerConnection,e=t=>this.handleNewPeerConnection(t);function s(s){const n=new t(s);return e(n),n}s.prototype=t.prototype,window.RTCPeerConnection=s}}export{b as AvailableOutgoingBitrateIssueDetector,w as BaseIssueDetector,N as CompositeRTCStatsParser,t as EventType,R as FrozenVideoTrackDetector,P as InboundNetworkIssueDetector,s as IssueReason,e as IssueType,n as MosQuality,T as NetworkMediaSyncIssueDetector,k as NetworkScoresCalculator,y as OutboundNetworkIssueDetector,S as PeriodicWebRTCStatsReporter,L as QualityLimitationsIssueDetector,_ as RTCStatsParser,C as UnknownVideoDecoderImplementationDetector,I as VideoDecoderIssueDetector,g as WebRTCIssueEmitter,O as default};
var t,e,s,n;function o(){}function r(){r.init.call(this)}function i(t){return void 0===t._maxListeners?r.defaultMaxListeners:t._maxListeners}function a(t,e,s){if(e)t.call(s);else for(var n=t.length,o=m(t,n),r=0;r<n;++r)o[r].call(s)}function c(t,e,s,n){if(e)t.call(s,n);else for(var o=t.length,r=m(t,o),i=0;i<o;++i)r[i].call(s,n)}function d(t,e,s,n,o){if(e)t.call(s,n,o);else for(var r=t.length,i=m(t,r),a=0;a<r;++a)i[a].call(s,n,o)}function u(t,e,s,n,o,r){if(e)t.call(s,n,o,r);else for(var i=t.length,a=m(t,i),c=0;c<i;++c)a[c].call(s,n,o,r)}function h(t,e,s,n){if(e)t.apply(s,n);else for(var o=t.length,r=m(t,o),i=0;i<o;++i)r[i].apply(s,n)}function l(t,e,s,n){var r,a,c,d;if("function"!=typeof s)throw new TypeError('"listener" argument must be a function');if((a=t._events)?(a.newListener&&(t.emit("newListener",e,s.listener?s.listener:s),a=t._events),c=a[e]):(a=t._events=new o,t._eventsCount=0),c){if("function"==typeof c?c=a[e]=n?[s,c]:[c,s]:n?c.unshift(s):c.push(s),!c.warned&&(r=i(t))&&r>0&&c.length>r){c.warned=!0;var u=new Error("Possible EventEmitter memory leak detected. "+c.length+" "+e+" listeners added. Use emitter.setMaxListeners() to increase limit");u.name="MaxListenersExceededWarning",u.emitter=t,u.type=e,u.count=c.length,d=u,"function"==typeof console.warn?console.warn(d):console.log(d)}}else c=a[e]=s,++t._eventsCount;return t}function p(t,e,s){var n=!1;function o(){t.removeListener(e,o),n||(n=!0,s.apply(t,arguments))}return o.listener=s,o}function f(t){var e=this._events;if(e){var s=e[t];if("function"==typeof s)return 1;if(s)return s.length}return 0}function m(t,e){for(var s=new Array(e);e--;)s[e]=t[e];return s}o.prototype=Object.create(null),r.EventEmitter=r,r.usingDomains=!1,r.prototype.domain=void 0,r.prototype._events=void 0,r.prototype._maxListeners=void 0,r.defaultMaxListeners=10,r.init=function(){this.domain=null,r.usingDomains&&undefined.active,this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=new o,this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},r.prototype.setMaxListeners=function(t){if("number"!=typeof t||t<0||isNaN(t))throw new TypeError('"n" argument must be a positive number');return this._maxListeners=t,this},r.prototype.getMaxListeners=function(){return i(this)},r.prototype.emit=function(t){var e,s,n,o,r,i,l,p="error"===t;if(i=this._events)p=p&&null==i.error;else if(!p)return!1;if(l=this.domain,p){if(e=arguments[1],!l){if(e instanceof Error)throw e;var f=new Error('Uncaught, unspecified "error" event. ('+e+")");throw f.context=e,f}return e||(e=new Error('Uncaught, unspecified "error" event')),e.domainEmitter=this,e.domain=l,e.domainThrown=!1,l.emit("error",e),!1}if(!(s=i[t]))return!1;var m="function"==typeof s;switch(n=arguments.length){case 1:a(s,m,this);break;case 2:c(s,m,this,arguments[1]);break;case 3:d(s,m,this,arguments[1],arguments[2]);break;case 4:u(s,m,this,arguments[1],arguments[2],arguments[3]);break;default:for(o=new Array(n-1),r=1;r<n;r++)o[r-1]=arguments[r];h(s,m,this,o)}return!0},r.prototype.addListener=function(t,e){return l(this,t,e,!1)},r.prototype.on=r.prototype.addListener,r.prototype.prependListener=function(t,e){return l(this,t,e,!0)},r.prototype.once=function(t,e){if("function"!=typeof e)throw new TypeError('"listener" argument must be a function');return this.on(t,p(this,t,e)),this},r.prototype.prependOnceListener=function(t,e){if("function"!=typeof e)throw new TypeError('"listener" argument must be a function');return this.prependListener(t,p(this,t,e)),this},r.prototype.removeListener=function(t,e){var s,n,r,i,a;if("function"!=typeof e)throw new TypeError('"listener" argument must be a function');if(!(n=this._events))return this;if(!(s=n[t]))return this;if(s===e||s.listener&&s.listener===e)0==--this._eventsCount?this._events=new o:(delete n[t],n.removeListener&&this.emit("removeListener",t,s.listener||e));else if("function"!=typeof s){for(r=-1,i=s.length;i-- >0;)if(s[i]===e||s[i].listener&&s[i].listener===e){a=s[i].listener,r=i;break}if(r<0)return this;if(1===s.length){if(s[0]=void 0,0==--this._eventsCount)return this._events=new o,this;delete n[t]}else!function(t,e){for(var s=e,n=s+1,o=t.length;n<o;s+=1,n+=1)t[s]=t[n];t.pop()}(s,r);n.removeListener&&this.emit("removeListener",t,a||e)}return this},r.prototype.off=function(t,e){return this.removeListener(t,e)},r.prototype.removeAllListeners=function(t){var e,s;if(!(s=this._events))return this;if(!s.removeListener)return 0===arguments.length?(this._events=new o,this._eventsCount=0):s[t]&&(0==--this._eventsCount?this._events=new o:delete s[t]),this;if(0===arguments.length){for(var n,r=Object.keys(s),i=0;i<r.length;++i)"removeListener"!==(n=r[i])&&this.removeAllListeners(n);return this.removeAllListeners("removeListener"),this._events=new o,this._eventsCount=0,this}if("function"==typeof(e=s[t]))this.removeListener(t,e);else if(e)do{this.removeListener(t,e[e.length-1])}while(e[0]);return this},r.prototype.listeners=function(t){var e,s=this._events;return s&&(e=s[t])?"function"==typeof e?[e.listener||e]:function(t){for(var e=new Array(t.length),s=0;s<e.length;++s)e[s]=t[s].listener||t[s];return e}(e):[]},r.listenerCount=function(t,e){return"function"==typeof t.listenerCount?t.listenerCount(e):f.call(t,e)},r.prototype.listenerCount=f,r.prototype.eventNames=function(){return this._eventsCount>0?Reflect.ownKeys(this._events):[]};class g extends r{}!function(t){t.Issue="issue",t.NetworkScoresUpdated="network-scores-updated",t.StatsParsingFinished="stats-parsing-finished"}(t||(t={})),function(t){t.Network="network",t.CPU="cpu",t.Server="server",t.Stream="stream"}(e||(e={})),function(t){t.OutboundNetworkQuality="outbound-network-quality",t.InboundNetworkQuality="inbound-network-quality",t.OutboundNetworkMediaLatency="outbound-network-media-latency",t.InboundNetworkMediaLatency="inbound-network-media-latency",t.NetworkMediaSyncFailure="network-media-sync-failure",t.OutboundNetworkThroughput="outbound-network-throughput",t.InboundNetworkThroughput="inbound-network-throughput",t.EncoderCPUThrottling="encoder-cpu-throttling",t.DecoderCPUThrottling="decoder-cpu-throttling",t.ServerIssue="server-issue",t.UnknownVideoDecoderIssue="unknown-video-decoder",t.LowInboundMOS="low-inbound-mean-opinion-score",t.LowOutboundMOS="low-outbound-mean-opinion-score",t.FrozenVideoTrack="frozen-video-track"}(s||(s={})),function(t){t[t.BAD=2.1]="BAD",t[t.POOR=2.6]="POOR",t[t.FAIR=3.1]="FAIR",t[t.GOOD=3.8]="GOOD",t[t.EXCELLENT=4.3]="EXCELLENT"}(n||(n={}));class S extends r{static STATS_REPORT_READY_EVENT="stats-report-ready";static STATS_REPORTS_PARSED="stats-reports-parsed";isStopped=!1;reportTimer;getStatsInterval;compositeStatsParser;constructor(t){super(),this.compositeStatsParser=t.compositeStatsParser,this.getStatsInterval=t.getStatsInterval??1e4}get isRunning(){return!!this.reportTimer&&!this.isStopped}startReporting(){if(this.reportTimer)return;const t=()=>setTimeout((()=>{this.isStopped?this.reportTimer=void 0:this.parseReports().finally((()=>{this.reportTimer=t()}))}),this.getStatsInterval);this.isStopped=!1,this.reportTimer=t()}stopReporting(){this.isStopped=!0,this.reportTimer&&(clearTimeout(this.reportTimer),this.reportTimer=void 0)}async parseReports(){const t=Date.now(),e=await this.compositeStatsParser.parse(),s=Date.now()-t;this.emit(S.STATS_REPORTS_PARSED,{timeTaken:s,reportItems:e}),e.forEach((t=>{this.emit(S.STATS_REPORT_READY_EVENT,t)}))}}const v=(()=>{const t=new Map;return e=>{const{taskId:s,delayMs:n,maxJitterMs:o,callback:r}=e,i=Math.ceil(Math.random()*(o||0)),a=t.get(s);a&&clearTimeout(a);const c=setTimeout((()=>{r(),t.delete(s)}),n+i);t.set(s,c)}})();class k{#t={};calculate(t){const{connection:{id:e}}=t,{mos:s,stats:n}=this.calculateOutboundScore(t)||{},{mos:o,stats:r}=this.calculateInboundScore(t)||{};return this.#t[e]=t,v({taskId:e,delayMs:35e3,callback:()=>delete this.#t[e]}),{outbound:s,inbound:o,connectionId:e,statsSamples:{inboundStatsSample:r,outboundStatsSample:n}}}calculateOutboundScore(t){const e=[...t.remote?.audio.inbound||[],...t.remote?.video.inbound||[]];if(!e.length)return;const s=this.#t[t.connection.id];if(!s)return;const n=[...s.remote?.audio.inbound||[],...s.remote?.video.inbound||[]],{packetsSent:o}=t.connection,r=s.connection.packetsSent,i=e.reduce(((t,e)=>{const s=n.find((t=>t.ssrc===e.ssrc));return{sumJitter:t.sumJitter+e.jitter,packetsLost:t.packetsLost+e.packetsLost,lastPacketsLost:t.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,packetsLost:0,lastPacketsLost:0}),a=1e3*t.connection.currentRoundTripTime||0,{sumJitter:c}=i,d=c/e.length,u=o-r,h=i.packetsLost-i.lastPacketsLost,l=u&&h?Math.round(100*h/(u+h)):0;return{mos:this.calculateMOS({avgJitter:d,rtt:a,packetsLoss:l}),stats:{avgJitter:d,rtt:a,packetsLoss:l}}}calculateInboundScore(t){const e=[...t.audio?.inbound,...t.video?.inbound];if(!e.length)return;const s=this.#t[t.connection.id];if(!s)return;const n=[...s.video?.inbound,...s.audio?.inbound],{packetsReceived:o}=t.connection,r=s.connection.packetsReceived,i=e.reduce(((t,e)=>{const s=n.find((t=>t.ssrc===e.ssrc));return{sumJitter:t.sumJitter+e.jitter,packetsLost:t.packetsLost+e.packetsLost,lastPacketsLost:t.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,packetsLost:0,lastPacketsLost:0}),a=1e3*t.connection.currentRoundTripTime||0,{sumJitter:c}=i,d=c/e.length,u=o-r,h=i.packetsLost-i.lastPacketsLost,l=u&&h?Math.round(100*h/(u+h)):0;return{mos:this.calculateMOS({avgJitter:d,rtt:a,packetsLoss:l}),stats:{avgJitter:d,rtt:a,packetsLoss:l}}}calculateMOS({avgJitter:t,rtt:e,packetsLoss:s}){const n=e+2*t+10;let o=n<160?93.2-n/40:93.2-n/120-10;return o-=2.5*s,1+.035*o+7e-6*o*(o-60)*(100-o)}}class w{#e=new Map;#s;#n;constructor(t={}){this.#s=t.statsCleanupTtlMs??35e3,this.#n=t.maxParsedStatsStorageSize??5}detect(t,e){const s={...t,networkScores:{...e,statsSamples:e?.statsSamples||{}}},n=this.performDetection(s);return this.setLastProcessedStats(t.connection.id,s),this.performPrevStatsCleanup({connectionId:t.connection.id}),n}performPrevStatsCleanup(t){const{connectionId:e,cleanupCallback:s}=t;this.#e.has(e)&&v({taskId:e,delayMs:this.#s,callback:()=>{this.deleteLastProcessedStats(e),"function"==typeof s&&s()}})}setLastProcessedStats(t,e){if(!t||e.connection.id!==t)return;const s=this.#e.get(t)??[];s.push(e),s.length>this.#n&&s.shift(),this.#e.set(t,s)}getLastProcessedStats(t){const e=this.#e.get(t);return e?.[e.length-1]}getAllLastProcessedStats(t){return this.#e.get(t)??[]}deleteLastProcessedStats(t){this.#e.delete(t)}}class b extends w{#o;constructor(t={}){super(t),this.#o=t.availableOutgoingBitrateThreshold??1e5}performDetection(t){const n=[],{availableOutgoingBitrate:o}=t.connection;if(void 0===o)return n;const r=t.audio.outbound.reduce(((t,e)=>t+e.targetBitrate),0),i=t.video.outbound.reduce(((t,e)=>t+e.bitrate),0);if(!r&&!i)return n;const a={availableOutgoingBitrate:o,videoStreamsTotalBitrate:i,audioStreamsTotalTargetBitrate:r};return r>o||i>0&&o<this.#o?(n.push({statsSample:a,type:e.Network,reason:s.OutboundNetworkThroughput}),n):n}}class P extends w{#r;#i;#a;#c;constructor(t={}){super(),this.#r=t.highPacketLossThresholdPct??5,this.#i=t.highJitterThreshold??200,this.#a=t.highJitterBufferDelayThresholdMs??500,this.#c=t.highRttThresholdMs??250}performDetection(t){return this.processData(t)}processData(t){const n=[],o=[...t.audio?.inbound,...t.video?.inbound];if(!o.length)return n;const r=this.getLastProcessedStats(t.connection.id);if(!r)return n;const i=[...r.video?.inbound,...r.audio?.inbound],{packetsReceived:a}=t.connection,c=r.connection.packetsReceived,d=o.reduce(((t,e)=>{const s=i.find((t=>t.ssrc===e.ssrc)),n=s?.jitterBufferDelay||0,o=s?.jitterBufferEmittedCount||0,r=e.jitterBufferDelay-n,a=e.jitterBufferEmittedCount-o,c=r&&a?1e3*r/a:0;return{sumJitter:t.sumJitter+e.jitter,sumJitterBufferDelayMs:t.sumJitterBufferDelayMs+c,packetsLost:t.packetsLost+e.packetsLost,lastPacketsLost:t.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,sumJitterBufferDelayMs:0,packetsLost:0,lastPacketsLost:0}),u=1e3*t.connection.currentRoundTripTime||0,{sumJitter:h,sumJitterBufferDelayMs:l}=d,p=h/o.length,f=l/o.length,m=a-c,g=d.packetsLost-d.lastPacketsLost,S=m&&g?Math.round(100*g/(m+g)):0,v=S>this.#r,k=p>=this.#i,w=u>=this.#c,b=f>this.#a,P=w&&!k&&!v,y=v&&k,T=k&&b,L={rtt:u,packetLossPct:S,avgJitter:p,avgJitterBufferDelay:f};return(k||v)&&n.push({statsSample:L,type:e.Network,reason:s.InboundNetworkQuality,iceCandidate:t.connection.local.id}),P&&n.push({statsSample:L,type:e.Server,reason:s.ServerIssue,iceCandidate:t.connection.remote.id}),y&&n.push({statsSample:L,type:e.Network,reason:s.InboundNetworkMediaLatency,iceCandidate:t.connection.local.id}),T&&n.push({statsSample:L,type:e.Network,reason:s.NetworkMediaSyncFailure,iceCandidate:t.connection.local.id}),n}}class y extends w{#d;constructor(t={}){super(),this.#d=t.correctedSamplesThresholdPct??5}performDetection(t){return this.processData(t)}processData(t){const n=t.audio.inbound,o=[],r=this.getLastProcessedStats(t.connection.id)?.audio.inbound;return r?(n.forEach((t=>{const n=r.find((e=>e.ssrc===t.ssrc));if(!n)return;const i=t.track.insertedSamplesForDeceleration+t.track.removedSamplesForAcceleration,a=n.track.insertedSamplesForDeceleration+n.track.removedSamplesForAcceleration;if(i===a)return;const c=t.track.totalSamplesReceived-n.track.totalSamplesReceived,d=i-a,u=Math.round(100*d/c),h={correctedSamplesPct:u};u>this.#d&&o.push({statsSample:h,type:e.Network,reason:s.NetworkMediaSyncFailure,ssrc:t.ssrc})})),o):o}}class T extends w{#r;#i;constructor(t={}){super(),this.#r=t.highPacketLossThresholdPct??5,this.#i=t.highJitterThreshold??200}performDetection(t){return this.processData(t)}processData(t){const n=[],o=[...t.remote?.audio.inbound||[],...t.remote?.video.inbound||[]];if(!o.length)return n;const r=this.getLastProcessedStats(t.connection.id);if(!r)return n;const i=[...r.remote?.audio.inbound||[],...r.remote?.video.inbound||[]],{packetsSent:a}=t.connection,c=r.connection.packetsSent,d=o.reduce(((t,e)=>{const s=i.find((t=>t.ssrc===e.ssrc));return{sumJitter:t.sumJitter+e.jitter,packetsLost:t.packetsLost+e.packetsLost,lastPacketsLost:t.lastPacketsLost+(s?.packetsLost||0)}}),{sumJitter:0,packetsLost:0,lastPacketsLost:0}),u=1e3*t.connection.currentRoundTripTime||0,{sumJitter:h}=d,l=h/o.length,p=a-c,f=d.packetsLost-d.lastPacketsLost,m=p&&f?Math.round(100*f/(p+f)):0,g=m>this.#r,S=l>=this.#i,v=!g&&S||S||g,k={rtt:u,avgJitter:l,packetLossPct:m};return g&&S&&n.push({statsSample:k,type:e.Network,reason:s.OutboundNetworkMediaLatency,iceCandidate:t.connection.local.id}),v&&n.push({statsSample:k,type:e.Network,reason:s.OutboundNetworkQuality,iceCandidate:t.connection.local.id}),n}}class L extends w{performDetection(t){return this.processData(t)}processData(t){const n=t.video.outbound.filter((t=>"none"!==t.qualityLimitationReason)),o=[],r=this.getLastProcessedStats(t.connection.id)?.video.outbound;return r?(n.forEach((t=>{const n=r.find((e=>e.ssrc===t.ssrc));if(!n)return;const i={qualityLimitationReason:t.qualityLimitationReason};t.framesSent>n.framesSent||("cpu"===t.qualityLimitationReason&&o.push({statsSample:i,type:e.CPU,reason:s.EncoderCPUThrottling,ssrc:t.ssrc}),"bandwidth"===t.qualityLimitationReason&&o.push({statsSample:i,type:e.Network,reason:s.OutboundNetworkThroughput,ssrc:t.ssrc}))})),o):o}}class C extends w{UNKNOWN_DECODER="unknown";#u={};performDetection(t){return this.processData(t)}performPrevStatsCleanup(t){const{connectionId:e,cleanupCallback:s}=t;super.performPrevStatsCleanup({...t,cleanupCallback:()=>{delete this.#u[e],"function"==typeof s&&s()}})}processData(t){const n=[],{id:o}=t.connection,r=this.getLastProcessedStats(o)?.video.inbound;return t.video.inbound.forEach((t=>{const{decoderImplementation:i,ssrc:a}=t,c=r?.find((t=>t.ssrc===a));if(c)if(i===this.UNKNOWN_DECODER){if(!this.hadLastDecoderWithIssue(o,a)){this.setLastDecoderWithIssue(o,a,this.UNKNOWN_DECODER);const r={mimeType:t.mimeType,decoderImplementation:i};n.push({ssrc:a,statsSample:r,type:e.Stream,reason:s.UnknownVideoDecoderIssue,trackIdentifier:t.track.trackIdentifier})}}else this.setLastDecoderWithIssue(o,a,void 0)})),n}setLastDecoderWithIssue(t,e,s){const n=this.#u[t]??{};void 0===s?delete n[e]:n[e]=s,this.#u[t]=n}hadLastDecoderWithIssue(t,e){const s=this.#u[t];return(s&&s[e])===this.UNKNOWN_DECODER}}const D=(t,e)=>{for(let s=1;s<e.length;s+=1){const n=e[s].video.inbound.find((e=>e.ssrc===t));if(!n)continue;const o=e[s-1].video.inbound.find((e=>e.ssrc===t)),r=n.frameWidth!==o?.frameWidth,i=n.frameHeight!==o?.frameHeight;if(r||i)return!0}return!1};class R extends w{#h;#l;constructor(t={}){super(),this.#h=t.avgFreezeDurationThresholdMs??1e3,this.#l=t.frozenDurationThresholdPct??30}performDetection(t){const e=t.networkScores.inbound;return void 0!==e&&e<=n.BAD?[]:this.processData(t)}processData(t){const n=[],o=this.getAllLastProcessedStats(t.connection.id);if(0===o.length)return[];const r=t.video.inbound.map((e=>{const s=o[o.length-1].video.inbound.find((t=>t.ssrc===e.ssrc));if(!s)return;if(D(e.ssrc,[o[o.length-1],t]))return;const n=e.freezeCount-(s.freezeCount??0),r=1e3*(e.totalFreezesDuration-(s.totalFreezesDuration??0)),i=n>0?r/n:0,a=r/(e.timestamp-s.timestamp)*100;return a>this.#l||i>this.#h?{ssrc:e.ssrc,avgFreezeDurationMs:i,frozenDurationPct:a}:void 0})).filter((t=>void 0!==t));return r.length>0&&n.push({type:e.Stream,reason:s.FrozenVideoTrack,statsSample:{ssrcs:r.map((t=>t.ssrc))}}),n}}class I extends w{#p;#f;constructor(t={}){super(t),this.#p=t.volatilityThreshold??1.5,this.#f=t.affectedStreamsPercentThreshold??50}performDetection(t){return[...this.getAllLastProcessedStats(t.connection.id),t].find((t=>void 0!==t.networkScores.inbound&&t.networkScores.inbound<=n.BAD))?[]:this.processData(t)}processData(t){const n=[],o=[...this.getAllLastProcessedStats(t.connection.id),t],r=t.video.inbound.map((t=>{const e=[],s=[];if(D(t.ssrc,o))return;if(o.length<5)return;for(let n=1;n<o.length;n+=1){let r=0,i=0,a=0;const c=o[n].video.inbound.find((e=>e.ssrc===t.ssrc));if(!c)continue;const d=o[n-1].video.inbound.find((e=>e.ssrc===t.ssrc));d&&(r=c.framesDecoded-d.framesDecoded,i=c.totalDecodeTime-d.totalDecodeTime),i>0&&r>0&&(a=1e3*i/r),e.push(a),s.push(c.framesPerSecond)}const n=e.reduce(((t,e)=>t+e),0)/e.length,r=e.map((t=>(t-n)**2)),i=r.reduce(((t,e)=>t+e),0)/r.length,a=Math.sqrt(i),c=e.every(((t,s)=>0===s||t>e[s-1])),d=s.reduce(((t,e)=>t+e),0)/e.length,u=s.map((t=>(t-d)**2)),h=u.reduce(((t,e)=>t+e),0)/u.length,l=Math.sqrt(h);return console.log("THROTTLE",{volatilityFps:l,allFpss:s}),a>this.#p&&c?(console.log("THROTTLE DETECTED"),{ssrc:t.ssrc,allDecodeTimePerFrame:e,volatility:a}):void 0})).filter((t=>Boolean(t))),i=r.length/(t.video.inbound.length/100);return i>this.#f&&(n.push({type:e.CPU,reason:s.DecoderCPUThrottling,statsSample:{affectedStreamsPercent:i,throtthedStreams:r}}),this.deleteLastProcessedStats(t.connection.id)),n}}const E=t=>"closed"===t.iceConnectionState||"closed"===t.connectionState,M=(t,e,s)=>8*((t,e,s)=>{if(!e)return 0;const n=t[s],o=e[s];if(null==n||null==o)return 0;const r=Math.floor(t.timestamp)-Math.floor(e.timestamp);return 0===r?0:(Number(n)-Number(o))/r*1e3})(t,e,s);class N{connections=[];statsParser;constructor(t){this.statsParser=t.statsParser}listConnections(){return[...this.connections]}addPeerConnection(t){this.connections.push({id:t.id??String(Date.now()+Math.random().toString(32)),pc:t.pc})}removePeerConnection(t){const e=this.connections.findIndex((({pc:e})=>e===t.pc));e>=0&&this.removeConnectionsByIndexes([e])}async parse(){const t=[],e=this.connections.map((async(e,s)=>{if(!E(e.pc))return this.statsParser.parse(e);t.unshift(s)}));t.length&&this.removeConnectionsByIndexes(t);return(await Promise.all(e)).filter((t=>void 0!==t))}removeConnectionsByIndexes(t){t.forEach((t=>{this.connections.splice(t,1)}))}}class _{prevStats=new Map;allowedReportTypes=new Set(["candidate-pair","inbound-rtp","outbound-rtp","remote-outbound-rtp","remote-inbound-rtp","track","transport"]);ignoreSSRCList;logger;constructor(t){this.ignoreSSRCList=t.ignoreSSRCList??[],this.logger=t.logger}get previouslyParsedStatsConnectionsIds(){return[...this.prevStats.keys()]}async parse(t){if(!E(t.pc))return this.getConnectionStats(t);this.logger.debug("Skip stats parsing. Connection is closed.",{connection:t})}async getConnectionStats(t){const{pc:e,id:s}=t;try{const n=Date.now(),o=e.getReceivers().filter((t=>t.track?.enabled)),r=e.getSenders().filter((t=>t.track?.enabled)),i=await Promise.all(o.map((t=>t.getStats()))),a=await Promise.all(r.map((t=>t.getStats())));return{id:s,stats:this.mapReportsStats([...i,...a],t),timeTaken:Date.now()-n}}catch(t){return void this.logger.error("Failed to get stats for PC",{id:s,pc:e,error:t})}}mapReportsStats(t,e){const s={audio:{inbound:[],outbound:[]},video:{inbound:[],outbound:[]},connection:{},remote:{video:{inbound:[],outbound:[]},audio:{inbound:[],outbound:[]}}};t.forEach((t=>{t.forEach((e=>{this.allowedReportTypes.has(e.type)&&this.updateMappedStatsWithReportItemData(e,s,t)}))}));const{id:n}=e,o=this.prevStats.get(n);return o&&this.propagateStatsWithRateValues(s,o.stats),this.prevStats.set(n,{stats:s,ts:Date.now()}),v({taskId:n,delayMs:35e3,callback:()=>this.prevStats.delete(n)}),s}updateMappedStatsWithReportItemData(t,e,s){const n=t.type;if("candidate-pair"===n&&"succeeded"===t.state&&t.nominated)return void(e.connection=this.prepareConnectionStats(t,s));const o=this.getMediaType(t);if(!o)return;const r=t.ssrc;if(!r||!this.ignoreSSRCList.includes(r))if("outbound-rtp"!==n)if("inbound-rtp"!==n)"remote-outbound-rtp"!==n?"remote-inbound-rtp"===n&&(this.mapConnectionStatsIfNecessary(e,t,s),e.remote[o].inbound.push({...t})):e.remote[o].outbound.push({...t});else{const n=s.get(t.trackId)||s.get(t.mediaSourceId)||{};this.mapConnectionStatsIfNecessary(e,t,s);const r={...t,track:{...n}};e[o].inbound.push(r)}else{const n=s.get(t.trackId)||s.get(t.mediaSourceId)||{},r={...t,track:{...n}};e[o].outbound.push(r)}}getMediaType(t){const e=t.mediaType||t.kind;if(!["audio","video"].includes(e)){const{id:e}=t;if(!e)return;return String(e).includes("Video")?"video":String(e).includes("Audio")?"audio":void 0}return e}propagateStatsWithRateValues(t,e){t.audio.inbound.forEach((t=>{const s=e.audio.inbound.find((({id:e})=>e===t.id));t.bitrate=M(t,s,"bytesReceived"),t.packetRate=M(t,s,"packetsReceived")})),t.audio.outbound.forEach((t=>{const s=e.audio.outbound.find((({id:e})=>e===t.id));t.bitrate=M(t,s,"bytesSent"),t.packetRate=M(t,s,"packetsSent")})),t.video.inbound.forEach((t=>{const s=e.video.inbound.find((({id:e})=>e===t.id));t.bitrate=M(t,s,"bytesReceived"),t.packetRate=M(t,s,"packetsReceived")})),t.video.outbound.forEach((t=>{const s=e.video.outbound.find((({id:e})=>e===t.id));t.bitrate=M(t,s,"bytesSent"),t.packetRate=M(t,s,"packetsSent")}))}mapConnectionStatsIfNecessary(t,e,s){if(t.connection.id||!e.transportId)return;const n=s.get(e.transportId);if(n&&n.selectedCandidatePairId){const e=s.get(n.selectedCandidatePairId);t.connection=this.prepareConnectionStats(e,s)}}prepareConnectionStats(t,e){if(!t||!e)return{};const s={...t};if(s.remoteCandidateId){const t=e.get(s.remoteCandidateId);s.remote={...t}}if(s.localCandidateId){const t=e.get(s.localCandidateId);s.local={...t}}return s}}class O{eventEmitter;#m=!1;detectors=[];networkScoresCalculator;statsReporter;compositeStatsParser;logger;autoAddPeerConnections;constructor(e){this.logger=e.logger??{debug:()=>{},info:()=>{},warn:()=>{},error:()=>{}},this.eventEmitter=e.issueEmitter??new g,e.onIssues&&this.eventEmitter.on(t.Issue,e.onIssues),e.onNetworkScoresUpdated&&this.eventEmitter.on(t.NetworkScoresUpdated,e.onNetworkScoresUpdated),this.detectors=e.detectors??[new L,new P,new T,new y,new b,new C,new R,new I],this.networkScoresCalculator=e.networkScoresCalculator??new k,this.compositeStatsParser=e.compositeStatsParser??new N({statsParser:new _({ignoreSSRCList:e.ignoreSSRCList,logger:this.logger})}),this.statsReporter=e.statsReporter??new S({compositeStatsParser:this.compositeStatsParser,getStatsInterval:e.getStatsInterval??5e3}),window.wid=this,this.autoAddPeerConnections=e.autoAddPeerConnections??!0,this.autoAddPeerConnections&&this.wrapRTCPeerConnection(),this.statsReporter.on(S.STATS_REPORT_READY_EVENT,(t=>{const e=this.calculateNetworkScores(t.stats);this.detectIssues({data:t.stats},e)})),this.statsReporter.on(S.STATS_REPORTS_PARSED,(s=>{const n={timeTaken:s.timeTaken,ts:Date.now()};e.onStats&&e.onStats(s.reportItems),this.eventEmitter.emit(t.StatsParsingFinished,n)}))}watchNewPeerConnections(){if(!this.autoAddPeerConnections)throw new Error("Auto add peer connections was disabled in the constructor.");this.#m?this.logger.warn("WebRTCIssueDetector is already started. Skip processing"):(this.logger.info("Start watching peer connections"),this.#m=!0,this.statsReporter.startReporting())}stopWatchingNewPeerConnections(){this.#m?(this.logger.info("Stop watching peer connections"),this.#m=!1,this.statsReporter.stopReporting()):this.logger.warn("WebRTCIssueDetector is already stopped. Skip processing")}handleNewPeerConnection(t,e){this.#m||!this.autoAddPeerConnections?(this.#m||!1!==this.autoAddPeerConnections||(this.logger.info("Starting stats reporting for new peer connection"),this.#m=!0,this.statsReporter.startReporting()),this.logger.debug("Handling new peer connection",t),this.compositeStatsParser.addPeerConnection({pc:t,id:e})):this.logger.debug("Skip handling new peer connection. Detector is not running",t)}emitIssues(e){this.eventEmitter.emit(t.Issue,e)}detectIssues({data:t},e){const s=this.detectors.reduce(((s,n)=>[...s,...n.detect(t,e)]),[]);s.length>0&&this.emitIssues(s)}calculateNetworkScores(e){const s=this.networkScoresCalculator.calculate(e);return this.eventEmitter.emit(t.NetworkScoresUpdated,s),s}wrapRTCPeerConnection(){if(!window.RTCPeerConnection)return void this.logger.warn("No RTCPeerConnection found in browser window. Skipping");const t=window.RTCPeerConnection,e=t=>this.handleNewPeerConnection(t);function s(s){const n=new t(s);return e(n),n}s.prototype=t.prototype,window.RTCPeerConnection=s}}export{b as AvailableOutgoingBitrateIssueDetector,w as BaseIssueDetector,N as CompositeRTCStatsParser,t as EventType,R as FrozenVideoTrackDetector,P as InboundNetworkIssueDetector,s as IssueReason,e as IssueType,n as MosQuality,y as NetworkMediaSyncIssueDetector,k as NetworkScoresCalculator,T as OutboundNetworkIssueDetector,S as PeriodicWebRTCStatsReporter,L as QualityLimitationsIssueDetector,_ as RTCStatsParser,C as UnknownVideoDecoderImplementationDetector,I as VideoDecoderIssueDetector,g as WebRTCIssueEmitter,O as default};
{
"name": "webrtc-issue-detector",
"version": "1.14.0-debug-detectors.1",
"version": "1.14.0-debug-detectors.2",
"description": "WebRTC diagnostic tool that detects issues with network or user devices",

@@ -5,0 +5,0 @@ "repository": "git@github.com:VLprojects/webrtc-issue-detector.git",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc