public bool ChangeStream(StreamDescription stream) { if (stream == null) { throw new ArgumentNullException(nameof(stream), "stream cannot be null"); } if (availableStreams.Count <= stream.Id) { throw new ArgumentOutOfRangeException(); } var newMedia = availableStreams[stream.Id].Media; var newRepresentation = availableStreams[stream.Id].Representation; var newStream = new DashStream(newMedia, newRepresentation); if (currentStream.Media.Type.Value != newMedia.Type.Value) { throw new ArgumentException("wrong media type"); } if (newStream.Equals(currentStream)) { Logger.Info($"Selected stream {stream.Id} {stream.Description} already playing. Not changing."); return(false); } SetStream(newStream); Logger.Info($"Stream {stream.Id} {stream.Description} set."); return(true); }
private void StartPipeline(DashStream newStream = null) { if (pipelineStarted) { return; } if (newStream != null) { currentStream = newStream; Logger.Info($"{StreamType}: Dash pipeline start."); Logger.Info($"{StreamType}: Media: {currentStream.Media}"); Logger.Info($"{StreamType}: {currentStream.Representation}"); dashClient.UpdateRepresentation(currentStream.Representation); ParseDrms(currentStream.Media); PushMetaDataConfiguration(); } var fullInitRequired = (newStream != null) || DisableAdaptiveStreaming; demuxerController.StartForEs(); dashClient.Start(fullInitRequired); pipelineStarted = true; }
public void AdaptToNetConditions() { // Treat adaptive streaming as "non critical". If cannot be processed now due to lock, attempt // will be done at next iteration. if (!Monitor.TryEnter(switchStreamLock)) { return; } try { if (DisableAdaptiveStreaming) { return; } if (currentStream == null && pendingStream == null) { return; } var streamToAdapt = pendingStream ?? currentStream; if (streamToAdapt.Representation.Bandwidth.HasValue == false) { return; } var currentThroughput = throughputHistory.GetAverageThroughput(); if (Math.Abs(currentThroughput) < 0.1) { return; } Logger.Debug("Adaptation values:"); Logger.Debug(" current throughput: " + currentThroughput); Logger.Debug(" current stream bandwidth: " + streamToAdapt.Representation.Bandwidth.Value); // availableStreams is sorted array by descending bandwidth var stream = availableStreams.FirstOrDefault(o => o.Representation.Bandwidth <= currentThroughput) ?? availableStreams.Last(); if (stream.Representation.Bandwidth == streamToAdapt.Representation.Bandwidth) { return; } Logger.Info("Changing stream to bandwidth: " + stream.Representation.Bandwidth); pendingStream = stream; } finally { Monitor.Exit(switchStreamLock); } }
public void Stop() { if (!pipelineStarted) { return; } dashClient.Stop(); ResetPipeline(); currentStream = null; pendingStream = null; }
public async Task SwitchStreamIfNeeded() { // Access serialization is needed. // SwitchStreamIfNeeded can be called from DashDataProvider or // timer based manifest reload (different threads) which can cause // null object reference if pendingStream is nulled AFTER another thread // passed pendingStream null check. // // Stream switching does not need to be serialized. If stream switch is already // in progress, next stream switch can safely be ignored, thus use of monitor // rather then lock. if (!Monitor.TryEnter(switchStreamLock)) { return; } try { if (pendingStream == null) { return; } Logger.Info($"{StreamType}"); if (currentStream == null) { StartPipeline(pendingStream); pendingStream = null; return; } if (!CanSwitchStream()) { return; } await FlushPipeline(); StartPipeline(pendingStream); pendingStream = null; } finally { Monitor.Exit(switchStreamLock); } }
private void SetStream(DashStream newStream) { Logger.Info(""); Monitor.Enter(switchStreamLock); try { DisableAdaptiveStreaming = true; currentStream = newStream; pendingStream = null; dashClient.UpdateRepresentation(currentStream.Representation); } finally { Monitor.Exit(switchStreamLock); } }
private void SetStream(DashStream newStream) { Logger.Info(""); Monitor.Enter(switchStreamLock); try { ResetPipeline(); DisableAdaptiveStreaming = true; pendingStream = null; StartPipeline(newStream); } finally { Monitor.Exit(switchStreamLock); } }
private static string CreateStreamDescription(DashStream stream) { var description = ""; if (!string.IsNullOrEmpty(stream.Media.Lang)) { description += stream.Media.Lang; } if (stream.Representation.Height.HasValue && stream.Representation.Width.HasValue) { description += $" ( {stream.Representation.Width}x{stream.Representation.Height} )"; } if (stream.Representation.NumChannels.HasValue) { description += $" ( {stream.Representation.NumChannels} ch )"; } return(description); }
public void Stop() { if (!pipelineStarted) { return; } dashClient.Stop(); ResetPipeline(); demuxerClock.Reset(); lastPushedClock.Reset(); lastSeek = TimeSpan.Zero; trimOffset = null; currentStream = null; pendingStream = null; }
public void ChangeStream(StreamDescription stream) { Logger.Info(""); if (stream == null) { throw new ArgumentNullException(nameof(stream), "stream cannot be null"); } if (availableStreams.Count <= stream.Id) { throw new ArgumentOutOfRangeException(); } var newMedia = availableStreams[stream.Id].Media; var newRepresentation = availableStreams[stream.Id].Representation; // Share lock with switchStreamIfNeeded. Change stream may happen on a separate thread. // As such, we do not want 2 starts happening // - One as a manual stream selection // - One as a adaptive stream switching. // lock (switchStreamLock) { var newStream = new DashStream(newMedia, newRepresentation); if (currentStream.Media.Type.Value != newMedia.Type.Value) { throw new ArgumentException("wrong media type"); } DisableAdaptiveStreaming = true; FlushPipeline().ContinueWith(task => { if (task.Status == TaskStatus.RanToCompletion) { StartPipeline(newStream); } }, TaskScheduler.FromCurrentSynchronizationContext()); } }
public void AdaptToNetConditions() { if (DisableAdaptiveStreaming) { return; } if (currentStream == null && pendingStream == null) { return; } var streamToAdapt = pendingStream ?? currentStream; if (streamToAdapt.Representation.Bandwidth.HasValue == false) { return; } var currentThroughput = throughputHistory.GetAverageThroughput(); if (Math.Abs(currentThroughput) < 0.1) { return; } Logger.Debug("Adaptation values:"); Logger.Debug(" current throughput: " + currentThroughput); Logger.Debug(" current stream bandwidth: " + streamToAdapt.Representation.Bandwidth.Value); // availableStreams is sorted array by descending bandwidth var stream = availableStreams.FirstOrDefault(o => o.Representation.Bandwidth <= currentThroughput) ?? availableStreams.Last(); if (stream.Representation.Bandwidth == streamToAdapt.Representation.Bandwidth) { return; } Logger.Info("Changing stream to bandwidth: " + stream.Representation.Bandwidth); pendingStream = stream; }
public void UpdateMedia(Period period) { var media = period.GetAdaptationSets(ToMediaType(StreamType)); if (!media.Any()) { throw new ArgumentOutOfRangeException($"{StreamType}: No media in period {period}"); } lock (switchStreamLock) { if (currentStream != null) { var currentMedia = media.Count == 1 ? media.First() : media.FirstOrDefault(o => o.Id == currentStream.Media.Id); var currentRepresentation = currentMedia?.Representations.FirstOrDefault(o => o.Id == currentStream.Representation.Id); if (currentRepresentation != null) { GetAvailableStreams(media, currentMedia); // Media Preparation (Call to Initialize) is done upon assignment to pendingStream. currentStream = new DashStream(currentMedia, currentRepresentation); dashClient.UpdateRepresentation(currentStream.Representation); PushMetaDataConfiguration(); return; } } var defaultMedia = GetDefaultMedia(media); GetAvailableStreams(media, defaultMedia); // get first element of sorted array var representation = defaultMedia.Representations.OrderByDescending(o => o.Bandwidth).Last(); pendingStream = new DashStream(defaultMedia, representation); } }
private void StartPipeline(DashStream newStream = null) { if (pipelineStarted) { return; } if (newStream != null) { currentStream = newStream; Logger.Info($"{StreamType}: Dash pipeline start."); Logger.Info($"{StreamType}: Media: {currentStream.Media}"); Logger.Info($"{StreamType}: {currentStream.Representation}"); dashClient.UpdateRepresentation(currentStream.Representation); ParseDrms(currentStream.Media); } if (!trimOffset.HasValue) { trimOffset = currentStream.Representation.AlignedTrimOffset; } var fullInitRequired = (newStream != null); demuxerController.StartForEs(); dashClient.Start(fullInitRequired); pipelineStarted = true; if (newStream != null) { PushMetaDataConfiguration(); } }