aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/LiveTv
diff options
context:
space:
mode:
authorstefan <stefan@hegedues.at>2018-09-12 19:26:21 +0200
committerstefan <stefan@hegedues.at>2018-09-12 19:26:21 +0200
commit48facb797ed912e4ea6b04b17d1ff190ac2daac4 (patch)
tree8dae77a31670a888d733484cb17dd4077d5444e8 /Emby.Server.Implementations/LiveTv
parentc32d8656382a0eacb301692e0084377fc433ae9b (diff)
Update to 3.5.2 and .net core 2.1
Diffstat (limited to 'Emby.Server.Implementations/LiveTv')
-rw-r--r--Emby.Server.Implementations/LiveTv/ChannelImageProvider.cs85
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs42
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs831
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTVRegistration.cs36
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs81
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs1
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs27
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs32
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs68
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs315
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs113
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs234
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs1519
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs128
-rw-r--r--Emby.Server.Implementations/LiveTv/RecordingImageProvider.cs82
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs110
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs113
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs125
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs61
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs179
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs79
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs43
24 files changed, 1457 insertions, 2851 deletions
diff --git a/Emby.Server.Implementations/LiveTv/ChannelImageProvider.cs b/Emby.Server.Implementations/LiveTv/ChannelImageProvider.cs
deleted file mode 100644
index 0c8049d8b1..0000000000
--- a/Emby.Server.Implementations/LiveTv/ChannelImageProvider.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using MediaBrowser.Common;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Emby.Server.Implementations.LiveTv
-{
- public class ChannelImageProvider : IDynamicImageProvider, IHasItemChangeMonitor
- {
- private readonly ILiveTvManager _liveTvManager;
- private readonly IHttpClient _httpClient;
- private readonly ILogger _logger;
- private readonly IApplicationHost _appHost;
-
- public ChannelImageProvider(ILiveTvManager liveTvManager, IHttpClient httpClient, ILogger logger, IApplicationHost appHost)
- {
- _liveTvManager = liveTvManager;
- _httpClient = httpClient;
- _logger = logger;
- _appHost = appHost;
- }
-
- public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item)
- {
- return new[] { ImageType.Primary };
- }
-
- public async Task<DynamicImageResponse> GetImage(IHasMetadata item, ImageType type, CancellationToken cancellationToken)
- {
- var liveTvItem = (LiveTvChannel)item;
-
- var imageResponse = new DynamicImageResponse();
-
- var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
-
- if (service != null && !item.HasImage(ImageType.Primary))
- {
- try
- {
- var response = await service.GetChannelImageAsync(liveTvItem.ExternalId, cancellationToken).ConfigureAwait(false);
-
- if (response != null)
- {
- imageResponse.HasImage = true;
- imageResponse.Stream = response.Stream;
- imageResponse.Format = response.Format;
- }
- }
- catch (NotImplementedException)
- {
- }
- }
-
- return imageResponse;
- }
-
- public string Name
- {
- get { return "Live TV Service Provider"; }
- }
-
- public bool Supports(IHasMetadata item)
- {
- return item is LiveTvChannel;
- }
-
- public int Order
- {
- get { return 0; }
- }
-
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- return GetSupportedImages(item).Any(i => !item.HasImage(i));
- }
- }
-}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index f0578d9efb..0c7980ca0b 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -17,12 +17,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
private readonly IFileSystem _fileSystem;
+ private readonly IStreamHelper _streamHelper;
- public DirectRecorder(ILogger logger, IHttpClient httpClient, IFileSystem fileSystem)
+ public DirectRecorder(ILogger logger, IHttpClient httpClient, IFileSystem fileSystem, IStreamHelper streamHelper)
{
_logger = logger;
_httpClient = httpClient;
_fileSystem = fileSystem;
+ _streamHelper = streamHelper;
}
public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile)
@@ -50,7 +52,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.Info("Copying recording stream to file {0}", targetFile);
- // The media source if infinite so we need to handle stopping ourselves
+ // The media source is infinite so we need to handle stopping ourselves
var durationToken = new CancellationTokenSource(duration);
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
@@ -90,45 +92,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var durationToken = new CancellationTokenSource(duration);
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
- await CopyUntilCancelled(response.Content, output, cancellationToken).ConfigureAwait(false);
+ await _streamHelper.CopyUntilCancelled(response.Content, output, 81920, cancellationToken).ConfigureAwait(false);
}
}
_logger.Info("Recording completed to file {0}", targetFile);
}
-
- private const int BufferSize = 81920;
- public static async Task CopyUntilCancelled(Stream source, Stream target, CancellationToken cancellationToken)
- {
- byte[] buffer = new byte[BufferSize];
-
- while (!cancellationToken.IsCancellationRequested)
- {
- var bytesRead = await CopyToAsyncInternal(source, target, buffer, cancellationToken).ConfigureAwait(false);
-
- //var position = fs.Position;
- //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
-
- if (bytesRead == 0)
- {
- await Task.Delay(100).ConfigureAwait(false);
- }
- }
- }
-
- private static async Task<int> CopyToAsyncInternal(Stream source, Stream destination, byte[] buffer, CancellationToken cancellationToken)
- {
- int bytesRead;
- int totalBytesRead = 0;
-
- while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
- {
- destination.Write(buffer, 0, bytesRead);
-
- totalBytesRead += bytesRead;
- }
-
- return totalBytesRead;
- }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 35d2d3c0ab..d21abb74e8 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -39,6 +39,10 @@ using MediaBrowser.Model.System;
using MediaBrowser.Model.Threading;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Querying;
+using MediaBrowser.Model.Reflection;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Model.MediaInfo;
+using Emby.Server.Implementations.Library;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
@@ -62,16 +66,21 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IMediaEncoder _mediaEncoder;
private readonly IProcessFactory _processFactory;
private readonly ISystemEvents _systemEvents;
+ private readonly IAssemblyInfo _assemblyInfo;
+ private IMediaSourceManager _mediaSourceManager;
public static EmbyTV Current;
public event EventHandler DataSourceChanged;
- public event EventHandler<RecordingStatusChangedEventArgs> RecordingStatusChanged;
+ public event EventHandler<GenericEventArgs<TimerInfo>> TimerCreated;
+ public event EventHandler<GenericEventArgs<string>> TimerCancelled;
private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
- public EmbyTV(IServerApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IMediaEncoder mediaEncoder, ITimerFactory timerFactory, IProcessFactory processFactory, ISystemEvents systemEvents)
+ private readonly IStreamHelper _streamHelper;
+
+ public EmbyTV(IServerApplicationHost appHost, IStreamHelper streamHelper, IMediaSourceManager mediaSourceManager, IAssemblyInfo assemblyInfo, ILogger logger, IJsonSerializer jsonSerializer, IPowerManagement powerManagement, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IMediaEncoder mediaEncoder, ITimerFactory timerFactory, IProcessFactory processFactory, ISystemEvents systemEvents)
{
Current = this;
@@ -88,9 +97,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_systemEvents = systemEvents;
_liveTvManager = (LiveTvManager)liveTvManager;
_jsonSerializer = jsonSerializer;
+ _assemblyInfo = assemblyInfo;
+ _mediaSourceManager = mediaSourceManager;
+ _streamHelper = streamHelper;
_seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
- _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger, timerFactory);
+ _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger, timerFactory, powerManagement);
_timerProvider.TimerFired += _timerProvider_TimerFired;
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
@@ -104,12 +116,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- public void Start()
+ public async void Start()
{
_timerProvider.RestartTimers();
_systemEvents.Resume += _systemEvents_Resume;
- CreateRecordingFolders();
+ await CreateRecordingFolders().ConfigureAwait(false);
}
private void _systemEvents_Resume(object sender, EventArgs e)
@@ -117,83 +129,78 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_timerProvider.RestartTimers();
}
- private void OnRecordingFoldersChanged()
+ private async void OnRecordingFoldersChanged()
{
- CreateRecordingFolders();
+ await CreateRecordingFolders().ConfigureAwait(false);
}
- internal void CreateRecordingFolders()
+ internal async Task CreateRecordingFolders()
{
try
{
- CreateRecordingFoldersInternal();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error creating recording folders", ex);
- }
- }
+ var recordingFolders = GetRecordingFolders();
- internal void CreateRecordingFoldersInternal()
- {
- var recordingFolders = GetRecordingFolders();
+ var virtualFolders = _libraryManager.GetVirtualFolders()
+ .ToList();
- var virtualFolders = _libraryManager.GetVirtualFolders()
- .ToList();
+ var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
- var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
+ var pathsAdded = new List<string>();
- var pathsAdded = new List<string>();
+ foreach (var recordingFolder in recordingFolders)
+ {
+ var pathsToCreate = recordingFolder.Locations
+ .Where(i => !allExistingPaths.Any(p => _fileSystem.AreEqual(p, i)))
+ .ToList();
- foreach (var recordingFolder in recordingFolders)
- {
- var pathsToCreate = recordingFolder.Locations
- .Where(i => !allExistingPaths.Any(p => _fileSystem.AreEqual(p, i)))
- .ToList();
+ if (pathsToCreate.Count == 0)
+ {
+ continue;
+ }
- if (pathsToCreate.Count == 0)
- {
- continue;
- }
+ var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo { Path = i }).ToArray();
- var mediaPathInfos = pathsToCreate.Select(i => new MediaPathInfo { Path = i }).ToArray();
+ var libraryOptions = new LibraryOptions
+ {
+ PathInfos = mediaPathInfos
+ };
+ try
+ {
+ await _libraryManager.AddVirtualFolder(recordingFolder.Name, recordingFolder.CollectionType, libraryOptions, true).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error creating virtual folder", ex);
+ }
- var libraryOptions = new LibraryOptions
- {
- PathInfos = mediaPathInfos
- };
- try
- {
- _libraryManager.AddVirtualFolder(recordingFolder.Name, recordingFolder.CollectionType, libraryOptions, true);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error creating virtual folder", ex);
+ pathsAdded.AddRange(pathsToCreate);
}
- pathsAdded.AddRange(pathsToCreate);
- }
+ var config = GetConfiguration();
- var config = GetConfiguration();
+ var pathsToRemove = config.MediaLocationsCreated
+ .Except(recordingFolders.SelectMany(i => i.Locations))
+ .ToList();
- var pathsToRemove = config.MediaLocationsCreated
- .Except(recordingFolders.SelectMany(i => i.Locations))
- .ToList();
+ if (pathsAdded.Count > 0 || pathsToRemove.Count > 0)
+ {
+ pathsAdded.InsertRange(0, config.MediaLocationsCreated);
+ config.MediaLocationsCreated = pathsAdded.Except(pathsToRemove).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
+ _config.SaveConfiguration("livetv", config);
+ }
- if (pathsAdded.Count > 0 || pathsToRemove.Count > 0)
- {
- pathsAdded.InsertRange(0, config.MediaLocationsCreated);
- config.MediaLocationsCreated = pathsAdded.Except(pathsToRemove).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
- _config.SaveConfiguration("livetv", config);
+ foreach (var path in pathsToRemove)
+ {
+ await RemovePathFromLibrary(path).ConfigureAwait(false);
+ }
}
-
- foreach (var path in pathsToRemove)
+ catch (Exception ex)
{
- RemovePathFromLibrary(path);
+ _logger.ErrorException("Error creating recording folders", ex);
}
}
- private void RemovePathFromLibrary(string path)
+ private async Task RemovePathFromLibrary(string path)
{
_logger.Debug("Removing path from library: {0}", path);
@@ -213,7 +220,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
// remove entire virtual folder
try
{
- _libraryManager.RemoveVirtualFolder(virtualFolder.Name, true);
+ await _libraryManager.RemoveVirtualFolder(virtualFolder.Name, true).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -272,33 +279,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public string HomePageUrl
{
- get { return "http://emby.media"; }
- }
-
- public async Task<LiveTvServiceStatusInfo> GetStatusInfoAsync(CancellationToken cancellationToken)
- {
- var status = new LiveTvServiceStatusInfo();
- var list = new List<LiveTvTunerInfo>();
-
- foreach (var hostInstance in _liveTvManager.TunerHosts)
- {
- try
- {
- var tuners = await hostInstance.GetTunerInfos(cancellationToken).ConfigureAwait(false);
-
- list.AddRange(tuners);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting tuners", ex);
- }
- }
-
- status.Tuners = list;
- status.Status = LiveTvServiceStatus.Ok;
- status.Version = _appHost.ApplicationVersion.ToString();
- status.IsVisible = false;
- return status;
+ get { return "https://emby.media"; }
}
public async Task RefreshSeriesTimers(CancellationToken cancellationToken, IProgress<double> progress)
@@ -315,7 +296,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var timers = await GetTimersAsync(cancellationToken).ConfigureAwait(false);
- var tempChannelCache = new Dictionary<string, LiveTvChannel>();
+ var tempChannelCache = new Dictionary<Guid, LiveTvChannel>();
foreach (var timer in timers)
{
@@ -408,33 +389,90 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!string.IsNullOrWhiteSpace(epgChannel.ImageUrl))
{
tunerChannel.ImageUrl = epgChannel.ImageUrl;
- tunerChannel.HasImage = true;
}
}
}
}
- private readonly ConcurrentDictionary<string, List<ChannelInfo>> _epgChannels =
- new ConcurrentDictionary<string, List<ChannelInfo>>(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary<string, EpgChannelData> _epgChannels =
+ new ConcurrentDictionary<string, EpgChannelData>(StringComparer.OrdinalIgnoreCase);
- private async Task<List<ChannelInfo>> GetEpgChannels(IListingsProvider provider, ListingsProviderInfo info, bool enableCache, CancellationToken cancellationToken)
+ private async Task<EpgChannelData> GetEpgChannels(IListingsProvider provider, ListingsProviderInfo info, bool enableCache, CancellationToken cancellationToken)
{
- List<ChannelInfo> result;
+ EpgChannelData result;
if (!enableCache || !_epgChannels.TryGetValue(info.Id, out result))
{
- result = await provider.GetChannels(info, cancellationToken).ConfigureAwait(false);
+ var channels = await provider.GetChannels(info, cancellationToken).ConfigureAwait(false);
- foreach (var channel in result)
+ foreach (var channel in channels)
{
_logger.Info("Found epg channel in {0} {1} {2} {3}", provider.Name, info.ListingsId, channel.Name, channel.Id);
}
+ result = new EpgChannelData(channels);
_epgChannels.AddOrUpdate(info.Id, result, (k, v) => result);
}
return result;
}
+ private class EpgChannelData
+ {
+ public EpgChannelData(List<ChannelInfo> channels)
+ {
+ ChannelsById = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
+ ChannelsByNumber = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
+ ChannelsByName = new Dictionary<string, ChannelInfo>(StringComparer.OrdinalIgnoreCase);
+
+ foreach (var channel in channels)
+ {
+ ChannelsById[channel.Id] = channel;
+
+ if (!string.IsNullOrEmpty(channel.Number))
+ {
+ ChannelsByNumber[channel.Number] = channel;
+ }
+
+ var normalizedName = NormalizeName(channel.Name ?? string.Empty);
+ if (!string.IsNullOrWhiteSpace(normalizedName))
+ {
+ ChannelsByName[normalizedName] = channel;
+ }
+ }
+ }
+
+ private Dictionary<string, ChannelInfo> ChannelsById { get; set; }
+ private Dictionary<string, ChannelInfo> ChannelsByNumber { get; set; }
+ private Dictionary<string, ChannelInfo> ChannelsByName { get; set; }
+
+ public ChannelInfo GetChannelById(string id)
+ {
+ ChannelInfo result = null;
+
+ ChannelsById.TryGetValue(id, out result);
+
+ return result;
+ }
+
+ public ChannelInfo GetChannelByNumber(string number)
+ {
+ ChannelInfo result = null;
+
+ ChannelsByNumber.TryGetValue(number, out result);
+
+ return result;
+ }
+
+ public ChannelInfo GetChannelByName(string name)
+ {
+ ChannelInfo result = null;
+
+ ChannelsByName.TryGetValue(name, out result);
+
+ return result;
+ }
+ }
+
private async Task<ChannelInfo> GetEpgChannelFromTunerChannel(IListingsProvider provider, ListingsProviderInfo info, ChannelInfo tunerChannel, CancellationToken cancellationToken)
{
var epgChannels = await GetEpgChannels(provider, info, true, cancellationToken).ConfigureAwait(false);
@@ -454,12 +492,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return channelId;
}
- private ChannelInfo GetEpgChannelFromTunerChannel(ListingsProviderInfo info, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
+ internal ChannelInfo GetEpgChannelFromTunerChannel(NameValuePair[] mappings, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
+ {
+ return GetEpgChannelFromTunerChannel(mappings, tunerChannel, new EpgChannelData(epgChannels));
+ }
+
+ private ChannelInfo GetEpgChannelFromTunerChannel(ListingsProviderInfo info, ChannelInfo tunerChannel, EpgChannelData epgChannels)
{
return GetEpgChannelFromTunerChannel(info.ChannelMappings, tunerChannel, epgChannels);
}
- public ChannelInfo GetEpgChannelFromTunerChannel(NameValuePair[] mappings, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
+ private ChannelInfo GetEpgChannelFromTunerChannel(NameValuePair[] mappings, ChannelInfo tunerChannel, EpgChannelData epgChannelData)
{
if (!string.IsNullOrWhiteSpace(tunerChannel.Id))
{
@@ -470,7 +513,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
mappedTunerChannelId = tunerChannel.Id;
}
- var channel = epgChannels.FirstOrDefault(i => string.Equals(mappedTunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase));
+ var channel = epgChannelData.GetChannelById(mappedTunerChannelId);
if (channel != null)
{
@@ -493,7 +536,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
mappedTunerChannelId = tunerChannelId;
}
- var channel = epgChannels.FirstOrDefault(i => string.Equals(mappedTunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase));
+ var channel = epgChannelData.GetChannelById(mappedTunerChannelId);
if (channel != null)
{
@@ -510,7 +553,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
tunerChannelNumber = tunerChannel.Number;
}
- var channel = epgChannels.FirstOrDefault(i => string.Equals(tunerChannelNumber, i.Number, StringComparison.OrdinalIgnoreCase));
+ var channel = epgChannelData.GetChannelByNumber(tunerChannelNumber);
if (channel != null)
{
@@ -522,7 +565,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var normalizedName = NormalizeName(tunerChannel.Name);
- var channel = epgChannels.FirstOrDefault(i => string.Equals(normalizedName, NormalizeName(i.Name ?? string.Empty), StringComparison.OrdinalIgnoreCase));
+ var channel = epgChannelData.GetChannelByName(normalizedName);
if (channel != null)
{
@@ -533,7 +576,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return null;
}
- private string NormalizeName(string value)
+ private static string NormalizeName(string value)
{
return value.Replace(" ", string.Empty).Replace("-", string.Empty);
}
@@ -575,7 +618,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var timer in timers)
{
- CancelTimerInternal(timer.Id, true);
+ CancelTimerInternal(timer.Id, true, true);
}
var remove = _seriesTimerProvider.GetAll().FirstOrDefault(r => string.Equals(r.Id, timerId, StringComparison.OrdinalIgnoreCase));
@@ -583,16 +626,22 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
_seriesTimerProvider.Delete(remove);
}
- return Task.FromResult(true);
+ return Task.CompletedTask;
}
- private void CancelTimerInternal(string timerId, bool isSeriesCancelled)
+ private void CancelTimerInternal(string timerId, bool isSeriesCancelled, bool isManualCancellation)
{
var timer = _timerProvider.GetTimer(timerId);
if (timer != null)
{
+ var statusChanging = timer.Status != RecordingStatus.Cancelled;
timer.Status = RecordingStatus.Cancelled;
+ if (isManualCancellation)
+ {
+ timer.IsManual = true;
+ }
+
if (string.IsNullOrWhiteSpace(timer.SeriesTimerId) || isSeriesCancelled)
{
_timerProvider.Delete(timer);
@@ -601,6 +650,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
_timerProvider.AddOrUpdate(timer, false);
}
+
+ if (statusChanging && TimerCancelled != null)
+ {
+ TimerCancelled(this, new GenericEventArgs<string>(timerId));
+ }
}
ActiveRecordingInfo activeRecordingInfo;
@@ -613,13 +667,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public Task CancelTimerAsync(string timerId, CancellationToken cancellationToken)
{
- CancelTimerInternal(timerId, false);
- return Task.FromResult(true);
+ CancelTimerInternal(timerId, false, true);
+ return Task.CompletedTask;
}
public Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken)
{
- return Task.FromResult(true);
+ return Task.CompletedTask;
}
public Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
@@ -675,6 +729,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
timer.IsManual = true;
_timerProvider.Add(timer);
+
+ if (TimerCreated != null)
+ {
+ TimerCreated(this, new GenericEventArgs<TimerInfo>(timer));
+ }
+
return Task.FromResult(timer.Id);
}
@@ -776,7 +836,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_timerProvider.Update(existingTimer);
}
- return Task.FromResult(true);
+ return Task.CompletedTask;
}
private void UpdateExistingTimerWithNewMetadata(TimerInfo existingTimer, TimerInfo updatedTimer)
@@ -788,16 +848,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
existingTimer.EpisodeNumber = updatedTimer.EpisodeNumber;
existingTimer.EpisodeTitle = updatedTimer.EpisodeTitle;
existingTimer.Genres = updatedTimer.Genres;
- existingTimer.HomePageUrl = updatedTimer.HomePageUrl;
- existingTimer.IsKids = updatedTimer.IsKids;
- existingTimer.IsNews = updatedTimer.IsNews;
existingTimer.IsMovie = updatedTimer.IsMovie;
existingTimer.IsSeries = updatedTimer.IsSeries;
- existingTimer.IsLive = updatedTimer.IsLive;
- existingTimer.IsPremiere = updatedTimer.IsPremiere;
+ existingTimer.Tags = updatedTimer.Tags;
existingTimer.IsProgramSeries = updatedTimer.IsProgramSeries;
existingTimer.IsRepeat = updatedTimer.IsRepeat;
- existingTimer.IsSports = updatedTimer.IsSports;
existingTimer.Name = updatedTimer.Name;
existingTimer.OfficialRating = updatedTimer.OfficialRating;
existingTimer.OriginalAirDate = updatedTimer.OriginalAirDate;
@@ -807,26 +862,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
existingTimer.SeasonNumber = updatedTimer.SeasonNumber;
existingTimer.StartDate = updatedTimer.StartDate;
existingTimer.ShowId = updatedTimer.ShowId;
- }
-
- public Task<ImageStream> GetChannelImageAsync(string channelId, CancellationToken cancellationToken)
- {
- throw new NotImplementedException();
- }
-
- public Task<ImageStream> GetRecordingImageAsync(string recordingId, CancellationToken cancellationToken)
- {
- throw new NotImplementedException();
- }
-
- public Task<ImageStream> GetProgramImageAsync(string programId, string channelId, CancellationToken cancellationToken)
- {
- throw new NotImplementedException();
- }
-
- public async Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken)
- {
- return new List<RecordingInfo>();
+ existingTimer.ProviderIds = updatedTimer.ProviderIds;
+ existingTimer.SeriesProviderIds = updatedTimer.SeriesProviderIds;
}
public string GetActiveRecordingPath(string id)
@@ -909,6 +946,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
defaults.SeriesId = program.SeriesId;
defaults.ProgramId = program.Id;
defaults.RecordNewOnly = !program.IsRepeat;
+ defaults.Name = program.Name;
}
defaults.SkipEpisodesInLibrary = defaults.RecordNewOnly;
@@ -972,10 +1010,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
program.ChannelId = channelId;
- if (provider.Item2.EnableNewProgramIds)
- {
- program.Id += "_" + channelId;
- }
+ program.Id += "_" + channelId;
}
if (programs.Count > 0)
@@ -1000,26 +1035,51 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
.ToList();
}
- public Task<MediaSourceInfo> GetRecordingStream(string recordingId, string streamId, CancellationToken cancellationToken)
+ public Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
- private readonly SemaphoreSlim _liveStreamsSemaphore = new SemaphoreSlim(1, 1);
- private readonly List<ILiveStream> _liveStreams = new List<ILiveStream>();
-
- public async Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
+ public async Task<ILiveStream> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
- var result = await GetChannelStreamWithDirectStreamProvider(channelId, streamId, cancellationToken).ConfigureAwait(false);
+ _logger.Info("Streaming Channel " + channelId);
- return result.Item1;
- }
+ var result = string.IsNullOrEmpty(streamId) ?
+ null :
+ currentLiveStreams.FirstOrDefault(i => string.Equals(i.OriginalStreamId, streamId, StringComparison.OrdinalIgnoreCase));
- public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, CancellationToken cancellationToken)
- {
- var result = await GetChannelStreamInternal(channelId, streamId, cancellationToken).ConfigureAwait(false);
+ if (result != null && result.EnableStreamSharing)
+ {
+ result.ConsumerCount++;
+
+ _logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount);
+
+ return result;
+ }
+
+ foreach (var hostInstance in _liveTvManager.TunerHosts)
+ {
+ try
+ {
+ result = await hostInstance.GetChannelStream(channelId, streamId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
+
+ var openedMediaSource = result.MediaSource;
+
+ result.OriginalStreamId = streamId;
+
+ _logger.Info("Returning mediasource streamId {0}, mediaSource.Id {1}, mediaSource.LiveStreamId {2}", streamId, openedMediaSource.Id, openedMediaSource.LiveStreamId);
+
+ return result;
+ }
+ catch (FileNotFoundException)
+ {
+ }
+ catch (OperationCanceledException)
+ {
+ }
+ }
- return new Tuple<MediaSourceInfo, IDirectStreamProvider>(result.Item2, result.Item1 as IDirectStreamProvider);
+ throw new Exception("Tuner not found.");
}
private MediaSourceInfo CloneMediaSource(MediaSourceInfo mediaSource, bool enableStreamSharing)
@@ -1039,93 +1099,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return mediaSource;
}
- public async Task<ILiveStream> GetLiveStream(string uniqueId)
- {
- await _liveStreamsSemaphore.WaitAsync().ConfigureAwait(false);
-
- try
- {
- return _liveStreams
- .FirstOrDefault(i => string.Equals(i.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));
- }
- finally
- {
- _liveStreamsSemaphore.Release();
- }
- }
-
- public async Task<List<ILiveStream>> GetLiveStreams(TunerHostInfo host, CancellationToken cancellationToken)
- {
- //await _liveStreamsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- //try
- //{
- var hostId = host.Id;
-
- return _liveStreams
- .Where(i => string.Equals(i.TunerHostId, hostId, StringComparison.OrdinalIgnoreCase))
- .ToList();
- //}
- //finally
- //{
- // _liveStreamsSemaphore.Release();
- //}
- }
-
- private async Task<Tuple<ILiveStream, MediaSourceInfo>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
- {
- _logger.Info("Streaming Channel " + channelId);
-
- await _liveStreamsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- var result = _liveStreams.FirstOrDefault(i => string.Equals(i.OriginalStreamId, streamId, StringComparison.OrdinalIgnoreCase));
-
- if (result != null && result.EnableStreamSharing)
- {
- var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.EnableStreamSharing);
- result.SharedStreamIds.Add(openedMediaSource.Id);
-
- _logger.Info("Live stream {0} consumer count is now {1}", streamId, result.ConsumerCount);
-
- return new Tuple<ILiveStream, MediaSourceInfo>(result, openedMediaSource);
- }
-
- foreach (var hostInstance in _liveTvManager.TunerHosts)
- {
- try
- {
- result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
-
- var openedMediaSource = CloneMediaSource(result.OpenedMediaSource, result.EnableStreamSharing);
-
- result.SharedStreamIds.Add(openedMediaSource.Id);
- _liveStreams.Add(result);
-
- result.OriginalStreamId = streamId;
-
- _logger.Info("Returning mediasource streamId {0}, mediaSource.Id {1}, mediaSource.LiveStreamId {2}",
- streamId, openedMediaSource.Id, openedMediaSource.LiveStreamId);
-
- return new Tuple<ILiveStream, MediaSourceInfo>(result, openedMediaSource);
- }
- catch (FileNotFoundException)
- {
- }
- catch (OperationCanceledException)
- {
- }
- }
- }
- finally
- {
- _liveStreamsSemaphore.Release();
- }
-
- throw new Exception("Tuner not found.");
- }
-
public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(channelId))
@@ -1153,47 +1126,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new NotImplementedException();
}
- public async Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(string recordingId, CancellationToken cancellationToken)
- {
- ActiveRecordingInfo info;
-
- recordingId = recordingId.Replace("recording", string.Empty);
-
- if (_activeRecordings.TryGetValue(recordingId, out info))
- {
- var stream = new MediaSourceInfo
- {
- Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveRecordings/" + recordingId + "/stream",
- Id = recordingId,
- SupportsDirectPlay = false,
- SupportsDirectStream = true,
- SupportsTranscoding = true,
- IsInfiniteStream = true,
- RequiresOpening = false,
- RequiresClosing = false,
- Protocol = MediaBrowser.Model.MediaInfo.MediaProtocol.Http,
- BufferMs = 0,
- IgnoreDts = true,
- IgnoreIndex = true
- };
-
- var isAudio = false;
- await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false);
-
- return new List<MediaSourceInfo>
- {
- stream
- };
- }
-
- throw new FileNotFoundException();
- }
-
public async Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken)
{
var stream = new MediaSourceInfo
{
- Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
+ EncoderPath = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
+ EncoderProtocol = MediaProtocol.Http,
+ Path = info.Path,
+ Protocol = MediaProtocol.File,
Id = info.Id,
SupportsDirectPlay = false,
SupportsDirectStream = true,
@@ -1201,14 +1141,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IsInfiniteStream = true,
RequiresOpening = false,
RequiresClosing = false,
- Protocol = MediaBrowser.Model.MediaInfo.MediaProtocol.Http,
BufferMs = 0,
IgnoreDts = true,
IgnoreIndex = true
};
var isAudio = false;
- await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false);
+ await new LiveStreamHelper(_mediaEncoder, _logger, _jsonSerializer, _config.CommonApplicationPaths).AddMediaInfoWithProbe(stream, isAudio, false, cancellationToken).ConfigureAwait(false);
return new List<MediaSourceInfo>
{
@@ -1216,48 +1155,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
};
}
- public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
+ public Task CloseLiveStream(string id, CancellationToken cancellationToken)
{
- // Ignore the consumer id
- //id = id.Substring(id.IndexOf('_') + 1);
-
- await _liveStreamsSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- var stream = _liveStreams.FirstOrDefault(i => i.SharedStreamIds.Contains(id));
- if (stream != null)
- {
- stream.SharedStreamIds.Remove(id);
-
- _logger.Info("Live stream {0} consumer count is now {1}", id, stream.ConsumerCount);
-
- if (stream.ConsumerCount <= 0)
- {
- _liveStreams.Remove(stream);
-
- _logger.Info("Closing live stream {0}", id);
-
- stream.Close();
- _logger.Info("Live stream {0} closed successfully", id);
- }
- }
- else
- {
- _logger.Warn("Live stream not found: {0}, unable to close", id);
- }
- }
- catch (OperationCanceledException)
- {
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error closing live stream", ex);
- }
- finally
- {
- _liveStreamsSemaphore.Release();
- }
+ return Task.CompletedTask;
}
public Task RecordLiveStream(string id, CancellationToken cancellationToken)
@@ -1274,7 +1174,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var timer = e.Argument;
- _logger.Info("Recording timer fired.");
+ _logger.Info("Recording timer fired for {0}.", timer.Name);
try
{
@@ -1321,7 +1221,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- private string GetRecordingPath(TimerInfo timer, out string seriesPath)
+ private string GetRecordingPath(TimerInfo timer, RemoteSearchResult metadata, out string seriesPath)
{
var recordPath = RecordingPath;
var config = GetConfiguration();
@@ -1342,7 +1242,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
recordPath = Path.Combine(recordPath, "Series");
}
- var folderName = _fileSystem.GetValidFilename(timer.Name).Trim();
+ // trim trailing period from the folder name
+ var folderName = _fileSystem.GetValidFilename(timer.Name).Trim().TrimEnd('.').Trim();
+
+ if (metadata != null && metadata.ProductionYear.HasValue)
+ {
+ folderName += " (" + metadata.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
+ }
// Can't use the year here in the folder name because it is the year of the episode, not the series.
recordPath = Path.Combine(recordPath, folderName);
@@ -1375,6 +1281,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
}
+
+ // trim trailing period from the folder name
+ folderName = folderName.TrimEnd('.').Trim();
+
recordPath = Path.Combine(recordPath, folderName);
}
else if (timer.IsKids)
@@ -1389,6 +1299,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
}
+
+ // trim trailing period from the folder name
+ folderName = folderName.TrimEnd('.').Trim();
+
recordPath = Path.Combine(recordPath, folderName);
}
else if (timer.IsSports)
@@ -1438,23 +1352,36 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
string seriesPath = null;
- var recordPath = GetRecordingPath(timer, out seriesPath);
+ var remoteMetadata = await FetchInternetMetadata(timer, CancellationToken.None).ConfigureAwait(false);
+ var recordPath = GetRecordingPath(timer, remoteMetadata, out seriesPath);
var recordingStatus = RecordingStatus.New;
- var recorder = await GetRecorder().ConfigureAwait(false);
-
string liveStreamId = null;
+ var channelItem = _liveTvManager.GetLiveTvChannel(timer, this);
+
try
{
- var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false);
+ var allMediaSources = await _mediaSourceManager.GetPlayackMediaSources(channelItem, null, true, false, CancellationToken.None).ConfigureAwait(false);
+
+ var mediaStreamInfo = allMediaSources[0];
+ IDirectStreamProvider directStreamProvider = null;
- _logger.Info("Opening recording stream from tuner provider");
- var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None)
- .ConfigureAwait(false);
+ if (mediaStreamInfo.RequiresOpening)
+ {
+ var liveStreamResponse = await _mediaSourceManager.OpenLiveStreamInternal(new LiveStreamRequest
+ {
+ ItemId = channelItem.Id,
+ OpenToken = mediaStreamInfo.OpenToken
- var mediaStreamInfo = liveStreamInfo.Item2;
- liveStreamId = mediaStreamInfo.Id;
+ }, CancellationToken.None).ConfigureAwait(false);
+
+ mediaStreamInfo = liveStreamResponse.Item1.MediaSource;
+ liveStreamId = mediaStreamInfo.LiveStreamId;
+ directStreamProvider = liveStreamResponse.Item2;
+ }
+
+ var recorder = GetRecorder(mediaStreamInfo);
recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath);
recordPath = EnsureFileUnique(recordPath, timer.Id);
@@ -1478,13 +1405,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
await SaveRecordingMetadata(timer, recordPath, seriesPath).ConfigureAwait(false);
- CreateRecordingFolders();
+ await CreateRecordingFolders().ConfigureAwait(false);
TriggerRefresh(recordPath);
EnforceKeepUpTo(timer, seriesPath);
};
- await recorder.Record(liveStreamInfo.Item1 as IDirectStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false);
+ await recorder.Record(directStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false);
recordingStatus = RecordingStatus.Completed;
_logger.Info("Recording completed: {0}", recordPath);
@@ -1504,7 +1431,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
try
{
- await CloseLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
+ await _mediaSourceManager.CloseLiveStream(liveStreamId).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -1544,6 +1471,34 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
+ private async Task<RemoteSearchResult> FetchInternetMetadata(TimerInfo timer, CancellationToken cancellationToken)
+ {
+ if (timer.IsSeries)
+ {
+ if (timer.SeriesProviderIds.Count == 0)
+ {
+ return null;
+ }
+
+ var query = new RemoteSearchQuery<SeriesInfo>()
+ {
+ SearchInfo = new SeriesInfo
+ {
+ ProviderIds = timer.SeriesProviderIds,
+ Name = timer.Name,
+ MetadataCountryCode = _config.Configuration.MetadataCountryCode,
+ MetadataLanguage = _config.Configuration.PreferredMetadataLanguage
+ }
+ };
+
+ var results = await _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(query, cancellationToken).ConfigureAwait(false);
+
+ return results.FirstOrDefault();
+ }
+
+ return null;
+ }
+
private void DeleteFileIfEmpty(string path)
{
var file = _fileSystem.GetFileInfo(path);
@@ -1573,8 +1528,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem)
{
- ValidateChildren = true,
- RefreshPaths = new List<string>
+ RefreshPaths = new string[]
{
path,
_fileSystem.GetDirectoryName(path),
@@ -1627,7 +1581,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var seriesTimerId = timer.SeriesTimerId;
var seriesTimer = _seriesTimerProvider.GetAll().FirstOrDefault(i => string.Equals(i.Id, seriesTimerId, StringComparison.OrdinalIgnoreCase));
- if (seriesTimer == null || seriesTimer.KeepUpTo <= 1)
+ if (seriesTimer == null || seriesTimer.KeepUpTo <= 0)
{
return;
}
@@ -1654,7 +1608,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
.Skip(seriesTimer.KeepUpTo - 1)
.ToList();
- await DeleteLibraryItemsForTimers(timersToDelete).ConfigureAwait(false);
+ DeleteLibraryItemsForTimers(timersToDelete);
var librarySeries = _libraryManager.FindByPath(seriesPath, true) as Folder;
@@ -1665,14 +1619,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var episodesToDelete = (librarySeries.GetItemList(new InternalItemsQuery
{
- OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
+ OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
IsVirtualItem = false,
IsFolder = false,
Recursive = true,
DtoOptions = new DtoOptions(true)
}))
- .Where(i => i.LocationType == LocationType.FileSystem && _fileSystem.FileExists(i.Path))
+ .Where(i => i.IsFileProtocol && _fileSystem.FileExists(i.Path))
.Skip(seriesTimer.KeepUpTo - 1)
.ToList();
@@ -1680,11 +1634,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
try
{
- await _libraryManager.DeleteItem(item, new DeleteOptions
+ _libraryManager.DeleteItem(item, new DeleteOptions
{
DeleteFileLocation = true
- }).ConfigureAwait(false);
+ }, true);
}
catch (Exception ex)
{
@@ -1699,7 +1653,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
private readonly SemaphoreSlim _recordingDeleteSemaphore = new SemaphoreSlim(1, 1);
- private async Task DeleteLibraryItemsForTimers(List<TimerInfo> timers)
+ private void DeleteLibraryItemsForTimers(List<TimerInfo> timers)
{
foreach (var timer in timers)
{
@@ -1710,7 +1664,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
try
{
- await DeleteLibraryItemForTimer(timer).ConfigureAwait(false);
+ DeleteLibraryItemForTimer(timer);
}
catch (Exception ex)
{
@@ -1719,17 +1673,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
}
- private async Task DeleteLibraryItemForTimer(TimerInfo timer)
+ private void DeleteLibraryItemForTimer(TimerInfo timer)
{
var libraryItem = _libraryManager.FindByPath(timer.RecordingPath, false);
if (libraryItem != null)
{
- await _libraryManager.DeleteItem(libraryItem, new DeleteOptions
+ _libraryManager.DeleteItem(libraryItem, new DeleteOptions
{
DeleteFileLocation = true
- }).ConfigureAwait(false);
+ }, true);
}
else
{
@@ -1783,57 +1737,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return false;
}
- private async Task<IRecorder> GetRecorder()
+ private IRecorder GetRecorder(MediaSourceInfo mediaSource)
{
- var config = GetConfiguration();
-
- var regInfo = await _liveTvManager.GetRegistrationInfo("dvr").ConfigureAwait(false);
-
- if (regInfo.IsValid)
+ if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
{
- if (config.EnableRecordingEncoding)
- {
- return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config, _httpClient, _processFactory, _config);
- }
-
- return new DirectRecorder(_logger, _httpClient, _fileSystem);
-
- //var options = new LiveTvOptions
- //{
- // EnableOriginalAudioWithEncodedRecordings = true,
- // RecordedVideoCodec = "copy",
- // RecordingEncodingFormat = "ts"
- //};
- //return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, options, _httpClient, _processFactory, _config);
+ return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _httpClient, _processFactory, _config, _assemblyInfo);
}
- throw new InvalidOperationException("Emby DVR Requires an active Emby Premiere subscription.");
+ return new DirectRecorder(_logger, _httpClient, _fileSystem, _streamHelper);
}
private void OnSuccessfulRecording(TimerInfo timer, string path)
{
- //if (timer.IsProgramSeries && GetConfiguration().EnableAutoOrganize)
- //{
- // try
- // {
- // // this is to account for the library monitor holding a lock for additional time after the change is complete.
- // // ideally this shouldn't be hard-coded
- // await Task.Delay(30000).ConfigureAwait(false);
-
- // var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager);
-
- // var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false);
-
- // if (result.Status == FileSortingStatus.Success)
- // {
- // return;
- // }
- // }
- // catch (Exception ex)
- // {
- // _logger.ErrorException("Error processing new recording", ex);
- // }
- //}
PostProcessRecording(timer, path);
}
@@ -2026,7 +1941,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
program = new LiveTvProgram
{
Name = timer.Name,
- HomePageUrl = timer.HomePageUrl,
Overview = timer.Overview,
Genres = timer.Genres,
CommunityRating = timer.CommunityRating,
@@ -2040,16 +1954,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (timer.IsSports)
{
- AddGenre(program.Genres, "Sports");
+ program.AddGenre("Sports");
}
if (timer.IsKids)
{
- AddGenre(program.Genres, "Kids");
- AddGenre(program.Genres, "Children");
+ program.AddGenre("Kids");
+ program.AddGenre("Children");
}
if (timer.IsNews)
{
- AddGenre(program.Genres, "News");
+ program.AddGenre("News");
}
if (timer.IsProgramSeries)
@@ -2097,12 +2011,30 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
writer.WriteStartDocument(true);
writer.WriteStartElement("tvshow");
+ string id;
+ if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Tvdb.ToString(), out id))
+ {
+ writer.WriteElementString("id", id);
+ }
+ if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Imdb.ToString(), out id))
+ {
+ writer.WriteElementString("imdb_id", id);
+ }
+ if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Tmdb.ToString(), out id))
+ {
+ writer.WriteElementString("tmdbid", id);
+ }
+ if (timer.SeriesProviderIds.TryGetValue(MetadataProviders.Zap2It.ToString(), out id))
+ {
+ writer.WriteElementString("zap2itid", id);
+ }
+
if (!string.IsNullOrWhiteSpace(timer.Name))
{
writer.WriteElementString("title", timer.Name);
}
- if (!string.IsNullOrEmpty(timer.OfficialRating))
+ if (!string.IsNullOrWhiteSpace(timer.OfficialRating))
{
writer.WriteElementString("mpaa", timer.OfficialRating);
}
@@ -2139,11 +2071,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var options = _config.GetNfoConfiguration();
+ var isSeriesEpisode = timer.IsProgramSeries;
+
using (XmlWriter writer = XmlWriter.Create(stream, settings))
{
writer.WriteStartDocument(true);
- if (timer.IsProgramSeries)
+ if (isSeriesEpisode)
{
writer.WriteStartElement("episodedetails");
@@ -2152,11 +2086,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
writer.WriteElementString("title", timer.EpisodeTitle);
}
- if (item.PremiereDate.HasValue)
+ var premiereDate = item.PremiereDate ?? (!timer.IsRepeat ? DateTime.UtcNow : (DateTime?)null);
+
+ if (premiereDate.HasValue)
{
var formatString = options.ReleaseDateFormat;
- writer.WriteElementString("aired", item.PremiereDate.Value.ToLocalTime().ToString(formatString));
+ writer.WriteElementString("aired", premiereDate.Value.ToLocalTime().ToString(formatString));
}
if (item.IndexNumber.HasValue)
@@ -2210,11 +2146,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
writer.WriteElementString("plot", overview);
- if (lockData)
- {
- writer.WriteElementString("lockdata", true.ToString().ToLower());
- }
-
if (item.CommunityRating.HasValue)
{
writer.WriteElementString("rating", item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture));
@@ -2225,12 +2156,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
writer.WriteElementString("genre", genre);
}
- if (!string.IsNullOrWhiteSpace(item.HomePageUrl))
- {
- writer.WriteElementString("website", item.HomePageUrl);
- }
-
- var people = item.Id == Guid.Empty ? new List<PersonInfo>() : _libraryManager.GetPeople(item);
+ var people = item.Id.Equals(Guid.Empty) ? new List<PersonInfo>() : _libraryManager.GetPeople(item);
var directors = people
.Where(i => IsPersonType(i, PersonType.Director))
@@ -2268,26 +2194,38 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var imdb = item.GetProviderId(MetadataProviders.Imdb);
if (!string.IsNullOrEmpty(imdb))
{
- if (item is Series)
- {
- writer.WriteElementString("imdb_id", imdb);
- }
- else
+ if (!isSeriesEpisode)
{
- writer.WriteElementString("imdbid", imdb);
+ writer.WriteElementString("id", imdb);
}
+
+ writer.WriteElementString("imdbid", imdb);
+
+ // No need to lock if we have identified the content already
+ lockData = false;
}
var tvdb = item.GetProviderId(MetadataProviders.Tvdb);
if (!string.IsNullOrEmpty(tvdb))
{
writer.WriteElementString("tvdbid", tvdb);
+
+ // No need to lock if we have identified the content already
+ lockData = false;
}
var tmdb = item.GetProviderId(MetadataProviders.Tmdb);
if (!string.IsNullOrEmpty(tmdb))
{
writer.WriteElementString("tmdbid", tmdb);
+
+ // No need to lock if we have identified the content already
+ lockData = false;
+ }
+
+ if (lockData)
+ {
+ writer.WriteElementString("lockdata", true.ToString().ToLower());
}
if (item.CriticRating.HasValue)
@@ -2328,7 +2266,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
var query = new InternalItemsQuery
{
- ItemIds = new[] { _liveTvManager.GetInternalProgramId(Name, programId).ToString("N") },
+ ItemIds = new[] { _liveTvManager.GetInternalProgramId(programId) },
Limit = 1,
DtoOptions = new DtoOptions()
};
@@ -2358,12 +2296,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
},
MinStartDate = startDateUtc.AddMinutes(-3),
MaxStartDate = startDateUtc.AddMinutes(3),
- OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) }
+ OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) }
};
if (!string.IsNullOrWhiteSpace(channelId))
{
- query.ChannelIds = new[] { _liveTvManager.GetInternalChannelId(Name, channelId).ToString("N") };
+ query.ChannelIds = new[] { _liveTvManager.GetInternalChannelId(Name, channelId) };
}
return _libraryManager.GetItemList(query).Cast<LiveTvProgram>().FirstOrDefault();
@@ -2383,7 +2321,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (!seriesTimer.RecordAnyTime)
{
- if (Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - timer.StartDate.TimeOfDay.Ticks) >= TimeSpan.FromMinutes(5).Ticks)
+ if (Math.Abs(seriesTimer.StartDate.TimeOfDay.Ticks - timer.StartDate.TimeOfDay.Ticks) >= TimeSpan.FromMinutes(10).Ticks)
{
return true;
}
@@ -2473,6 +2411,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
enabledTimersForSeries.Add(timer);
}
_timerProvider.Add(timer);
+
+ if (TimerCreated != null)
+ {
+ TimerCreated(this, new GenericEventArgs<TimerInfo>(timer));
+ }
}
else
{
@@ -2525,7 +2468,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
.Select(i => i.Id)
.ToList();
- var deleteStatuses = new List<RecordingStatus>
+ var deleteStatuses = new[]
{
RecordingStatus.New
};
@@ -2538,7 +2481,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var timer in deletes)
{
- CancelTimerInternal(timer.Id, false);
+ CancelTimerInternal(timer.Id, false, false);
}
}
}
@@ -2550,11 +2493,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new ArgumentNullException("seriesTimer");
}
- if (string.IsNullOrWhiteSpace(seriesTimer.SeriesId))
- {
- return new List<TimerInfo>();
- }
-
var query = new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
@@ -2566,21 +2504,26 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
MinEndDate = DateTime.UtcNow
};
+ if (string.IsNullOrEmpty(seriesTimer.SeriesId))
+ {
+ query.Name = seriesTimer.Name;
+ }
+
if (!seriesTimer.RecordAnyChannel)
{
- query.ChannelIds = new[] { _liveTvManager.GetInternalChannelId(Name, seriesTimer.ChannelId).ToString("N") };
+ query.ChannelIds = new[] { _liveTvManager.GetInternalChannelId(Name, seriesTimer.ChannelId) };
}
- var tempChannelCache = new Dictionary<string, LiveTvChannel>();
+ var tempChannelCache = new Dictionary<Guid, LiveTvChannel>();
return _libraryManager.GetItemList(query).Cast<LiveTvProgram>().Select(i => CreateTimer(i, seriesTimer, tempChannelCache));
}
- private TimerInfo CreateTimer(LiveTvProgram parent, SeriesTimerInfo seriesTimer, Dictionary<string, LiveTvChannel> tempChannelCache)
+ private TimerInfo CreateTimer(LiveTvProgram parent, SeriesTimerInfo seriesTimer, Dictionary<Guid, LiveTvChannel> tempChannelCache)
{
string channelId = seriesTimer.RecordAnyChannel ? null : seriesTimer.ChannelId;
- if (string.IsNullOrWhiteSpace(channelId) && !string.IsNullOrWhiteSpace(parent.ChannelId))
+ if (string.IsNullOrWhiteSpace(channelId) && !parent.ChannelId.Equals(Guid.Empty))
{
LiveTvChannel channel;
@@ -2633,15 +2576,15 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private void CopyProgramInfoToTimerInfo(LiveTvProgram programInfo, TimerInfo timerInfo)
{
- var tempChannelCache = new Dictionary<string, LiveTvChannel>();
+ var tempChannelCache = new Dictionary<Guid, LiveTvChannel>();
CopyProgramInfoToTimerInfo(programInfo, timerInfo, tempChannelCache);
}
- private void CopyProgramInfoToTimerInfo(LiveTvProgram programInfo, TimerInfo timerInfo, Dictionary<string, LiveTvChannel> tempChannelCache)
+ private void CopyProgramInfoToTimerInfo(LiveTvProgram programInfo, TimerInfo timerInfo, Dictionary<Guid, LiveTvChannel> tempChannelCache)
{
string channelId = null;
- if (!string.IsNullOrWhiteSpace(programInfo.ChannelId))
+ if (!programInfo.ChannelId.Equals(Guid.Empty))
{
LiveTvChannel channel;
@@ -2679,24 +2622,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
timerInfo.SeasonNumber = programInfo.ParentIndexNumber;
timerInfo.EpisodeNumber = programInfo.IndexNumber;
timerInfo.IsMovie = programInfo.IsMovie;
- timerInfo.IsKids = programInfo.IsKids;
- timerInfo.IsNews = programInfo.IsNews;
- timerInfo.IsSports = programInfo.IsSports;
timerInfo.ProductionYear = programInfo.ProductionYear;
timerInfo.EpisodeTitle = programInfo.EpisodeTitle;
timerInfo.OriginalAirDate = programInfo.PremiereDate;
timerInfo.IsProgramSeries = programInfo.IsSeries;
timerInfo.IsSeries = programInfo.IsSeries;
- timerInfo.IsLive = programInfo.IsLive;
- timerInfo.IsPremiere = programInfo.IsPremiere;
- timerInfo.HomePageUrl = programInfo.HomePageUrl;
timerInfo.CommunityRating = programInfo.CommunityRating;
timerInfo.Overview = programInfo.Overview;
timerInfo.OfficialRating = programInfo.OfficialRating;
timerInfo.IsRepeat = programInfo.IsRepeat;
timerInfo.SeriesId = programInfo.ExternalSeriesId;
+ timerInfo.ProviderIds = programInfo.ProviderIds;
+ timerInfo.Tags = programInfo.Tags;
+
+ var seriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ foreach (var providerId in timerInfo.ProviderIds)
+ {
+ var srch = "Series";
+ if (providerId.Key.StartsWith(srch, StringComparison.OrdinalIgnoreCase))
+ {
+ seriesProviderIds[providerId.Key.Substring(srch.Length)] = providerId.Value;
+ }
+ }
+
+ timerInfo.SeriesProviderIds = seriesProviderIds;
}
private bool IsProgramAlreadyInLibrary(TimerInfo program)
@@ -2708,7 +2660,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
IncludeItemTypes = new[] { typeof(Series).Name },
Name = program.Name
- }).Select(i => i.ToString("N")).ToArray();
+ }).ToArray();
if (seriesIds.Length == 0)
{
@@ -2745,7 +2697,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
pair.Value.CancellationTokenSource.Cancel();
}
- GC.SuppressFinalize(this);
}
public List<VirtualFolderInfo> GetRecordingFolders()
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTVRegistration.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTVRegistration.cs
deleted file mode 100644
index b339537ae7..0000000000
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTVRegistration.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Threading.Tasks;
-using MediaBrowser.Common.Security;
-
-namespace Emby.Server.Implementations.LiveTv.EmbyTV
-{
- public class EmbyTVRegistration : IRequiresRegistration
- {
- private readonly ISecurityManager _securityManager;
-
- public static EmbyTVRegistration Instance;
-
- public EmbyTVRegistration(ISecurityManager securityManager)
- {
- _securityManager = securityManager;
- Instance = this;
- }
-
- private bool? _isXmlTvEnabled;
-
- public Task LoadRegistrationInfoAsync()
- {
- _isXmlTvEnabled = null;
- return Task.FromResult(true);
- }
-
- public async Task<bool> EnableXmlTv()
- {
- if (!_isXmlTvEnabled.HasValue)
- {
- var info = await _securityManager.GetRegistrationStatus("xmltv").ConfigureAwait(false);
- _isXmlTvEnabled = info.IsValid;
- }
- return _isXmlTvEnabled.Value;
- }
- }
-}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index d6f5e0d9fe..9506a82beb 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -22,6 +22,7 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Reflection;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
@@ -32,7 +33,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IHttpClient _httpClient;
private readonly IMediaEncoder _mediaEncoder;
private readonly IServerApplicationPaths _appPaths;
- private readonly LiveTvOptions _liveTvOptions;
private bool _hasExited;
private Stream _logFileStream;
private string _targetPath;
@@ -41,37 +41,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IJsonSerializer _json;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly IServerConfigurationManager _config;
+ private readonly IAssemblyInfo _assemblyInfo;
- public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, LiveTvOptions liveTvOptions, IHttpClient httpClient, IProcessFactory processFactory, IServerConfigurationManager config)
+ public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, IHttpClient httpClient, IProcessFactory processFactory, IServerConfigurationManager config, IAssemblyInfo assemblyInfo)
{
_logger = logger;
_fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
_json = json;
- _liveTvOptions = liveTvOptions;
_httpClient = httpClient;
_processFactory = processFactory;
_config = config;
- }
-
- private string OutputFormat
- {
- get
- {
- var format = _liveTvOptions.RecordingEncodingFormat;
-
- if (string.Equals(format, "mkv", StringComparison.OrdinalIgnoreCase))
- {
- return "mkv";
- }
- if (string.Equals(format, "ts", StringComparison.OrdinalIgnoreCase))
- {
- return "ts";
- }
-
- return "mkv";
- }
+ _assemblyInfo = assemblyInfo;
}
private bool CopySubtitles
@@ -85,20 +67,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile)
{
- var extension = OutputFormat;
-
- if (string.Equals(extension, "mpegts", StringComparison.OrdinalIgnoreCase))
- {
- extension = "ts";
- }
-
- return Path.ChangeExtension(targetFile, "." + extension);
+ return Path.ChangeExtension(targetFile, ".ts");
}
public async Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
- //var durationToken = new CancellationTokenSource(duration);
- //cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ // The media source is infinite so we need to handle stopping ourselves
+ var durationToken = new CancellationTokenSource(duration);
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false);
@@ -185,6 +161,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
videoArgs += " -fflags +genpts";
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
+ durationParam = string.Empty;
var flags = new List<string>();
if (mediaSource.IgnoreDts)
@@ -208,9 +185,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
var videoStream = mediaSource.VideoStream;
- var videoDecoder = videoStream == null ? null : new EncodingHelper(_mediaEncoder, _fileSystem, null).GetVideoDecoder(VideoType.VideoFile, videoStream, GetEncodingOptions());
+ string videoDecoder = null;
- if (!string.IsNullOrWhiteSpace(videoDecoder))
+ if (!string.IsNullOrEmpty(videoDecoder))
{
inputModifier += " " + videoDecoder;
}
@@ -222,7 +199,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
if (mediaSource.RequiresLooping)
{
- inputModifier += " -stream_loop -1";
+ inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
}
var analyzeDurationSeconds = 5;
@@ -255,39 +232,27 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var mediaStreams = mediaSource.MediaStreams ?? new List<MediaStream>();
var inputAudioCodec = mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Select(i => i.Codec).FirstOrDefault() ?? string.Empty;
- // do not copy aac because many players have difficulty with aac_latm
- if (_liveTvOptions.EnableOriginalAudioWithEncodedRecordings && !string.Equals(inputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase))
- {
- return "-codec:a:0 copy";
- }
+ return "-codec:a:0 copy";
- var audioChannels = 2;
- var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
- if (audioStream != null)
- {
- audioChannels = audioStream.Channels ?? audioChannels;
- }
- return "-codec:a:0 aac -strict experimental -ab 320000";
+ //var audioChannels = 2;
+ //var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
+ //if (audioStream != null)
+ //{
+ // audioChannels = audioStream.Channels ?? audioChannels;
+ //}
+ //return "-codec:a:0 aac -strict experimental -ab 320000";
}
private bool EncodeVideo(MediaSourceInfo mediaSource)
{
- var mediaStreams = mediaSource.MediaStreams ?? new List<MediaStream>();
- return !mediaStreams.Any(i => i.Type == MediaStreamType.Video && string.Equals(i.Codec, "h264", StringComparison.OrdinalIgnoreCase) && !i.IsInterlaced);
+ return false;
}
protected string GetOutputSizeParam()
{
var filters = new List<string>();
-
- if (string.Equals(GetEncodingOptions().DeinterlaceMethod, "bobandweave", StringComparison.OrdinalIgnoreCase))
- {
- filters.Add("yadif=1:-1:0");
- }
- else
- {
- filters.Add("yadif=0:-1:0");
- }
+
+ filters.Add("yadif=0:-1:0");
var output = string.Empty;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs
index 7c5f630a70..cc9e80a821 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EntryPoint.cs
@@ -12,7 +12,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public void Dispose()
{
- GC.SuppressFinalize(this);
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index a5712b4808..e694a82811 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
@@ -26,11 +26,18 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
else if (info.OriginalAirDate.HasValue)
{
- name += " " + info.OriginalAirDate.Value.ToString("yyyy-MM-dd");
+ if (info.OriginalAirDate.Value.Date.Equals(info.StartDate.Date))
+ {
+ name += " " + GetDateString(info.StartDate);
+ }
+ else
+ {
+ name += " " + info.OriginalAirDate.Value.ToLocalTime().ToString("yyyy-MM-dd");
+ }
}
else
{
- name += " " + DateTime.Now.ToString("yyyy-MM-dd");
+ name += " " + GetDateString(info.StartDate);
}
if (!string.IsNullOrWhiteSpace(info.EpisodeTitle))
@@ -50,10 +57,24 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
else
{
- name += " " + info.StartDate.ToString("yyyy-MM-dd");
+ name += " " + GetDateString(info.StartDate);
}
return name;
}
+
+ private static string GetDateString(DateTime date)
+ {
+ date = date.ToLocalTime();
+
+ return string.Format("{0}_{1}_{2}_{3}_{4}_{5}",
+ date.Year.ToString("0000", CultureInfo.InvariantCulture),
+ date.Month.ToString("00", CultureInfo.InvariantCulture),
+ date.Day.ToString("00", CultureInfo.InvariantCulture),
+ date.Hour.ToString("00", CultureInfo.InvariantCulture),
+ date.Minute.ToString("00", CultureInfo.InvariantCulture),
+ date.Second.ToString("00", CultureInfo.InvariantCulture)
+ );
+ }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
index 843ba7e42c..63cd26c7e1 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
@@ -15,7 +15,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public override void Add(SeriesTimerInfo item)
{
- if (string.IsNullOrWhiteSpace(item.Id))
+ if (string.IsNullOrEmpty(item.Id))
{
throw new ArgumentException("SeriesTimerInfo.Id cannot be null or empty.");
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index 380b248001..b5f93b8828 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -13,6 +13,7 @@ using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Threading;
+using MediaBrowser.Model.System;
namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
@@ -23,12 +24,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
private readonly ITimerFactory _timerFactory;
+ private readonly IPowerManagement _powerManagement;
- public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1, ITimerFactory timerFactory)
+ public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1, ITimerFactory timerFactory, IPowerManagement powerManagement)
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
_logger = logger1;
_timerFactory = timerFactory;
+ _powerManagement = powerManagement;
}
public void RestartTimers()
@@ -37,7 +40,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
foreach (var item in GetAll().ToList())
{
- AddOrUpdateSystemTimer(item);
+ AddOrUpdateSystemTimer(item, false);
}
}
@@ -60,7 +63,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public override void Update(TimerInfo item)
{
base.Update(item);
- AddOrUpdateSystemTimer(item);
+ AddOrUpdateSystemTimer(item, false);
}
public void AddOrUpdate(TimerInfo item, bool resetTimer)
@@ -85,13 +88,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public override void Add(TimerInfo item)
{
- if (string.IsNullOrWhiteSpace(item.Id))
+ if (string.IsNullOrEmpty(item.Id))
{
throw new ArgumentException("TimerInfo.Id cannot be null or empty.");
}
base.Add(item);
- AddOrUpdateSystemTimer(item);
+ AddOrUpdateSystemTimer(item, true);
}
private bool ShouldStartTimer(TimerInfo item)
@@ -105,7 +108,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return true;
}
- private void AddOrUpdateSystemTimer(TimerInfo item)
+ private void AddOrUpdateSystemTimer(TimerInfo item, bool scheduleSystemWakeTimer)
{
StopTimer(item);
@@ -125,6 +128,23 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var dueTime = startDate - now;
StartTimer(item, dueTime);
+
+ if (scheduleSystemWakeTimer && dueTime >= TimeSpan.FromMinutes(15))
+ {
+ ScheduleSystemWakeTimer(startDate, item.Name);
+ }
+ }
+
+ private void ScheduleSystemWakeTimer(DateTime startDate, string displayName)
+ {
+ try
+ {
+ _powerManagement.ScheduleWake(startDate.AddMinutes(-5), displayName);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error scheduling wake timer", ex);
+ }
}
private void StartTimer(TimerInfo item, TimeSpan dueTime)
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index e210e2224e..9021666a30 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -16,6 +16,9 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Extensions;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.LiveTv.Listings
{
@@ -63,7 +66,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
{
- if (string.IsNullOrWhiteSpace(channelId))
+ if (string.IsNullOrEmpty(channelId))
{
throw new ArgumentNullException("channelId");
}
@@ -75,7 +78,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
- if (string.IsNullOrWhiteSpace(token))
+ if (string.IsNullOrEmpty(token))
{
_logger.Warn("SchedulesDirect token is empty, returning empty program list");
return programsInfo;
@@ -246,8 +249,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
DateTime endAt = startAt.AddSeconds(programInfo.duration);
ProgramAudio audioType = ProgramAudio.Stereo;
- bool repeat = programInfo.@new == null;
-
var programId = programInfo.programID ?? string.Empty;
string newID = programId + "T" + startAt.Ticks + "C" + channelId;
@@ -293,14 +294,17 @@ namespace Emby.Server.Implementations.LiveTv.Listings
CommunityRating = null,
EpisodeTitle = episodeTitle,
Audio = audioType,
- IsRepeat = repeat,
+ //IsNew = programInfo.@new ?? false,
+ IsRepeat = programInfo.@new == null,
IsSeries = string.Equals(details.entityType, "episode", StringComparison.OrdinalIgnoreCase),
ImageUrl = details.primaryImage,
ThumbImageUrl = details.thumbImage,
IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase),
IsSports = string.Equals(details.entityType, "sports", StringComparison.OrdinalIgnoreCase),
IsMovie = IsMovie(details),
- Etag = programInfo.md5
+ Etag = programInfo.md5,
+ IsLive = string.Equals(programInfo.liveTapeDelay, "live", StringComparison.OrdinalIgnoreCase),
+ IsPremiere = programInfo.premiere || (programInfo.isPremiereOrFinale ?? string.Empty).IndexOf("premiere", StringComparison.OrdinalIgnoreCase) != -1
};
var showId = programId;
@@ -356,6 +360,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
info.SeriesId = programId.Substring(0, 10);
+ info.SeriesProviderIds[MetadataProviders.Zap2It.ToString()] = info.SeriesId;
+
if (details.metadata != null)
{
foreach (var metadataProgram in details.metadata)
@@ -376,12 +382,21 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- if (!string.IsNullOrWhiteSpace(details.originalAirDate) && (!info.IsSeries || info.IsRepeat))
+ if (!string.IsNullOrWhiteSpace(details.originalAirDate))
{
info.OriginalAirDate = DateTime.Parse(details.originalAirDate);
info.ProductionYear = info.OriginalAirDate.Value.Year;
}
+ if (details.movie != null)
+ {
+ int year;
+ if (!string.IsNullOrEmpty(details.movie.year) && int.TryParse(details.movie.year, out year))
+ {
+ info.ProductionYear = year;
+ }
+ }
+
if (details.genres != null)
{
info.Genres = details.genres.Where(g => !string.IsNullOrWhiteSpace(g)).ToList();
@@ -506,8 +521,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using (var innerResponse2 = await Post(httpOptions, true, info).ConfigureAwait(false))
{
- return _jsonSerializer.DeserializeFromStream<List<ScheduleDirect.ShowImages>>(
- innerResponse2.Content);
+ return await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.ShowImages>>(
+ innerResponse2.Content).ConfigureAwait(false);
}
}
catch (Exception ex)
@@ -545,7 +560,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using (Stream responce = httpResponse.Content)
{
- var root = _jsonSerializer.DeserializeFromStream<List<ScheduleDirect.Headends>>(responce);
+ var root = await _jsonSerializer.DeserializeFromStreamAsync<List<ScheduleDirect.Headends>>(responce).ConfigureAwait(false);
if (root != null)
{
@@ -589,7 +604,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
var password = info.Password;
- if (string.IsNullOrWhiteSpace(password))
+ if (string.IsNullOrEmpty(password))
{
return null;
}
@@ -607,7 +622,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
_tokens.TryAdd(username, savedToken);
}
- if (!string.IsNullOrWhiteSpace(savedToken.Name) && !string.IsNullOrWhiteSpace(savedToken.Value))
+ if (!string.IsNullOrEmpty(savedToken.Name) && !string.IsNullOrEmpty(savedToken.Value))
{
long ticks;
if (long.TryParse(savedToken.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out ticks))
@@ -740,7 +755,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using (var responce = await Post(httpOptions, false, null).ConfigureAwait(false))
{
- var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Token>(responce.Content);
+ var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(responce.Content).ConfigureAwait(false);
if (root.message == "OK")
{
_logger.Info("Authenticated with Schedules Direct token: " + root.token);
@@ -755,12 +770,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
var token = await GetToken(info, cancellationToken);
- if (string.IsNullOrWhiteSpace(token))
+ if (string.IsNullOrEmpty(token))
{
throw new ArgumentException("Authentication required.");
}
- if (string.IsNullOrWhiteSpace(info.ListingsId))
+ if (string.IsNullOrEmpty(info.ListingsId))
{
throw new ArgumentException("Listings Id required");
}
@@ -796,14 +811,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private async Task<bool> HasLineup(ListingsProviderInfo info, CancellationToken cancellationToken)
{
- if (string.IsNullOrWhiteSpace(info.ListingsId))
+ if (string.IsNullOrEmpty(info.ListingsId))
{
throw new ArgumentException("Listings Id required");
}
var token = await GetToken(info, cancellationToken);
- if (string.IsNullOrWhiteSpace(token))
+ if (string.IsNullOrEmpty(token))
{
throw new Exception("token required");
}
@@ -826,7 +841,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using (var response = httpResponse.Content)
{
- var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Lineups>(response);
+ var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Lineups>(response).ConfigureAwait(false);
return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
}
@@ -848,18 +863,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
if (validateLogin)
{
- if (string.IsNullOrWhiteSpace(info.Username))
+ if (string.IsNullOrEmpty(info.Username))
{
throw new ArgumentException("Username is required");
}
- if (string.IsNullOrWhiteSpace(info.Password))
+ if (string.IsNullOrEmpty(info.Password))
{
throw new ArgumentException("Password is required");
}
}
if (validateListings)
{
- if (string.IsNullOrWhiteSpace(info.ListingsId))
+ if (string.IsNullOrEmpty(info.ListingsId))
{
throw new ArgumentException("Listings Id required");
}
@@ -881,14 +896,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public async Task<List<ChannelInfo>> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken)
{
var listingsId = info.ListingsId;
- if (string.IsNullOrWhiteSpace(listingsId))
+ if (string.IsNullOrEmpty(listingsId))
{
throw new Exception("ListingsId required");
}
var token = await GetToken(info, cancellationToken);
- if (string.IsNullOrWhiteSpace(token))
+ if (string.IsNullOrEmpty(token))
{
throw new Exception("token required");
}
@@ -911,7 +926,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using (var response = httpResponse.Content)
{
- var root = _jsonSerializer.DeserializeFromStream<ScheduleDirect.Channel>(response);
+ var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Channel>(response).ConfigureAwait(false);
_logger.Info("Found " + root.map.Count + " channels on the lineup on ScheduleDirect");
_logger.Info("Mapping Stations to Channel");
@@ -951,7 +966,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (station.logo != null)
{
channelInfo.ImageUrl = station.logo.URL;
- channelInfo.HasImage = true;
}
}
@@ -1112,6 +1126,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public List<Rating> ratings { get; set; }
public bool? @new { get; set; }
public Multipart multipart { get; set; }
+ public string liveTapeDelay { get; set; }
+ public bool premiere { get; set; }
+ public bool repeat { get; set; }
+ public string isPremiereOrFinale { get; set; }
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs b/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
deleted file mode 100644
index 7c251e3039..0000000000
--- a/Emby.Server.Implementations/LiveTv/Listings/XmlTvListingsProvider.cs
+++ /dev/null
@@ -1,315 +0,0 @@
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.LiveTv;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.IO.Compression;
-using System.Linq;
-using System.Net;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Emby.XmlTv.Classes;
-using Emby.XmlTv.Entities;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Common.Progress;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-
-namespace Emby.Server.Implementations.LiveTv.Listings
-{
- public class XmlTvListingsProvider : IListingsProvider
- {
- private readonly IServerConfigurationManager _config;
- private readonly IHttpClient _httpClient;
- private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
- private readonly IZipClient _zipClient;
-
- public XmlTvListingsProvider(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger, IFileSystem fileSystem, IZipClient zipClient)
- {
- _config = config;
- _httpClient = httpClient;
- _logger = logger;
- _fileSystem = fileSystem;
- _zipClient = zipClient;
- }
-
- public string Name
- {
- get { return "XmlTV"; }
- }
-
- public string Type
- {
- get { return "xmltv"; }
- }
-
- private string GetLanguage(ListingsProviderInfo info)
- {
- if (!string.IsNullOrWhiteSpace(info.PreferredLanguage))
- {
- return info.PreferredLanguage;
- }
-
- return _config.Configuration.PreferredMetadataLanguage;
- }
-
- private async Task<string> GetXml(string path, CancellationToken cancellationToken)
- {
- _logger.Info("xmltv path: {0}", path);
-
- if (!path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
- {
- return UnzipIfNeeded(path, path);
- }
-
- var cacheFilename = DateTime.UtcNow.DayOfYear.ToString(CultureInfo.InvariantCulture) + "-" + DateTime.UtcNow.Hour.ToString(CultureInfo.InvariantCulture) + ".xml";
- var cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
- if (_fileSystem.FileExists(cacheFile))
- {
- return UnzipIfNeeded(path, cacheFile);
- }
-
- _logger.Info("Downloading xmltv listings from {0}", path);
-
- var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = path,
- Progress = new SimpleProgress<Double>(),
- DecompressionMethod = CompressionMethod.Gzip,
-
- // It's going to come back gzipped regardless of this value
- // So we need to make sure the decompression method is set to gzip
- EnableHttpCompression = true,
-
- UserAgent = "Emby/3.0"
-
- }).ConfigureAwait(false);
-
- _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(cacheFile));
-
- _fileSystem.CopyFile(tempFile, cacheFile, true);
-
- return UnzipIfNeeded(path, cacheFile);
- }
-
- private string UnzipIfNeeded(string originalUrl, string file)
- {
- var ext = Path.GetExtension(originalUrl.Split('?')[0]);
-
- if (string.Equals(ext, ".gz", StringComparison.OrdinalIgnoreCase))
- {
- try
- {
- var tempFolder = ExtractGz(file);
- return FindXmlFile(tempFolder);
- }
- catch (Exception ex)
- {
- //_logger.ErrorException("Error extracting from gz file {0}", ex, file);
- }
-
- try
- {
- var tempFolder = ExtractFirstFileFromGz(file);
- return FindXmlFile(tempFolder);
- }
- catch (Exception ex)
- {
- //_logger.ErrorException("Error extracting from zip file {0}", ex, file);
- }
- }
-
- return file;
- }
-
- private string ExtractFirstFileFromGz(string file)
- {
- using (var stream = _fileSystem.OpenRead(file))
- {
- var tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
- _fileSystem.CreateDirectory(tempFolder);
-
- _zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml");
-
- return tempFolder;
- }
- }
-
- private string ExtractGz(string file)
- {
- using (var stream = _fileSystem.OpenRead(file))
- {
- var tempFolder = Path.Combine(_config.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString());
- _fileSystem.CreateDirectory(tempFolder);
-
- _zipClient.ExtractAllFromGz(stream, tempFolder, true);
-
- return tempFolder;
- }
- }
-
- private string FindXmlFile(string directory)
- {
- return _fileSystem.GetFiles(directory, true)
- .Where(i => string.Equals(i.Extension, ".xml", StringComparison.OrdinalIgnoreCase))
- .Select(i => i.FullName)
- .FirstOrDefault();
- }
-
- public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
- {
- if (string.IsNullOrWhiteSpace(channelId))
- {
- throw new ArgumentNullException("channelId");
- }
-
- if (!await EmbyTV.EmbyTVRegistration.Instance.EnableXmlTv().ConfigureAwait(false))
- {
- var length = endDateUtc - startDateUtc;
- if (length.TotalDays > 1)
- {
- endDateUtc = startDateUtc.AddDays(1);
- }
- }
-
- _logger.Debug("Getting xmltv programs for channel {0}", channelId);
-
- var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
- _logger.Debug("Opening XmlTvReader for {0}", path);
- var reader = new XmlTvReader(path, GetLanguage(info));
-
- var results = reader.GetProgrammes(channelId, startDateUtc, endDateUtc, cancellationToken);
- return results.Select(p => GetProgramInfo(p, info));
- }
-
- private ProgramInfo GetProgramInfo(XmlTvProgram p, ListingsProviderInfo info)
- {
- var episodeTitle = p.Episode == null ? null : p.Episode.Title;
-
- var programInfo = new ProgramInfo
- {
- ChannelId = p.ChannelId,
- EndDate = GetDate(p.EndDate),
- EpisodeNumber = p.Episode == null ? null : p.Episode.Episode,
- EpisodeTitle = episodeTitle,
- Genres = p.Categories,
- StartDate = GetDate(p.StartDate),
- Name = p.Title,
- Overview = p.Description,
- ProductionYear = !p.CopyrightDate.HasValue ? (int?)null : p.CopyrightDate.Value.Year,
- SeasonNumber = p.Episode == null ? null : p.Episode.Series,
- IsSeries = p.Episode != null,
- IsRepeat = p.IsPreviouslyShown && !p.IsNew,
- IsPremiere = p.Premiere != null,
- IsKids = p.Categories.Any(c => info.KidsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
- IsMovie = p.Categories.Any(c => info.MovieCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
- IsNews = p.Categories.Any(c => info.NewsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
- IsSports = p.Categories.Any(c => info.SportsCategories.Contains(c, StringComparer.OrdinalIgnoreCase)),
- ImageUrl = p.Icon != null && !String.IsNullOrEmpty(p.Icon.Source) ? p.Icon.Source : null,
- HasImage = p.Icon != null && !String.IsNullOrEmpty(p.Icon.Source),
- OfficialRating = p.Rating != null && !String.IsNullOrEmpty(p.Rating.Value) ? p.Rating.Value : null,
- CommunityRating = p.StarRating.HasValue ? p.StarRating.Value : (float?)null,
- SeriesId = p.Episode != null ? p.Title.GetMD5().ToString("N") : null
- };
-
- if (!string.IsNullOrWhiteSpace(p.ProgramId))
- {
- programInfo.ShowId = p.ProgramId;
- }
- else
- {
- var uniqueString = (p.Title ?? string.Empty) + (episodeTitle ?? string.Empty) + (p.IceTvEpisodeNumber ?? string.Empty);
-
- if (programInfo.SeasonNumber.HasValue)
- {
- uniqueString = "-" + programInfo.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture);
- }
- if (programInfo.EpisodeNumber.HasValue)
- {
- uniqueString = "-" + programInfo.EpisodeNumber.Value.ToString(CultureInfo.InvariantCulture);
- }
-
- programInfo.ShowId = uniqueString.GetMD5().ToString("N");
-
- // If we don't have valid episode info, assume it's a unique program, otherwise recordings might be skipped
- if (programInfo.IsSeries && !programInfo.IsRepeat)
- {
- if ((programInfo.EpisodeNumber ?? 0) == 0)
- {
- programInfo.ShowId = programInfo.ShowId + programInfo.StartDate.Ticks.ToString(CultureInfo.InvariantCulture);
- }
- }
- }
-
- // Construct an id from the channel and start date
- programInfo.Id = String.Format("{0}_{1:O}", p.ChannelId, p.StartDate);
-
- if (programInfo.IsMovie)
- {
- programInfo.IsSeries = false;
- programInfo.EpisodeNumber = null;
- programInfo.EpisodeTitle = null;
- }
-
- return programInfo;
- }
-
- private DateTime GetDate(DateTime date)
- {
- if (date.Kind != DateTimeKind.Utc)
- {
- date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
- }
- return date;
- }
-
- public Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings)
- {
- // Assume all urls are valid. check files for existence
- if (!info.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase) && !_fileSystem.FileExists(info.Path))
- {
- throw new FileNotFoundException("Could not find the XmlTv file specified:", info.Path);
- }
-
- return Task.FromResult(true);
- }
-
- public async Task<List<NameIdPair>> GetLineups(ListingsProviderInfo info, string country, string location)
- {
- // In theory this should never be called because there is always only one lineup
- var path = await GetXml(info.Path, CancellationToken.None).ConfigureAwait(false);
- _logger.Debug("Opening XmlTvReader for {0}", path);
- var reader = new XmlTvReader(path, GetLanguage(info));
- var results = reader.GetChannels();
-
- // Should this method be async?
- return results.Select(c => new NameIdPair() { Id = c.Id, Name = c.DisplayName }).ToList();
- }
-
- public async Task<List<ChannelInfo>> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken)
- {
- // In theory this should never be called because there is always only one lineup
- var path = await GetXml(info.Path, cancellationToken).ConfigureAwait(false);
- _logger.Debug("Opening XmlTvReader for {0}", path);
- var reader = new XmlTvReader(path, GetLanguage(info));
- var results = reader.GetChannels();
-
- // Should this method be async?
- return results.Select(c => new ChannelInfo
- {
- Id = c.Id,
- Name = c.DisplayName,
- ImageUrl = c.Icon != null && !String.IsNullOrEmpty(c.Icon.Source) ? c.Icon.Source : null,
- Number = string.IsNullOrWhiteSpace(c.Number) ? c.Id : c.Number
-
- }).ToList();
- }
- }
-} \ No newline at end of file
diff --git a/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs b/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs
deleted file mode 100644
index 143350a8b7..0000000000
--- a/Emby.Server.Implementations/LiveTv/LiveStreamHelper.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-using System;
-using System.Globalization;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-
-namespace Emby.Server.Implementations.LiveTv
-{
- public class LiveStreamHelper
- {
- private readonly IMediaEncoder _mediaEncoder;
- private readonly ILogger _logger;
-
- const int ProbeAnalyzeDurationMs = 3000;
- const int PlaybackAnalyzeDurationMs = 3000;
-
- public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger)
- {
- _mediaEncoder = mediaEncoder;
- _logger = logger;
- }
-
- public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
- {
- var originalRuntime = mediaSource.RunTimeTicks;
-
- var now = DateTime.UtcNow;
-
- var info = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
- {
- InputPath = mediaSource.Path,
- Protocol = mediaSource.Protocol,
- MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
- ExtractChapters = false,
- AnalyzeDurationMs = ProbeAnalyzeDurationMs
-
- }, cancellationToken).ConfigureAwait(false);
-
- _logger.Info("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture));
-
- mediaSource.Bitrate = info.Bitrate;
- mediaSource.Container = info.Container;
- mediaSource.Formats = info.Formats;
- mediaSource.MediaStreams = info.MediaStreams;
- mediaSource.RunTimeTicks = info.RunTimeTicks;
- mediaSource.Size = info.Size;
- mediaSource.Timestamp = info.Timestamp;
- mediaSource.Video3DFormat = info.Video3DFormat;
- mediaSource.VideoType = info.VideoType;
-
- mediaSource.DefaultSubtitleStreamIndex = null;
-
- // Null this out so that it will be treated like a live stream
- if (!originalRuntime.HasValue)
- {
- mediaSource.RunTimeTicks = null;
- }
-
- var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio);
-
- if (audioStream == null || audioStream.Index == -1)
- {
- mediaSource.DefaultAudioStreamIndex = null;
- }
- else
- {
- mediaSource.DefaultAudioStreamIndex = audioStream.Index;
- }
-
- var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video);
- if (videoStream != null)
- {
- if (!videoStream.BitRate.HasValue)
- {
- var width = videoStream.Width ?? 1920;
-
- if (width >= 3000)
- {
- videoStream.BitRate = 30000000;
- }
-
- else if (width >= 1900)
- {
- videoStream.BitRate = 20000000;
- }
-
- else if (width >= 1200)
- {
- videoStream.BitRate = 8000000;
- }
-
- else if (width >= 700)
- {
- videoStream.BitRate = 2000000;
- }
- }
-
- // This is coming up false and preventing stream copy
- videoStream.IsAVC = null;
- }
-
- // Try to estimate this
- mediaSource.InferTotalBitrate(true);
-
- mediaSource.AnalyzeDurationMs = PlaybackAnalyzeDurationMs;
- }
- }
-}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs b/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs
index 2be6427372..205a767eb8 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvConfigurationFactory.cs
@@ -8,7 +8,7 @@ namespace Emby.Server.Implementations.LiveTv
{
public IEnumerable<ConfigurationStore> GetConfigurations()
{
- return new List<ConfigurationStore>
+ return new ConfigurationStore[]
{
new ConfigurationStore
{
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 15bbca136a..56b3b5e4bd 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -15,6 +15,7 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Extensions;
+using System.Collections.Generic;
namespace Emby.Server.Implementations.LiveTv
{
@@ -36,19 +37,19 @@ namespace Emby.Server.Implementations.LiveTv
_libraryManager = libraryManager;
}
- public TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service, LiveTvProgram program, LiveTvChannel channel)
+ public TimerInfoDto GetTimerInfoDto(TimerInfo info, ILiveTvService service, LiveTvProgram program, BaseItem channel)
{
var dto = new TimerInfoDto
{
- Id = GetInternalTimerId(service.Name, info.Id).ToString("N"),
+ Id = GetInternalTimerId(info.Id),
Overview = info.Overview,
EndDate = info.EndDate,
Name = info.Name,
StartDate = info.StartDate,
ExternalId = info.Id,
- ChannelId = GetInternalChannelId(service.Name, info.ChannelId).ToString("N"),
+ ChannelId = GetInternalChannelId(service.Name, info.ChannelId),
Status = info.Status,
- SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N"),
+ SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) ? null : GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N"),
PrePaddingSeconds = info.PrePaddingSeconds,
PostPaddingSeconds = info.PostPaddingSeconds,
IsPostPaddingRequired = info.IsPostPaddingRequired,
@@ -65,7 +66,7 @@ namespace Emby.Server.Implementations.LiveTv
if (!string.IsNullOrEmpty(info.ProgramId))
{
- dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
+ dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N");
}
if (program != null)
@@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.LiveTv
dto.ProgramInfo.SeriesTimerId = dto.SeriesTimerId;
- if (!string.IsNullOrWhiteSpace(info.SeriesTimerId))
+ if (!string.IsNullOrEmpty(info.SeriesTimerId))
{
FillImages(dto.ProgramInfo, info.Name, info.SeriesId);
}
@@ -103,7 +104,7 @@ namespace Emby.Server.Implementations.LiveTv
{
var dto = new SeriesTimerInfoDto
{
- Id = GetInternalSeriesTimerId(service.Name, info.Id).ToString("N"),
+ Id = GetInternalSeriesTimerId(info.Id).ToString("N"),
Overview = info.Overview,
EndDate = info.EndDate,
Name = info.Name,
@@ -130,12 +131,12 @@ namespace Emby.Server.Implementations.LiveTv
if (!string.IsNullOrEmpty(info.ChannelId))
{
- dto.ChannelId = GetInternalChannelId(service.Name, info.ChannelId).ToString("N");
+ dto.ChannelId = GetInternalChannelId(service.Name, info.ChannelId);
}
if (!string.IsNullOrEmpty(info.ProgramId))
{
- dto.ProgramId = GetInternalProgramId(service.Name, info.ProgramId).ToString("N");
+ dto.ProgramId = GetInternalProgramId(info.ProgramId).ToString("N");
}
dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days.ToArray(info.Days.Count));
@@ -188,49 +189,47 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- if (!string.IsNullOrWhiteSpace(programSeriesId))
+ var program = _libraryManager.GetItemList(new InternalItemsQuery
{
- var program = _libraryManager.GetItemList(new InternalItemsQuery
- {
- IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
- ExternalSeriesId = programSeriesId,
- Limit = 1,
- ImageTypes = new ImageType[] { ImageType.Primary },
- DtoOptions = new DtoOptions(false)
+ IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
+ ExternalSeriesId = programSeriesId,
+ Limit = 1,
+ ImageTypes = new ImageType[] { ImageType.Primary },
+ DtoOptions = new DtoOptions(false),
+ Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
- }).FirstOrDefault();
+ }).FirstOrDefault();
- if (program != null)
+ if (program != null)
+ {
+ var image = program.GetImageInfo(ImageType.Primary, 0);
+ if (image != null)
+ {
+ try
+ {
+ dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
+ dto.ParentPrimaryImageItemId = program.Id.ToString("N");
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+
+ if (dto.ParentBackdropImageTags == null || dto.ParentBackdropImageTags.Length == 0)
{
- var image = program.GetImageInfo(ImageType.Primary, 0);
+ image = program.GetImageInfo(ImageType.Backdrop, 0);
if (image != null)
{
try
{
- dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
- dto.ParentPrimaryImageItemId = program.Id.ToString("N");
- }
- catch (Exception ex)
+ dto.ParentBackdropImageTags = new string[]
{
+ _imageProcessor.GetImageCacheTag(program, image)
+ };
+ dto.ParentBackdropItemId = program.Id.ToString("N");
}
- }
-
- if (dto.ParentBackdropImageTags == null || dto.ParentBackdropImageTags.Length == 0)
- {
- image = program.GetImageInfo(ImageType.Backdrop, 0);
- if (image != null)
+ catch (Exception ex)
{
- try
- {
- dto.ParentBackdropImageTags = new string[]
- {
- _imageProcessor.GetImageCacheTag(program, image)
- };
- dto.ParentBackdropItemId = program.Id.ToString("N");
- }
- catch (Exception ex)
- {
- }
}
}
}
@@ -280,59 +279,62 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- if (!string.IsNullOrWhiteSpace(programSeriesId))
+ var program = _libraryManager.GetItemList(new InternalItemsQuery
{
- var program = _libraryManager.GetItemList(new InternalItemsQuery
- {
- IncludeItemTypes = new string[] { typeof(Series).Name },
- Name = seriesName,
- Limit = 1,
- ImageTypes = new ImageType[] { ImageType.Primary },
- DtoOptions = new DtoOptions(false)
+ IncludeItemTypes = new string[] { typeof(Series).Name },
+ Name = seriesName,
+ Limit = 1,
+ ImageTypes = new ImageType[] { ImageType.Primary },
+ DtoOptions = new DtoOptions(false)
+
+ }).FirstOrDefault();
- }).FirstOrDefault() ?? _libraryManager.GetItemList(new InternalItemsQuery
+ if (program == null)
+ {
+ program = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
ExternalSeriesId = programSeriesId,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
- DtoOptions = new DtoOptions(false)
+ DtoOptions = new DtoOptions(false),
+ Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
}).FirstOrDefault();
+ }
- if (program != null)
+ if (program != null)
+ {
+ var image = program.GetImageInfo(ImageType.Primary, 0);
+ if (image != null)
{
- var image = program.GetImageInfo(ImageType.Primary, 0);
+ try
+ {
+ dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
+ dto.ParentPrimaryImageItemId = program.Id.ToString("N");
+ }
+ catch (Exception ex)
+ {
+ }
+ }
+
+ if (dto.ParentBackdropImageTags == null || dto.ParentBackdropImageTags.Length == 0)
+ {
+ image = program.GetImageInfo(ImageType.Backdrop, 0);
if (image != null)
{
try
{
- dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
- dto.ParentPrimaryImageItemId = program.Id.ToString("N");
+ dto.ParentBackdropImageTags = new string[]
+ {
+ _imageProcessor.GetImageCacheTag(program, image)
+ };
+ dto.ParentBackdropItemId = program.Id.ToString("N");
}
catch (Exception ex)
{
}
}
-
- if (dto.ParentBackdropImageTags == null || dto.ParentBackdropImageTags.Length == 0)
- {
- image = program.GetImageInfo(ImageType.Backdrop, 0);
- if (image != null)
- {
- try
- {
- dto.ParentBackdropImageTags = new string[]
- {
- _imageProcessor.GetImageCacheTag(program, image)
- };
- dto.ParentBackdropItemId = program.Id.ToString("N");
- }
- catch (Exception ex)
- {
- }
- }
- }
}
}
}
@@ -366,35 +368,7 @@ namespace Emby.Server.Implementations.LiveTv
return pattern;
}
- public LiveTvTunerInfoDto GetTunerInfoDto(string serviceName, LiveTvTunerInfo info, string channelName)
- {
- var dto = new LiveTvTunerInfoDto
- {
- Name = info.Name,
- Id = info.Id,
- Clients = info.Clients.ToArray(),
- ProgramName = info.ProgramName,
- SourceType = info.SourceType,
- Status = info.Status,
- ChannelName = channelName,
- Url = info.Url,
- CanReset = info.CanReset
- };
-
- if (!string.IsNullOrEmpty(info.ChannelId))
- {
- dto.ChannelId = GetInternalChannelId(serviceName, info.ChannelId).ToString("N");
- }
-
- if (!string.IsNullOrEmpty(info.RecordingId))
- {
- dto.RecordingId = GetInternalRecordingId(serviceName, info.RecordingId).ToString("N");
- }
-
- return dto;
- }
-
- internal string GetImageTag(IHasMetadata info)
+ internal string GetImageTag(BaseItem info)
{
try
{
@@ -417,46 +391,28 @@ namespace Emby.Server.Implementations.LiveTv
return _libraryManager.GetNewItemId(name.ToLower(), typeof(LiveTvChannel));
}
- public Guid GetInternalTimerId(string serviceName, string externalId)
+ private const string ServiceName = "Emby";
+ public string GetInternalTimerId(string externalId)
{
- var name = serviceName + externalId + InternalVersionNumber;
+ var name = ServiceName + externalId + InternalVersionNumber;
- return name.ToLower().GetMD5();
+ return name.ToLower().GetMD5().ToString("N");
}
- public Guid GetInternalSeriesTimerId(string serviceName, string externalId)
+ public Guid GetInternalSeriesTimerId(string externalId)
{
- var name = serviceName + externalId + InternalVersionNumber;
+ var name = ServiceName + externalId + InternalVersionNumber;
return name.ToLower().GetMD5();
}
- public Guid GetInternalProgramId(string serviceName, string externalId)
+ public Guid GetInternalProgramId(string externalId)
{
- var name = serviceName + externalId + InternalVersionNumber;
+ var name = ServiceName + externalId + InternalVersionNumber;
return _libraryManager.GetNewItemId(name.ToLower(), typeof(LiveTvProgram));
}
- public Guid GetInternalRecordingId(string serviceName, string externalId)
- {
- var name = serviceName + externalId + InternalVersionNumber + "0";
-
- return _libraryManager.GetNewItemId(name.ToLower(), typeof(ILiveTvRecording));
- }
-
- private string GetItemExternalId(BaseItem item)
- {
- var externalId = item.ExternalId;
-
- if (string.IsNullOrWhiteSpace(externalId))
- {
- externalId = item.GetProviderId("ProviderExternalId");
- }
-
- return externalId;
- }
-
public async Task<TimerInfo> GetTimerInfo(TimerInfoDto dto, bool isNew, LiveTvManager liveTv, CancellationToken cancellationToken)
{
var info = new TimerInfo
@@ -486,23 +442,23 @@ namespace Emby.Server.Implementations.LiveTv
info.Id = timer.ExternalId;
}
- if (!string.IsNullOrEmpty(dto.ChannelId) && string.IsNullOrEmpty(info.ChannelId))
+ if (!dto.ChannelId.Equals(Guid.Empty) && string.IsNullOrEmpty(info.ChannelId))
{
- var channel = liveTv.GetInternalChannel(dto.ChannelId);
+ var channel = _libraryManager.GetItemById(dto.ChannelId);
if (channel != null)
{
- info.ChannelId = GetItemExternalId(channel);
+ info.ChannelId = channel.ExternalId;
}
}
if (!string.IsNullOrEmpty(dto.ProgramId) && string.IsNullOrEmpty(info.ProgramId))
{
- var program = liveTv.GetInternalProgram(dto.ProgramId);
+ var program = _libraryManager.GetItemById(dto.ProgramId);
if (program != null)
{
- info.ProgramId = GetItemExternalId(program);
+ info.ProgramId = program.ExternalId;
}
}
@@ -552,23 +508,23 @@ namespace Emby.Server.Implementations.LiveTv
info.Id = timer.ExternalId;
}
- if (!string.IsNullOrEmpty(dto.ChannelId) && string.IsNullOrEmpty(info.ChannelId))
+ if (!dto.ChannelId.Equals(Guid.Empty) && string.IsNullOrEmpty(info.ChannelId))
{
- var channel = liveTv.GetInternalChannel(dto.ChannelId);
+ var channel = _libraryManager.GetItemById(dto.ChannelId);
if (channel != null)
{
- info.ChannelId = GetItemExternalId(channel);
+ info.ChannelId = channel.ExternalId;
}
}
if (!string.IsNullOrEmpty(dto.ProgramId) && string.IsNullOrEmpty(info.ProgramId))
{
- var program = liveTv.GetInternalProgram(dto.ProgramId);
+ var program = _libraryManager.GetItemById(dto.ProgramId);
if (program != null)
{
- info.ProgramId = GetItemExternalId(program);
+ info.ProgramId = program.ExternalId;
}
}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 211e0de4be..9cdf105d70 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -26,7 +26,6 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Common.Events;
-
using MediaBrowser.Common.Security;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
@@ -36,6 +35,10 @@ using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
using Emby.Server.Implementations.LiveTv.Listings;
+using MediaBrowser.Controller.Channels;
+using Emby.Server.Implementations.Library;
+using MediaBrowser.Controller;
+using MediaBrowser.Common.Net;
namespace Emby.Server.Implementations.LiveTv
{
@@ -54,6 +57,7 @@ namespace Emby.Server.Implementations.LiveTv
private readonly IJsonSerializer _jsonSerializer;
private readonly IProviderManager _providerManager;
private readonly ISecurityManager _security;
+ private readonly Func<IChannelManager> _channelManager;
private readonly IDtoService _dtoService;
private readonly ILocalizationManager _localization;
@@ -62,10 +66,8 @@ namespace Emby.Server.Implementations.LiveTv
private ILiveTvService[] _services = new ILiveTvService[] { };
- private readonly SemaphoreSlim _refreshRecordingsLock = new SemaphoreSlim(1, 1);
-
- private readonly List<ITunerHost> _tunerHosts = new List<ITunerHost>();
- private readonly List<IListingsProvider> _listingProviders = new List<IListingsProvider>();
+ private ITunerHost[] _tunerHosts = Array.Empty<ITunerHost>();
+ private IListingsProvider[] _listingProviders = Array.Empty<IListingsProvider>();
private readonly IFileSystem _fileSystem;
public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
@@ -78,13 +80,12 @@ namespace Emby.Server.Implementations.LiveTv
return EmbyTV.EmbyTV.Current.GetActiveRecordingPath(id);
}
- public Task<ILiveStream> GetEmbyTvLiveStream(string id)
- {
- return EmbyTV.EmbyTV.Current.GetLiveStream(id);
- }
+ private IServerApplicationHost _appHost;
+ private IHttpClient _httpClient;
- public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager, IFileSystem fileSystem, ISecurityManager security)
+ public LiveTvManager(IServerApplicationHost appHost, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager, IFileSystem fileSystem, ISecurityManager security, Func<IChannelManager> channelManager)
{
+ _appHost = appHost;
_config = config;
_logger = logger;
_itemRepo = itemRepo;
@@ -98,6 +99,8 @@ namespace Emby.Server.Implementations.LiveTv
_security = security;
_dtoService = dtoService;
_userDataManager = userDataManager;
+ _channelManager = channelManager;
+ _httpClient = httpClient;
_tvDtoService = new LiveTvDtoService(dtoService, imageProcessor, logger, appHost, _libraryManager);
}
@@ -125,27 +128,58 @@ namespace Emby.Server.Implementations.LiveTv
public void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders)
{
_services = services.ToArray();
- _tunerHosts.AddRange(tunerHosts.Where(i => i.IsSupported));
- _listingProviders.AddRange(listingProviders);
+ _tunerHosts = tunerHosts.Where(i => i.IsSupported).ToArray();
+
+ _listingProviders = listingProviders.ToArray();
foreach (var service in _services)
{
service.DataSourceChanged += service_DataSourceChanged;
- service.RecordingStatusChanged += Service_RecordingStatusChanged;
+
+ var embyTv = service as EmbyTV.EmbyTV;
+
+ if (embyTv != null)
+ {
+ embyTv.TimerCreated += EmbyTv_TimerCreated;
+ embyTv.TimerCancelled += EmbyTv_TimerCancelled;
+ }
}
}
- private void Service_RecordingStatusChanged(object sender, RecordingStatusChangedEventArgs e)
+ private void EmbyTv_TimerCancelled(object sender, GenericEventArgs<string> e)
{
- _lastRecordingRefreshTime = DateTime.MinValue;
+ var timerId = e.Argument;
+
+ EventHelper.FireEventIfNotNull(TimerCancelled, this, new GenericEventArgs<TimerEventInfo>
+ {
+ Argument = new TimerEventInfo
+ {
+ Id = timerId
+ }
+ }, _logger);
+ }
+
+ private void EmbyTv_TimerCreated(object sender, GenericEventArgs<TimerInfo> e)
+ {
+ var timer = e.Argument;
+ var service = sender as ILiveTvService;
+
+ EventHelper.FireEventIfNotNull(TimerCreated, this, new GenericEventArgs<TimerEventInfo>
+ {
+ Argument = new TimerEventInfo
+ {
+ ProgramId = _tvDtoService.GetInternalProgramId(timer.ProgramId),
+ Id = timer.Id
+ }
+ }, _logger);
}
- public List<ITunerHost> TunerHosts
+ public ITunerHost[] TunerHosts
{
get { return _tunerHosts; }
}
- public List<IListingsProvider> ListingProviders
+ public IListingsProvider[] ListingProviders
{
get { return _listingProviders; }
}
@@ -175,7 +209,7 @@ namespace Emby.Server.Implementations.LiveTv
public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
{
- var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
+ var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId);
var topFolder = GetInternalLiveTvFolder(cancellationToken);
@@ -187,7 +221,7 @@ namespace Emby.Server.Implementations.LiveTv
IsSports = query.IsSports,
IsSeries = query.IsSeries,
IncludeItemTypes = new[] { typeof(LiveTvChannel).Name },
- TopParentIds = new[] { topFolder.Id.ToString("N") },
+ TopParentIds = new[] { topFolder.Id },
IsFavorite = query.IsFavorite,
IsLiked = query.IsLiked,
StartIndex = query.StartIndex,
@@ -197,16 +231,16 @@ namespace Emby.Server.Implementations.LiveTv
var orderBy = internalQuery.OrderBy.ToList();
- orderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending)));
+ orderBy.AddRange(query.SortBy.Select(i => new ValueTuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending)));
if (query.EnableFavoriteSorting)
{
- orderBy.Insert(0, new Tuple<string, SortOrder>(ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
+ orderBy.Insert(0, new ValueTuple<string, SortOrder>(ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
}
if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)))
{
- orderBy.Add(new Tuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
+ orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
}
internalQuery.OrderBy = orderBy.ToArray();
@@ -214,75 +248,54 @@ namespace Emby.Server.Implementations.LiveTv
return _libraryManager.GetItemsResult(internalQuery);
}
- public LiveTvChannel GetInternalChannel(string id)
- {
- return GetInternalChannel(new Guid(id));
- }
-
- private LiveTvChannel GetInternalChannel(Guid id)
+ public async Task<Tuple<MediaSourceInfo, ILiveStream>> GetChannelStream(string id, string mediaSourceId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
- return _libraryManager.GetItemById(id) as LiveTvChannel;
- }
+ if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
+ {
+ mediaSourceId = null;
+ }
- internal LiveTvProgram GetInternalProgram(string id)
- {
- return _libraryManager.GetItemById(id) as LiveTvProgram;
- }
+ MediaSourceInfo info;
+ bool isVideo;
+ ILiveTvService service;
+ ILiveStream liveStream;
- internal LiveTvProgram GetInternalProgram(Guid id)
- {
- return _libraryManager.GetItemById(id) as LiveTvProgram;
- }
+ var channel = (LiveTvChannel)_libraryManager.GetItemById(id);
+ isVideo = channel.ChannelType == ChannelType.TV;
+ service = GetService(channel);
+ _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
- public async Task<BaseItem> GetInternalRecording(string id, CancellationToken cancellationToken)
- {
- if (string.IsNullOrWhiteSpace(id))
+ var supportsManagedStream = service as ISupportsDirectStreamProvider;
+ if (supportsManagedStream != null)
{
- throw new ArgumentNullException("id");
+ liveStream = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(channel.ExternalId, mediaSourceId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
+ info = liveStream.MediaSource;
}
-
- var result = await GetInternalRecordings(new RecordingQuery
+ else
{
- Id = id
-
- }, new DtoOptions(), cancellationToken).ConfigureAwait(false);
+ info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
+ var openedId = info.Id;
+ Func<Task> closeFn = () => service.CloseLiveStream(openedId, CancellationToken.None);
- return result.Items.FirstOrDefault();
- }
-
- public async Task<MediaSourceInfo> GetRecordingStream(string id, CancellationToken cancellationToken)
- {
- var info = await GetLiveStream(id, null, false, cancellationToken).ConfigureAwait(false);
-
- return info.Item1;
- }
-
- public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetChannelStream(string id, string mediaSourceId, CancellationToken cancellationToken)
- {
- return GetLiveStream(id, mediaSourceId, true, cancellationToken);
- }
-
- private string GetItemExternalId(BaseItem item)
- {
- var externalId = item.ExternalId;
+ liveStream = new ExclusiveLiveStream(info, closeFn);
- if (string.IsNullOrWhiteSpace(externalId))
- {
- externalId = item.GetProviderId("ProviderExternalId");
+ var startTime = DateTime.UtcNow;
+ await liveStream.Open(cancellationToken).ConfigureAwait(false);
+ var endTime = DateTime.UtcNow;
+ _logger.Info("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
}
+ info.RequiresClosing = true;
- return externalId;
- }
+ var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
- public async Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
- {
- var baseItem = (BaseItem)item;
- var service = GetService(baseItem);
+ info.LiveStreamId = idPrefix + info.Id;
+
+ Normalize(info, service, isVideo);
- return await service.GetRecordingStreamMediaSources(GetItemExternalId(baseItem), cancellationToken).ConfigureAwait(false);
+ return new Tuple<MediaSourceInfo, ILiveStream>(info, liveStream);
}
- public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
+ public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(BaseItem item, CancellationToken cancellationToken)
{
var baseItem = (LiveTvChannel)item;
var service = GetService(baseItem);
@@ -304,14 +317,17 @@ namespace Emby.Server.Implementations.LiveTv
return list;
}
- private ILiveTvService GetService(ILiveTvRecording item)
+ private ILiveTvService GetService(LiveTvChannel item)
{
- return GetService(item.ServiceName);
+ var name = item.ServiceName;
+ return GetService(name);
}
- private ILiveTvService GetService(BaseItem item)
+ private ILiveTvService GetService(LiveTvProgram item)
{
- return GetService(item.ServiceName);
+ var channel = _libraryManager.GetItemById(item.ChannelId) as LiveTvChannel;
+
+ return GetService(channel);
}
private ILiveTvService GetService(string name)
@@ -319,70 +335,11 @@ namespace Emby.Server.Implementations.LiveTv
return _services.FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase));
}
- private async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStream(string id, string mediaSourceId, bool isChannel, CancellationToken cancellationToken)
- {
- if (string.Equals(id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
- {
- mediaSourceId = null;
- }
-
- MediaSourceInfo info;
- bool isVideo;
- ILiveTvService service;
- IDirectStreamProvider directStreamProvider = null;
-
- if (isChannel)
- {
- var channel = GetInternalChannel(id);
- isVideo = channel.ChannelType == ChannelType.TV;
- service = GetService(channel);
- _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, GetItemExternalId(channel));
-
- var supportsManagedStream = service as ISupportsDirectStreamProvider;
- if (supportsManagedStream != null)
- {
- var streamInfo = await supportsManagedStream.GetChannelStreamWithDirectStreamProvider(GetItemExternalId(channel), mediaSourceId, cancellationToken).ConfigureAwait(false);
- info = streamInfo.Item1;
- directStreamProvider = streamInfo.Item2;
- }
- else
- {
- info = await service.GetChannelStream(GetItemExternalId(channel), mediaSourceId, cancellationToken).ConfigureAwait(false);
- }
- info.RequiresClosing = true;
-
- if (info.RequiresClosing)
- {
- var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
-
- info.LiveStreamId = idPrefix + info.Id;
- }
- }
- else
- {
- var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
- isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
- service = GetService(recording);
-
- _logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, GetItemExternalId(recording));
- info = await service.GetRecordingStream(GetItemExternalId(recording), null, cancellationToken).ConfigureAwait(false);
- info.RequiresClosing = true;
-
- if (info.RequiresClosing)
- {
- var idPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
-
- info.LiveStreamId = idPrefix + info.Id;
- }
- }
-
- Normalize(info, service, isVideo);
-
- return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info, directStreamProvider);
- }
-
private void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo)
{
+ // Not all of the plugins are setting this
+ mediaSource.IsInfiniteStream = true;
+
if (mediaSource.MediaStreams.Count == 0)
{
if (isVideo)
@@ -475,7 +432,7 @@ namespace Emby.Server.Implementations.LiveTv
{
// We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
//mediaSource.SupportsDirectPlay = false;
- mediaSource.SupportsDirectStream = false;
+ //mediaSource.SupportsDirectStream = false;
mediaSource.SupportsTranscoding = true;
foreach (var stream in mediaSource.MediaStreams)
{
@@ -492,8 +449,10 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, Guid parentFolderId, CancellationToken cancellationToken)
+ private const string ExternalServiceTag = "ExternalServiceId";
+ private LiveTvChannel GetChannel(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
{
+ var parentFolderId = parentFolder.Id;
var isNew = false;
var forceUpdate = false;
@@ -507,17 +466,20 @@ namespace Emby.Server.Implementations.LiveTv
{
Name = channelInfo.Name,
Id = id,
- DateCreated = DateTime.UtcNow,
+ DateCreated = DateTime.UtcNow
};
isNew = true;
}
- if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal))
+ if (channelInfo.Tags != null)
{
- isNew = true;
+ if (!channelInfo.Tags.SequenceEqual(item.Tags, StringComparer.OrdinalIgnoreCase))
+ {
+ isNew = true;
+ }
+ item.Tags = channelInfo.Tags;
}
- item.ExternalId = channelInfo.Id;
if (!item.ParentId.Equals(parentFolderId))
{
@@ -528,6 +490,18 @@ namespace Emby.Server.Implementations.LiveTv
item.ChannelType = channelInfo.ChannelType;
item.ServiceName = serviceName;
+ if (!string.Equals(item.GetProviderId(ExternalServiceTag), serviceName, StringComparison.OrdinalIgnoreCase))
+ {
+ forceUpdate = true;
+ }
+ item.SetProviderId(ExternalServiceTag, serviceName);
+
+ if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal))
+ {
+ forceUpdate = true;
+ }
+ item.ExternalId = channelInfo.Id;
+
if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal))
{
forceUpdate = true;
@@ -556,25 +530,21 @@ namespace Emby.Server.Implementations.LiveTv
if (isNew)
{
- _libraryManager.CreateItem(item, cancellationToken);
+ _libraryManager.CreateItem(item, parentFolder);
}
else if (forceUpdate)
{
- _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken);
+ _libraryManager.UpdateItem(item, parentFolder, ItemUpdateType.MetadataImport, cancellationToken);
}
- await item.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
- {
- ForceSave = isNew || forceUpdate
-
- }, cancellationToken);
-
return item;
}
+ private const string EtagKey = "ProgramEtag";
+
private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
{
- var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id);
+ var id = _tvDtoService.GetInternalProgramId(info.Id);
LiveTvProgram item = null;
allExistingPrograms.TryGetValue(id, out item);
@@ -590,9 +560,13 @@ namespace Emby.Server.Implementations.LiveTv
Name = info.Name,
Id = id,
DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow,
- ExternalEtag = info.Etag
+ DateModified = DateTime.UtcNow
};
+
+ if (!string.IsNullOrEmpty(info.Etag))
+ {
+ item.SetProviderId(EtagKey, info.Etag);
+ }
}
if (!string.Equals(info.ShowId, item.ShowId, StringComparison.OrdinalIgnoreCase))
@@ -610,10 +584,9 @@ namespace Emby.Server.Implementations.LiveTv
item.ParentId = channel.Id;
//item.ChannelType = channelType;
- item.ServiceName = serviceName;
item.Audio = info.Audio;
- item.ChannelId = channel.Id.ToString("N");
+ item.ChannelId = channel.Id;
item.CommunityRating = item.CommunityRating ?? info.CommunityRating;
if ((item.CommunityRating ?? 0).Equals(0))
{
@@ -629,20 +602,76 @@ namespace Emby.Server.Implementations.LiveTv
}
item.ExternalSeriesId = seriesId;
- item.Genres = info.Genres;
- item.IsHD = info.IsHD;
- item.IsKids = info.IsKids;
- item.IsLive = info.IsLive;
+ var isSeries = info.IsSeries || !string.IsNullOrEmpty(info.EpisodeTitle);
+
+ if (isSeries || !string.IsNullOrEmpty(info.EpisodeTitle))
+ {
+ item.SeriesName = info.Name;
+ }
+
+ var tags = new List<string>();
+ if (info.IsLive)
+ {
+ tags.Add("Live");
+ }
+ if (info.IsPremiere)
+ {
+ tags.Add("Premiere");
+ }
+ if (info.IsNews)
+ {
+ tags.Add("News");
+ }
+ if (info.IsSports)
+ {
+ tags.Add("Sports");
+ }
+ if (info.IsKids)
+ {
+ tags.Add("Kids");
+ }
+ if (info.IsRepeat)
+ {
+ tags.Add("Repeat");
+ }
+ if (info.IsMovie)
+ {
+ tags.Add("Movie");
+ }
+ if (isSeries)
+ {
+ tags.Add("Series");
+ }
+
+ item.Tags = tags.ToArray();
+
+ item.Genres = info.Genres.ToArray();
+
+ if (info.IsHD ?? false)
+ {
+ item.Width = 1280;
+ item.Height = 720;
+ }
+
item.IsMovie = info.IsMovie;
- item.IsNews = info.IsNews;
- item.IsPremiere = info.IsPremiere;
item.IsRepeat = info.IsRepeat;
- item.IsSeries = info.IsSeries;
- item.IsSports = info.IsSports;
+
+ if (item.IsSeries != isSeries)
+ {
+ forceUpdate = true;
+ }
+ item.IsSeries = isSeries;
+
item.Name = info.Name;
item.OfficialRating = item.OfficialRating ?? info.OfficialRating;
item.Overview = item.Overview ?? info.Overview;
item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
+ item.ProviderIds = info.ProviderIds;
+
+ foreach (var providerId in info.SeriesProviderIds)
+ {
+ info.ProviderIds["Series" + providerId.Key] = providerId.Value;
+ }
if (item.StartDate != info.StartDate)
{
@@ -656,11 +685,9 @@ namespace Emby.Server.Implementations.LiveTv
}
item.EndDate = info.EndDate;
- item.HomePageUrl = info.HomePageUrl;
-
item.ProductionYear = info.ProductionYear;
- if (!info.IsSeries || info.IsRepeat)
+ if (!isSeries || info.IsRepeat)
{
item.PremiereDate = info.OriginalAirDate;
}
@@ -737,12 +764,11 @@ namespace Emby.Server.Implementations.LiveTv
}
else
{
- // Increment this whenver some internal change deems it necessary
- var etag = info.Etag + "6";
+ var etag = info.Etag;
- if (!string.Equals(etag, item.ExternalEtag, StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(etag, item.GetProviderId(EtagKey), StringComparison.OrdinalIgnoreCase))
{
- item.ExternalEtag = etag;
+ item.SetProviderId(EtagKey, etag);
isUpdated = true;
}
}
@@ -755,180 +781,47 @@ namespace Emby.Server.Implementations.LiveTv
return new Tuple<LiveTvProgram, bool, bool>(item, isNew, isUpdated);
}
- private Guid CreateRecordingRecord(RecordingInfo info, string serviceName, Guid parentFolderId, CancellationToken cancellationToken)
- {
- var isNew = false;
-
- var id = _tvDtoService.GetInternalRecordingId(serviceName, info.Id);
-
- var item = _itemRepo.RetrieveItem(id);
-
- if (item == null)
- {
- if (info.ChannelType == ChannelType.TV)
- {
- item = new LiveTvVideoRecording
- {
- Name = info.Name,
- Id = id,
- DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow,
- VideoType = VideoType.VideoFile
- };
- }
- else
- {
- item = new LiveTvAudioRecording
- {
- Name = info.Name,
- Id = id,
- DateCreated = DateTime.UtcNow,
- DateModified = DateTime.UtcNow
- };
- }
-
- isNew = true;
- }
-
- item.ChannelId = _tvDtoService.GetInternalChannelId(serviceName, info.ChannelId).ToString("N");
- item.CommunityRating = info.CommunityRating;
- item.OfficialRating = info.OfficialRating;
- item.Overview = info.Overview;
- item.EndDate = info.EndDate;
- item.Genres = info.Genres;
- item.PremiereDate = info.OriginalAirDate;
-
- var recording = (ILiveTvRecording)item;
-
- recording.ExternalId = info.Id;
-
- var dataChanged = false;
-
- recording.Audio = info.Audio;
- recording.EndDate = info.EndDate;
- recording.EpisodeTitle = info.EpisodeTitle;
- recording.IsHD = info.IsHD;
- recording.IsKids = info.IsKids;
- recording.IsLive = info.IsLive;
- recording.IsMovie = info.IsMovie;
- recording.IsNews = info.IsNews;
- recording.IsPremiere = info.IsPremiere;
- recording.IsRepeat = info.IsRepeat;
- recording.IsSports = info.IsSports;
- recording.SeriesTimerId = info.SeriesTimerId;
- recording.TimerId = info.TimerId;
- recording.StartDate = info.StartDate;
-
- if (!dataChanged)
- {
- dataChanged = recording.IsSeries != info.IsSeries;
- }
- recording.IsSeries = info.IsSeries;
-
- if (!item.ParentId.Equals(parentFolderId))
- {
- dataChanged = true;
- }
- item.ParentId = parentFolderId;
-
- if (!item.HasImage(ImageType.Primary))
- {
- if (!string.IsNullOrWhiteSpace(info.ImagePath))
- {
- item.SetImage(new ItemImageInfo
- {
- Path = info.ImagePath,
- Type = ImageType.Primary
- }, 0);
- }
- else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
- {
- item.SetImage(new ItemImageInfo
- {
- Path = info.ImageUrl,
- Type = ImageType.Primary
- }, 0);
- }
- }
-
- var statusChanged = info.Status != recording.Status;
-
- recording.Status = info.Status;
-
- recording.ServiceName = serviceName;
-
- if (!string.IsNullOrEmpty(info.Path))
- {
- if (!dataChanged)
- {
- dataChanged = !string.Equals(item.Path, info.Path);
- }
- var fileInfo = _fileSystem.GetFileInfo(info.Path);
-
- recording.DateCreated = _fileSystem.GetCreationTimeUtc(fileInfo);
- recording.DateModified = _fileSystem.GetLastWriteTimeUtc(fileInfo);
- item.Path = info.Path;
- }
- else if (!string.IsNullOrEmpty(info.Url))
- {
- if (!dataChanged)
- {
- dataChanged = !string.Equals(item.Path, info.Url);
- }
- item.Path = info.Url;
- }
-
- var metadataRefreshMode = MetadataRefreshMode.Default;
-
- if (isNew)
- {
- _libraryManager.CreateItem(item, cancellationToken);
- }
- else if (dataChanged || info.DateLastUpdated > recording.DateLastSaved || statusChanged)
- {
- metadataRefreshMode = MetadataRefreshMode.FullRefresh;
- _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken);
- }
-
- if (info.Status != RecordingStatus.InProgress)
- {
- _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem)
- {
- MetadataRefreshMode = metadataRefreshMode
-
- }, RefreshPriority.Normal);
- }
-
- return item.Id;
- }
-
public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
{
- var program = GetInternalProgram(id);
+ var program = _libraryManager.GetItemById(id);
var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
- var list = new List<Tuple<BaseItemDto, string, string, string>>();
+ var list = new List<Tuple<BaseItemDto, string, string>>();
var externalSeriesId = program.ExternalSeriesId;
- list.Add(new Tuple<BaseItemDto, string, string, string>(dto, program.ServiceName, GetItemExternalId(program), externalSeriesId));
+ list.Add(new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, externalSeriesId));
await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
return dto;
}
- public async Task<QueryResult<BaseItemDto>> GetPrograms(ProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
+ public async Task<QueryResult<BaseItemDto>> GetPrograms(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken)
{
- var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
+ var user = query.User;
var topFolder = GetInternalLiveTvFolder(cancellationToken);
if (query.OrderBy.Length == 0)
{
- // Unless something else was specified, order by start date to take advantage of a specialized index
- query.OrderBy = new Tuple<string, SortOrder>[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) };
+ if (query.IsAiring ?? false)
+ {
+ // Unless something else was specified, order by start date to take advantage of a specialized index
+ query.OrderBy = new ValueTuple<string, SortOrder>[]
+ {
+ new ValueTuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending)
+ };
+ }
+ else
+ {
+ // Unless something else was specified, order by start date to take advantage of a specialized index
+ query.OrderBy = new ValueTuple<string, SortOrder>[]
+ {
+ new ValueTuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending)
+ };
+ }
}
RemoveFields(options);
@@ -952,15 +845,17 @@ namespace Emby.Server.Implementations.LiveTv
Limit = query.Limit,
OrderBy = query.OrderBy,
EnableTotalRecordCount = query.EnableTotalRecordCount,
- TopParentIds = new[] { topFolder.Id.ToString("N") },
+ TopParentIds = new[] { topFolder.Id },
Name = query.Name,
- DtoOptions = options
+ DtoOptions = options,
+ HasAired = query.HasAired,
+ IsAiring = query.IsAiring
};
if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
{
var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery { }, cancellationToken).ConfigureAwait(false);
- var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.ServiceName, i.Id).ToString("N"), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
+ var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.Id).ToString("N"), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
if (seriesTimer != null)
{
internalQuery.ExternalSeriesId = seriesTimer.SeriesId;
@@ -978,18 +873,6 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- if (query.HasAired.HasValue)
- {
- if (query.HasAired.Value)
- {
- internalQuery.MaxEndDate = DateTime.UtcNow;
- }
- else
- {
- internalQuery.MinEndDate = DateTime.UtcNow;
- }
- }
-
var queryResult = _libraryManager.QueryItems(internalQuery);
var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user);
@@ -1003,9 +886,9 @@ namespace Emby.Server.Implementations.LiveTv
return result;
}
- public QueryResult<BaseItem> GetRecommendedProgramsInternal(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
+ public QueryResult<BaseItem> GetRecommendedProgramsInternal(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken)
{
- var user = _userManager.GetUserById(query.UserId);
+ var user = query.User;
var topFolder = GetInternalLiveTvFolder(cancellationToken);
@@ -1013,14 +896,15 @@ namespace Emby.Server.Implementations.LiveTv
{
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
IsAiring = query.IsAiring,
+ HasAired = query.HasAired,
IsNews = query.IsNews,
IsMovie = query.IsMovie,
IsSeries = query.IsSeries,
IsSports = query.IsSports,
IsKids = query.IsKids,
EnableTotalRecordCount = query.EnableTotalRecordCount,
- OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) },
- TopParentIds = new[] { topFolder.Id.ToString("N") },
+ OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) },
+ TopParentIds = new[] { topFolder.Id },
DtoOptions = options,
GenreIds = query.GenreIds
};
@@ -1030,18 +914,6 @@ namespace Emby.Server.Implementations.LiveTv
internalQuery.Limit = Math.Max(query.Limit.Value * 4, 200);
}
- if (query.HasAired.HasValue)
- {
- if (query.HasAired.Value)
- {
- internalQuery.MaxEndDate = DateTime.UtcNow;
- }
- else
- {
- internalQuery.MinEndDate = DateTime.UtcNow;
- }
- }
-
var programList = _libraryManager.QueryItems(internalQuery).Items;
var totalCount = programList.Length;
@@ -1050,7 +922,7 @@ namespace Emby.Server.Implementations.LiveTv
if (query.IsAiring ?? false)
{
orderedPrograms = orderedPrograms
- .ThenByDescending(i => GetRecommendationScore(i, user.Id, true));
+ .ThenByDescending(i => GetRecommendationScore(i, user, true));
}
IEnumerable<BaseItem> programs = orderedPrograms;
@@ -1069,13 +941,18 @@ namespace Emby.Server.Implementations.LiveTv
return result;
}
- public QueryResult<BaseItemDto> GetRecommendedPrograms(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
+ public QueryResult<BaseItemDto> GetRecommendedPrograms(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken)
{
+ if (!(query.IsAiring ?? false))
+ {
+ return GetPrograms(query, options, cancellationToken).Result;
+ }
+
RemoveFields(options);
var internalResult = GetRecommendedProgramsInternal(query, options, cancellationToken);
- var user = _userManager.GetUserById(query.UserId);
+ var user = query.User;
var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user);
@@ -1088,7 +965,7 @@ namespace Emby.Server.Implementations.LiveTv
return result;
}
- private int GetRecommendationScore(LiveTvProgram program, Guid userId, bool factorChannelWatchCount)
+ private int GetRecommendationScore(LiveTvProgram program, User user, bool factorChannelWatchCount)
{
var score = 0;
@@ -1102,11 +979,11 @@ namespace Emby.Server.Implementations.LiveTv
score++;
}
- var channel = GetInternalChannel(program.ChannelId);
+ var channel = _libraryManager.GetItemById(program.ChannelId);
if (channel != null)
{
- var channelUserdata = _userDataManager.GetUserData(userId, channel);
+ var channelUserdata = _userDataManager.GetUserData(user, channel);
if (channelUserdata.Likes ?? false)
{
@@ -1131,36 +1008,23 @@ namespace Emby.Server.Implementations.LiveTv
return score;
}
- private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string, string>> programs, CancellationToken cancellationToken)
+ private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string>> programs, CancellationToken cancellationToken)
{
var timers = new Dictionary<string, List<TimerInfo>>();
var seriesTimers = new Dictionary<string, List<SeriesTimerInfo>>();
+ TimerInfo[] timerList = null;
+ SeriesTimerInfo[] seriesTimerList = null;
+
foreach (var programTuple in programs)
{
var program = programTuple.Item1;
- var serviceName = programTuple.Item2;
- var externalProgramId = programTuple.Item3;
- string externalSeriesId = programTuple.Item4;
+ var externalProgramId = programTuple.Item2;
+ string externalSeriesId = programTuple.Item3;
- if (string.IsNullOrWhiteSpace(serviceName))
+ if (timerList == null)
{
- continue;
- }
-
- List<TimerInfo> timerList;
- if (!timers.TryGetValue(serviceName, out timerList))
- {
- try
- {
- var tempTimers = await GetService(serviceName).GetTimersAsync(cancellationToken).ConfigureAwait(false);
- timers[serviceName] = timerList = tempTimers.ToList();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting timer infos", ex);
- timers[serviceName] = timerList = new List<TimerInfo>();
- }
+ timerList = (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
}
var timer = timerList.FirstOrDefault(i => string.Equals(i.ProgramId, externalProgramId, StringComparison.OrdinalIgnoreCase));
@@ -1170,15 +1034,14 @@ namespace Emby.Server.Implementations.LiveTv
{
if (timer.Status != RecordingStatus.Cancelled && timer.Status != RecordingStatus.Error)
{
- program.TimerId = _tvDtoService.GetInternalTimerId(serviceName, timer.Id)
- .ToString("N");
+ program.TimerId = _tvDtoService.GetInternalTimerId(timer.Id);
program.Status = timer.Status.ToString();
}
if (!string.IsNullOrEmpty(timer.SeriesTimerId))
{
- program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, timer.SeriesTimerId)
+ program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.SeriesTimerId)
.ToString("N");
foundSeriesTimer = true;
@@ -1190,26 +1053,16 @@ namespace Emby.Server.Implementations.LiveTv
continue;
}
- List<SeriesTimerInfo> seriesTimerList;
- if (!seriesTimers.TryGetValue(serviceName, out seriesTimerList))
+ if (seriesTimerList == null)
{
- try
- {
- var tempTimers = await GetService(serviceName).GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
- seriesTimers[serviceName] = seriesTimerList = tempTimers.ToList();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting series timer infos", ex);
- seriesTimers[serviceName] = seriesTimerList = new List<SeriesTimerInfo>();
- }
+ seriesTimerList = (await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
}
var seriesTimer = seriesTimerList.FirstOrDefault(i => string.Equals(i.SeriesId, externalSeriesId, StringComparison.OrdinalIgnoreCase));
if (seriesTimer != null)
{
- program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(serviceName, seriesTimer.Id)
+ program.SeriesTimerId = _tvDtoService.GetInternalSeriesTimerId(seriesTimer.Id)
.ToString("N");
}
}
@@ -1222,7 +1075,7 @@ namespace Emby.Server.Implementations.LiveTv
private async Task RefreshChannelsInternal(IProgress<double> progress, CancellationToken cancellationToken)
{
- EmbyTV.EmbyTV.Current.CreateRecordingFolders();
+ await EmbyTV.EmbyTV.Current.CreateRecordingFolders().ConfigureAwait(false);
await EmbyTV.EmbyTV.Current.ScanForTunerDeviceChanges(cancellationToken).ConfigureAwait(false);
@@ -1271,8 +1124,8 @@ namespace Emby.Server.Implementations.LiveTv
if (cleanDatabase)
{
- await CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken).ConfigureAwait(false);
- await CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken).ConfigureAwait(false);
+ CleanDatabaseInternal(newChannelIdList.ToArray(), new[] { typeof(LiveTvChannel).Name }, progress, cancellationToken);
+ CleanDatabaseInternal(newProgramIdList.ToArray(), new[] { typeof(LiveTvProgram).Name }, progress, cancellationToken);
}
var coreService = _services.OfType<EmbyTV.EmbyTV>().FirstOrDefault();
@@ -1286,12 +1139,9 @@ namespace Emby.Server.Implementations.LiveTv
// Load these now which will prefetch metadata
var dtoOptions = new DtoOptions();
var fields = dtoOptions.Fields.ToList();
- fields.Remove(ItemFields.SyncInfo);
fields.Remove(ItemFields.BasicSyncInfo);
dtoOptions.Fields = fields.ToArray(fields.Count);
- await GetRecordings(new RecordingQuery(), dtoOptions, cancellationToken).ConfigureAwait(false);
-
progress.Report(100);
}
@@ -1315,7 +1165,7 @@ namespace Emby.Server.Implementations.LiveTv
try
{
- var item = await GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolderId, cancellationToken).ConfigureAwait(false);
+ var item = GetChannel(channelInfo.Item2, channelInfo.Item1, parentFolder, cancellationToken);
list.Add(item);
}
@@ -1363,19 +1213,19 @@ namespace Emby.Server.Implementations.LiveTv
var isKids = false;
var iSSeries = false;
- var channelPrograms = (await service.GetProgramsAsync(GetItemExternalId(currentChannel), start, end, cancellationToken).ConfigureAwait(false)).ToList();
+ var channelPrograms = (await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false)).ToList();
var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
- ChannelIds = new string[] { currentChannel.Id.ToString("N") },
+ ChannelIds = new Guid[] { currentChannel.Id },
DtoOptions = new DtoOptions(true)
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
var newPrograms = new List<LiveTvProgram>();
- var updatedPrograms = new List<LiveTvProgram>();
+ var updatedPrograms = new List<BaseItem>();
foreach (var program in channelPrograms)
{
@@ -1426,19 +1276,27 @@ namespace Emby.Server.Implementations.LiveTv
_libraryManager.CreateItems(newPrograms, null, cancellationToken);
}
- // TODO: Do this in bulk
- foreach (var program in updatedPrograms)
+ if (updatedPrograms.Count > 0)
{
- _libraryManager.UpdateItem(program, ItemUpdateType.MetadataImport, cancellationToken);
+ _libraryManager.UpdateItems(updatedPrograms, currentChannel, ItemUpdateType.MetadataImport, cancellationToken);
}
currentChannel.IsMovie = isMovie;
currentChannel.IsNews = isNews;
currentChannel.IsSports = isSports;
- currentChannel.IsKids = isKids;
currentChannel.IsSeries = iSSeries;
- currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
+ if (isKids)
+ {
+ currentChannel.AddTag("Kids");
+ }
+
+ //currentChannel.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
+ await currentChannel.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
+ {
+ ForceSave = true
+
+ }, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
@@ -1455,12 +1313,12 @@ namespace Emby.Server.Implementations.LiveTv
progress.Report(85 * percent + 15);
}
- progress.Report(100);
+ progress.Report(100);
return new Tuple<List<Guid>, List<Guid>>(channels, programs);
}
- private async Task CleanDatabaseInternal(Guid[] currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
+ private void CleanDatabaseInternal(Guid[] currentIdList, string[] validTypes, IProgress<double> progress, CancellationToken cancellationToken)
{
var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
{
@@ -1475,7 +1333,7 @@ namespace Emby.Server.Implementations.LiveTv
{
cancellationToken.ThrowIfCancellationRequested();
- if (itemId == Guid.Empty)
+ if (itemId.Equals(Guid.Empty))
{
// Somehow some invalid data got into the db. It probably predates the boundary checking
continue;
@@ -1487,11 +1345,12 @@ namespace Emby.Server.Implementations.LiveTv
if (item != null)
{
- await _libraryManager.DeleteItem(item, new DeleteOptions
+ _libraryManager.DeleteItem(item, new DeleteOptions
{
- DeleteFileLocation = false
+ DeleteFileLocation = false,
+ DeleteFromExternalProvider = false
- }).ConfigureAwait(false);
+ }, false);
}
}
@@ -1516,72 +1375,19 @@ namespace Emby.Server.Implementations.LiveTv
return 7;
}
- private DateTime _lastRecordingRefreshTime;
- private async Task RefreshRecordings(Guid internalLiveTvFolderId, CancellationToken cancellationToken)
- {
- const int cacheMinutes = 2;
-
- await _refreshRecordingsLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- if ((DateTime.UtcNow - _lastRecordingRefreshTime).TotalMinutes < cacheMinutes)
- {
- return;
- }
-
- var tasks = _services.Select(async i =>
- {
- try
- {
- var recs = await i.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
- return recs.Select(r => new Tuple<RecordingInfo, ILiveTvService>(r, i));
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting recordings", ex);
- return new List<Tuple<RecordingInfo, ILiveTvService>>();
- }
- });
-
- var results = await Task.WhenAll(tasks).ConfigureAwait(false);
-
- var idList = results.SelectMany(i => i.ToList()).Select(i => CreateRecordingRecord(i.Item1, i.Item2.Name, internalLiveTvFolderId, cancellationToken))
- .ToArray();
-
- await CleanDatabaseInternal(idList, new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
-
- _lastRecordingRefreshTime = DateTime.UtcNow;
- }
- finally
- {
- _refreshRecordingsLock.Release();
- }
- }
-
- private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, DtoOptions dtoOptions, Guid internalLiveTvFolderId, User user)
+ private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, DtoOptions dtoOptions, User user)
{
if (user == null)
{
return new QueryResult<BaseItem>();
}
- var folderIds = EmbyTV.EmbyTV.Current.GetRecordingFolders()
- .SelectMany(i => i.Locations)
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .Select(i => _libraryManager.FindByPath(i, true))
- .Where(i => i != null)
- .Where(i => i.IsVisibleStandalone(user))
+ var folderIds = GetRecordingFolders(user, true)
.Select(i => i.Id)
.ToList();
var excludeItemTypes = new List<string>();
- folderIds.Add(internalLiveTvFolderId);
-
- excludeItemTypes.Add(typeof(LiveTvChannel).Name);
- excludeItemTypes.Add(typeof(LiveTvProgram).Name);
-
if (folderIds.Count == 0)
{
return new QueryResult<BaseItem>();
@@ -1629,221 +1435,61 @@ namespace Emby.Server.Implementations.LiveTv
}
}
+ var limit = query.Limit;
+
if ((query.IsInProgress ?? false))
{
- // TODO: filter
- var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray();
- var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray();
+ limit = (query.Limit ?? 10) * 2;
+ limit = null;
- return new QueryResult<BaseItem>
- {
- Items = items,
- TotalRecordCount = items.Length
- };
+ //var allActivePaths = EmbyTV.EmbyTV.Current.GetAllActiveRecordings().Select(i => i.Path).ToArray();
+ //var items = allActivePaths.Select(i => _libraryManager.FindByPath(i, false)).Where(i => i != null).ToArray();
+
+ //return new QueryResult<BaseItem>
+ //{
+ // Items = items,
+ // TotalRecordCount = items.Length
+ //};
+
+ dtoOptions.Fields = dtoOptions.Fields.Concat(new[] { ItemFields.Tags }).Distinct().ToArray();
}
- return _libraryManager.GetItemsResult(new InternalItemsQuery(user)
+ var result = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
{
MediaTypes = new[] { MediaType.Video },
Recursive = true,
- AncestorIds = folderIds.Select(i => i.ToString("N")).ToArray(folderIds.Count),
+ AncestorIds = folderIds.ToArray(folderIds.Count),
IsFolder = false,
IsVirtualItem = false,
- Limit = query.Limit,
+ Limit = limit,
StartIndex = query.StartIndex,
- OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
+ OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
EnableTotalRecordCount = query.EnableTotalRecordCount,
IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count),
ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count),
Genres = genres.ToArray(genres.Count),
DtoOptions = dtoOptions
});
- }
-
- public QueryResult<BaseItemDto> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken)
- {
- var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
- if (user != null && !IsLiveTvEnabled(user))
- {
- return new QueryResult<BaseItemDto>();
- }
-
- if (user == null || (query.IsInProgress ?? false))
- {
- return new QueryResult<BaseItemDto>();
- }
-
- var folders = EmbyTV.EmbyTV.Current.GetRecordingFolders()
- .SelectMany(i => i.Locations)
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .Select(i => _libraryManager.FindByPath(i, true))
- .Where(i => i != null)
- .Where(i => i.IsVisibleStandalone(user))
- .ToList();
-
- if (folders.Count == 0)
- {
- return new QueryResult<BaseItemDto>();
- }
-
- var includeItemTypes = new List<string>();
- var excludeItemTypes = new List<string>();
-
- includeItemTypes.Add(typeof(Series).Name);
-
- RemoveFields(options);
-
- var internalResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
- {
- Recursive = true,
- AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(folders.Count),
- Limit = query.Limit,
- OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.DateCreated, SortOrder.Descending) },
- EnableTotalRecordCount = query.EnableTotalRecordCount,
- IncludeItemTypes = includeItemTypes.ToArray(includeItemTypes.Count),
- ExcludeItemTypes = excludeItemTypes.ToArray(excludeItemTypes.Count),
- DtoOptions = options
- });
-
- var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user);
-
- return new QueryResult<BaseItemDto>
- {
- Items = returnArray,
- TotalRecordCount = internalResult.TotalRecordCount
- };
- }
-
- public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken)
- {
- var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
- if (user != null && !IsLiveTvEnabled(user))
- {
- return new QueryResult<BaseItem>();
- }
-
- var folder = GetInternalLiveTvFolder(cancellationToken);
-
- // TODO: Figure out how to merge emby recordings + service recordings
- if (_services.Length == 1)
- {
- return GetEmbyRecordings(query, options, folder.Id, user);
- }
-
- return await GetInternalRecordingsFromServices(query, user, options, folder.Id, cancellationToken).ConfigureAwait(false);
- }
-
- private async Task<QueryResult<BaseItem>> GetInternalRecordingsFromServices(RecordingQuery query, User user, DtoOptions options, Guid internalLiveTvFolderId, CancellationToken cancellationToken)
- {
- await RefreshRecordings(internalLiveTvFolderId, cancellationToken).ConfigureAwait(false);
-
- var internalQuery = new InternalItemsQuery(user)
- {
- IncludeItemTypes = new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name },
- DtoOptions = options
- };
-
- if (!string.IsNullOrEmpty(query.ChannelId))
- {
- internalQuery.ChannelIds = new[] { query.ChannelId };
- }
-
- var queryResult = _libraryManager.GetItemList(internalQuery);
- IEnumerable<ILiveTvRecording> recordings = queryResult.Cast<ILiveTvRecording>();
-
- if (!string.IsNullOrWhiteSpace(query.Id))
- {
- var guid = new Guid(query.Id);
-
- recordings = recordings
- .Where(i => i.Id == guid);
- }
-
- if (!string.IsNullOrWhiteSpace(query.GroupId))
- {
- var guid = new Guid(query.GroupId);
-
- recordings = recordings.Where(i => GetRecordingGroupIds(i).Contains(guid));
- }
-
- if (query.IsInProgress.HasValue)
- {
- var val = query.IsInProgress.Value;
- recordings = recordings.Where(i => i.Status == RecordingStatus.InProgress == val);
- }
-
- if (query.Status.HasValue)
- {
- var val = query.Status.Value;
- recordings = recordings.Where(i => i.Status == val);
- }
-
- if (query.IsMovie.HasValue)
- {
- var val = query.IsMovie.Value;
- recordings = recordings.Where(i => i.IsMovie == val);
- }
-
- if (query.IsNews.HasValue)
- {
- var val = query.IsNews.Value;
- recordings = recordings.Where(i => i.IsNews == val);
- }
-
- if (query.IsSeries.HasValue)
- {
- var val = query.IsSeries.Value;
- recordings = recordings.Where(i => i.IsSeries == val);
- }
-
- if (query.IsKids.HasValue)
- {
- var val = query.IsKids.Value;
- recordings = recordings.Where(i => i.IsKids == val);
- }
-
- if (query.IsSports.HasValue)
- {
- var val = query.IsSports.Value;
- recordings = recordings.Where(i => i.IsSports == val);
- }
-
- if (!string.IsNullOrEmpty(query.SeriesTimerId))
- {
- var guid = new Guid(query.SeriesTimerId);
-
- recordings = recordings
- .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.ServiceName, i.SeriesTimerId) == guid);
- }
-
- recordings = recordings.OrderByDescending(i => i.StartDate);
- var entityList = recordings.ToList();
- IEnumerable<ILiveTvRecording> entities = entityList;
-
- if (query.StartIndex.HasValue)
+ if ((query.IsInProgress ?? false))
{
- entities = entities.Skip(query.StartIndex.Value);
- }
+ result.Items = result
+ .Items
+ .OfType<Video>()
+ .Where(i => !i.IsCompleteMedia)
+ .ToArray();
- if (query.Limit.HasValue)
- {
- entities = entities.Take(query.Limit.Value);
+ result.TotalRecordCount = result.Items.Length;
}
- return new QueryResult<BaseItem>
- {
- Items = entities.Cast<BaseItem>().ToArray(),
- TotalRecordCount = entityList.Count
- };
+ return result;
}
- public async Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> tuples, ItemFields[] fields, User user = null)
+ public Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> tuples, ItemFields[] fields, User user = null)
{
- var programTuples = new List<Tuple<BaseItemDto, string, string, string>>();
+ var programTuples = new List<Tuple<BaseItemDto, string, string>>();
var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
- var hasServiceName = fields.Contains(ItemFields.ServiceName);
foreach (var tuple in tuples)
{
@@ -1888,7 +1534,7 @@ namespace Emby.Server.Implementations.LiveTv
if (hasChannelInfo || hasChannelImage)
{
- var channel = GetInternalChannel(program.ChannelId);
+ var channel = _libraryManager.GetItemById(program.ChannelId) as LiveTvChannel;
if (channel != null)
{
@@ -1903,19 +1549,12 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- var serviceName = program.ServiceName;
-
- if (hasServiceName)
- {
- dto.ServiceName = serviceName;
- }
-
var externalSeriesId = program.ExternalSeriesId;
- programTuples.Add(new Tuple<BaseItemDto, string, string, string>(dto, serviceName, GetItemExternalId(program), externalSeriesId));
+ programTuples.Add(new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, externalSeriesId));
}
- await AddRecordingInfo(programTuples, CancellationToken.None).ConfigureAwait(false);
+ return AddRecordingInfo(programTuples, CancellationToken.None);
}
public ActiveRecordingInfo GetActiveRecordingInfo(string path)
@@ -1923,73 +1562,21 @@ namespace Emby.Server.Implementations.LiveTv
return EmbyTV.EmbyTV.Current.GetActiveRecordingInfo(path);
}
- public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, User user = null)
- {
- var recording = (ILiveTvRecording)item;
- var service = GetService(recording);
-
- var channel = string.IsNullOrWhiteSpace(recording.ChannelId) ? null : GetInternalChannel(recording.ChannelId);
-
- var info = recording;
-
- dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId) || service == null
- ? null
- : _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N");
-
- dto.TimerId = string.IsNullOrEmpty(info.TimerId) || service == null
- ? null
- : _tvDtoService.GetInternalTimerId(service.Name, info.TimerId).ToString("N");
-
- dto.StartDate = info.StartDate;
- dto.Status = info.Status.ToString();
- dto.IsRepeat = info.IsRepeat;
- dto.EpisodeTitle = info.EpisodeTitle;
- dto.IsMovie = info.IsMovie;
- dto.IsSeries = info.IsSeries;
- dto.IsSports = info.IsSports;
- dto.IsLive = info.IsLive;
- dto.IsNews = info.IsNews;
- dto.IsKids = info.IsKids;
- dto.IsPremiere = info.IsPremiere;
-
- if (info.Status == RecordingStatus.InProgress && info.EndDate.HasValue)
- {
- var now = DateTime.UtcNow.Ticks;
- var start = info.StartDate.Ticks;
- var end = info.EndDate.Value.Ticks;
-
- var pct = now - start;
- pct /= end;
- pct *= 100;
- dto.CompletionPercentage = pct;
- }
-
- if (channel != null)
- {
- dto.ChannelName = channel.Name;
-
- if (channel.HasImage(ImageType.Primary))
- {
- dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
- }
- }
- }
-
public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, User user = null)
{
var service = EmbyTV.EmbyTV.Current;
var info = activeRecordingInfo.Timer;
- var channel = string.IsNullOrWhiteSpace(info.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, info.ChannelId));
+ var channel = string.IsNullOrWhiteSpace(info.ChannelId) ? null : _libraryManager.GetItemById(_tvDtoService.GetInternalChannelId(service.Name, info.ChannelId));
dto.SeriesTimerId = string.IsNullOrEmpty(info.SeriesTimerId)
? null
- : _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N");
+ : _tvDtoService.GetInternalSeriesTimerId(info.SeriesTimerId).ToString("N");
dto.TimerId = string.IsNullOrEmpty(info.Id)
? null
- : _tvDtoService.GetInternalTimerId(service.Name, info.Id).ToString("N");
+ : _tvDtoService.GetInternalTimerId(info.Id);
var startDate = info.StartDate;
var endDate = info.EndDate;
@@ -2034,13 +1621,13 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- public async Task<QueryResult<BaseItemDto>> GetRecordings(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken)
+ public QueryResult<BaseItemDto> GetRecordings(RecordingQuery query, DtoOptions options)
{
- var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
+ var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId);
RemoveFields(options);
- var internalResult = await GetInternalRecordings(query, options, cancellationToken).ConfigureAwait(false);
+ var internalResult = GetEmbyRecordings(query, options, user);
var returnArray = _dtoService.GetBaseItemDtos(internalResult.Items, options, user);
@@ -2051,7 +1638,7 @@ namespace Emby.Server.Implementations.LiveTv
};
}
- public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
+ private async Task<QueryResult<TimerInfo>> GetTimersInternal(TimerQuery query, CancellationToken cancellationToken)
{
var tasks = _services.Select(async i =>
{
@@ -2104,7 +1691,7 @@ namespace Emby.Server.Implementations.LiveTv
var guid = new Guid(query.SeriesTimerId);
timers = timers
- .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item2.Name, i.Item1.SeriesTimerId) == guid);
+ .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item1.SeriesTimerId) == guid);
}
if (!string.IsNullOrEmpty(query.Id))
@@ -2112,72 +1699,105 @@ namespace Emby.Server.Implementations.LiveTv
var guid = new Guid(query.Id);
timers = timers
- .Where(i => _tvDtoService.GetInternalTimerId(i.Item2.Name, i.Item1.Id) == guid);
+ .Where(i => string.Equals(_tvDtoService.GetInternalTimerId(i.Item1.Id), query.Id, StringComparison.OrdinalIgnoreCase));
}
- var returnList = new List<TimerInfoDto>();
-
- foreach (var i in timers)
- {
- var program = string.IsNullOrEmpty(i.Item1.ProgramId) ?
- null :
- GetInternalProgram(_tvDtoService.GetInternalProgramId(i.Item2.Name, i.Item1.ProgramId).ToString("N"));
-
- var channel = string.IsNullOrEmpty(i.Item1.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId));
-
- returnList.Add(_tvDtoService.GetTimerInfoDto(i.Item1, i.Item2, program, channel));
- }
-
- var returnArray = returnList
+ var returnArray = timers
+ .Select(i => i.Item1)
.OrderBy(i => i.StartDate)
- .ToArray(returnList.Count);
+ .ToArray();
- return new QueryResult<TimerInfoDto>
+ return new QueryResult<TimerInfo>
{
Items = returnArray,
TotalRecordCount = returnArray.Length
};
}
- public async Task DeleteRecording(string recordingId)
+ public async Task<QueryResult<TimerInfoDto>> GetTimers(TimerQuery query, CancellationToken cancellationToken)
{
- var recording = await GetInternalRecording(recordingId, CancellationToken.None).ConfigureAwait(false);
+ var tasks = _services.Select(async i =>
+ {
+ try
+ {
+ var recs = await i.GetTimersAsync(cancellationToken).ConfigureAwait(false);
+ return recs.Select(r => new Tuple<TimerInfo, ILiveTvService>(r, i));
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting recordings", ex);
+ return new List<Tuple<TimerInfo, ILiveTvService>>();
+ }
+ });
+ var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+ var timers = results.SelectMany(i => i.ToList());
- if (recording == null)
+ if (query.IsActive.HasValue)
{
- throw new ResourceNotFoundException(string.Format("Recording with Id {0} not found", recordingId));
+ if (query.IsActive.Value)
+ {
+ timers = timers.Where(i => i.Item1.Status == RecordingStatus.InProgress);
+ }
+ else
+ {
+ timers = timers.Where(i => i.Item1.Status != RecordingStatus.InProgress);
+ }
}
- await DeleteRecording((BaseItem)recording).ConfigureAwait(false);
- }
-
- public async Task DeleteRecording(BaseItem recording)
- {
- var service = GetService(recording.ServiceName);
-
- if (service != null)
+ if (query.IsScheduled.HasValue)
{
- // handle the service being uninstalled and the item hanging around in the database
- try
+ if (query.IsScheduled.Value)
{
- await service.DeleteRecordingAsync(GetItemExternalId(recording), CancellationToken.None).ConfigureAwait(false);
+ timers = timers.Where(i => i.Item1.Status == RecordingStatus.New);
}
- catch (ResourceNotFoundException)
+ else
{
-
+ timers = timers.Where(i => i.Item1.Status != RecordingStatus.New);
}
}
- _lastRecordingRefreshTime = DateTime.MinValue;
+ if (!string.IsNullOrEmpty(query.ChannelId))
+ {
+ var guid = new Guid(query.ChannelId);
+ timers = timers.Where(i => guid == _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId));
+ }
+
+ if (!string.IsNullOrEmpty(query.SeriesTimerId))
+ {
+ var guid = new Guid(query.SeriesTimerId);
+
+ timers = timers
+ .Where(i => _tvDtoService.GetInternalSeriesTimerId(i.Item1.SeriesTimerId) == guid);
+ }
+
+ if (!string.IsNullOrEmpty(query.Id))
+ {
+ timers = timers
+ .Where(i => string.Equals(_tvDtoService.GetInternalTimerId(i.Item1.Id), query.Id, StringComparison.OrdinalIgnoreCase));
+ }
+
+ var returnList = new List<TimerInfoDto>();
- // This is the responsibility of the live tv service
- await _libraryManager.DeleteItem((BaseItem)recording, new DeleteOptions
+ foreach (var i in timers)
{
- DeleteFileLocation = false
+ var program = string.IsNullOrEmpty(i.Item1.ProgramId) ?
+ null :
+ _libraryManager.GetItemById(_tvDtoService.GetInternalProgramId(i.Item1.ProgramId)) as LiveTvProgram;
+
+ var channel = string.IsNullOrEmpty(i.Item1.ChannelId) ? null : _libraryManager.GetItemById(_tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId));
- }).ConfigureAwait(false);
+ returnList.Add(_tvDtoService.GetTimerInfoDto(i.Item1, i.Item2, program, channel));
+ }
- _lastRecordingRefreshTime = DateTime.MinValue;
+ var returnArray = returnList
+ .OrderBy(i => i.StartDate)
+ .ToArray(returnList.Count);
+
+ return new QueryResult<TimerInfoDto>
+ {
+ Items = returnArray,
+ TotalRecordCount = returnArray.Length
+ };
}
public async Task CancelTimer(string id)
@@ -2192,15 +1812,17 @@ namespace Emby.Server.Implementations.LiveTv
var service = GetService(timer.ServiceName);
await service.CancelTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
- _lastRecordingRefreshTime = DateTime.MinValue;
- EventHelper.FireEventIfNotNull(TimerCancelled, this, new GenericEventArgs<TimerEventInfo>
+ if (!(service is EmbyTV.EmbyTV))
{
- Argument = new TimerEventInfo
+ EventHelper.FireEventIfNotNull(TimerCancelled, this, new GenericEventArgs<TimerEventInfo>
{
- Id = id
- }
- }, _logger);
+ Argument = new TimerEventInfo
+ {
+ Id = id
+ }
+ }, _logger);
+ }
}
public async Task CancelSeriesTimer(string id)
@@ -2215,7 +1837,6 @@ namespace Emby.Server.Implementations.LiveTv
var service = GetService(timer.ServiceName);
await service.CancelSeriesTimerAsync(timer.ExternalId, CancellationToken.None).ConfigureAwait(false);
- _lastRecordingRefreshTime = DateTime.MinValue;
EventHelper.FireEventIfNotNull(SeriesTimerCancelled, this, new GenericEventArgs<TimerEventInfo>
{
@@ -2226,18 +1847,6 @@ namespace Emby.Server.Implementations.LiveTv
}, _logger);
}
- public async Task<BaseItemDto> GetRecording(string id, DtoOptions options, CancellationToken cancellationToken, User user = null)
- {
- var item = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
-
- if (item == null)
- {
- return null;
- }
-
- return _dtoService.GetBaseItemDto((BaseItem)item, options, user);
- }
-
public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
{
var results = await GetTimers(new TimerQuery
@@ -2345,7 +1954,7 @@ namespace Emby.Server.Implementations.LiveTv
if (!string.IsNullOrEmpty(i.Item1.ChannelId))
{
var internalChannelId = _tvDtoService.GetInternalChannelId(i.Item2.Name, i.Item1.ChannelId);
- var channel = GetInternalChannel(internalChannelId);
+ var channel = _libraryManager.GetItemById(internalChannelId);
channelName = channel == null ? null : channel.Name;
}
@@ -2361,11 +1970,17 @@ namespace Emby.Server.Implementations.LiveTv
};
}
+ public BaseItem GetLiveTvChannel(TimerInfo timer, ILiveTvService service)
+ {
+ var internalChannelId = _tvDtoService.GetInternalChannelId(service.Name, timer.ChannelId);
+ return _libraryManager.GetItemById(internalChannelId);
+ }
+
public void AddChannelInfo(List<Tuple<BaseItemDto, LiveTvChannel>> tuples, DtoOptions options, User user)
{
var now = DateTime.UtcNow;
- var channelIds = tuples.Select(i => i.Item2.Id.ToString("N")).Distinct().ToArray();
+ var channelIds = tuples.Select(i => i.Item2.Id).Distinct().ToArray();
var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
{
@@ -2374,8 +1989,8 @@ namespace Emby.Server.Implementations.LiveTv
MaxStartDate = now,
MinEndDate = now,
Limit = channelIds.Length,
- OrderBy = new[] { new Tuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) },
- TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Id.ToString("N") },
+ OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.StartDate, SortOrder.Ascending) },
+ TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Id },
DtoOptions = options
}) : new List<BaseItem>();
@@ -2383,10 +1998,9 @@ namespace Emby.Server.Implementations.LiveTv
RemoveFields(options);
var currentProgramsList = new List<BaseItem>();
- var currentChannelsDict = new Dictionary<string, BaseItemDto>();
+ var currentChannelsDict = new Dictionary<Guid, BaseItemDto>();
var addCurrentProgram = options.AddCurrentProgram;
- var addServiceName = options.Fields.Contains(ItemFields.ServiceName);
foreach (var tuple in tuples)
{
@@ -2397,17 +2011,11 @@ namespace Emby.Server.Implementations.LiveTv
dto.ChannelNumber = channel.Number;
dto.ChannelType = channel.ChannelType;
- if (addServiceName)
- {
- dto.ServiceName = channel.ServiceName;
- }
-
currentChannelsDict[dto.Id] = dto;
if (addCurrentProgram)
{
- var channelIdString = channel.Id.ToString("N");
- var currentProgram = programs.FirstOrDefault(i => string.Equals(i.ChannelId, channelIdString));
+ var currentProgram = programs.FirstOrDefault(i => channel.Id.Equals(i.ChannelId));
if (currentProgram != null)
{
@@ -2422,13 +2030,10 @@ namespace Emby.Server.Implementations.LiveTv
foreach (var programDto in currentProgramDtos)
{
- if (!string.IsNullOrWhiteSpace(programDto.ChannelId))
+ BaseItemDto channelDto;
+ if (currentChannelsDict.TryGetValue(programDto.ChannelId, out channelDto))
{
- BaseItemDto channelDto;
- if (currentChannelsDict.TryGetValue(programDto.ChannelId, out channelDto))
- {
- channelDto.CurrentProgram = programDto;
- }
+ channelDto.CurrentProgram = programDto;
}
}
}
@@ -2436,25 +2041,30 @@ namespace Emby.Server.Implementations.LiveTv
private async Task<Tuple<SeriesTimerInfo, ILiveTvService>> GetNewTimerDefaultsInternal(CancellationToken cancellationToken, LiveTvProgram program = null)
{
- var service = program != null && !string.IsNullOrWhiteSpace(program.ServiceName) ?
+ var service = program != null ?
GetService(program) :
- _services.FirstOrDefault();
+ null;
+
+ if (service == null)
+ {
+ service = _services.First();
+ }
ProgramInfo programInfo = null;
if (program != null)
{
- var channel = GetInternalChannel(program.ChannelId);
+ var channel = _libraryManager.GetItemById(program.ChannelId);
programInfo = new ProgramInfo
{
Audio = program.Audio,
- ChannelId = GetItemExternalId(channel),
+ ChannelId = channel.ExternalId,
CommunityRating = program.CommunityRating,
EndDate = program.EndDate ?? DateTime.MinValue,
EpisodeTitle = program.EpisodeTitle,
- Genres = program.Genres,
- Id = GetItemExternalId(program),
+ Genres = program.Genres.ToList(),
+ Id = program.ExternalId,
IsHD = program.IsHD,
IsKids = program.IsKids,
IsLive = program.IsLive,
@@ -2503,7 +2113,7 @@ namespace Emby.Server.Implementations.LiveTv
public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
{
- var program = GetInternalProgram(programId);
+ var program = (LiveTvProgram)_libraryManager.GetItemById(programId);
var programDto = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
@@ -2519,8 +2129,8 @@ namespace Emby.Server.Implementations.LiveTv
info.StartDate = program.StartDate;
info.Name = program.Name;
info.Overview = program.Overview;
- info.ProgramId = programDto.Id;
- info.ExternalProgramId = GetItemExternalId(program);
+ info.ProgramId = programDto.Id.ToString("N");
+ info.ExternalProgramId = program.ExternalId;
if (program.EndDate.HasValue)
{
@@ -2545,24 +2155,26 @@ namespace Emby.Server.Implementations.LiveTv
if (supportsNewTimerIds != null)
{
newTimerId = await supportsNewTimerIds.CreateTimer(info, cancellationToken).ConfigureAwait(false);
- newTimerId = _tvDtoService.GetInternalTimerId(timer.ServiceName, newTimerId).ToString("N");
+ newTimerId = _tvDtoService.GetInternalTimerId(newTimerId);
}
else
{
await service.CreateTimerAsync(info, cancellationToken).ConfigureAwait(false);
}
- _lastRecordingRefreshTime = DateTime.MinValue;
_logger.Info("New recording scheduled");
- EventHelper.FireEventIfNotNull(TimerCreated, this, new GenericEventArgs<TimerEventInfo>
+ if (!(service is EmbyTV.EmbyTV))
{
- Argument = new TimerEventInfo
+ EventHelper.FireEventIfNotNull(TimerCreated, this, new GenericEventArgs<TimerEventInfo>
{
- ProgramId = _tvDtoService.GetInternalProgramId(timer.ServiceName, info.ProgramId).ToString("N"),
- Id = newTimerId
- }
- }, _logger);
+ Argument = new TimerEventInfo
+ {
+ ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId),
+ Id = newTimerId
+ }
+ }, _logger);
+ }
}
public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
@@ -2588,20 +2200,18 @@ namespace Emby.Server.Implementations.LiveTv
if (supportsNewTimerIds != null)
{
newTimerId = await supportsNewTimerIds.CreateSeriesTimer(info, cancellationToken).ConfigureAwait(false);
- newTimerId = _tvDtoService.GetInternalSeriesTimerId(timer.ServiceName, newTimerId).ToString("N");
+ newTimerId = _tvDtoService.GetInternalSeriesTimerId(newTimerId).ToString("N");
}
else
{
await service.CreateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
}
- _lastRecordingRefreshTime = DateTime.MinValue;
-
EventHelper.FireEventIfNotNull(SeriesTimerCreated, this, new GenericEventArgs<TimerEventInfo>
{
Argument = new TimerEventInfo
{
- ProgramId = _tvDtoService.GetInternalProgramId(timer.ServiceName, info.ProgramId).ToString("N"),
+ ProgramId = _tvDtoService.GetInternalProgramId(info.ProgramId),
Id = newTimerId
}
}, _logger);
@@ -2614,7 +2224,6 @@ namespace Emby.Server.Implementations.LiveTv
var service = GetService(timer.ServiceName);
await service.UpdateTimerAsync(info, cancellationToken).ConfigureAwait(false);
- _lastRecordingRefreshTime = DateTime.MinValue;
}
public async Task UpdateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
@@ -2624,138 +2233,6 @@ namespace Emby.Server.Implementations.LiveTv
var service = GetService(timer.ServiceName);
await service.UpdateSeriesTimerAsync(info, cancellationToken).ConfigureAwait(false);
- _lastRecordingRefreshTime = DateTime.MinValue;
- }
-
- private IEnumerable<string> GetRecordingGroupNames(ILiveTvRecording recording)
- {
- var list = new List<string>();
-
- if (recording.IsSeries)
- {
- list.Add(recording.Name);
- }
-
- if (recording.IsKids)
- {
- list.Add("Kids");
- }
-
- if (recording.IsMovie)
- {
- list.Add("Movies");
- }
-
- if (recording.IsNews)
- {
- list.Add("News");
- }
-
- if (recording.IsSports)
- {
- list.Add("Sports");
- }
-
- if (!recording.IsSports && !recording.IsNews && !recording.IsMovie && !recording.IsKids && !recording.IsSeries)
- {
- list.Add("Others");
- }
-
- return list;
- }
-
- private List<Guid> GetRecordingGroupIds(ILiveTvRecording recording)
- {
- return GetRecordingGroupNames(recording).Select(i => i.ToLower()
- .GetMD5())
- .ToList();
- }
-
- public async Task<QueryResult<BaseItemDto>> GetRecordingGroups(RecordingGroupQuery query, CancellationToken cancellationToken)
- {
- var recordingResult = await GetInternalRecordings(new RecordingQuery
- {
- UserId = query.UserId
-
- }, new DtoOptions(), cancellationToken).ConfigureAwait(false);
-
- var embyServiceName = EmbyTV.EmbyTV.Current.Name;
- var recordings = recordingResult.Items.Where(i => !string.Equals(i.ServiceName, embyServiceName, StringComparison.OrdinalIgnoreCase)).OfType<ILiveTvRecording>().ToList();
-
- var groups = new List<BaseItemDto>();
-
- var series = recordings
- .Where(i => i.IsSeries)
- .ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase);
-
- groups.AddRange(series.OrderByString(i => i.Key).Select(i => new BaseItemDto
- {
- Name = i.Key,
- RecordingCount = i.Count()
- }));
-
- groups.Add(new BaseItemDto
- {
- Name = "Kids",
- RecordingCount = recordings.Count(i => i.IsKids)
- });
-
- groups.Add(new BaseItemDto
- {
- Name = "Movies",
- RecordingCount = recordings.Count(i => i.IsMovie)
- });
-
- groups.Add(new BaseItemDto
- {
- Name = "News",
- RecordingCount = recordings.Count(i => i.IsNews)
- });
-
- groups.Add(new BaseItemDto
- {
- Name = "Sports",
- RecordingCount = recordings.Count(i => i.IsSports)
- });
-
- groups.Add(new BaseItemDto
- {
- Name = "Others",
- RecordingCount = recordings.Count(i => !i.IsSports && !i.IsNews && !i.IsMovie && !i.IsKids && !i.IsSeries)
- });
-
- groups = groups
- .Where(i => i.RecordingCount > 0)
- .ToList();
-
- foreach (var group in groups)
- {
- group.Id = group.Name.ToLower().GetMD5().ToString("N");
- }
-
- return new QueryResult<BaseItemDto>
- {
- Items = groups.ToArray(groups.Count),
- TotalRecordCount = groups.Count
- };
- }
-
- public async Task CloseLiveStream(string id)
- {
- var parts = id.Split(new[] { '_' }, 2);
-
- var service = _services.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), parts[0], StringComparison.OrdinalIgnoreCase));
-
- if (service == null)
- {
- throw new ArgumentException("Service not found.");
- }
-
- id = parts[1];
-
- _logger.Info("Closing live stream from {0}, stream Id: {1}", service.Name, id);
-
- await service.CloseLiveStream(id, CancellationToken.None).ConfigureAwait(false);
}
public GuideInfo GetGuideInfo()
@@ -2776,7 +2253,6 @@ namespace Emby.Server.Implementations.LiveTv
public void Dispose()
{
Dispose(true);
- GC.SuppressFinalize(this);
}
private bool _isDisposed = false;
@@ -2792,66 +2268,22 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- private async Task<LiveTvServiceInfo[]> GetServiceInfos(CancellationToken cancellationToken)
+ private LiveTvServiceInfo[] GetServiceInfos()
{
- var tasks = Services.Select(i => GetServiceInfo(i, cancellationToken));
-
- return await Task.WhenAll(tasks).ConfigureAwait(false);
+ return Services.Select(GetServiceInfo).ToArray();
}
- private async Task<LiveTvServiceInfo> GetServiceInfo(ILiveTvService service, CancellationToken cancellationToken)
+ private LiveTvServiceInfo GetServiceInfo(ILiveTvService service)
{
- var info = new LiveTvServiceInfo
+ return new LiveTvServiceInfo
{
Name = service.Name
};
-
- var tunerIdPrefix = service.GetType().FullName.GetMD5().ToString("N") + "_";
-
- try
- {
- var statusInfo = await service.GetStatusInfoAsync(cancellationToken).ConfigureAwait(false);
-
- info.Status = statusInfo.Status;
- info.StatusMessage = statusInfo.StatusMessage;
- info.Version = statusInfo.Version;
- info.HasUpdateAvailable = statusInfo.HasUpdateAvailable;
- info.HomePageUrl = service.HomePageUrl;
- info.IsVisible = statusInfo.IsVisible;
-
- info.Tuners = statusInfo.Tuners.Select(i =>
- {
- string channelName = null;
-
- if (!string.IsNullOrEmpty(i.ChannelId))
- {
- var internalChannelId = _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId);
- var channel = GetInternalChannel(internalChannelId);
- channelName = channel == null ? null : channel.Name;
- }
-
- var dto = _tvDtoService.GetTunerInfoDto(service.Name, i, channelName);
-
- dto.Id = tunerIdPrefix + dto.Id;
-
- return dto;
-
- }).ToArray();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error getting service status info from {0}", ex, service.Name ?? string.Empty);
-
- info.Status = LiveTvServiceStatus.Unavailable;
- info.StatusMessage = ex.Message;
- }
-
- return info;
}
- public async Task<LiveTvInfo> GetLiveTvInfo(CancellationToken cancellationToken)
+ public LiveTvInfo GetLiveTvInfo(CancellationToken cancellationToken)
{
- var services = await GetServiceInfos(CancellationToken.None).ConfigureAwait(false);
+ var services = GetServiceInfos();
var info = new LiveTvInfo
{
@@ -2898,15 +2330,6 @@ namespace Emby.Server.Implementations.LiveTv
return service.ResetTuner(parts[1], cancellationToken);
}
- public BaseItemDto GetLiveTvFolder(string userId, CancellationToken cancellationToken)
- {
- var user = string.IsNullOrEmpty(userId) ? null : _userManager.GetUserById(userId);
-
- var folder = GetInternalLiveTvFolder(cancellationToken);
-
- return _dtoService.GetBaseItemDto(folder, new DtoOptions(), user);
- }
-
private void RemoveFields(DtoOptions options)
{
var fields = options.Fields.ToList();
@@ -2921,7 +2344,7 @@ namespace Emby.Server.Implementations.LiveTv
public Folder GetInternalLiveTvFolder(CancellationToken cancellationToken)
{
var name = _localization.GetLocalizedString("HeaderLiveTV");
- return _libraryManager.GetNamedView(name, CollectionType.LiveTv, name, cancellationToken);
+ return _libraryManager.GetNamedView(name, CollectionType.LiveTv, name);
}
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
@@ -2990,7 +2413,6 @@ namespace Emby.Server.Implementations.LiveTv
info.Id = Guid.NewGuid().ToString("N");
list.Add(info);
config.ListingProviders = list.ToArray(list.Count);
- info.EnableNewProgramIds = true;
}
else
{
@@ -3146,9 +2568,38 @@ namespace Emby.Server.Implementations.LiveTv
return _tvDtoService.GetInternalChannelId(serviceName, externalId);
}
- public Guid GetInternalProgramId(string serviceName, string externalId)
+ public Guid GetInternalProgramId(string externalId)
+ {
+ return _tvDtoService.GetInternalProgramId(externalId);
+ }
+
+ public List<BaseItem> GetRecordingFolders(User user)
+ {
+ return GetRecordingFolders(user, false);
+ }
+
+ private List<BaseItem> GetRecordingFolders(User user, bool refreshChannels)
{
- return _tvDtoService.GetInternalProgramId(serviceName, externalId);
+ var folders = EmbyTV.EmbyTV.Current.GetRecordingFolders()
+ .SelectMany(i => i.Locations)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .Select(i => _libraryManager.FindByPath(i, true))
+ .Where(i => i != null)
+ .Where(i => i.IsVisibleStandalone(user))
+ .SelectMany(i => _libraryManager.GetCollectionFolders(i))
+ .DistinctBy(i => i.Id)
+ .OrderBy(i => i.SortName)
+ .ToList();
+
+ folders.AddRange(_channelManager().GetChannelsInternal(new MediaBrowser.Model.Channels.ChannelQuery
+ {
+ UserId = user.Id,
+ IsRecordingsFolder = true,
+ RefreshLatestChannelItems = refreshChannels
+
+ }).Items);
+
+ return folders.Cast<BaseItem>().ToList();
}
}
} \ No newline at end of file
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
index 29b7c41ef2..808672d462 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvMediaSourceProvider.cs
@@ -15,6 +15,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Extensions;
+using MediaBrowser.Common.Configuration;
namespace Emby.Server.Implementations.LiveTv
{
@@ -26,8 +27,9 @@ namespace Emby.Server.Implementations.LiveTv
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IServerApplicationHost _appHost;
+ private IApplicationPaths _appPaths;
- public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IJsonSerializer jsonSerializer, ILogManager logManager, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IServerApplicationHost appHost)
+ public LiveTvMediaSourceProvider(ILiveTvManager liveTvManager, IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IServerApplicationHost appHost)
{
_liveTvManager = liveTvManager;
_jsonSerializer = jsonSerializer;
@@ -35,9 +37,10 @@ namespace Emby.Server.Implementations.LiveTv
_mediaEncoder = mediaEncoder;
_appHost = appHost;
_logger = logManager.GetLogger(GetType().Name);
+ _appPaths = appPaths;
}
- public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
+ public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
{
var baseItem = (BaseItem)item;
@@ -45,20 +48,20 @@ namespace Emby.Server.Implementations.LiveTv
{
var activeRecordingInfo = _liveTvManager.GetActiveRecordingInfo(item.Path);
- if (string.IsNullOrWhiteSpace(baseItem.Path) || activeRecordingInfo != null)
+ if (string.IsNullOrEmpty(baseItem.Path) || activeRecordingInfo != null)
{
return GetMediaSourcesInternal(item, activeRecordingInfo, cancellationToken);
}
}
- return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
+ return Task.FromResult<IEnumerable<MediaSourceInfo>>(Array.Empty<MediaSourceInfo>());
}
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
private const char StreamIdDelimeter = '_';
private const string StreamIdDelimeterString = "_";
- private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(IHasMediaSources item, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
+ private async Task<IEnumerable<MediaSourceInfo>> GetMediaSourcesInternal(BaseItem item, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
{
IEnumerable<MediaSourceInfo> sources;
@@ -66,30 +69,20 @@ namespace Emby.Server.Implementations.LiveTv
try
{
- if (item is ILiveTvRecording)
+ if (activeRecordingInfo != null)
{
- sources = await _liveTvManager.GetRecordingMediaSources(item, cancellationToken)
+ sources = await EmbyTV.EmbyTV.Current.GetRecordingStreamMediaSources(activeRecordingInfo, cancellationToken)
.ConfigureAwait(false);
}
else
{
- if (activeRecordingInfo != null)
- {
- sources = await EmbyTV.EmbyTV.Current.GetRecordingStreamMediaSources(activeRecordingInfo, cancellationToken)
- .ConfigureAwait(false);
- }
- else
- {
- sources = await _liveTvManager.GetChannelMediaSources(item, cancellationToken)
- .ConfigureAwait(false);
- }
+ sources = await _liveTvManager.GetChannelMediaSources(item, cancellationToken)
+ .ConfigureAwait(false);
}
}
catch (NotImplementedException)
{
- var hasMediaSources = (IHasMediaSources)item;
-
- sources = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false);
+ sources = _mediaSourceManager.GetStaticMediaSources(item, false);
forceRequireOpening = true;
}
@@ -128,102 +121,15 @@ namespace Emby.Server.Implementations.LiveTv
return list;
}
- public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, bool allowLiveStreamProbe, CancellationToken cancellationToken)
+ public async Task<ILiveStream> OpenMediaSource(string openToken, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
- MediaSourceInfo stream = null;
- const bool isAudio = false;
-
var keys = openToken.Split(new[] { StreamIdDelimeter }, 3);
var mediaSourceId = keys.Length >= 3 ? keys[2] : null;
- IDirectStreamProvider directStreamProvider = null;
-
- if (string.Equals(keys[0], typeof(LiveTvChannel).Name, StringComparison.OrdinalIgnoreCase))
- {
- var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, cancellationToken).ConfigureAwait(false);
- stream = info.Item1;
- directStreamProvider = info.Item2;
-
- //allowLiveStreamProbe = false;
- }
- else
- {
- stream = await _liveTvManager.GetRecordingStream(keys[1], cancellationToken).ConfigureAwait(false);
- }
-
- try
- {
- if (!allowLiveStreamProbe || !stream.SupportsProbing || stream.MediaStreams.Any(i => i.Index != -1))
- {
- AddMediaInfo(stream, isAudio, cancellationToken);
- }
- else
- {
- await new LiveStreamHelper(_mediaEncoder, _logger).AddMediaInfoWithProbe(stream, isAudio, cancellationToken).ConfigureAwait(false);
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error probing live tv stream", ex);
- }
-
- _logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(stream));
- return new Tuple<MediaSourceInfo, IDirectStreamProvider>(stream, directStreamProvider);
- }
-
- private void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
- {
- mediaSource.DefaultSubtitleStreamIndex = null;
- // Null this out so that it will be treated like a live stream
- mediaSource.RunTimeTicks = null;
+ var info = await _liveTvManager.GetChannelStream(keys[1], mediaSourceId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
+ var liveStream = info.Item2;
- var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio);
-
- if (audioStream == null || audioStream.Index == -1)
- {
- mediaSource.DefaultAudioStreamIndex = null;
- }
- else
- {
- mediaSource.DefaultAudioStreamIndex = audioStream.Index;
- }
-
- var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video);
- if (videoStream != null)
- {
- if (!videoStream.BitRate.HasValue)
- {
- var width = videoStream.Width ?? 1920;
-
- if (width >= 3000)
- {
- videoStream.BitRate = 30000000;
- }
-
- else if (width >= 1900)
- {
- videoStream.BitRate = 20000000;
- }
-
- else if (width >= 1200)
- {
- videoStream.BitRate = 8000000;
- }
-
- else if (width >= 700)
- {
- videoStream.BitRate = 2000000;
- }
- }
- }
-
- // Try to estimate this
- mediaSource.InferTotalBitrate();
- }
-
- public Task CloseMediaSource(string liveStreamId)
- {
- return _liveTvManager.CloseLiveStream(liveStreamId);
+ return liveStream;
}
}
}
diff --git a/Emby.Server.Implementations/LiveTv/RecordingImageProvider.cs b/Emby.Server.Implementations/LiveTv/RecordingImageProvider.cs
deleted file mode 100644
index 992badbb53..0000000000
--- a/Emby.Server.Implementations/LiveTv/RecordingImageProvider.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Emby.Server.Implementations.LiveTv
-{
- public class RecordingImageProvider : IDynamicImageProvider, IHasItemChangeMonitor
- {
- private readonly ILiveTvManager _liveTvManager;
-
- public RecordingImageProvider(ILiveTvManager liveTvManager)
- {
- _liveTvManager = liveTvManager;
- }
-
- public IEnumerable<ImageType> GetSupportedImages(IHasMetadata item)
- {
- return new[] { ImageType.Primary };
- }
-
- public async Task<DynamicImageResponse> GetImage(IHasMetadata item, ImageType type, CancellationToken cancellationToken)
- {
- var liveTvItem = (ILiveTvRecording)item;
-
- var imageResponse = new DynamicImageResponse();
-
- var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
-
- if (service != null)
- {
- try
- {
- var response = await service.GetRecordingImageAsync(liveTvItem.ExternalId, cancellationToken).ConfigureAwait(false);
-
- if (response != null)
- {
- imageResponse.HasImage = true;
- imageResponse.Stream = response.Stream;
- imageResponse.Format = response.Format;
- }
- }
- catch (NotImplementedException)
- {
- }
- }
-
- return imageResponse;
- }
-
- public string Name
- {
- get { return "Live TV Service Provider"; }
- }
-
- public bool Supports(IHasMetadata item)
- {
- return item is ILiveTvRecording;
- }
-
- public int Order
- {
- get { return 0; }
- }
-
- public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
- {
- var liveTvItem = item as ILiveTvRecording;
-
- if (liveTvItem != null)
- {
- return !liveTvItem.HasImage(ImageType.Primary);
- }
- return false;
- }
- }
-}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 45e96c36d8..ca5e51971d 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -16,6 +16,7 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
+using MediaBrowser.Controller.Library;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
@@ -55,22 +56,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
ChannelCache cache = null;
var key = tuner.Id;
- if (enableCache && !string.IsNullOrWhiteSpace(key) && _channelCache.TryGetValue(key, out cache))
+ if (enableCache && !string.IsNullOrEmpty(key) && _channelCache.TryGetValue(key, out cache))
{
- if (DateTime.UtcNow - cache.Date < TimeSpan.FromMinutes(60))
- {
- return cache.Channels.ToList();
- }
+ return cache.Channels.ToList();
}
var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
var list = result.ToList();
- Logger.Info("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
+ //Logger.Info("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
- if (!string.IsNullOrWhiteSpace(key) && list.Count > 0)
+ if (!string.IsNullOrEmpty(key) && list.Count > 0)
{
cache = cache ?? new ChannelCache();
- cache.Date = DateTime.UtcNow;
cache.Channels = list;
_channelCache.AddOrUpdate(key, cache, (k, v) => cache);
}
@@ -137,11 +134,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return list;
}
- protected abstract Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken);
+ protected abstract Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, ChannelInfo channel, CancellationToken cancellationToken);
public async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken)
{
- if (string.IsNullOrWhiteSpace(channelId))
+ if (string.IsNullOrEmpty(channelId))
{
throw new ArgumentNullException("channelId");
}
@@ -150,17 +147,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
var hosts = GetTunerHosts();
- var hostsWithChannel = new List<TunerHostInfo>();
-
foreach (var host in hosts)
{
try
{
var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
+ var channelInfo = channels.FirstOrDefault(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase));
- if (channels.Any(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase)))
+ if (channelInfo != null)
{
- hostsWithChannel.Add(host);
+ return await GetChannelStreamMediaSources(host, channelInfo, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception ex)
@@ -168,44 +164,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Logger.Error("Error getting channels", ex);
}
}
-
- foreach (var host in hostsWithChannel)
- {
- try
- {
- // 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;
- }
- catch (Exception ex)
- {
- Logger.Error("Error opening tuner", ex);
- }
- }
}
return new List<MediaSourceInfo>();
}
- protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken);
+ protected abstract Task<ILiveStream> GetChannelStream(TunerHostInfo tuner, ChannelInfo channel, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
- public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
+ public async Task<ILiveStream> GetChannelStream(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
- if (string.IsNullOrWhiteSpace(channelId))
+ if (string.IsNullOrEmpty(channelId))
{
throw new ArgumentNullException("channelId");
}
@@ -217,44 +185,34 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var hosts = GetTunerHosts();
- var hostsWithChannel = new List<TunerHostInfo>();
+ var hostsWithChannel = new List<Tuple<TunerHostInfo, ChannelInfo>>();
foreach (var host in hosts)
{
- if (string.IsNullOrWhiteSpace(streamId))
+ try
{
- try
- {
- var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
+ var channels = await GetChannels(host, true, cancellationToken).ConfigureAwait(false);
+ var channelInfo = channels.FirstOrDefault(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase));
- if (channels.Any(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase)))
- {
- hostsWithChannel.Add(host);
- }
- }
- catch (Exception ex)
+ if (channelInfo != null)
{
- Logger.Error("Error getting channels", ex);
+ hostsWithChannel.Add(new Tuple<TunerHostInfo, ChannelInfo>(host, channelInfo));
}
}
- else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase))
+ catch (Exception ex)
{
- hostsWithChannel = new List<TunerHostInfo> { host };
- streamId = streamId.Substring(host.Id.Length);
- break;
+ Logger.Error("Error getting channels", ex);
}
}
- foreach (var host in hostsWithChannel)
+ foreach (var hostTuple in hostsWithChannel)
{
- if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
+ var host = hostTuple.Item1;
+ var channelInfo = hostTuple.Item2;
try
{
- var liveStream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
+ var liveStream = await GetChannelStream(host, channelInfo, streamId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
var startTime = DateTime.UtcNow;
await liveStream.Open(cancellationToken).ConfigureAwait(false);
var endTime = DateTime.UtcNow;
@@ -270,21 +228,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
throw new LiveTvConflictException();
}
- protected async Task<bool> IsAvailable(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
- {
- try
- {
- return await IsAvailableInternal(tuner, channelId, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error checking tuner availability", ex);
- return false;
- }
- }
-
- protected abstract Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken);
-
protected virtual string ChannelIdPrefix
{
get
@@ -294,7 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
protected virtual bool IsValidChannelId(string channelId)
{
- if (string.IsNullOrWhiteSpace(channelId))
+ if (string.IsNullOrEmpty(channelId))
{
throw new ArgumentNullException("channelId");
}
@@ -309,7 +252,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private class ChannelCache
{
- public DateTime Date;
public List<ChannelInfo> Channels;
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 74758e906c..e873eb8e98 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -21,6 +21,7 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.System;
+using MediaBrowser.Controller.Library;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
@@ -68,11 +69,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
var id = ChannelIdPrefix + i.GuideNumber;
- if (!info.EnableNewHdhrChannelIds)
- {
- id += '_' + (i.GuideName ?? string.Empty).GetMD5().ToString("N");
- }
-
return id;
}
@@ -90,7 +86,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
using (var stream = response.Content)
{
- var lineup = JsonSerializer.DeserializeFromStream<List<Channels>>(stream) ?? new List<Channels>();
+ var lineup = await JsonSerializer.DeserializeFromStreamAsync<List<Channels>>(stream).ConfigureAwait(false) ?? new List<Channels>();
if (info.ImportFavoritesOnly)
{
@@ -131,12 +127,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>();
private async Task<DiscoverResponse> GetModelInfo(TunerHostInfo info, bool throwAllExceptions, CancellationToken cancellationToken)
{
+ var cacheKey = info.Id;
+
lock (_modelCache)
{
- DiscoverResponse response;
- if (_modelCache.TryGetValue(info.Url, out response))
+ if (!string.IsNullOrEmpty(cacheKey))
{
- if ((DateTime.UtcNow - response.DateQueried).TotalHours <= 12)
+ DiscoverResponse response;
+ if (_modelCache.TryGetValue(cacheKey, out response))
{
return response;
}
@@ -149,20 +147,20 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
Url = string.Format("{0}/discover.json", GetApiUrl(info)),
CancellationToken = cancellationToken,
- TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds),
+ TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(10).TotalMilliseconds),
BufferContent = false
}, "GET").ConfigureAwait(false))
{
using (var stream = response.Content)
{
- var discoverResponse = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
+ var discoverResponse = await JsonSerializer.DeserializeFromStreamAsync<DiscoverResponse>(stream).ConfigureAwait(false);
- if (!string.IsNullOrWhiteSpace(info.Id))
+ if (!string.IsNullOrEmpty(cacheKey))
{
lock (_modelCache)
{
- _modelCache[info.Id] = discoverResponse;
+ _modelCache[cacheKey] = discoverResponse;
}
}
@@ -179,12 +177,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
ModelNumber = defaultValue
};
- if (!string.IsNullOrWhiteSpace(info.Id))
+ if (!string.IsNullOrEmpty(cacheKey))
{
// HDHR4 doesn't have this api
lock (_modelCache)
{
- _modelCache[info.Id] = response;
+ _modelCache[cacheKey] = response;
}
}
return response;
@@ -395,10 +393,18 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
videoCodec = "h264";
videoBitrate = 15000000;
}
+ else if (string.Equals(profile, "internet720", StringComparison.OrdinalIgnoreCase))
+ {
+ width = 1280;
+ height = 720;
+ isInterlaced = false;
+ videoCodec = "h264";
+ videoBitrate = 8000000;
+ }
else if (string.Equals(profile, "internet540", StringComparison.OrdinalIgnoreCase))
{
width = 960;
- height = 546;
+ height = 540;
isInterlaced = false;
videoCodec = "h264";
videoBitrate = 2500000;
@@ -511,8 +517,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
SupportsTranscoding = true,
IsInfiniteStream = true,
IgnoreDts = true,
- SupportsProbing = false,
- //AnalyzeDurationMs = 2000000
//IgnoreIndex = true,
//ReadAtNativeFramerate = true
};
@@ -522,19 +526,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return mediaSource;
}
- protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
+ protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, ChannelInfo channelInfo, CancellationToken cancellationToken)
{
var list = new List<MediaSourceInfo>();
- if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase))
- {
- return list;
- }
+ var channelId = channelInfo.Id;
var hdhrId = GetHdHrIdFromChannelId(channelId);
- var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
- var channelInfo = channels.FirstOrDefault(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase));
-
var hdHomerunChannelInfo = channelInfo as HdHomerunChannelInfo;
var isLegacyTuner = hdHomerunChannelInfo != null && hdHomerunChannelInfo.IsLegacyTuner;
@@ -548,12 +546,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
try
{
var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
- var model = modelInfo == null ? string.Empty : (modelInfo.ModelNumber ?? string.Empty);
- if ((model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1))
+ if (modelInfo != null && modelInfo.SupportsTranscoding)
{
- list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
-
if (info.AllowHWTranscoding)
{
list.Add(GetMediaSource(info, hdhrId, channelInfo, "heavy"));
@@ -564,6 +559,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
list.Add(GetMediaSource(info, hdhrId, channelInfo, "internet240"));
list.Add(GetMediaSource(info, hdhrId, channelInfo, "mobile"));
}
+
+ list.Add(GetMediaSource(info, hdhrId, channelInfo, "native"));
}
}
catch
@@ -580,31 +577,31 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return list;
}
- protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
+ protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
var profile = streamId.Split('_')[0];
- Logger.Info("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelId, streamId, profile);
-
- var hdhrId = GetHdHrIdFromChannelId(channelId);
+ Logger.Info("GetChannelStream: channel id: {0}. stream id: {1} profile: {2}", channelInfo.Id, streamId, profile);
- var channels = await GetChannels(info, true, CancellationToken.None).ConfigureAwait(false);
- var channelInfo = channels.FirstOrDefault(i => string.Equals(i.Id, channelId, StringComparison.OrdinalIgnoreCase));
+ var hdhrId = GetHdHrIdFromChannelId(channelInfo.Id);
var hdhomerunChannel = channelInfo as HdHomerunChannelInfo;
- var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
var modelInfo = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
+ if (!modelInfo.SupportsTranscoding)
+ {
+ profile = "native";
+ }
+
+ var mediaSource = GetMediaSource(info, hdhrId, channelInfo, profile);
+
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
{
- return new HdHomerunUdpStream(mediaSource, info, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment);
+ return new HdHomerunUdpStream(mediaSource, info, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Path), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
}
- // The UDP method is not working reliably on OSX, and on BSD it hasn't been tested yet
- var enableHttpStream = _environment.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX
- || _environment.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.BSD;
- enableHttpStream = true;
+ var enableHttpStream = true;
if (enableHttpStream)
{
mediaSource.Protocol = MediaProtocol.Http;
@@ -618,10 +615,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
mediaSource.Path = httpUrl;
- return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment);
+ return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
}
- return new HdHomerunUdpStream(mediaSource, info, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment);
+ return new HdHomerunUdpStream(mediaSource, info, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
}
public async Task Validate(TunerHostInfo info)
@@ -649,13 +646,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- protected override async Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
- {
- var info = await GetTunerInfos(tuner, cancellationToken).ConfigureAwait(false);
-
- return info.Any(i => i.Status == LiveTvTunerStatus.Available);
- }
-
public class DiscoverResponse
{
public string FriendlyName { get; set; }
@@ -668,16 +658,29 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public string LineupURL { get; set; }
public int TunerCount { get; set; }
- public DateTime DateQueried { get; set; }
-
- public DiscoverResponse()
+ public bool SupportsTranscoding
{
- DateQueried = DateTime.UtcNow;
+ get
+ {
+ var model = ModelNumber ?? string.Empty;
+
+ if ((model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1))
+ {
+ return true;
+ }
+
+ return false;
+ }
}
}
public async Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken)
{
+ lock (_modelCache)
+ {
+ _modelCache.Clear();
+ }
+
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token;
var list = new List<TunerHostInfo>();
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index 5156f1744b..2bc5a0ed3e 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -7,6 +7,8 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Controller.LiveTv;
+using System.Net;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
@@ -61,7 +63,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (!String.IsNullOrEmpty(_channel))
{
- if (!string.IsNullOrWhiteSpace(_profile) && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(_profile) && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase))
{
commands.Add(Tuple.Create("vchannel", String.Format("{0} transcode={1}", _channel, _profile)));
}
@@ -86,11 +88,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private static ushort GetSetReply = 5;
private uint? _lockkey = null;
- private int _activeTuner = 0;
+ private int _activeTuner = -1;
private readonly ISocketFactory _socketFactory;
private IpAddressInfo _remoteIp;
private ILogger _logger;
+ private ISocket _currentTcpSocket;
public HdHomerunManager(ISocketFactory socketFactory, ILogger logger)
{
@@ -100,10 +103,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public void Dispose()
{
- var task = StopStreaming();
+ using (var socket = _currentTcpSocket)
+ {
+ if (socket != null)
+ {
+ _currentTcpSocket = null;
- Task.WaitAll(task);
- GC.SuppressFinalize(this);
+ var task = StopStreaming(socket);
+ Task.WaitAll(task);
+ }
+ }
}
public async Task<bool> CheckTunerAvailability(IpAddressInfo remoteIp, int tuner, CancellationToken cancellationToken)
@@ -130,58 +139,45 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return string.Equals(returnVal, "none", StringComparison.OrdinalIgnoreCase);
}
- public async Task StartStreaming(IpAddressInfo remoteIp, IpAddressInfo localIp, int localPort, IHdHomerunChannelCommands commands, int numTuners, CancellationToken cancellationToken)
+ public async Task StartStreaming(IpAddressInfo remoteIp, IPAddress localIp, int localPort, IHdHomerunChannelCommands commands, int numTuners, CancellationToken cancellationToken)
{
_remoteIp = remoteIp;
-
- using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort))
- {
- var receiveBuffer = new byte[8192];
-
- if (!_lockkey.HasValue)
- {
- var rand = new Random();
- _lockkey = (uint)rand.Next();
- }
- var lockKeyValue = _lockkey.Value;
+ var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort);
+ _currentTcpSocket = tcpClient;
- var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort);
+ var receiveBuffer = new byte[8192];
- for (int i = 0; i < numTuners; ++i)
- {
- if (!await CheckTunerAvailability(tcpClient, _remoteIp, i, cancellationToken).ConfigureAwait(false))
- continue;
+ if (!_lockkey.HasValue)
+ {
+ var rand = new Random();
+ _lockkey = (uint)rand.Next();
+ }
- _activeTuner = i;
- var lockKeyString = String.Format("{0:d}", lockKeyValue);
- var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
- await tcpClient.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
- var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
- string returnVal;
- // parse response to make sure it worked
- if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
- continue;
+ var lockKeyValue = _lockkey.Value;
- var commandList = commands.GetCommands();
- foreach(Tuple<string,string> command in commandList)
- {
- var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
- await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
- response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
- // parse response to make sure it worked
- if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
- {
- await ReleaseLockkey(tcpClient, lockKeyValue).ConfigureAwait(false);
- continue;
- }
+ var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort);
- }
-
- var targetValue = String.Format("rtp://{0}:{1}", localIp, localPort);
- var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue);
+ for (int i = 0; i < numTuners; ++i)
+ {
+ if (!await CheckTunerAvailability(tcpClient, _remoteIp, i, cancellationToken).ConfigureAwait(false))
+ continue;
+
+ _activeTuner = i;
+ var lockKeyString = String.Format("{0:d}", lockKeyValue);
+ var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
+ await tcpClient.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
+ var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
+ string returnVal;
+ // parse response to make sure it worked
+ if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
+ continue;
- await tcpClient.SendToAsync(targetMsg, 0, targetMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
+ var commandList = commands.GetCommands();
+ foreach (Tuple<string, string> command in commandList)
+ {
+ var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, lockKeyValue);
+ await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
// parse response to make sure it worked
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
@@ -190,9 +186,25 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
continue;
}
- break;
}
+
+ var targetValue = String.Format("rtp://{0}:{1}", localIp, localPort);
+ var targetMsg = CreateSetMessage(i, "target", targetValue, lockKeyValue);
+
+ await tcpClient.SendToAsync(targetMsg, 0, targetMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
+ response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
+ // parse response to make sure it worked
+ if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
+ {
+ await ReleaseLockkey(tcpClient, lockKeyValue).ConfigureAwait(false);
+ continue;
+ }
+
+ return;
}
+
+ _activeTuner = -1;
+ throw new LiveTvConflictException();
}
public async Task ChangeChannel(IHdHomerunChannelCommands commands, CancellationToken cancellationToken)
@@ -220,32 +232,31 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- public async Task StopStreaming()
+ public Task StopStreaming(ISocket socket)
{
var lockKey = _lockkey;
if (!lockKey.HasValue)
- return;
+ return Task.CompletedTask;
- using (var socket = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort))
- {
- await ReleaseLockkey(socket, lockKey.Value).ConfigureAwait(false);
- }
+ return ReleaseLockkey(socket, lockKey.Value);
}
private async Task ReleaseLockkey(ISocket tcpClient, uint lockKeyValue)
{
_logger.Info("HdHomerunManager.ReleaseLockkey {0}", lockKeyValue);
+ var ipEndPoint = new IpEndPointInfo(_remoteIp, HdHomeRunPort);
+
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", lockKeyValue);
- await tcpClient.SendToAsync(releaseTarget, 0, releaseTarget.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false);
+ await tcpClient.SendToAsync(releaseTarget, 0, releaseTarget.Length, ipEndPoint, CancellationToken.None).ConfigureAwait(false);
var receiveBuffer = new byte[8192];
await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false);
var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", lockKeyValue);
_lockkey = null;
- await tcpClient.SendToAsync(releaseKeyMsg, 0, releaseKeyMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false);
+ await tcpClient.SendToAsync(releaseKeyMsg, 0, releaseKeyMsg.Length, ipEndPoint, CancellationToken.None).ConfigureAwait(false);
await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false);
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index 6e93055be0..33103979e4 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -9,23 +9,25 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Net;
using MediaBrowser.Model.System;
using MediaBrowser.Model.LiveTv;
+using System.Collections.Generic;
+using System.Net.Sockets;
+using System.Net;
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
{
private readonly IServerApplicationHost _appHost;
- private readonly ISocketFactory _socketFactory;
+ private readonly MediaBrowser.Model.Net.ISocketFactory _socketFactory;
private readonly IHdHomerunChannelCommands _channelCommands;
private readonly int _numTuners;
private readonly INetworkManager _networkManager;
- public HdHomerunUdpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment)
- : base(mediaSource, tunerHostInfo, environment, fileSystem, logger, appPaths)
+ public HdHomerunUdpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, MediaBrowser.Model.Net.ISocketFactory socketFactory, INetworkManager networkManager)
+ : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
{
_appHost = appHost;
_socketFactory = socketFactory;
@@ -36,6 +38,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
EnableStreamSharing = true;
}
+ private Socket CreateSocket(AddressFamily addressFamily, SocketType socketType, ProtocolType protocolType)
+ {
+ var socket = new Socket(addressFamily, SocketType.Stream, ProtocolType.Tcp);
+
+ return socket;
+ }
+
public override async Task Open(CancellationToken openCancellationToken)
{
LiveStreamCancellationTokenSource.Token.ThrowIfCancellationRequested();
@@ -49,14 +58,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Logger.Info("Opening HDHR UDP Live stream from {0}", uri.Host);
- var remoteAddress = _networkManager.ParseIpAddress(uri.Host);
- IpAddressInfo localAddress = null;
- using (var tcpSocket = _socketFactory.CreateSocket(remoteAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp, false))
+ var remoteAddress = IPAddress.Parse(uri.Host);
+ var embyRemoteAddress = _networkManager.ParseIpAddress(uri.Host);
+ IPAddress localAddress = null;
+ using (var tcpSocket = CreateSocket(remoteAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
{
try
{
- tcpSocket.Connect(new IpEndPointInfo(remoteAddress, HdHomerunManager.HdHomeRunPort));
- localAddress = tcpSocket.LocalEndPoint.IpAddress;
+ tcpSocket.Connect(new IPEndPoint(remoteAddress, HdHomerunManager.HdHomeRunPort));
+ localAddress = ((IPEndPoint)tcpSocket.LocalEndPoint).Address;
tcpSocket.Close();
}
catch (Exception)
@@ -72,7 +82,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
try
{
// send url to start streaming
- await hdHomerunManager.StartStreaming(remoteAddress, localAddress, localPort, _channelCommands, _numTuners, openCancellationToken).ConfigureAwait(false);
+ await hdHomerunManager.StartStreaming(embyRemoteAddress, localAddress, localPort, _channelCommands, _numTuners, openCancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -91,14 +101,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
var taskCompletionSource = new TaskCompletionSource<bool>();
- StartStreaming(udpClient, hdHomerunManager, remoteAddress, localAddress, localPort, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
+ StartStreaming(udpClient, hdHomerunManager, remoteAddress, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
//OpenedMediaSource.Protocol = MediaProtocol.File;
//OpenedMediaSource.Path = tempFile;
//OpenedMediaSource.ReadAtNativeFramerate = true;
- OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
- OpenedMediaSource.Protocol = MediaProtocol.Http;
+ MediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
+ MediaSource.Protocol = MediaProtocol.Http;
//OpenedMediaSource.SupportsDirectPlay = false;
//OpenedMediaSource.SupportsDirectStream = true;
//OpenedMediaSource.SupportsTranscoding = true;
@@ -107,12 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
await taskCompletionSource.Task.ConfigureAwait(false);
}
- protected override void CloseInternal()
- {
- LiveStreamCancellationTokenSource.Cancel();
- }
-
- private Task StartStreaming(ISocket udpClient, HdHomerunManager hdHomerunManager, IpAddressInfo remoteAddress, IpAddressInfo localAddress, int localPort, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
+ private Task StartStreaming(MediaBrowser.Model.Net.ISocket udpClient, HdHomerunManager hdHomerunManager, IPAddress remoteAddress, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
return Task.Run(async () =>
{
@@ -136,19 +141,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
EnableStreamSharing = false;
-
- try
- {
- await hdHomerunManager.StopStreaming().ConfigureAwait(false);
- }
- catch
- {
-
- }
}
}
- await DeleteTempFile(TempFilePath).ConfigureAwait(false);
+ await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
});
}
@@ -161,7 +157,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
private static int RtpHeaderBytes = 12;
- private async Task CopyTo(ISocket udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
+ private async Task CopyTo(MediaBrowser.Model.Net.ISocket udpClient, string file, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
{
var bufferSize = 81920;
@@ -191,6 +187,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (!resolved)
{
resolved = true;
+ DateOpened = DateTime.UtcNow;
Resolve(openTaskCompletionSource);
}
}
@@ -202,10 +199,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
private static int RtpHeaderBytes = 12;
private static int PacketSize = 1316;
- private readonly ISocket _udpClient;
+ private readonly MediaBrowser.Model.Net.ISocket _udpClient;
bool disposed;
- public UdpClientStream(ISocket udpClient) : base()
+ public UdpClientStream(MediaBrowser.Model.Net.ISocket udpClient) : base()
{
_udpClient = udpClient;
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
index f6758e94e2..7e0ac4131d 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs
@@ -11,47 +11,52 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.System;
using MediaBrowser.Model.LiveTv;
+using System.Linq;
+using MediaBrowser.Controller.Library;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class LiveStream : ILiveStream
{
public MediaSourceInfo OriginalMediaSource { get; set; }
- public MediaSourceInfo OpenedMediaSource { get; set; }
- public int ConsumerCount
- {
- get { return SharedStreamIds.Count; }
- }
+ public MediaSourceInfo MediaSource { get; set; }
+
+ public int ConsumerCount { get; set; }
public string OriginalStreamId { get; set; }
public bool EnableStreamSharing { get; set; }
public string UniqueId { get; private set; }
- public List<string> SharedStreamIds { get; private set; }
- protected readonly IEnvironmentInfo Environment;
protected readonly IFileSystem FileSystem;
protected readonly IServerApplicationPaths AppPaths;
- protected string TempFilePath;
+ protected string TempFilePath;
protected readonly ILogger Logger;
protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource();
public string TunerHostId { get; private set; }
- public LiveStream(MediaSourceInfo mediaSource, TunerHostInfo tuner, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths)
+ public DateTime DateOpened { get; protected set; }
+
+ public Func<Task> OnClose { get; set; }
+
+ public LiveStream(MediaSourceInfo mediaSource, TunerHostInfo tuner, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths)
{
OriginalMediaSource = mediaSource;
- Environment = environment;
FileSystem = fileSystem;
- OpenedMediaSource = mediaSource;
+ MediaSource = mediaSource;
Logger = logger;
EnableStreamSharing = true;
- SharedStreamIds = new List<string>();
UniqueId = Guid.NewGuid().ToString("N");
- TunerHostId = tuner.Id;
+
+ if (tuner != null)
+ {
+ TunerHostId = tuner.Id;
+ }
AppPaths = appPaths;
+ ConsumerCount = 1;
SetTempFilePath("ts");
}
@@ -62,20 +67,36 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public virtual Task Open(CancellationToken openCancellationToken)
{
- return Task.FromResult(true);
+ DateOpened = DateTime.UtcNow;
+ return Task.CompletedTask;
}
- public void Close()
+ public Task Close()
{
EnableStreamSharing = false;
Logger.Info("Closing " + GetType().Name);
- CloseInternal();
+ LiveStreamCancellationTokenSource.Cancel();
+
+ if (OnClose != null)
+ {
+ return CloseWithExternalFn();
+ }
+
+ return Task.CompletedTask;
}
- protected virtual void CloseInternal()
+ private async Task CloseWithExternalFn()
{
+ try
+ {
+ await OnClose().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error closing live stream", ex);
+ }
}
protected Stream GetInputStream(string path, bool allowAsyncFileRead)
@@ -90,91 +111,123 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return FileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, fileOpenOptions);
}
- protected async Task DeleteTempFile(string path, int retryCount = 0)
+ public Task DeleteTempFiles()
+ {
+ return DeleteTempFiles(GetStreamFilePaths());
+ }
+
+ protected async Task DeleteTempFiles(List<string> paths, int retryCount = 0)
{
if (retryCount == 0)
{
- Logger.Info("Deleting temp file {0}", path);
+ Logger.Info("Deleting temp files {0}", string.Join(", ", paths.ToArray()));
}
- try
- {
- FileSystem.DeleteFile(path);
- return;
- }
- catch (DirectoryNotFoundException)
- {
- return;
- }
- catch (FileNotFoundException)
- {
- return;
- }
- catch
- {
+ var failedFiles = new List<string>();
+ foreach (var path in paths)
+ {
+ try
+ {
+ FileSystem.DeleteFile(path);
+ }
+ catch (DirectoryNotFoundException)
+ {
+ }
+ catch (FileNotFoundException)
+ {
+ }
+ catch (Exception ex)
+ {
+ //Logger.ErrorException("Error deleting file {0}", ex, path);
+ failedFiles.Add(path);
+ }
}
- if (retryCount > 20)
+ if (failedFiles.Count > 0 && retryCount <= 40)
{
- return;
+ await Task.Delay(500).ConfigureAwait(false);
+ await DeleteTempFiles(failedFiles, retryCount + 1).ConfigureAwait(false);
}
+ }
- await Task.Delay(500).ConfigureAwait(false);
- await DeleteTempFile(path, retryCount + 1).ConfigureAwait(false);
+ protected virtual List<string> GetStreamFilePaths()
+ {
+ return new List<string> { TempFilePath };
}
public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
{
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token;
- var allowAsync = false;//Environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows;
+ var allowAsync = false;
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
- using (var inputStream = (FileStream)GetInputStream(TempFilePath, allowAsync))
+ bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10;
+
+ var nextFileInfo = GetNextFile(null);
+ var nextFile = nextFileInfo.Item1;
+ var isLastFile = nextFileInfo.Item2;
+
+ while (!string.IsNullOrEmpty(nextFile))
{
- TrySeek(inputStream, -20000);
+ var emptyReadLimit = isLastFile ? EmptyReadLimit : 1;
+
+ await CopyFile(nextFile, seekFile, emptyReadLimit, allowAsync, stream, cancellationToken).ConfigureAwait(false);
- await CopyTo(inputStream, stream, 81920, null, cancellationToken).ConfigureAwait(false);
+ seekFile = false;
+ nextFileInfo = GetNextFile(nextFile);
+ nextFile = nextFileInfo.Item1;
+ isLastFile = nextFileInfo.Item2;
}
+
+ Logger.Info("Live Stream ended.");
}
- private static async Task CopyTo(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken)
+ private Tuple<string, bool> GetNextFile(string currentFile)
{
- byte[] buffer = new byte[bufferSize];
+ var files = GetStreamFilePaths();
- var eofCount = 0;
- var emptyReadLimit = 1000;
+ //Logger.Info("Live stream files: {0}", string.Join(", ", files.ToArray()));
- while (eofCount < emptyReadLimit)
+ if (string.IsNullOrEmpty(currentFile))
{
- cancellationToken.ThrowIfCancellationRequested();
+ return new Tuple<string, bool>(files.Last(), true);
+ }
+
+ var nextIndex = files.FindIndex(i => string.Equals(i, currentFile, StringComparison.OrdinalIgnoreCase)) + 1;
+
+ var isLastFile = nextIndex == files.Count - 1;
- var bytesRead = source.Read(buffer, 0, buffer.Length);
+ return new Tuple<string, bool>(files.ElementAtOrDefault(nextIndex), isLastFile);
+ }
+
+ private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken)
+ {
+ //Logger.Info("Opening live stream file {0}. Empty read limit: {1}", path, emptyReadLimit);
- if (bytesRead == 0)
+ using (var inputStream = (FileStream)GetInputStream(path, allowAsync))
+ {
+ if (seekFile)
{
- eofCount++;
- await Task.Delay(10, cancellationToken).ConfigureAwait(false);
+ TrySeek(inputStream, -20000);
}
- else
- {
- eofCount = 0;
- //await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false);
- destination.Write(buffer, 0, bytesRead);
+ await ApplicationHost.StreamHelper.CopyToAsync(inputStream, stream, 81920, emptyReadLimit, cancellationToken).ConfigureAwait(false);
+ }
+ }
- if (onStarted != null)
- {
- onStarted();
- onStarted = null;
- }
- }
+ protected virtual int EmptyReadLimit
+ {
+ get
+ {
+ return 1000;
}
}
private void TrySeek(FileStream stream, long offset)
{
+ //Logger.Info("TrySeek live stream");
try
{
stream.Seek(offset, SeekOrigin.End);
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 04c5303f11..a1bff2b5bd 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -18,6 +18,7 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.System;
using System.IO;
+using MediaBrowser.Controller.Library;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
@@ -27,13 +28,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private readonly IServerApplicationHost _appHost;
private readonly IEnvironmentInfo _environment;
private readonly INetworkManager _networkManager;
+ private readonly IMediaSourceManager _mediaSourceManager;
- public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment, INetworkManager networkManager) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
+ public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment, INetworkManager networkManager) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_environment = environment;
_networkManager = networkManager;
+ _mediaSourceManager = mediaSourceManager;
}
public override string Type
@@ -76,7 +79,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return Task.FromResult(list);
}
- private string[] _disallowedSharedStreamExtensions = new string[]
+ private string[] _disallowedSharedStreamExtensions = new string[]
{
".mkv",
".mp4",
@@ -84,21 +87,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
".mpd"
};
- protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
+ protected override async Task<ILiveStream> GetChannelStream(TunerHostInfo info, ChannelInfo channelInfo, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken)
{
var tunerCount = info.TunerCount;
if (tunerCount > 0)
{
- var liveStreams = await EmbyTV.EmbyTV.Current.GetLiveStreams(info, cancellationToken).ConfigureAwait(false);
+ var tunerHostId = info.Id;
+ var liveStreams = currentLiveStreams.Where(i => string.Equals(i.TunerHostId, tunerHostId, StringComparison.OrdinalIgnoreCase)).ToList();
- if (liveStreams.Count >= info.TunerCount)
+ if (liveStreams.Count >= tunerCount)
{
- throw new LiveTvConflictException();
+ throw new LiveTvConflictException("M3U simultaneous stream limit has been reached.");
}
}
- var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false);
+ var sources = await GetChannelStreamMediaSources(info, channelInfo, cancellationToken).ConfigureAwait(false);
var mediaSource = sources.First();
@@ -108,11 +112,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
{
- return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment);
+ return new SharedHttpStream(mediaSource, info, streamId, FileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
}
}
- return new LiveStream(mediaSource, info, _environment, FileSystem, Logger, Config.ApplicationPaths);
+ return new LiveStream(mediaSource, info, FileSystem, Logger, Config.ApplicationPaths);
}
public async Task Validate(TunerHostInfo info)
@@ -123,41 +127,19 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
}
}
- protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
+ protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, ChannelInfo channelInfo, CancellationToken cancellationToken)
{
- var channels = await GetChannels(info, true, cancellationToken).ConfigureAwait(false);
- var channel = channels.FirstOrDefault(c => string.Equals(c.Id, channelId, StringComparison.OrdinalIgnoreCase));
- if (channel != null)
- {
- return new List<MediaSourceInfo> { CreateMediaSourceInfo(info, channel) };
- }
- return new List<MediaSourceInfo>();
+ return Task.FromResult(new List<MediaSourceInfo> { CreateMediaSourceInfo(info, channelInfo) });
}
protected virtual MediaSourceInfo CreateMediaSourceInfo(TunerHostInfo info, ChannelInfo channel)
{
var path = channel.Path;
- MediaProtocol protocol = MediaProtocol.File;
- if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
- {
- protocol = MediaProtocol.Http;
- }
- else if (path.StartsWith("rtmp", StringComparison.OrdinalIgnoreCase))
- {
- protocol = MediaProtocol.Rtmp;
- }
- else if (path.StartsWith("rtsp", StringComparison.OrdinalIgnoreCase))
- {
- protocol = MediaProtocol.Rtsp;
- }
- else if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
- {
- protocol = MediaProtocol.Udp;
- }
- else if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
- {
- protocol = MediaProtocol.Rtmp;
- }
+
+ var supportsDirectPlay = !info.EnableStreamLooping && info.TunerCount == 0;
+ var supportsDirectStream = !info.EnableStreamLooping;
+
+ var protocol = _mediaSourceManager.GetPathProtocol(path);
Uri uri;
var isRemote = true;
@@ -166,7 +148,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
isRemote = !_networkManager.IsInLocalNetwork(uri.Host);
}
- var supportsDirectPlay = !info.EnableStreamLooping && info.TunerCount == 0;
+ var httpHeaders = new Dictionary<string, string>();
+
+ if (protocol == MediaProtocol.Http)
+ {
+ // Use user-defined user-agent. If there isn't one, make it look like a browser.
+ httpHeaders["User-Agent"] = string.IsNullOrWhiteSpace(info.UserAgent) ?
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.85 Safari/537.36" :
+ info.UserAgent;
+ }
var mediaSource = new MediaSourceInfo
{
@@ -186,7 +176,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Type = MediaStreamType.Audio,
// Set the index to -1 because we don't know the exact index of the audio stream within the container
Index = -1
-
}
},
RequiresOpening = true,
@@ -200,7 +189,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
IsRemote = isRemote,
IgnoreDts = true,
- SupportsDirectPlay = supportsDirectPlay
+ SupportsDirectPlay = supportsDirectPlay,
+ SupportsDirectStream = supportsDirectStream,
+
+ RequiredHttpHeaders = httpHeaders
};
mediaSource.InferTotalBitrate();
@@ -208,11 +200,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
return mediaSource;
}
- protected override Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
- {
- return Task.FromResult(true);
- }
-
public Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs, CancellationToken cancellationToken)
{
return Task.FromResult(new List<TunerHostInfo>());
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index af7491e862..572edb1678 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -15,6 +15,7 @@ using MediaBrowser.Model.System;
using System.Globalization;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.LiveTv;
+using System.Collections.Generic;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
@@ -23,8 +24,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost;
- public SharedHttpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment)
- : base(mediaSource, tunerHostInfo, environment, fileSystem, logger, appPaths)
+ public SharedHttpStream(MediaSourceInfo mediaSource, TunerHostInfo tunerHostInfo, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
+ : base(mediaSource, tunerHostInfo, fileSystem, logger, appPaths)
{
_httpClient = httpClient;
_appHost = appHost;
@@ -45,7 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var typeName = GetType().Name;
Logger.Info("Opening " + typeName + " Live stream from {0}", url);
- var response = await _httpClient.SendAsync(new HttpRequestOptions
+ var httpRequestOptions = new HttpRequestOptions
{
Url = url,
CancellationToken = CancellationToken.None,
@@ -58,8 +59,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
LogResponse = true,
LogResponseHeaders = true
+ };
- }, "GET").ConfigureAwait(false);
+ foreach (var header in mediaSource.RequiredHttpHeaders)
+ {
+ httpRequestOptions.RequestHeaders[header.Key] = header.Value;
+ }
+
+ var response = await _httpClient.SendAsync(httpRequestOptions, "GET").ConfigureAwait(false);
var extension = "ts";
var requiresRemux = false;
@@ -98,8 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
//OpenedMediaSource.Path = tempFile;
//OpenedMediaSource.ReadAtNativeFramerate = true;
- OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
- OpenedMediaSource.Protocol = MediaProtocol.Http;
+ MediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
+ MediaSource.Protocol = MediaProtocol.Http;
//OpenedMediaSource.Path = TempFilePath;
//OpenedMediaSource.Protocol = MediaProtocol.File;
@@ -110,25 +117,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
//OpenedMediaSource.SupportsDirectStream = true;
//OpenedMediaSource.SupportsTranscoding = true;
await taskCompletionSource.Task.ConfigureAwait(false);
-
- if (OpenedMediaSource.SupportsProbing)
- {
- var elapsed = (DateTime.UtcNow - now).TotalMilliseconds;
-
- var delay = Convert.ToInt32(3000 - elapsed);
-
- if (delay > 0)
- {
- Logger.Info("Delaying shared stream by {0}ms to allow the buffer to build.", delay);
-
- await Task.Delay(delay).ConfigureAwait(false);
- }
- }
- }
-
- protected override void CloseInternal()
- {
- LiveStreamCancellationTokenSource.Cancel();
}
private Task StartStreaming(HttpResponseInfo response, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
@@ -145,7 +133,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
{
- StreamHelper.CopyTo(stream, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken);
+ await ApplicationHost.StreamHelper.CopyToAsync(stream, fileStream, 81920, () => Resolve(openTaskCompletionSource), cancellationToken).ConfigureAwait(false);
}
}
}
@@ -158,12 +146,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
Logger.ErrorException("Error copying live stream.", ex);
}
EnableStreamSharing = false;
- await DeleteTempFile(TempFilePath).ConfigureAwait(false);
+ await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
});
}
private void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)
{
+ DateOpened = DateTime.UtcNow;
openTaskCompletionSource.TrySetResult(true);
}
}