Not able to send ICE candidates with aiortc python

1,013 views
Skip to first unread message

Harsh Trivedi

unread,
Apr 15, 2024, 1:23:46 AM4/15/24
to discuss-webrtc
So basically I wanted to connect a JS client which is connecting the signaling server using browser. I have a python script which connects to the signaling server and then waits for the offer to be made from Js client. My python script handles offer by answering the offer. Then I receive some ICE candidates from JS client but I dont know how to generate my own ICE candidates which I can return back so that the connection can complete. My connection is stuck at "Connection state change: connecting" 

This script aims to do following:
1. Connect to signaling server which is runnig locally
2. Wait for JS client to make offer 
3. Send answer to JS client back logic handled in function handle_message
4. Receive all ICE candidates in function handle_candidate
5. Now only thing pending is to send back ICE candidates. 

Also one observation I make was this event is never getting triggered:
@self.pc.on("icecandidate") I dont know the reason for it. 

My ideal world would be this script also sends back ICE candidates once it has gathered all the candidates so the connection cycle can complete.


My python script output logs:
Connected to the signaling server
TRACK RECEIVED <aiortc.rtcrtpreceiver.RemoteStreamTrack object at 0x11363b590>
TRACK KIND audio
TRACK RECEIVED <aiortc.rtcrtpreceiver.RemoteStreamTrack object at 0x113661690>
TRACK KIND video
ICE gathering state changed to gathering
ICE gathering state changed to complete
All ICE candidates have been gathered.
ICE connection state is checking
Connection state change: connecting



My Python script:
import asyncio
import json
import websockets
from aiortc import RTCIceCandidate, RTCPeerConnection, RTCSessionDescription, RTCIceServer, RTCConfiguration

class WebRTCClient:
def __init__(self, uri):
self.uri = uri
ice_servers = [
RTCIceServer(urls="stun:stun.l.google.com:19302") # Google's public STUN server
]
self.pc = RTCPeerConnection(RTCConfiguration(iceServers=ice_servers))
self.ice_candidates = []

async def connect_to_websocket(self):
self.websocket = await websockets.connect(self.uri)
print("Connected to the signaling server")
asyncio.create_task(self.receive_messages())

@self.pc.on("track")
async def on_track(track):
print('TRACK RECEIVED', track)
print('TRACK KIND', track.kind)

@self.pc.on("icecandidate")
async def on_icecandidate(event):
candidate = event.candidate
if candidate:
print('ICE CANDIDATE GENERATED:', candidate)
message = {
'event': 'candidate',
'data': {
'candidate': candidate.candidate,
'sdpMLineIndex': candidate.sdpMLineIndex,
'sdpMid': candidate.sdpMid
}
}
await self.send_message(message)

@self.pc.on('iceconnectionstatechange')
async def on_iceconnectionstatechange():
print("ICE connection state is", self.pc.iceConnectionState)
if self.pc.iceConnectionState == "failed":
print("ICE Connection has failed, attempting to restart ICE")
await self.pc.restartIce()

@self.pc.on('connectionstatechange')
async def on_connectionstatechange():
print('Connection state change:', self.pc.connectionState)
if self.pc.connectionState == 'connected':
print('Peers successfully connected')


@self.pc.on('icegatheringstatechange')
async def on_icegatheringstatechange():
print('ICE gathering state changed to', self.pc.iceGatheringState)
if self.pc.iceGatheringState == 'complete':
print('All ICE candidates have been gathered.')
# Log all gathered candidates
for candidate in self.ice_candidates:
print('Gathered candidate:', candidate)

async def create_offer(self):
await self.setup_media() # Setup media before creating an offer
offer = await self.pc.createOffer()
await self.pc.setLocalDescription(offer)
await self.send_message({'event': 'offer', 'data': {'type': offer.type, 'sdp': offer.sdp}})

async def setup_media(self):
self.pc.addTransceiver('audio')
self.pc.addTransceiver('video')

async def receive_messages(self):
async for message in self.websocket:
data = json.loads(message)
await self.handle_message(data)

async def handle_message(self, message):
event = message.get('event')
data = message.get('data')
if event == 'offer':
await self.handle_offer(data)
elif event == 'candidate':
await self.handle_candidate(data)
elif event == 'answer':
await self.handle_answer(data)

async def handle_offer(self, offer):
await self.pc.setRemoteDescription(RTCSessionDescription(sdp=offer['sdp'], type=offer['type']))
answer = await self.pc.createAnswer()
await self.pc.setLocalDescription(answer)
await self.send_message({'event': 'answer', 'data': {'type': answer.type, 'sdp': answer.sdp}})

async def handle_candidate(self, candidate):
ip = candidate['candidate'].split(' ')[4]
port = candidate['candidate'].split(' ')[5]
protocol = candidate['candidate'].split(' ')[7]
priority = candidate['candidate'].split(' ')[3]
foundation = candidate['candidate'].split(' ')[0]
component = candidate['candidate'].split(' ')[1]
type = candidate['candidate'].split(' ')[7]
rtc_candidate = RTCIceCandidate(
ip=ip,
port=port,
protocol=protocol,
priority=priority,
foundation=foundation,
component=component,
type=type,
sdpMid=candidate['sdpMid'],
sdpMLineIndex=candidate['sdpMLineIndex']
)
await self.pc.addIceCandidate(rtc_candidate)

async def handle_answer(self, answer):
await self.pc.setRemoteDescription(RTCSessionDescription(sdp=answer['sdp'], type=answer['type']))

async def send_message(self, message):
await self.websocket.send(json.dumps(message))

async def close(self):
await self.pc.close()
await self.websocket.close()

async def main():
client = WebRTCClient('ws://localhost:8080/socket')
await client.connect_to_websocket()
# await client.create_offer() # Create an offer after connecting to the websocket
await asyncio.sleep(3600) # Keep the session alive for debugging
await client.close()

if __name__ == '__main__':
asyncio.run(main())




Reply all
Reply to author
Forward
0 new messages