rtp-stats-lifetime.https.html (9819B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <meta name="timeout" content="long"> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <script src="RTCPeerConnection-helper.js"></script> 7 <script src="third_party/sdp/sdp.js"></script> 8 <script src="simulcast/simulcast.js"></script> 9 <script> 10 'use strict'; 11 12 async function hasStats(pc, type) { 13 const report = await pc.getStats(); 14 for (const stats of report.values()) { 15 if (stats.type == type) { 16 return true; 17 } 18 } 19 return false; 20 } 21 22 async function getStatsTypePollUntilItExists(pc, type, kTimeoutMs = 10000) { 23 const t0 = performance.now(); 24 while (performance.now() - t0 < kTimeoutMs) { 25 const report = await pc.getStats(); 26 for (const stats of report.values()) { 27 if (stats.type == type) { 28 return stats; 29 } 30 } 31 } 32 return null; 33 } 34 35 promise_test(async t => { 36 const pc = new RTCPeerConnection(); 37 t.add_cleanup(() => pc.close()); 38 39 pc.addTransceiver('video'); 40 assert_false(await hasStats(pc, 'outbound-rtp'), 41 'outbound-rtp does not exist after addTransceiver'); 42 await pc.setLocalDescription(); 43 assert_false(await hasStats(pc, 'outbound-rtp'), 44 'outbound-rtp does not exist in have-local-offer'); 45 }, `RTCOutboundRtpStreamStats does not exist as early as have-local-offer`); 46 47 // This test does not exchange ICE candidates, meaning no packets are sent. 48 // We should still see outbound-rtp stats because they are created by the O/A. 49 promise_test(async t => { 50 const pc1 = new RTCPeerConnection(); 51 t.add_cleanup(() => pc1.close()); 52 const pc2 = new RTCPeerConnection(); 53 t.add_cleanup(() => pc2.close()); 54 55 // Offer to send. See previous test for assertions that the outbound-rtp is 56 // not created this early, which this test does not care about. 57 pc1.addTransceiver('video'); 58 await pc1.setLocalDescription(); 59 60 // Answer to send. 61 await pc2.setRemoteDescription(pc1.localDescription); 62 const [transceiver] = pc2.getTransceivers(); 63 transceiver.direction = 'sendrecv'; 64 assert_false(await hasStats(pc2, 'outbound-rtp'), 65 'outbound-rtp does not exist in has-remote-offer'); 66 await pc2.setLocalDescription(); 67 assert_true(await hasStats(pc2, 'outbound-rtp'), 68 'outbound-rtp exists after answerer returns to stable'); 69 70 // Complete offerer negotiation. 71 await pc1.setRemoteDescription(pc2.localDescription); 72 assert_true(await hasStats(pc1, 'outbound-rtp'), 73 'outbound-rtp exists after offerer returns to stable'); 74 }, `RTCOutboundRtpStreamStats exists after returning to stable`); 75 76 promise_test(async t => { 77 const pc1 = new RTCPeerConnection(); 78 t.add_cleanup(() => pc1.close()); 79 const pc2 = new RTCPeerConnection(); 80 t.add_cleanup(() => pc2.close()); 81 82 pc1.addTransceiver('video', {sendEncodings: [{rid: 'foo'}, {rid: 'bar'}]}); 83 // O/A with tweaks to accept simulcast. 84 await doOfferToSendSimulcastAndAnswer(pc1, pc2, ['foo', 'bar']); 85 86 // Despite not sending anything (ICE not connected) both outbound-rtp stats 87 // objects should appear immediately. 88 const report = await pc1.getStats(); 89 const outboundRtpFoo = 90 report.values().find(s => s.type == 'outbound-rtp' && s.rid == 'foo'); 91 const outboundRtpBar = 92 report.values().find(s => s.type == 'outbound-rtp' && s.rid == 'bar'); 93 assert_not_equals(outboundRtpFoo, undefined); 94 assert_not_equals(outboundRtpFoo.ssrc, undefined); 95 assert_not_equals(outboundRtpBar, undefined); 96 assert_not_equals(outboundRtpBar.ssrc, undefined); 97 }, `RTCOutboundRtpStreamStats exists per simulcast encoding`); 98 99 promise_test(async t => { 100 const pc1 = new RTCPeerConnection(); 101 t.add_cleanup(() => pc1.close()); 102 const pc2 = new RTCPeerConnection(); 103 t.add_cleanup(() => pc2.close()); 104 pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate); 105 pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate); 106 107 // Negotaite to send, but don't send anything yet (track is null). 108 const {sender} = pc1.addTransceiver('video'); 109 await pc1.setLocalDescription(); 110 await pc2.setRemoteDescription(pc1.localDescription); 111 await pc2.setLocalDescription(); 112 await pc1.setRemoteDescription(pc2.localDescription); 113 assert_false(await hasStats(pc2, 'inbound-rtp'), 114 'inbound-rtp does not exist before packets are received'); 115 116 // Start sending. This results in inbound-rtp being created. 117 const stream = await getNoiseStream({video:true}); 118 const [track] = stream.getTracks(); 119 t.add_cleanup(() => track.stop()); 120 await sender.replaceTrack(track); 121 const inboundRtp = await getStatsTypePollUntilItExists(pc2, 'inbound-rtp'); 122 assert_not_equals( 123 inboundRtp, null, 124 'inbound-rtp should be created in response to the sender having a track'); 125 assert_greater_than( 126 inboundRtp.packetsReceived, 0, 127 'inbound-rtp must only exist after packets have been received'); 128 }, `RTCInboundRtpStreamStats are created by packet reception`); 129 130 [{name: "pc", statsSource: pc => pc, kind: "audio"}, 131 {name: "sender", statsSource: pc => pc.getSenders()[0], kind: "audio"}, 132 {name: "pc", statsSource: pc => pc, kind: "video"}, 133 {name: "sender", statsSource: pc => pc.getSenders()[0], kind: "video"} 134 ].forEach(({name, statsSource, kind}) => promise_test(async t => { 135 const pc1 = new RTCPeerConnection(); 136 t.add_cleanup(() => pc1.close()); 137 const pc2 = new RTCPeerConnection(); 138 t.add_cleanup(() => pc2.close()); 139 pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate); 140 pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate); 141 142 // Check statsSource for the outbound-rtp stats object. 143 const getOutboundRtpStats = async () => 144 await getStatsTypePollUntilItExists(statsSource(pc1), 'outbound-rtp'); 145 146 pc1.addTransceiver(kind); 147 await pc1.setLocalDescription(); 148 await pc2.setRemoteDescription(pc1.localDescription); 149 await pc2.setLocalDescription(); 150 await pc1.setRemoteDescription(pc2.localDescription); 151 152 { 153 const stats = await getOutboundRtpStats(); 154 assert_not_equals( 155 stats, null, 156 `outbound-rtp should be created in ${name}.getStats() in response to the pc.addTransceiver("${kind}")`); 157 } 158 }, `RTCOutboundRtpStreamStats exist in ${name}.getStats() after created with a null track through pc.addTransceiver("${kind}")`)); 159 160 [{name: "pc", statsSource: pc => pc}, 161 {name: "sender", statsSource: pc => pc.getSenders()[0]} 162 ].forEach(({name, statsSource}) => promise_test(async t => { 163 const pc1 = new RTCPeerConnection(); 164 t.add_cleanup(() => pc1.close()); 165 const pc2 = new RTCPeerConnection(); 166 t.add_cleanup(() => pc2.close()); 167 pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate); 168 pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate); 169 170 // Check statsSource for the outbound-rtp stats object. 171 const getOutboundRtpStats = async () => 172 await getStatsTypePollUntilItExists(statsSource(pc1), 'outbound-rtp'); 173 174 const stream = await getNoiseStream({video:true}); 175 176 const {sender} = pc1.addTransceiver(stream.getTracks()[0]); 177 await pc1.setLocalDescription(); 178 await pc2.setRemoteDescription(pc1.localDescription); 179 await pc2.setLocalDescription(); 180 await pc1.setRemoteDescription(pc2.localDescription); 181 182 // Wait for packets to be received 183 { 184 const stats = await getOutboundRtpStats(); 185 assert_not_equals( 186 stats, null, 187 `outbound-rtp should be created in ${name}.getStats() in response to the sender having a track`); 188 } 189 190 // Stop sending. This should not remove the outbound-rtp stats object. 191 await sender.replaceTrack(null); 192 // Check that the outbound-rtp stats object remains. 193 { 194 const stats = await getOutboundRtpStats(); 195 assert_not_equals( 196 stats, null, 197 `outbound-rtp should remain in ${name}.getStats() after sender.replaceTrack(null)`); 198 } 199 }, `RTCOutboundRtpStreamStats remain in ${name}.getStats() after sender.replaceTrack(null)`)); 200 201 promise_test(async t => { 202 const pc1 = new RTCPeerConnection(); 203 t.add_cleanup(() => pc1.close()); 204 const pc2 = new RTCPeerConnection(); 205 t.add_cleanup(() => pc2.close()); 206 pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate); 207 pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate); 208 209 // Negotiate a dummy m-section for DTLS to be established. 210 pc1.addTransceiver('video', {direction:'sendonly'}); 211 await pc1.setLocalDescription(); 212 await pc2.setRemoteDescription(pc1.localDescription); 213 await pc2.setLocalDescription(); 214 await pc1.setRemoteDescription(pc2.localDescription); 215 216 // Offer to receive. 217 pc1.addTransceiver('video', {direction:'recvonly'}); 218 await pc1.setLocalDescription(); 219 await pc2.setRemoteDescription(pc1.localDescription); 220 assert_false(await hasStats(pc1, 'inbound-rtp'), 221 'inbound-rtp does not exist before packets are received'); 222 // Answer to send. 223 const transceivers = pc2.getTransceivers(); 224 assert_equals(transceivers.length, 2); 225 const transceiver = transceivers[1]; 226 const stream = await getNoiseStream({video:true}); 227 const [track] = stream.getTracks(); 228 t.add_cleanup(() => track.stop()); 229 await transceiver.sender.replaceTrack(track); 230 transceiver.direction = 'sendonly'; 231 await pc2.setLocalDescription(); 232 233 // We never set the remote answer. 234 assert_equals(pc1.signalingState, 'have-local-offer'); 235 // But because of early media, we're still able to receive packets. 236 // - Whether or not we unmute the track in response to this is outside the 237 // scope of this test. 238 const inboundRtp = await getStatsTypePollUntilItExists(pc1, 'inbound-rtp'); 239 assert_not_equals( 240 inboundRtp, null, 241 'inbound-rtp should be created in the early media use case'); 242 assert_greater_than( 243 inboundRtp.packetsReceived, 0, 244 'inbound-rtp must only exist after packets have been received'); 245 }, `RTCInboundRtpStreamStats exists for early media`); 246 </script>