From 8c32aefb53ed6684a9cd492662460e0fe7b83b0f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 19 Aug 2015 15:25:18 -0400 Subject: [PATCH] update tuner pooling --- MediaBrowser.Controller/LiveTv/ITunerHost.cs | 6 +- .../LiveTv/EmbyTV/EmbyTV.cs | 39 +------ .../LiveTv/TunerHosts/BaseTunerHost.cs | 107 +++++++++++++++++- .../TunerHosts/HdHomerun/HdHomerunHost.cs | 15 ++- .../LiveTv/TunerHosts/M3UTunerHost.cs | 28 +++-- 5 files changed, 146 insertions(+), 49 deletions(-) diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs index 0d1852fe2e..bedbcffe32 100644 --- a/MediaBrowser.Controller/LiveTv/ITunerHost.cs +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -33,20 +33,18 @@ namespace MediaBrowser.Controller.LiveTv /// /// Gets the channel stream. /// - /// The information. /// The channel identifier. /// The stream identifier. /// The cancellation token. /// Task<MediaSourceInfo>. - Task GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken); + Task GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken); /// /// Gets the channel stream media sources. /// - /// The information. /// The channel identifier. /// The cancellation token. /// Task<List<MediaSourceInfo>>. - Task> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken); + Task> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken); /// /// Validates the specified information. /// diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index eb23fab80e..7fbff31b23 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -1,6 +1,5 @@ using MediaBrowser.Common; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Drawing; @@ -13,7 +12,6 @@ using MediaBrowser.Model.Serialization; using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Threading; @@ -161,20 +159,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return GetChannelsAsync(false, cancellationToken); } - private List> GetTunerHosts() - { - return GetConfiguration().TunerHosts - .Where(i => i.IsEnabled) - .Select(i => - { - var provider = _liveTvManager.TunerHosts.FirstOrDefault(l => string.Equals(l.Type, i.Type, StringComparison.OrdinalIgnoreCase)); - - return provider == null ? null : new Tuple(provider, i); - }) - .Where(i => i != null) - .ToList(); - } - public Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken) { var timers = _timerProvider.GetAll().Where(i => string.Equals(i.SeriesTimerId, timerId, StringComparison.OrdinalIgnoreCase)); @@ -409,22 +393,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { _logger.Info("Streaming Channel " + channelId); - foreach (var hostInstance in GetTunerHosts()) + foreach (var hostInstance in _liveTvManager.TunerHosts) { - if (!string.IsNullOrWhiteSpace(streamId)) - { - var originalStreamId = string.Join("-", streamId.Split('-').Skip(1).ToArray()); - - if (!string.Equals(hostInstance.Item2.Id, originalStreamId, StringComparison.OrdinalIgnoreCase)) - { - continue; - } - } - MediaSourceInfo mediaSourceInfo = null; try { - mediaSourceInfo = await hostInstance.Item1.GetChannelStream(hostInstance.Item2, channelId, streamId, cancellationToken).ConfigureAwait(false); + mediaSourceInfo = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false); } catch (Exception e) { @@ -443,16 +417,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public async Task> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken) { - foreach (var hostInstance in GetTunerHosts()) + foreach (var hostInstance in _liveTvManager.TunerHosts) { try { - var sources = await hostInstance.Item1.GetChannelStreamMediaSources(hostInstance.Item2, channelId, cancellationToken).ConfigureAwait(false); - - foreach (var source in sources) - { - source.Id = hostInstance.Item2.Id + "-" + source.Id; - } + var sources = await hostInstance.GetChannelStreamMediaSources(channelId, cancellationToken).ConfigureAwait(false); if (sources.Count > 0) { diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs index b3c8bce262..fb6fa9084b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Logging; using System; @@ -83,11 +84,115 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts return list; } + protected abstract Task> GetChannelStreamMediaSources(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken); + + public async Task> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken) + { + if (IsValidChannelId(channelId)) + { + var hosts = GetTunerHosts(); + + var hostsWithChannel = new List(); + + foreach (var host in hosts) + { + var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false); + + if (channels.Any(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase))) + { + hostsWithChannel.Add(host); + } + } + + foreach (var host in hostsWithChannel) + { + // Check to make sure the tuner is available + // If there's only one tuner, don't bother with the check and just let the tuner be the one to throw an error + if (hostsWithChannel.Count > 1 && !await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false)) + { + Logger.Error("Tuner is not currently available"); + continue; + } + + var mediaSources = await GetChannelStreamMediaSources(host, channelId, cancellationToken).ConfigureAwait(false); + + // Prefix the id with the host Id so that we can easily find it + foreach (var mediaSource in mediaSources) + { + mediaSource.Id = host.Id + mediaSource.Id; + } + + return mediaSources; + } + } + + return new List(); + } + + protected abstract Task GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken); + + public async Task GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken) + { + if (IsValidChannelId(channelId)) + { + var hosts = GetTunerHosts(); + + var hostsWithChannel = new List(); + + foreach (var host in hosts) + { + if (string.IsNullOrWhiteSpace(streamId)) + { + var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false); + + if (channels.Any(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase))) + { + hostsWithChannel.Add(host); + } + } + else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase)) + { + hostsWithChannel = new List { host }; + streamId = streamId.Substring(host.Id.Length); + break; + } + } + + foreach (var host in hostsWithChannel) + { + // Check to make sure the tuner is available + // If there's only one tuner, don't bother with the check and just let the tuner be the one to throw an error + // If a streamId is specified then availibility has already been checked in GetChannelStreamMediaSources + if (string.IsNullOrWhiteSpace(streamId) && hostsWithChannel.Count > 1) + { + if (!await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false)) + { + Logger.Error("Tuner is not currently available"); + continue; + } + } + + var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false); + + if (stream != null) + { + return null; + } + } + } + + throw new LiveTvConflictException(); + } + + protected abstract Task IsAvailable(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken); + + protected abstract bool IsValidChannelId(string channelId); + protected LiveTvOptions GetConfiguration() { return Config.GetConfiguration("livetv"); } - + private class ChannelCache { public DateTime Date; diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs index a07ccd3a48..307a61588b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs @@ -329,7 +329,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun return mediaSource; } - public async Task> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken) + protected override async Task> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken) { var list = new List(); @@ -364,7 +364,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun return list; } - public async Task GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) + protected override bool IsValidChannelId(string channelId) + { + return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase); + } + + protected override async Task GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) { if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase)) { @@ -379,5 +384,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun { await GetChannels(info, false, CancellationToken.None).ConfigureAwait(false); } + + protected override async Task IsAvailable(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken) + { + // TODO + return true; + } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index 0dc170cfdd..bd0feb5737 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -17,7 +17,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts { public class M3UTunerHost : BaseTunerHost, ITunerHost { - public M3UTunerHost(IConfigurationManager config, ILogger logger) : base(config, logger) + public M3UTunerHost(IConfigurationManager config, ILogger logger) + : base(config, logger) { } @@ -31,6 +32,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts get { return "M3U Tuner"; } } + private const string ChannelIdPrefix = "m3u_"; + protected override async Task> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken) { var url = info.Url; @@ -88,7 +91,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts switch (list[0]) { case "tvg-id": - channels.Last().Id = urlHash + list[1]; + channels.Last().Id = ChannelIdPrefix + urlHash + list[1]; channels.Last().Number = list[1]; break; case "tvg-name": @@ -117,19 +120,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts Url = i.Url }) .ToList(); - + return Task.FromResult(list); } - public async Task GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) + protected override async Task GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken) { var urlHash = info.Url.GetMD5().ToString("N"); - if (!channelId.StartsWith(urlHash, StringComparison.OrdinalIgnoreCase)) + var prefix = ChannelIdPrefix + urlHash; + if (!channelId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { return null; } - channelId = channelId.Substring(urlHash.Length); + channelId = channelId.Substring(prefix.Length); var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false); var m3uchannels = channels.Cast(); @@ -196,9 +200,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } } - public Task> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken) + protected override bool IsValidChannelId(string channelId) + { + return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase); + } + + protected override Task> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken) { throw new NotImplementedException(); } + + protected override Task IsAvailable(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken) + { + return Task.FromResult(true); + } } }