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