@observertc/client-monitor-js
Version:
ObserveRTC Client Integration Javascript Library
1 lines • 4.4 kB
JavaScript
export class FreezedVideoTrackDetector{trackMonitor;static ISSUE_TYPE="freezed-video-track";static KEYFRAME_STORM_ISSUE_TYPE="keyframe-storm";static RECOVERY_FAILED_ISSUE_TYPE="video-recovery-failed";name="freezed-video-track-detector";disabled=!1;static MAX_WINDOW_ENTRIES=128;issueKey;_stormIssueKey;_recoveryIssueKey;_lastFreezeCount=0;_startedFreezeAt;_window=[];_sumPlis=0;_sumFirs=0;_sumKeyFrames=0;_stormOn=!1;_stormStartedAt;_recoveryFailedOn=!1;_recoveryFailedStartedAt;_stalledSince;_pliCountSinceStalled=0;constructor(e){this.trackMonitor=e,this.issueKey=`${FreezedVideoTrackDetector.ISSUE_TYPE}-track-${e.track.id}`,this._stormIssueKey=`${FreezedVideoTrackDetector.KEYFRAME_STORM_ISSUE_TYPE}-track-${e.track.id}`,this._recoveryIssueKey=`${FreezedVideoTrackDetector.RECOVERY_FAILED_ISSUE_TYPE}-track-${e.track.id}`}get peerConnection(){return this.trackMonitor.getPeerConnection()}update(){if(this.disabled)return;const e=this.trackMonitor.getInboundRtp();if(!e)return;const t=this.peerConnection.parent.config,i=!0===e.isFreezed,r=e.freezeCount??0,s=Math.max(0,r-this._lastFreezeCount);this._lastFreezeCount=r;const o=0<s||i&&0===e.deltaFramesRendered;e.isFreezed=o,t.videoFreezesDetector&&this._checkFreeze(i,o,e.trackIdentifier);const n=t.videoRecoveryDetector;n&&(this._updateWindow(n,e),this._checkRecoveryFailed(n,o,e.deltaPliCount??0,e.deltaKeyFramesDecoded??0,e.freezeCount))}_checkFreeze(e,t,i){if(!e&&t){const e=this.peerConnection.parent;e.emit("freezed-video-track",{clientMonitor:e,trackMonitor:this.trackMonitor}),this._startedFreezeAt=Date.now(),e.raiseIssue(this.issueKey,{type:FreezedVideoTrackDetector.ISSUE_TYPE,payload:{trackId:i}})}else e&&!t&&(this._resolve(this.issueKey,"video freeze ended",this._startedFreezeAt),this._startedFreezeAt=void 0)}_updateWindow(e,t){const i=Date.now(),r=t.deltaPliCount??0,s=t.deltaFirCount??0,o=t.deltaKeyFramesDecoded??0;this._window.push({timestamp:i,pliCount:r,firCount:s,keyFramesDecoded:o}),this._sumPlis+=r,this._sumFirs+=s,this._sumKeyFrames+=o;for(let t=this._window[0];t&&(t.timestamp<i-e.windowInMs||FreezedVideoTrackDetector.MAX_WINDOW_ENTRIES<this._window.length);t=this._window[0])this._sumPlis-=t.pliCount,this._sumFirs-=t.firCount,this._sumKeyFrames-=t.keyFramesDecoded,this._window.shift();this._checkKeyframeStorm(e,i)}_checkKeyframeStorm(e,t){const i=this._window[0];if(!i)return;const r=Math.max(1e3,t-i.timestamp)/1e3,s=this._sumPlis/r;if(this._stormOn)return void(s<e.pliRateAlertOff&&(this._stormOn=!1,this._resolve(this._stormIssueKey,"keyframe storm subsided",this._stormStartedAt),this._stormStartedAt=void 0));if(s<=e.pliRateAlertOn)return;if(t-i.timestamp<e.windowInMs/2)return;this._stormOn=!0,this._stormStartedAt=t;const o=this.peerConnection.parent;o.emit("keyframe-storm",{clientMonitor:o,trackMonitor:this.trackMonitor,pliRate:s}),o.raiseIssue(this._stormIssueKey,{type:FreezedVideoTrackDetector.KEYFRAME_STORM_ISSUE_TYPE,payload:{peerConnectionId:this.peerConnection.peerConnectionId,trackId:this.trackMonitor.track.id,pliRate:s,firRate:this._sumFirs/r,keyFrameRate:this._sumKeyFrames/r,windowInMs:e.windowInMs}})}_checkRecoveryFailed(e,t,i,r,s){if(!t||0<r)return this._stalledSince=void 0,this._pliCountSinceStalled=0,void(this._recoveryFailedOn&&(this._recoveryFailedOn=!1,this._resolve(this._recoveryIssueKey,"video recovered",this._recoveryFailedStartedAt),this._recoveryFailedStartedAt=void 0));if(i<1&&void 0===this._stalledSince)return;const o=Date.now();if(this._stalledSince??=o,this._pliCountSinceStalled+=i,this._recoveryFailedOn)return;const n=o-this._stalledSince;if(n<e.recoveryFailedThresholdInMs)return;if(this._pliCountSinceStalled<e.recoveryFailedMinPliCount)return;this._recoveryFailedOn=!0,this._recoveryFailedStartedAt=o;const a=this.peerConnection.parent;a.emit("video-recovery-failed",{clientMonitor:a,trackMonitor:this.trackMonitor,pliCountSinceStalled:this._pliCountSinceStalled,stalledForInMs:n}),a.raiseIssue(this._recoveryIssueKey,{type:FreezedVideoTrackDetector.RECOVERY_FAILED_ISSUE_TYPE,payload:{peerConnectionId:this.peerConnection.peerConnectionId,trackId:this.trackMonitor.track.id,pliCountSinceStalled:this._pliCountSinceStalled,stalledForInMs:n,freezeCount:s}})}_resolve(e,t,i){const r=this.peerConnection.parent,s=r.activeIssues.get(e);let o;s&&(o={...s.payload,durationInMs:i?Date.now()-i:void 0}),r.resolveIssue(e,{comment:t,payload:o,resolvedAt:Date.now()})}}