1import asyncio 2import re 3from unittest import TestCase 4 5import aioice.ice 6import aioice.stun 7 8from aiortc import ( 9 RTCConfiguration, 10 RTCIceCandidate, 11 RTCPeerConnection, 12 RTCSessionDescription, 13) 14from aiortc.contrib.media import MediaPlayer 15from aiortc.exceptions import ( 16 InternalError, 17 InvalidAccessError, 18 InvalidStateError, 19 OperationError, 20) 21from aiortc.mediastreams import AudioStreamTrack, VideoStreamTrack 22from aiortc.rtcpeerconnection import filter_preferred_codecs, find_common_codecs 23from aiortc.rtcrtpparameters import ( 24 RTCRtcpFeedback, 25 RTCRtpCodecCapability, 26 RTCRtpCodecParameters, 27) 28from aiortc.rtcrtpsender import RTCRtpSender 29from aiortc.sdp import SessionDescription 30from aiortc.stats import RTCStatsReport 31 32from .test_contrib_media import MediaTestCase 33from .utils import lf2crlf, run 34 35LONG_DATA = b"\xff" * 2000 36STRIP_CANDIDATES_RE = re.compile("^a=(candidate:.*|end-of-candidates)\r\n", re.M) 37 38 39class BogusStreamTrack(AudioStreamTrack): 40 kind = "bogus" 41 42 43def mids(pc): 44 mids = [x.mid for x in pc.getTransceivers()] 45 if pc.sctp: 46 mids.append(pc.sctp.mid) 47 return sorted(mids) 48 49 50def strip_ice_candidates(description): 51 return RTCSessionDescription( 52 sdp=STRIP_CANDIDATES_RE.sub("", description.sdp), type=description.type 53 ) 54 55 56def track_states(pc): 57 states = { 58 "connectionState": [pc.connectionState], 59 "iceConnectionState": [pc.iceConnectionState], 60 "iceGatheringState": [pc.iceGatheringState], 61 "signalingState": [pc.signalingState], 62 } 63 64 @pc.on("connectionstatechange") 65 def connectionstatechange(): 66 states["connectionState"].append(pc.connectionState) 67 68 @pc.on("iceconnectionstatechange") 69 def iceconnectionstatechange(): 70 states["iceConnectionState"].append(pc.iceConnectionState) 71 72 @pc.on("icegatheringstatechange") 73 def icegatheringstatechange(): 74 states["iceGatheringState"].append(pc.iceGatheringState) 75 76 @pc.on("signalingstatechange") 77 def signalingstatechange(): 78 states["signalingState"].append(pc.signalingState) 79 80 return states 81 82 83def track_remote_tracks(pc): 84 tracks = [] 85 86 @pc.on("track") 87 def track(track): 88 tracks.append(track) 89 90 return tracks 91 92 93class RTCRtpCodecParametersTest(TestCase): 94 def test_common_static(self): 95 local_codecs = [ 96 RTCRtpCodecParameters( 97 mimeType="audio/opus", clockRate=48000, channels=2, payloadType=96 98 ), 99 RTCRtpCodecParameters( 100 mimeType="audio/PCMU", clockRate=8000, channels=1, payloadType=0 101 ), 102 RTCRtpCodecParameters( 103 mimeType="audio/PCMA", clockRate=8000, channels=1, payloadType=8 104 ), 105 ] 106 remote_codecs = [ 107 RTCRtpCodecParameters( 108 mimeType="audio/PCMA", clockRate=8000, channels=1, payloadType=8 109 ), 110 RTCRtpCodecParameters( 111 mimeType="audio/PCMU", clockRate=8000, channels=1, payloadType=0 112 ), 113 ] 114 common = find_common_codecs(local_codecs, remote_codecs) 115 self.assertEqual( 116 common, 117 [ 118 RTCRtpCodecParameters( 119 mimeType="audio/PCMA", clockRate=8000, channels=1, payloadType=8 120 ), 121 RTCRtpCodecParameters( 122 mimeType="audio/PCMU", clockRate=8000, channels=1, payloadType=0 123 ), 124 ], 125 ) 126 127 def test_common_dynamic(self): 128 local_codecs = [ 129 RTCRtpCodecParameters( 130 mimeType="audio/opus", clockRate=48000, channels=2, payloadType=96 131 ), 132 RTCRtpCodecParameters( 133 mimeType="audio/PCMU", clockRate=8000, channels=1, payloadType=0 134 ), 135 RTCRtpCodecParameters( 136 mimeType="audio/PCMA", clockRate=8000, channels=1, payloadType=8 137 ), 138 ] 139 remote_codecs = [ 140 RTCRtpCodecParameters( 141 mimeType="audio/opus", clockRate=48000, channels=2, payloadType=100 142 ), 143 RTCRtpCodecParameters( 144 mimeType="audio/PCMA", clockRate=8000, channels=1, payloadType=8 145 ), 146 ] 147 common = find_common_codecs(local_codecs, remote_codecs) 148 self.assertEqual( 149 common, 150 [ 151 RTCRtpCodecParameters( 152 mimeType="audio/opus", clockRate=48000, channels=2, payloadType=100 153 ), 154 RTCRtpCodecParameters( 155 mimeType="audio/PCMA", clockRate=8000, channels=1, payloadType=8 156 ), 157 ], 158 ) 159 160 def test_common_feedback(self): 161 local_codecs = [ 162 RTCRtpCodecParameters( 163 mimeType="video/VP8", 164 clockRate=90000, 165 payloadType=100, 166 rtcpFeedback=[ 167 RTCRtcpFeedback(type="nack"), 168 RTCRtcpFeedback(type="nack", parameter="pli"), 169 ], 170 ) 171 ] 172 remote_codecs = [ 173 RTCRtpCodecParameters( 174 mimeType="video/VP8", 175 clockRate=90000, 176 payloadType=120, 177 rtcpFeedback=[ 178 RTCRtcpFeedback(type="nack"), 179 RTCRtcpFeedback(type="nack", parameter="sli"), 180 ], 181 ) 182 ] 183 common = find_common_codecs(local_codecs, remote_codecs) 184 self.assertEqual(len(common), 1) 185 self.assertEqual(common[0].clockRate, 90000) 186 self.assertEqual(common[0].name, "VP8") 187 self.assertEqual(common[0].payloadType, 120) 188 self.assertEqual(common[0].rtcpFeedback, [RTCRtcpFeedback(type="nack")]) 189 190 def test_common_rtx(self): 191 local_codecs = [ 192 RTCRtpCodecParameters( 193 mimeType="video/VP8", clockRate=90000, payloadType=100 194 ), 195 RTCRtpCodecParameters( 196 mimeType="video/rtx", 197 clockRate=90000, 198 payloadType=101, 199 parameters={"apt": 100}, 200 ), 201 ] 202 remote_codecs = [ 203 RTCRtpCodecParameters( 204 mimeType="video/VP8", clockRate=90000, payloadType=96 205 ), 206 RTCRtpCodecParameters( 207 mimeType="video/rtx", 208 clockRate=90000, 209 payloadType=97, 210 parameters={"apt": 96}, 211 ), 212 RTCRtpCodecParameters( 213 mimeType="video/VP9", clockRate=90000, payloadType=98 214 ), 215 RTCRtpCodecParameters( 216 mimeType="video/rtx", 217 clockRate=90000, 218 payloadType=99, 219 parameters={"apt": 98}, 220 ), 221 ] 222 common = find_common_codecs(local_codecs, remote_codecs) 223 self.assertEqual( 224 common, 225 [ 226 RTCRtpCodecParameters( 227 mimeType="video/VP8", clockRate=90000, payloadType=96 228 ), 229 RTCRtpCodecParameters( 230 mimeType="video/rtx", 231 clockRate=90000, 232 payloadType=97, 233 parameters={"apt": 96}, 234 ), 235 ], 236 ) 237 238 def test_filter_preferred(self): 239 codecs = [ 240 RTCRtpCodecParameters( 241 mimeType="video/VP8", clockRate=90000, payloadType=100 242 ), 243 RTCRtpCodecParameters( 244 mimeType="video/rtx", 245 clockRate=90000, 246 payloadType=101, 247 parameters={"apt": 100}, 248 ), 249 RTCRtpCodecParameters( 250 mimeType="video/H264", clockRate=90000, payloadType=102 251 ), 252 RTCRtpCodecParameters( 253 mimeType="video/rtx", 254 clockRate=90000, 255 payloadType=103, 256 parameters={"apt": 102}, 257 ), 258 ] 259 260 # no preferences 261 self.assertEqual(filter_preferred_codecs(codecs, []), codecs) 262 263 # with RTX, prefer VP8 264 self.assertEqual( 265 filter_preferred_codecs( 266 codecs, 267 [ 268 RTCRtpCodecCapability(mimeType="video/VP8", clockRate=90000), 269 RTCRtpCodecCapability(mimeType="video/rtx", clockRate=90000), 270 RTCRtpCodecCapability(mimeType="video/H264", clockRate=90000), 271 ], 272 ), 273 [ 274 RTCRtpCodecParameters( 275 mimeType="video/VP8", clockRate=90000, payloadType=100 276 ), 277 RTCRtpCodecParameters( 278 mimeType="video/rtx", 279 clockRate=90000, 280 payloadType=101, 281 parameters={"apt": 100}, 282 ), 283 RTCRtpCodecParameters( 284 mimeType="video/H264", clockRate=90000, payloadType=102 285 ), 286 RTCRtpCodecParameters( 287 mimeType="video/rtx", 288 clockRate=90000, 289 payloadType=103, 290 parameters={"apt": 102}, 291 ), 292 ], 293 ) 294 295 # with RTX, prefer H264 296 self.assertEqual( 297 filter_preferred_codecs( 298 codecs, 299 [ 300 RTCRtpCodecCapability(mimeType="video/H264", clockRate=90000), 301 RTCRtpCodecCapability(mimeType="video/rtx", clockRate=90000), 302 RTCRtpCodecCapability(mimeType="video/VP8", clockRate=90000), 303 ], 304 ), 305 [ 306 RTCRtpCodecParameters( 307 mimeType="video/H264", clockRate=90000, payloadType=102 308 ), 309 RTCRtpCodecParameters( 310 mimeType="video/rtx", 311 clockRate=90000, 312 payloadType=103, 313 parameters={"apt": 102}, 314 ), 315 RTCRtpCodecParameters( 316 mimeType="video/VP8", clockRate=90000, payloadType=100 317 ), 318 RTCRtpCodecParameters( 319 mimeType="video/rtx", 320 clockRate=90000, 321 payloadType=101, 322 parameters={"apt": 100}, 323 ), 324 ], 325 ) 326 327 # no RTX, same order 328 self.assertEqual( 329 filter_preferred_codecs( 330 codecs, 331 [ 332 RTCRtpCodecCapability(mimeType="video/VP8", clockRate=90000), 333 RTCRtpCodecCapability(mimeType="video/H264", clockRate=90000), 334 ], 335 ), 336 [ 337 RTCRtpCodecParameters( 338 mimeType="video/VP8", clockRate=90000, payloadType=100 339 ), 340 RTCRtpCodecParameters( 341 mimeType="video/H264", clockRate=90000, payloadType=102 342 ), 343 ], 344 ) 345 346 347class RTCPeerConnectionTest(TestCase): 348 def assertBundled(self, pc): 349 transceivers = pc.getTransceivers() 350 self.assertEqual( 351 transceivers[0].receiver.transport, transceivers[0].sender.transport 352 ) 353 transport = transceivers[0].receiver.transport 354 for i in range(1, len(transceivers)): 355 self.assertEqual(transceivers[i].receiver.transport, transport) 356 self.assertEqual(transceivers[i].sender.transport, transport) 357 if pc.sctp: 358 self.assertEqual(pc.sctp.transport, transport) 359 360 def assertDataChannelOpen(self, dc): 361 self.sleepWhile(lambda: dc.readyState == "connecting") 362 self.assertEqual(dc.readyState, "open") 363 364 def assertIceCompleted(self, pc1, pc2): 365 self.sleepWhile( 366 lambda: pc1.iceConnectionState == "checking" 367 or pc2.iceConnectionState == "checking" 368 ) 369 self.assertEqual(pc1.iceConnectionState, "completed") 370 self.assertEqual(pc2.iceConnectionState, "completed") 371 372 def assertHasIceCandidates(self, description): 373 self.assertTrue("a=candidate:" in description.sdp) 374 self.assertTrue("a=end-of-candidates" in description.sdp) 375 376 def assertHasDtls(self, description, setup): 377 self.assertTrue("a=fingerprint:sha-256" in description.sdp) 378 self.assertEqual( 379 set(re.findall("a=setup:(.*)\r$", description.sdp)), set([setup]) 380 ) 381 382 def closeDataChannel(self, dc): 383 dc.close() 384 self.sleepWhile(lambda: dc.readyState == "closing") 385 self.assertEqual(dc.readyState, "closed") 386 387 def sleepWhile(self, f, max_sleep=1.0): 388 sleep = 0.1 389 total = 0.0 390 while f() and total < max_sleep: 391 run(asyncio.sleep(sleep)) 392 total += sleep 393 394 def setUp(self): 395 # save timers 396 self.consent_failures = aioice.ice.CONSENT_FAILURES 397 self.consent_interval = aioice.ice.CONSENT_INTERVAL 398 self.retry_max = aioice.stun.RETRY_MAX 399 self.retry_rto = aioice.stun.RETRY_RTO 400 401 # shorten timers to run tests faster 402 aioice.ice.CONSENT_FAILURES = 1 403 aioice.ice.CONSENT_INTERVAL = 1 404 aioice.stun.RETRY_MAX = 1 405 aioice.stun.RETRY_RTO = 0.1 406 407 def tearDown(self): 408 # restore timers 409 aioice.ice.CONSENT_FAILURES = self.consent_failures 410 aioice.ice.CONSENT_INTERVAL = self.consent_interval 411 aioice.stun.RETRY_MAX = self.retry_max 412 aioice.stun.RETRY_RTO = self.retry_rto 413 414 def test_addIceCandidate_no_sdpMid_or_sdpMLineIndex(self): 415 pc = RTCPeerConnection() 416 with self.assertRaises(ValueError) as cm: 417 run( 418 pc.addIceCandidate( 419 RTCIceCandidate( 420 component=1, 421 foundation="0", 422 ip="192.168.99.7", 423 port=33543, 424 priority=2122252543, 425 protocol="UDP", 426 type="host", 427 ) 428 ) 429 ) 430 self.assertEqual( 431 str(cm.exception), "Candidate must have either sdpMid or sdpMLineIndex" 432 ) 433 434 def test_addTrack_audio(self): 435 pc = RTCPeerConnection() 436 437 # add audio track 438 track1 = AudioStreamTrack() 439 sender1 = pc.addTrack(track1) 440 self.assertIsNotNone(sender1) 441 self.assertEqual(sender1.track, track1) 442 self.assertEqual(pc.getSenders(), [sender1]) 443 self.assertEqual(len(pc.getTransceivers()), 1) 444 445 # try to add same track again 446 with self.assertRaises(InvalidAccessError) as cm: 447 pc.addTrack(track1) 448 self.assertEqual(str(cm.exception), "Track already has a sender") 449 450 # add another audio track 451 track2 = AudioStreamTrack() 452 sender2 = pc.addTrack(track2) 453 self.assertIsNotNone(sender2) 454 self.assertEqual(sender2.track, track2) 455 self.assertEqual(pc.getSenders(), [sender1, sender2]) 456 self.assertEqual(len(pc.getTransceivers()), 2) 457 458 def test_addTrack_bogus(self): 459 pc = RTCPeerConnection() 460 461 # try adding a bogus track 462 with self.assertRaises(InternalError) as cm: 463 pc.addTrack(BogusStreamTrack()) 464 self.assertEqual(str(cm.exception), 'Invalid track kind "bogus"') 465 466 def test_addTrack_video(self): 467 pc = RTCPeerConnection() 468 469 # add video track 470 video_track1 = VideoStreamTrack() 471 video_sender1 = pc.addTrack(video_track1) 472 self.assertIsNotNone(video_sender1) 473 self.assertEqual(video_sender1.track, video_track1) 474 self.assertEqual(pc.getSenders(), [video_sender1]) 475 self.assertEqual(len(pc.getTransceivers()), 1) 476 477 # try to add same track again 478 with self.assertRaises(InvalidAccessError) as cm: 479 pc.addTrack(video_track1) 480 self.assertEqual(str(cm.exception), "Track already has a sender") 481 482 # add another video track 483 video_track2 = VideoStreamTrack() 484 video_sender2 = pc.addTrack(video_track2) 485 self.assertIsNotNone(video_sender2) 486 self.assertEqual(video_sender2.track, video_track2) 487 self.assertEqual(pc.getSenders(), [video_sender1, video_sender2]) 488 self.assertEqual(len(pc.getTransceivers()), 2) 489 490 # add audio track 491 audio_track = AudioStreamTrack() 492 audio_sender = pc.addTrack(audio_track) 493 self.assertIsNotNone(audio_sender) 494 self.assertEqual(audio_sender.track, audio_track) 495 self.assertEqual(pc.getSenders(), [video_sender1, video_sender2, audio_sender]) 496 self.assertEqual(len(pc.getTransceivers()), 3) 497 498 def test_addTrack_closed(self): 499 pc = RTCPeerConnection() 500 run(pc.close()) 501 with self.assertRaises(InvalidStateError) as cm: 502 pc.addTrack(AudioStreamTrack()) 503 self.assertEqual(str(cm.exception), "RTCPeerConnection is closed") 504 505 def test_addTransceiver_audio_inactive(self): 506 pc = RTCPeerConnection() 507 508 # add transceiver 509 transceiver = pc.addTransceiver("audio", direction="inactive") 510 self.assertIsNotNone(transceiver) 511 self.assertEqual(transceiver.currentDirection, None) 512 self.assertEqual(transceiver.direction, "inactive") 513 self.assertEqual(transceiver.sender.track, None) 514 self.assertEqual(transceiver.stopped, False) 515 self.assertEqual(pc.getSenders(), [transceiver.sender]) 516 self.assertEqual(len(pc.getTransceivers()), 1) 517 518 # add track 519 track = AudioStreamTrack() 520 pc.addTrack(track) 521 self.assertEqual(transceiver.currentDirection, None) 522 self.assertEqual(transceiver.direction, "sendonly") 523 self.assertEqual(transceiver.sender.track, track) 524 self.assertEqual(transceiver.stopped, False) 525 self.assertEqual(len(pc.getTransceivers()), 1) 526 527 # stop transceiver 528 run(transceiver.stop()) 529 self.assertEqual(transceiver.currentDirection, None) 530 self.assertEqual(transceiver.direction, "sendonly") 531 self.assertEqual(transceiver.sender.track, track) 532 self.assertEqual(transceiver.stopped, True) 533 534 def test_addTransceiver_audio_sendrecv(self): 535 pc = RTCPeerConnection() 536 537 # add transceiver 538 transceiver = pc.addTransceiver("audio") 539 self.assertIsNotNone(transceiver) 540 self.assertEqual(transceiver.currentDirection, None) 541 self.assertEqual(transceiver.direction, "sendrecv") 542 self.assertEqual(transceiver.sender.track, None) 543 self.assertEqual(transceiver.stopped, False) 544 self.assertEqual(pc.getSenders(), [transceiver.sender]) 545 self.assertEqual(len(pc.getTransceivers()), 1) 546 547 # add track 548 track = AudioStreamTrack() 549 pc.addTrack(track) 550 self.assertEqual(transceiver.currentDirection, None) 551 self.assertEqual(transceiver.direction, "sendrecv") 552 self.assertEqual(transceiver.sender.track, track) 553 self.assertEqual(transceiver.stopped, False) 554 self.assertEqual(len(pc.getTransceivers()), 1) 555 556 def test_addTransceiver_audio_track(self): 557 pc = RTCPeerConnection() 558 559 # add audio track 560 track1 = AudioStreamTrack() 561 transceiver1 = pc.addTransceiver(track1) 562 self.assertIsNotNone(transceiver1) 563 self.assertEqual(transceiver1.currentDirection, None) 564 self.assertEqual(transceiver1.direction, "sendrecv") 565 self.assertEqual(transceiver1.sender.track, track1) 566 self.assertEqual(transceiver1.stopped, False) 567 self.assertEqual(pc.getSenders(), [transceiver1.sender]) 568 self.assertEqual(len(pc.getTransceivers()), 1) 569 570 # try to add same track again 571 with self.assertRaises(InvalidAccessError) as cm: 572 pc.addTransceiver(track1) 573 self.assertEqual(str(cm.exception), "Track already has a sender") 574 575 # add another audio track 576 track2 = AudioStreamTrack() 577 transceiver2 = pc.addTransceiver(track2) 578 self.assertIsNotNone(transceiver2) 579 self.assertEqual(transceiver2.currentDirection, None) 580 self.assertEqual(transceiver2.direction, "sendrecv") 581 self.assertEqual(transceiver2.sender.track, track2) 582 self.assertEqual(transceiver2.stopped, False) 583 self.assertEqual(pc.getSenders(), [transceiver1.sender, transceiver2.sender]) 584 self.assertEqual(len(pc.getTransceivers()), 2) 585 586 def test_addTransceiver_bogus_direction(self): 587 pc = RTCPeerConnection() 588 589 # try adding a bogus kind 590 with self.assertRaises(InternalError) as cm: 591 pc.addTransceiver("audio", direction="bogus") 592 self.assertEqual(str(cm.exception), 'Invalid direction "bogus"') 593 594 def test_addTransceiver_bogus_kind(self): 595 pc = RTCPeerConnection() 596 597 # try adding a bogus kind 598 with self.assertRaises(InternalError) as cm: 599 pc.addTransceiver("bogus") 600 self.assertEqual(str(cm.exception), 'Invalid track kind "bogus"') 601 602 def test_addTransceiver_bogus_track(self): 603 pc = RTCPeerConnection() 604 605 # try adding a bogus track 606 with self.assertRaises(InternalError) as cm: 607 pc.addTransceiver(BogusStreamTrack()) 608 self.assertEqual(str(cm.exception), 'Invalid track kind "bogus"') 609 610 def test_close(self): 611 pc = RTCPeerConnection() 612 pc_states = track_states(pc) 613 614 # close once 615 run(pc.close()) 616 617 # close twice 618 run(pc.close()) 619 620 self.assertEqual(pc_states["signalingState"], ["stable", "closed"]) 621 622 def _test_connect_audio_bidirectional(self, pc1, pc2): 623 pc1_states = track_states(pc1) 624 pc1_tracks = track_remote_tracks(pc1) 625 pc2_states = track_states(pc2) 626 pc2_tracks = track_remote_tracks(pc2) 627 628 self.assertEqual(pc1.iceConnectionState, "new") 629 self.assertEqual(pc1.iceGatheringState, "new") 630 self.assertIsNone(pc1.localDescription) 631 self.assertIsNone(pc1.remoteDescription) 632 633 self.assertEqual(pc2.iceConnectionState, "new") 634 self.assertEqual(pc2.iceGatheringState, "new") 635 self.assertIsNone(pc2.localDescription) 636 self.assertIsNone(pc2.remoteDescription) 637 638 # create offer 639 track1 = AudioStreamTrack() 640 pc1.addTrack(track1) 641 offer = run(pc1.createOffer()) 642 self.assertEqual(offer.type, "offer") 643 self.assertTrue("m=audio " in offer.sdp) 644 self.assertFalse("a=candidate:" in offer.sdp) 645 self.assertFalse("a=end-of-candidates" in offer.sdp) 646 647 run(pc1.setLocalDescription(offer)) 648 self.assertEqual(pc1.iceConnectionState, "new") 649 self.assertEqual(pc1.iceGatheringState, "complete") 650 self.assertEqual(mids(pc1), ["0"]) 651 self.assertTrue("m=audio " in pc1.localDescription.sdp) 652 self.assertTrue( 653 lf2crlf( 654 """a=rtpmap:96 opus/48000/2 655a=rtpmap:0 PCMU/8000 656a=rtpmap:8 PCMA/8000 657""" 658 ) 659 in pc1.localDescription.sdp 660 ) 661 self.assertTrue("a=sendrecv" in pc1.localDescription.sdp) 662 self.assertHasIceCandidates(pc1.localDescription) 663 self.assertHasDtls(pc1.localDescription, "actpass") 664 665 # handle offer 666 run(pc2.setRemoteDescription(pc1.localDescription)) 667 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 668 self.assertEqual(len(pc2.getReceivers()), 1) 669 self.assertEqual(len(pc2.getSenders()), 1) 670 self.assertEqual(len(pc2.getTransceivers()), 1) 671 self.assertEqual(mids(pc2), ["0"]) 672 673 # the RemoteStreamTrack should have the same ID as the source track 674 self.assertEqual(len(pc2_tracks), 1) 675 self.assertEqual(pc2_tracks[0].id, track1.id) 676 677 # create answer 678 track2 = AudioStreamTrack() 679 pc2.addTrack(track2) 680 answer = run(pc2.createAnswer()) 681 self.assertEqual(answer.type, "answer") 682 self.assertTrue("m=audio " in answer.sdp) 683 self.assertFalse("a=candidate:" in answer.sdp) 684 self.assertFalse("a=end-of-candidates" in answer.sdp) 685 686 run(pc2.setLocalDescription(answer)) 687 self.assertEqual(pc2.iceConnectionState, "checking") 688 self.assertEqual(pc2.iceGatheringState, "complete") 689 self.assertEqual(mids(pc2), ["0"]) 690 self.assertTrue("m=audio " in pc2.localDescription.sdp) 691 self.assertTrue( 692 lf2crlf( 693 """a=rtpmap:96 opus/48000/2 694a=rtpmap:0 PCMU/8000 695a=rtpmap:8 PCMA/8000 696""" 697 ) 698 in pc2.localDescription.sdp 699 ) 700 self.assertTrue("a=sendrecv" in pc2.localDescription.sdp) 701 self.assertHasIceCandidates(pc2.localDescription) 702 self.assertHasDtls(pc2.localDescription, "active") 703 self.assertEqual(pc2.getTransceivers()[0].currentDirection, "sendrecv") 704 self.assertEqual(pc2.getTransceivers()[0].direction, "sendrecv") 705 706 # handle answer 707 run(pc1.setRemoteDescription(pc2.localDescription)) 708 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 709 self.assertEqual(pc1.iceConnectionState, "checking") 710 self.assertEqual(pc1.getTransceivers()[0].currentDirection, "sendrecv") 711 self.assertEqual(pc1.getTransceivers()[0].direction, "sendrecv") 712 713 # the RemoteStreamTrack should have the same ID as the source track 714 self.assertEqual(len(pc1_tracks), 1) 715 self.assertEqual(pc1_tracks[0].id, track2.id) 716 717 # check outcome 718 self.assertIceCompleted(pc1, pc2) 719 720 # allow media to flow long enough to collect stats 721 run(asyncio.sleep(2)) 722 723 # check stats 724 report = run(pc1.getStats()) 725 self.assertTrue(isinstance(report, RTCStatsReport)) 726 self.assertEqual( 727 sorted([s.type for s in report.values()]), 728 [ 729 "inbound-rtp", 730 "outbound-rtp", 731 "remote-inbound-rtp", 732 "remote-outbound-rtp", 733 "transport", 734 ], 735 ) 736 737 # close 738 run(pc1.close()) 739 run(pc2.close()) 740 self.assertEqual(pc1.iceConnectionState, "closed") 741 self.assertEqual(pc2.iceConnectionState, "closed") 742 743 # check state changes 744 self.assertEqual( 745 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 746 ) 747 self.assertEqual( 748 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 749 ) 750 self.assertEqual( 751 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 752 ) 753 self.assertEqual( 754 pc1_states["signalingState"], 755 ["stable", "have-local-offer", "stable", "closed"], 756 ) 757 758 self.assertEqual( 759 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 760 ) 761 self.assertEqual( 762 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 763 ) 764 self.assertEqual( 765 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 766 ) 767 self.assertEqual( 768 pc2_states["signalingState"], 769 ["stable", "have-remote-offer", "stable", "closed"], 770 ) 771 772 def test_connect_audio_bidirectional(self): 773 pc1 = RTCPeerConnection() 774 pc2 = RTCPeerConnection() 775 self._test_connect_audio_bidirectional(pc1, pc2) 776 777 def test_connect_audio_bidirectional_with_empty_iceservers(self): 778 pc1 = RTCPeerConnection(RTCConfiguration(iceServers=[])) 779 pc2 = RTCPeerConnection() 780 self._test_connect_audio_bidirectional(pc1, pc2) 781 782 def test_connect_audio_bidirectional_with_trickle(self): 783 pc1 = RTCPeerConnection() 784 pc1_states = track_states(pc1) 785 786 pc2 = RTCPeerConnection() 787 pc2_states = track_states(pc2) 788 789 self.assertEqual(pc1.iceConnectionState, "new") 790 self.assertEqual(pc1.iceGatheringState, "new") 791 self.assertIsNone(pc1.localDescription) 792 self.assertIsNone(pc1.remoteDescription) 793 794 self.assertEqual(pc2.iceConnectionState, "new") 795 self.assertEqual(pc2.iceGatheringState, "new") 796 self.assertIsNone(pc2.localDescription) 797 self.assertIsNone(pc2.remoteDescription) 798 799 # create offer 800 pc1.addTrack(AudioStreamTrack()) 801 offer = run(pc1.createOffer()) 802 self.assertEqual(offer.type, "offer") 803 self.assertTrue("m=audio " in offer.sdp) 804 self.assertFalse("a=candidate:" in offer.sdp) 805 self.assertFalse("a=end-of-candidates" in offer.sdp) 806 807 run(pc1.setLocalDescription(offer)) 808 self.assertEqual(pc1.iceConnectionState, "new") 809 self.assertEqual(pc1.iceGatheringState, "complete") 810 self.assertEqual(mids(pc1), ["0"]) 811 self.assertTrue("m=audio " in pc1.localDescription.sdp) 812 self.assertTrue("a=sendrecv" in pc1.localDescription.sdp) 813 self.assertHasIceCandidates(pc1.localDescription) 814 self.assertHasDtls(pc1.localDescription, "actpass") 815 816 # strip out candidates 817 desc1 = strip_ice_candidates(pc1.localDescription) 818 819 # handle offer 820 run(pc2.setRemoteDescription(desc1)) 821 self.assertEqual(pc2.remoteDescription, desc1) 822 self.assertEqual(len(pc2.getReceivers()), 1) 823 self.assertEqual(len(pc2.getSenders()), 1) 824 self.assertEqual(len(pc2.getTransceivers()), 1) 825 self.assertEqual(mids(pc2), ["0"]) 826 827 # create answer 828 pc2.addTrack(AudioStreamTrack()) 829 answer = run(pc2.createAnswer()) 830 self.assertEqual(answer.type, "answer") 831 self.assertTrue("m=audio " in answer.sdp) 832 self.assertFalse("a=candidate:" in answer.sdp) 833 self.assertFalse("a=end-of-candidates" in answer.sdp) 834 835 run(pc2.setLocalDescription(answer)) 836 self.assertEqual(pc2.iceConnectionState, "checking") 837 self.assertEqual(pc2.iceGatheringState, "complete") 838 self.assertEqual(mids(pc2), ["0"]) 839 self.assertTrue("m=audio " in pc2.localDescription.sdp) 840 self.assertTrue("a=sendrecv" in pc2.localDescription.sdp) 841 self.assertHasIceCandidates(pc2.localDescription) 842 self.assertHasDtls(pc2.localDescription, "active") 843 844 # strip out candidates 845 desc2 = strip_ice_candidates(pc2.localDescription) 846 847 # handle answer 848 run(pc1.setRemoteDescription(desc2)) 849 self.assertEqual(pc1.remoteDescription, desc2) 850 self.assertEqual(pc1.iceConnectionState, "checking") 851 852 # trickle candidates 853 for transceiver in pc2.getTransceivers(): 854 iceGatherer = transceiver.sender.transport.transport.iceGatherer 855 for candidate in iceGatherer.getLocalCandidates(): 856 candidate.sdpMid = transceiver.mid 857 run(pc1.addIceCandidate(candidate)) 858 for transceiver in pc1.getTransceivers(): 859 iceGatherer = transceiver.sender.transport.transport.iceGatherer 860 for candidate in iceGatherer.getLocalCandidates(): 861 candidate.sdpMid = transceiver.mid 862 run(pc2.addIceCandidate(candidate)) 863 864 # check outcome 865 self.assertIceCompleted(pc1, pc2) 866 867 # close 868 run(pc1.close()) 869 run(pc2.close()) 870 self.assertEqual(pc1.iceConnectionState, "closed") 871 self.assertEqual(pc2.iceConnectionState, "closed") 872 873 # check state changes 874 self.assertEqual( 875 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 876 ) 877 self.assertEqual( 878 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 879 ) 880 self.assertEqual( 881 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 882 ) 883 self.assertEqual( 884 pc1_states["signalingState"], 885 ["stable", "have-local-offer", "stable", "closed"], 886 ) 887 888 self.assertEqual( 889 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 890 ) 891 self.assertEqual( 892 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 893 ) 894 self.assertEqual( 895 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 896 ) 897 self.assertEqual( 898 pc2_states["signalingState"], 899 ["stable", "have-remote-offer", "stable", "closed"], 900 ) 901 902 def test_connect_audio_bidirectional_and_close(self): 903 pc1 = RTCPeerConnection() 904 pc1_states = track_states(pc1) 905 906 pc2 = RTCPeerConnection() 907 pc2_states = track_states(pc2) 908 909 # create offer 910 track1 = AudioStreamTrack() 911 pc1.addTrack(track1) 912 offer = run(pc1.createOffer()) 913 run(pc1.setLocalDescription(offer)) 914 915 # handle offer 916 run(pc2.setRemoteDescription(pc1.localDescription)) 917 918 # create answer 919 track2 = AudioStreamTrack() 920 pc2.addTrack(track2) 921 answer = run(pc2.createAnswer()) 922 run(pc2.setLocalDescription(answer)) 923 924 # handle answer 925 run(pc1.setRemoteDescription(pc2.localDescription)) 926 927 # check outcome 928 self.assertIceCompleted(pc1, pc2) 929 930 # close one side 931 run(pc1.close()) 932 self.assertEqual(pc1.iceConnectionState, "closed") 933 934 # wait for consent to expire 935 run(asyncio.sleep(2)) 936 937 # close other side 938 run(pc2.close()) 939 self.assertEqual(pc2.iceConnectionState, "closed") 940 941 # check state changes 942 self.assertEqual( 943 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 944 ) 945 self.assertEqual( 946 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 947 ) 948 self.assertEqual( 949 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 950 ) 951 self.assertEqual( 952 pc1_states["signalingState"], 953 ["stable", "have-local-offer", "stable", "closed"], 954 ) 955 956 self.assertEqual( 957 pc2_states["connectionState"], 958 ["new", "connecting", "connected", "failed", "closed"], 959 ) 960 self.assertEqual( 961 pc2_states["iceConnectionState"], 962 ["new", "checking", "completed", "failed", "closed"], 963 ) 964 self.assertEqual( 965 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 966 ) 967 self.assertEqual( 968 pc2_states["signalingState"], 969 ["stable", "have-remote-offer", "stable", "closed"], 970 ) 971 972 def test_connect_audio_codec_preferences_offerer(self): 973 pc1 = RTCPeerConnection() 974 pc1_states = track_states(pc1) 975 976 pc2 = RTCPeerConnection() 977 pc2_states = track_states(pc2) 978 979 self.assertEqual(pc1.iceConnectionState, "new") 980 self.assertEqual(pc1.iceGatheringState, "new") 981 self.assertIsNone(pc1.localDescription) 982 self.assertIsNone(pc1.remoteDescription) 983 984 self.assertEqual(pc2.iceConnectionState, "new") 985 self.assertEqual(pc2.iceGatheringState, "new") 986 self.assertIsNone(pc2.localDescription) 987 self.assertIsNone(pc2.remoteDescription) 988 989 # add track and set codec preferences to prefer PCMA / PCMU 990 pc1.addTrack(AudioStreamTrack()) 991 capabilities = RTCRtpSender.getCapabilities("audio") 992 preferences = list(filter(lambda x: x.name == "PCMA", capabilities.codecs)) 993 preferences += list(filter(lambda x: x.name == "PCMU", capabilities.codecs)) 994 transceiver = pc1.getTransceivers()[0] 995 transceiver.setCodecPreferences(preferences) 996 997 # create offer 998 offer = run(pc1.createOffer()) 999 self.assertEqual(offer.type, "offer") 1000 self.assertTrue("m=audio " in offer.sdp) 1001 self.assertFalse("a=candidate:" in offer.sdp) 1002 self.assertFalse("a=end-of-candidates" in offer.sdp) 1003 1004 run(pc1.setLocalDescription(offer)) 1005 self.assertEqual(pc1.iceConnectionState, "new") 1006 self.assertEqual(pc1.iceGatheringState, "complete") 1007 self.assertEqual(mids(pc1), ["0"]) 1008 self.assertTrue("m=audio " in pc1.localDescription.sdp) 1009 self.assertTrue( 1010 lf2crlf( 1011 """a=rtpmap:8 PCMA/8000 1012a=rtpmap:0 PCMU/8000 1013""" 1014 ) 1015 in pc1.localDescription.sdp 1016 ) 1017 self.assertTrue("a=sendrecv" in pc1.localDescription.sdp) 1018 self.assertHasIceCandidates(pc1.localDescription) 1019 self.assertHasDtls(pc1.localDescription, "actpass") 1020 1021 # handle offer 1022 run(pc2.setRemoteDescription(pc1.localDescription)) 1023 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 1024 self.assertEqual(len(pc2.getReceivers()), 1) 1025 self.assertEqual(len(pc2.getSenders()), 1) 1026 self.assertEqual(len(pc2.getTransceivers()), 1) 1027 self.assertEqual(mids(pc2), ["0"]) 1028 1029 # create answer 1030 pc2.addTrack(AudioStreamTrack()) 1031 answer = run(pc2.createAnswer()) 1032 self.assertEqual(answer.type, "answer") 1033 self.assertTrue("m=audio " in answer.sdp) 1034 self.assertFalse("a=candidate:" in answer.sdp) 1035 self.assertFalse("a=end-of-candidates" in answer.sdp) 1036 1037 run(pc2.setLocalDescription(answer)) 1038 self.assertEqual(pc2.iceConnectionState, "checking") 1039 self.assertEqual(pc2.iceGatheringState, "complete") 1040 self.assertEqual(mids(pc2), ["0"]) 1041 self.assertTrue("m=audio " in pc2.localDescription.sdp) 1042 self.assertTrue( 1043 lf2crlf( 1044 """a=rtpmap:8 PCMA/8000 1045a=rtpmap:0 PCMU/8000 1046""" 1047 ) 1048 in pc2.localDescription.sdp 1049 ) 1050 self.assertTrue("a=sendrecv" in pc2.localDescription.sdp) 1051 self.assertHasIceCandidates(pc2.localDescription) 1052 self.assertHasDtls(pc2.localDescription, "active") 1053 self.assertEqual(pc2.getTransceivers()[0].currentDirection, "sendrecv") 1054 self.assertEqual(pc2.getTransceivers()[0].direction, "sendrecv") 1055 1056 # handle answer 1057 run(pc1.setRemoteDescription(pc2.localDescription)) 1058 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 1059 self.assertEqual(pc1.iceConnectionState, "checking") 1060 self.assertEqual(pc1.getTransceivers()[0].currentDirection, "sendrecv") 1061 self.assertEqual(pc1.getTransceivers()[0].direction, "sendrecv") 1062 1063 # check outcome 1064 self.assertIceCompleted(pc1, pc2) 1065 1066 # allow media to flow long enough to collect stats 1067 run(asyncio.sleep(2)) 1068 1069 # check stats 1070 report = run(pc1.getStats()) 1071 self.assertTrue(isinstance(report, RTCStatsReport)) 1072 self.assertEqual( 1073 sorted([s.type for s in report.values()]), 1074 [ 1075 "inbound-rtp", 1076 "outbound-rtp", 1077 "remote-inbound-rtp", 1078 "remote-outbound-rtp", 1079 "transport", 1080 ], 1081 ) 1082 1083 # close 1084 run(pc1.close()) 1085 run(pc2.close()) 1086 self.assertEqual(pc1.iceConnectionState, "closed") 1087 self.assertEqual(pc2.iceConnectionState, "closed") 1088 1089 # check state changes 1090 self.assertEqual( 1091 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 1092 ) 1093 self.assertEqual( 1094 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1095 ) 1096 self.assertEqual( 1097 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 1098 ) 1099 self.assertEqual( 1100 pc1_states["signalingState"], 1101 ["stable", "have-local-offer", "stable", "closed"], 1102 ) 1103 1104 self.assertEqual( 1105 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 1106 ) 1107 self.assertEqual( 1108 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1109 ) 1110 self.assertEqual( 1111 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 1112 ) 1113 self.assertEqual( 1114 pc2_states["signalingState"], 1115 ["stable", "have-remote-offer", "stable", "closed"], 1116 ) 1117 1118 def test_connect_audio_mid_changes(self): 1119 pc1 = RTCPeerConnection() 1120 pc1_states = track_states(pc1) 1121 1122 pc2 = RTCPeerConnection() 1123 pc2_states = track_states(pc2) 1124 1125 self.assertEqual(pc1.iceConnectionState, "new") 1126 self.assertEqual(pc1.iceGatheringState, "new") 1127 self.assertIsNone(pc1.localDescription) 1128 self.assertIsNone(pc1.remoteDescription) 1129 1130 self.assertEqual(pc2.iceConnectionState, "new") 1131 self.assertEqual(pc2.iceGatheringState, "new") 1132 self.assertIsNone(pc2.localDescription) 1133 self.assertIsNone(pc2.remoteDescription) 1134 1135 # add audio tracks immediately 1136 pc1.addTrack(AudioStreamTrack()) 1137 pc2.addTrack(AudioStreamTrack()) 1138 1139 # create offer 1140 offer = run(pc1.createOffer()) 1141 self.assertEqual(offer.type, "offer") 1142 self.assertTrue("m=audio " in offer.sdp) 1143 self.assertFalse("a=candidate:" in offer.sdp) 1144 self.assertFalse("a=end-of-candidates" in offer.sdp) 1145 1146 # pretend we're Firefox! 1147 offer.sdp = offer.sdp.replace("a=mid:0", "a=mid:sdparta_0") 1148 1149 run(pc1.setLocalDescription(offer)) 1150 self.assertEqual(pc1.iceConnectionState, "new") 1151 self.assertEqual(pc1.iceGatheringState, "complete") 1152 self.assertEqual(mids(pc1), ["sdparta_0"]) 1153 self.assertTrue("m=audio " in pc1.localDescription.sdp) 1154 self.assertTrue("a=sendrecv" in pc1.localDescription.sdp) 1155 self.assertHasIceCandidates(pc1.localDescription) 1156 self.assertHasDtls(pc1.localDescription, "actpass") 1157 self.assertTrue("a=mid:sdparta_0" in pc1.localDescription.sdp) 1158 1159 # handle offer 1160 run(pc2.setRemoteDescription(pc1.localDescription)) 1161 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 1162 self.assertEqual(len(pc2.getReceivers()), 1) 1163 self.assertEqual(len(pc2.getSenders()), 1) 1164 self.assertEqual(len(pc2.getTransceivers()), 1) 1165 self.assertEqual(mids(pc2), ["sdparta_0"]) 1166 1167 # create answer 1168 answer = run(pc2.createAnswer()) 1169 self.assertEqual(answer.type, "answer") 1170 self.assertTrue("m=audio " in answer.sdp) 1171 self.assertFalse("a=candidate:" in answer.sdp) 1172 self.assertFalse("a=end-of-candidates" in answer.sdp) 1173 1174 run(pc2.setLocalDescription(answer)) 1175 self.assertEqual(pc2.iceConnectionState, "checking") 1176 self.assertEqual(pc2.iceGatheringState, "complete") 1177 self.assertTrue("m=audio " in pc2.localDescription.sdp) 1178 self.assertTrue("a=sendrecv" in pc2.localDescription.sdp) 1179 self.assertHasIceCandidates(pc2.localDescription) 1180 self.assertHasDtls(pc2.localDescription, "active") 1181 self.assertTrue("a=mid:sdparta_0" in pc2.localDescription.sdp) 1182 1183 # handle answer 1184 run(pc1.setRemoteDescription(pc2.localDescription)) 1185 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 1186 self.assertEqual(pc1.iceConnectionState, "checking") 1187 1188 # check outcome 1189 self.assertIceCompleted(pc1, pc2) 1190 1191 # close 1192 run(pc1.close()) 1193 run(pc2.close()) 1194 self.assertEqual(pc1.iceConnectionState, "closed") 1195 self.assertEqual(pc2.iceConnectionState, "closed") 1196 1197 # check state changes 1198 self.assertEqual( 1199 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 1200 ) 1201 self.assertEqual( 1202 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1203 ) 1204 self.assertEqual( 1205 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 1206 ) 1207 self.assertEqual( 1208 pc1_states["signalingState"], 1209 ["stable", "have-local-offer", "stable", "closed"], 1210 ) 1211 1212 self.assertEqual( 1213 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 1214 ) 1215 self.assertEqual( 1216 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1217 ) 1218 self.assertEqual( 1219 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 1220 ) 1221 self.assertEqual( 1222 pc2_states["signalingState"], 1223 ["stable", "have-remote-offer", "stable", "closed"], 1224 ) 1225 1226 def test_connect_audio_offer_recvonly_answer_recvonly(self): 1227 pc1 = RTCPeerConnection() 1228 pc1_states = track_states(pc1) 1229 1230 pc2 = RTCPeerConnection() 1231 pc2_states = track_states(pc2) 1232 1233 self.assertEqual(pc1.iceConnectionState, "new") 1234 self.assertEqual(pc1.iceGatheringState, "new") 1235 self.assertIsNone(pc1.localDescription) 1236 self.assertIsNone(pc1.remoteDescription) 1237 1238 self.assertEqual(pc2.iceConnectionState, "new") 1239 self.assertEqual(pc2.iceGatheringState, "new") 1240 self.assertIsNone(pc2.localDescription) 1241 self.assertIsNone(pc2.remoteDescription) 1242 1243 # create offer 1244 pc1.addTransceiver("audio", direction="recvonly") 1245 offer = run(pc1.createOffer()) 1246 self.assertEqual(offer.type, "offer") 1247 self.assertTrue("m=audio " in offer.sdp) 1248 self.assertFalse("a=candidate:" in offer.sdp) 1249 self.assertFalse("a=end-of-candidates" in offer.sdp) 1250 1251 run(pc1.setLocalDescription(offer)) 1252 self.assertEqual(pc1.iceConnectionState, "new") 1253 self.assertEqual(pc1.iceGatheringState, "complete") 1254 self.assertEqual(mids(pc1), ["0"]) 1255 self.assertTrue("m=audio " in pc1.localDescription.sdp) 1256 self.assertTrue("a=recvonly" in pc1.localDescription.sdp) 1257 self.assertHasIceCandidates(pc1.localDescription) 1258 self.assertHasDtls(pc1.localDescription, "actpass") 1259 1260 # handle offer 1261 run(pc2.setRemoteDescription(pc1.localDescription)) 1262 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 1263 self.assertEqual(len(pc2.getReceivers()), 1) 1264 self.assertEqual(len(pc2.getSenders()), 1) 1265 self.assertEqual(len(pc2.getTransceivers()), 1) 1266 self.assertEqual(mids(pc2), ["0"]) 1267 1268 # create answer 1269 answer = run(pc2.createAnswer()) 1270 self.assertEqual(answer.type, "answer") 1271 self.assertTrue("m=audio " in answer.sdp) 1272 self.assertFalse("a=candidate:" in answer.sdp) 1273 self.assertFalse("a=end-of-candidates" in answer.sdp) 1274 1275 run(pc2.setLocalDescription(answer)) 1276 self.assertEqual(pc2.iceConnectionState, "checking") 1277 self.assertEqual(pc2.iceGatheringState, "complete") 1278 self.assertEqual(mids(pc2), ["0"]) 1279 self.assertTrue("m=audio " in pc2.localDescription.sdp) 1280 self.assertTrue("a=inactive" in pc2.localDescription.sdp) 1281 self.assertHasIceCandidates(pc2.localDescription) 1282 self.assertHasDtls(pc2.localDescription, "active") 1283 self.assertEqual(pc2.getTransceivers()[0].currentDirection, "inactive") 1284 self.assertEqual(pc2.getTransceivers()[0].direction, "recvonly") 1285 1286 # handle answer 1287 run(pc1.setRemoteDescription(pc2.localDescription)) 1288 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 1289 self.assertEqual(pc1.iceConnectionState, "checking") 1290 self.assertEqual(pc1.getTransceivers()[0].currentDirection, "inactive") 1291 self.assertEqual(pc1.getTransceivers()[0].direction, "recvonly") 1292 1293 # check outcome 1294 self.assertIceCompleted(pc1, pc2) 1295 1296 # close 1297 run(pc1.close()) 1298 run(pc2.close()) 1299 self.assertEqual(pc1.iceConnectionState, "closed") 1300 self.assertEqual(pc2.iceConnectionState, "closed") 1301 1302 # check state changes 1303 self.assertEqual( 1304 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 1305 ) 1306 self.assertEqual( 1307 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1308 ) 1309 self.assertEqual( 1310 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 1311 ) 1312 self.assertEqual( 1313 pc1_states["signalingState"], 1314 ["stable", "have-local-offer", "stable", "closed"], 1315 ) 1316 1317 self.assertEqual( 1318 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 1319 ) 1320 self.assertEqual( 1321 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1322 ) 1323 self.assertEqual( 1324 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 1325 ) 1326 self.assertEqual( 1327 pc2_states["signalingState"], 1328 ["stable", "have-remote-offer", "stable", "closed"], 1329 ) 1330 1331 def test_connect_audio_offer_recvonly(self): 1332 pc1 = RTCPeerConnection() 1333 pc1_states = track_states(pc1) 1334 1335 pc2 = RTCPeerConnection() 1336 pc2_states = track_states(pc2) 1337 1338 self.assertEqual(pc1.iceConnectionState, "new") 1339 self.assertEqual(pc1.iceGatheringState, "new") 1340 self.assertIsNone(pc1.localDescription) 1341 self.assertIsNone(pc1.remoteDescription) 1342 1343 self.assertEqual(pc2.iceConnectionState, "new") 1344 self.assertEqual(pc2.iceGatheringState, "new") 1345 self.assertIsNone(pc2.localDescription) 1346 self.assertIsNone(pc2.remoteDescription) 1347 1348 # create offer 1349 pc1.addTransceiver("audio", direction="recvonly") 1350 offer = run(pc1.createOffer()) 1351 self.assertEqual(offer.type, "offer") 1352 self.assertTrue("m=audio " in offer.sdp) 1353 self.assertFalse("a=candidate:" in offer.sdp) 1354 self.assertFalse("a=end-of-candidates" in offer.sdp) 1355 1356 run(pc1.setLocalDescription(offer)) 1357 self.assertEqual(pc1.iceConnectionState, "new") 1358 self.assertEqual(pc1.iceGatheringState, "complete") 1359 self.assertEqual(mids(pc1), ["0"]) 1360 self.assertTrue("m=audio " in pc1.localDescription.sdp) 1361 self.assertTrue("a=recvonly" in pc1.localDescription.sdp) 1362 self.assertHasIceCandidates(pc1.localDescription) 1363 self.assertHasDtls(pc1.localDescription, "actpass") 1364 1365 # handle offer 1366 run(pc2.setRemoteDescription(pc1.localDescription)) 1367 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 1368 self.assertEqual(len(pc2.getReceivers()), 1) 1369 self.assertEqual(len(pc2.getSenders()), 1) 1370 self.assertEqual(len(pc2.getTransceivers()), 1) 1371 self.assertEqual(mids(pc2), ["0"]) 1372 1373 # create answer 1374 pc2.addTrack(AudioStreamTrack()) 1375 answer = run(pc2.createAnswer()) 1376 self.assertEqual(answer.type, "answer") 1377 self.assertTrue("m=audio " in answer.sdp) 1378 self.assertFalse("a=candidate:" in answer.sdp) 1379 self.assertFalse("a=end-of-candidates" in answer.sdp) 1380 1381 run(pc2.setLocalDescription(answer)) 1382 self.assertEqual(pc2.iceConnectionState, "checking") 1383 self.assertEqual(pc2.iceGatheringState, "complete") 1384 self.assertEqual(mids(pc2), ["0"]) 1385 self.assertTrue("m=audio " in pc2.localDescription.sdp) 1386 self.assertTrue("a=sendonly" in pc2.localDescription.sdp) 1387 self.assertHasIceCandidates(pc2.localDescription) 1388 self.assertHasDtls(pc2.localDescription, "active") 1389 self.assertEqual(pc2.getTransceivers()[0].currentDirection, "sendonly") 1390 self.assertEqual(pc2.getTransceivers()[0].direction, "sendrecv") 1391 1392 # handle answer 1393 run(pc1.setRemoteDescription(pc2.localDescription)) 1394 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 1395 self.assertEqual(pc1.iceConnectionState, "checking") 1396 self.assertEqual(pc1.getTransceivers()[0].currentDirection, "recvonly") 1397 self.assertEqual(pc1.getTransceivers()[0].direction, "recvonly") 1398 1399 # check outcome 1400 self.assertIceCompleted(pc1, pc2) 1401 1402 # close 1403 run(pc1.close()) 1404 run(pc2.close()) 1405 self.assertEqual(pc1.iceConnectionState, "closed") 1406 self.assertEqual(pc2.iceConnectionState, "closed") 1407 1408 # check state changes 1409 self.assertEqual( 1410 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 1411 ) 1412 self.assertEqual( 1413 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1414 ) 1415 self.assertEqual( 1416 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 1417 ) 1418 self.assertEqual( 1419 pc1_states["signalingState"], 1420 ["stable", "have-local-offer", "stable", "closed"], 1421 ) 1422 1423 self.assertEqual( 1424 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 1425 ) 1426 self.assertEqual( 1427 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1428 ) 1429 self.assertEqual( 1430 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 1431 ) 1432 self.assertEqual( 1433 pc2_states["signalingState"], 1434 ["stable", "have-remote-offer", "stable", "closed"], 1435 ) 1436 1437 def test_connect_audio_offer_sendonly(self): 1438 pc1 = RTCPeerConnection() 1439 pc1_states = track_states(pc1) 1440 1441 pc2 = RTCPeerConnection() 1442 pc2_states = track_states(pc2) 1443 1444 self.assertEqual(pc1.iceConnectionState, "new") 1445 self.assertEqual(pc1.iceGatheringState, "new") 1446 self.assertIsNone(pc1.localDescription) 1447 self.assertIsNone(pc1.remoteDescription) 1448 1449 self.assertEqual(pc2.iceConnectionState, "new") 1450 self.assertEqual(pc2.iceGatheringState, "new") 1451 self.assertIsNone(pc2.localDescription) 1452 self.assertIsNone(pc2.remoteDescription) 1453 1454 # create offer 1455 pc1.addTransceiver(AudioStreamTrack(), direction="sendonly") 1456 offer = run(pc1.createOffer()) 1457 self.assertEqual(offer.type, "offer") 1458 self.assertTrue("m=audio " in offer.sdp) 1459 self.assertFalse("a=candidate:" in offer.sdp) 1460 self.assertFalse("a=end-of-candidates" in offer.sdp) 1461 1462 run(pc1.setLocalDescription(offer)) 1463 self.assertEqual(pc1.iceConnectionState, "new") 1464 self.assertEqual(pc1.iceGatheringState, "complete") 1465 self.assertEqual(mids(pc1), ["0"]) 1466 self.assertTrue("m=audio " in pc1.localDescription.sdp) 1467 self.assertTrue("a=sendonly" in pc1.localDescription.sdp) 1468 self.assertHasIceCandidates(pc1.localDescription) 1469 self.assertHasDtls(pc1.localDescription, "actpass") 1470 1471 # handle offer 1472 run(pc2.setRemoteDescription(pc1.localDescription)) 1473 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 1474 self.assertEqual(len(pc2.getReceivers()), 1) 1475 self.assertEqual(len(pc2.getSenders()), 1) 1476 self.assertEqual(len(pc2.getTransceivers()), 1) 1477 self.assertEqual(mids(pc2), ["0"]) 1478 1479 # create answer 1480 answer = run(pc2.createAnswer()) 1481 self.assertEqual(answer.type, "answer") 1482 self.assertTrue("m=audio " in answer.sdp) 1483 self.assertFalse("a=candidate:" in answer.sdp) 1484 self.assertFalse("a=end-of-candidates" in answer.sdp) 1485 1486 run(pc2.setLocalDescription(answer)) 1487 self.assertEqual(pc2.iceConnectionState, "checking") 1488 self.assertEqual(pc2.iceGatheringState, "complete") 1489 self.assertEqual(mids(pc2), ["0"]) 1490 self.assertTrue("m=audio " in pc2.localDescription.sdp) 1491 self.assertTrue("a=recvonly" in pc2.localDescription.sdp) 1492 self.assertHasIceCandidates(pc2.localDescription) 1493 self.assertHasDtls(pc2.localDescription, "active") 1494 self.assertEqual(pc2.getTransceivers()[0].currentDirection, "recvonly") 1495 self.assertEqual(pc2.getTransceivers()[0].direction, "recvonly") 1496 1497 # handle answer 1498 run(pc1.setRemoteDescription(pc2.localDescription)) 1499 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 1500 self.assertEqual(pc1.iceConnectionState, "checking") 1501 self.assertEqual(pc1.getTransceivers()[0].currentDirection, "sendonly") 1502 self.assertEqual(pc1.getTransceivers()[0].direction, "sendonly") 1503 1504 # check outcome 1505 self.assertIceCompleted(pc1, pc2) 1506 1507 # close 1508 run(pc1.close()) 1509 run(pc2.close()) 1510 self.assertEqual(pc1.iceConnectionState, "closed") 1511 self.assertEqual(pc2.iceConnectionState, "closed") 1512 1513 # check state changes 1514 self.assertEqual( 1515 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 1516 ) 1517 self.assertEqual( 1518 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1519 ) 1520 self.assertEqual( 1521 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 1522 ) 1523 self.assertEqual( 1524 pc1_states["signalingState"], 1525 ["stable", "have-local-offer", "stable", "closed"], 1526 ) 1527 1528 self.assertEqual( 1529 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 1530 ) 1531 self.assertEqual( 1532 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1533 ) 1534 self.assertEqual( 1535 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 1536 ) 1537 self.assertEqual( 1538 pc2_states["signalingState"], 1539 ["stable", "have-remote-offer", "stable", "closed"], 1540 ) 1541 1542 def test_connect_audio_offer_sendrecv_answer_recvonly(self): 1543 pc1 = RTCPeerConnection() 1544 pc1_states = track_states(pc1) 1545 1546 pc2 = RTCPeerConnection() 1547 pc2_states = track_states(pc2) 1548 1549 self.assertEqual(pc1.iceConnectionState, "new") 1550 self.assertEqual(pc1.iceGatheringState, "new") 1551 self.assertIsNone(pc1.localDescription) 1552 self.assertIsNone(pc1.remoteDescription) 1553 1554 self.assertEqual(pc2.iceConnectionState, "new") 1555 self.assertEqual(pc2.iceGatheringState, "new") 1556 self.assertIsNone(pc2.localDescription) 1557 self.assertIsNone(pc2.remoteDescription) 1558 1559 # create offer 1560 pc1.addTrack(AudioStreamTrack()) 1561 offer = run(pc1.createOffer()) 1562 self.assertEqual(offer.type, "offer") 1563 self.assertTrue("m=audio " in offer.sdp) 1564 self.assertFalse("a=candidate:" in offer.sdp) 1565 self.assertFalse("a=end-of-candidates" in offer.sdp) 1566 1567 run(pc1.setLocalDescription(offer)) 1568 self.assertEqual(pc1.iceConnectionState, "new") 1569 self.assertEqual(pc1.iceGatheringState, "complete") 1570 self.assertTrue("m=audio " in pc1.localDescription.sdp) 1571 self.assertTrue("a=sendrecv" in pc1.localDescription.sdp) 1572 self.assertHasIceCandidates(pc1.localDescription) 1573 self.assertHasDtls(pc1.localDescription, "actpass") 1574 1575 # handle offer 1576 run(pc2.setRemoteDescription(pc1.localDescription)) 1577 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 1578 self.assertEqual(len(pc2.getReceivers()), 1) 1579 self.assertEqual(len(pc2.getSenders()), 1) 1580 self.assertEqual(len(pc2.getTransceivers()), 1) 1581 self.assertEqual(mids(pc2), ["0"]) 1582 1583 # create answer 1584 answer = run(pc2.createAnswer()) 1585 self.assertEqual(answer.type, "answer") 1586 self.assertTrue("m=audio " in answer.sdp) 1587 self.assertFalse("a=candidate:" in answer.sdp) 1588 self.assertFalse("a=end-of-candidates" in answer.sdp) 1589 1590 run(pc2.setLocalDescription(answer)) 1591 self.assertEqual(pc2.iceConnectionState, "checking") 1592 self.assertEqual(pc2.iceGatheringState, "complete") 1593 self.assertTrue("m=audio " in pc2.localDescription.sdp) 1594 self.assertTrue("a=recvonly" in pc2.localDescription.sdp) 1595 self.assertHasIceCandidates(pc2.localDescription) 1596 self.assertHasDtls(pc2.localDescription, "active") 1597 self.assertEqual(pc2.getTransceivers()[0].currentDirection, "recvonly") 1598 self.assertEqual(pc2.getTransceivers()[0].direction, "recvonly") 1599 1600 # handle answer 1601 run(pc1.setRemoteDescription(pc2.localDescription)) 1602 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 1603 self.assertEqual(pc1.iceConnectionState, "checking") 1604 self.assertEqual(pc1.getTransceivers()[0].currentDirection, "sendonly") 1605 self.assertEqual(pc1.getTransceivers()[0].direction, "sendrecv") 1606 1607 # check outcome 1608 self.assertIceCompleted(pc1, pc2) 1609 1610 # close 1611 run(pc1.close()) 1612 run(pc2.close()) 1613 self.assertEqual(pc1.iceConnectionState, "closed") 1614 self.assertEqual(pc2.iceConnectionState, "closed") 1615 1616 # check state changes 1617 self.assertEqual( 1618 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 1619 ) 1620 self.assertEqual( 1621 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1622 ) 1623 self.assertEqual( 1624 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 1625 ) 1626 self.assertEqual( 1627 pc1_states["signalingState"], 1628 ["stable", "have-local-offer", "stable", "closed"], 1629 ) 1630 1631 self.assertEqual( 1632 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 1633 ) 1634 self.assertEqual( 1635 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1636 ) 1637 self.assertEqual( 1638 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 1639 ) 1640 self.assertEqual( 1641 pc2_states["signalingState"], 1642 ["stable", "have-remote-offer", "stable", "closed"], 1643 ) 1644 1645 def test_connect_audio_offer_sendrecv_answer_sendonly(self): 1646 pc1 = RTCPeerConnection() 1647 pc1_states = track_states(pc1) 1648 1649 pc2 = RTCPeerConnection() 1650 pc2_states = track_states(pc2) 1651 1652 self.assertEqual(pc1.iceConnectionState, "new") 1653 self.assertEqual(pc1.iceGatheringState, "new") 1654 self.assertIsNone(pc1.localDescription) 1655 self.assertIsNone(pc1.remoteDescription) 1656 1657 self.assertEqual(pc2.iceConnectionState, "new") 1658 self.assertEqual(pc2.iceGatheringState, "new") 1659 self.assertIsNone(pc2.localDescription) 1660 self.assertIsNone(pc2.remoteDescription) 1661 1662 # create offer 1663 pc1.addTrack(AudioStreamTrack()) 1664 offer = run(pc1.createOffer()) 1665 self.assertEqual(offer.type, "offer") 1666 self.assertTrue("m=audio " in offer.sdp) 1667 self.assertFalse("a=candidate:" in offer.sdp) 1668 self.assertFalse("a=end-of-candidates" in offer.sdp) 1669 1670 run(pc1.setLocalDescription(offer)) 1671 self.assertEqual(pc1.iceConnectionState, "new") 1672 self.assertEqual(pc1.iceGatheringState, "complete") 1673 self.assertTrue("m=audio " in pc1.localDescription.sdp) 1674 self.assertTrue("a=sendrecv" in pc1.localDescription.sdp) 1675 self.assertHasIceCandidates(pc1.localDescription) 1676 self.assertHasDtls(pc1.localDescription, "actpass") 1677 1678 # handle offer 1679 run(pc2.setRemoteDescription(pc1.localDescription)) 1680 pc2.getTransceivers()[0].direction = "sendonly" 1681 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 1682 self.assertEqual(len(pc2.getReceivers()), 1) 1683 self.assertEqual(len(pc2.getSenders()), 1) 1684 self.assertEqual(len(pc2.getTransceivers()), 1) 1685 self.assertEqual(mids(pc2), ["0"]) 1686 1687 # create answer 1688 answer = run(pc2.createAnswer()) 1689 self.assertEqual(answer.type, "answer") 1690 self.assertTrue("m=audio " in answer.sdp) 1691 self.assertFalse("a=candidate:" in answer.sdp) 1692 self.assertFalse("a=end-of-candidates" in answer.sdp) 1693 1694 run(pc2.setLocalDescription(answer)) 1695 self.assertEqual(pc2.iceConnectionState, "checking") 1696 self.assertEqual(pc2.iceGatheringState, "complete") 1697 self.assertTrue("m=audio " in pc2.localDescription.sdp) 1698 self.assertTrue("a=sendonly" in pc2.localDescription.sdp) 1699 self.assertHasIceCandidates(pc2.localDescription) 1700 self.assertHasDtls(pc2.localDescription, "active") 1701 self.assertEqual(pc2.getTransceivers()[0].currentDirection, "sendonly") 1702 self.assertEqual(pc2.getTransceivers()[0].direction, "sendonly") 1703 1704 # handle answer 1705 run(pc1.setRemoteDescription(pc2.localDescription)) 1706 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 1707 self.assertEqual(pc1.iceConnectionState, "checking") 1708 self.assertEqual(pc1.getTransceivers()[0].currentDirection, "recvonly") 1709 self.assertEqual(pc1.getTransceivers()[0].direction, "sendrecv") 1710 1711 # check outcome 1712 self.assertIceCompleted(pc1, pc2) 1713 1714 # close 1715 run(pc1.close()) 1716 run(pc2.close()) 1717 self.assertEqual(pc1.iceConnectionState, "closed") 1718 self.assertEqual(pc2.iceConnectionState, "closed") 1719 1720 # check state changes 1721 self.assertEqual( 1722 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 1723 ) 1724 self.assertEqual( 1725 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1726 ) 1727 self.assertEqual( 1728 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 1729 ) 1730 self.assertEqual( 1731 pc1_states["signalingState"], 1732 ["stable", "have-local-offer", "stable", "closed"], 1733 ) 1734 1735 self.assertEqual( 1736 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 1737 ) 1738 self.assertEqual( 1739 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1740 ) 1741 self.assertEqual( 1742 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 1743 ) 1744 self.assertEqual( 1745 pc2_states["signalingState"], 1746 ["stable", "have-remote-offer", "stable", "closed"], 1747 ) 1748 1749 def test_connect_audio_two_tracks(self): 1750 pc1 = RTCPeerConnection() 1751 pc1_states = track_states(pc1) 1752 1753 pc2 = RTCPeerConnection() 1754 pc2_states = track_states(pc2) 1755 1756 self.assertEqual(pc1.iceConnectionState, "new") 1757 self.assertEqual(pc1.iceGatheringState, "new") 1758 self.assertIsNone(pc1.localDescription) 1759 self.assertIsNone(pc1.remoteDescription) 1760 1761 self.assertEqual(pc2.iceConnectionState, "new") 1762 self.assertEqual(pc2.iceGatheringState, "new") 1763 self.assertIsNone(pc2.localDescription) 1764 self.assertIsNone(pc2.remoteDescription) 1765 1766 # create offer 1767 pc1.addTrack(AudioStreamTrack()) 1768 pc1.addTrack(AudioStreamTrack()) 1769 offer = run(pc1.createOffer()) 1770 self.assertEqual(offer.type, "offer") 1771 self.assertTrue("m=audio " in offer.sdp) 1772 self.assertFalse("a=candidate:" in offer.sdp) 1773 self.assertFalse("a=end-of-candidates" in offer.sdp) 1774 1775 run(pc1.setLocalDescription(offer)) 1776 self.assertEqual(pc1.iceConnectionState, "new") 1777 self.assertEqual(pc1.iceGatheringState, "complete") 1778 self.assertEqual(mids(pc1), ["0", "1"]) 1779 self.assertTrue("m=audio " in pc1.localDescription.sdp) 1780 self.assertTrue("a=sendrecv" in pc1.localDescription.sdp) 1781 self.assertHasIceCandidates(pc1.localDescription) 1782 self.assertHasDtls(pc1.localDescription, "actpass") 1783 1784 # handle offer 1785 run(pc2.setRemoteDescription(pc1.localDescription)) 1786 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 1787 self.assertEqual(len(pc2.getReceivers()), 2) 1788 self.assertEqual(len(pc2.getSenders()), 2) 1789 self.assertEqual(len(pc2.getTransceivers()), 2) 1790 self.assertEqual(mids(pc2), ["0", "1"]) 1791 1792 # create answer 1793 pc2.addTrack(AudioStreamTrack()) 1794 answer = run(pc2.createAnswer()) 1795 self.assertEqual(answer.type, "answer") 1796 self.assertTrue("m=audio " in answer.sdp) 1797 self.assertFalse("a=candidate:" in answer.sdp) 1798 self.assertFalse("a=end-of-candidates" in answer.sdp) 1799 1800 run(pc2.setLocalDescription(answer)) 1801 self.assertEqual(pc2.iceConnectionState, "checking") 1802 self.assertEqual(pc2.iceGatheringState, "complete") 1803 self.assertEqual(mids(pc2), ["0", "1"]) 1804 self.assertTrue("m=audio " in pc2.localDescription.sdp) 1805 self.assertTrue("a=sendrecv" in pc2.localDescription.sdp) 1806 self.assertHasIceCandidates(pc2.localDescription) 1807 self.assertHasDtls(pc2.localDescription, "active") 1808 1809 # handle answer 1810 run(pc1.setRemoteDescription(pc2.localDescription)) 1811 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 1812 self.assertEqual(pc1.iceConnectionState, "checking") 1813 1814 # check outcome 1815 self.assertIceCompleted(pc1, pc2) 1816 1817 # close 1818 run(pc1.close()) 1819 run(pc2.close()) 1820 self.assertEqual(pc1.iceConnectionState, "closed") 1821 self.assertEqual(pc2.iceConnectionState, "closed") 1822 1823 # check state changes 1824 self.assertEqual( 1825 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 1826 ) 1827 self.assertEqual( 1828 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1829 ) 1830 self.assertEqual( 1831 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 1832 ) 1833 self.assertEqual( 1834 pc1_states["signalingState"], 1835 ["stable", "have-local-offer", "stable", "closed"], 1836 ) 1837 1838 self.assertEqual( 1839 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 1840 ) 1841 self.assertEqual( 1842 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1843 ) 1844 self.assertEqual( 1845 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 1846 ) 1847 self.assertEqual( 1848 pc2_states["signalingState"], 1849 ["stable", "have-remote-offer", "stable", "closed"], 1850 ) 1851 1852 def test_connect_audio_and_video(self): 1853 pc1 = RTCPeerConnection() 1854 pc1_states = track_states(pc1) 1855 1856 pc2 = RTCPeerConnection() 1857 pc2_states = track_states(pc2) 1858 1859 self.assertEqual(pc1.connectionState, "new") 1860 self.assertEqual(pc1.iceConnectionState, "new") 1861 self.assertEqual(pc1.iceGatheringState, "new") 1862 self.assertIsNone(pc1.localDescription) 1863 self.assertIsNone(pc1.remoteDescription) 1864 1865 self.assertEqual(pc2.connectionState, "new") 1866 self.assertEqual(pc2.iceConnectionState, "new") 1867 self.assertEqual(pc2.iceGatheringState, "new") 1868 self.assertIsNone(pc2.localDescription) 1869 self.assertIsNone(pc2.remoteDescription) 1870 1871 # create offer 1872 pc1.addTrack(AudioStreamTrack()) 1873 pc1.addTrack(VideoStreamTrack()) 1874 offer = run(pc1.createOffer()) 1875 self.assertEqual(offer.type, "offer") 1876 self.assertTrue("m=audio " in offer.sdp) 1877 self.assertTrue("m=video " in offer.sdp) 1878 1879 run(pc1.setLocalDescription(offer)) 1880 self.assertEqual(pc1.iceConnectionState, "new") 1881 self.assertEqual(pc1.iceGatheringState, "complete") 1882 self.assertEqual(mids(pc1), ["0", "1"]) 1883 1884 # handle offer 1885 run(pc2.setRemoteDescription(pc1.localDescription)) 1886 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 1887 self.assertEqual(len(pc2.getReceivers()), 2) 1888 self.assertEqual(len(pc2.getSenders()), 2) 1889 self.assertEqual(len(pc2.getTransceivers()), 2) 1890 self.assertEqual(mids(pc2), ["0", "1"]) 1891 1892 # create answer 1893 pc2.addTrack(AudioStreamTrack()) 1894 pc2.addTrack(VideoStreamTrack()) 1895 answer = run(pc2.createAnswer()) 1896 self.assertEqual(answer.type, "answer") 1897 self.assertTrue("m=audio " in answer.sdp) 1898 self.assertTrue("m=video " in answer.sdp) 1899 1900 run(pc2.setLocalDescription(answer)) 1901 self.assertEqual(pc2.iceConnectionState, "checking") 1902 self.assertEqual(pc2.iceGatheringState, "complete") 1903 self.assertTrue("m=audio " in pc2.localDescription.sdp) 1904 self.assertTrue("m=video " in pc2.localDescription.sdp) 1905 1906 # handle answer 1907 run(pc1.setRemoteDescription(pc2.localDescription)) 1908 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 1909 self.assertEqual(pc1.iceConnectionState, "checking") 1910 1911 # check outcome 1912 self.assertIceCompleted(pc1, pc2) 1913 1914 # check a single transport is used 1915 self.assertBundled(pc1) 1916 self.assertBundled(pc2) 1917 1918 # close 1919 run(pc1.close()) 1920 run(pc2.close()) 1921 self.assertEqual(pc1.iceConnectionState, "closed") 1922 self.assertEqual(pc2.iceConnectionState, "closed") 1923 1924 # check state changes 1925 self.assertEqual( 1926 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 1927 ) 1928 self.assertEqual( 1929 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1930 ) 1931 self.assertEqual( 1932 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 1933 ) 1934 self.assertEqual( 1935 pc1_states["signalingState"], 1936 ["stable", "have-local-offer", "stable", "closed"], 1937 ) 1938 1939 self.assertEqual( 1940 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 1941 ) 1942 self.assertEqual( 1943 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 1944 ) 1945 self.assertEqual( 1946 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 1947 ) 1948 self.assertEqual( 1949 pc2_states["signalingState"], 1950 ["stable", "have-remote-offer", "stable", "closed"], 1951 ) 1952 1953 def _test_connect_audio_and_video_mediaplayer(self, stop_tracks: bool): 1954 """ 1955 Negotiate bidirectional audio + video, with one party reading media from a file. 1956 1957 We can optionally stop the media tracks before closing the peer connections. 1958 """ 1959 media_test = MediaTestCase() 1960 media_test.setUp() 1961 media_path = media_test.create_audio_and_video_file(name="test.mp4", duration=5) 1962 player = MediaPlayer(media_path) 1963 1964 pc1 = RTCPeerConnection() 1965 pc1_states = track_states(pc1) 1966 1967 pc2 = RTCPeerConnection() 1968 pc2_states = track_states(pc2) 1969 1970 self.assertEqual(pc1.iceConnectionState, "new") 1971 self.assertEqual(pc1.iceGatheringState, "new") 1972 self.assertIsNone(pc1.localDescription) 1973 self.assertIsNone(pc1.remoteDescription) 1974 1975 self.assertEqual(pc2.iceConnectionState, "new") 1976 self.assertEqual(pc2.iceGatheringState, "new") 1977 self.assertIsNone(pc2.localDescription) 1978 self.assertIsNone(pc2.remoteDescription) 1979 1980 # create offer 1981 pc1.addTrack(player.audio) 1982 pc1.addTrack(player.video) 1983 offer = run(pc1.createOffer()) 1984 self.assertEqual(offer.type, "offer") 1985 self.assertTrue("m=audio " in offer.sdp) 1986 self.assertTrue("m=video " in offer.sdp) 1987 1988 run(pc1.setLocalDescription(offer)) 1989 self.assertEqual(pc1.iceConnectionState, "new") 1990 self.assertEqual(pc1.iceGatheringState, "complete") 1991 self.assertEqual(mids(pc1), ["0", "1"]) 1992 1993 # handle offer 1994 run(pc2.setRemoteDescription(pc1.localDescription)) 1995 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 1996 self.assertEqual(len(pc2.getReceivers()), 2) 1997 self.assertEqual(len(pc2.getSenders()), 2) 1998 self.assertEqual(len(pc2.getTransceivers()), 2) 1999 self.assertEqual(mids(pc2), ["0", "1"]) 2000 2001 # create answer 2002 pc2.addTrack(AudioStreamTrack()) 2003 pc2.addTrack(VideoStreamTrack()) 2004 answer = run(pc2.createAnswer()) 2005 self.assertEqual(answer.type, "answer") 2006 self.assertTrue("m=audio " in answer.sdp) 2007 self.assertTrue("m=video " in answer.sdp) 2008 2009 run(pc2.setLocalDescription(answer)) 2010 self.assertEqual(pc2.iceConnectionState, "checking") 2011 self.assertEqual(pc2.iceGatheringState, "complete") 2012 self.assertTrue("m=audio " in pc2.localDescription.sdp) 2013 self.assertTrue("m=video " in pc2.localDescription.sdp) 2014 2015 # handle answer 2016 run(pc1.setRemoteDescription(pc2.localDescription)) 2017 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 2018 self.assertEqual(pc1.iceConnectionState, "checking") 2019 2020 # check outcome 2021 self.assertIceCompleted(pc1, pc2) 2022 2023 # check a single transport is used 2024 self.assertBundled(pc1) 2025 self.assertBundled(pc2) 2026 2027 # let media flow 2028 run(asyncio.sleep(1)) 2029 2030 # stop tracks 2031 if stop_tracks: 2032 player.audio.stop() 2033 player.video.stop() 2034 2035 # close 2036 run(pc1.close()) 2037 run(pc2.close()) 2038 self.assertEqual(pc1.iceConnectionState, "closed") 2039 self.assertEqual(pc2.iceConnectionState, "closed") 2040 2041 # check state changes 2042 self.assertEqual( 2043 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 2044 ) 2045 self.assertEqual( 2046 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 2047 ) 2048 self.assertEqual( 2049 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 2050 ) 2051 self.assertEqual( 2052 pc1_states["signalingState"], 2053 ["stable", "have-local-offer", "stable", "closed"], 2054 ) 2055 2056 self.assertEqual( 2057 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 2058 ) 2059 self.assertEqual( 2060 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 2061 ) 2062 self.assertEqual( 2063 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 2064 ) 2065 self.assertEqual( 2066 pc2_states["signalingState"], 2067 ["stable", "have-remote-offer", "stable", "closed"], 2068 ) 2069 media_test.tearDown() 2070 2071 def test_connect_audio_and_video_mediaplayer(self): 2072 self._test_connect_audio_and_video_mediaplayer(stop_tracks=False) 2073 2074 def test_connect_audio_and_video_mediaplayer_stop_tracks(self): 2075 self._test_connect_audio_and_video_mediaplayer(stop_tracks=True) 2076 2077 def test_connect_audio_and_video_and_data_channel(self): 2078 pc1 = RTCPeerConnection() 2079 pc1_states = track_states(pc1) 2080 2081 pc2 = RTCPeerConnection() 2082 pc2_states = track_states(pc2) 2083 2084 self.assertEqual(pc1.iceConnectionState, "new") 2085 self.assertEqual(pc1.iceGatheringState, "new") 2086 self.assertIsNone(pc1.localDescription) 2087 self.assertIsNone(pc1.remoteDescription) 2088 2089 self.assertEqual(pc2.iceConnectionState, "new") 2090 self.assertEqual(pc2.iceGatheringState, "new") 2091 self.assertIsNone(pc2.localDescription) 2092 self.assertIsNone(pc2.remoteDescription) 2093 2094 # create offer 2095 pc1.addTrack(AudioStreamTrack()) 2096 pc1.addTrack(VideoStreamTrack()) 2097 pc1.createDataChannel("chat", protocol="bob") 2098 offer = run(pc1.createOffer()) 2099 self.assertEqual(offer.type, "offer") 2100 self.assertTrue("m=audio " in offer.sdp) 2101 self.assertTrue("m=video " in offer.sdp) 2102 self.assertTrue("m=application " in offer.sdp) 2103 2104 run(pc1.setLocalDescription(offer)) 2105 self.assertEqual(pc1.iceConnectionState, "new") 2106 self.assertEqual(pc1.iceGatheringState, "complete") 2107 self.assertEqual(mids(pc1), ["0", "1", "2"]) 2108 2109 # handle offer 2110 run(pc2.setRemoteDescription(pc1.localDescription)) 2111 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 2112 self.assertEqual(len(pc2.getReceivers()), 2) 2113 self.assertEqual(len(pc2.getSenders()), 2) 2114 self.assertEqual(len(pc2.getTransceivers()), 2) 2115 self.assertEqual(mids(pc2), ["0", "1", "2"]) 2116 2117 # create answer 2118 pc2.addTrack(AudioStreamTrack()) 2119 pc2.addTrack(VideoStreamTrack()) 2120 answer = run(pc2.createAnswer()) 2121 self.assertEqual(answer.type, "answer") 2122 self.assertTrue("m=audio " in answer.sdp) 2123 self.assertTrue("m=video " in answer.sdp) 2124 self.assertTrue("m=application " in answer.sdp) 2125 2126 run(pc2.setLocalDescription(answer)) 2127 self.assertEqual(pc2.iceConnectionState, "checking") 2128 self.assertEqual(pc2.iceGatheringState, "complete") 2129 self.assertTrue("m=audio " in pc2.localDescription.sdp) 2130 self.assertTrue("m=video " in pc2.localDescription.sdp) 2131 self.assertTrue("m=application " in pc2.localDescription.sdp) 2132 2133 # handle answer 2134 run(pc1.setRemoteDescription(pc2.localDescription)) 2135 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 2136 self.assertEqual(pc1.iceConnectionState, "checking") 2137 2138 # check outcome 2139 self.assertIceCompleted(pc1, pc2) 2140 2141 # check a single transport is used 2142 self.assertBundled(pc1) 2143 self.assertBundled(pc2) 2144 2145 # close 2146 run(pc1.close()) 2147 run(pc2.close()) 2148 self.assertEqual(pc1.iceConnectionState, "closed") 2149 self.assertEqual(pc2.iceConnectionState, "closed") 2150 2151 # check state changes 2152 self.assertEqual( 2153 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 2154 ) 2155 self.assertEqual( 2156 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 2157 ) 2158 self.assertEqual( 2159 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 2160 ) 2161 self.assertEqual( 2162 pc1_states["signalingState"], 2163 ["stable", "have-local-offer", "stable", "closed"], 2164 ) 2165 2166 self.assertEqual( 2167 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 2168 ) 2169 self.assertEqual( 2170 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 2171 ) 2172 self.assertEqual( 2173 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 2174 ) 2175 self.assertEqual( 2176 pc2_states["signalingState"], 2177 ["stable", "have-remote-offer", "stable", "closed"], 2178 ) 2179 2180 def test_connect_audio_and_video_and_data_channel_ice_fail(self): 2181 pc1 = RTCPeerConnection() 2182 pc1_states = track_states(pc1) 2183 2184 pc2 = RTCPeerConnection() 2185 pc2_states = track_states(pc2) 2186 2187 self.assertEqual(pc1.iceConnectionState, "new") 2188 self.assertEqual(pc1.iceGatheringState, "new") 2189 self.assertIsNone(pc1.localDescription) 2190 self.assertIsNone(pc1.remoteDescription) 2191 2192 self.assertEqual(pc2.iceConnectionState, "new") 2193 self.assertEqual(pc2.iceGatheringState, "new") 2194 self.assertIsNone(pc2.localDescription) 2195 self.assertIsNone(pc2.remoteDescription) 2196 2197 # create offer 2198 pc1.addTrack(AudioStreamTrack()) 2199 pc1.addTrack(VideoStreamTrack()) 2200 pc1.createDataChannel("chat", protocol="bob") 2201 offer = run(pc1.createOffer()) 2202 self.assertEqual(offer.type, "offer") 2203 self.assertTrue("m=audio " in offer.sdp) 2204 self.assertTrue("m=video " in offer.sdp) 2205 self.assertTrue("m=application " in offer.sdp) 2206 2207 run(pc1.setLocalDescription(offer)) 2208 self.assertEqual(pc1.iceConnectionState, "new") 2209 self.assertEqual(pc1.iceGatheringState, "complete") 2210 self.assertEqual(mids(pc1), ["0", "1", "2"]) 2211 2212 # close one side 2213 pc1_description = pc1.localDescription 2214 run(pc1.close()) 2215 2216 # handle offer 2217 run(pc2.setRemoteDescription(pc1_description)) 2218 self.assertEqual(pc2.remoteDescription, pc1_description) 2219 self.assertEqual(len(pc2.getReceivers()), 2) 2220 self.assertEqual(len(pc2.getSenders()), 2) 2221 self.assertEqual(len(pc2.getTransceivers()), 2) 2222 self.assertEqual(mids(pc2), ["0", "1", "2"]) 2223 2224 # create answer 2225 pc2.addTrack(AudioStreamTrack()) 2226 pc2.addTrack(VideoStreamTrack()) 2227 answer = run(pc2.createAnswer()) 2228 self.assertEqual(answer.type, "answer") 2229 self.assertTrue("m=audio " in answer.sdp) 2230 self.assertTrue("m=video " in answer.sdp) 2231 self.assertTrue("m=application " in answer.sdp) 2232 2233 run(pc2.setLocalDescription(answer)) 2234 self.assertEqual(pc2.iceConnectionState, "checking") 2235 self.assertEqual(pc2.iceGatheringState, "complete") 2236 self.assertTrue("m=audio " in pc2.localDescription.sdp) 2237 self.assertTrue("m=video " in pc2.localDescription.sdp) 2238 self.assertTrue("m=application " in pc2.localDescription.sdp) 2239 2240 # check outcome 2241 done = asyncio.Event() 2242 2243 @pc2.on("iceconnectionstatechange") 2244 def iceconnectionstatechange(): 2245 done.set() 2246 2247 run(done.wait()) 2248 self.assertEqual(pc1.iceConnectionState, "closed") 2249 self.assertEqual(pc2.iceConnectionState, "failed") 2250 2251 # close 2252 run(pc1.close()) 2253 run(pc2.close()) 2254 self.assertEqual(pc1.iceConnectionState, "closed") 2255 self.assertEqual(pc2.iceConnectionState, "closed") 2256 2257 # check state changes 2258 self.assertEqual(pc1_states["connectionState"], ["new", "closed"]) 2259 self.assertEqual(pc1_states["iceConnectionState"], ["new", "closed"]) 2260 self.assertEqual( 2261 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 2262 ) 2263 self.assertEqual( 2264 pc1_states["signalingState"], ["stable", "have-local-offer", "closed"] 2265 ) 2266 2267 self.assertEqual( 2268 pc2_states["connectionState"], ["new", "connecting", "failed", "closed"] 2269 ) 2270 self.assertEqual( 2271 pc2_states["iceConnectionState"], ["new", "checking", "failed", "closed"] 2272 ) 2273 self.assertEqual( 2274 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 2275 ) 2276 self.assertEqual( 2277 pc2_states["signalingState"], 2278 ["stable", "have-remote-offer", "stable", "closed"], 2279 ) 2280 2281 def test_connect_audio_then_video(self): 2282 pc1 = RTCPeerConnection() 2283 pc1_states = track_states(pc1) 2284 2285 pc2 = RTCPeerConnection() 2286 pc2_states = track_states(pc2) 2287 2288 self.assertEqual(pc1.iceConnectionState, "new") 2289 self.assertEqual(pc1.iceGatheringState, "new") 2290 self.assertIsNone(pc1.localDescription) 2291 self.assertIsNone(pc1.remoteDescription) 2292 2293 self.assertEqual(pc2.iceConnectionState, "new") 2294 self.assertEqual(pc2.iceGatheringState, "new") 2295 self.assertIsNone(pc2.localDescription) 2296 self.assertIsNone(pc2.remoteDescription) 2297 2298 # 1. AUDIO ONLY 2299 2300 # create offer 2301 pc1.addTrack(AudioStreamTrack()) 2302 offer = run(pc1.createOffer()) 2303 self.assertEqual(offer.type, "offer") 2304 self.assertTrue("m=audio " in offer.sdp) 2305 self.assertFalse("m=video " in offer.sdp) 2306 2307 run(pc1.setLocalDescription(offer)) 2308 self.assertEqual(pc1.iceConnectionState, "new") 2309 self.assertEqual(pc1.iceGatheringState, "complete") 2310 self.assertEqual(mids(pc1), ["0"]) 2311 2312 # handle offer 2313 run(pc2.setRemoteDescription(pc1.localDescription)) 2314 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 2315 self.assertEqual(len(pc2.getReceivers()), 1) 2316 self.assertEqual(len(pc2.getSenders()), 1) 2317 self.assertEqual(len(pc2.getTransceivers()), 1) 2318 self.assertEqual(mids(pc2), ["0"]) 2319 2320 # create answer 2321 pc2.addTrack(AudioStreamTrack()) 2322 answer = run(pc2.createAnswer()) 2323 self.assertEqual(answer.type, "answer") 2324 self.assertTrue("m=audio " in answer.sdp) 2325 self.assertFalse("m=video " in answer.sdp) 2326 2327 run(pc2.setLocalDescription(answer)) 2328 self.assertEqual(pc2.iceConnectionState, "checking") 2329 self.assertEqual(pc2.iceGatheringState, "complete") 2330 self.assertTrue("m=audio " in pc2.localDescription.sdp) 2331 self.assertFalse("m=video " in pc2.localDescription.sdp) 2332 2333 # handle answer 2334 run(pc1.setRemoteDescription(pc2.localDescription)) 2335 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 2336 self.assertEqual(pc1.iceConnectionState, "checking") 2337 2338 # check outcome 2339 self.assertIceCompleted(pc1, pc2) 2340 2341 # check a single transport is used 2342 self.assertBundled(pc1) 2343 self.assertBundled(pc2) 2344 2345 # 2. ADD VIDEO 2346 2347 # create offer 2348 pc1.addTrack(VideoStreamTrack()) 2349 offer = run(pc1.createOffer()) 2350 self.assertEqual(offer.type, "offer") 2351 self.assertTrue("m=audio " in offer.sdp) 2352 self.assertTrue("m=video " in offer.sdp) 2353 2354 run(pc1.setLocalDescription(offer)) 2355 self.assertEqual(pc1.iceConnectionState, "new") 2356 self.assertEqual(pc1.iceGatheringState, "complete") 2357 self.assertEqual(mids(pc1), ["0", "1"]) 2358 2359 # handle offer 2360 run(pc2.setRemoteDescription(pc1.localDescription)) 2361 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 2362 self.assertEqual(len(pc2.getReceivers()), 2) 2363 self.assertEqual(len(pc2.getSenders()), 2) 2364 self.assertEqual(len(pc2.getTransceivers()), 2) 2365 self.assertEqual(mids(pc2), ["0", "1"]) 2366 2367 # create answer 2368 pc2.addTrack(VideoStreamTrack()) 2369 answer = run(pc2.createAnswer()) 2370 self.assertEqual(answer.type, "answer") 2371 self.assertTrue("m=audio " in answer.sdp) 2372 self.assertTrue("m=video " in answer.sdp) 2373 2374 run(pc2.setLocalDescription(answer)) 2375 self.assertEqual(pc2.iceConnectionState, "completed") 2376 self.assertEqual(pc2.iceGatheringState, "complete") 2377 self.assertTrue("m=audio " in pc2.localDescription.sdp) 2378 self.assertTrue("m=video " in pc2.localDescription.sdp) 2379 2380 # handle answer 2381 run(pc1.setRemoteDescription(pc2.localDescription)) 2382 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 2383 self.assertEqual(pc1.iceConnectionState, "completed") 2384 2385 # check outcome 2386 self.assertIceCompleted(pc1, pc2) 2387 2388 # check a single transport is used 2389 self.assertBundled(pc1) 2390 self.assertBundled(pc2) 2391 2392 # close 2393 run(pc1.close()) 2394 run(pc2.close()) 2395 self.assertEqual(pc1.iceConnectionState, "closed") 2396 self.assertEqual(pc2.iceConnectionState, "closed") 2397 2398 # check state changes 2399 self.assertEqual( 2400 pc1_states["connectionState"], 2401 ["new", "connecting", "connected", "connecting", "connected", "closed"], 2402 ) 2403 self.assertEqual( 2404 pc1_states["iceConnectionState"], 2405 ["new", "checking", "completed", "new", "completed", "closed"], 2406 ) 2407 self.assertEqual( 2408 pc1_states["iceGatheringState"], 2409 ["new", "gathering", "complete", "new", "gathering", "complete"], 2410 ) 2411 self.assertEqual( 2412 pc1_states["signalingState"], 2413 [ 2414 "stable", 2415 "have-local-offer", 2416 "stable", 2417 "have-local-offer", 2418 "stable", 2419 "closed", 2420 ], 2421 ) 2422 2423 self.assertEqual( 2424 pc2_states["connectionState"], 2425 ["new", "connecting", "connected", "connecting", "connected", "closed"], 2426 ) 2427 self.assertEqual( 2428 pc2_states["iceConnectionState"], 2429 ["new", "checking", "completed", "new", "completed", "closed"], 2430 ) 2431 self.assertEqual( 2432 pc2_states["iceGatheringState"], 2433 ["new", "gathering", "complete", "new", "complete"], 2434 ) 2435 self.assertEqual( 2436 pc2_states["signalingState"], 2437 [ 2438 "stable", 2439 "have-remote-offer", 2440 "stable", 2441 "have-remote-offer", 2442 "stable", 2443 "closed", 2444 ], 2445 ) 2446 2447 def test_connect_video_bidirectional(self): 2448 pc1 = RTCPeerConnection() 2449 pc1_states = track_states(pc1) 2450 2451 pc2 = RTCPeerConnection() 2452 pc2_states = track_states(pc2) 2453 2454 self.assertEqual(pc1.iceConnectionState, "new") 2455 self.assertEqual(pc1.iceGatheringState, "new") 2456 self.assertIsNone(pc1.localDescription) 2457 self.assertIsNone(pc1.remoteDescription) 2458 2459 self.assertEqual(pc2.iceConnectionState, "new") 2460 self.assertEqual(pc2.iceGatheringState, "new") 2461 self.assertIsNone(pc2.localDescription) 2462 self.assertIsNone(pc2.remoteDescription) 2463 2464 # create offer 2465 pc1.addTrack(VideoStreamTrack()) 2466 offer = run(pc1.createOffer()) 2467 self.assertEqual(offer.type, "offer") 2468 self.assertTrue("m=video " in offer.sdp) 2469 self.assertFalse("a=candidate:" in offer.sdp) 2470 self.assertFalse("a=end-of-candidates" in offer.sdp) 2471 2472 run(pc1.setLocalDescription(offer)) 2473 self.assertEqual(pc1.iceConnectionState, "new") 2474 self.assertEqual(pc1.iceGatheringState, "complete") 2475 self.assertEqual(mids(pc1), ["0"]) 2476 self.assertTrue("m=video " in pc1.localDescription.sdp) 2477 self.assertTrue( 2478 lf2crlf( 2479 """a=rtpmap:97 VP8/90000 2480a=rtcp-fb:97 nack 2481a=rtcp-fb:97 nack pli 2482a=rtcp-fb:97 goog-remb 2483a=rtpmap:98 rtx/90000 2484a=fmtp:98 apt=97 2485a=rtpmap:99 H264/90000 2486a=rtcp-fb:99 nack 2487a=rtcp-fb:99 nack pli 2488a=rtcp-fb:99 goog-remb 2489a=fmtp:99 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42001f 2490a=rtpmap:100 rtx/90000 2491a=fmtp:100 apt=99 2492a=rtpmap:101 H264/90000 2493a=rtcp-fb:101 nack 2494a=rtcp-fb:101 nack pli 2495a=rtcp-fb:101 goog-remb 2496a=fmtp:101 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42e01f 2497a=rtpmap:102 rtx/90000 2498a=fmtp:102 apt=101 2499""" 2500 ) 2501 in pc1.localDescription.sdp 2502 ) 2503 self.assertTrue("a=sendrecv" in pc1.localDescription.sdp) 2504 self.assertHasIceCandidates(pc1.localDescription) 2505 self.assertHasDtls(pc1.localDescription, "actpass") 2506 2507 # handle offer 2508 run(pc2.setRemoteDescription(pc1.localDescription)) 2509 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 2510 self.assertEqual(len(pc2.getReceivers()), 1) 2511 self.assertEqual(len(pc2.getSenders()), 1) 2512 self.assertEqual(len(pc2.getTransceivers()), 1) 2513 self.assertEqual(mids(pc2), ["0"]) 2514 2515 # create answer 2516 pc2.addTrack(VideoStreamTrack()) 2517 answer = run(pc2.createAnswer()) 2518 self.assertEqual(answer.type, "answer") 2519 self.assertTrue("m=video " in answer.sdp) 2520 self.assertFalse("a=candidate:" in answer.sdp) 2521 self.assertFalse("a=end-of-candidates" in answer.sdp) 2522 2523 run(pc2.setLocalDescription(answer)) 2524 self.assertEqual(pc2.iceConnectionState, "checking") 2525 self.assertEqual(pc2.iceGatheringState, "complete") 2526 self.assertTrue("m=video " in pc2.localDescription.sdp) 2527 self.assertTrue( 2528 lf2crlf( 2529 """a=rtpmap:97 VP8/90000 2530a=rtcp-fb:97 nack 2531a=rtcp-fb:97 nack pli 2532a=rtcp-fb:97 goog-remb 2533a=rtpmap:98 rtx/90000 2534a=fmtp:98 apt=97 2535a=rtpmap:99 H264/90000 2536a=rtcp-fb:99 nack 2537a=rtcp-fb:99 nack pli 2538a=rtcp-fb:99 goog-remb 2539a=fmtp:99 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42001f 2540a=rtpmap:100 rtx/90000 2541a=fmtp:100 apt=99 2542a=rtpmap:101 H264/90000 2543a=rtcp-fb:101 nack 2544a=rtcp-fb:101 nack pli 2545a=rtcp-fb:101 goog-remb 2546a=fmtp:101 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42e01f 2547a=rtpmap:102 rtx/90000 2548a=fmtp:102 apt=101 2549""" 2550 ) 2551 in pc2.localDescription.sdp 2552 ) 2553 self.assertTrue("a=sendrecv" in pc2.localDescription.sdp) 2554 self.assertHasIceCandidates(pc2.localDescription) 2555 self.assertHasDtls(pc2.localDescription, "active") 2556 2557 # handle answer 2558 run(pc1.setRemoteDescription(pc2.localDescription)) 2559 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 2560 self.assertEqual(pc1.iceConnectionState, "checking") 2561 2562 # check outcome 2563 self.assertIceCompleted(pc1, pc2) 2564 2565 # let media flow to trigger RTCP feedback, including REMB 2566 run(asyncio.sleep(5)) 2567 2568 # check stats 2569 report = run(pc1.getStats()) 2570 self.assertTrue(isinstance(report, RTCStatsReport)) 2571 self.assertEqual( 2572 sorted([s.type for s in report.values()]), 2573 [ 2574 "inbound-rtp", 2575 "outbound-rtp", 2576 "remote-inbound-rtp", 2577 "remote-outbound-rtp", 2578 "transport", 2579 ], 2580 ) 2581 2582 # close 2583 run(pc1.close()) 2584 run(pc2.close()) 2585 self.assertEqual(pc1.iceConnectionState, "closed") 2586 self.assertEqual(pc2.iceConnectionState, "closed") 2587 2588 # check state changes 2589 self.assertEqual( 2590 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 2591 ) 2592 self.assertEqual( 2593 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 2594 ) 2595 self.assertEqual( 2596 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 2597 ) 2598 self.assertEqual( 2599 pc1_states["signalingState"], 2600 ["stable", "have-local-offer", "stable", "closed"], 2601 ) 2602 2603 self.assertEqual( 2604 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 2605 ) 2606 self.assertEqual( 2607 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 2608 ) 2609 self.assertEqual( 2610 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 2611 ) 2612 self.assertEqual( 2613 pc2_states["signalingState"], 2614 ["stable", "have-remote-offer", "stable", "closed"], 2615 ) 2616 2617 def test_connect_video_h264(self): 2618 pc1 = RTCPeerConnection() 2619 pc1_states = track_states(pc1) 2620 2621 pc2 = RTCPeerConnection() 2622 pc2_states = track_states(pc2) 2623 2624 self.assertEqual(pc1.iceConnectionState, "new") 2625 self.assertEqual(pc1.iceGatheringState, "new") 2626 self.assertIsNone(pc1.localDescription) 2627 self.assertIsNone(pc1.remoteDescription) 2628 2629 self.assertEqual(pc2.iceConnectionState, "new") 2630 self.assertEqual(pc2.iceGatheringState, "new") 2631 self.assertIsNone(pc2.localDescription) 2632 self.assertIsNone(pc2.remoteDescription) 2633 2634 # create offer 2635 pc1.addTrack(VideoStreamTrack()) 2636 offer = run(pc1.createOffer()) 2637 self.assertEqual(offer.type, "offer") 2638 self.assertTrue("m=video " in offer.sdp) 2639 self.assertFalse("a=candidate:" in offer.sdp) 2640 self.assertFalse("a=end-of-candidates" in offer.sdp) 2641 2642 run(pc1.setLocalDescription(offer)) 2643 self.assertEqual(pc1.iceConnectionState, "new") 2644 self.assertEqual(pc1.iceGatheringState, "complete") 2645 self.assertEqual(mids(pc1), ["0"]) 2646 self.assertTrue("m=video " in pc1.localDescription.sdp) 2647 self.assertTrue("a=sendrecv" in pc1.localDescription.sdp) 2648 self.assertHasIceCandidates(pc1.localDescription) 2649 self.assertHasDtls(pc1.localDescription, "actpass") 2650 2651 # strip out vp8 2652 parsed = SessionDescription.parse(pc1.localDescription.sdp) 2653 parsed.media[0].rtp.codecs.pop(0) 2654 parsed.media[0].fmt.pop(0) 2655 desc1 = RTCSessionDescription(sdp=str(parsed), type=pc1.localDescription.type) 2656 self.assertFalse("VP8" in desc1.sdp) 2657 self.assertTrue("H264" in desc1.sdp) 2658 2659 # handle offer 2660 run(pc2.setRemoteDescription(desc1)) 2661 self.assertEqual(pc2.remoteDescription, desc1) 2662 self.assertEqual(len(pc2.getReceivers()), 1) 2663 self.assertEqual(len(pc2.getSenders()), 1) 2664 self.assertEqual(len(pc2.getTransceivers()), 1) 2665 self.assertEqual(mids(pc2), ["0"]) 2666 2667 # create answer 2668 pc2.addTrack(VideoStreamTrack()) 2669 answer = run(pc2.createAnswer()) 2670 self.assertEqual(answer.type, "answer") 2671 self.assertTrue("m=video " in answer.sdp) 2672 self.assertFalse("a=candidate:" in answer.sdp) 2673 self.assertFalse("a=end-of-candidates" in answer.sdp) 2674 2675 run(pc2.setLocalDescription(answer)) 2676 self.assertEqual(pc2.iceConnectionState, "checking") 2677 self.assertEqual(pc2.iceGatheringState, "complete") 2678 self.assertTrue("m=video " in pc2.localDescription.sdp) 2679 self.assertTrue("a=sendrecv" in pc2.localDescription.sdp) 2680 self.assertHasIceCandidates(pc2.localDescription) 2681 self.assertHasDtls(pc2.localDescription, "active") 2682 2683 # handle answer 2684 run(pc1.setRemoteDescription(pc2.localDescription)) 2685 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 2686 self.assertEqual(pc1.iceConnectionState, "checking") 2687 2688 # check outcome 2689 self.assertIceCompleted(pc1, pc2) 2690 2691 # close 2692 run(pc1.close()) 2693 run(pc2.close()) 2694 self.assertEqual(pc1.iceConnectionState, "closed") 2695 self.assertEqual(pc2.iceConnectionState, "closed") 2696 2697 # check state changes 2698 self.assertEqual( 2699 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 2700 ) 2701 self.assertEqual( 2702 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 2703 ) 2704 self.assertEqual( 2705 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 2706 ) 2707 self.assertEqual( 2708 pc1_states["signalingState"], 2709 ["stable", "have-local-offer", "stable", "closed"], 2710 ) 2711 2712 self.assertEqual( 2713 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 2714 ) 2715 self.assertEqual( 2716 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 2717 ) 2718 self.assertEqual( 2719 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 2720 ) 2721 self.assertEqual( 2722 pc2_states["signalingState"], 2723 ["stable", "have-remote-offer", "stable", "closed"], 2724 ) 2725 2726 def test_connect_video_no_ssrc(self): 2727 pc1 = RTCPeerConnection() 2728 pc1_states = track_states(pc1) 2729 2730 pc2 = RTCPeerConnection() 2731 pc2_states = track_states(pc2) 2732 2733 self.assertEqual(pc1.iceConnectionState, "new") 2734 self.assertEqual(pc1.iceGatheringState, "new") 2735 self.assertIsNone(pc1.localDescription) 2736 self.assertIsNone(pc1.remoteDescription) 2737 2738 self.assertEqual(pc2.iceConnectionState, "new") 2739 self.assertEqual(pc2.iceGatheringState, "new") 2740 self.assertIsNone(pc2.localDescription) 2741 self.assertIsNone(pc2.remoteDescription) 2742 2743 # create offer 2744 pc1.addTrack(VideoStreamTrack()) 2745 offer = run(pc1.createOffer()) 2746 self.assertEqual(offer.type, "offer") 2747 self.assertTrue("m=video " in offer.sdp) 2748 self.assertFalse("a=candidate:" in offer.sdp) 2749 self.assertFalse("a=end-of-candidates" in offer.sdp) 2750 2751 run(pc1.setLocalDescription(offer)) 2752 self.assertEqual(pc1.iceConnectionState, "new") 2753 self.assertEqual(pc1.iceGatheringState, "complete") 2754 self.assertEqual(mids(pc1), ["0"]) 2755 self.assertTrue("m=video " in pc1.localDescription.sdp) 2756 self.assertTrue("a=sendrecv" in pc1.localDescription.sdp) 2757 self.assertHasIceCandidates(pc1.localDescription) 2758 self.assertHasDtls(pc1.localDescription, "actpass") 2759 2760 # strip out SSRC 2761 mangled = RTCSessionDescription( 2762 sdp=re.sub("^a=ssrc:.*\r\n", "", pc1.localDescription.sdp, flags=re.M), 2763 type=pc1.localDescription.type, 2764 ) 2765 2766 # handle offer 2767 run(pc2.setRemoteDescription(mangled)) 2768 self.assertEqual(pc2.remoteDescription, mangled) 2769 self.assertEqual(len(pc2.getReceivers()), 1) 2770 self.assertEqual(len(pc2.getSenders()), 1) 2771 self.assertEqual(len(pc2.getTransceivers()), 1) 2772 self.assertEqual(mids(pc2), ["0"]) 2773 2774 # create answer 2775 pc2.addTrack(VideoStreamTrack()) 2776 answer = run(pc2.createAnswer()) 2777 self.assertEqual(answer.type, "answer") 2778 self.assertTrue("m=video " in answer.sdp) 2779 self.assertFalse("a=candidate:" in answer.sdp) 2780 self.assertFalse("a=end-of-candidates" in answer.sdp) 2781 2782 run(pc2.setLocalDescription(answer)) 2783 self.assertEqual(pc2.iceConnectionState, "checking") 2784 self.assertEqual(pc2.iceGatheringState, "complete") 2785 self.assertTrue("m=video " in pc2.localDescription.sdp) 2786 self.assertTrue("a=sendrecv" in pc2.localDescription.sdp) 2787 self.assertHasIceCandidates(pc2.localDescription) 2788 self.assertHasDtls(pc2.localDescription, "active") 2789 2790 # handle answer 2791 run(pc1.setRemoteDescription(pc2.localDescription)) 2792 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 2793 self.assertEqual(pc1.iceConnectionState, "checking") 2794 2795 # check outcome 2796 self.assertIceCompleted(pc1, pc2) 2797 2798 # close 2799 run(pc1.close()) 2800 run(pc2.close()) 2801 self.assertEqual(pc1.iceConnectionState, "closed") 2802 self.assertEqual(pc2.iceConnectionState, "closed") 2803 2804 # check state changes 2805 self.assertEqual( 2806 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 2807 ) 2808 self.assertEqual( 2809 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 2810 ) 2811 self.assertEqual( 2812 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 2813 ) 2814 self.assertEqual( 2815 pc1_states["signalingState"], 2816 ["stable", "have-local-offer", "stable", "closed"], 2817 ) 2818 2819 self.assertEqual( 2820 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 2821 ) 2822 self.assertEqual( 2823 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 2824 ) 2825 self.assertEqual( 2826 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 2827 ) 2828 self.assertEqual( 2829 pc2_states["signalingState"], 2830 ["stable", "have-remote-offer", "stable", "closed"], 2831 ) 2832 2833 def test_connect_video_codec_preferences_offerer(self): 2834 pc1 = RTCPeerConnection() 2835 pc1_states = track_states(pc1) 2836 2837 pc2 = RTCPeerConnection() 2838 pc2_states = track_states(pc2) 2839 2840 self.assertEqual(pc1.iceConnectionState, "new") 2841 self.assertEqual(pc1.iceGatheringState, "new") 2842 self.assertIsNone(pc1.localDescription) 2843 self.assertIsNone(pc1.remoteDescription) 2844 2845 self.assertEqual(pc2.iceConnectionState, "new") 2846 self.assertEqual(pc2.iceGatheringState, "new") 2847 self.assertIsNone(pc2.localDescription) 2848 self.assertIsNone(pc2.remoteDescription) 2849 2850 # add track and set codec preferences to prefer H264 2851 pc1.addTrack(VideoStreamTrack()) 2852 capabilities = RTCRtpSender.getCapabilities("video") 2853 preferences = list(filter(lambda x: x.name == "H264", capabilities.codecs)) 2854 preferences += list(filter(lambda x: x.name == "VP8", capabilities.codecs)) 2855 preferences += list(filter(lambda x: x.name == "rtx", capabilities.codecs)) 2856 transceiver = pc1.getTransceivers()[0] 2857 transceiver.setCodecPreferences(preferences) 2858 2859 # create offer 2860 offer = run(pc1.createOffer()) 2861 self.assertEqual(offer.type, "offer") 2862 self.assertTrue("m=video " in offer.sdp) 2863 self.assertFalse("a=candidate:" in offer.sdp) 2864 self.assertFalse("a=end-of-candidates" in offer.sdp) 2865 2866 run(pc1.setLocalDescription(offer)) 2867 self.assertEqual(pc1.iceConnectionState, "new") 2868 self.assertEqual(pc1.iceGatheringState, "complete") 2869 self.assertEqual(mids(pc1), ["0"]) 2870 self.assertTrue("m=video " in pc1.localDescription.sdp) 2871 self.assertTrue("a=sendrecv" in pc1.localDescription.sdp) 2872 self.assertHasIceCandidates(pc1.localDescription) 2873 self.assertHasDtls(pc1.localDescription, "actpass") 2874 self.assertTrue( 2875 lf2crlf( 2876 """a=rtpmap:99 H264/90000 2877a=rtcp-fb:99 nack 2878a=rtcp-fb:99 nack pli 2879a=rtcp-fb:99 goog-remb 2880a=fmtp:99 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42001f 2881a=rtpmap:100 rtx/90000 2882a=fmtp:100 apt=99 2883a=rtpmap:101 H264/90000 2884a=rtcp-fb:101 nack 2885a=rtcp-fb:101 nack pli 2886a=rtcp-fb:101 goog-remb 2887a=fmtp:101 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42e01f 2888a=rtpmap:102 rtx/90000 2889a=fmtp:102 apt=101 2890a=rtpmap:97 VP8/90000 2891a=rtcp-fb:97 nack 2892a=rtcp-fb:97 nack pli 2893a=rtcp-fb:97 goog-remb 2894a=rtpmap:98 rtx/90000 2895a=fmtp:98 apt=97 2896""" 2897 ) 2898 in pc1.localDescription.sdp 2899 ) 2900 2901 # handle offer 2902 run(pc2.setRemoteDescription(pc1.localDescription)) 2903 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 2904 self.assertEqual(len(pc2.getReceivers()), 1) 2905 self.assertEqual(len(pc2.getSenders()), 1) 2906 self.assertEqual(len(pc2.getTransceivers()), 1) 2907 self.assertEqual(mids(pc2), ["0"]) 2908 2909 # create answer 2910 pc2.addTrack(VideoStreamTrack()) 2911 answer = run(pc2.createAnswer()) 2912 self.assertEqual(answer.type, "answer") 2913 self.assertTrue("m=video " in answer.sdp) 2914 self.assertFalse("a=candidate:" in answer.sdp) 2915 self.assertFalse("a=end-of-candidates" in answer.sdp) 2916 2917 run(pc2.setLocalDescription(answer)) 2918 self.assertEqual(pc2.iceConnectionState, "checking") 2919 self.assertEqual(pc2.iceGatheringState, "complete") 2920 self.assertTrue("m=video " in pc2.localDescription.sdp) 2921 self.assertTrue("a=sendrecv" in pc2.localDescription.sdp) 2922 self.assertHasIceCandidates(pc2.localDescription) 2923 self.assertHasDtls(pc2.localDescription, "active") 2924 self.assertTrue( 2925 lf2crlf( 2926 """a=rtpmap:99 H264/90000 2927a=rtcp-fb:99 nack 2928a=rtcp-fb:99 nack pli 2929a=rtcp-fb:99 goog-remb 2930a=fmtp:99 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42001f 2931a=rtpmap:100 rtx/90000 2932a=fmtp:100 apt=99 2933a=rtpmap:101 H264/90000 2934a=rtcp-fb:101 nack 2935a=rtcp-fb:101 nack pli 2936a=rtcp-fb:101 goog-remb 2937a=fmtp:101 packetization-mode=1;level-asymmetry-allowed=1;profile-level-id=42e01f 2938a=rtpmap:102 rtx/90000 2939a=fmtp:102 apt=101 2940a=rtpmap:97 VP8/90000 2941a=rtcp-fb:97 nack 2942a=rtcp-fb:97 nack pli 2943a=rtcp-fb:97 goog-remb 2944a=rtpmap:98 rtx/90000 2945a=fmtp:98 apt=97 2946""" 2947 ) 2948 in pc2.localDescription.sdp 2949 ) 2950 2951 # handle answer 2952 run(pc1.setRemoteDescription(pc2.localDescription)) 2953 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 2954 self.assertEqual(pc1.iceConnectionState, "checking") 2955 2956 # check outcome 2957 self.assertIceCompleted(pc1, pc2) 2958 2959 # close 2960 run(pc1.close()) 2961 run(pc2.close()) 2962 self.assertEqual(pc1.iceConnectionState, "closed") 2963 self.assertEqual(pc2.iceConnectionState, "closed") 2964 2965 # check state changes 2966 self.assertEqual( 2967 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 2968 ) 2969 self.assertEqual( 2970 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 2971 ) 2972 self.assertEqual( 2973 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 2974 ) 2975 self.assertEqual( 2976 pc1_states["signalingState"], 2977 ["stable", "have-local-offer", "stable", "closed"], 2978 ) 2979 2980 self.assertEqual( 2981 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 2982 ) 2983 self.assertEqual( 2984 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 2985 ) 2986 self.assertEqual( 2987 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 2988 ) 2989 self.assertEqual( 2990 pc2_states["signalingState"], 2991 ["stable", "have-remote-offer", "stable", "closed"], 2992 ) 2993 2994 def test_connect_video_codec_preferences_offerer_only_h264(self): 2995 pc1 = RTCPeerConnection() 2996 pc1_states = track_states(pc1) 2997 2998 pc2 = RTCPeerConnection() 2999 pc2_states = track_states(pc2) 3000 3001 self.assertEqual(pc1.iceConnectionState, "new") 3002 self.assertEqual(pc1.iceGatheringState, "new") 3003 self.assertIsNone(pc1.localDescription) 3004 self.assertIsNone(pc1.remoteDescription) 3005 3006 self.assertEqual(pc2.iceConnectionState, "new") 3007 self.assertEqual(pc2.iceGatheringState, "new") 3008 self.assertIsNone(pc2.localDescription) 3009 self.assertIsNone(pc2.remoteDescription) 3010 3011 # add track and set codec preferences to only allow H264 3012 pc1.addTrack(VideoStreamTrack()) 3013 capabilities = RTCRtpSender.getCapabilities("video") 3014 preferences = list(filter(lambda x: x.name == "H264", capabilities.codecs)) 3015 preferences += list(filter(lambda x: x.name == "rtx", capabilities.codecs)) 3016 transceiver = pc1.getTransceivers()[0] 3017 transceiver.setCodecPreferences(preferences) 3018 3019 # create offer 3020 offer = run(pc1.createOffer()) 3021 self.assertEqual(offer.type, "offer") 3022 self.assertTrue("m=video " in offer.sdp) 3023 self.assertFalse("a=candidate:" in offer.sdp) 3024 self.assertFalse("a=end-of-candidates" in offer.sdp) 3025 3026 run(pc1.setLocalDescription(offer)) 3027 self.assertEqual(pc1.iceConnectionState, "new") 3028 self.assertEqual(pc1.iceGatheringState, "complete") 3029 self.assertEqual(mids(pc1), ["0"]) 3030 self.assertTrue("m=video " in pc1.localDescription.sdp) 3031 self.assertTrue("a=sendrecv" in pc1.localDescription.sdp) 3032 self.assertHasIceCandidates(pc1.localDescription) 3033 self.assertHasDtls(pc1.localDescription, "actpass") 3034 self.assertFalse("VP8" in pc1.localDescription.sdp) 3035 3036 # handle offer 3037 run(pc2.setRemoteDescription(pc1.localDescription)) 3038 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 3039 self.assertEqual(len(pc2.getReceivers()), 1) 3040 self.assertEqual(len(pc2.getSenders()), 1) 3041 self.assertEqual(len(pc2.getTransceivers()), 1) 3042 self.assertEqual(mids(pc2), ["0"]) 3043 3044 # create answer 3045 pc2.addTrack(VideoStreamTrack()) 3046 answer = run(pc2.createAnswer()) 3047 self.assertEqual(answer.type, "answer") 3048 self.assertTrue("m=video " in answer.sdp) 3049 self.assertFalse("a=candidate:" in answer.sdp) 3050 self.assertFalse("a=end-of-candidates" in answer.sdp) 3051 3052 run(pc2.setLocalDescription(answer)) 3053 self.assertEqual(pc2.iceConnectionState, "checking") 3054 self.assertEqual(pc2.iceGatheringState, "complete") 3055 self.assertTrue("m=video " in pc2.localDescription.sdp) 3056 self.assertTrue("a=sendrecv" in pc2.localDescription.sdp) 3057 self.assertHasIceCandidates(pc2.localDescription) 3058 self.assertHasDtls(pc2.localDescription, "active") 3059 self.assertFalse("VP8" in pc2.localDescription.sdp) 3060 3061 # handle answer 3062 run(pc1.setRemoteDescription(pc2.localDescription)) 3063 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 3064 self.assertEqual(pc1.iceConnectionState, "checking") 3065 3066 # check outcome 3067 self.assertIceCompleted(pc1, pc2) 3068 3069 # close 3070 run(pc1.close()) 3071 run(pc2.close()) 3072 self.assertEqual(pc1.iceConnectionState, "closed") 3073 self.assertEqual(pc2.iceConnectionState, "closed") 3074 3075 # check state changes 3076 self.assertEqual( 3077 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 3078 ) 3079 self.assertEqual( 3080 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 3081 ) 3082 self.assertEqual( 3083 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 3084 ) 3085 self.assertEqual( 3086 pc1_states["signalingState"], 3087 ["stable", "have-local-offer", "stable", "closed"], 3088 ) 3089 3090 self.assertEqual( 3091 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 3092 ) 3093 self.assertEqual( 3094 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 3095 ) 3096 self.assertEqual( 3097 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 3098 ) 3099 self.assertEqual( 3100 pc2_states["signalingState"], 3101 ["stable", "have-remote-offer", "stable", "closed"], 3102 ) 3103 3104 def test_connect_datachannel_and_close_immediately(self): 3105 pc1 = RTCPeerConnection() 3106 pc2 = RTCPeerConnection() 3107 3108 # create two data channels 3109 dc1 = pc1.createDataChannel("chat1") 3110 self.assertEqual(dc1.readyState, "connecting") 3111 dc2 = pc1.createDataChannel("chat2") 3112 self.assertEqual(dc2.readyState, "connecting") 3113 3114 # close one data channel 3115 dc1.close() 3116 self.assertEqual(dc1.readyState, "closed") 3117 self.assertEqual(dc2.readyState, "connecting") 3118 3119 # perform SDP exchange 3120 run(pc1.setLocalDescription(run(pc1.createOffer()))) 3121 run(pc2.setRemoteDescription(pc1.localDescription)) 3122 run(pc2.setLocalDescription(run(pc2.createAnswer()))) 3123 run(pc1.setRemoteDescription(pc2.localDescription)) 3124 3125 # check outcome 3126 self.assertIceCompleted(pc1, pc2) 3127 self.assertEqual(dc1.readyState, "closed") 3128 self.assertDataChannelOpen(dc2) 3129 3130 # close 3131 run(pc1.close()) 3132 run(pc2.close()) 3133 self.assertEqual(pc1.iceConnectionState, "closed") 3134 self.assertEqual(pc2.iceConnectionState, "closed") 3135 3136 def test_connect_datachannel_negotiated_and_close_immediately(self): 3137 pc1 = RTCPeerConnection() 3138 pc2 = RTCPeerConnection() 3139 3140 # create two negotiated data channels 3141 dc1 = pc1.createDataChannel("chat1", negotiated=True, id=100) 3142 self.assertEqual(dc1.readyState, "connecting") 3143 dc2 = pc1.createDataChannel("chat2", negotiated=True, id=102) 3144 self.assertEqual(dc2.readyState, "connecting") 3145 3146 # close one data channel 3147 dc1.close() 3148 self.assertEqual(dc1.readyState, "closed") 3149 self.assertEqual(dc2.readyState, "connecting") 3150 3151 # perform SDP exchange 3152 run(pc1.setLocalDescription(run(pc1.createOffer()))) 3153 run(pc2.setRemoteDescription(pc1.localDescription)) 3154 run(pc2.setLocalDescription(run(pc2.createAnswer()))) 3155 run(pc1.setRemoteDescription(pc2.localDescription)) 3156 3157 # check outcome 3158 self.assertIceCompleted(pc1, pc2) 3159 self.assertEqual(dc1.readyState, "closed") 3160 self.assertDataChannelOpen(dc2) 3161 3162 # close 3163 run(pc1.close()) 3164 run(pc2.close()) 3165 self.assertEqual(pc1.iceConnectionState, "closed") 3166 self.assertEqual(pc2.iceConnectionState, "closed") 3167 3168 def test_connect_datachannel_legacy_sdp(self): 3169 pc1 = RTCPeerConnection() 3170 pc1._sctpLegacySdp = True 3171 pc1_data_messages = [] 3172 pc1_states = track_states(pc1) 3173 3174 pc2 = RTCPeerConnection() 3175 pc2_data_channels = [] 3176 pc2_data_messages = [] 3177 pc2_states = track_states(pc2) 3178 3179 @pc2.on("datachannel") 3180 def on_datachannel(channel): 3181 self.assertEqual(channel.readyState, "open") 3182 pc2_data_channels.append(channel) 3183 3184 @channel.on("message") 3185 def on_message(message): 3186 pc2_data_messages.append(message) 3187 if isinstance(message, str): 3188 channel.send("string-echo: " + message) 3189 else: 3190 channel.send(b"binary-echo: " + message) 3191 3192 # create data channel 3193 dc = pc1.createDataChannel("chat", protocol="bob") 3194 self.assertEqual(dc.label, "chat") 3195 self.assertEqual(dc.maxPacketLifeTime, None) 3196 self.assertEqual(dc.maxRetransmits, None) 3197 self.assertEqual(dc.ordered, True) 3198 self.assertEqual(dc.protocol, "bob") 3199 self.assertEqual(dc.readyState, "connecting") 3200 3201 # send messages 3202 @dc.on("open") 3203 def on_open(): 3204 dc.send("hello") 3205 dc.send("") 3206 dc.send(b"\x00\x01\x02\x03") 3207 dc.send(b"") 3208 dc.send(LONG_DATA) 3209 with self.assertRaises(ValueError) as cm: 3210 dc.send(1234) 3211 self.assertEqual( 3212 str(cm.exception), "Cannot send unsupported data type: <class 'int'>" 3213 ) 3214 self.assertEqual(dc.bufferedAmount, 2011) 3215 3216 @dc.on("message") 3217 def on_message(message): 3218 pc1_data_messages.append(message) 3219 3220 # create offer 3221 offer = run(pc1.createOffer()) 3222 self.assertEqual(offer.type, "offer") 3223 self.assertTrue("m=application " in offer.sdp) 3224 self.assertFalse("a=candidate:" in offer.sdp) 3225 self.assertFalse("a=end-of-candidates" in offer.sdp) 3226 3227 run(pc1.setLocalDescription(offer)) 3228 self.assertEqual(pc1.iceConnectionState, "new") 3229 self.assertEqual(pc1.iceGatheringState, "complete") 3230 self.assertEqual(mids(pc1), ["0"]) 3231 self.assertTrue("m=application " in pc1.localDescription.sdp) 3232 self.assertTrue( 3233 "a=sctpmap:5000 webrtc-datachannel 65535" in pc1.localDescription.sdp 3234 ) 3235 self.assertHasIceCandidates(pc1.localDescription) 3236 self.assertHasDtls(pc1.localDescription, "actpass") 3237 3238 # handle offer 3239 run(pc2.setRemoteDescription(pc1.localDescription)) 3240 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 3241 self.assertEqual(len(pc2.getReceivers()), 0) 3242 self.assertEqual(len(pc2.getSenders()), 0) 3243 self.assertEqual(len(pc2.getTransceivers()), 0) 3244 self.assertEqual(mids(pc2), ["0"]) 3245 3246 # create answer 3247 answer = run(pc2.createAnswer()) 3248 self.assertEqual(answer.type, "answer") 3249 self.assertTrue("m=application " in answer.sdp) 3250 self.assertFalse("a=candidate:" in answer.sdp) 3251 self.assertFalse("a=end-of-candidates" in answer.sdp) 3252 3253 run(pc2.setLocalDescription(answer)) 3254 self.assertEqual(pc2.iceConnectionState, "checking") 3255 self.assertEqual(pc2.iceGatheringState, "complete") 3256 self.assertTrue("m=application " in pc2.localDescription.sdp) 3257 self.assertTrue( 3258 "a=sctpmap:5000 webrtc-datachannel 65535" in pc2.localDescription.sdp 3259 ) 3260 self.assertHasIceCandidates(pc2.localDescription) 3261 self.assertHasDtls(pc2.localDescription, "active") 3262 3263 # handle answer 3264 run(pc1.setRemoteDescription(pc2.localDescription)) 3265 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 3266 self.assertEqual(pc1.iceConnectionState, "checking") 3267 3268 # check outcome 3269 self.assertIceCompleted(pc1, pc2) 3270 self.assertDataChannelOpen(dc) 3271 self.assertEqual(dc.bufferedAmount, 0) 3272 3273 # check pc2 got a datachannel 3274 self.assertEqual(len(pc2_data_channels), 1) 3275 self.assertEqual(pc2_data_channels[0].label, "chat") 3276 self.assertEqual(pc2_data_channels[0].maxPacketLifeTime, None) 3277 self.assertEqual(pc2_data_channels[0].maxRetransmits, None) 3278 self.assertEqual(pc2_data_channels[0].ordered, True) 3279 self.assertEqual(pc2_data_channels[0].protocol, "bob") 3280 3281 # check pc2 got messages 3282 run(asyncio.sleep(0.1)) 3283 self.assertEqual( 3284 pc2_data_messages, ["hello", "", b"\x00\x01\x02\x03", b"", LONG_DATA] 3285 ) 3286 3287 # check pc1 got replies 3288 self.assertEqual( 3289 pc1_data_messages, 3290 [ 3291 "string-echo: hello", 3292 "string-echo: ", 3293 b"binary-echo: \x00\x01\x02\x03", 3294 b"binary-echo: ", 3295 b"binary-echo: " + LONG_DATA, 3296 ], 3297 ) 3298 3299 # close data channel 3300 self.closeDataChannel(dc) 3301 3302 # close 3303 run(pc1.close()) 3304 run(pc2.close()) 3305 self.assertEqual(pc1.iceConnectionState, "closed") 3306 self.assertEqual(pc2.iceConnectionState, "closed") 3307 3308 # check state changes 3309 self.assertEqual( 3310 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 3311 ) 3312 self.assertEqual( 3313 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 3314 ) 3315 self.assertEqual( 3316 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 3317 ) 3318 self.assertEqual( 3319 pc1_states["signalingState"], 3320 ["stable", "have-local-offer", "stable", "closed"], 3321 ) 3322 3323 self.assertEqual( 3324 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 3325 ) 3326 self.assertEqual( 3327 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 3328 ) 3329 self.assertEqual( 3330 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 3331 ) 3332 self.assertEqual( 3333 pc2_states["signalingState"], 3334 ["stable", "have-remote-offer", "stable", "closed"], 3335 ) 3336 3337 def test_connect_datachannel_modern_sdp(self): 3338 pc1 = RTCPeerConnection() 3339 pc1._sctpLegacySdp = False 3340 pc1_data_messages = [] 3341 pc1_states = track_states(pc1) 3342 3343 pc2 = RTCPeerConnection() 3344 pc2_data_channels = [] 3345 pc2_data_messages = [] 3346 pc2_states = track_states(pc2) 3347 3348 @pc2.on("datachannel") 3349 def on_datachannel(channel): 3350 self.assertEqual(channel.readyState, "open") 3351 pc2_data_channels.append(channel) 3352 3353 @channel.on("message") 3354 def on_message(message): 3355 pc2_data_messages.append(message) 3356 if isinstance(message, str): 3357 channel.send("string-echo: " + message) 3358 else: 3359 channel.send(b"binary-echo: " + message) 3360 3361 # create data channel 3362 dc = pc1.createDataChannel("chat", protocol="bob") 3363 self.assertEqual(dc.label, "chat") 3364 self.assertEqual(dc.maxPacketLifeTime, None) 3365 self.assertEqual(dc.maxRetransmits, None) 3366 self.assertEqual(dc.ordered, True) 3367 self.assertEqual(dc.protocol, "bob") 3368 self.assertEqual(dc.readyState, "connecting") 3369 3370 # send messages 3371 @dc.on("open") 3372 def on_open(): 3373 dc.send("hello") 3374 dc.send("") 3375 dc.send(b"\x00\x01\x02\x03") 3376 dc.send(b"") 3377 dc.send(LONG_DATA) 3378 with self.assertRaises(ValueError) as cm: 3379 dc.send(1234) 3380 self.assertEqual( 3381 str(cm.exception), "Cannot send unsupported data type: <class 'int'>" 3382 ) 3383 3384 @dc.on("message") 3385 def on_message(message): 3386 pc1_data_messages.append(message) 3387 3388 # create offer 3389 offer = run(pc1.createOffer()) 3390 self.assertEqual(offer.type, "offer") 3391 self.assertTrue("m=application " in offer.sdp) 3392 self.assertFalse("a=candidate:" in offer.sdp) 3393 self.assertFalse("a=end-of-candidates" in offer.sdp) 3394 3395 run(pc1.setLocalDescription(offer)) 3396 self.assertEqual(pc1.iceConnectionState, "new") 3397 self.assertEqual(pc1.iceGatheringState, "complete") 3398 self.assertEqual(mids(pc1), ["0"]) 3399 self.assertTrue("m=application " in pc1.localDescription.sdp) 3400 self.assertTrue("a=sctp-port:5000" in pc1.localDescription.sdp) 3401 self.assertHasIceCandidates(pc1.localDescription) 3402 self.assertHasDtls(pc1.localDescription, "actpass") 3403 3404 # handle offer 3405 run(pc2.setRemoteDescription(pc1.localDescription)) 3406 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 3407 self.assertEqual(len(pc2.getReceivers()), 0) 3408 self.assertEqual(len(pc2.getSenders()), 0) 3409 self.assertEqual(len(pc2.getTransceivers()), 0) 3410 self.assertEqual(mids(pc2), ["0"]) 3411 3412 # create answer 3413 answer = run(pc2.createAnswer()) 3414 self.assertEqual(answer.type, "answer") 3415 self.assertTrue("m=application " in answer.sdp) 3416 self.assertFalse("a=candidate:" in answer.sdp) 3417 self.assertFalse("a=end-of-candidates" in answer.sdp) 3418 3419 run(pc2.setLocalDescription(answer)) 3420 self.assertEqual(pc2.iceConnectionState, "checking") 3421 self.assertEqual(pc2.iceGatheringState, "complete") 3422 self.assertTrue("m=application " in pc2.localDescription.sdp) 3423 self.assertTrue("a=sctp-port:5000" in pc2.localDescription.sdp) 3424 self.assertHasIceCandidates(pc2.localDescription) 3425 self.assertHasDtls(pc2.localDescription, "active") 3426 3427 # handle answer 3428 run(pc1.setRemoteDescription(pc2.localDescription)) 3429 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 3430 self.assertEqual(pc1.iceConnectionState, "checking") 3431 3432 # check outcome 3433 self.assertIceCompleted(pc1, pc2) 3434 self.assertDataChannelOpen(dc) 3435 3436 # check pc2 got a datachannel 3437 self.assertEqual(len(pc2_data_channels), 1) 3438 self.assertEqual(pc2_data_channels[0].label, "chat") 3439 self.assertEqual(pc2_data_channels[0].maxPacketLifeTime, None) 3440 self.assertEqual(pc2_data_channels[0].maxRetransmits, None) 3441 self.assertEqual(pc2_data_channels[0].ordered, True) 3442 self.assertEqual(pc2_data_channels[0].protocol, "bob") 3443 3444 # check pc2 got messages 3445 run(asyncio.sleep(0.1)) 3446 self.assertEqual( 3447 pc2_data_messages, ["hello", "", b"\x00\x01\x02\x03", b"", LONG_DATA] 3448 ) 3449 3450 # check pc1 got replies 3451 self.assertEqual( 3452 pc1_data_messages, 3453 [ 3454 "string-echo: hello", 3455 "string-echo: ", 3456 b"binary-echo: \x00\x01\x02\x03", 3457 b"binary-echo: ", 3458 b"binary-echo: " + LONG_DATA, 3459 ], 3460 ) 3461 3462 # close data channel 3463 self.closeDataChannel(dc) 3464 3465 # close 3466 run(pc1.close()) 3467 run(pc2.close()) 3468 self.assertEqual(pc1.iceConnectionState, "closed") 3469 self.assertEqual(pc2.iceConnectionState, "closed") 3470 3471 # check state changes 3472 self.assertEqual( 3473 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 3474 ) 3475 self.assertEqual( 3476 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 3477 ) 3478 self.assertEqual( 3479 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 3480 ) 3481 self.assertEqual( 3482 pc1_states["signalingState"], 3483 ["stable", "have-local-offer", "stable", "closed"], 3484 ) 3485 3486 self.assertEqual( 3487 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 3488 ) 3489 self.assertEqual( 3490 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 3491 ) 3492 self.assertEqual( 3493 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 3494 ) 3495 self.assertEqual( 3496 pc2_states["signalingState"], 3497 ["stable", "have-remote-offer", "stable", "closed"], 3498 ) 3499 3500 def test_connect_datachannel_modern_sdp_negotiated(self): 3501 pc1 = RTCPeerConnection() 3502 pc1._sctpLegacySdp = False 3503 pc1_data_messages = [] 3504 pc1_states = track_states(pc1) 3505 3506 pc2 = RTCPeerConnection() 3507 pc2_data_messages = [] 3508 pc2_states = track_states(pc2) 3509 3510 # create data channels 3511 dc1 = pc1.createDataChannel("chat", protocol="bob", negotiated=True, id=100) 3512 self.assertEqual(dc1.id, 100) 3513 self.assertEqual(dc1.label, "chat") 3514 self.assertEqual(dc1.maxPacketLifeTime, None) 3515 self.assertEqual(dc1.maxRetransmits, None) 3516 self.assertEqual(dc1.ordered, True) 3517 self.assertEqual(dc1.protocol, "bob") 3518 self.assertEqual(dc1.readyState, "connecting") 3519 3520 dc2 = pc2.createDataChannel("chat", protocol="bob", negotiated=True, id=100) 3521 self.assertEqual(dc2.id, 100) 3522 self.assertEqual(dc2.label, "chat") 3523 self.assertEqual(dc2.maxPacketLifeTime, None) 3524 self.assertEqual(dc2.maxRetransmits, None) 3525 self.assertEqual(dc2.ordered, True) 3526 self.assertEqual(dc2.protocol, "bob") 3527 self.assertEqual(dc2.readyState, "connecting") 3528 3529 @dc1.on("message") 3530 def on_message1(message): 3531 pc1_data_messages.append(message) 3532 3533 @dc2.on("message") 3534 def on_message2(message): 3535 pc2_data_messages.append(message) 3536 if isinstance(message, str): 3537 dc2.send("string-echo: " + message) 3538 else: 3539 dc2.send(b"binary-echo: " + message) 3540 3541 # create offer 3542 offer = run(pc1.createOffer()) 3543 self.assertEqual(offer.type, "offer") 3544 self.assertTrue("m=application " in offer.sdp) 3545 self.assertFalse("a=candidate:" in offer.sdp) 3546 self.assertFalse("a=end-of-candidates" in offer.sdp) 3547 3548 run(pc1.setLocalDescription(offer)) 3549 self.assertEqual(pc1.iceConnectionState, "new") 3550 self.assertEqual(pc1.iceGatheringState, "complete") 3551 self.assertEqual(mids(pc1), ["0"]) 3552 self.assertTrue("m=application " in pc1.localDescription.sdp) 3553 self.assertTrue("a=sctp-port:5000" in pc1.localDescription.sdp) 3554 self.assertHasIceCandidates(pc1.localDescription) 3555 self.assertHasDtls(pc1.localDescription, "actpass") 3556 3557 # handle offer 3558 run(pc2.setRemoteDescription(pc1.localDescription)) 3559 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 3560 self.assertEqual(len(pc2.getReceivers()), 0) 3561 self.assertEqual(len(pc2.getSenders()), 0) 3562 self.assertEqual(len(pc2.getTransceivers()), 0) 3563 self.assertEqual(mids(pc2), ["0"]) 3564 3565 # create answer 3566 answer = run(pc2.createAnswer()) 3567 self.assertEqual(answer.type, "answer") 3568 self.assertTrue("m=application " in answer.sdp) 3569 self.assertFalse("a=candidate:" in answer.sdp) 3570 self.assertFalse("a=end-of-candidates" in answer.sdp) 3571 3572 run(pc2.setLocalDescription(answer)) 3573 self.assertEqual(pc2.iceConnectionState, "checking") 3574 self.assertEqual(pc2.iceGatheringState, "complete") 3575 self.assertTrue("m=application " in pc2.localDescription.sdp) 3576 self.assertTrue("a=sctp-port:5000" in pc2.localDescription.sdp) 3577 self.assertHasIceCandidates(pc2.localDescription) 3578 self.assertHasDtls(pc2.localDescription, "active") 3579 3580 # handle answer 3581 run(pc1.setRemoteDescription(pc2.localDescription)) 3582 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 3583 self.assertEqual(pc1.iceConnectionState, "checking") 3584 3585 # check outcome 3586 self.assertIceCompleted(pc1, pc2) 3587 self.assertDataChannelOpen(dc1) 3588 self.assertDataChannelOpen(dc2) 3589 3590 # send message 3591 dc1.send("hello") 3592 dc1.send("") 3593 dc1.send(b"\x00\x01\x02\x03") 3594 dc1.send(b"") 3595 dc1.send(LONG_DATA) 3596 with self.assertRaises(ValueError) as cm: 3597 dc1.send(1234) 3598 self.assertEqual( 3599 str(cm.exception), "Cannot send unsupported data type: <class 'int'>" 3600 ) 3601 3602 # check pc2 got messages 3603 run(asyncio.sleep(0.1)) 3604 self.assertEqual( 3605 pc2_data_messages, ["hello", "", b"\x00\x01\x02\x03", b"", LONG_DATA] 3606 ) 3607 3608 # check pc1 got replies 3609 self.assertEqual( 3610 pc1_data_messages, 3611 [ 3612 "string-echo: hello", 3613 "string-echo: ", 3614 b"binary-echo: \x00\x01\x02\x03", 3615 b"binary-echo: ", 3616 b"binary-echo: " + LONG_DATA, 3617 ], 3618 ) 3619 3620 # close data channels 3621 self.closeDataChannel(dc1) 3622 3623 # close 3624 run(pc1.close()) 3625 run(pc2.close()) 3626 self.assertEqual(pc1.iceConnectionState, "closed") 3627 self.assertEqual(pc2.iceConnectionState, "closed") 3628 3629 # check state changes 3630 self.assertEqual( 3631 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 3632 ) 3633 self.assertEqual( 3634 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 3635 ) 3636 self.assertEqual( 3637 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 3638 ) 3639 self.assertEqual( 3640 pc1_states["signalingState"], 3641 ["stable", "have-local-offer", "stable", "closed"], 3642 ) 3643 3644 self.assertEqual( 3645 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 3646 ) 3647 self.assertEqual( 3648 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 3649 ) 3650 self.assertEqual( 3651 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 3652 ) 3653 self.assertEqual( 3654 pc2_states["signalingState"], 3655 ["stable", "have-remote-offer", "stable", "closed"], 3656 ) 3657 3658 def test_connect_datachannel_recycle_stream_id(self): 3659 pc1 = RTCPeerConnection() 3660 pc2 = RTCPeerConnection() 3661 3662 # create three data channels 3663 dc1 = pc1.createDataChannel("chat1") 3664 self.assertEqual(dc1.readyState, "connecting") 3665 dc2 = pc1.createDataChannel("chat2") 3666 self.assertEqual(dc2.readyState, "connecting") 3667 dc3 = pc1.createDataChannel("chat3") 3668 self.assertEqual(dc3.readyState, "connecting") 3669 3670 # perform SDP exchange 3671 run(pc1.setLocalDescription(run(pc1.createOffer()))) 3672 run(pc2.setRemoteDescription(pc1.localDescription)) 3673 run(pc2.setLocalDescription(run(pc2.createAnswer()))) 3674 run(pc1.setRemoteDescription(pc2.localDescription)) 3675 3676 # check outcome 3677 self.assertIceCompleted(pc1, pc2) 3678 self.assertDataChannelOpen(dc1) 3679 self.assertEqual(dc1.id, 1) 3680 self.assertDataChannelOpen(dc2) 3681 self.assertEqual(dc2.id, 3) 3682 self.assertDataChannelOpen(dc3) 3683 self.assertEqual(dc3.id, 5) 3684 3685 # close one data channel 3686 self.closeDataChannel(dc2) 3687 3688 # create a new data channel 3689 dc4 = pc1.createDataChannel("chat4") 3690 self.assertDataChannelOpen(dc4) 3691 self.assertEqual(dc4.id, 3) 3692 3693 # close 3694 run(pc1.close()) 3695 run(pc2.close()) 3696 self.assertEqual(pc1.iceConnectionState, "closed") 3697 self.assertEqual(pc2.iceConnectionState, "closed") 3698 3699 def test_create_datachannel_with_maxpacketlifetime_and_maxretransmits(self): 3700 pc = RTCPeerConnection() 3701 with self.assertRaises(ValueError) as cm: 3702 pc.createDataChannel("chat", maxPacketLifeTime=500, maxRetransmits=0) 3703 self.assertEqual( 3704 str(cm.exception), 3705 "Cannot specify both maxPacketLifeTime and maxRetransmits", 3706 ) 3707 3708 def test_datachannel_bufferedamountlowthreshold(self): 3709 pc = RTCPeerConnection() 3710 dc = pc.createDataChannel("chat") 3711 self.assertEqual(dc.bufferedAmountLowThreshold, 0) 3712 3713 dc.bufferedAmountLowThreshold = 4294967295 3714 self.assertEqual(dc.bufferedAmountLowThreshold, 4294967295) 3715 3716 dc.bufferedAmountLowThreshold = 16384 3717 self.assertEqual(dc.bufferedAmountLowThreshold, 16384) 3718 3719 dc.bufferedAmountLowThreshold = 0 3720 self.assertEqual(dc.bufferedAmountLowThreshold, 0) 3721 3722 with self.assertRaises(ValueError): 3723 dc.bufferedAmountLowThreshold = -1 3724 self.assertEqual(dc.bufferedAmountLowThreshold, 0) 3725 3726 with self.assertRaises(ValueError): 3727 dc.bufferedAmountLowThreshold = 4294967296 3728 self.assertEqual(dc.bufferedAmountLowThreshold, 0) 3729 3730 def test_datachannel_send_invalid_state(self): 3731 pc = RTCPeerConnection() 3732 dc = pc.createDataChannel("chat") 3733 with self.assertRaises(InvalidStateError): 3734 dc.send("hello") 3735 3736 def test_connect_datachannel_then_audio(self): 3737 pc1 = RTCPeerConnection() 3738 pc1_data_messages = [] 3739 pc1_states = track_states(pc1) 3740 3741 pc2 = RTCPeerConnection() 3742 pc2_data_channels = [] 3743 pc2_data_messages = [] 3744 pc2_states = track_states(pc2) 3745 3746 @pc2.on("datachannel") 3747 def on_datachannel(channel): 3748 self.assertEqual(channel.readyState, "open") 3749 pc2_data_channels.append(channel) 3750 3751 @channel.on("message") 3752 def on_message(message): 3753 pc2_data_messages.append(message) 3754 if isinstance(message, str): 3755 channel.send("string-echo: " + message) 3756 else: 3757 channel.send(b"binary-echo: " + message) 3758 3759 # create data channel 3760 dc = pc1.createDataChannel("chat", protocol="bob") 3761 self.assertEqual(dc.label, "chat") 3762 self.assertEqual(dc.maxPacketLifeTime, None) 3763 self.assertEqual(dc.maxRetransmits, None) 3764 self.assertEqual(dc.ordered, True) 3765 self.assertEqual(dc.protocol, "bob") 3766 self.assertEqual(dc.readyState, "connecting") 3767 3768 # send messages 3769 @dc.on("open") 3770 def on_open(): 3771 dc.send("hello") 3772 dc.send("") 3773 dc.send(b"\x00\x01\x02\x03") 3774 dc.send(b"") 3775 dc.send(LONG_DATA) 3776 with self.assertRaises(ValueError) as cm: 3777 dc.send(1234) 3778 self.assertEqual( 3779 str(cm.exception), "Cannot send unsupported data type: <class 'int'>" 3780 ) 3781 3782 @dc.on("message") 3783 def on_message(message): 3784 pc1_data_messages.append(message) 3785 3786 # 1. DATA CHANNEL ONLY 3787 3788 # create offer 3789 offer = run(pc1.createOffer()) 3790 self.assertEqual(offer.type, "offer") 3791 self.assertTrue("m=application " in offer.sdp) 3792 self.assertFalse("a=candidate:" in offer.sdp) 3793 self.assertFalse("a=end-of-candidates" in offer.sdp) 3794 3795 run(pc1.setLocalDescription(offer)) 3796 self.assertEqual(pc1.iceConnectionState, "new") 3797 self.assertEqual(pc1.iceGatheringState, "complete") 3798 self.assertEqual(mids(pc1), ["0"]) 3799 self.assertTrue("m=application " in pc1.localDescription.sdp) 3800 self.assertHasIceCandidates(pc1.localDescription) 3801 self.assertHasDtls(pc1.localDescription, "actpass") 3802 3803 # handle offer 3804 run(pc2.setRemoteDescription(pc1.localDescription)) 3805 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 3806 self.assertEqual(len(pc2.getReceivers()), 0) 3807 self.assertEqual(len(pc2.getSenders()), 0) 3808 self.assertEqual(len(pc2.getTransceivers()), 0) 3809 self.assertEqual(mids(pc2), ["0"]) 3810 3811 # create answer 3812 answer = run(pc2.createAnswer()) 3813 self.assertEqual(answer.type, "answer") 3814 self.assertTrue("m=application " in answer.sdp) 3815 self.assertFalse("a=candidate:" in answer.sdp) 3816 self.assertFalse("a=end-of-candidates" in answer.sdp) 3817 3818 run(pc2.setLocalDescription(answer)) 3819 self.assertEqual(pc2.iceConnectionState, "checking") 3820 self.assertEqual(pc2.iceGatheringState, "complete") 3821 self.assertTrue("m=application " in pc2.localDescription.sdp) 3822 self.assertHasIceCandidates(pc2.localDescription) 3823 self.assertHasDtls(pc2.localDescription, "active") 3824 3825 # handle answer 3826 run(pc1.setRemoteDescription(pc2.localDescription)) 3827 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 3828 self.assertEqual(pc1.iceConnectionState, "checking") 3829 3830 # check outcome 3831 self.assertIceCompleted(pc1, pc2) 3832 self.assertDataChannelOpen(dc) 3833 3834 # check pc2 got a datachannel 3835 self.assertEqual(len(pc2_data_channels), 1) 3836 self.assertEqual(pc2_data_channels[0].label, "chat") 3837 self.assertEqual(pc2_data_channels[0].maxPacketLifeTime, None) 3838 self.assertEqual(pc2_data_channels[0].maxRetransmits, None) 3839 self.assertEqual(pc2_data_channels[0].ordered, True) 3840 self.assertEqual(pc2_data_channels[0].protocol, "bob") 3841 3842 # check pc2 got messages 3843 run(asyncio.sleep(0.1)) 3844 self.assertEqual( 3845 pc2_data_messages, ["hello", "", b"\x00\x01\x02\x03", b"", LONG_DATA] 3846 ) 3847 3848 # check pc1 got replies 3849 self.assertEqual( 3850 pc1_data_messages, 3851 [ 3852 "string-echo: hello", 3853 "string-echo: ", 3854 b"binary-echo: \x00\x01\x02\x03", 3855 b"binary-echo: ", 3856 b"binary-echo: " + LONG_DATA, 3857 ], 3858 ) 3859 3860 # 2. ADD AUDIO 3861 3862 # create offer 3863 pc1.addTrack(AudioStreamTrack()) 3864 offer = run(pc1.createOffer()) 3865 self.assertEqual(offer.type, "offer") 3866 self.assertTrue("m=application " in offer.sdp) 3867 self.assertTrue("m=audio " in offer.sdp) 3868 3869 run(pc1.setLocalDescription(offer)) 3870 self.assertEqual(pc1.iceConnectionState, "new") 3871 self.assertEqual(pc1.iceGatheringState, "complete") 3872 self.assertEqual(mids(pc1), ["0", "1"]) 3873 3874 # handle offer 3875 run(pc2.setRemoteDescription(pc1.localDescription)) 3876 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 3877 self.assertEqual(len(pc2.getReceivers()), 1) 3878 self.assertEqual(len(pc2.getSenders()), 1) 3879 self.assertEqual(len(pc2.getTransceivers()), 1) 3880 self.assertEqual(mids(pc2), ["0", "1"]) 3881 3882 # create answer 3883 pc2.addTrack(AudioStreamTrack()) 3884 answer = run(pc2.createAnswer()) 3885 self.assertEqual(answer.type, "answer") 3886 self.assertTrue("m=application " in answer.sdp) 3887 self.assertTrue("m=audio " in answer.sdp) 3888 3889 run(pc2.setLocalDescription(answer)) 3890 self.assertEqual(pc2.iceConnectionState, "completed") 3891 self.assertEqual(pc2.iceGatheringState, "complete") 3892 self.assertTrue("m=application " in pc2.localDescription.sdp) 3893 self.assertTrue("m=audio " in pc2.localDescription.sdp) 3894 3895 # handle answer 3896 run(pc1.setRemoteDescription(pc2.localDescription)) 3897 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 3898 self.assertEqual(pc1.iceConnectionState, "completed") 3899 3900 # check outcome 3901 self.assertIceCompleted(pc1, pc2) 3902 3903 # check a single transport is used 3904 self.assertBundled(pc1) 3905 self.assertBundled(pc2) 3906 3907 # 3. CLEANUP 3908 3909 # close data channel 3910 self.closeDataChannel(dc) 3911 3912 # close 3913 run(pc1.close()) 3914 run(pc2.close()) 3915 self.assertEqual(pc1.iceConnectionState, "closed") 3916 self.assertEqual(pc2.iceConnectionState, "closed") 3917 3918 # check state changes 3919 self.assertEqual( 3920 pc1_states["connectionState"], 3921 ["new", "connecting", "connected", "connecting", "connected", "closed"], 3922 ) 3923 self.assertEqual( 3924 pc1_states["iceConnectionState"], 3925 ["new", "checking", "completed", "new", "completed", "closed"], 3926 ) 3927 self.assertEqual( 3928 pc1_states["iceGatheringState"], 3929 ["new", "gathering", "complete", "new", "gathering", "complete"], 3930 ) 3931 self.assertEqual( 3932 pc1_states["signalingState"], 3933 [ 3934 "stable", 3935 "have-local-offer", 3936 "stable", 3937 "have-local-offer", 3938 "stable", 3939 "closed", 3940 ], 3941 ) 3942 3943 self.assertEqual( 3944 pc2_states["connectionState"], 3945 ["new", "connecting", "connected", "connecting", "connected", "closed"], 3946 ) 3947 self.assertEqual( 3948 pc2_states["iceConnectionState"], 3949 ["new", "checking", "completed", "new", "completed", "closed"], 3950 ) 3951 self.assertEqual( 3952 pc2_states["iceGatheringState"], 3953 ["new", "gathering", "complete", "new", "complete"], 3954 ) 3955 self.assertEqual( 3956 pc2_states["signalingState"], 3957 [ 3958 "stable", 3959 "have-remote-offer", 3960 "stable", 3961 "have-remote-offer", 3962 "stable", 3963 "closed", 3964 ], 3965 ) 3966 3967 def test_connect_datachannel_trickle(self): 3968 pc1 = RTCPeerConnection() 3969 pc1_data_messages = [] 3970 pc1_states = track_states(pc1) 3971 3972 pc2 = RTCPeerConnection() 3973 pc2_data_channels = [] 3974 pc2_data_messages = [] 3975 pc2_states = track_states(pc2) 3976 3977 @pc2.on("datachannel") 3978 def on_datachannel(channel): 3979 self.assertEqual(channel.readyState, "open") 3980 pc2_data_channels.append(channel) 3981 3982 @channel.on("message") 3983 def on_message(message): 3984 pc2_data_messages.append(message) 3985 if isinstance(message, str): 3986 channel.send("string-echo: " + message) 3987 else: 3988 channel.send(b"binary-echo: " + message) 3989 3990 # create data channel 3991 dc = pc1.createDataChannel("chat", protocol="bob") 3992 self.assertEqual(dc.label, "chat") 3993 self.assertEqual(dc.maxPacketLifeTime, None) 3994 self.assertEqual(dc.maxRetransmits, None) 3995 self.assertEqual(dc.ordered, True) 3996 self.assertEqual(dc.protocol, "bob") 3997 self.assertEqual(dc.readyState, "connecting") 3998 3999 # send messages 4000 @dc.on("open") 4001 def on_open(): 4002 dc.send("hello") 4003 dc.send("") 4004 dc.send(b"\x00\x01\x02\x03") 4005 dc.send(b"") 4006 dc.send(LONG_DATA) 4007 with self.assertRaises(ValueError) as cm: 4008 dc.send(1234) 4009 self.assertEqual( 4010 str(cm.exception), "Cannot send unsupported data type: <class 'int'>" 4011 ) 4012 4013 @dc.on("message") 4014 def on_message(message): 4015 pc1_data_messages.append(message) 4016 4017 # create offer 4018 offer = run(pc1.createOffer()) 4019 self.assertEqual(offer.type, "offer") 4020 self.assertTrue("m=application " in offer.sdp) 4021 self.assertFalse("a=candidate:" in offer.sdp) 4022 self.assertFalse("a=end-of-candidates" in offer.sdp) 4023 4024 run(pc1.setLocalDescription(offer)) 4025 self.assertEqual(pc1.iceConnectionState, "new") 4026 self.assertEqual(pc1.iceGatheringState, "complete") 4027 self.assertEqual(mids(pc1), ["0"]) 4028 self.assertTrue("m=application " in pc1.localDescription.sdp) 4029 self.assertHasIceCandidates(pc1.localDescription) 4030 self.assertHasDtls(pc1.localDescription, "actpass") 4031 4032 # strip out candidates 4033 desc1 = strip_ice_candidates(pc1.localDescription) 4034 4035 # handle offer 4036 run(pc2.setRemoteDescription(desc1)) 4037 self.assertEqual(pc2.remoteDescription, desc1) 4038 self.assertEqual(len(pc2.getReceivers()), 0) 4039 self.assertEqual(len(pc2.getSenders()), 0) 4040 self.assertEqual(len(pc2.getTransceivers()), 0) 4041 self.assertEqual(mids(pc2), ["0"]) 4042 4043 # create answer 4044 answer = run(pc2.createAnswer()) 4045 self.assertEqual(answer.type, "answer") 4046 self.assertTrue("m=application " in answer.sdp) 4047 self.assertFalse("a=candidate:" in answer.sdp) 4048 self.assertFalse("a=end-of-candidates" in answer.sdp) 4049 4050 run(pc2.setLocalDescription(answer)) 4051 self.assertEqual(pc2.iceConnectionState, "checking") 4052 self.assertEqual(pc2.iceGatheringState, "complete") 4053 self.assertTrue("m=application " in pc2.localDescription.sdp) 4054 self.assertHasIceCandidates(pc2.localDescription) 4055 self.assertHasDtls(pc2.localDescription, "active") 4056 4057 # strip out candidates 4058 desc2 = strip_ice_candidates(pc2.localDescription) 4059 4060 # handle answer 4061 run(pc1.setRemoteDescription(desc2)) 4062 self.assertEqual(pc1.remoteDescription, desc2) 4063 self.assertEqual(pc1.iceConnectionState, "checking") 4064 4065 # trickle candidates 4066 for candidate in pc2.sctp.transport.transport.iceGatherer.getLocalCandidates(): 4067 candidate.sdpMid = pc2.sctp.mid 4068 run(pc1.addIceCandidate(candidate)) 4069 for candidate in pc1.sctp.transport.transport.iceGatherer.getLocalCandidates(): 4070 candidate.sdpMid = pc1.sctp.mid 4071 run(pc2.addIceCandidate(candidate)) 4072 4073 # check outcome 4074 self.assertIceCompleted(pc1, pc2) 4075 self.assertDataChannelOpen(dc) 4076 4077 # check pc2 got a datachannel 4078 self.assertEqual(len(pc2_data_channels), 1) 4079 self.assertEqual(pc2_data_channels[0].label, "chat") 4080 self.assertEqual(pc2_data_channels[0].maxPacketLifeTime, None) 4081 self.assertEqual(pc2_data_channels[0].maxRetransmits, None) 4082 self.assertEqual(pc2_data_channels[0].ordered, True) 4083 self.assertEqual(pc2_data_channels[0].protocol, "bob") 4084 4085 # check pc2 got messages 4086 run(asyncio.sleep(0.1)) 4087 self.assertEqual( 4088 pc2_data_messages, ["hello", "", b"\x00\x01\x02\x03", b"", LONG_DATA] 4089 ) 4090 4091 # check pc1 got replies 4092 self.assertEqual( 4093 pc1_data_messages, 4094 [ 4095 "string-echo: hello", 4096 "string-echo: ", 4097 b"binary-echo: \x00\x01\x02\x03", 4098 b"binary-echo: ", 4099 b"binary-echo: " + LONG_DATA, 4100 ], 4101 ) 4102 4103 # close data channel 4104 self.closeDataChannel(dc) 4105 4106 # close 4107 run(pc1.close()) 4108 run(pc2.close()) 4109 self.assertEqual(pc1.iceConnectionState, "closed") 4110 self.assertEqual(pc2.iceConnectionState, "closed") 4111 4112 # check state changes 4113 self.assertEqual( 4114 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 4115 ) 4116 self.assertEqual( 4117 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 4118 ) 4119 self.assertEqual( 4120 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 4121 ) 4122 self.assertEqual( 4123 pc1_states["signalingState"], 4124 ["stable", "have-local-offer", "stable", "closed"], 4125 ) 4126 4127 self.assertEqual( 4128 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 4129 ) 4130 self.assertEqual( 4131 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 4132 ) 4133 self.assertEqual( 4134 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 4135 ) 4136 self.assertEqual( 4137 pc2_states["signalingState"], 4138 ["stable", "have-remote-offer", "stable", "closed"], 4139 ) 4140 4141 def test_connect_datachannel_max_packet_lifetime(self): 4142 pc1 = RTCPeerConnection() 4143 pc1_data_messages = [] 4144 pc1_states = track_states(pc1) 4145 4146 pc2 = RTCPeerConnection() 4147 pc2_data_channels = [] 4148 pc2_data_messages = [] 4149 pc2_states = track_states(pc2) 4150 4151 @pc2.on("datachannel") 4152 def on_datachannel(channel): 4153 self.assertEqual(channel.readyState, "open") 4154 pc2_data_channels.append(channel) 4155 4156 @channel.on("message") 4157 def on_message(message): 4158 pc2_data_messages.append(message) 4159 channel.send("string-echo: " + message) 4160 4161 # create data channel 4162 dc = pc1.createDataChannel("chat", maxPacketLifeTime=500, protocol="bob") 4163 self.assertEqual(dc.label, "chat") 4164 self.assertEqual(dc.maxPacketLifeTime, 500) 4165 self.assertEqual(dc.maxRetransmits, None) 4166 self.assertEqual(dc.ordered, True) 4167 self.assertEqual(dc.protocol, "bob") 4168 self.assertEqual(dc.readyState, "connecting") 4169 4170 # send message 4171 @dc.on("open") 4172 def on_open(): 4173 dc.send("hello") 4174 4175 @dc.on("message") 4176 def on_message(message): 4177 pc1_data_messages.append(message) 4178 4179 # create offer 4180 offer = run(pc1.createOffer()) 4181 run(pc1.setLocalDescription(offer)) 4182 run(pc2.setRemoteDescription(pc1.localDescription)) 4183 4184 # create answer 4185 answer = run(pc2.createAnswer()) 4186 run(pc2.setLocalDescription(answer)) 4187 run(pc1.setRemoteDescription(pc2.localDescription)) 4188 4189 # check outcome 4190 self.assertIceCompleted(pc1, pc2) 4191 self.assertDataChannelOpen(dc) 4192 4193 # check pc2 got a datachannel 4194 self.assertEqual(len(pc2_data_channels), 1) 4195 self.assertEqual(pc2_data_channels[0].label, "chat") 4196 self.assertEqual(pc2_data_channels[0].maxPacketLifeTime, 500) 4197 self.assertEqual(pc2_data_channels[0].maxRetransmits, None) 4198 self.assertEqual(pc2_data_channels[0].ordered, True) 4199 self.assertEqual(pc2_data_channels[0].protocol, "bob") 4200 4201 # check pc2 got message 4202 run(asyncio.sleep(0.1)) 4203 self.assertEqual(pc2_data_messages, ["hello"]) 4204 4205 # check pc1 got replies 4206 self.assertEqual(pc1_data_messages, ["string-echo: hello"]) 4207 4208 # close data channel 4209 self.closeDataChannel(dc) 4210 4211 # close 4212 run(pc1.close()) 4213 run(pc2.close()) 4214 self.assertEqual(pc1.iceConnectionState, "closed") 4215 self.assertEqual(pc2.iceConnectionState, "closed") 4216 4217 # check state changes 4218 self.assertEqual( 4219 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 4220 ) 4221 self.assertEqual( 4222 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 4223 ) 4224 self.assertEqual( 4225 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 4226 ) 4227 self.assertEqual( 4228 pc1_states["signalingState"], 4229 ["stable", "have-local-offer", "stable", "closed"], 4230 ) 4231 4232 self.assertEqual( 4233 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 4234 ) 4235 self.assertEqual( 4236 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 4237 ) 4238 self.assertEqual( 4239 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 4240 ) 4241 self.assertEqual( 4242 pc2_states["signalingState"], 4243 ["stable", "have-remote-offer", "stable", "closed"], 4244 ) 4245 4246 def test_connect_datachannel_max_retransmits(self): 4247 pc1 = RTCPeerConnection() 4248 pc1_data_messages = [] 4249 pc1_states = track_states(pc1) 4250 4251 pc2 = RTCPeerConnection() 4252 pc2_data_channels = [] 4253 pc2_data_messages = [] 4254 pc2_states = track_states(pc2) 4255 4256 @pc2.on("datachannel") 4257 def on_datachannel(channel): 4258 self.assertEqual(channel.readyState, "open") 4259 pc2_data_channels.append(channel) 4260 4261 @channel.on("message") 4262 def on_message(message): 4263 pc2_data_messages.append(message) 4264 channel.send("string-echo: " + message) 4265 4266 # create data channel 4267 dc = pc1.createDataChannel("chat", maxRetransmits=0, protocol="bob") 4268 self.assertEqual(dc.label, "chat") 4269 self.assertEqual(dc.maxPacketLifeTime, None) 4270 self.assertEqual(dc.maxRetransmits, 0) 4271 self.assertEqual(dc.ordered, True) 4272 self.assertEqual(dc.protocol, "bob") 4273 self.assertEqual(dc.readyState, "connecting") 4274 4275 # send message 4276 @dc.on("open") 4277 def on_open(): 4278 dc.send("hello") 4279 4280 @dc.on("message") 4281 def on_message(message): 4282 pc1_data_messages.append(message) 4283 4284 # create offer 4285 offer = run(pc1.createOffer()) 4286 run(pc1.setLocalDescription(offer)) 4287 run(pc2.setRemoteDescription(pc1.localDescription)) 4288 4289 # create answer 4290 answer = run(pc2.createAnswer()) 4291 run(pc2.setLocalDescription(answer)) 4292 run(pc1.setRemoteDescription(pc2.localDescription)) 4293 4294 # check outcome 4295 self.assertIceCompleted(pc1, pc2) 4296 self.assertDataChannelOpen(dc) 4297 4298 # check pc2 got a datachannel 4299 self.assertEqual(len(pc2_data_channels), 1) 4300 self.assertEqual(pc2_data_channels[0].label, "chat") 4301 self.assertEqual(pc2_data_channels[0].maxPacketLifeTime, None) 4302 self.assertEqual(pc2_data_channels[0].maxRetransmits, 0) 4303 self.assertEqual(pc2_data_channels[0].ordered, True) 4304 self.assertEqual(pc2_data_channels[0].protocol, "bob") 4305 4306 # check pc2 got message 4307 run(asyncio.sleep(0.1)) 4308 self.assertEqual(pc2_data_messages, ["hello"]) 4309 4310 # check pc1 got replies 4311 self.assertEqual(pc1_data_messages, ["string-echo: hello"]) 4312 4313 # close data channel 4314 self.closeDataChannel(dc) 4315 4316 # close 4317 run(pc1.close()) 4318 run(pc2.close()) 4319 self.assertEqual(pc1.iceConnectionState, "closed") 4320 self.assertEqual(pc2.iceConnectionState, "closed") 4321 4322 # check state changes 4323 self.assertEqual( 4324 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 4325 ) 4326 self.assertEqual( 4327 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 4328 ) 4329 self.assertEqual( 4330 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 4331 ) 4332 self.assertEqual( 4333 pc1_states["signalingState"], 4334 ["stable", "have-local-offer", "stable", "closed"], 4335 ) 4336 4337 self.assertEqual( 4338 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 4339 ) 4340 self.assertEqual( 4341 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 4342 ) 4343 self.assertEqual( 4344 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 4345 ) 4346 self.assertEqual( 4347 pc2_states["signalingState"], 4348 ["stable", "have-remote-offer", "stable", "closed"], 4349 ) 4350 4351 def test_connect_datachannel_unordered(self): 4352 pc1 = RTCPeerConnection() 4353 pc1_data_messages = [] 4354 pc1_states = track_states(pc1) 4355 4356 pc2 = RTCPeerConnection() 4357 pc2_data_channels = [] 4358 pc2_data_messages = [] 4359 pc2_states = track_states(pc2) 4360 4361 @pc2.on("datachannel") 4362 def on_datachannel(channel): 4363 self.assertEqual(channel.readyState, "open") 4364 pc2_data_channels.append(channel) 4365 4366 @channel.on("message") 4367 def on_message(message): 4368 pc2_data_messages.append(message) 4369 channel.send("string-echo: " + message) 4370 4371 # create data channel 4372 dc = pc1.createDataChannel("chat", ordered=False, protocol="bob") 4373 self.assertEqual(dc.label, "chat") 4374 self.assertEqual(dc.maxPacketLifeTime, None) 4375 self.assertEqual(dc.maxRetransmits, None) 4376 self.assertEqual(dc.ordered, False) 4377 self.assertEqual(dc.protocol, "bob") 4378 self.assertEqual(dc.readyState, "connecting") 4379 4380 # send message 4381 @dc.on("open") 4382 def on_open(): 4383 dc.send("hello") 4384 4385 @dc.on("message") 4386 def on_message(message): 4387 pc1_data_messages.append(message) 4388 4389 # create offer 4390 offer = run(pc1.createOffer()) 4391 self.assertEqual(offer.type, "offer") 4392 self.assertTrue("m=application " in offer.sdp) 4393 self.assertFalse("a=candidate:" in offer.sdp) 4394 self.assertFalse("a=end-of-candidates" in offer.sdp) 4395 4396 run(pc1.setLocalDescription(offer)) 4397 self.assertEqual(pc1.iceConnectionState, "new") 4398 self.assertEqual(pc1.iceGatheringState, "complete") 4399 self.assertEqual(mids(pc1), ["0"]) 4400 self.assertTrue("m=application " in pc1.localDescription.sdp) 4401 self.assertHasIceCandidates(pc1.localDescription) 4402 self.assertHasDtls(pc1.localDescription, "actpass") 4403 4404 # handle offer 4405 run(pc2.setRemoteDescription(pc1.localDescription)) 4406 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 4407 self.assertEqual(len(pc2.getReceivers()), 0) 4408 self.assertEqual(len(pc2.getSenders()), 0) 4409 self.assertEqual(len(pc2.getTransceivers()), 0) 4410 self.assertEqual(mids(pc2), ["0"]) 4411 4412 # create answer 4413 answer = run(pc2.createAnswer()) 4414 self.assertEqual(answer.type, "answer") 4415 self.assertTrue("m=application " in answer.sdp) 4416 self.assertFalse("a=candidate:" in answer.sdp) 4417 self.assertFalse("a=end-of-candidates" in answer.sdp) 4418 4419 run(pc2.setLocalDescription(answer)) 4420 self.assertEqual(pc2.iceConnectionState, "checking") 4421 self.assertEqual(pc2.iceGatheringState, "complete") 4422 self.assertTrue("m=application " in pc2.localDescription.sdp) 4423 self.assertHasIceCandidates(pc2.localDescription) 4424 self.assertHasDtls(pc2.localDescription, "active") 4425 4426 # handle answer 4427 run(pc1.setRemoteDescription(pc2.localDescription)) 4428 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 4429 self.assertEqual(pc1.iceConnectionState, "checking") 4430 4431 # check outcome 4432 self.assertIceCompleted(pc1, pc2) 4433 self.assertDataChannelOpen(dc) 4434 4435 # check pc2 got a datachannel 4436 self.assertEqual(len(pc2_data_channels), 1) 4437 self.assertEqual(pc2_data_channels[0].label, "chat") 4438 self.assertEqual(pc2_data_channels[0].maxPacketLifeTime, None) 4439 self.assertEqual(pc2_data_channels[0].maxRetransmits, None) 4440 self.assertEqual(pc2_data_channels[0].ordered, False) 4441 self.assertEqual(pc2_data_channels[0].protocol, "bob") 4442 4443 # check pc2 got message 4444 run(asyncio.sleep(0.1)) 4445 self.assertEqual(pc2_data_messages, ["hello"]) 4446 4447 # check pc1 got replies 4448 self.assertEqual(pc1_data_messages, ["string-echo: hello"]) 4449 4450 # close data channel 4451 self.closeDataChannel(dc) 4452 4453 # close 4454 run(pc1.close()) 4455 run(pc2.close()) 4456 self.assertEqual(pc1.iceConnectionState, "closed") 4457 self.assertEqual(pc2.iceConnectionState, "closed") 4458 4459 # check state changes 4460 self.assertEqual( 4461 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 4462 ) 4463 self.assertEqual( 4464 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 4465 ) 4466 self.assertEqual( 4467 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 4468 ) 4469 self.assertEqual( 4470 pc1_states["signalingState"], 4471 ["stable", "have-local-offer", "stable", "closed"], 4472 ) 4473 4474 self.assertEqual( 4475 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 4476 ) 4477 self.assertEqual( 4478 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 4479 ) 4480 self.assertEqual( 4481 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 4482 ) 4483 self.assertEqual( 4484 pc2_states["signalingState"], 4485 ["stable", "have-remote-offer", "stable", "closed"], 4486 ) 4487 4488 def test_createAnswer_closed(self): 4489 pc = RTCPeerConnection() 4490 run(pc.close()) 4491 with self.assertRaises(InvalidStateError) as cm: 4492 run(pc.createAnswer()) 4493 self.assertEqual(str(cm.exception), "RTCPeerConnection is closed") 4494 4495 def test_createAnswer_without_offer(self): 4496 pc = RTCPeerConnection() 4497 with self.assertRaises(InvalidStateError) as cm: 4498 run(pc.createAnswer()) 4499 self.assertEqual( 4500 str(cm.exception), 'Cannot create answer in signaling state "stable"' 4501 ) 4502 4503 def test_createOffer_closed(self): 4504 pc = RTCPeerConnection() 4505 run(pc.close()) 4506 with self.assertRaises(InvalidStateError) as cm: 4507 run(pc.createOffer()) 4508 self.assertEqual(str(cm.exception), "RTCPeerConnection is closed") 4509 4510 def test_createOffer_without_media(self): 4511 pc = RTCPeerConnection() 4512 with self.assertRaises(InternalError) as cm: 4513 run(pc.createOffer()) 4514 self.assertEqual( 4515 str(cm.exception), 4516 "Cannot create an offer with no media and no data channels", 4517 ) 4518 4519 # close 4520 run(pc.close()) 4521 4522 def test_setLocalDescription_unexpected_answer(self): 4523 pc = RTCPeerConnection() 4524 pc.addTrack(AudioStreamTrack()) 4525 answer = run(pc.createOffer()) 4526 answer.type = "answer" 4527 with self.assertRaises(InvalidStateError) as cm: 4528 run(pc.setLocalDescription(answer)) 4529 self.assertEqual( 4530 str(cm.exception), 'Cannot handle answer in signaling state "stable"' 4531 ) 4532 4533 # close 4534 run(pc.close()) 4535 4536 def test_setLocalDescription_unexpected_offer(self): 4537 pc1 = RTCPeerConnection() 4538 pc2 = RTCPeerConnection() 4539 4540 # apply offer 4541 pc1.addTrack(AudioStreamTrack()) 4542 run(pc1.setLocalDescription(run(pc1.createOffer()))) 4543 run(pc2.setRemoteDescription(pc1.localDescription)) 4544 4545 # mangle answer into an offer 4546 offer = pc2.remoteDescription 4547 offer.type = "offer" 4548 with self.assertRaises(InvalidStateError) as cm: 4549 run(pc2.setLocalDescription(offer)) 4550 self.assertEqual( 4551 str(cm.exception), 4552 'Cannot handle offer in signaling state "have-remote-offer"', 4553 ) 4554 4555 # close 4556 run(pc1.close()) 4557 run(pc2.close()) 4558 4559 def test_setRemoteDescription_no_common_audio(self): 4560 pc1 = RTCPeerConnection() 4561 pc2 = RTCPeerConnection() 4562 pc1.addTrack(AudioStreamTrack()) 4563 offer = run(pc1.createOffer()) 4564 4565 mangled_sdp = [] 4566 for line in offer.sdp.split("\n"): 4567 if line.startswith("a=rtpmap:"): 4568 continue 4569 mangled_sdp.append(line) 4570 4571 mangled = RTCSessionDescription(sdp="\n".join(mangled_sdp), type=offer.type) 4572 4573 with self.assertRaises(OperationError) as cm: 4574 run(pc2.setRemoteDescription(mangled)) 4575 self.assertEqual( 4576 str(cm.exception), "Failed to set remote audio description send parameters" 4577 ) 4578 4579 # close 4580 run(pc1.close()) 4581 run(pc2.close()) 4582 4583 def test_setRemoteDescription_no_common_video(self): 4584 pc1 = RTCPeerConnection() 4585 pc2 = RTCPeerConnection() 4586 pc1.addTrack(VideoStreamTrack()) 4587 offer = run(pc1.createOffer()) 4588 4589 mangled = RTCSessionDescription( 4590 sdp=offer.sdp.replace("90000", "92000"), 4591 type=offer.type, 4592 ) 4593 with self.assertRaises(OperationError) as cm: 4594 run(pc2.setRemoteDescription(mangled)) 4595 self.assertEqual( 4596 str(cm.exception), "Failed to set remote video description send parameters" 4597 ) 4598 4599 # close 4600 run(pc1.close()) 4601 run(pc2.close()) 4602 4603 def test_setRemoteDescription_media_mismatch(self): 4604 pc1 = RTCPeerConnection() 4605 pc2 = RTCPeerConnection() 4606 4607 # apply offer 4608 pc1.addTrack(AudioStreamTrack()) 4609 offer = run(pc1.createOffer()) 4610 run(pc1.setLocalDescription(offer)) 4611 run(pc2.setRemoteDescription(pc1.localDescription)) 4612 4613 # apply answer 4614 answer = run(pc2.createAnswer()) 4615 run(pc2.setLocalDescription(answer)) 4616 mangled = RTCSessionDescription( 4617 sdp=pc2.localDescription.sdp.replace("m=audio", "m=video"), 4618 type=pc2.localDescription.type, 4619 ) 4620 with self.assertRaises(ValueError) as cm: 4621 run(pc1.setRemoteDescription(mangled)) 4622 self.assertEqual( 4623 str(cm.exception), "Media sections in answer do not match offer" 4624 ) 4625 4626 # close 4627 run(pc1.close()) 4628 run(pc2.close()) 4629 4630 def test_setRemoteDescription_with_invalid_dtls_setup_for_offer(self): 4631 pc1 = RTCPeerConnection() 4632 pc2 = RTCPeerConnection() 4633 4634 # apply offer 4635 pc1.addTrack(AudioStreamTrack()) 4636 offer = run(pc1.createOffer()) 4637 run(pc1.setLocalDescription(offer)) 4638 mangled = RTCSessionDescription( 4639 sdp=pc1.localDescription.sdp.replace("a=setup:actpass", "a=setup:active"), 4640 type=pc1.localDescription.type, 4641 ) 4642 with self.assertRaises(ValueError) as cm: 4643 run(pc2.setRemoteDescription(mangled)) 4644 self.assertEqual( 4645 str(cm.exception), 4646 "DTLS setup attribute must be 'actpass' for an offer", 4647 ) 4648 4649 # close 4650 run(pc1.close()) 4651 run(pc2.close()) 4652 4653 def test_setRemoteDescription_with_invalid_dtls_setup_for_answer(self): 4654 pc1 = RTCPeerConnection() 4655 pc2 = RTCPeerConnection() 4656 4657 # apply offer 4658 pc1.addTrack(AudioStreamTrack()) 4659 offer = run(pc1.createOffer()) 4660 run(pc1.setLocalDescription(offer)) 4661 run(pc2.setRemoteDescription(pc1.localDescription)) 4662 4663 # apply answer 4664 answer = run(pc2.createAnswer()) 4665 run(pc2.setLocalDescription(answer)) 4666 mangled = RTCSessionDescription( 4667 sdp=pc2.localDescription.sdp.replace("a=setup:active", "a=setup:actpass"), 4668 type=pc2.localDescription.type, 4669 ) 4670 with self.assertRaises(ValueError) as cm: 4671 run(pc1.setRemoteDescription(mangled)) 4672 self.assertEqual( 4673 str(cm.exception), 4674 "DTLS setup attribute must be 'active' or 'passive' for an answer", 4675 ) 4676 4677 # close 4678 run(pc1.close()) 4679 run(pc2.close()) 4680 4681 def test_setRemoteDescription_without_ice_credentials(self): 4682 pc1 = RTCPeerConnection() 4683 pc2 = RTCPeerConnection() 4684 4685 pc1.addTrack(AudioStreamTrack()) 4686 offer = run(pc1.createOffer()) 4687 run(pc1.setLocalDescription(offer)) 4688 4689 mangled = RTCSessionDescription( 4690 sdp=re.sub( 4691 "^a=(ice-ufrag|ice-pwd):.*\r\n", 4692 "", 4693 pc1.localDescription.sdp, 4694 flags=re.M, 4695 ), 4696 type=pc1.localDescription.type, 4697 ) 4698 with self.assertRaises(ValueError) as cm: 4699 run(pc2.setRemoteDescription(mangled)) 4700 self.assertEqual( 4701 str(cm.exception), "ICE username fragment or password is missing" 4702 ) 4703 4704 # close 4705 run(pc1.close()) 4706 run(pc2.close()) 4707 4708 def test_setRemoteDescription_without_rtcp_mux(self): 4709 pc1 = RTCPeerConnection() 4710 pc2 = RTCPeerConnection() 4711 4712 pc1.addTrack(AudioStreamTrack()) 4713 offer = run(pc1.createOffer()) 4714 run(pc1.setLocalDescription(offer)) 4715 4716 mangled = RTCSessionDescription( 4717 sdp=re.sub("^a=rtcp-mux\r\n", "", pc1.localDescription.sdp, flags=re.M), 4718 type=pc1.localDescription.type, 4719 ) 4720 with self.assertRaises(ValueError) as cm: 4721 run(pc2.setRemoteDescription(mangled)) 4722 self.assertEqual(str(cm.exception), "RTCP mux is not enabled") 4723 4724 # close 4725 run(pc1.close()) 4726 run(pc2.close()) 4727 4728 def test_setRemoteDescription_unexpected_answer(self): 4729 pc = RTCPeerConnection() 4730 with self.assertRaises(InvalidStateError) as cm: 4731 run(pc.setRemoteDescription(RTCSessionDescription(sdp="", type="answer"))) 4732 self.assertEqual( 4733 str(cm.exception), 'Cannot handle answer in signaling state "stable"' 4734 ) 4735 4736 # close 4737 run(pc.close()) 4738 4739 def test_setRemoteDescription_unexpected_offer(self): 4740 pc = RTCPeerConnection() 4741 pc.addTrack(AudioStreamTrack()) 4742 offer = run(pc.createOffer()) 4743 run(pc.setLocalDescription(offer)) 4744 with self.assertRaises(InvalidStateError) as cm: 4745 run(pc.setRemoteDescription(RTCSessionDescription(sdp="", type="offer"))) 4746 self.assertEqual( 4747 str(cm.exception), 4748 'Cannot handle offer in signaling state "have-local-offer"', 4749 ) 4750 4751 # close 4752 run(pc.close()) 4753 4754 def test_setRemoteDescription_media_datachannel_bundled(self): 4755 pc1 = RTCPeerConnection() 4756 pc2 = RTCPeerConnection() 4757 4758 pc1_states = track_states(pc1) 4759 pc2_states = track_states(pc2) 4760 4761 self.assertEqual(pc1.iceConnectionState, "new") 4762 self.assertEqual(pc1.iceGatheringState, "new") 4763 self.assertIsNone(pc1.localDescription) 4764 self.assertIsNone(pc1.remoteDescription) 4765 4766 self.assertEqual(pc2.iceConnectionState, "new") 4767 self.assertEqual(pc2.iceGatheringState, "new") 4768 self.assertIsNone(pc2.localDescription) 4769 self.assertIsNone(pc2.remoteDescription) 4770 4771 """ 4772 initial negotiation 4773 """ 4774 4775 # create offer 4776 pc1.addTrack(AudioStreamTrack()) 4777 pc1.createDataChannel("chat", protocol="") 4778 offer = run(pc1.createOffer()) 4779 self.assertEqual(offer.type, "offer") 4780 4781 run(pc1.setLocalDescription(offer)) 4782 self.assertEqual(pc1.iceConnectionState, "new") 4783 self.assertEqual(pc1.iceGatheringState, "complete") 4784 self.assertEqual(mids(pc1), ["0", "1"]) 4785 self.assertTrue("a=group:BUNDLE 0 1" in pc1.localDescription.sdp) 4786 self.assertTrue("m=audio " in pc1.localDescription.sdp) 4787 4788 # handle offer 4789 run(pc2.setRemoteDescription(pc1.localDescription)) 4790 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 4791 self.assertEqual(len(pc2.getReceivers()), 1) 4792 self.assertEqual(len(pc2.getSenders()), 1) 4793 self.assertEqual(len(pc2.getTransceivers()), 1) 4794 self.assertEqual(mids(pc2), ["0", "1"]) 4795 4796 # create answer 4797 answer = run(pc2.createAnswer()) 4798 self.assertEqual(answer.type, "answer") 4799 self.assertTrue("a=group:BUNDLE 0 1" in answer.sdp) 4800 self.assertTrue("m=audio " in answer.sdp) 4801 self.assertTrue("m=application " in answer.sdp) 4802 4803 run(pc2.setLocalDescription(answer)) 4804 self.assertEqual(pc2.iceConnectionState, "checking") 4805 self.assertEqual(pc2.iceGatheringState, "complete") 4806 self.assertEqual(mids(pc2), ["0", "1"]) 4807 self.assertTrue("a=group:BUNDLE 0 1" in pc2.localDescription.sdp) 4808 self.assertTrue("m=audio " in pc2.localDescription.sdp) 4809 self.assertTrue("m=application " in pc2.localDescription.sdp) 4810 4811 # handle answer 4812 run(pc1.setRemoteDescription(pc2.localDescription)) 4813 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 4814 self.assertEqual(pc1.iceConnectionState, "checking") 4815 4816 # check outcome 4817 self.assertIceCompleted(pc1, pc2) 4818 4819 """ 4820 renegotiation 4821 """ 4822 4823 # create offer 4824 offer = run(pc1.createOffer()) 4825 self.assertEqual(offer.type, "offer") 4826 4827 run(pc1.setLocalDescription(offer)) 4828 self.assertEqual(pc1.iceConnectionState, "completed") 4829 self.assertEqual(pc1.iceGatheringState, "complete") 4830 self.assertEqual(mids(pc1), ["0", "1"]) 4831 self.assertTrue("a=group:BUNDLE 0 1" in pc1.localDescription.sdp) 4832 self.assertTrue("m=audio " in pc1.localDescription.sdp) 4833 self.assertTrue("m=application " in pc1.localDescription.sdp) 4834 self.assertHasDtls(pc1.localDescription, "actpass") 4835 4836 # handle offer 4837 run(pc2.setRemoteDescription(pc1.localDescription)) 4838 self.assertEqual(pc2.remoteDescription, pc1.localDescription) 4839 self.assertEqual(len(pc2.getReceivers()), 1) 4840 self.assertEqual(len(pc2.getSenders()), 1) 4841 self.assertEqual(len(pc2.getTransceivers()), 1) 4842 self.assertEqual(mids(pc2), ["0", "1"]) 4843 4844 # create answer 4845 answer = run(pc2.createAnswer()) 4846 self.assertEqual(answer.type, "answer") 4847 self.assertTrue("a=group:BUNDLE 0 1" in answer.sdp) 4848 self.assertTrue("m=audio " in answer.sdp) 4849 self.assertTrue("m=application " in answer.sdp) 4850 4851 run(pc2.setLocalDescription(answer)) 4852 self.assertEqual(pc2.iceConnectionState, "completed") 4853 self.assertEqual(pc2.iceGatheringState, "complete") 4854 self.assertEqual(mids(pc2), ["0", "1"]) 4855 self.assertTrue("a=group:BUNDLE 0 1" in pc2.localDescription.sdp) 4856 self.assertTrue("m=audio " in pc2.localDescription.sdp) 4857 self.assertTrue("m=application " in pc2.localDescription.sdp) 4858 self.assertHasDtls(pc2.localDescription, "active") 4859 4860 # handle answer 4861 run(pc1.setRemoteDescription(pc2.localDescription)) 4862 self.assertEqual(pc1.remoteDescription, pc2.localDescription) 4863 self.assertEqual(pc1.iceConnectionState, "completed") 4864 4865 # allow media to flow long enough to collect stats 4866 run(asyncio.sleep(2)) 4867 4868 # close 4869 run(pc1.close()) 4870 run(pc2.close()) 4871 self.assertEqual(pc1.iceConnectionState, "closed") 4872 self.assertEqual(pc2.iceConnectionState, "closed") 4873 4874 # check state changes 4875 self.assertEqual( 4876 pc1_states["connectionState"], ["new", "connecting", "connected", "closed"] 4877 ) 4878 self.assertEqual( 4879 pc1_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 4880 ) 4881 self.assertEqual( 4882 pc1_states["iceGatheringState"], ["new", "gathering", "complete"] 4883 ) 4884 self.assertEqual( 4885 pc1_states["signalingState"], 4886 [ 4887 "stable", 4888 "have-local-offer", 4889 "stable", 4890 "have-local-offer", 4891 "stable", 4892 "closed", 4893 ], 4894 ) 4895 4896 self.assertEqual( 4897 pc2_states["connectionState"], ["new", "connecting", "connected", "closed"] 4898 ) 4899 self.assertEqual( 4900 pc2_states["iceConnectionState"], ["new", "checking", "completed", "closed"] 4901 ) 4902 self.assertEqual( 4903 pc2_states["iceGatheringState"], ["new", "gathering", "complete"] 4904 ) 4905 self.assertEqual( 4906 pc2_states["signalingState"], 4907 [ 4908 "stable", 4909 "have-remote-offer", 4910 "stable", 4911 "have-remote-offer", 4912 "stable", 4913 "closed", 4914 ], 4915 ) 4916