Example #1
0
async def barcode_offer(request):
    values = await request.post()

    rtc_sdp = validate_string(values["sdp"])
    rtc_type = validate_string(values["type"])

    if not rtc_sdp or not rtc_type:
        return web.Response(content_type="text/json",
                            body=generate_type_error())

    offer = RTCSessionDescription(sdp=rtc_sdp, type=rtc_type)

    rtc_peer = RTCPeerConnection()
    session = RTCSession(rtc_peer)

    rtc_peer.addTransceiver("video", "recvonly")

    @rtc_peer.on("datachannel")
    def on_datachannel(channel):
        session.data_channel = channel

        @channel.on("message")
        def on_message(message):
            if message == "start":
                session.barcode_scanning = True
            else:
                session.barcode_scanning = False

    @rtc_peer.on("track")
    def on_track(track):
        # handle video tracks
        if track.kind == "video":
            rtc_peer.addTrack(VideoTransformTrack(session, track))

        # @track.on("ended")
        # def on_ended():
        # 	print("track ended")

    # set the options that the client sent us
    await rtc_peer.setRemoteDescription(offer)

    answer = await rtc_peer.createAnswer()
    # reply to the client with the options we generated
    await rtc_peer.setLocalDescription(answer)

    return web.Response(
        content_type="text/json",
        body=json.dumps({
            "sdp": rtc_peer.localDescription.sdp,
            "type": rtc_peer.localDescription.type
        }),
    )
Example #2
0
    async def getNativeRtpCapabilities(self) -> RtpCapabilities:
        logging.debug('getNativeRtpCapabilities()')

        pc = RTCPeerConnection()
        for track in self._tracks:
            pc.addTrack(track)
        pc.addTransceiver('audio')
        pc.addTransceiver('video')

        offer: RTCSessionDescription = await pc.createOffer()
        await pc.close()

        sdpDict: dict = sdp_transform.parse(offer.sdp)
        nativeRtpCapabilities: RtpCapabilities = common_utils.extractRtpCapabilities(
            sdpDict)

        return nativeRtpCapabilities
Example #3
0
async def get_RTCPeer_payload():
    pc = RTCPeerConnection(
        RTCConfiguration(
            iceServers=[RTCIceServer("stun:stun.l.google.com:19302")]))

    @pc.on("track")
    async def on_track(track):
        logger.debug("Receiving %s" % track.kind)
        if track.kind == "video":
            pc.addTrack(VideoTransformTrack(track))

        @track.on("ended")
        async def on_ended():
            logger.info("Track %s ended", track.kind)

    pc.addTransceiver("video", direction="recvonly")
    offer = await pc.createOffer()
    await pc.setLocalDescription(offer)
    new_offer = pc.localDescription
    payload = {"sdp": new_offer.sdp, "type": new_offer.type}
    return (pc, json.dumps(payload, separators=(",", ":")))
