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 }), )
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
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=(",", ":")))
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)
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)
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)
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)