diff options
Diffstat (limited to 'MediaBrowser.Server.Implementations/LiveTv')
5 files changed, 110 insertions, 51 deletions
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 3ec64a017b..0d49607953 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -491,6 +491,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { try { + var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); + + result.Item2.Release(); + + return result.Item1; + } + catch (Exception e) + { + _logger.ErrorException("Error getting channel stream", e); + } + } + + throw new ApplicationException("Tuner not found."); + } + + private async Task<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken) + { + _logger.Info("Streaming Channel " + channelId); + + foreach (var hostInstance in _liveTvManager.TunerHosts) + { + try + { return await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); } catch (Exception e) @@ -653,40 +676,56 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV try { - var mediaStreamInfo = await GetChannelStream(timer.ChannelId, null, CancellationToken.None); - - // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg - await Task.Delay(3000, cancellationToken).ConfigureAwait(false); + var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None); + var mediaStreamInfo = result.Item1; + var isResourceOpen = true; - var duration = recordingEndDate - DateTime.UtcNow; - - HttpRequestOptions httpRequestOptions = new HttpRequestOptions() + // Unfortunately due to the semaphore we have to have a nested try/finally + try { - Url = mediaStreamInfo.Path - }; + // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg + await Task.Delay(3000, cancellationToken).ConfigureAwait(false); + + var duration = recordingEndDate - DateTime.UtcNow; - recording.Path = recordPath; - recording.Status = RecordingStatus.InProgress; - recording.DateLastUpdated = DateTime.UtcNow; - _recordingProvider.Update(recording); + HttpRequestOptions httpRequestOptions = new HttpRequestOptions() + { + Url = mediaStreamInfo.Path + }; + + recording.Path = recordPath; + recording.Status = RecordingStatus.InProgress; + recording.DateLastUpdated = DateTime.UtcNow; + _recordingProvider.Update(recording); + + _logger.Info("Beginning recording."); + + httpRequestOptions.BufferContent = false; + var durationToken = new CancellationTokenSource(duration); + var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; + httpRequestOptions.CancellationToken = linkedToken; + _logger.Info("Writing file to path: " + recordPath); + using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET")) + { + using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + result.Item2.Release(); + isResourceOpen = false; - _logger.Info("Beginning recording."); + await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken); + } + } - httpRequestOptions.BufferContent = false; - var durationToken = new CancellationTokenSource(duration); - var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - httpRequestOptions.CancellationToken = linkedToken; - _logger.Info("Writing file to path: " + recordPath); - using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET")) + recording.Status = RecordingStatus.Completed; + _logger.Info("Recording completed"); + } + finally { - using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read)) + if (isResourceOpen) { - await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken); + result.Item2.Release(); } } - - recording.Status = RecordingStatus.Completed; - _logger.Info("Recording completed"); } catch (OperationCanceledException) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 65125da66b..538a963fdc 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -1446,7 +1446,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { dto.ChannelName = channel.Name; - if (!string.IsNullOrEmpty(channel.PrimaryImagePath)) + if (channel.HasImage(ImageType.Primary)) { dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel); } @@ -1512,7 +1512,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { dto.ChannelName = channel.Name; - if (!string.IsNullOrEmpty(channel.PrimaryImagePath)) + if (channel.HasImage(ImageType.Primary)) { dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel); } diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs index ba9ce0db52..e0d90d349c 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Server.Implementations.LiveTv { @@ -40,24 +41,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv { if (liveTvItem.ExternalImagePath.StartsWith("http", StringComparison.OrdinalIgnoreCase)) { - var options = new HttpRequestOptions - { - CancellationToken = cancellationToken, - Url = liveTvItem.ExternalImagePath - }; - - var response = await _httpClient.GetResponse(options).ConfigureAwait(false); - - if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) - { - imageResponse.HasImage = true; - imageResponse.Stream = response.Content; - imageResponse.SetFormatFromMimeType(response.ContentType); - } - else - { - _logger.Error("Provider did not return an image content type."); - } + imageResponse.Path = liveTvItem.ExternalImagePath; + imageResponse.Protocol = MediaProtocol.Http; + imageResponse.HasImage = true; } else { diff --git a/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs index d8d91c2f97..3fb1d96614 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace MediaBrowser.Server.Implementations.LiveTv { - class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask, IHasKey + public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask, IHasKey { private readonly ILiveTvManager _liveTvManager; private readonly IConfigurationManager _config; diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index 616d01a327..d811152c26 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -141,7 +141,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts protected abstract Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken); - public async Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken) + public async Task<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken) { if (IsValidChannelId(channelId)) { @@ -173,9 +173,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts try { var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); - - await AddMediaInfo(stream, false, cancellationToken).ConfigureAwait(false); - return stream; + var resourcePool = GetLock(host.Url); + + await AddMediaInfo(stream, false, resourcePool, cancellationToken).ConfigureAwait(false); + return new Tuple<MediaSourceInfo, SemaphoreSlim>(stream, resourcePool); } catch (Exception ex) { @@ -187,7 +188,40 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts throw new LiveTvConflictException(); } - private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken) + /// <summary> + /// The _semaphoreLocks + /// </summary> + private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = new ConcurrentDictionary<string, SemaphoreSlim>(StringComparer.OrdinalIgnoreCase); + /// <summary> + /// Gets the lock. + /// </summary> + /// <param name="url">The filename.</param> + /// <returns>System.Object.</returns> + private SemaphoreSlim GetLock(string url) + { + return _semaphoreLocks.GetOrAdd(url, key => new SemaphoreSlim(1, 1)); + } + + private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, SemaphoreSlim resourcePool, CancellationToken cancellationToken) + { + await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + await AddMediaInfoInternal(mediaSource, isAudio, cancellationToken).ConfigureAwait(false); + + // Leave the resource locked. it will be released upstream + } + catch (Exception) + { + // Release the resource if there's some kind of failure. + resourcePool.Release(); + + throw; + } + } + + private async Task AddMediaInfoInternal(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken) { var originalRuntime = mediaSource.RunTimeTicks; |