Example #4
0
class Handler:
    def __init__(self,
                 handlerId: str,
                 channel: Channel,
                 loop: asyncio.AbstractEventLoop,
                 getTrack,
                 addRemoteTrack,
                 getRemoteTrack,
                 configuration: Optional[RTCConfiguration] = None) -> None:
        self._handlerId = handlerId
        self._channel = channel
        self._pc = RTCPeerConnection(configuration or None)
        # dictionary of sending transceivers mapped by given localId
        self._sendTransceivers = dict()  # type: Dict[str, RTCRtpTransceiver]
        # dictionary of dataChannelds mapped by internal id
        self._dataChannels = dict()  # type: Dict[str, RTCDataChannel]
        # function returning a sending track given a player id and a kind
        self._getTrack = getTrack
        # function to store a receiving track
        self._addRemoteTrack = addRemoteTrack
        # function returning a receiving track
        self._getRemoteTrack = getRemoteTrack

        @self._pc.on("track")  # type: ignore
        def on_track(track) -> None:
            Logger.debug(
                f"handler: ontrack [kind:{track.kind}, id:{track.id}]")

            # store it
            self._addRemoteTrack(track)

        @self._pc.on("signalingstatechange")  # type: ignore
        async def on_signalingstatechange() -> None:
            Logger.debug(
                f"handler: signalingstatechange [state:{self._pc.signalingState}]"
            )
            await self._channel.notify(self._handlerId, "signalingstatechange",
                                       self._pc.signalingState)

        @self._pc.on("icegatheringstatechange")  # type: ignore
        async def on_icegatheringstatechange() -> None:
            Logger.debug(
                f"handler: icegatheringstatechange [state:{self._pc.iceGatheringState}]"
            )
            await self._channel.notify(self._handlerId,
                                       "icegatheringstatechange",
                                       self._pc.iceGatheringState)

        @self._pc.on("iceconnectionstatechange")  # type: ignore
        async def on_iceconnectionstatechange() -> None:
            Logger.debug(
                f"handler: iceconnectionstatechange [state:{self._pc.iceConnectionState}]"
            )
            await self._channel.notify(self._handlerId,
                                       "iceconnectionstatechange",
                                       self._pc.iceConnectionState)

        async def checkDataChannelsBufferedAmount() -> None:
            while True:
                await asyncio.sleep(1)
                for dataChannelId, dataChannel in self._dataChannels.items():
                    await self._channel.notify(dataChannelId, "bufferedamount",
                                               dataChannel.bufferedAmount)

        self._dataChannelsBufferedAmountTask = loop.create_task(
            checkDataChannelsBufferedAmount())

    async def close(self) -> None:
        # stop the periodic task
        self._dataChannelsBufferedAmountTask.cancel()

        # close peerconnection
        await self._pc.close()

    def dump(self) -> Any:
        result = {
            "id": self._handlerId,
            "signalingState": self._pc.signalingState,
            "iceConnectionState": self._pc.iceConnectionState,
            "iceGatheringState": self._pc.iceGatheringState,
            "transceivers": [],
            "sendTransceivers": []
        }

        for transceiver in self._pc.getTransceivers():
            transceiverInfo = {
                "mid": transceiver.mid,
                "stopped": transceiver.stopped,
                "kind": transceiver.kind,
                "currentDirection": transceiver.currentDirection,
                "direction": transceiver.direction,
                "sender": {
                    "trackId":
                    transceiver.sender.track.id
                    if transceiver.sender.track else None
                },
                "receiver": {
                    "trackId":
                    transceiver.receiver.track.id
                    if transceiver.receiver.track else None
                }
            }
            result["transceivers"].append(transceiverInfo)

        for localId, transceiver in self._sendTransceivers.items():
            sendTransceiverInfo = {"localId": localId, "mid": transceiver.mid}
            result["sendTransceivers"].append(sendTransceiverInfo)

        return result

    async def processRequest(self, request: Request) -> Any:
        if request.method == "handler.getLocalDescription":
            localDescription = self._pc.localDescription
            if (localDescription is not None):
                return {
                    "type": localDescription.type,
                    "sdp": localDescription.sdp
                }
            else:
                return None

        elif request.method == "handler.createOffer":
            offer = await self._pc.createOffer()
            return {"type": offer.type, "sdp": offer.sdp}

        elif request.method == "handler.createAnswer":
            answer = await self._pc.createAnswer()
            return {"type": answer.type, "sdp": answer.sdp}

        elif request.method == "handler.setLocalDescription":
            data = request.data
            if isinstance(data, RTCSessionDescription):
                raise TypeError("request data not a RTCSessionDescription")

            description = RTCSessionDescription(**data)
            await self._pc.setLocalDescription(description)

        elif request.method == "handler.setRemoteDescription":
            data = request.data
            if isinstance(data, RTCSessionDescription):
                raise TypeError("request data not a RTCSessionDescription")

            description = RTCSessionDescription(**data)
            await self._pc.setRemoteDescription(description)

        elif request.method == "handler.getMid":
            data = request.data
            localId = data.get("localId")
            if localId is None:
                raise TypeError("missing data.localId")

            # raise on purpose if the key is not found
            transceiver = self._sendTransceivers[localId]
            return transceiver.mid

        elif request.method == "handler.addTrack":
            data = request.data
            localId = data.get("localId")
            if localId is None:
                raise TypeError("missing data.localId")

            kind = data["kind"]
            playerId = data.get("playerId")
            recvTrackId = data.get("recvTrackId")

            # sending a track got from a MediaPlayer
            if playerId:
                track = self._getTrack(playerId, kind)
                transceiver = self._pc.addTransceiver(track)

            # sending a track which is a remote/receiving track
            elif recvTrackId:
                track = self._getRemoteTrack(recvTrackId, kind)
                transceiver = self._pc.addTransceiver(track)

            else:
                raise TypeError("missing data.playerId or data.recvTrackId")

            # store transceiver in the dictionary
            self._sendTransceivers[localId] = transceiver

        elif request.method == "handler.removeTrack":
            data = request.data
            localId = data.get("localId")
            if localId is None:
                raise TypeError("missing data.localId")

            transceiver = self._sendTransceivers[localId]
            transceiver.direction = "inactive"
            transceiver.sender.replaceTrack(None)

            # NOTE: do not remove transceiver from the dictionary

        elif request.method == "handler.replaceTrack":
            data = request.data
            localId = data.get("localId")
            if localId is None:
                raise TypeError("missing data.localId")

            kind = data["kind"]
            playerId = data.get("playerId")
            recvTrackId = data.get("recvTrackId")
            transceiver = self._sendTransceivers[localId]

            # sending a track got from a MediaPlayer
            if playerId:
                track = self._getTrack(playerId, kind)

            # sending a track which is a remote/receiving track
            elif recvTrackId:
                track = self._getRemoteTrack(recvTrackId, kind)

            else:
                raise TypeError("missing data.playerId or data.recvTrackId")

            transceiver.sender.replaceTrack(track)

        elif request.method == "handler.getTransportStats":
            result = {}
            stats = await self._pc.getStats()
            for key in stats:
                type = stats[key].type
                if type == "inbound-rtp":
                    result[key] = self._serializeInboundStats(stats[key])
                elif type == "outbound-rtp":
                    result[key] = self._serializeOutboundStats(stats[key])
                elif type == "remote-inbound-rtp":
                    result[key] = self._serializeRemoteInboundStats(stats[key])
                elif type == "remote-outbound-rtp":
                    result[key] = self._serializeRemoteOutboundStats(
                        stats[key])
                elif type == "transport":
                    result[key] = self._serializeTransportStats(stats[key])

            return result

        elif request.method == "handler.getSenderStats":
            data = request.data
            mid = data.get("mid")
            if mid is None:
                raise TypeError("missing data.mid")

            transceiver = self._getTransceiverByMid(mid)
            sender = transceiver.sender
            result = {}
            stats = await sender.getStats()
            for key in stats:
                type = stats[key].type
                if type == "outbound-rtp":
                    result[key] = self._serializeOutboundStats(stats[key])
                elif type == "remote-inbound-rtp":
                    result[key] = self._serializeRemoteInboundStats(stats[key])
                elif type == "transport":
                    result[key] = self._serializeTransportStats(stats[key])

            return result

        elif request.method == "handler.getReceiverStats":
            data = request.data
            mid = data.get("mid")
            if mid is None:
                raise TypeError("missing data.mid")

            transceiver = self._getTransceiverByMid(mid)
            receiver = transceiver.receiver
            result = {}
            stats = await receiver.getStats()
            for key in stats:
                type = stats[key].type
                if type == "inbound-rtp":
                    result[key] = self._serializeInboundStats(stats[key])
                elif type == "remote-outbound-rtp":
                    result[key] = self._serializeRemoteOutboundStats(
                        stats[key])
                elif type == "transport":
                    result[key] = self._serializeTransportStats(stats[key])

            return result

        elif request.method == "handler.createDataChannel":
            internal = request.internal
            dataChannelId = internal.get("dataChannelId")
            data = request.data
            id = data.get("id")
            ordered = data.get("ordered")
            maxPacketLifeTime = data.get("maxPacketLifeTime")
            maxRetransmits = data.get("maxRetransmits")
            label = data.get("label")
            protocol = data.get("protocol")
            dataChannel = self._pc.createDataChannel(
                negotiated=True,
                id=id,
                ordered=ordered,
                maxPacketLifeTime=maxPacketLifeTime,
                maxRetransmits=maxRetransmits,
                label=label,
                protocol=protocol)

            # store datachannel in the dictionary
            self._dataChannels[dataChannelId] = dataChannel

            @dataChannel.on("open")  # type: ignore
            async def on_open() -> None:
                await self._channel.notify(dataChannelId, "open")

            @dataChannel.on("closing")  # type: ignore
            async def on_closing() -> None:
                await self._channel.notify(dataChannelId, "closing")

            @dataChannel.on("close")  # type: ignore
            async def on_close() -> None:
                # NOTE: After calling dataChannel.close() aiortc emits "close" event
                # on the dataChannel. Probably it shouldn't do it. So caution.
                try:
                    del self._dataChannels[dataChannelId]
                    await self._channel.notify(dataChannelId, "close")
                except KeyError:
                    pass

            @dataChannel.on("message")  # type: ignore
            async def on_message(message) -> None:
                if isinstance(message, str):
                    await self._channel.notify(dataChannelId, "message",
                                               message)
                if isinstance(message, bytes):
                    message_bytes = base64.b64encode(message)
                    await self._channel.notify(dataChannelId, "binary",
                                               str(message_bytes))

            @dataChannel.on("bufferedamountlow")  # type: ignore
            async def on_bufferedamountlow() -> None:
                await self._channel.notify(dataChannelId, "bufferedamountlow")

            return {
                "streamId":
                dataChannel.id,
                "ordered":
                dataChannel.ordered,
                "maxPacketLifeTime":
                dataChannel.maxPacketLifeTime,
                "maxRetransmits":
                dataChannel.maxRetransmits,
                "label":
                dataChannel.label,
                "protocol":
                dataChannel.protocol,
                # status fields
                "readyState":
                dataChannel.readyState,
                "bufferedAmount":
                dataChannel.bufferedAmount,
                "bufferedAmountLowThreshold":
                dataChannel.bufferedAmountLowThreshold
            }

        else:
            raise TypeError("unknown request method")

    async def processNotification(self, notification: Notification) -> None:
        if notification.event == "enableTrack":
            Logger.warning("handler: enabling track not implemented")

        elif notification.event == "disableTrack":
            Logger.warning("handler: disabling track not implemented")

        elif notification.event == "datachannel.send":
            internal = notification.internal
            dataChannelId = internal.get("dataChannelId")
            if dataChannelId is None:
                raise TypeError("missing internal.dataChannelId")

            data = notification.data
            dataChannel = self._dataChannels[dataChannelId]
            dataChannel.send(data)

            # Good moment to update bufferedAmount in Node.js side
            await self._channel.notify(dataChannelId, "bufferedamount",
                                       dataChannel.bufferedAmount)

        elif notification.event == "datachannel.sendBinary":
            internal = notification.internal
            dataChannelId = internal.get("dataChannelId")
            if dataChannelId is None:
                raise TypeError("missing internal.dataChannelId")

            data = notification.data
            dataChannel = self._dataChannels[dataChannelId]
            dataChannel.send(base64.b64decode(data))

            # Good moment to update bufferedAmount in Node.js side
            await self._channel.notify(dataChannelId, "bufferedamount",
                                       dataChannel.bufferedAmount)

        elif notification.event == "datachannel.close":
            internal = notification.internal
            dataChannelId = internal.get("dataChannelId")
            if dataChannelId is None:
                raise TypeError("missing internal.dataChannelId")

            dataChannel = self._dataChannels.get(dataChannelId)
            if dataChannel is None:
                return

            # NOTE: After calling dataChannel.close() aiortc emits "close" event
            # on the dataChannel. Probably it shouldn't do it. So caution.
            try:
                del self._dataChannels[dataChannelId]
            except KeyError:
                pass

            dataChannel.close()

        elif notification.event == "datachannel.setBufferedAmountLowThreshold":
            internal = notification.internal
            dataChannelId = internal.get("dataChannelId")
            if dataChannelId is None:
                raise TypeError("missing internal.dataChannelId")

            value = notification.data
            dataChannel = self._dataChannels[dataChannelId]
            dataChannel.bufferedAmountLowThreshold = value

        else:
            raise TypeError("unknown notification event")

    """
    Helper functions
    """

    def _getTransceiverByMid(self, mid: str) -> Optional[RTCRtpTransceiver]:
        return next(filter(lambda x: x.mid == mid, self._pc.getTransceivers()),
                    None)

    def _serializeInboundStats(self, stats: RTCStatsReport) -> Dict[str, Any]:
        return {
            # RTCStats
            "timestamp": stats.timestamp.timestamp(),
            "type": stats.type,
            "id": stats.id,
            # RTCStreamStats
            "ssrc": stats.ssrc,
            "kind": stats.kind,
            "transportId": stats.transportId,
            # RTCReceivedRtpStreamStats
            "packetsReceived": stats.packetsReceived,
            "packetsLost": stats.packetsLost,
            "jitter": stats.jitter
        }

    def _serializeOutboundStats(self, stats: RTCStatsReport) -> Dict[str, Any]:
        return {
            # RTCStats
            "timestamp": stats.timestamp.timestamp(),
            "type": stats.type,
            "id": stats.id,
            # RTCStreamStats
            "ssrc": stats.ssrc,
            "kind": stats.kind,
            "transportId": stats.transportId,
            # RTCSentRtpStreamStats
            "packetsSent": stats.packetsSent,
            "bytesSent": stats.bytesSent,
            # RTCOutboundRtpStreamStats
            "trackId": stats.trackId
        }

    def _serializeRemoteInboundStats(self,
                                     stats: RTCStatsReport) -> Dict[str, Any]:
        return {
            # RTCStats
            "timestamp": stats.timestamp.timestamp(),
            "type": stats.type,
            "id": stats.id,
            # RTCStreamStats
            "ssrc": stats.ssrc,
            "kind": stats.kind,
            "transportId": stats.transportId,
            # RTCReceivedRtpStreamStats
            "packetsReceived": stats.packetsReceived,
            "packetsLost": stats.packetsLost,
            "jitter": stats.jitter,
            # RTCRemoteInboundRtpStreamStats
            "roundTripTime": stats.roundTripTime,
            "fractionLost": stats.fractionLost
        }

    def _serializeRemoteOutboundStats(self,
                                      stats: RTCStatsReport) -> Dict[str, Any]:
        return {
            # RTCStats
            "timestamp": stats.timestamp.timestamp(),
            "type": stats.type,
            "id": stats.id,
            # RTCStreamStats
            "ssrc": stats.ssrc,
            "kind": stats.kind,
            "transportId": stats.transportId,
            # RTCSentRtpStreamStats
            "packetsSent": stats.packetsSent,
            "bytesSent": stats.bytesSent,
            # RTCRemoteOutboundRtpStreamStats
            "remoteTimestamp": stats.remoteTimestamp.timestamp()
        }

    def _serializeTransportStats(self,
                                 stats: RTCStatsReport) -> Dict[str, Any]:
        return {
            # RTCStats
            "timestamp": stats.timestamp.timestamp(),
            "type": stats.type,
            "id": stats.id,
            # RTCTransportStats
            "packetsSent": stats.packetsSent,
            "packetsReceived": stats.packetsReceived,
            "bytesSent": stats.bytesSent,
            "bytesReceived": stats.bytesReceived,
            "iceRole": stats.iceRole,
            "dtlsState": stats.dtlsState
        }
