RTCRtpTransceiver-stopping.https.html (9679B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <script src="/resources/testharness.js"></script> 4 <script src="/resources/testharnessreport.js"></script> 5 <script> 6 'use strict'; 7 8 ['audio', 'video'].forEach((kind) => { 9 promise_test(async t => { 10 const pc1 = new RTCPeerConnection(); 11 t.add_cleanup(() => pc1.close()); 12 const pc2 = new RTCPeerConnection(); 13 t.add_cleanup(() => pc2.close()); 14 15 const transceiver = pc1.addTransceiver(kind); 16 17 // Complete O/A exchange such that the transceiver gets associated. 18 await pc1.setLocalDescription(); 19 await pc2.setRemoteDescription(pc1.localDescription); 20 await pc2.setLocalDescription(); 21 await pc1.setRemoteDescription(pc2.localDescription); 22 assert_not_equals(transceiver.mid, null, 'mid before stop()'); 23 assert_not_equals(transceiver.direction, 'stopped', 24 'direction before stop()'); 25 assert_not_equals(transceiver.currentDirection, 'stopped', 26 'currentDirection before stop()'); 27 28 // Stop makes it stopping, but not stopped. 29 transceiver.stop(); 30 assert_not_equals(transceiver.mid, null, 'mid after stop()'); 31 assert_equals(transceiver.direction, 'stopped', 'direction after stop()'); 32 assert_not_equals(transceiver.currentDirection, 'stopped', 33 'currentDirection after stop()'); 34 35 // Negotiating makes it stopped. 36 await pc1.setLocalDescription(); 37 await pc2.setRemoteDescription(pc1.localDescription); 38 await pc2.setLocalDescription(); 39 await pc1.setRemoteDescription(pc2.localDescription); 40 assert_equals(transceiver.mid, null, 'mid after negotiation'); 41 assert_equals(transceiver.direction, 'stopped', 42 'direction after negotiation'); 43 assert_equals(transceiver.currentDirection, 'stopped', 44 'currentDirection after negotiation'); 45 }, `[${kind}] Locally stopped transceiver goes from stopping to stopped`); 46 47 promise_test(async t => { 48 const pc = new RTCPeerConnection(); 49 t.add_cleanup(() => pc.close()); 50 51 const transceiver = pc.addTransceiver(kind); 52 const trackEnded = new Promise(r => transceiver.receiver.track.onended = r); 53 assert_equals(transceiver.receiver.track.readyState, 'live'); 54 transceiver.stop(); 55 // Stopping triggers ending the track, but this happens asynchronously. 56 assert_equals(transceiver.receiver.track.readyState, 'live'); 57 await trackEnded; 58 assert_equals(transceiver.receiver.track.readyState, 'ended'); 59 }, `[${kind}] Locally stopping a transceiver ends the track`); 60 61 promise_test(async t => { 62 const pc1 = new RTCPeerConnection(); 63 t.add_cleanup(() => pc1.close()); 64 const pc2 = new RTCPeerConnection(); 65 t.add_cleanup(() => pc2.close()); 66 67 const pc1Transceiver = pc1.addTransceiver(kind); 68 await pc1.setLocalDescription(); 69 await pc2.setRemoteDescription(pc1.localDescription); 70 await pc2.setLocalDescription(); 71 await pc1.setRemoteDescription(pc2.localDescription); 72 const [pc2Transceiver] = pc2.getTransceivers(); 73 74 pc1Transceiver.stop(); 75 76 await pc1.setLocalDescription(); 77 assert_equals(pc2Transceiver.receiver.track.readyState, 'live'); 78 // Applying the remote offer immediately ends the track, we don't need to 79 // create or apply an answer. 80 await pc2.setRemoteDescription(pc1.localDescription); 81 // sRD just resolved, so we're in the success task for sRD. The transition 82 // from live -> ended is queued right now. 83 assert_equals(pc2Transceiver.receiver.track.readyState, 'live'); 84 await new Promise(r => pc2Transceiver.receiver.track.onended = r); 85 assert_equals(pc2Transceiver.receiver.track.readyState, 'ended'); 86 }, `[${kind}] Remotely stopping a transceiver ends the track`); 87 88 promise_test(async t => { 89 const pc1 = new RTCPeerConnection(); 90 t.add_cleanup(() => pc1.close()); 91 const pc2 = new RTCPeerConnection(); 92 t.add_cleanup(() => pc2.close()); 93 94 const pc1Transceiver = pc1.addTransceiver(kind); 95 96 // Complete O/A exchange such that the transceiver gets associated. 97 await pc1.setLocalDescription(); 98 await pc2.setRemoteDescription(pc1.localDescription); 99 await pc2.setLocalDescription(); 100 await pc1.setRemoteDescription(pc2.localDescription); 101 const [pc2Transceiver] = pc2.getTransceivers(); 102 assert_not_equals(pc2Transceiver.mid, null, 'mid before stop()'); 103 assert_not_equals(pc2Transceiver.direction, 'stopped', 104 'direction before stop()'); 105 assert_not_equals(pc2Transceiver.currentDirection, 'stopped', 106 'currentDirection before stop()'); 107 108 // Make the remote transceiver stopped. 109 pc1Transceiver.stop(); 110 111 // Negotiating makes it stopped. 112 assert_equals(pc2.getTransceivers().length, 1); 113 await pc1.setLocalDescription(); 114 await pc2.setRemoteDescription(pc1.localDescription); 115 // As soon as the remote offer is set, the transceiver is stopped but it is 116 // not disassociated or removed until setting the local answer. 117 assert_equals(pc2.getTransceivers().length, 1); 118 assert_not_equals(pc2Transceiver.mid, null, 'mid during negotiation'); 119 assert_equals(pc2Transceiver.direction, 'stopped', 120 'direction during negotiation'); 121 assert_equals(pc2Transceiver.currentDirection, 'stopped', 122 'currentDirection during negotiation'); 123 await pc2.setLocalDescription(); 124 assert_equals(pc2.getTransceivers().length, 0); 125 assert_equals(pc2Transceiver.mid, null, 'mid after negotiation'); 126 assert_equals(pc2Transceiver.direction, 'stopped', 127 'direction after negotiation'); 128 assert_equals(pc2Transceiver.currentDirection, 'stopped', 129 'currentDirection after negotiation'); 130 }, `[${kind}] Remotely stopped transceiver goes directly to stopped`); 131 132 promise_test(async t => { 133 const pc = new RTCPeerConnection(); 134 t.add_cleanup(() => pc.close()); 135 136 const transceiver = pc.addTransceiver(kind); 137 138 // Rollback does not end the track, because the transceiver is not removed. 139 await pc.setLocalDescription(); 140 await pc.setLocalDescription({type:'rollback'}); 141 assert_equals(transceiver.receiver.track.readyState, 'live'); 142 }, `[${kind}] Rollback when transceiver is not removed does not end track`); 143 144 promise_test(async t => { 145 const pc1 = new RTCPeerConnection(); 146 t.add_cleanup(() => pc1.close()); 147 const pc2 = new RTCPeerConnection(); 148 t.add_cleanup(() => pc2.close()); 149 150 const pc1Transceiver = pc1.addTransceiver(kind); 151 152 // Start negotiation, causing a transceiver to be created. 153 await pc1.setLocalDescription(); 154 await pc2.setRemoteDescription(pc1.localDescription); 155 const [pc2Transceiver] = pc2.getTransceivers(); 156 157 // Rollback such that the transceiver is removed. 158 await pc2.setRemoteDescription({type:'rollback'}); 159 assert_equals(pc2.getTransceivers().length, 0); 160 // sRD just resolved, so we're in the success task for sRD. The transition 161 // from live -> ended is queued right now. 162 assert_equals(pc2Transceiver.receiver.track.readyState, 'live'); 163 await new Promise(r => pc2Transceiver.receiver.track.onended = r); 164 assert_equals(pc2Transceiver.receiver.track.readyState, 'ended'); 165 }, `[${kind}] Rollback when removing transceiver does end the track`); 166 167 // Same test as above but looking at direction and currentDirection. 168 promise_test(async t => { 169 const pc1 = new RTCPeerConnection(); 170 t.add_cleanup(() => pc1.close()); 171 const pc2 = new RTCPeerConnection(); 172 t.add_cleanup(() => pc2.close()); 173 174 const pc1Transceiver = pc1.addTransceiver(kind); 175 176 // Start negotiation, causing a transceiver to be created. 177 await pc1.setLocalDescription(); 178 await pc2.setRemoteDescription(pc1.localDescription); 179 const [pc2Transceiver] = pc2.getTransceivers(); 180 181 // Rollback such that the transceiver is removed. 182 await pc2.setRemoteDescription({type:'rollback'}); 183 assert_equals(pc2.getTransceivers().length, 0); 184 // The removed transceiver is stopped. 185 assert_equals(pc2Transceiver.currentDirection, 'stopped', 186 'currentDirection indicate stopped'); 187 // A stopped transceiver is necessarily also stopping. 188 assert_equals(pc2Transceiver.direction, 'stopped', 189 'direction indicate stopping'); 190 // A stopped transceiver has no mid. 191 assert_equals(pc2Transceiver.mid, null, 'not associated'); 192 }, `[${kind}] Rollback when removing transceiver makes it stopped`); 193 194 promise_test(async t => { 195 const pc1 = new RTCPeerConnection(); 196 t.add_cleanup(() => pc1.close()); 197 const pc2 = new RTCPeerConnection(); 198 t.add_cleanup(() => pc2.close()); 199 200 const constraints = {}; 201 constraints[kind] = true; 202 const stream = await navigator.mediaDevices.getUserMedia(constraints); 203 const [track] = stream.getTracks(); 204 205 pc1.addTrack(track); 206 pc2.addTrack(track); 207 const transceiver = pc2.getTransceivers()[0]; 208 209 const ontrackEvent = new Promise(r => { 210 pc2.ontrack = e => r(e.track); 211 }); 212 213 // Simulate glare: both peer connections set local offers. 214 await pc1.setLocalDescription(); 215 await pc2.setLocalDescription(); 216 // Set remote offer, which implicitly rolls back the local offer. Because 217 // `transceiver` is an addTrack-transceiver, it should get repurposed. 218 await pc2.setRemoteDescription(pc1.localDescription); 219 assert_equals(transceiver.receiver.track.readyState, 'live'); 220 // Sanity check: the track should still be live when ontrack fires. 221 assert_equals((await ontrackEvent).readyState, 'live'); 222 }, `[${kind}] Glare when transceiver is not removed does not end track`); 223 }); 224 </script>