async def receive(self, trackId: str, kind: MediaKind, rtpParameters: RtpParameters) -> HandlerReceiveResult: options = HandlerReceiveOptions(trackId=trackId, kind=kind, rtpParameters=rtpParameters) self._assertRecvDirection() logging.debug( f'receive() [trackId:{options.trackId}, kind:{options.kind}]') localId = options.rtpParameters.mid if options.rtpParameters.mid != None else str( len(self._mapMidTransceiver)) self.remoteSdp.receive(mid=localId, kind=options.kind, offerRtpParameters=options.rtpParameters, streamId=options.rtpParameters.rtcp.cname, trackId=options.trackId) offer: RTCSessionDescription = RTCSessionDescription( type='offer', sdp=self.remoteSdp.getSdp()) logging.debug( f'receive() | calling pc.setRemoteDescription() [offer:{offer}]') await self.pc.setRemoteDescription(offer) answer: RTCSessionDescription = await self.pc.createAnswer() localSdpDict = sdp_transform.parse(answer.sdp) answerMediaDict = [ m for m in localSdpDict.get('media') if str(m.get('mid')) == localId ][0] # May need to modify codec parameters in the answer based on codec # parameters in the offer. applyCodecParameters(offerRtpParameters=options.rtpParameters, answerMediaDict=answerMediaDict) answer = RTCSessionDescription(type='answer', sdp=sdp_transform.write(localSdpDict)) if not self._transportReady: await self._setupTransport(localDtlsRole='client', localSdpDict=localSdpDict) logging.debug( f'receive() | calling pc.setLocalDescription() [answer:{answer}]') await self.pc.setLocalDescription(answer) transceivers = [ t for t in self.pc.getTransceivers() if t.mid == localId ] if not transceivers: raise Exception('new RTCRtpTransceiver not found') # Store in the map. transceiver = transceivers[0] self._mapMidTransceiver[localId] = transceiver return HandlerReceiveResult(localId=localId, track=transceiver.receiver.track, rtpReceiver=transceiver.receiver)
async def _setupTransport(self, localDtlsRole: DtlsRole, localSdpDict: dict = {}): if localSdpDict == {}: localSdpDict = sdp_transform.parse(self.pc.localDescription.sdp) # Get our local DTLS parameters. dtlsParameters: DtlsParameters = extractDtlsParameters(localSdpDict) # Set our DTLS role. dtlsParameters.role = localDtlsRole # Update the remote DTLS role in the SDP. self.remoteSdp.updateDtlsRole('server' if localDtlsRole == 'client' else 'client') # Need to tell the remote transport about our parameters. await self.emit_for_results('@connect', dtlsParameters) self._transportReady = True
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 receiveDataChannel( self, sctpStreamParameters: SctpStreamParameters, label: Optional[str] = None, protocol: Optional[str] = None) -> HandlerReceiveDataChannelResult: options = HandlerReceiveDataChannelOptions( sctpStreamParameters=sctpStreamParameters, label=label, protocol=protocol) self._assertRecvDirection() logging.debug( f'[receiveDataChannel() [options:{options.sctpStreamParameters}]]') dataChannel = self.pc.createDataChannel( label=options.label, maxPacketLifeTime=options.sctpStreamParameters.maxPacketLifeTime, maxRetransmits=options.sctpStreamParameters.maxRetransmits, ordered=options.sctpStreamParameters.ordered, protocol=options.protocol, negotiated=True, id=options.sctpStreamParameters.streamId) # If this is the first DataChannel we need to create the SDP offer with # m=application section. if not self._hasDataChannelMediaSection: self.remoteSdp.receiveSctpAssociation() offer: RTCSessionDescription = RTCSessionDescription( type='offer', sdp=self.remoteSdp.getSdp()) logging.debug( f'receiveDataChannel() | calling pc.setRemoteDescription() [offer:{offer}]' ) await self.pc.setRemoteDescription(offer) answer = await self.pc.createAnswer() if not self._transportReady: localSdpDict = sdp_transform.parse(answer.sdp) await self._setupTransport(localDtlsRole='client', localSdpDict=localSdpDict) logging.debug( f'receiveDataChannel() | calling pc.setRemoteDescription() [answer:{answer}]' ) await self.pc.setLocalDescription(answer) self._hasDataChannelMediaSection = True return HandlerReceiveDataChannelResult(dataChannel=dataChannel)
async def sendDataChannel( self, streamId: Optional[int] = None, ordered: Optional[bool] = True, maxPacketLifeTime: Optional[int] = None, maxRetransmits: Optional[int] = None, priority: Optional[Literal['very-low', 'low', 'medium', 'high']] = None, label: Optional[str] = None, protocol: Optional[str] = None) -> HandlerSendDataChannelResult: if streamId == None: streamId = self._nextSendSctpStreamId options = SctpStreamParameters(streamId=streamId, ordered=ordered, maxPacketLifeTime=maxPacketLifeTime, maxRetransmits=maxRetransmits, priority=priority, label=label, protocol=protocol) self._assertSendDirection() logging.debug('sendDataChannel()') dataChannel = self.pc.createDataChannel( label=options.label, maxPacketLifeTime=options.maxPacketLifeTime, ordered=options.ordered, protocol=options.protocol, negotiated=True, id=self._nextSendSctpStreamId) # Increase next id. self._nextSendSctpStreamId = (self._nextSendSctpStreamId + 1) % SCTP_NUM_STREAMS.get('MIS', 1) # If this is the first DataChannel we need to create the SDP answer with # m=application section. if not self._hasDataChannelMediaSection: offer: RTCSessionDescription = await self.pc.createOffer() localSdpDict = sdp_transform.parse(offer.sdp) offerMediaDicts = [ m for m in localSdpDict.get('media') if m.get('type') == 'application' ] if not offerMediaDicts: raise Exception('No datachannel') offerMediaDict = offerMediaDicts[0] if not self._transportReady: await self._setupTransport(localDtlsRole='server', localSdpDict=localSdpDict) logging.debug( f'sendDataChannel() | calling pc.setLocalDescription() [offer:{offer}]' ) await self.pc.setLocalDescription(offer) self.remoteSdp.sendSctpAssociation(offerMediaDict=offerMediaDict) answer: RTCSessionDescription = RTCSessionDescription( type='answer', sdp=self.remoteSdp.getSdp()) logging.debug( f'sendDataChannel() | calling pc.setRemoteDescription() [answer:{answer}]' ) await self.pc.setRemoteDescription(answer) self._hasDataChannelMediaSection = True return HandlerSendDataChannelResult(dataChannel=dataChannel, sctpStreamParameters=options)
async def send( self, track: MediaStreamTrack, encodings: List[RtpEncodingParameters] = [], codecOptions: Optional[ProducerCodecOptions] = None, codec: Optional[RtpCodecCapability] = None) -> HandlerSendResult: options = HandlerSendOptions(track=track, encodings=encodings, codecOptions=codecOptions, codec=codec) self._assertSendDirection() logging.debug( f'send() [kind:{options.track.kind}, track.id:{options.track.id}]') if options.encodings: for idx in range(len(options.encodings)): options.encodings[idx].rid = f'r{idx}' sendingRtpParameters: RtpParameters = self._sendingRtpParametersByKind[ options.track.kind].copy(deep=True) sendingRtpParameters.codecs = reduceCodecs(sendingRtpParameters.codecs, options.codec) sendingRemoteRtpParameters: RtpParameters = self._sendingRemoteRtpParametersByKind[ options.track.kind].copy(deep=True) sendingRemoteRtpParameters.codecs = reduceCodecs( sendingRemoteRtpParameters.codecs, options.codec) mediaSectionIdx = self.remoteSdp.getNextMediaSectionIdx() transceiver = self.pc.addTransceiver(options.track, direction='sendonly') offer: RTCSessionDescription = await self.pc.createOffer() offerMediaDict: dict localSdpDict = sdp_transform.parse(offer.sdp) if not self._transportReady: await self._setupTransport(localDtlsRole='server', localSdpDict=localSdpDict) # Special case for VP9 with SVC. hackVp9Svc = False if options.encodings: layers = smParse(options.encodings[0].scalabilityMode if options. encodings[0].scalabilityMode else '') else: layers = smParse('') if len( options.encodings ) == 1 and layers.spatialLayers > 1 and sendingRtpParameters.codecs[ 0].mimeType.lower() == 'video/vp9': logging.debug('send() | enabling legacy simulcast for VP9 SVC') hackVp9Svc = True localSdpDict = sdp_transform.parse offerMediaDict = localSdpDict['media'][mediaSectionIdx.idx] addLegacySimulcast(offerMediaDict=offerMediaDict, numStreams=layers.spatialLayers) offer = RTCSessionDescription( type='offer', sdp=sdp_transform.write(localSdpDict)) logging.debug( f'send() | calling pc.setLocalDescription() [offer:{offer}]') await self.pc.setLocalDescription(offer) # We can now get the transceiver.mid. localId = transceiver.mid # Set MID. sendingRtpParameters.mid = localId localSdpDict = sdp_transform.parse(self.pc.localDescription.sdp) offerMediaDict = localSdpDict['media'][mediaSectionIdx.idx] logging.debug( f"send() | get offerMediaDict {offerMediaDict} \n from localSdpDict {localSdpDict['media']} index {mediaSectionIdx.idx}" ) # Set RTCP CNAME. if sendingRtpParameters.rtcp == None: sendingRtpParameters.rtcp = RtcpParameters() sendingRtpParameters.rtcp.cname = common_utils.getCname(offerMediaDict) # Set RTP encodings by parsing the SDP offer if no encodings are given. if not options.encodings: sendingRtpParameters.encodings = getRtpEncodings(offerMediaDict) # Set RTP encodings by parsing the SDP offer and complete them with given # one if just a single encoding has been given. elif len(options.encodings) == 1: newEncodings = getRtpEncodings(offerMediaDict) if newEncodings and options.encodings[0]: firstEncodingDict: dict = newEncodings[0].dict() optionsEncodingDict: dict = options.encodings[0].dict() firstEncodingDict.update(optionsEncodingDict) newEncodings[0] = RtpEncodingParameters(**firstEncodingDict) if hackVp9Svc: newEncodings = [newEncodings[0]] sendingRtpParameters.encodings = newEncodings # Otherwise if more than 1 encoding are given use them verbatim. else: sendingRtpParameters.encodings = options.encodings # If VP8 or H264 and there is effective simulcast, add scalabilityMode to # each encoding. if len(sendingRtpParameters.encodings) > 1 and ( sendingRtpParameters.codecs[0].mimeType.lower() == 'video/vp8' or sendingRtpParameters.codecs[0].mimeType.lower() == 'video/h264'): for encoding in sendingRtpParameters.encodings: encoding.scalabilityMode = 'S1T3' self.remoteSdp.send(offerMediaDict=offerMediaDict, reuseMid=mediaSectionIdx.reuseMid, offerRtpParameters=sendingRtpParameters, answerRtpParameters=sendingRemoteRtpParameters, codecOptions=options.codecOptions, extmapAllowMixed=True) answer: RTCSessionDescription = RTCSessionDescription( type='answer', sdp=self.remoteSdp.getSdp()) logging.debug( f'send() | calling pc.setRemoteDescription() [answer:{answer}]') await self.pc.setRemoteDescription(answer) # Store in the map. self._mapMidTransceiver[localId] = transceiver return HandlerSendResult(localId=localId, rtpParameters=sendingRtpParameters, rtpSender=transceiver.sender)