async def ask_stream(ask_stream, timeout):
    while True:
        await asyncio.sleep(3)
        if ask_stream == "False":
            return
        # Onko meillä streami
        if broadcast:
            return

        print("Haetaan streami")
        ## TODO: testaa onko meillä streami olemassa
        pc = RTCPeerConnection()
        pc_id = "PeerConnection(%s)" % uuid.uuid4()

        pcs.add(pc)

        def log_info(msg, *args):
            logger.info(pc_id + " " + msg, *args)

        # Videolle on kanava "track"
        pc.createDataChannel("track")
        pc.addTransceiver("video", direction="recvonly")
        # Tämä luo itse offerin oikeassa muodossa
        await pc.setLocalDescription(await pc.createOffer())
        # Asetetaan pyynnön parametreiksi
        params = {
            "sdp": pc.localDescription.sdp,
            "type": pc.localDescription.type,
            "listen_video": True
        }

        @pc.on("track")
        def on_track(track):
            log_info("Track %s received from other server", track.kind)
            if track.kind == "audio":
                pc.addTrack(player.audio)
            elif track.kind == "video":
                create_broadcast(track)
                pc.addTrack(relay.subscribe(broadcast))

            @track.on("ended")
            async def on_ended():
                log_info("Track %s ended", track.kind)
                broadcast_ended()
                coros = [pc.close() for pc in pcs]
                await asyncio.gather(*coros)
                pcs.clear()

            @track.on("oninactive")
            async def on_inactive():
                log_info("Track inactive")

        @pc.on("connectionstatechange")
        async def on_connectionstatechange():
            log_info("Connection state is %s", pc.connectionState)
            if pc.connectionState == "failed":
                broadcast_ended()
                coros = [pc.close() for pc in pcs]
                await asyncio.gather(*coros)
                pcs.clear()

        print("onko jumiss")

        # POST-pyyntö dispatcherille
        session = ClientSession()
        res = await session.post('https://localhost:8080/offer',
                                 json=params,
                                 ssl=False,
                                 timeout=3)
        if res.status == "500":
            continue
        try:
            result = await res.json()
        except:
            continue
        await session.close()
        answer = RTCSessionDescription(sdp=result["sdp"], type=result["type"])
        #print(answer.sdp)
        await pc.setRemoteDescription(answer)
