def test_addTrack_video(self): pc = RTCPeerConnection() # add video track video_track = VideoStreamTrack() video_sender = pc.addTrack(video_track) self.assertIsNotNone(video_sender) self.assertEqual(video_sender.track, video_track) self.assertEqual(pc.getSenders(), [video_sender]) # try to add same track again with self.assertRaises(InvalidAccessError) as cm: pc.addTrack(video_track) self.assertEqual(str(cm.exception), 'Track already has a sender') # try adding another video track with self.assertRaises(InternalError) as cm: pc.addTrack(VideoStreamTrack()) self.assertEqual(str(cm.exception), 'Only a single video track is supported for now') # add audio track audio_track = AudioStreamTrack() audio_sender = pc.addTrack(audio_track) self.assertIsNotNone(audio_sender) self.assertEqual(audio_sender.track, audio_track) self.assertEqual(pc.getSenders(), [video_sender, audio_sender])
def test_video_ended(self): track = VideoStreamTrack() recorder = MediaBlackhole() recorder.addTrack(track) run(recorder.start()) run(asyncio.sleep(1)) track.stop() run(asyncio.sleep(1)) run(recorder.stop())
def test_handle_rtcp_remb(self): sender = RTCRtpSender(VideoStreamTrack(), self.local_transport) self.assertEqual(sender.kind, 'video') run( sender.send( RTCRtpParameters(codecs=[ RTCRtpCodecParameters( name='VP8', clockRate=90000, payloadType=100), ]))) # receive RTCP feedback REMB packet = RtcpPsfbPacket(fmt=RTCP_PSFB_APP, ssrc=1234, media_ssrc=0, fci=b'REMB\x01\x13\xf7\xa0\x96\xbe\x96\xcf') run(sender._handle_rtcp_packet(packet)) # receive RTCP feedback REMB (malformed) packet = RtcpPsfbPacket(fmt=RTCP_PSFB_APP, ssrc=1234, media_ssrc=0, fci=b'JUNK') run(sender._handle_rtcp_packet(packet)) # clean shutdown run(sender.stop())
def test_retransmit(self): """ Ask for an RTP packet retransmission. """ transport = FakeDtlsTransport() sender = RTCRtpSender(VideoStreamTrack(), transport) self.assertEqual(sender.kind, 'video') self.assertEqual(sender.transport, transport) run( sender.send( RTCRtpParameters(codecs=[ RTCRtpCodecParameters( name='VP8', clockRate=90000, payloadType=100), ]))) # wait for one packet to be transmitted, and ask to retransmit packet = run(transport.queue.get()) run(sender._retransmit(packet.sequence_number)) # wait for packet to be transmitted rtx_packet = run(transport.queue.get()) self.assertEqual(rtx_packet.sequence_number, packet.sequence_number) # clean shutdown run(sender.stop())
def test_handle_rtcp_rr(self): sender = RTCRtpSender(VideoStreamTrack(), self.local_transport) self.assertEqual(sender.kind, 'video') run(sender.send(RTCRtpParameters(codecs=[VP8_CODEC]))) # receive RTCP RR packet = RtcpRrPacket(ssrc=1234, reports=[ RtcpReceiverInfo(ssrc=sender._ssrc, fraction_lost=0, packets_lost=0, highest_sequence=630, jitter=1906, lsr=0, dlsr=0) ]) run(sender._handle_rtcp_packet(packet)) # check stats report = run(sender.getStats()) self.assertTrue(isinstance(report, RTCStatsReport)) self.assertEqual(sorted([s.type for s in report.values()]), ['outbound-rtp', 'remote-inbound-rtp', 'transport']) # clean shutdown run(sender.stop())
async def test_handle_rtcp_remb(self): async with dummy_dtls_transport_pair() as (local_transport, _): sender = RTCRtpSender(VideoStreamTrack(), local_transport) self.assertEqual(sender.kind, "video") await sender.send(RTCRtpParameters(codecs=[VP8_CODEC])) # receive RTCP feedback REMB packet = RtcpPsfbPacket( fmt=RTCP_PSFB_APP, ssrc=1234, media_ssrc=0, fci=pack_remb_fci(4160000, [sender._ssrc]), ) await sender._handle_rtcp_packet(packet) # receive RTCP feedback REMB (malformed) packet = RtcpPsfbPacket(fmt=RTCP_PSFB_APP, ssrc=1234, media_ssrc=0, fci=b"JUNK") await sender._handle_rtcp_packet(packet) # clean shutdown await sender.stop()
def test_retransmit(self): """ Ask for an RTP packet retransmission. """ transport = FakeDtlsTransport() sender = RTCRtpSender(VideoStreamTrack(), transport) self.assertEqual(sender.kind, 'video') self.assertEqual(sender.transport, transport) run( sender.send( RTCRtpParameters(codecs=[ RTCRtpCodecParameters( name='VP8', clockRate=90000, payloadType=100), ]))) # wait for one packet to be transmitted, and ask to retransmit packet = run(transport.queue.get()) run(sender._retransmit(packet.sequence_number)) # wait for packet to be retransmitted, then shutdown run(asyncio.sleep(0.5)) run(sender.stop()) # check packet was retransmitted found_rtx = False while not transport.queue.empty(): queue_packet = transport.queue.get_nowait() if queue_packet.sequence_number == packet.sequence_number: found_rtx = True self.assertTrue(found_rtx)
def test_send_keyframe(self): """ Ask for a keyframe. """ queue = asyncio.Queue() async def mock_send_rtp(data): if not is_rtcp(data): await queue.put(RtpPacket.parse(data)) self.local_transport._send_rtp = mock_send_rtp sender = RTCRtpSender(VideoStreamTrack(), self.local_transport) self.assertEqual(sender.kind, 'video') run( sender.send( RTCRtpParameters(codecs=[ RTCRtpCodecParameters( name='VP8', clockRate=90000, payloadType=100), ]))) # wait for one packet to be transmitted, and ask for keyframe run(queue.get()) sender._send_keyframe() # wait for packet to be transmitted, then shutdown run(asyncio.sleep(0.1)) run(sender.stop())
async def test_send_keyframe(self): """ Ask for a keyframe. """ queue = asyncio.Queue() async def mock_send_rtp(data): if not is_rtcp(data): await queue.put(RtpPacket.parse(data)) async with dummy_dtls_transport_pair() as (local_transport, _): local_transport._send_rtp = mock_send_rtp sender = RTCRtpSender(VideoStreamTrack(), local_transport) self.assertEqual(sender.kind, "video") await sender.send(RTCRtpParameters(codecs=[VP8_CODEC])) # wait for one packet to be transmitted, and ask for keyframe await queue.get() sender._send_keyframe() # wait for packet to be transmitted, then shutdown await asyncio.sleep(0.1) await sender.stop()
async def test_handle_rtcp_rr(self): async with dummy_dtls_transport_pair() as (local_transport, _): sender = RTCRtpSender(VideoStreamTrack(), local_transport) self.assertEqual(sender.kind, "video") await sender.send(RTCRtpParameters(codecs=[VP8_CODEC])) # receive RTCP RR packet = RtcpRrPacket( ssrc=1234, reports=[ RtcpReceiverInfo( ssrc=sender._ssrc, fraction_lost=0, packets_lost=0, highest_sequence=630, jitter=1906, lsr=0, dlsr=0, ) ], ) await sender._handle_rtcp_packet(packet) # check stats report = await sender.getStats() self.assertTrue(isinstance(report, RTCStatsReport)) self.assertEqual( sorted([s.type for s in report.values()]), ["outbound-rtp", "remote-inbound-rtp", "transport"], ) # clean shutdown await sender.stop()
def test_handle_rtcp_rr(self): transport, remote = dummy_dtls_transport_pair() sender = RTCRtpSender(VideoStreamTrack(), transport) self.assertEqual(sender.kind, 'video') self.assertEqual(sender.transport, transport) run( sender.send( RTCRtpParameters(codecs=[ RTCRtpCodecParameters( name='VP8', clockRate=90000, payloadType=100), ]))) # receive RTCP RR for packet in RtcpPacket.parse(load('rtcp_rr.bin')): run(sender._handle_rtcp_packet(packet)) # check stats report = run(sender.getStats()) self.assertTrue(isinstance(report, RTCStatsReport)) self.assertEqual(sorted([s.type for s in report.values()]), ['outbound-rtp', 'remote-inbound-rtp', 'transport']) # clean shutdown run(sender.stop())
def test_audio_and_video(self): recorder = MediaBlackhole() recorder.addTrack(AudioStreamTrack()) recorder.addTrack(VideoStreamTrack()) run(recorder.start()) run(asyncio.sleep(2)) run(recorder.stop())
async def test_log_debug(self, mock_is_enabled_for): mock_is_enabled_for.return_value = True async with dummy_dtls_transport_pair() as (local_transport, _): sender = RTCRtpSender(VideoStreamTrack(), local_transport) await sender.send(RTCRtpParameters(codecs=[VP8_CODEC])) # clean shutdown await sender.stop()
def test_handle_rtcp_pli(self): sender = RTCRtpSender(VideoStreamTrack(), self.local_transport) self.assertEqual(sender.kind, "video") run(sender.send(RTCRtpParameters(codecs=[VP8_CODEC]))) # receive RTCP feedback NACK packet = RtcpPsfbPacket(fmt=RTCP_PSFB_PLI, ssrc=1234, media_ssrc=sender._ssrc) run(sender._handle_rtcp_packet(packet)) # clean shutdown run(sender.stop())
def test_retransmit_with_rtx(self): """ Ask for an RTP packet retransmission. """ queue = asyncio.Queue() async def mock_send_rtp(data): if not is_rtcp(data): await queue.put(RtpPacket.parse(data)) self.local_transport._send_rtp = mock_send_rtp sender = RTCRtpSender(VideoStreamTrack(), self.local_transport) sender._ssrc = 1234 sender._rtx_ssrc = 2345 self.assertEqual(sender.kind, "video") run( sender.send( RTCRtpParameters( codecs=[ VP8_CODEC, RTCRtpCodecParameters( mimeType="video/rtx", clockRate=90000, payloadType=101, parameters={"apt": 100}, ), ] ) ) ) # wait for one packet to be transmitted, and ask to retransmit packet = run(queue.get()) run(sender._retransmit(packet.sequence_number)) # wait for packet to be retransmitted, then shutdown run(asyncio.sleep(0.1)) run(sender.stop()) # check packet was retransmitted found_rtx = None while not queue.empty(): queue_packet = queue.get_nowait() if queue_packet.payload_type == 101: found_rtx = queue_packet break self.assertIsNotNone(found_rtx) self.assertEqual(found_rtx.payload_type, 101) self.assertEqual(found_rtx.ssrc, 2345) self.assertEqual(found_rtx.payload[0:2], pack("!H", packet.sequence_number))
def test_handle_rtcp_nack(self): sender = RTCRtpSender(VideoStreamTrack(), self.local_transport) self.assertEqual(sender.kind, 'video') run(sender.send(RTCRtpParameters(codecs=[VP8_CODEC]))) # receive RTCP feedback NACK packet = RtcpRtpfbPacket(fmt=RTCP_RTPFB_NACK, ssrc=1234, media_ssrc=sender._ssrc) packet.lost.append(7654) run(sender._handle_rtcp_packet(packet)) # clean shutdown run(sender.stop())
async def test_handle_rtcp_pli(self): async with dummy_dtls_transport_pair() as (local_transport, _): sender = RTCRtpSender(VideoStreamTrack(), local_transport) self.assertEqual(sender.kind, "video") await sender.send(RTCRtpParameters(codecs=[VP8_CODEC])) # receive RTCP feedback NACK packet = RtcpPsfbPacket(fmt=RTCP_PSFB_PLI, ssrc=1234, media_ssrc=sender._ssrc) await sender._handle_rtcp_packet(packet) # clean shutdown await sender.stop()
def test_video_mp4(self): path = self.temporary_path("test.mp4") recorder = MediaRecorder(path) recorder.addTrack(VideoStreamTrack()) run(recorder.start()) run(asyncio.sleep(2)) run(recorder.stop()) # check output media container = av.open(path, "r") self.assertEqual(len(container.streams), 1) self.assertEqual(container.streams[0].codec.name, "h264") self.assertGreater( float(container.streams[0].duration * container.streams[0].time_base), 0 ) self.assertEqual(container.streams[0].width, 640) self.assertEqual(container.streams[0].height, 480)
def test_handle_rtcp_pli(self): sender = RTCRtpSender(VideoStreamTrack(), self.local_transport) self.assertEqual(sender.kind, 'video') run( sender.send( RTCRtpParameters(codecs=[ RTCRtpCodecParameters( name='VP8', clockRate=90000, payloadType=100), ]))) # receive RTCP feedback NACK packet = RtcpPsfbPacket(fmt=RTCP_PSFB_PLI, ssrc=1234, media_ssrc=5678) run(sender._handle_rtcp_packet(packet)) # clean shutdown run(sender.stop())
def test_handle_rtcp_nack(self): transport, remote = dummy_dtls_transport_pair() sender = RTCRtpSender(VideoStreamTrack(), transport) self.assertEqual(sender.kind, 'video') self.assertEqual(sender.transport, transport) run(sender.send(RTCRtpParameters(codecs=[ RTCRtpCodecParameters(name='VP8', clockRate=90000, payloadType=100), ]))) # receive RTCP feedback NACK packet = RtcpRtpfbPacket(fmt=RTCP_RTPFB_NACK, ssrc=1234, media_ssrc=5678) packet.lost.append(7654) run(sender._handle_rtcp_packet(packet)) # clean shutdown run(sender.stop())
def test_retransmit(self): """ Ask for an RTP packet retransmission. """ queue = asyncio.Queue() async def mock_send_rtp(data): if not is_rtcp(data): await queue.put(RtpPacket.parse(data)) self.local_transport._send_rtp = mock_send_rtp sender = RTCRtpSender(VideoStreamTrack(), self.local_transport) sender._ssrc = 1234 self.assertEqual(sender.kind, 'video') run( sender.send( RTCRtpParameters(codecs=[ RTCRtpCodecParameters( name='VP8', clockRate=90000, payloadType=100), ]))) # wait for one packet to be transmitted, and ask to retransmit packet = run(queue.get()) run(sender._retransmit(packet.sequence_number)) # wait for packet to be retransmitted, then shutdown run(asyncio.sleep(0.1)) run(sender.stop()) # check packet was retransmitted found_rtx = None while not queue.empty(): queue_packet = queue.get_nowait() if queue_packet.sequence_number == packet.sequence_number: found_rtx = queue_packet break self.assertIsNotNone(found_rtx) self.assertEqual(found_rtx.payload_type, 100) self.assertEqual(found_rtx.ssrc, 1234)
async def test_retransmit(self): """ Ask for an RTP packet retransmission. """ queue = asyncio.Queue() async def mock_send_rtp(data): if not is_rtcp(data): await queue.put(RtpPacket.parse(data)) async with dummy_dtls_transport_pair() as (local_transport, _): local_transport._send_rtp = mock_send_rtp sender = RTCRtpSender(VideoStreamTrack(), local_transport) sender._ssrc = 1234 self.assertEqual(sender.kind, "video") await sender.send(RTCRtpParameters(codecs=[VP8_CODEC])) # wait for one packet to be transmitted, and ask to retransmit packet = await queue.get() await sender._retransmit(packet.sequence_number) # wait for packet to be retransmitted, then shutdown await asyncio.sleep(0.1) await sender.stop() # check packet was retransmitted found_rtx = None while not queue.empty(): queue_packet = queue.get_nowait() if queue_packet.sequence_number == packet.sequence_number: found_rtx = queue_packet break self.assertIsNotNone(found_rtx) self.assertEqual(found_rtx.payload_type, 100) self.assertEqual(found_rtx.ssrc, 1234)
def test_send_keyframe(self): """ Ask for a keyframe. """ transport = FakeDtlsTransport() sender = RTCRtpSender(VideoStreamTrack(), transport) self.assertEqual(sender.kind, 'video') self.assertEqual(sender.transport, transport) run( sender.send( RTCRtpParameters(codecs=[ RTCRtpCodecParameters( name='VP8', clockRate=90000, payloadType=100), ]))) # wait for one packet to be transmitted, and ask for keyframe run(transport.queue.get()) sender._send_keyframe() # wait for packet to be transmitted, then shutdown run(asyncio.sleep(0.5)) run(sender.stop())
async def test_video(self): recorder = MediaBlackhole() recorder.addTrack(VideoStreamTrack()) await recorder.start() await asyncio.sleep(2) await recorder.stop()
async def run(pc, player, session, bitrate=512000, record=False): @pc.on('track') def on_track(track): logger.info(f'Track {track.kind} received') @track.on('ended') def on_ended(): print(f'Track {track.kind} ended') @pc.on('iceconnectionstatechange') def on_ice_state_change(): # logger.info(f'ICE state changed to {pc.iceConnectionState}') pass # create session await session.create() # configure media media = {'audio': True, 'video': True} if player and player.audio: pc.addTrack(player.audio) else: pc.addTrack(AudioStreamTrack()) if player and player.video: pc.addTrack(player.video) else: pc.addTrack(VideoStreamTrack()) # attach to echotest plugin plugin = await session.attach('janus.plugin.echotest') # create data-channel channel = pc.createDataChannel('JanusDataChannel') logger.info(f'DataChannel ({channel.label}) created') dc_probe_message = 'echo-ping' dc_open = False dc_probe_received = False @channel.on('open') def on_open(): nonlocal dc_open dc_open = True logger.info(f'DataChannel ({channel.label}) open') logger.info( f'DataChannel ({channel.label}) sending: {dc_probe_message}') channel.send(dc_probe_message) @channel.on('close') def on_close(): nonlocal dc_open dc_open = False logger.info(f'DataChannel ({channel.label}) closed') @channel.on('message') def on_message(message): logger.info(f'DataChannel ({channel.label}) received: {message}') if dc_probe_message in message: nonlocal dc_probe_received dc_probe_received = True # send offer await pc.setLocalDescription(await pc.createOffer()) request = {'record': record, 'bitrate': bitrate} request.update(media) response = await plugin.sendMessage({ 'body': request, 'jsep': { 'sdp': pc.localDescription.sdp, 'trickle': False, 'type': pc.localDescription.type, }, }) assert response['plugindata']['data']['result'] == 'ok' # apply answer answer = RTCSessionDescription(sdp=response['jsep']['sdp'], type=response['jsep']['type']) await pc.setRemoteDescription(answer) logger.info('Running for a while...') await asyncio.sleep(5) # Check WebSocket status assert session._websocket.connection.open # Get RTC stats and check the status rtcstats = await pc.getStats() rtp = {'audio': {'in': 0, 'out': 0}, 'video': {'in': 0, 'out': 0}} dtls_state = None for stat in rtcstats.values(): if stat.type == 'inbound-rtp': rtp[stat.kind]['in'] = stat.packetsReceived elif stat.type == 'outbound-rtp': rtp[stat.kind]['out'] = stat.packetsSent elif stat.type == 'transport': dtls_state = stat.dtlsState # ICE succeded assert pc.iceConnectionState == 'completed' # DTLS succeded assert dtls_state == 'connected' # Janus echoed the sent packets assert rtp['audio']['out'] >= rtp['audio']['in'] > 0 assert rtp['video']['out'] >= rtp['video']['in'] > 0 # DataChannels worked assert dc_open assert dc_probe_received logger.info('Ending the test now')
def test_connect_audio_and_video_and_data_channel(self): pc1 = RTCPeerConnection() pc1_states = track_states(pc1) pc2 = RTCPeerConnection() pc2_states = track_states(pc2) self.assertEqual(pc1.iceConnectionState, 'new') self.assertEqual(pc1.iceGatheringState, 'new') self.assertIsNone(pc1.localDescription) self.assertIsNone(pc1.remoteDescription) self.assertEqual(pc2.iceConnectionState, 'new') self.assertEqual(pc2.iceGatheringState, 'new') self.assertIsNone(pc2.localDescription) self.assertIsNone(pc2.remoteDescription) # create offer pc1.addTrack(AudioStreamTrack()) pc1.addTrack(VideoStreamTrack()) pc1.createDataChannel('chat', protocol='bob') offer = run(pc1.createOffer()) self.assertEqual(offer.type, 'offer') self.assertTrue('m=audio ' in offer.sdp) self.assertTrue('m=video ' in offer.sdp) self.assertTrue('m=application ' in offer.sdp) run(pc1.setLocalDescription(offer)) self.assertEqual(pc1.iceConnectionState, 'new') self.assertEqual(pc1.iceGatheringState, 'complete') # handle offer run(pc2.setRemoteDescription(pc1.localDescription)) self.assertEqual(pc2.remoteDescription, pc1.localDescription) self.assertEqual(len(pc2.getSenders()), 2) self.assertEqual(len(pc2.getReceivers()), 2) # create answer pc2.addTrack(AudioStreamTrack()) pc2.addTrack(VideoStreamTrack()) answer = run(pc2.createAnswer()) self.assertEqual(answer.type, 'answer') self.assertTrue('m=audio ' in answer.sdp) self.assertTrue('m=video ' in answer.sdp) self.assertTrue('m=application ' in answer.sdp) run(pc2.setLocalDescription(answer)) self.assertEqual(pc2.iceConnectionState, 'checking') self.assertEqual(pc2.iceGatheringState, 'complete') self.assertTrue('m=audio ' in pc2.localDescription.sdp) self.assertTrue('m=video ' in pc2.localDescription.sdp) self.assertTrue('m=application ' in pc2.localDescription.sdp) # handle answer run(pc1.setRemoteDescription(pc2.localDescription)) self.assertEqual(pc1.remoteDescription, pc2.localDescription) self.assertEqual(pc1.iceConnectionState, 'checking') # check outcome run(asyncio.sleep(1)) self.assertEqual(pc1.iceConnectionState, 'completed') self.assertEqual(pc2.iceConnectionState, 'completed') # check a single transport is used self.assertBundled(pc1) self.assertBundled(pc2) # close run(pc1.close()) run(pc2.close()) self.assertEqual(pc1.iceConnectionState, 'closed') self.assertEqual(pc2.iceConnectionState, 'closed') # check state changes self.assertEqual(pc1_states['iceConnectionState'], ['new', 'checking', 'completed', 'closed']) self.assertEqual(pc1_states['iceGatheringState'], [ 'new', 'gathering', 'new', 'gathering', 'new', 'gathering', 'complete' ]) self.assertEqual(pc1_states['signalingState'], ['stable', 'have-local-offer', 'stable', 'closed']) self.assertEqual(pc2_states['iceConnectionState'], ['new', 'checking', 'completed', 'closed']) self.assertEqual(pc2_states['iceGatheringState'], ['new', 'gathering', 'complete']) self.assertEqual(pc2_states['signalingState'], ['stable', 'have-remote-offer', 'stable', 'closed'])
def test_connect_video_bidirectional(self): pc1 = RTCPeerConnection() pc1_states = track_states(pc1) pc2 = RTCPeerConnection() pc2_states = track_states(pc2) self.assertEqual(pc1.iceConnectionState, 'new') self.assertEqual(pc1.iceGatheringState, 'new') self.assertIsNone(pc1.localDescription) self.assertIsNone(pc1.remoteDescription) self.assertEqual(pc2.iceConnectionState, 'new') self.assertEqual(pc2.iceGatheringState, 'new') self.assertIsNone(pc2.localDescription) self.assertIsNone(pc2.remoteDescription) # create offer pc1.addTrack(VideoStreamTrack()) offer = run(pc1.createOffer()) self.assertEqual(offer.type, 'offer') self.assertTrue('m=video ' in offer.sdp) self.assertFalse('a=candidate:' in offer.sdp) run(pc1.setLocalDescription(offer)) self.assertEqual(pc1.iceConnectionState, 'new') self.assertEqual(pc1.iceGatheringState, 'complete') self.assertTrue('m=video ' in pc1.localDescription.sdp) self.assertTrue('a=candidate:' in pc1.localDescription.sdp) self.assertTrue('a=sendrecv' in pc1.localDescription.sdp) self.assertTrue('a=fingerprint:sha-256' in pc1.localDescription.sdp) self.assertTrue('a=setup:actpass' in pc1.localDescription.sdp) # handle offer run(pc2.setRemoteDescription(pc1.localDescription)) self.assertEqual(pc2.remoteDescription, pc1.localDescription) self.assertEqual(len(pc2.getReceivers()), 1) # create answer pc2.addTrack(VideoStreamTrack()) answer = run(pc2.createAnswer()) self.assertEqual(answer.type, 'answer') self.assertTrue('m=video ' in answer.sdp) self.assertFalse('a=candidate:' in answer.sdp) run(pc2.setLocalDescription(answer)) self.assertEqual(pc2.iceConnectionState, 'checking') self.assertEqual(pc2.iceGatheringState, 'complete') self.assertTrue('m=video ' in pc2.localDescription.sdp) self.assertTrue('a=candidate:' in pc2.localDescription.sdp) self.assertTrue('a=sendrecv' in pc1.localDescription.sdp) self.assertTrue('a=fingerprint:sha-256' in pc2.localDescription.sdp) self.assertTrue('a=setup:active' in pc2.localDescription.sdp) # handle answer run(pc1.setRemoteDescription(pc2.localDescription)) self.assertEqual(pc1.remoteDescription, pc2.localDescription) self.assertEqual(pc1.iceConnectionState, 'checking') # check outcome run(asyncio.sleep(1)) self.assertEqual(pc1.iceConnectionState, 'completed') self.assertEqual(pc2.iceConnectionState, 'completed') # close run(pc1.close()) run(pc2.close()) self.assertEqual(pc1.iceConnectionState, 'closed') self.assertEqual(pc2.iceConnectionState, 'closed') # check state changes self.assertEqual(pc1_states['iceConnectionState'], ['new', 'checking', 'completed', 'closed']) self.assertEqual(pc1_states['iceGatheringState'], ['new', 'gathering', 'complete']) self.assertEqual(pc1_states['signalingState'], ['stable', 'have-local-offer', 'stable', 'closed']) self.assertEqual(pc2_states['iceConnectionState'], ['new', 'checking', 'completed', 'closed']) self.assertEqual(pc2_states['iceGatheringState'], ['new', 'gathering', 'complete']) self.assertEqual(pc2_states['signalingState'], ['stable', 'have-remote-offer', 'stable', 'closed'])