Esempio n. 1
0
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
Esempio n. 2
0
    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())