Example #6
0
class RTCConnection(SubscriptionProducerConsumer):
    _log = logging.getLogger("rtcbot.RTCConnection")

    def __init__(self, defaultChannelOrdered=True, loop=None):
        super().__init__(
            directPutSubscriptionType=asyncio.Queue,
            defaultSubscriptionType=asyncio.Queue,
            logger=self._log,
        )
        self._loop = loop
        if self._loop is None:
            self._loop = asyncio.get_event_loop()

        self._dataChannels = {}

        # These allow us to easily signal when the given events happen
        self._dataChannelSubscriber = SubscriptionProducer(
            logger=self._log.getChild("dataChannelSubscriber")
        )

        self._rtc = RTCPeerConnection()
        self._rtc.on("datachannel", self._onDatachannel)
        # self._rtc.on("iceconnectionstatechange", self._onIceConnectionStateChange)
        self._rtc.on("track", self._onTrack)

        self._hasRemoteDescription = False
        self._defaultChannelOrdered = defaultChannelOrdered

        self._videoHandler = ConnectionVideoHandler(self._rtc)
        self._audioHandler = ConnectionAudioHandler(self._rtc)

    async def getLocalDescription(self, description=None):
        """
        Gets the description to send on. Creates an initial description
        if no remote description was passed, and creates a response if
        a remote was given,
        """
        if self._hasRemoteDescription or description is not None:
            # This means that we received an offer - either the remote description
            # was already set, or we passed in a description. In either case,
            # instead of initializing a new connection, we prepare a response
            if not self._hasRemoteDescription:
                await self.setRemoteDescription(description)
            self._log.debug("Creating response to connection offer")
            try:
                answer = await self._rtc.createAnswer()
            except AttributeError:
                self._log.exception(
                    "\n>>> Looks like the offer didn't include the necessary info to set up audio/video. See RTCConnection.video.offerToReceive(). <<<\n\n"
                )
                raise
            await self._rtc.setLocalDescription(answer)
            return {
                "sdp": self._rtc.localDescription.sdp,
                "type": self._rtc.localDescription.type,
            }

        # There was no remote description, which means that we are initializing the
        # connection.

        # Before starting init, we create a default data channel for the connection
        self._log.debug("Setting up default data channel")
        channel = DataChannel(
            self._rtc.createDataChannel("default", ordered=self._defaultChannelOrdered)
        )
        # Subscribe the default channel directly to our own inputs and outputs.
        # We have it listen to our own self._get, and write to our self._put_nowait
        channel.putSubscription(NoClosedSubscription(self._get))
        channel.subscribe(self._put_nowait)
        self._dataChannels[channel.name] = channel

        # Make sure we offer to receive video and audio if if isn't set up yet
        if len(self.video._senders) == 0 and self.video._offerToReceive:
            self._log.debug("Offering to receive video")
            self._rtc.addTransceiver("video", "recvonly")
        if len(self.audio._senders) == 0 and self.audio._offerToReceive:
            self._log.debug("Offering to receive audio")
            self._rtc.addTransceiver("audio", "recvonly")

        self._log.debug("Creating new connection offer")
        offer = await self._rtc.createOffer()
        await self._rtc.setLocalDescription(offer)
        return {
            "sdp": self._rtc.localDescription.sdp,
            "type": self._rtc.localDescription.type,
        }

    async def setRemoteDescription(self, description):
        self._log.debug("Setting remote connection description")
        await self._rtc.setRemoteDescription(RTCSessionDescription(**description))
        self._hasRemoteDescription = True

    def _onDatachannel(self, channel):
        """
        When a data channel comes in, adds it to the data channels, and sets up its messaging and stuff.

        """
        channel = DataChannel(channel)
        self._log.debug("Got channel: %s", channel.name)
        if channel.name == "default":
            # Subscribe the default channel directly to our own inputs and outputs.
            # We have it listen to our own self._get, and write to our self._put_nowait
            channel.putSubscription(NoClosedSubscription(self._get))
            channel.subscribe(self._put_nowait)

            # Set the default channel
            self._defaultChannel = channel

        else:
            self._dataChannelSubscriber.put_nowait(channel)
        self._dataChannels[channel.name] = channel

    def _onTrack(self, track):
        self._log.debug("Received %s track from connection", track.kind)
        if track.kind == "audio":
            self._audioHandler._onTrack(track)
        elif track.kind == "video":
            self._videoHandler._onTrack(track)

    def onDataChannel(self, callback=None):
        """
        Acts as a subscriber...
        """
        return self._dataChannelSubscriber.subscribe(callback)

    def addDataChannel(self, name, ordered=True):
        """
        Adds a data channel to the connection. Note that the RTCConnection adds a "default" channel
        automatically, which you can subscribe to directly.
        """
        self._log.debug("Adding data channel to connection")

        if name in self._dataChannels or name == "default":
            raise KeyError("Data channel %s already exists", name)

        dc = DataChannel(self._rtc.createDataChannel(name, ordered=ordered))
        self._dataChannels[name] = dc
        return dc

    def getDataChannel(self, name):
        """
        Returns the data channel with the given name. Please note that the "default" channel is considered special,
        and is not returned.
        """
        if name == "default":
            raise KeyError(
                "Default channel not available for 'get'. Use the RTCConnection's subscribe and put_nowait methods for access to it."
            )
        return self._dataChannels[name]

    @property
    def video(self):
        """
        Convenience function - you can subscribe to it to get video frames once they show up
        """
        return self._videoHandler

    @property
    def audio(self):
        """
        Convenience function - you can subscribe to it to get video frames once they show up
        """
        return self._audioHandler

    def close(self):
        """
        If the loop is running, returns a future that will close the connection. Otherwise, runs
        the loop temporarily to complete closing.
        """
        super().close()
        # And closes all tracks
        self.video.close()
        self.audio.close()

        for dc in self._dataChannels:
            self._dataChannels[dc].close()

        self._dataChannelSubscriber.close()

        if self._loop.is_running():
            self._log.debug("Loop is running - close will return a future!")
            return asyncio.ensure_future(self._rtc.close())
        else:
            self._loop.run_until_complete(self._rtc.close())
        return None

    def send(self, msg):
        """
        Send is an alias for put_nowait - makes it easier for people new to rtcbot to understand
        what is going on
        """
        self.put_nowait(msg)
    async def processRequest(request: Request) -> Any:
        Logger.debug(f"worker: processRequest() [method:{request.method}]")

        if request.method == "dump":
            result = {
                "pid": getpid(),
                "players": [],
                "handlers": []
            }

            for playerId, player in players.items():
                playerDump = {
                    "id": playerId
                }  # type: Dict[str, Any]
                if player.audio:
                    playerDump["audioTrack"] = {
                        "id": player.audio.id,
                        "kind": player.audio.kind,
                        "readyState": player.audio.readyState
                    }
                if player.video:
                    playerDump["videoTrack"] = {
                        "id": player.video.id,
                        "kind": player.video.kind,
                        "readyState": player.video.readyState
                    }
                result["players"].append(playerDump)  # type: ignore

            for handler in handlers.values():
                result["handlers"].append(handler.dump())  # type: ignore

            return result

        elif request.method == "createPlayer":
            internal = request.internal
            playerId = internal["playerId"]
            data = request.data
            player = MediaPlayer(
                data["file"],
                data["format"] if "format" in data else None,
                data["options"] if "options" in data else None
            )

            # store the player in the map
            players[playerId] = player

            result = {}
            if player.audio:
                result["audioTrackId"] = player.audio.id
            if player.video:
                result["videoTrackId"] = player.video.id
            return result

        elif request.method == "getRtpCapabilities":
            pc = RTCPeerConnection()
            pc.addTransceiver("audio", "sendonly")
            pc.addTransceiver("video", "sendonly")
            offer = await pc.createOffer()
            await pc.close()
            return offer.sdp

        elif request.method == "createHandler":
            internal = request.internal
            handlerId = internal["handlerId"]
            data = request.data

            # use RTCConfiguration if given
            jsonRtcConfiguration = data.get("rtcConfiguration")
            rtcConfiguration = None

            if jsonRtcConfiguration and "iceServers" in jsonRtcConfiguration:
                iceServers = []
                for entry in jsonRtcConfiguration["iceServers"]:
                    iceServer = RTCIceServer(
                        urls=entry.get("urls"),
                        username=entry.get("username"),
                        credential=entry.get("credential"),
                        credentialType=entry.get("credentialType")
                    )
                    iceServers.append(iceServer)
                rtcConfiguration = RTCConfiguration(iceServers)

            handler = Handler(
                handlerId,
                channel,
                loop,
                getTrack,
                addRemoteTrack,
                getRemoteTrack,
                rtcConfiguration
            )

            handlers[handlerId] = handler
            return

        else:
            internal = request.internal
            handler = handlers.get(internal["handlerId"])
            if handler is None:
                raise Exception("hander not found")

            return await handler.processRequest(request)
