RTCPeerConnection-setRemoteDescription-tracks.https.html (17255B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <meta name="timeout" content="long"> 4 <title>RTCPeerConnection.prototype.setRemoteDescription - add/remove remote tracks</title> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="RTCPeerConnection-helper.js"></script> 8 <script> 9 'use strict'; 10 11 // The following helper functions are called from RTCPeerConnection-helper.js: 12 // addEventListenerPromise 13 // exchangeOffer 14 // exchangeOfferAnswer 15 // Resolver 16 17 // These tests are concerned with the observable consequences of processing 18 // the addition or removal of remote tracks, including events firing and the 19 // states of RTCPeerConnection, MediaStream and MediaStreamTrack. 20 21 promise_test(async t => { 22 const caller = new RTCPeerConnection(); 23 t.add_cleanup(() => caller.close()); 24 const callee = new RTCPeerConnection(); 25 t.add_cleanup(() => callee.close()); 26 const localStream = 27 await getNoiseStream({audio: true}); 28 t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop())); 29 caller.addTrack(localStream.getTracks()[0]); 30 const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => { 31 assert_equals(e.streams.length, 0, 'No remote stream created.'); 32 }); 33 await exchangeOffer(caller, callee); 34 await ontrackPromise; 35 }, 'addTrack() with a track and no stream makes ontrack fire with a track and no stream.'); 36 37 promise_test(async t => { 38 const caller = new RTCPeerConnection(); 39 t.add_cleanup(() => caller.close()); 40 const callee = new RTCPeerConnection(); 41 t.add_cleanup(() => callee.close()); 42 const localStream = 43 await getNoiseStream({audio: true}); 44 t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop())); 45 caller.addTrack(localStream.getTracks()[0], localStream); 46 const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => { 47 assert_equals(e.streams.length, 1, 'Created a single remote stream.'); 48 assert_equals(e.streams[0].id, localStream.id, 49 'Local and remote stream IDs match.'); 50 assert_array_equals(e.streams[0].getTracks(), [e.track], 51 'The remote stream contains the remote track.'); 52 }); 53 await exchangeOffer(caller, callee); 54 await ontrackPromise; 55 }, 'addTrack() with a track and a stream makes ontrack fire with a track and a stream.'); 56 57 promise_test(async t => { 58 const caller = new RTCPeerConnection(); 59 t.add_cleanup(() => caller.close()); 60 const callee = new RTCPeerConnection(); 61 t.add_cleanup(() => callee.close()); 62 let eventSequence = ''; 63 const localStream = 64 await getNoiseStream({audio: true}); 65 t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop())); 66 caller.addTrack(localStream.getTracks()[0], localStream); 67 const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => { 68 eventSequence += 'ontrack;'; 69 }); 70 await exchangeOffer(caller, callee); 71 eventSequence += 'setRemoteDescription;'; 72 await ontrackPromise; 73 assert_equals(eventSequence, 'ontrack;setRemoteDescription;'); 74 }, 'ontrack fires before setRemoteDescription resolves.'); 75 76 promise_test(async t => { 77 const caller = new RTCPeerConnection(); 78 t.add_cleanup(() => caller.close()); 79 const callee = new RTCPeerConnection(); 80 t.add_cleanup(() => callee.close()); 81 const localStreams = await Promise.all([ 82 getNoiseStream({audio: true}), 83 getNoiseStream({audio: true}), 84 ]); 85 t.add_cleanup(() => localStreams.forEach((stream) => 86 stream.getTracks().forEach((track) => track.stop()))); 87 caller.addTrack(localStreams[0].getTracks()[0], localStreams[0]); 88 caller.addTrack(localStreams[1].getTracks()[0], localStreams[0]); 89 let ontrackEventsFired = 0; 90 const ontrackEventResolvers = [ new Resolver(), new Resolver() ]; 91 callee.ontrack = t.step_func(e => { 92 ontrackEventResolvers[ontrackEventsFired++].resolve(e); 93 }); 94 await exchangeOffer(caller, callee); 95 let firstTrackEvent = await ontrackEventResolvers[0]; 96 assert_equals(firstTrackEvent.streams.length, 1, 97 'First ontrack fires with a single stream.'); 98 assert_equals(firstTrackEvent.streams[0].id, 99 localStreams[0].id, 100 'First ontrack\'s stream ID matches local stream.'); 101 let secondTrackEvent = await ontrackEventResolvers[1]; 102 assert_equals(secondTrackEvent.streams.length, 1, 103 'Second ontrack fires with a single stream.'); 104 assert_equals(secondTrackEvent.streams[0].id, 105 localStreams[0].id, 106 'Second ontrack\'s stream ID matches local stream.'); 107 assert_array_equals(firstTrackEvent.streams, secondTrackEvent.streams, 108 'ontrack was fired with the same streams both times.'); 109 110 assert_equals(firstTrackEvent.streams[0].getTracks().length, 2, "stream should have two tracks"); 111 assert_true(firstTrackEvent.streams[0].getTracks().includes(firstTrackEvent.track), "remoteStream should have the first track"); 112 assert_true(firstTrackEvent.streams[0].getTracks().includes(secondTrackEvent.track), "remoteStream should have the second track"); 113 assert_equals(ontrackEventsFired, 2, 'Unexpected number of track events.'); 114 115 assert_equals(ontrackEventsFired, 2, 'Unexpected number of track events.'); 116 }, 'addTrack() with two tracks and one stream makes ontrack fire twice with the tracks and shared stream.'); 117 118 promise_test(async t => { 119 const caller = new RTCPeerConnection(); 120 t.add_cleanup(() => caller.close()); 121 const callee = new RTCPeerConnection(); 122 t.add_cleanup(() => callee.close()); 123 let eventSequence = ''; 124 const localStreams = await Promise.all([ 125 getNoiseStream({audio: true}), 126 getNoiseStream({audio: true}), 127 ]); 128 t.add_cleanup(() => localStreams.forEach((stream) => 129 stream.getTracks().forEach((track) => track.stop()))); 130 caller.addTrack(localStreams[0].getTracks()[0], localStreams[0]); 131 const remoteStreams = []; 132 callee.ontrack = e => { 133 if (!remoteStreams.includes(e.streams[0])) 134 remoteStreams.push(e.streams[0]); 135 }; 136 exchangeIceCandidates(caller, callee); 137 await exchangeOfferAnswer(caller, callee); 138 assert_equals(remoteStreams.length, 1, 'One remote stream created.'); 139 assert_equals(remoteStreams[0].id, localStreams[0].id, 140 'First local and remote streams have the same ID.'); 141 const firstRemoteTrack = remoteStreams[0].getTracks()[0]; 142 const onaddtrackPromise = addEventListenerPromise(t, remoteStreams[0], 'addtrack'); 143 caller.addTrack(localStreams[1].getTracks()[0], localStreams[0]); 144 await exchangeOffer(caller, callee); 145 const e = await onaddtrackPromise; 146 assert_equals(remoteStreams[0].getTracks().length, 2, 'stream has two tracks'); 147 assert_not_equals(e.track.id, firstRemoteTrack.id, 148 'addtrack event has a new track'); 149 assert_equals(remoteStreams.length, 1, 'Still a single remote stream.'); 150 }, 'addTrack() for an existing stream makes stream.onaddtrack fire.'); 151 152 promise_test(async t => { 153 const caller = new RTCPeerConnection(); 154 t.add_cleanup(() => caller.close()); 155 const callee = new RTCPeerConnection(); 156 t.add_cleanup(() => callee.close()); 157 let eventSequence = ''; 158 const localStreams = await Promise.all([ 159 getNoiseStream({audio: true}), 160 getNoiseStream({audio: true}), 161 ]); 162 t.add_cleanup(() => localStreams.forEach((stream) => 163 stream.getTracks().forEach((track) => track.stop()))); 164 caller.addTrack(localStreams[0].getTracks()[0], localStreams[0]); 165 const remoteStreams = []; 166 callee.ontrack = e => { 167 if (!remoteStreams.includes(e.streams[0])) 168 remoteStreams.push(e.streams[0]); 169 }; 170 exchangeIceCandidates(caller, callee); 171 await exchangeOfferAnswer(caller, callee); 172 assert_equals(remoteStreams.length, 1, 'One remote stream created.'); 173 const onaddtrackPromise = 174 addEventListenerPromise(t, remoteStreams[0], 'addtrack', e => { 175 eventSequence += 'stream.onaddtrack;'; 176 }); 177 caller.addTrack(localStreams[1].getTracks()[0], localStreams[0]); 178 await exchangeOffer(caller, callee); 179 eventSequence += 'setRemoteDescription;'; 180 await onaddtrackPromise; 181 assert_equals(remoteStreams.length, 1, 'Still a single remote stream.'); 182 assert_equals(eventSequence, 'stream.onaddtrack;setRemoteDescription;'); 183 }, 'stream.onaddtrack fires before setRemoteDescription resolves.'); 184 185 promise_test(async t => { 186 const caller = new RTCPeerConnection(); 187 t.add_cleanup(() => caller.close()); 188 const callee = new RTCPeerConnection(); 189 t.add_cleanup(() => callee.close()); 190 const localStreams = await Promise.all([ 191 getNoiseStream({audio: true}), 192 getNoiseStream({audio: true}), 193 ]); 194 t.add_cleanup(() => localStreams.forEach((stream) => 195 stream.getTracks().forEach((track) => track.stop()))); 196 caller.addTrack(localStreams[0].getTracks()[0], 197 localStreams[0], localStreams[1]); 198 const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => { 199 assert_equals(e.streams.length, 2, 'Two remote stream created.'); 200 assert_array_equals(e.streams[0].getTracks(), [e.track], 201 'First remote stream == [remote track].'); 202 assert_array_equals(e.streams[1].getTracks(), [e.track], 203 'Second remote stream == [remote track].'); 204 assert_equals(e.streams[0].id, localStreams[0].id, 205 'First local and remote stream IDs match.'); 206 assert_equals(e.streams[1].id, localStreams[1].id, 207 'Second local and remote stream IDs match.'); 208 }); 209 await exchangeOffer(caller, callee); 210 await ontrackPromise; 211 }, 'addTrack() with a track and two streams makes ontrack fire with a track and two streams.'); 212 213 promise_test(async t => { 214 const caller = new RTCPeerConnection(); 215 t.add_cleanup(() => caller.close()); 216 const callee = new RTCPeerConnection(); 217 t.add_cleanup(() => callee.close()); 218 const localStream = 219 await getNoiseStream({audio: true}); 220 t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop())); 221 caller.addTrack(localStream.getTracks()[0], localStream); 222 const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => { 223 assert_array_equals(callee.getReceivers(), [e.receiver], 224 'getReceivers() == [e.receiver].'); 225 }); 226 await exchangeOffer(caller, callee); 227 await ontrackPromise; 228 }, 'ontrack\'s receiver matches getReceivers().'); 229 230 promise_test(async t => { 231 const caller = new RTCPeerConnection(); 232 t.add_cleanup(() => caller.close()); 233 const callee = new RTCPeerConnection(); 234 t.add_cleanup(() => callee.close()); 235 const localStream = 236 await getNoiseStream({audio: true}); 237 t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop())); 238 const sender = caller.addTrack(localStream.getTracks()[0], localStream); 239 const ontrackPromise = addEventListenerPromise(t, callee, 'track'); 240 exchangeIceCandidates(caller, callee); 241 await exchangeOfferAnswer(caller, callee); 242 await ontrackPromise; 243 assert_equals(callee.getReceivers().length, 1, 'One receiver created.'); 244 caller.removeTrack(sender); 245 await exchangeOffer(caller, callee); 246 assert_equals(callee.getReceivers().length, 1, 'Receiver not removed.'); 247 }, 'removeTrack() does not remove the receiver.'); 248 249 promise_test(async t => { 250 const caller = new RTCPeerConnection(); 251 t.add_cleanup(() => caller.close()); 252 const callee = new RTCPeerConnection(); 253 t.add_cleanup(() => callee.close()); 254 const localStream = 255 await getNoiseStream({audio: true}); 256 t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop())); 257 const [track] = localStream.getTracks(); 258 const sender = caller.addTrack(track, localStream); 259 const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => { 260 assert_equals(e.streams.length, 1); 261 return e.streams[0]; 262 }); 263 exchangeIceCandidates(caller, callee); 264 await exchangeOfferAnswer(caller, callee); 265 const remoteStream = await ontrackPromise; 266 const remoteTrack = remoteStream.getTracks()[0]; 267 const onremovetrackPromise = 268 addEventListenerPromise(t, remoteStream, 'removetrack', e => { 269 assert_equals(e.track, remoteTrack); 270 assert_equals(remoteStream.getTracks().length, 0, 271 'Remote stream emptied of tracks.'); 272 }); 273 caller.removeTrack(sender); 274 await exchangeOffer(caller, callee); 275 await onremovetrackPromise; 276 }, 'removeTrack() makes stream.onremovetrack fire and the track to be removed from the stream.'); 277 278 promise_test(async t => { 279 const caller = new RTCPeerConnection(); 280 t.add_cleanup(() => caller.close()); 281 const callee = new RTCPeerConnection(); 282 t.add_cleanup(() => callee.close()); 283 let eventSequence = ''; 284 const localStream = 285 await getNoiseStream({audio: true}); 286 t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop())); 287 const sender = caller.addTrack(localStream.getTracks()[0], localStream); 288 const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => { 289 assert_equals(e.streams.length, 1); 290 return e.streams[0]; 291 }); 292 exchangeIceCandidates(caller, callee); 293 await exchangeOfferAnswer(caller, callee); 294 const remoteStream = await ontrackPromise; 295 const remoteTrack = remoteStream.getTracks()[0]; 296 const onremovetrackPromise = 297 addEventListenerPromise(t, remoteStream, 'removetrack', e => { 298 eventSequence += 'stream.onremovetrack;'; 299 }); 300 caller.removeTrack(sender); 301 await exchangeOffer(caller, callee); 302 eventSequence += 'setRemoteDescription;'; 303 await onremovetrackPromise; 304 assert_equals(eventSequence, 'stream.onremovetrack;setRemoteDescription;'); 305 }, 'stream.onremovetrack fires before setRemoteDescription resolves.'); 306 307 promise_test(async t => { 308 const caller = new RTCPeerConnection(); 309 t.add_cleanup(() => caller.close()); 310 const callee = new RTCPeerConnection(); 311 t.add_cleanup(() => callee.close()); 312 const localStream = 313 await getNoiseStream({audio: true}); 314 t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop())); 315 const sender = caller.addTrack(localStream.getTracks()[0], localStream); 316 exchangeIceCandidates(caller, callee); 317 const e = await exchangeOfferAndListenToOntrack(t, caller, callee); 318 const remoteTrack = e.track; 319 320 // Need to wait for unmute, otherwise there's no event for the transition 321 // back to muted. 322 const onunmutePromise = 323 addEventListenerPromise(t, remoteTrack, 'unmute', () => { 324 assert_false(remoteTrack.muted); 325 }); 326 await exchangeAnswer(caller, callee); 327 await onunmutePromise; 328 329 const onmutePromise = 330 addEventListenerPromise(t, remoteTrack, 'mute', () => { 331 assert_true(remoteTrack.muted); 332 }); 333 caller.removeTrack(sender); 334 await exchangeOffer(caller, callee); 335 await onmutePromise; 336 }, 'removeTrack() makes track.onmute fire and the track to be muted.'); 337 338 promise_test(async t => { 339 const caller = new RTCPeerConnection(); 340 t.add_cleanup(() => caller.close()); 341 const callee = new RTCPeerConnection(); 342 t.add_cleanup(() => callee.close()); 343 let eventSequence = ''; 344 const localStream = 345 await getNoiseStream({audio: true}); 346 t.add_cleanup(() => localStream.getTracks().forEach(track => track.stop())); 347 const sender = caller.addTrack(localStream.getTracks()[0], localStream); 348 const ontrackPromise = addEventListenerPromise(t, callee, 'track', e => { 349 assert_equals(e.streams.length, 1); 350 return e.streams[0]; 351 }); 352 exchangeIceCandidates(caller, callee); 353 const e = await exchangeOfferAndListenToOntrack(t, caller, callee); 354 const remoteTrack = e.track; 355 356 // Need to wait for unmute, otherwise there's no event for the transition 357 // back to muted. 358 const onunmutePromise = 359 addEventListenerPromise(t, remoteTrack, 'unmute', () => { 360 assert_false(remoteTrack.muted); 361 }); 362 await exchangeAnswer(caller, callee); 363 await onunmutePromise; 364 365 const onmutePromise = 366 addEventListenerPromise(t, remoteTrack, 'mute', () => { 367 eventSequence += 'track.onmute;'; 368 }); 369 caller.removeTrack(sender); 370 await exchangeOffer(caller, callee); 371 eventSequence += 'setRemoteDescription;'; 372 await onmutePromise; 373 assert_equals(eventSequence, 'track.onmute;setRemoteDescription;'); 374 }, 'track.onmute fires before setRemoteDescription resolves.'); 375 376 promise_test(async t => { 377 const pc = new RTCPeerConnection(); 378 t.add_cleanup(() => pc.close()); 379 const stream = await getNoiseStream({audio: true}); 380 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); 381 const sender = pc.addTrack(stream.getTracks()[0]); 382 pc.removeTrack(sender); 383 pc.removeTrack(sender); 384 }, 'removeTrack() twice is safe.'); 385 </script>