@@ -7,14 +7,22 @@ import {
77} from '../types' ;
88import BaseIssueDetector from './BaseIssueDetector' ;
99
10+ interface DeadVideoTrackDetectorParams {
11+ timeoutMs ?: number ;
12+ framesDroppedThreshold ?: number ;
13+ }
14+
1015class DeadVideoTrackDetector extends BaseIssueDetector {
11- #lastMarkedAt: number | undefined ;
16+ readonly #lastMarkedAt = new Map < string , number > ( ) ;
17+
18+ readonly #timeoutMs: number ;
1219
13- #timeoutMs : number ;
20+ readonly #framesDroppedThreshold : number ;
1421
15- constructor ( params : { timeoutMs ?: number } = { } ) {
22+ constructor ( params : DeadVideoTrackDetectorParams = { } ) {
1623 super ( ) ;
1724 this . #timeoutMs = params . timeoutMs ?? 10_000 ;
25+ this . #framesDroppedThreshold = params . framesDroppedThreshold ?? 0.5 ;
1826 }
1927
2028 performDetection ( data : WebRTCStatsParsed ) : IssueDetectorResult {
@@ -42,60 +50,85 @@ class DeadVideoTrackDetector extends BaseIssueDetector {
4250
4351 const newInboundByTrackId = mapByTrackId ( newInbound ) ;
4452 const prevInboundByTrackId = mapByTrackId ( prevInbound ) ;
53+ const unvisitedTrackIds = new Set ( this . #lastMarkedAt. keys ( ) ) ;
4554
4655 Array . from ( newInboundByTrackId . entries ( ) ) . forEach ( ( [ trackId , newInboundItem ] ) => {
56+ unvisitedTrackIds . delete ( trackId ) ;
57+
4758 const prevInboundItem = prevInboundByTrackId . get ( trackId ) ;
4859 if ( ! prevInboundItem ) {
4960 return ;
5061 }
5162
52- if (
53- newInboundItem . packetsReceived > prevInboundItem . packetsReceived
54- ) {
55- if ( newInboundItem . framesDecoded > prevInboundItem . framesDecoded ) {
56- this . removeMarkIssue ( ) ;
57- } else {
58- const hasIssue = this . markIssue ( ) ;
59-
60- if ( hasIssue ) {
61- const statsSample = {
62- packetsReceived : newInboundItem . packetsReceived ,
63- framesDecoded : newInboundItem . framesDecoded ,
64- deltaFramesDecoded : newInboundItem . framesDecoded - prevInboundItem . framesDecoded ,
65- deltaPacketsReceived : newInboundItem . packetsReceived - prevInboundItem . packetsReceived ,
66- } ;
67-
68- issues . push ( {
69- statsSample,
70- type : IssueType . Stream ,
71- reason : IssueReason . DeadVideoTrack ,
72- iceCandidate : trackId ,
73- } ) ;
74- }
75- }
63+ const deltaFramesReceived = newInboundItem . framesReceived - prevInboundItem . framesReceived ;
64+ const deltaFramesDropped = newInboundItem . framesDropped - prevInboundItem . framesDropped ;
65+ const deltaFramesDecoded = newInboundItem . framesDecoded - prevInboundItem . framesDecoded ;
66+ const ratioFramesDropped = deltaFramesDropped / deltaFramesReceived ;
67+
68+ if ( deltaFramesReceived === 0 ) {
69+ return ;
70+ }
71+
72+ if ( ratioFramesDropped >= this . #framesDroppedThreshold) {
73+ return ;
74+ }
75+
76+ // It seems that track is alive and we can remove mark if it was marked
77+ if ( deltaFramesDecoded > 0 ) {
78+ this . removeMarkIssue ( trackId ) ;
79+ return ;
80+ }
81+
82+ const hasIssue = this . markIssue ( trackId ) ;
83+
84+ if ( ! hasIssue ) {
85+ return ;
7686 }
87+
88+ const statsSample = {
89+ framesReceived : newInboundItem . framesReceived ,
90+ framesDropped : newInboundItem . framesDropped ,
91+ framesDecoded : newInboundItem . framesDecoded ,
92+ deltaFramesReceived,
93+ deltaFramesDropped,
94+ deltaFramesDecoded,
95+ } ;
96+
97+ issues . push ( {
98+ statsSample,
99+ type : IssueType . Stream ,
100+ reason : IssueReason . DeadVideoTrack ,
101+ iceCandidate : trackId ,
102+ } ) ;
103+ } ) ;
104+
105+ // just clear unvisited tracks from memory
106+ unvisitedTrackIds . forEach ( ( trackId ) => {
107+ this . removeMarkIssue ( trackId ) ;
77108 } ) ;
78109
79110 return issues ;
80111 }
81112
82- private markIssue ( ) : boolean {
113+ private markIssue ( trackId : string ) : boolean {
83114 const now = Date . now ( ) ;
84115
85- if ( ! this . #lastMarkedAt) {
86- this . #lastMarkedAt = now ;
116+ const lastMarkedAt = this . #lastMarkedAt. get ( trackId ) ;
117+
118+ if ( ! lastMarkedAt ) {
119+ this . #lastMarkedAt. set ( trackId , now ) ;
87120 return false ;
88121 }
89122
90- if ( now - this . # lastMarkedAt < this . #timeoutMs) {
123+ if ( now - lastMarkedAt < this . #timeoutMs) {
91124 return false ;
92125 }
93126
94127 return true ;
95128 }
96129
97- private removeMarkIssue ( ) : void {
98- this . #lastMarkedAt = undefined ;
130+ private removeMarkIssue ( trackId : string ) : void {
131+ this . #lastMarkedAt. delete ( trackId ) ;
99132 }
100133}
101134
0 commit comments