Example #8
0
class RTCConnection(SubscriptionProducerConsumer):
    _log = logging.getLogger("rtcbot.RTCConnection")

    def __init__(
        self,
        defaultChannelOrdered=True,
        loop=None,
        rtcConfiguration=RTCConfiguration(
            [RTCIceServer(urls="stun:stun.l.google.com:19302")]),
    ):
        super().__init__(
            directPutSubscriptionType=asyncio.Queue,
            defaultSubscriptionType=asyncio.Queue,
            logger=self._log,
        )
        self._loop = loop
        if self._loop is None:
            self._loop = asyncio.get_event_loop()

        self._dataChannels = {}

        # These allow us to easily signal when the given events happen
        self._dataChannelSubscriber = SubscriptionProducer(
            logger=self._log.getChild("dataChannelSubscriber"))
        self._rtc = RTCPeerConnection(configuration=rtcConfiguration)
        self._rtc.on("datachannel", self._onDatachannel)
        # self._rtc.on("iceconnectionstatechange", self._onIceConnectionStateChange)
        self._rtc.on("track", self._onTrack)

        self._hasRemoteDescription = False
        self._defaultChannelOrdered = defaultChannelOrdered

        self._videoHandler = ConnectionVideoHandler(self._rtc)
        self._audioHandler = ConnectionAudioHandler(self._rtc)

    async def getLocalDescription(self, description=None):
        """
        Gets the description to send on. Creates an initial description
        if no remote description was passed, and creates a response if
        a remote was given,
        """
        if self._hasRemoteDescription or description is not None:
            # This means that we received an offer - either the remote description
            # was already set, or we passed in a description. In either case,
            # instead of initializing a new connection, we prepare a response
            if not self._hasRemoteDescription:
                await self.setRemoteDescription(description)
            self._log.debug("Creating response to connection offer")
            try:
                answer = await self._rtc.createAnswer()
            except AttributeError:
                self._log.exception(
                    "\n>>> Looks like the offer didn't include the necessary info to set up audio/video. See RTCConnection.video.offerToReceive(). <<<\n\n"
                )
                raise
            await self._rtc.setLocalDescription(answer)
            return {
                "sdp": self._rtc.localDescription.sdp,
                "type": self._rtc.localDescription.type,
            }

        # There was no remote description, which means that we are initializing the
        # connection.

        # Before starting init, we create a default data channel for the connection
        self._log.debug("Setting up default data channel")
        channel = DataChannel(
            self._rtc.createDataChannel("default",
                                        ordered=self._defaultChannelOrdered))
        # Subscribe the default channel directly to our own inputs and outputs.
        # We have it listen to our own self._get, and write to our self._put_nowait
        channel.putSubscription(NoClosedSubscription(self._get))
        channel.subscribe(self._put_nowait)
        channel.onReady(lambda: self._setReady(channel.ready))
        self._dataChannels[channel.name] = channel

        # Make sure we offer to receive video and audio if if isn't set up yet with
        # all the receiving transceivers
        if len(self.video._senders) < self.video._offerToReceive:
            self._log.debug("Offering to receive video")
            for i in range(self.video._offerToReceive -
                           len(self.video._senders)):
                self._rtc.addTransceiver("video", "recvonly")
        if len(self.audio._senders) < self.audio._offerToReceive:
            self._log.debug("Offering to receive audio")
            for i in range(self.audio._offerToReceive -
                           len(self.audio._senders)):
                self._rtc.addTransceiver("audio", "recvonly")

        self._log.debug("Creating new connection offer")
        offer = await self._rtc.createOffer()
        await self._rtc.setLocalDescription(offer)
        return {
            "sdp": self._rtc.localDescription.sdp,
            "type": self._rtc.localDescription.type,
        }

    async def setRemoteDescription(self, description):
        self._log.debug("Setting remote connection description")
        await self._rtc.setRemoteDescription(
            RTCSessionDescription(**description))
        self._hasRemoteDescription = True

    def _onDatachannel(self, channel):
        """
        When a data channel comes in, adds it to the data channels, and sets up its messaging and stuff.

        """
        channel = DataChannel(channel)
        self._log.debug("Got channel: %s", channel.name)
        if channel.name == "default":
            # Subscribe the default channel directly to our own inputs and outputs.
            # We have it listen to our own self._get, and write to our self._put_nowait
            channel.putSubscription(NoClosedSubscription(self._get))
            channel.subscribe(self._put_nowait)
            channel.onReady(lambda: self._setReady(channel.ready))

            # Set the default channel
            self._defaultChannel = channel

        else:
            self._dataChannelSubscriber.put_nowait(channel)
        self._dataChannels[channel.name] = channel

    def _onTrack(self, track):
        self._log.debug("Received %s track from connection", track.kind)
        if track.kind == "audio":
            self._audioHandler._onTrack(track)
        elif track.kind == "video":
            self._videoHandler._onTrack(track)

    def onDataChannel(self, callback=None):
        """
        Acts as a subscriber...
        """
        return self._dataChannelSubscriber.subscribe(callback)

    def addDataChannel(self, name, ordered=True):
        """
        Adds a data channel to the connection. Note that the RTCConnection adds a "default" channel
        automatically, which you can subscribe to directly.
        """
        self._log.debug("Adding data channel to connection")

        if name in self._dataChannels or name == "default":
            raise KeyError("Data channel %s already exists", name)

        dc = DataChannel(self._rtc.createDataChannel(name, ordered=ordered))
        self._dataChannels[name] = dc
        return dc

    def getDataChannel(self, name):
        """
        Returns the data channel with the given name. Please note that the "default" channel is considered special,
        and is not returned.
        """
        if name == "default":
            raise KeyError(
                "Default channel not available for 'get'. Use the RTCConnection's subscribe and put_nowait methods for access to it."
            )
        return self._dataChannels[name]

    @property
    def video(self):
        """
        Convenience function - you can subscribe to it to get video frames once they show up
        """
        return self._videoHandler

    @property
    def audio(self):
        """
        Convenience function - you can subscribe to it to get audio once a stream is received
        """
        return self._audioHandler

    def close(self):
        """
        If the loop is running, returns a future that will close the connection. Otherwise, runs
        the loop temporarily to complete closing.
        """
        super().close()
        # And closes all tracks
        self.video.close()
        self.audio.close()

        for dc in self._dataChannels:
            self._dataChannels[dc].close()

        self._dataChannelSubscriber.close()

        if self._loop.is_running():
            self._log.debug("Loop is running - close will return a future!")
            return asyncio.ensure_future(self._rtc.close())
        else:
            self._loop.run_until_complete(self._rtc.close())
        return None

    def send(self, msg):
        """
        Send is an alias for put_nowait - makes it easier for people new to rtcbot to understand
        what is going on
        """
        self.put_nowait(msg)
