async def do_sdp_offer(self): width = 320 height = 240 iceservers = RTCIceServer(self.ice_servers) self.ice_env = RTCPeerConnection(configuration=RTCConfiguration( iceServers=[iceservers])) #self.ice_env = RTCPeerConnection() print(iceservers) gatherer = RTCIceGatherer(iceServers=[iceservers]) await gatherer.gather() __ice = RTCIceTransport(gatherer) print(__ice.iceGatherer) print(gatherer.getLocalParameters()) print(__ice.getRemoteCandidates()) local_video = CombinedVideoStreamTrack(tracks=[ ColorVideoStreamTrack(width=width, height=height, color=BLUE), ColorVideoStreamTrack(width=width, height=height, color=GREEN), ColorVideoStreamTrack(width=width, height=height, color=RED), ]) self.ice_env.addTrack(local_video) await self.ice_env.setLocalDescription(await self.ice_env.createOffer()) print(self.ice_env.localDescription) payload = { 'tc': 'sdp', 'type': 'offer', 'sdp': self.description_to_dict(self.ice_env.localDescription)['sdp'], 'handle': self.client_id } await self.send(payload) await asyncio.sleep(5)
async def start_ice_pair(): ice_a = RTCIceTransport(gatherer=RTCIceGatherer()) ice_b = RTCIceTransport(gatherer=RTCIceGatherer()) await asyncio.gather(ice_a.iceGatherer.gather(), ice_b.iceGatherer.gather()) for candidate in ice_b.iceGatherer.getLocalCandidates(): ice_a.addRemoteCandidate(candidate) for candidate in ice_a.iceGatherer.getLocalCandidates(): ice_b.addRemoteCandidate(candidate) await asyncio.gather(ice_a.start(ice_b.iceGatherer.getLocalParameters()), ice_b.start(ice_a.iceGatherer.getLocalParameters())) return ice_a, ice_b
async def on_connect(self, websocket): gatherer = RTCIceGatherer(iceServers=[]) websocket.state.iceTransport = RTCIceTransport(gatherer) certificate = RTCCertificate.generateCertificate() websocket.state.dtlsTransport = RTCDtlsTransport( websocket.state.iceTransport, [certificate]) # monkey-patch RTCDtlsTransport websocket.state.dtlsTransport._handle_rtp_data = functools.partial( handle_rtp_data, websocket) websocket.state.dtlsTransport._handle_rtcp_data = functools.partial( handle_rtcp_data, websocket) await websocket.accept()
class ICEHandler: Full: int = 1 PacingMs: int = 50 Version: int = 1 def __init__(self): self._gatherer = RTCIceGatherer() self._transport = RTCIceTransport(self._gatherer) @property def transport(self) -> RTCIceTransport: return self._transport @staticmethod def _candidate_to_dict(candidate: RTCIceCandidate) -> dict: return { "transportAddress": f"{candidate.ip}:{candidate.port}", "baseAddress": f"{candidate.ip}:{candidate.port}", "serverAddress": "", "ipv6": "0", "type": "0", "addressType": "3", # TODO: whats addressType ? "priority": str(candidate.priority), "foundation": candidate.foundation, "transport": candidate.protocol } @staticmethod def _dict_to_candidate(candidate_node: dict) -> RTCIceCandidate: host_port_combo: str = candidate_node.get('transportAddress') candidate_type: str = candidate_node.get('type') priority: int = int(candidate_node.get('priority')) foundation: str = candidate_node.get('foundation') protocol: str = candidate_node.get('transport') # TODO: whats component? component = 0 host, port = host_port_combo.rsplit(':', maxsplit=1) return RTCIceCandidate(component, foundation, host, port, priority, protocol, candidate_type) async def generate_local_config(self) -> dict: await self._gatherer.gather() local_candidates = self._gatherer.getLocalCandidates() local_params = self._gatherer.getLocalParameters() ice_config: dict = { "Full": str(ICEHandler.Full), "PacingMs": str(ICEHandler.PacingMs), "Version": str(ICEHandler.Version), "Username": local_params.usernameFragment, "Password": local_params.password, "Candidates": { "count": str(len(local_candidates)) } } for index, candidate in enumerate(local_candidates): candidate_node = ICEHandler._candidate_to_dict(candidate) ice_config['Candidates'].update({str(index): candidate_node}) return ice_config @staticmethod def parse_remote_config( ice_config: dict ) -> Tuple[List[RTCIceCandidate], RTCIceParameters]: candidate_nodes: dict = ice_config.get('Candidates') if not candidate_nodes: raise Exception( 'parse_remote_config: Invalid input, no Candidates node found') remote_params = RTCIceParameters(ice_config.get('Username'), ice_config.get('Password')) candidates: List[RTCIceCandidate] = [] candidate_count = int(candidate_nodes.get('count')) for i in range(candidate_count): candidate_node: dict = candidate_nodes.get(str(i)) candidate = ICEHandler._dict_to_candidate(candidate_node) candidates.append(candidate) return candidates, remote_params
def __init__(self): self._gatherer = RTCIceGatherer() self._transport = RTCIceTransport(self._gatherer)
async def _negotiate(self, initiator): """ (*Coroutine*) Handle the establishment of the WebRTC peer connection. """ ice_servers = [ RTCIceServer('stun:stun.l.google.com:19302'), RTCIceServer('stun:stun2.l.google.com:19302'), RTCIceServer('stun:stunserver.org:3478') ] self._pc = RTCPeerConnection(RTCConfiguration(ice_servers)) async def add_datachannel_listeners(): """ Set the listeners to handle data channel events """ @self._datachannel.on('message') async def on_message(message): try: data = json.loads(message) except: raise TypeError('Received an invalid json message data') self._data = data try: for handler in self._data_handlers: if inspect.iscoroutinefunction(handler): await handler(data) else: handler(data) except Exception as e: logging.exception(e, traceback.format_exc()) raise e @self._datachannel.on('close') async def on_close(): if self.readyState == PeerState.CONNECTED: logging.info('Datachannel lost, disconnecting...') self.disconnection_event.set() @self._datachannel.on('error') async def on_error(error): logging.error('Datachannel error: ' + str(error)) self.disconnection_event.set() @self._pc.on('track') def on_track(track): """ Set the consumer or destination of the incomming video and audio tracks """ logging.info('Track %s received' % track.kind) if track.kind == 'audio': #webrtc_connection.addTrack(player.audio) #recorder.addTrack(track) pass elif track.kind == 'video': #local_video = VideoTransformTrack(track, transform=signal['video_transform']) #webrtc_connection.addTrack(local_video) if self._frame_consumer_feeder: self._track_consumer_task = asyncio.create_task( self._frame_consumer_feeder.feed_with(track)) @track.on('ended') async def on_ended(): logging.info('Remote track %s ended' % track.kind) if self.readyState == PeerState.CONNECTED: logging.info('Remote media track ended, disconnecting...') self.disconnection_event.set() #await recorder.stop() @self._pc.on('iceconnectionstatechange') async def on_iceconnectionstatechange(): """ Monitor the ICE connection state """ logging.info('ICE connection state of peer (%s) is %s', self.id, self._pc.iceConnectionState) if self._pc.iceConnectionState == 'failed': self.disconnection_event.set() elif self._pc.iceConnectionState == 'completed': # self._set_readyState(PeerState.CONNECTED) pass # Add media tracks if self._media_source: if self._media_source_format: self._player = MediaPlayer(self._media_source, format=self._media_source_format) else: self._player = MediaPlayer(self._media_source) if self._player.audio: self._pc.addTrack(self._player.audio) if self._player.video: self._pc.addTrack(self._player.video) @self._player.video.on('ended') async def on_ended(): logging.info('Local track %s ended' % self._player.video.kind) if self.readyState == PeerState.CONNECTED: logging.info('disconnecting...') self.disconnection_event.set() logging.info('Video player track added') elif self._frame_generator: if inspect.isgeneratorfunction(self._frame_generator): self._pc.addTrack( FrameGeneratorTrack(self._frame_generator, frame_rate=self._frame_rate)) logging.info('Video frame generator track added') else: logging.info('No video track to add') if initiator: logging.info('Initiating peer connection...') do = self._datachannel_options if do: self._datachannel = self._pc.createDataChannel( do['label'], do['maxPacketLifeTime'], do['maxRetransmits'], do['ordered'], do['protocol']) else: self._datachannel = self._pc.createDataChannel('data_channel') await self._pc.setLocalDescription(await self._pc.createOffer()) signal = { 'sdp': self._pc.localDescription.sdp, 'type': self._pc.localDescription.type } await self._send(signal) signal = await self._get_signal() if signal['type'] != 'answer': raise Exception('Expected answer from remote peer', signal) answer = RTCSessionDescription(sdp=signal['sdp'], type=signal['type']) await self._pc.setRemoteDescription(answer) @self._datachannel.on('open') async def on_open(): self._set_readyState(PeerState.CONNECTED) await add_datachannel_listeners() pass #asyncio.ensure_future(send_pings()) else: logging.info('Waiting for peer connection...') @self._pc.on('datachannel') async def on_datachannel(channel): self._datachannel = channel self._set_readyState(PeerState.CONNECTED) await add_datachannel_listeners() signal = await self._get_signal() if signal['type'] != 'offer': raise Exception('Expected offer from remote peer', signal) offer = RTCSessionDescription(sdp=signal['sdp'], type=signal['type']) await self._pc.setRemoteDescription(offer) answer = await self._pc.createAnswer() await self._pc.setLocalDescription(answer) answer = { 'sdp': self._pc.localDescription.sdp, 'type': self._pc.localDescription.type } await self._send(answer) logging.info('starting _handle_candidates_task...') self._handle_candidates_task = asyncio.create_task( self._handle_ice_candidates()) logging.info('sending local ice candidates...') # ice_servers = RTCIceGatherer.getDefaultIceServers() logging.debug(f'ice_servers: {ice_servers}') ice_gatherer = RTCIceGatherer(ice_servers) local_candidates = ice_gatherer.getLocalCandidates() logging.debug(f'local_candidates: {local_candidates}') for candidate in local_candidates: sdp = ( f"{candidate.foundation} {candidate.component} {candidate.protocol} " f"{candidate.priority} {candidate.ip} {candidate.port} typ {candidate.type}" ) if candidate.relatedAddress is not None: sdp += f" raddr {candidate.relatedAddress}" if candidate.relatedPort is not None: sdp += f" rport {candidate.relatedPort}" if candidate.tcpType is not None: sdp += f" tcptype {candidate.tcpType}" message = { "candidate": "candidate:" + sdp, "id": candidate.sdpMid, "label": candidate.sdpMLineIndex, "type": "candidate", } logging.info(message) await self._send(message) while self.readyState == PeerState.CONNECTING: await asyncio.sleep(0.2) if self._track_consumer_task: logging.info('starting _remote_track_monitor_task...') self._remote_track_monitor_task = asyncio.create_task( self._remote_track_monitor()) self._connection_monitor_task = asyncio.create_task( self._connection_monitor())