@@ -24,31 +24,31 @@ type RTCStatsType =
2424 | "track"
2525 | "transport" ;
2626
27+ // https://developer.mozilla.org/en-US/docs/Web/API/RTCInboundRtpStreamStats
2728interface RTCReceiverStats extends RTCStats {
2829 type : "inbound-rtp" | "rtp-receiver" | "remote-outbound-rtp" ;
2930 kind ?: "audio" | "video" ;
3031 bytesReceived ?: number ;
3132 packetsReceived ?: number ;
3233 packetsLost ?: number ;
3334 jitter ?: number ;
34- framesDecoded ?: number ;
35- framesDropped ?: number ;
36- framesPerSecond ?: number ;
3735 totalSamplesReceived ?: number ;
36+
37+ // unsupported in Safari
38+ // framesDecoded?: number;
39+ // framesDropped?: number;
40+ // framesPerSecond?: number;
3841}
3942
4043interface RTCIceCandidatePairStats extends RTCStats {
4144 type : "candidate-pair" ;
4245 state ?:
43- | "new"
44- | "checking"
45- | "connected"
46- | "completed"
4746 | "failed"
48- | "disconnected" ;
49- roundTripTime ?: number ;
50- localCandidateId ?: string ;
51- remoteCandidateId ?: string ;
47+ | "frozen"
48+ | "in-progress"
49+ | "succeeded"
50+ | "waiting" ;
51+ currentRoundTripTime ?: number ;
5252}
5353
5454interface RTCPeerConnectionStats extends RTCStats {
@@ -64,23 +64,33 @@ interface RTCPeerConnectionStats extends RTCStats {
6464 iceGatheringState ?: "new" | "gathering" | "complete" ;
6565}
6666
67- export function calculateQualityScore ( stats : RTCStatsReport ) : number {
67+ export interface QualityStats {
68+ qualityScore ?: bigint ;
69+ // rtt in microseconds
70+ rttUs ?: bigint ;
71+ }
72+
73+ export function calculateQualityScore (
74+ stats : RTCStatsReport ,
75+ ) : QualityStats | null {
6876 let audioReceiverStats : RTCReceiverStats | undefined ;
6977 let videoReceiverStats : RTCReceiverStats | undefined ;
7078 let activeCandidatePairStats : RTCIceCandidatePairStats | undefined ;
7179 let peerConnectionStats : RTCPeerConnectionStats | undefined ;
7280
7381 for ( const stat of stats . values ( ) ) {
7482 if ( stat . type === "inbound-rtp" || stat . type === "rtp-receiver" ) {
83+ // https://developer.mozilla.org/en-US/docs/Web/API/RTCInboundRtpStreamStats
7584 const receiverStat = stat as RTCReceiverStats ;
7685 if ( receiverStat . kind === "audio" ) {
7786 audioReceiverStats = receiverStat ;
7887 } else {
7988 videoReceiverStats = receiverStat ;
8089 }
8190 } else if ( stat . type === "candidate-pair" ) {
91+ // https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats
8292 const candidatePairStat = stat as RTCIceCandidatePairStats ;
83- if ( candidatePairStat . state === "connected " ) {
93+ if ( candidatePairStat . state === "succeeded " ) {
8494 activeCandidatePairStats = candidatePairStat ;
8595 }
8696 } else if ( stat . type === "peer-connection" ) {
@@ -92,9 +102,11 @@ export function calculateQualityScore(stats: RTCStatsReport): number {
92102 peerConnectionStats && peerConnectionStats . iceConnectionState &&
93103 ! [ "connected" , "completed" ] . includes ( peerConnectionStats . iceConnectionState )
94104 ) {
95- return 15 ; // Low score for connection issues
105+ return null ;
96106 }
97107
108+ const quality : QualityStats = { } ;
109+
98110 let audioQuality = 100 ;
99111 if ( audioReceiverStats ) {
100112 const packetsLost = audioReceiverStats . packetsLost ?? 0 ;
@@ -128,31 +140,24 @@ export function calculateQualityScore(stats: RTCStatsReport): number {
128140 const videoJitterScore = videoJitter < 0.02
129141 ? 100
130142 : ( videoJitter < 0.1 ? 70 : 30 ) ;
131- const framesDropped = videoReceiverStats . framesDropped ?? 0 ;
132- const framesDecoded = videoReceiverStats . framesDecoded ?? 0 ;
133- const frameDropRatio = framesDecoded > 0
134- ? framesDropped / framesDecoded
135- : 0 ;
136- const videoFrameDropScore = Math . max ( 0 , 100 - ( frameDropRatio * 100 ) ) ;
137- const videoFps = videoReceiverStats . framesPerSecond ;
138- const videoFpsScore = videoFps !== undefined
139- ? Math . min ( 100 , ( videoFps / 15 ) * ( 100 / 1 ) )
140- : 100 ;
141- videoQuality =
142- ( videoPacketLossScore + videoJitterScore + videoFrameDropScore +
143- videoFpsScore ) / 4 ;
143+ videoQuality = ( videoPacketLossScore + videoJitterScore ) / 2 ;
144144 }
145145
146146 let latencyScore = 80 ;
147147 if ( activeCandidatePairStats ) {
148- const rtt = activeCandidatePairStats . roundTripTime ;
148+ const rtt = activeCandidatePairStats . currentRoundTripTime ;
149149 if ( rtt !== undefined ) {
150+ // second to microseconds
151+ quality . rttUs = BigInt ( rtt * 1e6 ) ;
150152 latencyScore = rtt < 0.1 ? 100 : ( rtt < 0.3 ? 80 : ( rtt < 0.5 ? 60 : 40 ) ) ;
151153 }
152154 }
153155
154156 const overallQuality = Math . round (
155157 ( videoQuality * 0.5 ) + ( audioQuality * 0.3 ) + ( latencyScore * 0.2 ) ,
156158 ) ;
157- return Math . max ( 0 , Math . min ( 100 , overallQuality ) ) ;
159+ const qualityScore = Math . max ( 0 , Math . min ( 100 , overallQuality ) ) ;
160+ quality . qualityScore = BigInt ( qualityScore ) ;
161+
162+ return quality ;
158163}
0 commit comments