Example #9
0
async def ask_stream(interval):
    global blackhole
    await asyncio.sleep(interval)
    print("Haetaan streami")
    pc = RTCPeerConnection()
    pc_id = "PeerConnection(%s)" % uuid.uuid4()

    pcs.add(pc)

    def log_info(msg, *args):
        logger.info(pc_id + " " + msg, *args)

    # Videolle on kanava "track"
    pc.createDataChannel("track")
    pc.addTransceiver("video", direction="recvonly")
    # Tämä luo itse offerin oikeassa muodossa
    await pc.setLocalDescription(await pc.createOffer())
    # Asetetaan pyynnön parametreiksi
    params = {
        "sdp": pc.localDescription.sdp,
        "type": pc.localDescription.type,
        "listen_video": True
    }

    @pc.on("track")
    def on_track(track):
        log_info("Track %s received from other server", track.kind)
        blackhole.addTrack(track)
        '''
        if track.kind == "audio":
            pc.addTrack(player.audio)
        elif track.kind == "video":
            create_broadcast(track)
            pc.addTrack(relay.subscribe(broadcast))
        '''
        @track.on("ended")
        async def on_ended():
            log_info("Track %s ended", track.kind)

    @pc.on("connectionstatechange")
    async def on_connectionstatechange():
        log_info("Connection state is %s", pc.connectionState)
        if pc.connectionState == "failed":
            await pc.close()
            pcs.discard(pc)

    status = False
    session = ClientSession()
    # POST-pyyntö dispatcherille
    while not status:
        status = True
        await asyncio.sleep(1)
        try:
            res = await session.post('https://localhost:8080/offer',
                                     json=params,
                                     ssl=False,
                                     timeout=3)
        except:
            status = False
            continue
        print("onko jumissa")
        if res.status == "500":
            status = False
            continue
        try:
            result = await res.json()
        except:
            status = False
            continue
    answer = RTCSessionDescription(sdp=result["sdp"], type=result["type"])
    await session.close()
    #print(answer.sdp)
    await pc.setRemoteDescription(answer)