/// <summary> /// Create a Router. /// </summary> public async Task <Router?> CreateRouterAsync(RouterOptions routerOptions) { if (_closed) { return(null); } await _closeLock.WaitAsync(); try { if (_closed) { return(null); } _logger.LogDebug("CreateRouterAsync()"); // This may throw. var rtpCapabilities = ORTC.GenerateRouterRtpCapabilities(routerOptions.MediaCodecs); var @internal = new { RouterId = Guid.NewGuid().ToString() }; await _channel.RequestAsync(MethodId.WORKER_CREATE_ROUTER, @internal); var router = new Router(_loggerFactory, @internal.RouterId, rtpCapabilities, _channel, _payloadChannel, AppData); lock (_routersLock) { _routers.Add(router); } router.On("@close", _ => { lock (_routersLock) { _routers.Remove(router); } return(Task.CompletedTask); }); // Emit observer event. Observer.Emit("newrouter", router); return(router); } finally { _closeLock.Set(); } }
/// <summary> /// Check whether the given RTP capabilities can consume the given Producer. /// </summary> public bool CanConsume(string producerId, RtpCapabilities rtpCapabilities) { if (!_producers.TryGetValue(producerId, out Producer producer)) { _logger.LogError($"CanConsume() | Producer with id {producerId} not found"); return(false); } try { return(ORTC.CanConsume(producer.ConsumableRtpParameters, rtpCapabilities)); } catch (Exception ex) { _logger.LogError(ex, "CanConsume() | unexpected error"); return(false); } }
/// <summary> /// Create a Router. /// </summary> public async Task <Router> CreateRouter(RouterOptions routerOptions) { _logger.LogDebug("CreateRouter()"); // This may throw. var rtpCapabilities = ORTC.GenerateRouterRtpCapabilities(routerOptions.MediaCodecs); var @internal = new { RouterId = Guid.NewGuid().ToString() }; await _channel.RequestAsync(MethodId.WORKER_CREATE_ROUTER, @internal); var router = new Router(_loggerFactory, @internal.RouterId, rtpCapabilities, _channel, AppData); _routers.Add(router); router.On("@close", _ => _routers.Remove(router)); // Emit observer event. Observer.Emit("newrouter", router); return(router); }
/// <summary> /// Create a Consumer. /// </summary> /// <param name="consumerOptions"></param> /// <returns></returns> public override async Task <Consumer> ConsumeAsync(ConsumerOptions consumerOptions) { _logger.LogDebug("ConsumeAsync()"); if (consumerOptions.ProducerId.IsNullOrWhiteSpace()) { throw new Exception("missing producerId"); } var producer = GetProducerById(consumerOptions.ProducerId); if (producer == null) { throw new Exception($"Producer with id {consumerOptions.ProducerId} not found"); } // This may throw. var rtpParameters = ORTC.GetPipeConsumerRtpParameters(producer.ConsumableRtpParameters, Rtx); var @internal = new ConsumerInternalData ( Internal.RouterId, Internal.TransportId, consumerOptions.ProducerId, Guid.NewGuid().ToString() ); var reqData = new { producer.Kind, RtpParameters = rtpParameters, Type = ConsumerType.Pipe, ConsumableRtpEncodings = producer.ConsumableRtpParameters.Encodings, }; var status = await Channel.RequestAsync(MethodId.TRANSPORT_CONSUME, @internal, reqData); var responseData = JsonConvert.DeserializeObject <TransportConsumeResponseData>(status !); var data = new { producer.Kind, RtpParameters = rtpParameters, Type = ConsumerType.Pipe, }; // 在 Node.js 实现中, 创建 Consumer 对象时没提供 score 和 preferredLayers 参数,且 score = { score: 10, producerScore: 10 }。 var consumer = new Consumer(_loggerFactory, @internal, data.Kind, data.RtpParameters, data.Type, Channel, AppData, responseData.Paused, responseData.ProducerPaused, responseData.Score, responseData.PreferredLayers); Consumers[consumer.Internal.ConsumerId] = consumer; consumer.On("@close", _ => Consumers.Remove(consumer.Internal.ConsumerId)); consumer.On("@producerclose", _ => Consumers.Remove(consumer.Internal.ConsumerId)); // Emit observer event. Observer.Emit("newconsumer", consumer); return(consumer); }
/// <summary> /// Create a DataProducer. /// </summary> /// <returns></returns> public async Task <DataProducer> ProduceDataAsync(DataProducerOptions dataProducerOptions) { _logger.LogDebug("ProduceDataAsync()"); if (!dataProducerOptions.Id.IsNullOrWhiteSpace() && DataProducers.ContainsKey(dataProducerOptions.Id !)) { throw new Exception($"a DataProducer with same id {dataProducerOptions.Id} already exists"); } if (dataProducerOptions.Label.IsNullOrWhiteSpace()) { dataProducerOptions.Label = string.Empty; } if (dataProducerOptions.Protocol.IsNullOrWhiteSpace()) { dataProducerOptions.Protocol = string.Empty; } // This may throw. ORTC.ValidateSctpStreamParameters(dataProducerOptions.SctpStreamParameters); var @internal = new DataProducerInternalData ( Internal.RouterId, Internal.TransportId, !dataProducerOptions.Id.IsNullOrWhiteSpace() ? dataProducerOptions.Id : Guid.NewGuid().ToString() ); var reqData = new { dataProducerOptions.SctpStreamParameters, Label = dataProducerOptions.Label !, Protocol = dataProducerOptions.Protocol ! }; var status = await Channel.RequestAsync(MethodId.TRANSPORT_PRODUCE_DATA, @internal, reqData); var responseData = JsonConvert.DeserializeObject <TransportDataProduceResponseData>(status); var dataProducer = new DataProducer(_loggerFactory, @internal, responseData.SctpStreamParameters, responseData.Label, responseData.Protocol, Channel, AppData); DataProducers[dataProducer.Internal.DataProducerId] = dataProducer; dataProducer.On("@close", _ => { DataProducers.Remove(dataProducer.Internal.DataProducerId); Emit("@dataproducerclose", dataProducer); }); Emit("@newdataproducer", dataProducer); // Emit observer event. Observer.Emit("newdataproducer", dataProducer); return(dataProducer); }
/// <summary> /// Create a Consumer. /// </summary> /// <param name="consumerOptions"></param> /// <returns></returns> public virtual async Task <Consumer> ConsumeAsync(ConsumerOptions consumerOptions) { _logger.LogDebug("ConsumeAsync()"); if (consumerOptions.ProducerId.IsNullOrWhiteSpace()) { throw new ArgumentException("missing producerId"); } if (consumerOptions.RtpCapabilities == null) { throw new ArgumentException(nameof(consumerOptions.RtpCapabilities)); } if (!consumerOptions.Paused.HasValue) { consumerOptions.Paused = false; } // This may throw. ORTC.ValidateRtpCapabilities(consumerOptions.RtpCapabilities); var producer = GetProducerById(consumerOptions.ProducerId); if (producer == null) { throw new NullReferenceException($"Producer with id {consumerOptions.ProducerId} not found"); } // This may throw. var rtpParameters = ORTC.GetConsumerRtpParameters(producer.ConsumableRtpParameters, consumerOptions.RtpCapabilities); // Set MID. rtpParameters.Mid = $"{_nextMidForConsumers++}"; // We use up to 8 bytes for MID (string). if (_nextMidForConsumers == 100000000) { _logger.LogDebug($"ConsumeAsync() | reaching max MID value {_nextMidForConsumers}"); _nextMidForConsumers = 0; } var @internal = new ConsumerInternalData ( Internal.RouterId, Internal.TransportId, consumerOptions.ProducerId, Guid.NewGuid().ToString() ); var reqData = new { producer.Kind, RtpParameters = rtpParameters, producer.Type, ConsumableRtpEncodings = producer.ConsumableRtpParameters.Encodings, consumerOptions.Paused, consumerOptions.PreferredLayers }; var status = await Channel.RequestAsync(MethodId.TRANSPORT_CONSUME, @internal, reqData); var responseData = JsonConvert.DeserializeObject <TransportConsumeResponseData>(status); var data = new { producer.Kind, RtpParameters = rtpParameters, Type = (ConsumerType)producer.Type, // 注意:类型转换 }; var consumer = new Consumer(_loggerFactory, @internal, data.Kind, data.RtpParameters, data.Type, Channel, AppData, responseData.Paused, responseData.ProducerPaused, responseData.Score, responseData.PreferredLayers); Consumers[consumer.Internal.ConsumerId] = consumer; consumer.On("@close", _ => Consumers.Remove(consumer.Internal.ConsumerId)); consumer.On("@producerclose", _ => Consumers.Remove(consumer.Internal.ConsumerId)); // Emit observer event. Observer.Emit("newconsumer", consumer); return(consumer); }
/// <summary> /// Create a Producer. /// </summary> public async Task <Producer> ProduceAsync(ProducerOptions producerOptions) { _logger.LogDebug("ProduceAsync()"); if (!producerOptions.Id.IsNullOrWhiteSpace() && Producers.ContainsKey(producerOptions.Id !)) { throw new Exception($"a Producer with same id \"{ producerOptions.Id }\" already exists"); } // This may throw. ORTC.ValidateRtpParameters(producerOptions.RtpParameters); // If missing or empty encodings, add one. // TODO: (alby)注意检查这样做是否合适 // 在 mediasoup-worker 中,要求 Encodings 至少要有一个元素。 if (producerOptions.RtpParameters.Encodings.IsNullOrEmpty()) { producerOptions.RtpParameters.Encodings = new List <RtpEncodingParameters> { new RtpEncodingParameters() }; } // Don't do this in PipeTransports since there we must keep CNAME value in // each Producer. // TODO: (alby)不好的模式 if (GetType() != typeof(PipeTransport)) { // If CNAME is given and we don't have yet a CNAME for Producers in this // Transport, take it. if (_cnameForProducers.IsNullOrWhiteSpace() && producerOptions.RtpParameters.Rtcp != null && !producerOptions.RtpParameters.Rtcp.CNAME.IsNullOrWhiteSpace()) { _cnameForProducers = producerOptions.RtpParameters.Rtcp.CNAME; } // Otherwise if we don't have yet a CNAME for Producers and the RTP parameters // do not include CNAME, create a random one. else if (_cnameForProducers.IsNullOrWhiteSpace()) { _cnameForProducers = Guid.NewGuid().ToString().Substring(0, 8); } // Override Producer's CNAME. // TODO: (alby)注意检查这样做是否合适 producerOptions.RtpParameters.Rtcp = producerOptions.RtpParameters.Rtcp ?? new RtcpParameters(); producerOptions.RtpParameters.Rtcp.CNAME = _cnameForProducers; } var routerRtpCapabilities = GetRouterRtpCapabilities(); // This may throw. var rtpMapping = ORTC.GetProducerRtpParametersMapping(producerOptions.RtpParameters, routerRtpCapabilities); // This may throw. var consumableRtpParameters = ORTC.GetConsumableRtpParameters(producerOptions.Kind, producerOptions.RtpParameters, routerRtpCapabilities, rtpMapping); var @internal = new ProducerInternalData ( Internal.RouterId, Internal.TransportId, producerOptions.Id.IsNullOrWhiteSpace() ? Guid.NewGuid().ToString() : producerOptions.Id ! ); var reqData = new { producerOptions.Kind, producerOptions.RtpParameters, RtpMapping = rtpMapping, producerOptions.KeyFrameRequestDelay, producerOptions.Paused, }; var status = await Channel.RequestAsync(MethodId.TRANSPORT_PRODUCE, @internal, reqData); var responseData = JsonConvert.DeserializeObject <TransportProduceResponseData>(status); var data = new { producerOptions.Kind, producerOptions.RtpParameters, responseData.Type, ConsumableRtpParameters = consumableRtpParameters }; var producer = new Producer(_loggerFactory, @internal, data.Kind, data.RtpParameters, data.Type, data.ConsumableRtpParameters, Channel, AppData, producerOptions.Paused !.Value); Producers[producer.Internal.ProducerId] = producer; producer.On("@close", _ => { Producers.Remove(producer.Internal.ProducerId); Emit("@producerclose", producer); }); Emit("@newproducer", producer); // Emit observer event. Observer.Emit("newproducer", producer); return(producer); }