RollbackEvents.https.html (9454B)
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 // Make sure "ontrack" fires if a prevuously rolled back track is added back. 10 promise_test(async t => { 11 const constraints = {}; 12 constraints[kind] = true; 13 const stream = await navigator.mediaDevices.getUserMedia(constraints); 14 const [track] = stream.getTracks(); 15 t.add_cleanup(() => track.stop()); 16 17 const pc1 = new RTCPeerConnection(); 18 t.add_cleanup(() => pc1.close()); 19 const pc2 = new RTCPeerConnection(); 20 t.add_cleanup(() => pc2.close()); 21 22 pc1.addTrack(track, stream); 23 pc2.addTrack(track, stream); 24 const [pc1Transceiver] = pc1.getTransceivers(); 25 const [pc2Transceiver] = pc2.getTransceivers(); 26 27 let remoteStreamViaOnTrackPromise = getRemoteStreamViaOnTrackPromise(pc2); 28 29 // Apply remote offer, but don't complete the entire exchange. 30 await pc1.setLocalDescription(); 31 await pc2.setRemoteDescription(pc1.localDescription); 32 // The addTrack-transceiver gets associated, no need for a second 33 // transceiver. 34 assert_equals(pc2.getTransceivers().length, 1); 35 const remoteStream = await remoteStreamViaOnTrackPromise; 36 assert_equals(remoteStream.id, stream.id); 37 38 const onRemoveTrackPromise = new Promise(r => { 39 remoteStream.onremovetrack = () => { r(); }; 40 }); 41 42 // Cause track removal due to rollback. 43 await pc2.setRemoteDescription({type:'rollback'}); 44 // The track was removed. 45 await onRemoveTrackPromise; 46 47 // Sanity check that ontrack still fires if we add it back again by applying 48 // the same remote offer. 49 remoteStreamViaOnTrackPromise = getRemoteStreamViaOnTrackPromise(pc2); 50 await pc2.setRemoteDescription(pc1.localDescription); 51 const revivedRemoteStream = await remoteStreamViaOnTrackPromise; 52 // This test only expects IDs to be the same. The same stream object should 53 // also be used, but this should be covered by separate tests. 54 // TODO(https://crbug.com/1321738): Add MediaStream identity tests. 55 assert_equals(remoteStream.id, revivedRemoteStream.id); 56 // No cheating, the same transciever should be used as before. 57 assert_equals(pc2.getTransceivers().length, 1); 58 }, `[${kind}] Track with stream: removal due to disassociation in rollback and then add it back again`); 59 60 // This is the same test as above, but this time without any remote streams. 61 // This test could fail if [[FiredDirection]] was not reset in a rollback but 62 // the above version of the test might still pass due to the track being 63 // re-added to its stream. 64 promise_test(async t => { 65 const constraints = {}; 66 constraints[kind] = true; 67 const stream = await navigator.mediaDevices.getUserMedia(constraints); 68 const [track] = stream.getTracks(); 69 t.add_cleanup(() => track.stop()); 70 71 const pc1 = new RTCPeerConnection(); 72 t.add_cleanup(() => pc1.close()); 73 const pc2 = new RTCPeerConnection(); 74 t.add_cleanup(() => pc2.close()); 75 76 pc1.addTrack(track); 77 pc2.addTrack(track); 78 const [pc1Transceiver] = pc1.getTransceivers(); 79 const [pc2Transceiver] = pc2.getTransceivers(); 80 81 let remoteTrackPromise = getTrackViaOnTrackPromise(pc2); 82 83 // Apply remote offer, but don't complete the entire exchange. 84 await pc1.setLocalDescription(); 85 await pc2.setRemoteDescription(pc1.localDescription); 86 // The addTrack-transceiver gets associated, no need for a second 87 // transceiver. 88 assert_equals(pc2.getTransceivers().length, 1); 89 const remoteTrack = await remoteTrackPromise; 90 assert_not_equals(remoteTrack, null); 91 92 // Cause track removal due to rollback. 93 await pc2.setRemoteDescription({type:'rollback'}); 94 // There's nothing equivalent to stream.onremovetrack when you don't have a 95 // stream, but the track should become muted (if it isn't already). 96 if (!remoteTrack.muted) { 97 await new Promise(r => remoteTrack.onmute = () => { r(); }); 98 } 99 assert_equals(remoteTrack.muted, true); 100 101 // Sanity check that ontrack still fires if we add it back again by applying 102 // the same remote offer. 103 remoteTrackPromise = getTrackViaOnTrackPromise(pc2); 104 await pc2.setRemoteDescription(pc1.localDescription); 105 const revivedRemoteTrack = await remoteTrackPromise; 106 // We can be sure the same track is used, because the same transceiver is 107 // used (and transciever.receiver.track has same lifetime as transceiver). 108 assert_equals(pc2.getTransceivers().length, 1); 109 assert_equals(remoteTrack, revivedRemoteTrack); 110 }, `[${kind}] Track without stream: removal due to disassociation in rollback and then add it back`); 111 112 // Make sure "ontrack" can fire in a rollback (undo making it inactive). 113 promise_test(async t => { 114 const constraints = {}; 115 constraints[kind] = true; 116 const stream = await navigator.mediaDevices.getUserMedia(constraints); 117 const [track] = stream.getTracks(); 118 t.add_cleanup(() => track.stop()); 119 120 const pc1 = new RTCPeerConnection(); 121 t.add_cleanup(() => pc1.close()); 122 const pc2 = new RTCPeerConnection(); 123 t.add_cleanup(() => pc2.close()); 124 125 pc1.addTrack(track, stream); 126 const [pc1Transceiver] = pc1.getTransceivers(); 127 128 let remoteStreamViaOnTrackPromise = getRemoteStreamViaOnTrackPromise(pc2); 129 130 // Complete O/A exchange such that the transceiver gets associated. 131 await pc1.setLocalDescription(); 132 await pc2.setRemoteDescription(pc1.localDescription); 133 await pc2.setLocalDescription(); 134 await pc1.setRemoteDescription(pc2.localDescription); 135 const [pc2Transceiver] = pc2.getTransceivers(); 136 assert_equals(pc2Transceiver.direction, 'recvonly'); 137 assert_equals(pc2Transceiver.currentDirection, 'recvonly'); 138 139 const remoteStream = await remoteStreamViaOnTrackPromise; 140 assert_equals(remoteStream.id, stream.id); 141 const onRemoveTrackPromise = new Promise(r => { 142 remoteStream.onremovetrack = () => { r(); }; 143 }); 144 145 // Cause track removal. 146 pc1Transceiver.direction = 'inactive'; 147 await pc1.setLocalDescription(); 148 await pc2.setRemoteDescription(pc1.localDescription); 149 // The track was removed. 150 await onRemoveTrackPromise; 151 152 // Rolling back the offer revives the track, causing ontrack to fire again. 153 remoteStreamViaOnTrackPromise = getRemoteStreamViaOnTrackPromise(pc2); 154 await pc2.setRemoteDescription({type:'rollback'}); 155 const revivedRemoteStream = await remoteStreamViaOnTrackPromise; 156 // This test only expects IDs to be the same. The same stream object should 157 // also be used, but this should be covered by separate tests. 158 // TODO(https://crbug.com/1321738): Add MediaStream identity tests. 159 assert_equals(remoteStream.id, revivedRemoteStream.id); 160 }, `[${kind}] Track with stream: removal due to direction changing and then add back using rollback`); 161 162 // Same test as above but without remote streams. 163 promise_test(async t => { 164 const constraints = {}; 165 constraints[kind] = true; 166 const stream = await navigator.mediaDevices.getUserMedia(constraints); 167 const [track] = stream.getTracks(); 168 t.add_cleanup(() => track.stop()); 169 170 const pc1 = new RTCPeerConnection(); 171 t.add_cleanup(() => pc1.close()); 172 const pc2 = new RTCPeerConnection(); 173 t.add_cleanup(() => pc2.close()); 174 175 pc1.addTrack(track); 176 const [pc1Transceiver] = pc1.getTransceivers(); 177 178 let remoteTrackPromise = getTrackViaOnTrackPromise(pc2); 179 180 // Complete O/A exchange such that the transceiver gets associated. 181 await pc1.setLocalDescription(); 182 await pc2.setRemoteDescription(pc1.localDescription); 183 await pc2.setLocalDescription(); 184 await pc1.setRemoteDescription(pc2.localDescription); 185 const [pc2Transceiver] = pc2.getTransceivers(); 186 assert_equals(pc2Transceiver.direction, 'recvonly'); 187 assert_equals(pc2Transceiver.currentDirection, 'recvonly'); 188 189 const remoteTrack = await remoteTrackPromise; 190 191 // Cause track removal. 192 pc1Transceiver.direction = 'inactive'; 193 await pc1.setLocalDescription(); 194 await pc2.setRemoteDescription(pc1.localDescription); 195 // There's nothing equivalent to stream.onremovetrack when you don't have a 196 // stream, but the track should become muted (if it isn't already). 197 if (!remoteTrack.muted) { 198 await new Promise(r => remoteTrack.onmute = () => { r(); }); 199 } 200 assert_equals(remoteTrack.muted, true); 201 202 // Rolling back the offer revives the track, causing ontrack to fire again. 203 remoteTrackPromise = getTrackViaOnTrackPromise(pc2); 204 await pc2.setRemoteDescription({type:'rollback'}); 205 const revivedRemoteTrack = await remoteTrackPromise; 206 // We can be sure the same track is used, because the same transceiver is 207 // used (and transciever.receiver.track has same lifetime as transceiver). 208 assert_equals(pc2.getTransceivers().length, 1); 209 assert_equals(remoteTrack, revivedRemoteTrack); 210 }, `[${kind}] Track without stream: removal due to direction changing and then add back using rollback`); 211 }); 212 213 function getTrackViaOnTrackPromise(pc) { 214 return new Promise(r => { 215 pc.ontrack = e => { 216 pc.ontrack = null; 217 r(e.track); 218 }; 219 }); 220 } 221 222 function getRemoteStreamViaOnTrackPromise(pc) { 223 return new Promise(r => { 224 pc.ontrack = e => { 225 pc.ontrack = null; 226 r(e.streams[0]); 227 }; 228 }); 229 } 230 231 </script>