RTCRtpTransceiver.https.html (72866B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <meta name="timeout" content="long"> 4 <title>RTCRtpTransceiver</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 const checkThrows = async (func, exceptionName, description) => { 12 try { 13 await func(); 14 assert_true(false, description + " throws " + exceptionName); 15 } catch (e) { 16 assert_equals(e.name, exceptionName, description + " throws " + exceptionName); 17 } 18 }; 19 20 const stopTracks = (...streams) => { 21 streams.forEach(stream => stream.getTracks().forEach(track => track.stop())); 22 }; 23 24 const collectEvents = (target, name, check) => { 25 const events = []; 26 const handler = e => { 27 check(e); 28 events.push(e); 29 }; 30 31 target.addEventListener(name, handler); 32 33 const finishCollecting = () => { 34 target.removeEventListener(name, handler); 35 return events; 36 }; 37 38 return {finish: finishCollecting}; 39 }; 40 41 const collectAddTrackEvents = stream => { 42 const checkEvent = e => { 43 assert_true(e.track instanceof MediaStreamTrack, "Track is set on event"); 44 assert_true(stream.getTracks().includes(e.track), 45 "track in addtrack event is in the stream"); 46 }; 47 return collectEvents(stream, "addtrack", checkEvent); 48 }; 49 50 const collectRemoveTrackEvents = stream => { 51 const checkEvent = e => { 52 assert_true(e.track instanceof MediaStreamTrack, "Track is set on event"); 53 assert_true(!stream.getTracks().includes(e.track), 54 "track in removetrack event is not in the stream"); 55 }; 56 return collectEvents(stream, "removetrack", checkEvent); 57 }; 58 59 const collectTrackEvents = pc => { 60 const checkEvent = e => { 61 assert_true(e.track instanceof MediaStreamTrack, "Track is set on event"); 62 assert_true(e.receiver instanceof RTCRtpReceiver, "Receiver is set on event"); 63 assert_true(e.transceiver instanceof RTCRtpTransceiver, "Transceiver is set on event"); 64 assert_true(Array.isArray(e.streams), "Streams is set on event"); 65 e.streams.forEach(stream => { 66 assert_true(stream.getTracks().includes(e.track), 67 "Each stream in event contains the track"); 68 }); 69 assert_equals(e.receiver, e.transceiver.receiver, 70 "Receiver belongs to transceiver"); 71 assert_equals(e.track, e.receiver.track, 72 "Track belongs to receiver"); 73 }; 74 75 return collectEvents(pc, "track", checkEvent); 76 }; 77 78 const setRemoteDescriptionReturnTrackEvents = async (pc, desc) => { 79 const trackEventCollector = collectTrackEvents(pc); 80 await pc.setRemoteDescription(desc); 81 return trackEventCollector.finish(); 82 }; 83 84 const offerAnswer = async (offerer, answerer) => { 85 const offer = await offerer.createOffer(); 86 await answerer.setRemoteDescription(offer); 87 await offerer.setLocalDescription(offer); 88 const answer = await answerer.createAnswer(); 89 await offerer.setRemoteDescription(answer); 90 await answerer.setLocalDescription(answer); 91 }; 92 93 const trickle = (t, pc1, pc2) => { 94 pc1.onicecandidate = t.step_func(async e => { 95 try { 96 await pc2.addIceCandidate(e.candidate); 97 } catch (e) { 98 assert_true(false, "addIceCandidate threw error: " + e.name); 99 } 100 }); 101 }; 102 103 const iceConnected = pc => { 104 return new Promise((resolve, reject) => { 105 const iceCheck = () => { 106 if (pc.iceConnectionState == "connected") { 107 assert_true(true, "ICE connected"); 108 resolve(); 109 } 110 111 if (pc.iceConnectionState == "failed") { 112 assert_true(false, "ICE failed"); 113 reject(); 114 } 115 }; 116 117 iceCheck(); 118 pc.oniceconnectionstatechange = iceCheck; 119 }); 120 }; 121 122 const negotiationNeeded = pc => { 123 return new Promise(resolve => pc.onnegotiationneeded = resolve); 124 }; 125 126 const countEvents = (target, name) => { 127 const result = {count: 0}; 128 target.addEventListener(name, e => result.count++); 129 return result; 130 }; 131 132 const gotMuteEvent = async track => { 133 await new Promise(r => track.addEventListener("mute", r, {once: true})); 134 135 assert_true(track.muted, "track should be muted after onmute"); 136 }; 137 138 const gotUnmuteEvent = async track => { 139 await new Promise(r => track.addEventListener("unmute", r, {once: true})); 140 141 assert_true(!track.muted, "track should not be muted after onunmute"); 142 }; 143 144 // comparable() - produces copy of object that is JSON comparable. 145 // o = original object (required) 146 // t = template of what to examine. Useful if o is non-enumerable (optional) 147 148 const comparable = (o, t = o) => { 149 if (typeof o != 'object' || !o) { 150 return o; 151 } 152 if (Array.isArray(t) && Array.isArray(o)) { 153 return o.map((n, i) => comparable(n, t[i])); 154 } 155 return Object.keys(t).sort() 156 .reduce((r, key) => (r[key] = comparable(o[key], t[key]), r), {}); 157 }; 158 159 const stripKeyQuotes = s => s.replace(/"(\w+)":/g, "$1:"); 160 161 const hasProps = (observed, expected) => { 162 const observable = comparable(observed, expected); 163 assert_equals(stripKeyQuotes(JSON.stringify(observable)), 164 stripKeyQuotes(JSON.stringify(comparable(expected)))); 165 }; 166 167 const hasPropsAndUniqueMids = (observed, expected) => { 168 hasProps(observed, expected); 169 170 const mids = []; 171 observed.forEach((transceiver, i) => { 172 if (!("mid" in expected[i])) { 173 assert_not_equals(transceiver.mid, null); 174 assert_equals(typeof transceiver.mid, "string"); 175 } 176 if (transceiver.mid) { 177 assert_false(mids.includes(transceiver.mid), "mid must be unique"); 178 mids.push(transceiver.mid); 179 } 180 }); 181 }; 182 183 const checkAddTransceiverNoTrack = async t => { 184 const pc = new RTCPeerConnection(); 185 t.add_cleanup(() => pc.close()); 186 187 hasProps(pc.getTransceivers(), []); 188 189 pc.addTransceiver("audio"); 190 pc.addTransceiver("video"); 191 192 hasProps(pc.getTransceivers(), 193 [ 194 { 195 receiver: {track: {kind: "audio", readyState: "live", muted: true}}, 196 sender: {track: null}, 197 direction: "sendrecv", 198 mid: null, 199 currentDirection: null, 200 }, 201 { 202 receiver: {track: {kind: "video", readyState: "live", muted: true}}, 203 sender: {track: null}, 204 direction: "sendrecv", 205 mid: null, 206 currentDirection: null, 207 } 208 ]); 209 }; 210 211 const checkAddTransceiverWithTrack = async t => { 212 const pc = new RTCPeerConnection(); 213 t.add_cleanup(() => pc.close()); 214 215 const stream = await getNoiseStream({audio: true, video: true}); 216 t.add_cleanup(() => stopTracks(stream)); 217 const audio = stream.getAudioTracks()[0]; 218 const video = stream.getVideoTracks()[0]; 219 220 pc.addTransceiver(audio); 221 pc.addTransceiver(video); 222 223 hasProps(pc.getTransceivers(), 224 [ 225 { 226 receiver: {track: {kind: "audio"}}, 227 sender: {track: audio}, 228 direction: "sendrecv", 229 mid: null, 230 currentDirection: null, 231 }, 232 { 233 receiver: {track: {kind: "video"}}, 234 sender: {track: video}, 235 direction: "sendrecv", 236 mid: null, 237 currentDirection: null, 238 } 239 ]); 240 }; 241 242 const checkAddTransceiverWithAddTrack = async t => { 243 const pc = new RTCPeerConnection(); 244 t.add_cleanup(() => pc.close()); 245 246 const stream = await getNoiseStream({audio: true, video: true}); 247 t.add_cleanup(() => stopTracks(stream)); 248 const audio = stream.getAudioTracks()[0]; 249 const video = stream.getVideoTracks()[0]; 250 251 pc.addTrack(audio, stream); 252 pc.addTrack(video, stream); 253 254 hasProps(pc.getTransceivers(), 255 [ 256 { 257 receiver: {track: {kind: "audio"}}, 258 sender: {track: audio}, 259 direction: "sendrecv", 260 mid: null, 261 currentDirection: null, 262 }, 263 { 264 receiver: {track: {kind: "video"}}, 265 sender: {track: video}, 266 direction: "sendrecv", 267 mid: null, 268 currentDirection: null, 269 } 270 ]); 271 }; 272 273 const checkAddTransceiverWithDirection = async t => { 274 const pc = new RTCPeerConnection(); 275 t.add_cleanup(() => pc.close()); 276 277 pc.addTransceiver("audio", {direction: "recvonly"}); 278 pc.addTransceiver("video", {direction: "recvonly"}); 279 280 hasProps(pc.getTransceivers(), 281 [ 282 { 283 receiver: {track: {kind: "audio"}}, 284 sender: {track: null}, 285 direction: "recvonly", 286 mid: null, 287 currentDirection: null, 288 }, 289 { 290 receiver: {track: {kind: "video"}}, 291 sender: {track: null}, 292 direction: "recvonly", 293 mid: null, 294 currentDirection: null, 295 } 296 ]); 297 }; 298 299 const checkAddTransceiverWithSetRemoteOfferSending = async t => { 300 const pc1 = new RTCPeerConnection(); 301 const pc2 = new RTCPeerConnection(); 302 t.add_cleanup(() => pc1.close()); 303 t.add_cleanup(() => pc2.close()); 304 305 const stream = await getNoiseStream({audio: true}); 306 t.add_cleanup(() => stopTracks(stream)); 307 const track = stream.getAudioTracks()[0]; 308 pc1.addTransceiver(track, {streams: [stream]}); 309 310 const offer = await pc1.createOffer(); 311 312 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 313 hasProps(trackEvents, 314 [ 315 { 316 track: pc2.getTransceivers()[0].receiver.track, 317 streams: [{id: stream.id}] 318 } 319 ]); 320 321 322 hasPropsAndUniqueMids(pc2.getTransceivers(), 323 [ 324 { 325 receiver: {track: {kind: "audio"}}, 326 sender: {track: null}, 327 direction: "recvonly", 328 currentDirection: null, 329 } 330 ]); 331 }; 332 333 const checkAddTransceiverWithSetRemoteOfferNoSend = async t => { 334 const pc1 = new RTCPeerConnection(); 335 const pc2 = new RTCPeerConnection(); 336 t.add_cleanup(() => pc1.close()); 337 t.add_cleanup(() => pc2.close()); 338 339 const stream = await getNoiseStream({audio: true}); 340 t.add_cleanup(() => stopTracks(stream)); 341 const track = stream.getAudioTracks()[0]; 342 pc1.addTransceiver(track); 343 pc1.getTransceivers()[0].direction = "recvonly"; 344 345 const offer = await pc1.createOffer(); 346 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 347 hasProps(trackEvents, []); 348 349 hasPropsAndUniqueMids(pc2.getTransceivers(), 350 [ 351 { 352 receiver: {track: {kind: "audio"}}, 353 sender: {track: null}, 354 // rtcweb-jsep says this is recvonly, w3c-webrtc does not... 355 direction: "recvonly", 356 currentDirection: null, 357 } 358 ]); 359 }; 360 361 const checkAddTransceiverBadKind = async t => { 362 const pc = new RTCPeerConnection(); 363 t.add_cleanup(() => pc.close()); 364 try { 365 pc.addTransceiver("foo"); 366 assert_true(false, 'addTransceiver("foo") throws'); 367 } 368 catch (e) { 369 if (e instanceof TypeError) { 370 assert_true(true, 'addTransceiver("foo") throws a TypeError'); 371 } else { 372 assert_true(false, 'addTransceiver("foo") throws a TypeError'); 373 } 374 } 375 376 hasProps(pc.getTransceivers(), []); 377 }; 378 379 const checkNoMidOffer = async t => { 380 const pc1 = new RTCPeerConnection(); 381 const pc2 = new RTCPeerConnection(); 382 t.add_cleanup(() => pc1.close()); 383 t.add_cleanup(() => pc2.close()); 384 385 const stream = await getNoiseStream({audio: true}); 386 t.add_cleanup(() => stopTracks(stream)); 387 const track = stream.getAudioTracks()[0]; 388 pc1.addTrack(track, stream); 389 390 const offer = await pc1.createOffer(); 391 await pc1.setLocalDescription(offer); 392 393 // Remove mid attr 394 offer.sdp = offer.sdp.replace("a=mid:", "a=unknownattr:"); 395 offer.sdp = offer.sdp.replace("a=group:", "a=unknownattr:"); 396 await pc2.setRemoteDescription(offer); 397 398 hasPropsAndUniqueMids(pc2.getTransceivers(), 399 [ 400 { 401 receiver: {track: {kind: "audio"}}, 402 sender: {track: null}, 403 direction: "recvonly", 404 currentDirection: null, 405 } 406 ]); 407 408 const answer = await pc2.createAnswer(); 409 await pc2.setLocalDescription(answer); 410 await pc1.setRemoteDescription(answer); 411 }; 412 413 const checkNoMidAnswer = async t => { 414 const pc1 = new RTCPeerConnection(); 415 const pc2 = new RTCPeerConnection(); 416 t.add_cleanup(() => pc1.close()); 417 t.add_cleanup(() => pc2.close()); 418 419 const stream = await getNoiseStream({audio: true}); 420 t.add_cleanup(() => stopTracks(stream)); 421 const track = stream.getAudioTracks()[0]; 422 pc1.addTrack(track, stream); 423 424 const offer = await pc1.createOffer(); 425 await pc1.setLocalDescription(offer); 426 await pc2.setRemoteDescription(offer); 427 428 hasPropsAndUniqueMids(pc1.getTransceivers(), 429 [ 430 { 431 receiver: {track: {kind: "audio"}}, 432 sender: {track: {kind: "audio"}}, 433 direction: "sendrecv", 434 currentDirection: null, 435 } 436 ]); 437 438 const lastMid = pc1.getTransceivers()[0].mid; 439 440 let answer = await pc2.createAnswer(); 441 // Remove mid attr 442 answer.sdp = answer.sdp.replace("a=mid:", "a=unknownattr:"); 443 // Remove group attr also 444 answer.sdp = answer.sdp.replace("a=group:", "a=unknownattr:"); 445 await pc1.setRemoteDescription(answer); 446 447 hasProps(pc1.getTransceivers(), 448 [ 449 { 450 receiver: {track: {kind: "audio"}}, 451 sender: {track: {kind: "audio"}}, 452 direction: "sendrecv", 453 currentDirection: "sendonly", 454 mid: lastMid 455 } 456 ]); 457 458 const reoffer = await pc1.createOffer(); 459 await pc1.setLocalDescription(reoffer); 460 hasProps(pc1.getTransceivers(), 461 [ 462 { 463 receiver: {track: {kind: "audio"}}, 464 sender: {track: {kind: "audio"}}, 465 direction: "sendrecv", 466 currentDirection: "sendonly", 467 mid: lastMid 468 } 469 ]); 470 }; 471 472 const checkAddTransceiverNoTrackDoesntPair = async t => { 473 const pc1 = new RTCPeerConnection(); 474 const pc2 = new RTCPeerConnection(); 475 t.add_cleanup(() => pc1.close()); 476 t.add_cleanup(() => pc2.close()); 477 478 pc1.addTransceiver("audio"); 479 pc2.addTransceiver("audio"); 480 481 const offer = await pc1.createOffer(); 482 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 483 hasProps(trackEvents, 484 [ 485 { 486 track: pc2.getTransceivers()[1].receiver.track, 487 streams: [] 488 } 489 ]); 490 491 hasPropsAndUniqueMids(pc2.getTransceivers(), 492 [ 493 {mid: null}, // no addTrack magic, doesn't auto-pair 494 {} // Created by SRD 495 ]); 496 }; 497 498 const checkAddTransceiverWithTrackDoesntPair = async t => { 499 const pc1 = new RTCPeerConnection(); 500 const pc2 = new RTCPeerConnection(); 501 t.add_cleanup(() => pc1.close()); 502 t.add_cleanup(() => pc2.close()); 503 pc1.addTransceiver("audio"); 504 505 const stream = await getNoiseStream({audio: true}); 506 t.add_cleanup(() => stopTracks(stream)); 507 const track = stream.getAudioTracks()[0]; 508 pc2.addTransceiver(track); 509 510 const offer = await pc1.createOffer(); 511 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 512 hasProps(trackEvents, 513 [ 514 { 515 track: pc2.getTransceivers()[1].receiver.track, 516 streams: [] 517 } 518 ]); 519 520 hasPropsAndUniqueMids(pc2.getTransceivers(), 521 [ 522 {mid: null, sender: {track}}, 523 {sender: {track: null}} // Created by SRD 524 ]); 525 }; 526 527 const checkAddTransceiverThenReplaceTrackDoesntPair = async t => { 528 const pc1 = new RTCPeerConnection(); 529 const pc2 = new RTCPeerConnection(); 530 t.add_cleanup(() => pc1.close()); 531 t.add_cleanup(() => pc2.close()); 532 pc1.addTransceiver("audio"); 533 pc2.addTransceiver("audio"); 534 535 const stream = await getNoiseStream({audio: true}); 536 t.add_cleanup(() => stopTracks(stream)); 537 const track = stream.getAudioTracks()[0]; 538 await pc2.getTransceivers()[0].sender.replaceTrack(track); 539 540 const offer = await pc1.createOffer(); 541 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 542 hasProps(trackEvents, 543 [ 544 { 545 track: pc2.getTransceivers()[1].receiver.track, 546 streams: [] 547 } 548 ]); 549 550 hasPropsAndUniqueMids(pc2.getTransceivers(), 551 [ 552 {mid: null, sender: {track}}, 553 {sender: {track: null}} // Created by SRD 554 ]); 555 }; 556 557 const checkAddTransceiverThenAddTrackPairs = async t => { 558 const pc1 = new RTCPeerConnection(); 559 const pc2 = new RTCPeerConnection(); 560 t.add_cleanup(() => pc1.close()); 561 t.add_cleanup(() => pc2.close()); 562 pc1.addTransceiver("audio"); 563 pc2.addTransceiver("audio"); 564 565 const stream = await getNoiseStream({audio: true}); 566 t.add_cleanup(() => stopTracks(stream)); 567 const track = stream.getAudioTracks()[0]; 568 pc2.addTrack(track, stream); 569 570 const offer = await pc1.createOffer(); 571 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 572 hasProps(trackEvents, 573 [ 574 { 575 track: pc2.getTransceivers()[0].receiver.track, 576 streams: [] 577 } 578 ]); 579 580 // addTransceiver-transceivers cannot attach to a remote offers, so a second 581 // transceiver is created and associated whilst the first transceiver 582 // remains unassociated. 583 assert_equals(pc2.getTransceivers()[0].mid, null); 584 assert_not_equals(pc2.getTransceivers()[1].mid, null); 585 }; 586 587 const checkAddTrackPairs = async t => { 588 const pc1 = new RTCPeerConnection(); 589 const pc2 = new RTCPeerConnection(); 590 t.add_cleanup(() => pc1.close()); 591 t.add_cleanup(() => pc2.close()); 592 pc1.addTransceiver("audio"); 593 594 const stream = await getNoiseStream({audio: true}); 595 t.add_cleanup(() => stopTracks(stream)); 596 const track = stream.getAudioTracks()[0]; 597 pc2.addTrack(track, stream); 598 599 const offer = await pc1.createOffer(); 600 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 601 hasProps(trackEvents, 602 [ 603 { 604 track: pc2.getTransceivers()[0].receiver.track, 605 streams: [] 606 } 607 ]); 608 609 hasPropsAndUniqueMids(pc2.getTransceivers(), 610 [ 611 {sender: {track}} 612 ]); 613 }; 614 615 const checkReplaceTrackNullDoesntPreventPairing = async t => { 616 const pc1 = new RTCPeerConnection(); 617 const pc2 = new RTCPeerConnection(); 618 t.add_cleanup(() => pc1.close()); 619 t.add_cleanup(() => pc2.close()); 620 pc1.addTransceiver("audio"); 621 622 const stream = await getNoiseStream({audio: true}); 623 t.add_cleanup(() => stopTracks(stream)); 624 const track = stream.getAudioTracks()[0]; 625 pc2.addTrack(track, stream); 626 await pc2.getTransceivers()[0].sender.replaceTrack(null); 627 628 const offer = await pc1.createOffer(); 629 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 630 hasProps(trackEvents, 631 [ 632 { 633 track: pc2.getTransceivers()[0].receiver.track, 634 streams: [] 635 } 636 ]); 637 638 hasPropsAndUniqueMids(pc2.getTransceivers(), 639 [ 640 {sender: {track: null}} 641 ]); 642 }; 643 644 const checkRemoveAndReadd = async t => { 645 const pc1 = new RTCPeerConnection(); 646 const pc2 = new RTCPeerConnection(); 647 t.add_cleanup(() => pc1.close()); 648 t.add_cleanup(() => pc2.close()); 649 const stream = await getNoiseStream({audio: true}); 650 t.add_cleanup(() => stopTracks(stream)); 651 const track = stream.getAudioTracks()[0]; 652 pc1.addTrack(track, stream); 653 654 await offerAnswer(pc1, pc2); 655 656 pc1.removeTrack(pc1.getSenders()[0]); 657 pc1.addTrack(track, stream); 658 659 hasProps(pc1.getTransceivers(), 660 [ 661 { 662 sender: {track: null}, 663 direction: "recvonly" 664 }, 665 { 666 sender: {track}, 667 direction: "sendrecv" 668 } 669 ]); 670 671 // pc1 is offerer 672 await offerAnswer(pc1, pc2); 673 674 hasProps(pc2.getTransceivers(), 675 [ 676 {currentDirection: "inactive"}, 677 {currentDirection: "recvonly"} 678 ]); 679 680 pc1.removeTrack(pc1.getSenders()[1]); 681 pc1.addTrack(track, stream); 682 683 hasProps(pc1.getTransceivers(), 684 [ 685 { 686 sender: {track: null}, 687 direction: "recvonly" 688 }, 689 { 690 sender: {track: null}, 691 direction: "recvonly" 692 }, 693 { 694 sender: {track}, 695 direction: "sendrecv" 696 } 697 ]); 698 699 // pc1 is answerer. We need to create a new transceiver so pc1 will have 700 // something to attach the re-added track to 701 pc2.addTransceiver("audio"); 702 703 await offerAnswer(pc2, pc1); 704 705 hasProps(pc2.getTransceivers(), 706 [ 707 {currentDirection: "inactive"}, 708 {currentDirection: "inactive"}, 709 {currentDirection: "sendrecv"} 710 ]); 711 }; 712 713 const checkAddTrackExistingTransceiverThenRemove = async t => { 714 const pc = new RTCPeerConnection(); 715 t.add_cleanup(() => pc.close()); 716 pc.addTransceiver("audio"); 717 const stream = await getNoiseStream({audio: true}); 718 const audio = stream.getAudioTracks()[0]; 719 let sender = pc.addTrack(audio, stream); 720 pc.removeTrack(sender); 721 722 // Cause transceiver to be associated 723 await pc.setLocalDescription(await pc.createOffer()); 724 725 // Make sure add/remove works still 726 sender = pc.addTrack(audio, stream); 727 pc.removeTrack(sender); 728 729 stopTracks(stream); 730 }; 731 732 const checkRemoveTrackNegotiation = async t => { 733 const pc1 = new RTCPeerConnection(); 734 const pc2 = new RTCPeerConnection(); 735 t.add_cleanup(() => pc1.close()); 736 t.add_cleanup(() => pc2.close()); 737 const stream = await getNoiseStream({audio: true, video: true}); 738 t.add_cleanup(() => stopTracks(stream)); 739 const audio = stream.getAudioTracks()[0]; 740 pc1.addTrack(audio, stream); 741 const video = stream.getVideoTracks()[0]; 742 pc1.addTrack(video, stream); 743 // We want both a sendrecv and sendonly transceiver to test that the 744 // appropriate direction changes happen. 745 pc1.getTransceivers()[1].direction = "sendonly"; 746 747 let offer = await pc1.createOffer(); 748 749 // Get a reference to the stream 750 let trackEventCollector = collectTrackEvents(pc2); 751 await pc2.setRemoteDescription(offer); 752 let pc2TrackEvents = trackEventCollector.finish(); 753 hasProps(pc2TrackEvents, 754 [ 755 {streams: [{id: stream.id}]}, 756 {streams: [{id: stream.id}]} 757 ]); 758 const receiveStream = pc2TrackEvents[0].streams[0]; 759 760 // Verify that rollback causes onremovetrack to fire for the added tracks 761 let removetrackEventCollector = collectRemoveTrackEvents(receiveStream); 762 await pc2.setRemoteDescription({type: "rollback"}); 763 let removedtracks = removetrackEventCollector.finish().map(e => e.track); 764 assert_equals(removedtracks.length, 2, 765 "Rollback should have removed two tracks"); 766 assert_true(removedtracks.includes(pc2TrackEvents[0].track), 767 "First track should be removed"); 768 assert_true(removedtracks.includes(pc2TrackEvents[1].track), 769 "Second track should be removed"); 770 771 offer = await pc1.createOffer(); 772 773 let addtrackEventCollector = collectAddTrackEvents(receiveStream); 774 trackEventCollector = collectTrackEvents(pc2); 775 await pc2.setRemoteDescription(offer); 776 pc2TrackEvents = trackEventCollector.finish(); 777 let addedtracks = addtrackEventCollector.finish().map(e => e.track); 778 assert_equals(addedtracks.length, 2, 779 "pc2.setRemoteDescription(offer) should've added 2 tracks to receive stream"); 780 assert_true(addedtracks.includes(pc2TrackEvents[0].track), 781 "First track should be added"); 782 assert_true(addedtracks.includes(pc2TrackEvents[1].track), 783 "Second track should be added"); 784 785 await pc1.setLocalDescription(offer); 786 let answer = await pc2.createAnswer(); 787 await pc1.setRemoteDescription(answer); 788 await pc2.setLocalDescription(answer); 789 pc1.removeTrack(pc1.getSenders()[0]); 790 791 hasProps(pc1.getSenders(), 792 [ 793 {track: null}, 794 {track: video} 795 ]); 796 797 hasProps(pc1.getTransceivers(), 798 [ 799 { 800 sender: {track: null}, 801 direction: "recvonly" 802 }, 803 { 804 sender: {track: video}, 805 direction: "sendonly" 806 } 807 ]); 808 809 await negotiationNeeded(pc1); 810 811 pc1.removeTrack(pc1.getSenders()[1]); 812 813 hasProps(pc1.getSenders(), 814 [ 815 {track: null}, 816 {track: null} 817 ]); 818 819 hasProps(pc1.getTransceivers(), 820 [ 821 { 822 sender: {track: null}, 823 direction: "recvonly" 824 }, 825 { 826 sender: {track: null}, 827 direction: "inactive" 828 } 829 ]); 830 831 // pc1 as offerer 832 offer = await pc1.createOffer(); 833 834 removetrackEventCollector = collectRemoveTrackEvents(receiveStream); 835 await pc2.setRemoteDescription(offer); 836 removedtracks = removetrackEventCollector.finish().map(e => e.track); 837 assert_equals(removedtracks.length, 2, "Should have two removed tracks"); 838 assert_true(removedtracks.includes(pc2TrackEvents[0].track), 839 "First track should be removed"); 840 assert_true(removedtracks.includes(pc2TrackEvents[1].track), 841 "Second track should be removed"); 842 843 addtrackEventCollector = collectAddTrackEvents(receiveStream); 844 await pc2.setRemoteDescription({type: "rollback"}); 845 addedtracks = addtrackEventCollector.finish().map(e => e.track); 846 assert_equals(addedtracks.length, 2, "Rollback should have added two tracks"); 847 848 // pc2 as offerer 849 offer = await pc2.createOffer(); 850 await pc2.setLocalDescription(offer); 851 await pc1.setRemoteDescription(offer); 852 answer = await pc1.createAnswer(); 853 await pc1.setLocalDescription(answer); 854 855 removetrackEventCollector = collectRemoveTrackEvents(receiveStream); 856 await pc2.setRemoteDescription(answer); 857 removedtracks = removetrackEventCollector.finish().map(e => e.track); 858 assert_equals(removedtracks.length, 2, "Should have two removed tracks"); 859 860 hasProps(pc2.getTransceivers(), 861 [ 862 { 863 currentDirection: "inactive" 864 }, 865 { 866 currentDirection: "inactive" 867 } 868 ]); 869 }; 870 871 const checkSetDirection = async t => { 872 const pc = new RTCPeerConnection(); 873 t.add_cleanup(() => pc.close()); 874 pc.addTransceiver("audio"); 875 876 pc.getTransceivers()[0].direction = "sendonly"; 877 hasProps(pc.getTransceivers(),[{direction: "sendonly"}]); 878 pc.getTransceivers()[0].direction = "recvonly"; 879 hasProps(pc.getTransceivers(),[{direction: "recvonly"}]); 880 pc.getTransceivers()[0].direction = "inactive"; 881 hasProps(pc.getTransceivers(),[{direction: "inactive"}]); 882 pc.getTransceivers()[0].direction = "sendrecv"; 883 hasProps(pc.getTransceivers(),[{direction: "sendrecv"}]); 884 }; 885 886 const checkCurrentDirection = async t => { 887 const pc1 = new RTCPeerConnection(); 888 const pc2 = new RTCPeerConnection(); 889 t.add_cleanup(() => pc1.close()); 890 t.add_cleanup(() => pc2.close()); 891 892 const stream = await getNoiseStream({audio: true}); 893 t.add_cleanup(() => stopTracks(stream)); 894 const track = stream.getAudioTracks()[0]; 895 pc1.addTrack(track, stream); 896 pc2.addTrack(track, stream); 897 hasProps(pc1.getTransceivers(), [{currentDirection: null}]); 898 899 let offer = await pc1.createOffer(); 900 hasProps(pc1.getTransceivers(), [{currentDirection: null}]); 901 902 await pc1.setLocalDescription(offer); 903 hasProps(pc1.getTransceivers(), [{currentDirection: null}]); 904 905 let trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 906 hasProps(trackEvents, 907 [ 908 { 909 track: pc2.getTransceivers()[0].receiver.track, 910 streams: [{id: stream.id}] 911 } 912 ]); 913 914 hasProps(pc2.getTransceivers(), [{currentDirection: null}]); 915 916 let answer = await pc2.createAnswer(); 917 hasProps(pc2.getTransceivers(), [{currentDirection: null}]); 918 919 await pc2.setLocalDescription(answer); 920 hasProps(pc2.getTransceivers(), [{currentDirection: "sendrecv"}]); 921 922 trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); 923 hasProps(trackEvents, 924 [ 925 { 926 track: pc1.getTransceivers()[0].receiver.track, 927 streams: [{id: stream.id}] 928 } 929 ]); 930 931 hasProps(pc1.getTransceivers(), [{currentDirection: "sendrecv"}]); 932 933 pc2.getTransceivers()[0].direction = "sendonly"; 934 935 offer = await pc2.createOffer(); 936 hasProps(pc2.getTransceivers(), [{currentDirection: "sendrecv"}]); 937 938 await pc2.setLocalDescription(offer); 939 hasProps(pc2.getTransceivers(), [{currentDirection: "sendrecv"}]); 940 941 trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, offer); 942 hasProps(trackEvents, []); 943 944 hasProps(pc1.getTransceivers(), [{currentDirection: "sendrecv"}]); 945 946 answer = await pc1.createAnswer(); 947 hasProps(pc1.getTransceivers(), [{currentDirection: "sendrecv"}]); 948 949 await pc1.setLocalDescription(answer); 950 hasProps(pc1.getTransceivers(), [{currentDirection: "recvonly"}]); 951 952 trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, answer); 953 hasProps(trackEvents, []); 954 955 hasProps(pc2.getTransceivers(), [{currentDirection: "sendonly"}]); 956 957 pc2.getTransceivers()[0].direction = "sendrecv"; 958 959 offer = await pc2.createOffer(); 960 hasProps(pc2.getTransceivers(), [{currentDirection: "sendonly"}]); 961 962 await pc2.setLocalDescription(offer); 963 hasProps(pc2.getTransceivers(), [{currentDirection: "sendonly"}]); 964 965 trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, offer); 966 hasProps(trackEvents, []); 967 968 hasProps(pc1.getTransceivers(), [{currentDirection: "recvonly"}]); 969 970 answer = await pc1.createAnswer(); 971 hasProps(pc1.getTransceivers(), [{currentDirection: "recvonly"}]); 972 973 await pc1.setLocalDescription(answer); 974 hasProps(pc1.getTransceivers(), [{currentDirection: "sendrecv"}]); 975 976 trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, answer); 977 hasProps(trackEvents, 978 [ 979 { 980 track: pc2.getTransceivers()[0].receiver.track, 981 streams: [{id: stream.id}] 982 } 983 ]); 984 985 hasProps(pc2.getTransceivers(), [{currentDirection: "sendrecv"}]); 986 987 pc2.close(); 988 hasProps(pc2.getTransceivers(), [{currentDirection: "stopped"}]); 989 }; 990 991 const checkSendrecvWithNoSendTrack = async t => { 992 const pc1 = new RTCPeerConnection(); 993 const pc2 = new RTCPeerConnection(); 994 t.add_cleanup(() => pc1.close()); 995 t.add_cleanup(() => pc2.close()); 996 997 const stream = await getNoiseStream({audio: true}); 998 t.add_cleanup(() => stopTracks(stream)); 999 const track = stream.getAudioTracks()[0]; 1000 pc1.addTransceiver("audio"); 1001 pc1.getTransceivers()[0].direction = "sendrecv"; 1002 pc2.addTrack(track, stream); 1003 1004 const offer = await pc1.createOffer(); 1005 1006 let trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 1007 hasProps(trackEvents, 1008 [ 1009 { 1010 track: pc2.getTransceivers()[0].receiver.track, 1011 streams: [] 1012 } 1013 ]); 1014 1015 trickle(t, pc1, pc2); 1016 await pc1.setLocalDescription(offer); 1017 1018 const answer = await pc2.createAnswer(); 1019 trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); 1020 hasProps(trackEvents, 1021 [ 1022 { 1023 track: pc1.getTransceivers()[0].receiver.track, 1024 streams: [{id: stream.id}] 1025 } 1026 ]); 1027 1028 trickle(t, pc2, pc1); 1029 await pc2.setLocalDescription(answer); 1030 1031 await iceConnected(pc1); 1032 await iceConnected(pc2); 1033 }; 1034 1035 const checkSendrecvWithTracklessStream = async t => { 1036 const pc1 = new RTCPeerConnection(); 1037 const pc2 = new RTCPeerConnection(); 1038 t.add_cleanup(() => pc1.close()); 1039 t.add_cleanup(() => pc2.close()); 1040 1041 const stream = new MediaStream(); 1042 pc1.addTransceiver("audio", {streams: [stream]}); 1043 1044 const offer = await pc1.createOffer(); 1045 1046 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 1047 hasProps(trackEvents, 1048 [ 1049 { 1050 track: pc2.getTransceivers()[0].receiver.track, 1051 streams: [{id: stream.id}] 1052 } 1053 ]); 1054 }; 1055 1056 const checkStop = async t => { 1057 const pc1 = new RTCPeerConnection(); 1058 t.add_cleanup(() => pc1.close()); 1059 const stream = await getNoiseStream({audio: true}); 1060 t.add_cleanup(() => stopTracks(stream)); 1061 const track = stream.getAudioTracks()[0]; 1062 pc1.addTrack(track, stream); 1063 1064 let offer = await pc1.createOffer(); 1065 await pc1.setLocalDescription(offer); 1066 1067 const pc2 = new RTCPeerConnection(); 1068 t.add_cleanup(() => pc2.close()); 1069 await pc2.setRemoteDescription(offer); 1070 1071 pc2.addTrack(track, stream); 1072 1073 const answer = await pc2.createAnswer(); 1074 await pc2.setLocalDescription(answer); 1075 await pc1.setRemoteDescription(answer); 1076 1077 let stoppedTransceiver = pc1.getTransceivers()[0]; 1078 let onended = new Promise(resolve => { 1079 stoppedTransceiver.receiver.track.onended = resolve; 1080 }); 1081 stoppedTransceiver.stop(); 1082 assert_equals(pc1.getReceivers().length, 1, 'getReceivers exposes a receiver of a stopped transceiver before negotiation'); 1083 assert_equals(pc1.getSenders().length, 1, 'getSenders exposes a sender of a stopped transceiver before negotiation'); 1084 await onended; 1085 // The transceiver has [[stopping]] = true, [[stopped]] = false 1086 hasPropsAndUniqueMids(pc1.getTransceivers(), 1087 [ 1088 { 1089 sender: {track: {kind: "audio"}}, 1090 receiver: {track: {kind: "audio", readyState: "ended"}}, 1091 currentDirection: "sendrecv", 1092 direction: "stopped" 1093 } 1094 ]); 1095 1096 const transceiver = pc1.getTransceivers()[0]; 1097 1098 checkThrows(() => transceiver.sender.setParameters( 1099 transceiver.sender.getParameters()), 1100 "InvalidStateError", "setParameters on stopped transceiver"); 1101 1102 const stream2 = await getNoiseStream({audio: true}); 1103 const track2 = stream.getAudioTracks()[0]; 1104 checkThrows(() => transceiver.sender.replaceTrack(track2), 1105 "InvalidStateError", "replaceTrack on stopped transceiver"); 1106 1107 checkThrows(() => transceiver.direction = "sendrecv", 1108 "InvalidStateError", "set direction on stopped transceiver"); 1109 1110 checkThrows(() => transceiver.sender.dtmf.insertDTMF("111"), 1111 "InvalidStateError", "insertDTMF on stopped transceiver"); 1112 1113 // Shouldn't throw 1114 stoppedTransceiver.stop(); 1115 1116 offer = await pc1.createOffer(); 1117 await pc1.setLocalDescription(offer); 1118 1119 const stoppedCalleeTransceiver = pc2.getTransceivers()[0]; 1120 onended = new Promise(resolve => { 1121 stoppedCalleeTransceiver.receiver.track.onended = resolve; 1122 }); 1123 1124 await pc2.setRemoteDescription(offer); 1125 1126 await onended; 1127 // pc2's transceiver was stopped remotely. 1128 // The track ends when setRemeoteDescription(offer) is set. 1129 hasProps(pc2.getTransceivers(), 1130 [ 1131 { 1132 sender: {track: {kind: "audio"}}, 1133 receiver: {track: {kind: "audio", readyState: "ended"}}, 1134 currentDirection: "stopped", 1135 direction: "stopped" 1136 } 1137 ]); 1138 // After setLocalDescription(answer), the transceiver has 1139 // [[stopping]] = true, [[stopped]] = true, and is removed from pc2. 1140 const stoppingAnswer = await pc2.createAnswer(); 1141 await pc2.setLocalDescription(stoppingAnswer); 1142 assert_equals(pc2.getTransceivers().length, 0); 1143 assert_equals(pc2.getReceivers().length, 0, 'getReceivers does not expose a receiver of a stopped transceiver after negotiation'); 1144 assert_equals(pc2.getSenders().length, 0, 'getSenders does not expose a sender of a stopped transceiver after negotiation'); 1145 1146 // Shouldn't throw either 1147 stoppedTransceiver.stop(); 1148 await pc1.setRemoteDescription(stoppingAnswer); 1149 assert_equals(pc1.getReceivers().length, 0, 'getReceivers does not expose a receiver of a stopped transceiver after negotiation'); 1150 assert_equals(pc1.getSenders().length, 0, 'getSenders does not expose a sender of a stopped transceiver after negotiation'); 1151 1152 pc1.close(); 1153 pc2.close(); 1154 1155 // Spec says the closed check comes before the stopped check, so this 1156 // should throw now. 1157 checkThrows(() => stoppedTransceiver.stop(), 1158 "InvalidStateError", "RTCRtpTransceiver.stop() with closed PC"); 1159 }; 1160 1161 const checkStopAfterCreateOffer = async t => { 1162 const pc1 = new RTCPeerConnection(); 1163 const pc2 = new RTCPeerConnection(); 1164 t.add_cleanup(() => pc1.close()); 1165 t.add_cleanup(() => pc2.close()); 1166 1167 const stream = await getNoiseStream({audio: true}); 1168 t.add_cleanup(() => stopTracks(stream)); 1169 const track = stream.getAudioTracks()[0]; 1170 pc1.addTrack(track, stream); 1171 pc2.addTrack(track, stream); 1172 1173 let offer = await pc1.createOffer(); 1174 1175 const transceiverThatWasStopped = pc1.getTransceivers()[0]; 1176 transceiverThatWasStopped.stop(); 1177 await pc2.setRemoteDescription(offer) 1178 trickle(t, pc1, pc2); 1179 await pc1.setLocalDescription(offer); 1180 1181 let answer = await pc2.createAnswer(); 1182 const negotiationNeededAwaiter = negotiationNeeded(pc1); 1183 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); 1184 // Spec language doesn't say anything about checking whether the transceiver 1185 // is stopped here. 1186 hasProps(trackEvents, 1187 [ 1188 { 1189 track: pc1.getTransceivers()[0].receiver.track, 1190 streams: [{id: stream.id}] 1191 } 1192 ]); 1193 1194 assert_equals(transceiverThatWasStopped, pc1.getTransceivers()[0]); 1195 // The transceiver should still be [[stopping]]=true, [[stopped]]=false. 1196 hasPropsAndUniqueMids(pc1.getTransceivers(), 1197 [ 1198 { 1199 currentDirection: "sendrecv", 1200 direction: "stopped" 1201 } 1202 ]); 1203 1204 await negotiationNeededAwaiter; 1205 1206 trickle(t, pc2, pc1); 1207 1208 await pc2.setLocalDescription(answer); 1209 1210 await iceConnected(pc1); 1211 await iceConnected(pc2); 1212 1213 offer = await pc1.createOffer(); 1214 await pc1.setLocalDescription(offer); 1215 await pc2.setRemoteDescription(offer); 1216 answer = await pc2.createAnswer(); 1217 await pc2.setLocalDescription(answer); 1218 await pc1.setRemoteDescription(answer); 1219 assert_equals(pc1.getTransceivers().length, 0); 1220 assert_equals(pc2.getTransceivers().length, 0); 1221 }; 1222 1223 const checkStopAfterSetLocalOffer = async t => { 1224 const pc1 = new RTCPeerConnection(); 1225 const pc2 = new RTCPeerConnection(); 1226 t.add_cleanup(() => pc1.close()); 1227 t.add_cleanup(() => pc2.close()); 1228 1229 const stream = await getNoiseStream({audio: true}); 1230 t.add_cleanup(() => stopTracks(stream)); 1231 const track = stream.getAudioTracks()[0]; 1232 pc1.addTrack(track, stream); 1233 pc2.addTrack(track, stream); 1234 1235 let offer = await pc1.createOffer(); 1236 1237 await pc2.setRemoteDescription(offer) 1238 trickle(t, pc1, pc2); 1239 await pc1.setLocalDescription(offer); 1240 1241 pc1.getTransceivers()[0].stop(); 1242 1243 let answer = await pc2.createAnswer(); 1244 const negotiationNeededAwaiter = negotiationNeeded(pc1); 1245 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); 1246 // Spec language doesn't say anything about checking whether the transceiver 1247 // is stopped here. 1248 hasProps(trackEvents, 1249 [ 1250 { 1251 track: pc1.getTransceivers()[0].receiver.track, 1252 streams: [{id: stream.id}] 1253 } 1254 ]); 1255 1256 hasPropsAndUniqueMids(pc1.getTransceivers(), 1257 [ 1258 { 1259 direction: "stopped", 1260 currentDirection: "sendrecv" 1261 } 1262 ]); 1263 await negotiationNeededAwaiter; 1264 1265 trickle(t, pc2, pc1); 1266 await pc2.setLocalDescription(answer); 1267 1268 await iceConnected(pc1); 1269 await iceConnected(pc2); 1270 1271 offer = await pc1.createOffer(); 1272 await pc1.setLocalDescription(offer); 1273 await pc2.setRemoteDescription(offer); 1274 answer = await pc2.createAnswer(); 1275 await pc2.setLocalDescription(answer); 1276 await pc1.setRemoteDescription(answer); 1277 1278 assert_equals(pc1.getTransceivers().length, 0); 1279 assert_equals(pc2.getTransceivers().length, 0); 1280 }; 1281 1282 const checkStopAfterSetRemoteOffer = async t => { 1283 const pc1 = new RTCPeerConnection(); 1284 const pc2 = new RTCPeerConnection(); 1285 t.add_cleanup(() => pc1.close()); 1286 t.add_cleanup(() => pc2.close()); 1287 1288 const stream = await getNoiseStream({audio: true}); 1289 t.add_cleanup(() => stopTracks(stream)); 1290 const track = stream.getAudioTracks()[0]; 1291 pc1.addTrack(track, stream); 1292 pc2.addTrack(track, stream); 1293 1294 const offer = await pc1.createOffer(); 1295 1296 await pc2.setRemoteDescription(offer) 1297 await pc1.setLocalDescription(offer); 1298 1299 // Stop on _answerer_ side now. Should not stop transceiver in answer, 1300 // but cause firing of negotiationNeeded at pc2, and disabling 1301 // of the transceiver with direction = inactive in answer. 1302 pc2.getTransceivers()[0].stop(); 1303 assert_equals(pc2.getTransceivers()[0].direction, 'stopped'); 1304 1305 const answer = await pc2.createAnswer(); 1306 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); 1307 hasProps(trackEvents, []); 1308 1309 hasProps(pc2.getTransceivers(), 1310 [ 1311 { 1312 direction: "stopped", 1313 currentDirection: null, 1314 } 1315 ]); 1316 1317 const negotiationNeededAwaiter = negotiationNeeded(pc2); 1318 await pc2.setLocalDescription(answer); 1319 hasProps(pc2.getTransceivers(), 1320 [ 1321 { 1322 direction: "stopped", 1323 currentDirection: "inactive", 1324 } 1325 ]); 1326 1327 await negotiationNeededAwaiter; 1328 }; 1329 1330 const checkStopAfterCreateAnswer = async t => { 1331 const pc1 = new RTCPeerConnection(); 1332 const pc2 = new RTCPeerConnection(); 1333 t.add_cleanup(() => pc1.close()); 1334 t.add_cleanup(() => pc2.close()); 1335 1336 const stream = await getNoiseStream({audio: true}); 1337 t.add_cleanup(() => stopTracks(stream)); 1338 const track = stream.getAudioTracks()[0]; 1339 pc1.addTrack(track, stream); 1340 pc2.addTrack(track, stream); 1341 1342 let offer = await pc1.createOffer(); 1343 1344 await pc2.setRemoteDescription(offer) 1345 trickle(t, pc1, pc2); 1346 await pc1.setLocalDescription(offer); 1347 1348 let answer = await pc2.createAnswer(); 1349 1350 // Too late for this to go in the answer. ICE should succeed. 1351 pc2.getTransceivers()[0].stop(); 1352 1353 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); 1354 hasProps(trackEvents, 1355 [ 1356 { 1357 track: pc1.getTransceivers()[0].receiver.track, 1358 streams: [{id: stream.id}] 1359 } 1360 ]); 1361 1362 hasPropsAndUniqueMids(pc2.getTransceivers(), 1363 [ 1364 { 1365 direction: "stopped", 1366 currentDirection: null, 1367 } 1368 ]); 1369 1370 trickle(t, pc2, pc1); 1371 // The negotiationneeded event is fired during processing of 1372 // setLocalDescription() 1373 const negotiationNeededAwaiter = negotiationNeeded(pc2); 1374 await pc2.setLocalDescription(answer); 1375 hasPropsAndUniqueMids(pc2.getTransceivers(), 1376 [ 1377 { 1378 direction: "stopped", 1379 currentDirection: "sendrecv", 1380 } 1381 ]); 1382 1383 await negotiationNeededAwaiter; 1384 await iceConnected(pc1); 1385 await iceConnected(pc2); 1386 1387 offer = await pc1.createOffer(); 1388 await pc1.setLocalDescription(offer); 1389 await pc2.setRemoteDescription(offer); 1390 answer = await pc2.createAnswer(); 1391 await pc2.setLocalDescription(answer); 1392 await pc1.setRemoteDescription(answer); 1393 1394 // Since this offer/answer exchange was initiated from pc1, 1395 // pc2 still doesn't get to say that it has a stopped transceiver, 1396 // but does get to set it to inactive. 1397 hasProps(pc1.getTransceivers(), 1398 [ 1399 { 1400 direction: "sendrecv", 1401 currentDirection: "inactive", 1402 } 1403 ]); 1404 1405 hasProps(pc2.getTransceivers(), 1406 [ 1407 { 1408 direction: "stopped", 1409 currentDirection: "inactive", 1410 } 1411 ]); 1412 }; 1413 1414 const checkStopAfterSetLocalAnswer = async t => { 1415 const pc1 = new RTCPeerConnection(); 1416 const pc2 = new RTCPeerConnection(); 1417 t.add_cleanup(() => pc1.close()); 1418 t.add_cleanup(() => pc2.close()); 1419 1420 const stream = await getNoiseStream({audio: true}); 1421 t.add_cleanup(() => stopTracks(stream)); 1422 const track = stream.getAudioTracks()[0]; 1423 pc1.addTrack(track, stream); 1424 pc2.addTrack(track, stream); 1425 1426 let offer = await pc1.createOffer(); 1427 1428 await pc2.setRemoteDescription(offer) 1429 trickle(t, pc1, pc2); 1430 await pc1.setLocalDescription(offer); 1431 1432 let answer = await pc2.createAnswer(); 1433 1434 const trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); 1435 hasProps(trackEvents, 1436 [ 1437 { 1438 track: pc1.getTransceivers()[0].receiver.track, 1439 streams: [{id: stream.id}] 1440 } 1441 ]); 1442 1443 trickle(t, pc2, pc1); 1444 await pc2.setLocalDescription(answer); 1445 1446 // ICE should succeed. 1447 pc2.getTransceivers()[0].stop(); 1448 1449 hasPropsAndUniqueMids(pc2.getTransceivers(), 1450 [ 1451 { 1452 direction: "stopped", 1453 currentDirection: "sendrecv", 1454 } 1455 ]); 1456 1457 await negotiationNeeded(pc2); 1458 await iceConnected(pc1); 1459 await iceConnected(pc2); 1460 1461 // Initiate an offer/answer exchange from pc2 in order 1462 // to negotiate the stopped transceiver. 1463 offer = await pc2.createOffer(); 1464 await pc2.setLocalDescription(offer); 1465 await pc1.setRemoteDescription(offer); 1466 answer = await pc1.createAnswer(); 1467 await pc1.setLocalDescription(answer); 1468 await pc2.setRemoteDescription(answer); 1469 1470 assert_equals(pc1.getTransceivers().length, 0); 1471 assert_equals(pc2.getTransceivers().length, 0); 1472 }; 1473 1474 const checkStopAfterClose = async t => { 1475 const pc1 = new RTCPeerConnection(); 1476 const pc2 = new RTCPeerConnection(); 1477 t.add_cleanup(() => pc1.close()); 1478 t.add_cleanup(() => pc2.close()); 1479 1480 const stream = await getNoiseStream({audio: true}); 1481 t.add_cleanup(() => stopTracks(stream)); 1482 const track = stream.getAudioTracks()[0]; 1483 pc1.addTrack(track, stream); 1484 pc2.addTrack(track, stream); 1485 1486 const offer = await pc1.createOffer(); 1487 await pc2.setRemoteDescription(offer) 1488 await pc1.setLocalDescription(offer); 1489 const answer = await pc2.createAnswer(); 1490 await pc2.setLocalDescription(answer); 1491 await pc1.setRemoteDescription(answer); 1492 1493 pc1.close(); 1494 await checkThrows(() => pc1.getTransceivers()[0].stop(), 1495 "InvalidStateError", 1496 "Stopping a transceiver on a closed PC should throw."); 1497 }; 1498 1499 const checkLocalRollback = async t => { 1500 const pc = new RTCPeerConnection(); 1501 t.add_cleanup(() => pc.close()); 1502 1503 const stream = await getNoiseStream({audio: true}); 1504 t.add_cleanup(() => stopTracks(stream)); 1505 const track = stream.getAudioTracks()[0]; 1506 pc.addTrack(track, stream); 1507 1508 let offer = await pc.createOffer(); 1509 await pc.setLocalDescription(offer); 1510 1511 hasPropsAndUniqueMids(pc.getTransceivers(), 1512 [ 1513 { 1514 receiver: {track: {kind: "audio"}}, 1515 sender: {track}, 1516 direction: "sendrecv", 1517 currentDirection: null, 1518 } 1519 ]); 1520 1521 // Verify that rollback doesn't stomp things it should not 1522 pc.getTransceivers()[0].direction = "sendonly"; 1523 const stream2 = await getNoiseStream({audio: true}); 1524 const track2 = stream2.getAudioTracks()[0]; 1525 await pc.getTransceivers()[0].sender.replaceTrack(track2); 1526 1527 await pc.setLocalDescription({type: "rollback"}); 1528 1529 hasProps(pc.getTransceivers(), 1530 [ 1531 { 1532 receiver: {track: {kind: "audio"}}, 1533 sender: {track: track2}, 1534 direction: "sendonly", 1535 mid: null, 1536 currentDirection: null, 1537 } 1538 ]); 1539 1540 // Make sure stop() isn't rolled back either. 1541 offer = await pc.createOffer(); 1542 await pc.setLocalDescription(offer); 1543 pc.getTransceivers()[0].stop(); 1544 await pc.setLocalDescription({type: "rollback"}); 1545 1546 hasProps(pc.getTransceivers(), [ 1547 { 1548 direction: "stopped", 1549 } 1550 ]); 1551 }; 1552 1553 const checkRollbackAndSetRemoteOfferWithDifferentType = async t => { 1554 const pc1 = new RTCPeerConnection(); 1555 t.add_cleanup(() => pc1.close()); 1556 1557 const audioStream = await getNoiseStream({audio: true}); 1558 t.add_cleanup(() => stopTracks(audioStream)); 1559 const audioTrack = audioStream.getAudioTracks()[0]; 1560 pc1.addTrack(audioTrack, audioStream); 1561 1562 const pc2 = new RTCPeerConnection(); 1563 t.add_cleanup(() => pc2.close()); 1564 1565 const videoStream = await getNoiseStream({video: true}); 1566 t.add_cleanup(() => stopTracks(videoStream)); 1567 const videoTrack = videoStream.getVideoTracks()[0]; 1568 pc2.addTrack(videoTrack, videoStream); 1569 1570 await pc1.setLocalDescription(await pc1.createOffer()); 1571 await pc1.setLocalDescription({type: "rollback"}); 1572 1573 hasProps(pc1.getTransceivers(), 1574 [ 1575 { 1576 receiver: {track: {kind: "audio"}}, 1577 sender: {track: audioTrack}, 1578 direction: "sendrecv", 1579 mid: null, 1580 currentDirection: null, 1581 } 1582 ]); 1583 1584 hasProps(pc2.getTransceivers(), 1585 [ 1586 { 1587 receiver: {track: {kind: "video"}}, 1588 sender: {track: videoTrack}, 1589 direction: "sendrecv", 1590 mid: null, 1591 currentDirection: null, 1592 } 1593 ]); 1594 1595 await offerAnswer(pc2, pc1); 1596 1597 hasPropsAndUniqueMids(pc1.getTransceivers(), 1598 [ 1599 { 1600 receiver: {track: {kind: "audio"}}, 1601 sender: {track: audioTrack}, 1602 direction: "sendrecv", 1603 mid: null, 1604 currentDirection: null, 1605 }, 1606 { 1607 receiver: {track: {kind: "video"}}, 1608 sender: {track: null}, 1609 direction: "recvonly", 1610 currentDirection: "recvonly", 1611 } 1612 ]); 1613 1614 hasPropsAndUniqueMids(pc2.getTransceivers(), 1615 [ 1616 { 1617 receiver: {track: {kind: "video"}}, 1618 sender: {track: videoTrack}, 1619 direction: "sendrecv", 1620 currentDirection: "sendonly", 1621 } 1622 ]); 1623 1624 await offerAnswer(pc1, pc2); 1625 }; 1626 1627 const checkRemoteRollback = async t => { 1628 const pc1 = new RTCPeerConnection(); 1629 t.add_cleanup(() => pc1.close()); 1630 1631 const stream = await getNoiseStream({audio: true}); 1632 t.add_cleanup(() => stopTracks(stream)); 1633 const track = stream.getAudioTracks()[0]; 1634 pc1.addTrack(track, stream); 1635 1636 let offer = await pc1.createOffer(); 1637 1638 const pc2 = new RTCPeerConnection(); 1639 t.add_cleanup(() => pc2.close()); 1640 await pc2.setRemoteDescription(offer); 1641 1642 const removedTransceiver = pc2.getTransceivers()[0]; 1643 1644 const onended = new Promise(resolve => { 1645 removedTransceiver.receiver.track.onended = resolve; 1646 }); 1647 1648 await pc2.setRemoteDescription({type: "rollback"}); 1649 1650 // Transceiver should be _gone_ 1651 hasProps(pc2.getTransceivers(), []); 1652 1653 hasProps(removedTransceiver, 1654 { 1655 mid: null, 1656 currentDirection: "stopped" 1657 } 1658 ); 1659 1660 await onended; 1661 1662 hasProps(removedTransceiver, 1663 { 1664 receiver: {track: {readyState: "ended"}}, 1665 mid: null, 1666 currentDirection: "stopped" 1667 } 1668 ); 1669 1670 // Setting the same offer again should do the same thing as before 1671 await pc2.setRemoteDescription(offer); 1672 hasPropsAndUniqueMids(pc2.getTransceivers(), 1673 [ 1674 { 1675 receiver: {track: {kind: "audio"}}, 1676 sender: {track: null}, 1677 direction: "recvonly", 1678 currentDirection: null, 1679 } 1680 ]); 1681 1682 const mid0 = pc2.getTransceivers()[0].mid; 1683 1684 // Give pc2 a track with replaceTrack 1685 const stream2 = await getNoiseStream({audio: true}); 1686 t.add_cleanup(() => stopTracks(stream2)); 1687 const track2 = stream2.getAudioTracks()[0]; 1688 await pc2.getTransceivers()[0].sender.replaceTrack(track2); 1689 pc2.getTransceivers()[0].direction = "sendrecv"; 1690 hasProps(pc2.getTransceivers(), 1691 [ 1692 { 1693 receiver: {track: {kind: "audio"}}, 1694 sender: {track: track2}, 1695 direction: "sendrecv", 1696 mid: mid0, 1697 currentDirection: null, 1698 } 1699 ]); 1700 1701 await pc2.setRemoteDescription({type: "rollback"}); 1702 1703 // Transceiver should be _gone_, again. replaceTrack doesn't prevent this, 1704 // nor does setting direction. 1705 hasProps(pc2.getTransceivers(), []); 1706 1707 // Setting the same offer for a _third_ time should do the same thing 1708 await pc2.setRemoteDescription(offer); 1709 hasProps(pc2.getTransceivers(), 1710 [ 1711 { 1712 receiver: {track: {kind: "audio"}}, 1713 sender: {track: null}, 1714 direction: "recvonly", 1715 mid: mid0, 1716 currentDirection: null, 1717 } 1718 ]); 1719 1720 // We should be able to add the same track again 1721 pc2.addTrack(track2, stream2); 1722 hasProps(pc2.getTransceivers(), 1723 [ 1724 { 1725 receiver: {track: {kind: "audio"}}, 1726 sender: {track: track2}, 1727 direction: "sendrecv", 1728 mid: mid0, 1729 currentDirection: null, 1730 } 1731 ]); 1732 1733 await pc2.setRemoteDescription({type: "rollback"}); 1734 // Transceiver should _not_ be gone this time, because addTrack touched it. 1735 hasProps(pc2.getTransceivers(), 1736 [ 1737 { 1738 receiver: {track: {kind: "audio"}}, 1739 sender: {track: track2}, 1740 direction: "sendrecv", 1741 mid: null, 1742 currentDirection: null, 1743 } 1744 ]); 1745 1746 // Complete negotiation so we can test interactions with transceiver.stop() 1747 await pc1.setLocalDescription(offer); 1748 1749 // After all this SRD/rollback, we should still get the track event 1750 let trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 1751 1752 assert_equals(trackEvents.length, 1); 1753 hasProps(trackEvents, 1754 [ 1755 { 1756 track: pc2.getTransceivers()[0].receiver.track, 1757 streams: [{id: stream.id}] 1758 } 1759 ]); 1760 1761 const answer = await pc2.createAnswer(); 1762 await pc2.setLocalDescription(answer); 1763 1764 // Make sure all this rollback hasn't messed up the signaling 1765 trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); 1766 assert_equals(trackEvents.length, 1); 1767 hasProps(trackEvents, 1768 [ 1769 { 1770 track: pc1.getTransceivers()[0].receiver.track, 1771 streams: [{id: stream2.id}] 1772 } 1773 ]); 1774 hasProps(pc1.getTransceivers(), 1775 [ 1776 { 1777 receiver: {track: {kind: "audio"}}, 1778 sender: {track}, 1779 direction: "sendrecv", 1780 mid: mid0, 1781 currentDirection: "sendrecv", 1782 } 1783 ]); 1784 1785 // Don't bother waiting for ICE and such 1786 1787 // Check to see whether rolling back a remote track removal works 1788 pc1.getTransceivers()[0].direction = "recvonly"; 1789 offer = await pc1.createOffer(); 1790 1791 trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 1792 hasProps(trackEvents, []); 1793 1794 trackEvents = 1795 await setRemoteDescriptionReturnTrackEvents(pc2, {type: "rollback"}); 1796 1797 assert_equals(trackEvents.length, 1, 'track event from remote rollback'); 1798 hasProps(trackEvents, 1799 [ 1800 { 1801 track: pc2.getTransceivers()[0].receiver.track, 1802 streams: [{id: stream.id}] 1803 } 1804 ]); 1805 1806 // Check to see that stop() cannot be rolled back 1807 pc1.getTransceivers()[0].stop(); 1808 offer = await pc1.createOffer(); 1809 1810 await pc2.setRemoteDescription(offer); 1811 hasProps(pc2.getTransceivers(), 1812 [ 1813 { 1814 receiver: {track: {kind: "audio"}}, 1815 sender: {track: track2}, 1816 direction: "stopped", 1817 mid: mid0, 1818 currentDirection: "stopped", 1819 } 1820 ]); 1821 1822 // stop() cannot be rolled back! 1823 // Transceiver should have [[stopping]]=true, [[stopped]]=false. 1824 await pc2.setRemoteDescription({type: "rollback"}); 1825 hasProps(pc2.getTransceivers(), 1826 [ 1827 { 1828 receiver: {track: {kind: "audio"}}, 1829 sender: {track: {kind: "audio"}}, 1830 direction: "stopped", 1831 mid: mid0, 1832 currentDirection: "stopped", 1833 } 1834 ]); 1835 }; 1836 1837 const checkBundleTagRejected = async t => { 1838 const pc1 = new RTCPeerConnection(); 1839 t.add_cleanup(() => pc1.close()); 1840 const pc2 = new RTCPeerConnection(); 1841 t.add_cleanup(() => pc2.close()); 1842 1843 const stream1 = await getNoiseStream({audio: true}); 1844 t.add_cleanup(() => stopTracks(stream1)); 1845 const track1 = stream1.getAudioTracks()[0]; 1846 const stream2 = await getNoiseStream({audio: true}); 1847 t.add_cleanup(() => stopTracks(stream2)); 1848 const track2 = stream2.getAudioTracks()[0]; 1849 1850 pc1.addTrack(track1, stream1); 1851 pc1.addTrack(track2, stream2); 1852 1853 await offerAnswer(pc1, pc2); 1854 1855 pc2.getTransceivers()[0].stop(); 1856 1857 await offerAnswer(pc1, pc2); 1858 await offerAnswer(pc2, pc1); 1859 }; 1860 1861 const checkMsectionReuse = async t => { 1862 // Use max-compat to make it easier to check for disabled m-sections 1863 const pc1 = new RTCPeerConnection({ bundlePolicy: "max-compat" }); 1864 const pc2 = new RTCPeerConnection({ bundlePolicy: "max-compat" }); 1865 t.add_cleanup(() => pc1.close()); 1866 t.add_cleanup(() => pc2.close()); 1867 1868 const stream = await getNoiseStream({audio: true}); 1869 t.add_cleanup(() => stopTracks(stream)); 1870 const track = stream.getAudioTracks()[0]; 1871 pc1.addTrack(track, stream); 1872 const [pc1Transceiver] = pc1.getTransceivers(); 1873 1874 await pc1.setLocalDescription(); 1875 await pc2.setRemoteDescription(pc1.localDescription); 1876 1877 // Answerer stops transceiver. The m-section is not immediately rejected 1878 // (a follow-up O/A exchange is needed) but it should become inactive in 1879 // the meantime. 1880 const stoppedMid0 = pc2.getTransceivers()[0].mid; 1881 const [pc2Transceiver] = pc2.getTransceivers(); 1882 pc2Transceiver.stop(); 1883 assert_equals(pc2.getTransceivers()[0].direction, "stopped"); 1884 assert_not_equals(pc2.getTransceivers()[0].currentDirection, "stopped"); 1885 1886 await pc2.setLocalDescription(); 1887 await pc1.setRemoteDescription(pc2.localDescription); 1888 1889 // Still not stopped - but inactive is reflected! 1890 assert_equals(pc1Transceiver.mid, stoppedMid0); 1891 assert_equals(pc1Transceiver.direction, "sendrecv"); 1892 assert_equals(pc1Transceiver.currentDirection, "inactive"); 1893 assert_equals(pc2Transceiver.mid, stoppedMid0); 1894 assert_equals(pc2Transceiver.direction, "stopped"); 1895 assert_equals(pc2Transceiver.currentDirection, "inactive"); 1896 1897 // Now do the follow-up O/A exchange pc2 -> pc1. 1898 await pc2.setLocalDescription(); 1899 await pc1.setRemoteDescription(pc2.localDescription); 1900 await pc1.setLocalDescription(); 1901 await pc2.setRemoteDescription(pc1.localDescription); 1902 1903 // Now they're stopped, and have been removed from the PCs. 1904 assert_equals(pc1.getTransceivers().length, 0); 1905 assert_equals(pc2.getTransceivers().length, 0); 1906 assert_equals(pc1Transceiver.mid, null); 1907 assert_equals(pc1Transceiver.direction, "stopped"); 1908 assert_equals(pc1Transceiver.currentDirection, "stopped"); 1909 assert_equals(pc2Transceiver.mid, null); 1910 assert_equals(pc2Transceiver.direction, "stopped"); 1911 assert_equals(pc2Transceiver.currentDirection, "stopped"); 1912 1913 // Check that m-section is reused on both ends 1914 const stream2 = await getNoiseStream({audio: true}); 1915 t.add_cleanup(() => stopTracks(stream2)); 1916 const track2 = stream2.getAudioTracks()[0]; 1917 1918 pc1.addTrack(track2, stream2); 1919 let offer = await pc1.createOffer(); 1920 assert_equals(offer.sdp.match(/m=/g).length, 1, 1921 "Exactly one m-line in offer, because it was reused"); 1922 hasProps(pc1.getTransceivers(), 1923 [ 1924 { 1925 sender: {track: track2} 1926 } 1927 ]); 1928 1929 assert_not_equals(pc1.getTransceivers()[0].mid, stoppedMid0); 1930 1931 pc2.addTrack(track, stream); 1932 offer = await pc2.createOffer(); 1933 assert_equals(offer.sdp.match(/m=/g).length, 1, 1934 "Exactly one m-line in offer, because it was reused"); 1935 hasProps(pc2.getTransceivers(), 1936 [ 1937 { 1938 sender: {track} 1939 } 1940 ]); 1941 1942 assert_not_equals(pc2.getTransceivers()[0].mid, stoppedMid0); 1943 1944 await pc2.setLocalDescription(offer); 1945 await pc1.setRemoteDescription(offer); 1946 let answer = await pc1.createAnswer(); 1947 await pc1.setLocalDescription(answer); 1948 await pc2.setRemoteDescription(answer); 1949 hasPropsAndUniqueMids(pc1.getTransceivers(), 1950 [ 1951 { 1952 sender: {track: track2}, 1953 currentDirection: "sendrecv" 1954 } 1955 ]); 1956 1957 const mid0 = pc1.getTransceivers()[0].mid; 1958 1959 hasProps(pc2.getTransceivers(), 1960 [ 1961 { 1962 sender: {track}, 1963 currentDirection: "sendrecv", 1964 mid: mid0 1965 } 1966 ]); 1967 1968 // stop the transceiver, and add a track. Verify that we don't reuse 1969 // prematurely in our offer. (There should be one rejected m-section, and a 1970 // new one for the new track) 1971 const stoppedMid1 = pc1.getTransceivers()[0].mid; 1972 pc1.getTransceivers()[0].stop(); 1973 const stream3 = await getNoiseStream({audio: true}); 1974 t.add_cleanup(() => stopTracks(stream3)); 1975 const track3 = stream3.getAudioTracks()[0]; 1976 pc1.addTrack(track3, stream3); 1977 offer = await pc1.createOffer(); 1978 assert_equals(offer.sdp.match(/m=/g).length, 2, 1979 "Exactly 2 m-lines in offer, because it is too early to reuse"); 1980 assert_equals(offer.sdp.match(/m=audio 0 /g).length, 1, 1981 "One m-line is rejected"); 1982 1983 await pc1.setLocalDescription(offer); 1984 1985 let trackEvents = await setRemoteDescriptionReturnTrackEvents(pc2, offer); 1986 hasProps(trackEvents, 1987 [ 1988 { 1989 track: pc2.getTransceivers()[1].receiver.track, 1990 streams: [{id: stream3.id}] 1991 } 1992 ]); 1993 1994 answer = await pc2.createAnswer(); 1995 await pc2.setLocalDescription(answer); 1996 1997 trackEvents = await setRemoteDescriptionReturnTrackEvents(pc1, answer); 1998 hasProps(trackEvents, []); 1999 2000 hasPropsAndUniqueMids(pc2.getTransceivers(), 2001 [ 2002 { 2003 sender: {track: null}, 2004 currentDirection: "recvonly" 2005 } 2006 ]); 2007 2008 // Verify that we don't reuse the mid from the stopped transceiver 2009 const mid1 = pc2.getTransceivers()[0].mid; 2010 assert_not_equals(mid1, stoppedMid1); 2011 2012 pc2.addTrack(track3, stream3); 2013 // There are two ways to handle this new track; reuse the recvonly 2014 // transceiver created above, or create a new transceiver and reuse the 2015 // disabled m-section. We're supposed to do the former. 2016 offer = await pc2.createOffer(); 2017 assert_equals(offer.sdp.match(/m=/g).length, 2, "Exactly 2 m-lines in offer"); 2018 assert_equals(offer.sdp.match(/m=audio 0 /g).length, 1, 2019 "One m-line is rejected, because the other was used"); 2020 2021 hasProps(pc2.getTransceivers(), 2022 [ 2023 { 2024 mid: mid1, 2025 sender: {track: track3}, 2026 currentDirection: "recvonly", 2027 direction: "sendrecv" 2028 } 2029 ]); 2030 2031 // Add _another_ track; this should reuse the disabled m-section 2032 const stream4 = await getNoiseStream({audio: true}); 2033 t.add_cleanup(() => stopTracks(stream4)); 2034 const track4 = stream4.getAudioTracks()[0]; 2035 pc2.addTrack(track4, stream4); 2036 offer = await pc2.createOffer(); 2037 await pc2.setLocalDescription(offer); 2038 hasPropsAndUniqueMids(pc2.getTransceivers(), 2039 [ 2040 { 2041 mid: mid1 2042 }, 2043 { 2044 sender: {track: track4}, 2045 } 2046 ]); 2047 2048 // Fourth transceiver should have a new mid 2049 assert_not_equals(pc2.getTransceivers()[1].mid, stoppedMid0); 2050 assert_not_equals(pc2.getTransceivers()[1].mid, stoppedMid1); 2051 2052 assert_equals(offer.sdp.match(/m=/g).length, 2, 2053 "Exactly 2 m-lines in offer, because m-section was reused"); 2054 assert_equals(offer.sdp.match(/m=audio 0 /g), null, 2055 "No rejected m-line, because it was reused"); 2056 }; 2057 2058 const checkStopAfterCreateOfferWithReusedMsection = async t => { 2059 const pc1 = new RTCPeerConnection(); 2060 t.add_cleanup(() => pc1.close()); 2061 const pc2 = new RTCPeerConnection(); 2062 t.add_cleanup(() => pc2.close()); 2063 2064 const stream = await getNoiseStream({audio: true, video: true}); 2065 t.add_cleanup(() => stopTracks(stream)); 2066 const audio = stream.getAudioTracks()[0]; 2067 const video = stream.getVideoTracks()[0]; 2068 2069 pc1.addTrack(audio, stream); 2070 pc1.addTrack(video, stream); 2071 2072 await offerAnswer(pc1, pc2); 2073 pc1.getTransceivers()[1].stop(); 2074 await offerAnswer(pc1, pc2); 2075 2076 // Second (video) m-section has been negotiated disabled. 2077 const transceiver = pc1.addTransceiver("video"); 2078 const offer = await pc1.createOffer(); 2079 transceiver.stop(); 2080 await pc1.setLocalDescription(offer); 2081 await pc2.setRemoteDescription(offer); 2082 2083 const answer = await pc2.createAnswer(); 2084 await pc2.setLocalDescription(answer); 2085 await pc1.setRemoteDescription(answer); 2086 }; 2087 2088 const checkAddIceCandidateToStoppedTransceiver = async t => { 2089 const pc1 = new RTCPeerConnection(); 2090 t.add_cleanup(() => pc1.close()); 2091 const pc2 = new RTCPeerConnection(); 2092 t.add_cleanup(() => pc2.close()); 2093 2094 const stream = await getNoiseStream({audio: true, video: true}); 2095 t.add_cleanup(() => stopTracks(stream)); 2096 const audio = stream.getAudioTracks()[0]; 2097 const video = stream.getVideoTracks()[0]; 2098 2099 pc1.addTrack(audio, stream); 2100 pc1.addTrack(video, stream); 2101 2102 pc2.addTrack(audio, stream); 2103 pc2.addTrack(video, stream); 2104 2105 await pc1.setLocalDescription(await pc1.createOffer()); 2106 pc1.getTransceivers()[1].stop(); 2107 pc1.setLocalDescription({type: "rollback"}); 2108 2109 const offer = await pc2.createOffer(); 2110 await pc2.setLocalDescription(offer); 2111 await pc1.setRemoteDescription(offer); 2112 2113 await pc1.addIceCandidate( 2114 { 2115 candidate: "candidate:0 1 UDP 2122252543 192.168.1.112 64261 typ host", 2116 sdpMid: pc2.getTransceivers()[1].mid 2117 }); 2118 }; 2119 2120 const tests = [ 2121 checkAddTransceiverNoTrack, 2122 checkAddTransceiverWithTrack, 2123 checkAddTransceiverWithAddTrack, 2124 checkAddTransceiverWithDirection, 2125 checkAddTransceiverWithSetRemoteOfferSending, 2126 checkAddTransceiverWithSetRemoteOfferNoSend, 2127 checkAddTransceiverBadKind, 2128 checkNoMidOffer, 2129 checkNoMidAnswer, 2130 checkSetDirection, 2131 checkCurrentDirection, 2132 checkSendrecvWithNoSendTrack, 2133 checkSendrecvWithTracklessStream, 2134 checkAddTransceiverNoTrackDoesntPair, 2135 checkAddTransceiverWithTrackDoesntPair, 2136 checkAddTransceiverThenReplaceTrackDoesntPair, 2137 checkAddTransceiverThenAddTrackPairs, 2138 checkAddTrackPairs, 2139 checkReplaceTrackNullDoesntPreventPairing, 2140 checkRemoveAndReadd, 2141 checkAddTrackExistingTransceiverThenRemove, 2142 checkRemoveTrackNegotiation, 2143 checkStop, 2144 checkStopAfterCreateOffer, 2145 checkStopAfterSetLocalOffer, 2146 checkStopAfterSetRemoteOffer, 2147 checkStopAfterCreateAnswer, 2148 checkStopAfterSetLocalAnswer, 2149 checkStopAfterClose, 2150 checkLocalRollback, 2151 checkRollbackAndSetRemoteOfferWithDifferentType, 2152 checkRemoteRollback, 2153 checkMsectionReuse, 2154 checkStopAfterCreateOfferWithReusedMsection, 2155 checkAddIceCandidateToStoppedTransceiver, 2156 checkBundleTagRejected 2157 ].forEach(test => promise_test(test, test.name)); 2158 2159 ['audio', 'video'].forEach(kind => { 2160 const waitUntilTrackEventWithTimeout = (obj, name, t, timeout = 1000/*ms*/) => { 2161 return new Promise((resolve) => { 2162 obj.addEventListener(name, resolve, {once: true}); 2163 t.step_timeout(resolve, timeout) 2164 }); 2165 } 2166 2167 promise_test(async t => { 2168 const pc1 = new RTCPeerConnection(); 2169 const pc2 = new RTCPeerConnection(); 2170 t.add_cleanup(() => pc1.close()); 2171 t.add_cleanup(() => pc2.close()); 2172 2173 pc1.addTransceiver(kind); 2174 pc2.ontrack = t.step_func(async e => { 2175 assert_true(e.track.muted, `track is muted in ontrack`); 2176 }); 2177 const offer = await pc1.createOffer(); 2178 await pc2.setRemoteDescription(offer); 2179 assert_true(pc2.getReceivers()[0].track.muted, `track is muted after SRD`); 2180 }, `track with ${kind} is muted after SRD`); 2181 2182 promise_test(async t => { 2183 const pc1 = new RTCPeerConnection(); 2184 const pc2 = new RTCPeerConnection(); 2185 t.add_cleanup(() => pc1.close()); 2186 t.add_cleanup(() => pc2.close()); 2187 2188 let stream; 2189 if (kind === 'audio') { 2190 stream = await navigator.mediaDevices.getUserMedia({[kind]: true}); 2191 } else { 2192 stream = await getNoiseStream({[kind]: true}); 2193 } 2194 t.add_cleanup(() => stream.getTracks().forEach(t => t.stop())); 2195 2196 pc1.addTrack(stream.getTracks()[0], stream); 2197 pc2.ontrack = t.step_func(async e => { 2198 assert_true(e.track.muted, `track is muted in ontrack`); 2199 }); 2200 2201 await exchangeIceCandidates(pc1, pc2); 2202 await exchangeOfferAnswer(pc1, pc2); 2203 await waitUntilTrackEventWithTimeout(pc2.getReceivers()[0].track, 'unmute', t); 2204 assert_false(pc2.getReceivers()[0].track.muted, `track is unmuted`); 2205 }, `track with ${kind} gets unmuted when packets flow.`); 2206 2207 promise_test(async t => { 2208 const pc1 = new RTCPeerConnection(); 2209 const pc2 = new RTCPeerConnection(); 2210 t.add_cleanup(() => pc1.close()); 2211 t.add_cleanup(() => pc2.close()); 2212 2213 let stream; 2214 if (kind === 'audio') { 2215 stream = await navigator.mediaDevices.getUserMedia({[kind]: true}); 2216 } else { 2217 stream = await getNoiseStream({[kind]: true}); 2218 } 2219 t.add_cleanup(() => stream.getTracks().forEach(t => t.stop())); 2220 2221 pc1.addTrack(stream.getTracks()[0], stream); 2222 pc2.ontrack = t.step_func(async e => { 2223 assert_true(e.track.muted, `track is muted in ontrack`); 2224 }); 2225 2226 await exchangeIceCandidates(pc1, pc2); 2227 await exchangeOfferAnswer(pc1, pc2); 2228 await waitUntilTrackEventWithTimeout(pc2.getReceivers()[0].track, 'unmute', t); 2229 assert_false(pc2.getReceivers()[0].track.muted, `track is unmuted`); 2230 2231 pc1.getTransceivers()[0].direction = 'inactive'; 2232 await exchangeOfferAnswer(pc1, pc2); 2233 await waitUntilTrackEventWithTimeout(pc2.getReceivers()[0].track, 'mute', t); 2234 assert_true(pc2.getReceivers()[0].track.muted, `track is muted`); 2235 }, `track with ${kind} gets muted when setting the transceiver direction to 'inactive'`); 2236 2237 promise_test(async t => { 2238 const pc1 = new RTCPeerConnection(); 2239 const pc2 = new RTCPeerConnection(); 2240 t.add_cleanup(() => pc1.close()); 2241 t.add_cleanup(() => pc2.close()); 2242 2243 let stream; 2244 if (kind === 'audio') { 2245 stream = await navigator.mediaDevices.getUserMedia({[kind]: true}); 2246 } else { 2247 stream = await getNoiseStream({[kind]: true}); 2248 } 2249 t.add_cleanup(() => stream.getTracks().forEach(t => t.stop())); 2250 2251 pc1.addTrack(stream.getTracks()[0], stream); 2252 pc2.ontrack = t.step_func(async e => { 2253 assert_true(e.track.muted, `track is muted in ontrack`); 2254 }); 2255 2256 await exchangeIceCandidates(pc1, pc2); 2257 await exchangeOfferAnswer(pc1, pc2); 2258 await waitUntilTrackEventWithTimeout(pc2.getReceivers()[0].track, 'unmute', t); 2259 assert_false(pc2.getReceivers()[0].track.muted, `track is unmuted`); 2260 2261 pc1.getTransceivers()[0].direction = 'inactive'; 2262 await exchangeOfferAnswer(pc1, pc2); 2263 await waitUntilTrackEventWithTimeout(pc2.getReceivers()[0].track, 'mute', t); 2264 assert_true(pc2.getReceivers()[0].track.muted, `track is muted`); 2265 2266 pc1.getTransceivers()[0].direction = 'sendrecv'; 2267 await exchangeOfferAnswer(pc1, pc2); 2268 await waitUntilTrackEventWithTimeout(pc2.getReceivers()[0].track, 'unmute', t); 2269 assert_false(pc2.getReceivers()[0].track.muted, `track is unmuted`); 2270 }, `track with ${kind} gets unmuted when setting the transceiver direction to 'sendrecv' after 'inactive'`); 2271 2272 promise_test(async t => { 2273 const pc1 = new RTCPeerConnection(); 2274 const pc2 = new RTCPeerConnection(); 2275 t.add_cleanup(() => pc1.close()); 2276 t.add_cleanup(() => pc2.close()); 2277 2278 let stream; 2279 if (kind === 'audio') { 2280 stream = await navigator.mediaDevices.getUserMedia({[kind]: true}); 2281 } else { 2282 stream = await getNoiseStream({[kind]: true}); 2283 } 2284 t.add_cleanup(() => stream.getTracks().forEach(t => t.stop())); 2285 2286 pc1.addTrack(stream.getTracks()[0], stream); 2287 pc2.ontrack = t.step_func(async e => { 2288 assert_true(e.track.muted, `track is muted in ontrack`); 2289 }); 2290 2291 await exchangeIceCandidates(pc1, pc2); 2292 await exchangeOfferAnswer(pc1, pc2); 2293 await waitUntilTrackEventWithTimeout(pc2.getReceivers()[0].track, 'unmute', t); 2294 assert_false(pc2.getReceivers()[0].track.muted, `track is unmuted`); 2295 pc1.getSenders()[0].replaceTrack(null); 2296 2297 pc1.getTransceivers()[0].direction = 'inactive'; 2298 await exchangeOfferAnswer(pc1, pc2); 2299 await waitUntilTrackEventWithTimeout(pc2.getReceivers()[0].track, 'mute', t); 2300 assert_true(pc2.getReceivers()[0].track.muted, `track is muted`); 2301 2302 pc1.getTransceivers()[0].direction = 'sendrecv'; 2303 await exchangeOfferAnswer(pc1, pc2); 2304 await waitUntilTrackEventWithTimeout(pc2.getReceivers()[0].track, 'unmute', t); 2305 assert_true(pc2.getReceivers()[0].track.muted, `track remains muted`); 2306 }, `track with ${kind} stays muted when setting the transceiver direction to 'sendrecv' after 'inactive' but no packets flow`); 2307 }); 2308 2309 </script>