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"
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
4. Receive all ICE candidates in function
5.
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.
All ICE candidates have been gathered.
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 = [
]
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())