aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Server.Implementations/LiveTv
diff options
context:
space:
mode:
authorLuke <luke.pulverenti@gmail.com>2015-10-26 18:50:19 -0400
committerLuke <luke.pulverenti@gmail.com>2015-10-26 18:50:19 -0400
commit35778ebc02e5931142a1fe31a256b7488a07c5c2 (patch)
treeced0290be8820f5e507b51ca4c5165212b1879d1 /MediaBrowser.Server.Implementations/LiveTv
parentc0dc8d055bfd4d2f58591083beb9e9128357aad6 (diff)
parent8d77308593c3b16b733b0109323770d9dfe7e166 (diff)
Merge pull request #1222 from MediaBrowser/dev
3.0.5768.7
Diffstat (limited to 'MediaBrowser.Server.Implementations/LiveTv')
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs56
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs249
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs8
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs6
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs6
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs6
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/Listings/Emby/EmbyListings.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs129
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTv.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs145
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs65
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs63
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs167
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs14
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs42
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs98
17 files changed, 645 insertions, 415 deletions
diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
index f205da70db..24d38a63e0 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs
@@ -39,58 +39,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var imageResponse = new DynamicImageResponse();
- if (!string.IsNullOrEmpty(liveTvItem.ProviderImagePath))
- {
- imageResponse.Path = liveTvItem.ProviderImagePath;
- imageResponse.HasImage = true;
- }
- else if (!string.IsNullOrEmpty(liveTvItem.ProviderImageUrl))
- {
- var options = new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = liveTvItem.ProviderImageUrl,
-
- // Some image hosts require a user agent to be specified.
- UserAgent = "Emby Server/" + _appHost.ApplicationVersion
- };
+ var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
- var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
-
- var contentType = response.ContentType;
-
- if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
- {
- imageResponse.HasImage = true;
- imageResponse.Stream = response.Content;
- imageResponse.SetFormatFromMimeType(contentType);
- }
- else
- {
- _logger.Error("Provider did not return an image content type.");
- }
- }
- else if (liveTvItem.HasProviderImage ?? true)
+ if (service != null)
{
- var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
-
- if (service != null)
+ try
{
- try
- {
- var response = await service.GetChannelImageAsync(liveTvItem.ExternalId, cancellationToken).ConfigureAwait(false);
+ 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)
+ if (response != null)
{
+ imageResponse.HasImage = true;
+ imageResponse.Stream = response.Stream;
+ imageResponse.Format = response.Format;
}
}
+ catch (NotImplementedException)
+ {
+ }
}
return imageResponse;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index b89d8a8d10..2ac06cda82 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -8,7 +8,9 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.FileOrganization;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
@@ -24,6 +26,8 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using CommonIO;
+using MediaBrowser.Common.Extensions;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
@@ -47,10 +51,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private readonly ILibraryManager _libraryManager;
private readonly IProviderManager _providerManager;
private readonly IFileOrganizationService _organizationService;
+ private readonly IMediaEncoder _mediaEncoder;
public static EmbyTV Current;
- public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService)
+ public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder)
{
Current = this;
@@ -64,12 +69,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_libraryMonitor = libraryMonitor;
_providerManager = providerManager;
_organizationService = organizationService;
+ _mediaEncoder = mediaEncoder;
_liveTvManager = (LiveTvManager)liveTvManager;
_jsonSerializer = jsonSerializer;
- _recordingProvider = new ItemDataProvider<RecordingInfo>(jsonSerializer, _logger, Path.Combine(DataPath, "recordings"), (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase));
- _seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
- _timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers"));
+ _recordingProvider = new ItemDataProvider<RecordingInfo>(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "recordings"), (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase));
+ _seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
+ _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"));
_timerProvider.TimerFired += _timerProvider_TimerFired;
}
@@ -126,6 +132,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return status;
}
+ public async Task RefreshSeriesTimers(CancellationToken cancellationToken, IProgress<double> progress)
+ {
+ var timers = await GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
+
+ List<ChannelInfo> channels = null;
+
+ foreach (var timer in timers)
+ {
+ List<ProgramInfo> epgData;
+
+ if (timer.RecordAnyChannel)
+ {
+ if (channels == null)
+ {
+ channels = (await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false)).ToList();
+ }
+ var channelIds = channels.Select(i => i.Id).ToList();
+ epgData = GetEpgDataForChannels(channelIds);
+ }
+ else
+ {
+ epgData = GetEpgDataForChannel(timer.ChannelId);
+ }
+ await UpdateTimersForSeriesTimer(epgData, timer).ConfigureAwait(false);
+ }
+ }
+
private List<ChannelInfo> _channelCache = null;
private async Task<IEnumerable<ChannelInfo>> GetChannelsAsync(bool enableCache, CancellationToken cancellationToken)
{
@@ -235,7 +268,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
try
{
- File.Delete(remove.Path);
+ _fileSystem.DeleteFile(remove.Path);
}
catch (DirectoryNotFoundException)
{
@@ -247,6 +280,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
_recordingProvider.Delete(remove);
}
+ else
+ {
+ throw new ResourceNotFoundException("Recording not found: " + recordingId);
+ }
}
public Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
@@ -327,9 +364,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
throw new NotImplementedException();
}
- public Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken)
+ public async Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken)
{
- return Task.FromResult((IEnumerable<RecordingInfo>)_recordingProvider.GetAll());
+ var recordings = _recordingProvider.GetAll().ToList();
+ var updated = false;
+
+ foreach (var recording in recordings)
+ {
+ if (recording.Status == RecordingStatus.InProgress)
+ {
+ if (string.IsNullOrWhiteSpace(recording.TimerId) || !_activeRecordings.ContainsKey(recording.TimerId))
+ {
+ recording.Status = RecordingStatus.Cancelled;
+ recording.DateLastUpdated = DateTime.UtcNow;
+ _recordingProvider.Update(recording);
+ updated = true;
+ }
+ }
+ }
+
+ if (updated)
+ {
+ recordings = _recordingProvider.GetAll().ToList();
+ }
+
+ return recordings;
}
public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
@@ -364,39 +423,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return Task.FromResult((IEnumerable<SeriesTimerInfo>)_seriesTimerProvider.GetAll());
}
- public async Task RefreshSeriesTimers(CancellationToken cancellationToken, IProgress<double> progress)
- {
- var timers = await GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
-
- List<ChannelInfo> channels = null;
-
- foreach (var timer in timers)
- {
- List<ProgramInfo> epgData;
-
- if (timer.RecordAnyChannel)
- {
- if (channels == null)
- {
- channels = (await GetChannelsAsync(true, CancellationToken.None).ConfigureAwait(false)).ToList();
- }
- var channelIds = channels.Select(i => i.Id).ToList();
- epgData = GetEpgDataForChannels(channelIds);
- }
- else
- {
- epgData = GetEpgDataForChannel(timer.ChannelId);
- }
- await UpdateTimersForSeriesTimer(epgData, timer).ConfigureAwait(false);
- }
- }
-
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
{
try
{
return await GetProgramsAsyncInternal(channelId, startDateUtc, endDateUtc, cancellationToken).ConfigureAwait(false);
}
+ catch (OperationCanceledException)
+ {
+ throw;
+ }
catch (Exception ex)
{
_logger.ErrorException("Error getting programs", ex);
@@ -411,7 +447,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
foreach (var provider in GetListingProviders())
{
- var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channel.Number, startDateUtc, endDateUtc, cancellationToken)
+ var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channel.Number, channel.Name, startDateUtc, endDateUtc, cancellationToken)
.ConfigureAwait(false);
var list = programs.ToList();
@@ -457,20 +493,36 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
foreach (var hostInstance in _liveTvManager.TunerHosts)
{
- MediaSourceInfo mediaSourceInfo = null;
try
{
- mediaSourceInfo = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+ var result = await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+
+ result.Item2.Release();
+
+ return result.Item1;
}
catch (Exception e)
{
_logger.ErrorException("Error getting channel stream", e);
}
+ }
+
+ throw new ApplicationException("Tuner not found.");
+ }
+
+ private async Task<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStreamInternal(string channelId, string streamId, CancellationToken cancellationToken)
+ {
+ _logger.Info("Streaming Channel " + channelId);
- if (mediaSourceInfo != null)
+ foreach (var hostInstance in _liveTvManager.TunerHosts)
+ {
+ try
{
- mediaSourceInfo.Id = Guid.NewGuid().ToString("N");
- return mediaSourceInfo;
+ return await hostInstance.GetChannelStream(channelId, streamId, cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception e)
+ {
+ _logger.ErrorException("Error getting channel stream", e);
}
}
@@ -527,11 +579,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
try
{
+ var recordingEndDate = timer.EndDate.AddSeconds(timer.PostPaddingSeconds);
+
+ if (recordingEndDate <= DateTime.UtcNow)
+ {
+ _logger.Warn("Recording timer fired for timer {0}, Id: {1}, but the program has already ended.", timer.Name, timer.Id);
+ return;
+ }
+
var cancellationTokenSource = new CancellationTokenSource();
if (_activeRecordings.TryAdd(timer.Id, cancellationTokenSource))
{
- await RecordStream(timer, cancellationTokenSource.Token).ConfigureAwait(false);
+ await RecordStream(timer, recordingEndDate, cancellationTokenSource.Token).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
@@ -544,58 +604,62 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- private async Task RecordStream(TimerInfo timer, CancellationToken cancellationToken)
+ private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, CancellationToken cancellationToken)
{
if (timer == null)
{
throw new ArgumentNullException("timer");
}
- var mediaStreamInfo = await GetChannelStream(timer.ChannelId, null, CancellationToken.None);
- var duration = (timer.EndDate - DateTime.UtcNow).Add(TimeSpan.FromSeconds(timer.PostPaddingSeconds));
-
- HttpRequestOptions httpRequestOptions = new HttpRequestOptions()
+ if (string.IsNullOrWhiteSpace(timer.ProgramId))
{
- Url = mediaStreamInfo.Path
- };
+ throw new InvalidOperationException("timer.ProgramId is null. Cannot record.");
+ }
var info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
+
+ if (info == null)
+ {
+ throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId));
+ }
+
var recordPath = RecordingPath;
if (info.IsMovie)
{
- recordPath = Path.Combine(recordPath, "Movies", _fileSystem.GetValidFilename(info.Name));
+ recordPath = Path.Combine(recordPath, "Movies", _fileSystem.GetValidFilename(info.Name).Trim());
}
else if (info.IsSeries)
{
- recordPath = Path.Combine(recordPath, "Series", _fileSystem.GetValidFilename(info.Name));
+ recordPath = Path.Combine(recordPath, "Series", _fileSystem.GetValidFilename(info.Name).Trim());
}
else if (info.IsKids)
{
- recordPath = Path.Combine(recordPath, "Kids", _fileSystem.GetValidFilename(info.Name));
+ recordPath = Path.Combine(recordPath, "Kids", _fileSystem.GetValidFilename(info.Name).Trim());
}
else if (info.IsSports)
{
- recordPath = Path.Combine(recordPath, "Sports", _fileSystem.GetValidFilename(info.Name));
+ recordPath = Path.Combine(recordPath, "Sports", _fileSystem.GetValidFilename(info.Name).Trim());
}
else
{
- recordPath = Path.Combine(recordPath, "Other", _fileSystem.GetValidFilename(info.Name));
+ recordPath = Path.Combine(recordPath, "Other", _fileSystem.GetValidFilename(info.Name).Trim());
}
- var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)) + ".ts";
+ var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts";
recordPath = Path.Combine(recordPath, recordingFileName);
- Directory.CreateDirectory(Path.GetDirectoryName(recordPath));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath));
- var recording = _recordingProvider.GetAll().FirstOrDefault(x => string.Equals(x.ProgramId, info.Id, StringComparison.OrdinalIgnoreCase));
+ var recordingId = info.Id.GetMD5().ToString("N");
+ var recording = _recordingProvider.GetAll().FirstOrDefault(x => string.Equals(x.Id, recordingId, StringComparison.OrdinalIgnoreCase));
if (recording == null)
{
recording = new RecordingInfo
{
ChannelId = info.ChannelId,
- Id = Guid.NewGuid().ToString("N"),
+ Id = recordingId,
StartDate = info.StartDate,
EndDate = info.EndDate,
Genres = info.Genres,
@@ -611,7 +675,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
Name = info.Name,
EpisodeTitle = info.EpisodeTitle,
ProgramId = info.Id,
- HasImage = info.HasImage,
ImagePath = info.ImagePath,
ImageUrl = info.ImageUrl,
OriginalAirDate = info.OriginalAirDate,
@@ -624,30 +687,58 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_recordingProvider.Add(recording);
}
- recording.Path = recordPath;
- recording.Status = RecordingStatus.InProgress;
- recording.DateLastUpdated = DateTime.UtcNow;
- _recordingProvider.Update(recording);
-
- _logger.Info("Beginning recording.");
-
try
{
- httpRequestOptions.BufferContent = false;
- var durationToken = new CancellationTokenSource(duration);
- var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
- httpRequestOptions.CancellationToken = linkedToken;
- _logger.Info("Writing file to path: " + recordPath);
- using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET"))
+ var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None);
+ var mediaStreamInfo = result.Item1;
+ var isResourceOpen = true;
+
+ // Unfortunately due to the semaphore we have to have a nested try/finally
+ try
+ {
+ // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg
+ await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
+
+ var duration = recordingEndDate - DateTime.UtcNow;
+
+ HttpRequestOptions httpRequestOptions = new HttpRequestOptions()
+ {
+ Url = mediaStreamInfo.Path
+ };
+
+ recording.Path = recordPath;
+ recording.Status = RecordingStatus.InProgress;
+ recording.DateLastUpdated = DateTime.UtcNow;
+ _recordingProvider.Update(recording);
+
+ _logger.Info("Beginning recording.");
+
+ httpRequestOptions.BufferContent = false;
+ var durationToken = new CancellationTokenSource(duration);
+ var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+ httpRequestOptions.CancellationToken = linkedToken;
+ _logger.Info("Writing file to path: " + recordPath);
+ using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET"))
+ {
+ using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read))
+ {
+ result.Item2.Release();
+ isResourceOpen = false;
+
+ await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken);
+ }
+ }
+
+ recording.Status = RecordingStatus.Completed;
+ _logger.Info("Recording completed");
+ }
+ finally
{
- using (var output = File.Open(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read))
+ if (isResourceOpen)
{
- await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken);
+ result.Item2.Release();
}
}
-
- recording.Status = RecordingStatus.Completed;
- _logger.Info("Recording completed");
}
catch (OperationCanceledException)
{
@@ -794,14 +885,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private string GetChannelEpgCachePath(string channelId)
{
- return Path.Combine(DataPath, "epg", channelId + ".json");
+ return Path.Combine(_config.CommonApplicationPaths.CachePath, "embytvepg", channelId + ".json");
}
private readonly object _epgLock = new object();
private void SaveEpgDataForChannel(string channelId, List<ProgramInfo> epgData)
{
var path = GetChannelEpgCachePath(channelId);
- Directory.CreateDirectory(Path.GetDirectoryName(path));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
lock (_epgLock)
{
_jsonSerializer.SerializeToFile(epgData, path);
@@ -848,4 +939,4 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
});
}
}
-}
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
index 75dec5f970..d89c1c0cb6 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
@@ -4,6 +4,8 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using CommonIO;
+using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
@@ -16,13 +18,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
protected readonly ILogger Logger;
private readonly string _dataPath;
protected readonly Func<T, T, bool> EqualityComparer;
+ private readonly IFileSystem _fileSystem;
- public ItemDataProvider(IJsonSerializer jsonSerializer, ILogger logger, string dataPath, Func<T, T, bool> equalityComparer)
+ public ItemDataProvider(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, Func<T, T, bool> equalityComparer)
{
Logger = logger;
_dataPath = dataPath;
EqualityComparer = equalityComparer;
_jsonSerializer = jsonSerializer;
+ _fileSystem = fileSystem;
}
public IReadOnlyList<T> GetAll()
@@ -69,7 +73,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private void UpdateList(List<T> newList)
{
var file = _dataPath + ".json";
- Directory.CreateDirectory(Path.GetDirectoryName(file));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(file));
lock (_fileDataLock)
{
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index 5b83d63b17..3ee808bb51 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
@@ -67,10 +67,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- else if (info.ProductionYear != null)
+ else if (info.IsMovie && info.ProductionYear != null)
{
name += " (" + info.ProductionYear + ")";
}
+ else
+ {
+ name += " " + info.StartDate.ToString("yyyy-MM-dd");
+ }
return name;
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
index eab278eb4d..6d88c7c0a5 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
@@ -2,13 +2,15 @@
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
+using CommonIO;
+using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
public class SeriesTimerManager : ItemDataProvider<SeriesTimerInfo>
{
- public SeriesTimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
- : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
+ public SeriesTimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
+ : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index 3ae38f382e..94ad5e5cec 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -7,6 +7,8 @@ using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
+using CommonIO;
+using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
@@ -16,8 +18,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
- public TimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
- : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
+ public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
+ : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/Emby/EmbyListings.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/Emby/EmbyListings.cs
index e446ff469f..ae441b44e0 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/Listings/Emby/EmbyListings.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/Emby/EmbyListings.cs
@@ -31,7 +31,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings.Emby
get { return "emby"; }
}
- public Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
+ public Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
{
return GetListingsProvider(info.Country).GetProgramsAsync(info, channelNumber, startDateUtc, endDateUtc, cancellationToken);
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 2efa911372..434578718a 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -60,7 +60,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
return dates;
}
- public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
+ public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
{
List<ProgramInfo> programsInfo = new List<ProgramInfo>();
@@ -82,19 +82,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
UserAgent = UserAgent,
CancellationToken = cancellationToken,
// The data can be large so give it some extra time
- TimeoutMs = 60000
+ TimeoutMs = 60000,
+ LogErrorResponseBody = true
};
httpOptions.RequestHeaders["token"] = token;
var dates = GetScheduleRequestDates(startDateUtc, endDateUtc);
- ScheduleDirect.Station station = null;
+ ScheduleDirect.Station station = GetStation(channelNumber, channelName);
- if (!_channelPair.TryGetValue(channelNumber, out station))
+ if (station == null)
{
+ _logger.Info("No Schedules Direct Station found for channel {0} with name {1}", channelNumber, channelName);
return programsInfo;
}
+
string stationID = station.stationID;
_logger.Info("Channel Station ID is: " + stationID);
@@ -122,7 +125,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
{
Url = ApiUrl + "/programs",
UserAgent = UserAgent,
- CancellationToken = cancellationToken
+ CancellationToken = cancellationToken,
+ LogErrorResponseBody = true
};
httpOptions.RequestHeaders["token"] = token;
@@ -152,10 +156,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
// schedule.programID + " which says it has images? " +
// programDict[schedule.programID].hasImageArtwork);
- var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
- if (imageIndex > -1)
+ if (images != null)
{
- programDict[schedule.programID].images = GetProgramLogo(ApiUrl, images[imageIndex]);
+ var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
+ if (imageIndex > -1)
+ {
+ programDict[schedule.programID].images = GetProgramLogo(ApiUrl, images[imageIndex]);
+ }
}
programsInfo.Add(GetProgram(channelNumber, schedule, programDict[schedule.programID]));
@@ -167,6 +174,30 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
return programsInfo;
}
+ private ScheduleDirect.Station GetStation(string channelNumber, string channelName)
+ {
+ ScheduleDirect.Station station;
+
+ if (_channelPair.TryGetValue(channelNumber, out station))
+ {
+ return station;
+ }
+
+ if (string.IsNullOrWhiteSpace(channelName))
+ {
+ return null;
+ }
+
+ channelName = NormalizeName(channelName);
+
+ return _channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase));
+ }
+
+ private string NormalizeName(string value)
+ {
+ return value.Replace(" ", string.Empty).Replace("-", string.Empty);
+ }
+
public async Task AddMetadata(ListingsProviderInfo info, List<ChannelInfo> channels,
CancellationToken cancellationToken)
{
@@ -188,7 +219,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
{
Url = ApiUrl + "/lineups/" + info.ListingsId,
UserAgent = UserAgent,
- CancellationToken = cancellationToken
+ CancellationToken = cancellationToken,
+ LogErrorResponseBody = true
};
httpOptions.RequestHeaders["token"] = token;
@@ -200,34 +232,42 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
_logger.Info("Mapping Stations to Channel");
foreach (ScheduleDirect.Map map in root.map)
{
- var channel = (map.channel ?? (map.atscMajor + "." + map.atscMinor)).TrimStart('0');
- _logger.Debug("Found channel: " + channel + " in Schedules Direct");
- var schChannel = root.stations.FirstOrDefault(item => item.stationID == map.stationID);
+ var channelNumber = map.logicalChannelNumber;
- if (!_channelPair.ContainsKey(channel) && channel != "0.0" && schChannel != null)
+ if (string.IsNullOrWhiteSpace(channelNumber))
+ {
+ channelNumber = map.channel;
+ }
+ if (string.IsNullOrWhiteSpace(channelNumber))
{
- _channelPair.TryAdd(channel, schChannel);
+ channelNumber = (map.atscMajor + "." + map.atscMinor);
}
+ channelNumber = channelNumber.TrimStart('0');
+
+ _logger.Debug("Found channel: " + channelNumber + " in Schedules Direct");
+ var schChannel = root.stations.FirstOrDefault(item => item.stationID == map.stationID);
+
+ _channelPair.TryAdd(channelNumber, schChannel);
}
- _logger.Info("Added " + _channelPair.Count() + " channels to the dictionary");
+ _logger.Info("Added " + _channelPair.Count + " channels to the dictionary");
foreach (ChannelInfo channel in channels)
{
- // Helper.logger.Info("Modifyin channel " + channel.Number);
- if (_channelPair.ContainsKey(channel.Number))
+ var station = GetStation(channel.Number, channel.Name);
+
+ if (station != null)
{
- if (_channelPair[channel.Number].logo != null)
+ if (station.logo != null)
{
- channel.ImageUrl = _channelPair[channel.Number].logo.URL;
+ channel.ImageUrl = station.logo.URL;
channel.HasImage = true;
}
- string channelName = _channelPair[channel.Number].name;
+ string channelName = station.name;
channel.Name = channelName;
}
else
{
- _logger.Info("Schedules Direct doesnt have data for channel: " + channel.Number + " " +
- channel.Name);
+ _logger.Info("Schedules Direct doesnt have data for channel: " + channel.Number + " " + channel.Name);
}
}
}
@@ -293,7 +333,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
IsRepeat = repeat,
IsSeries = showType.IndexOf("series", StringComparison.OrdinalIgnoreCase) != -1,
ImageUrl = imageUrl,
- HasImage = details.hasImageArtwork,
IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase),
IsSports = showType.IndexOf("sports", StringComparison.OrdinalIgnoreCase) != -1,
IsMovie = showType.IndexOf("movie", StringComparison.OrdinalIgnoreCase) != -1 || showType.IndexOf("film", StringComparison.OrdinalIgnoreCase) != -1,
@@ -362,13 +401,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
private DateTime GetDate(string value)
{
- return DateTime.ParseExact(value, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'",
- CultureInfo.InvariantCulture);
+ var date = DateTime.ParseExact(value, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'", CultureInfo.InvariantCulture);
+
+ if (date.Kind != DateTimeKind.Utc)
+ {
+ date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
+ }
+ return date;
}
private string GetProgramLogo(string apiUrl, ScheduleDirect.ShowImages images)
{
- string url = "";
+ string url = null;
if (images.data != null)
{
var smallImages = images.data.Where(i => i.size == "Sm").ToList();
@@ -381,13 +425,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
{
logoIndex = 0;
}
- if (images.data[logoIndex].uri.Contains("http"))
- {
- url = images.data[logoIndex].uri;
- }
- else
+ var uri = images.data[logoIndex].uri;
+
+ if (!string.IsNullOrWhiteSpace(uri))
{
- url = apiUrl + "/image/" + images.data[logoIndex].uri;
+ if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ url = uri;
+ }
+ else
+ {
+ url = apiUrl + "/image/" + uri;
+ }
}
//_logger.Debug("URL for image is : " + url);
}
@@ -413,7 +462,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
Url = ApiUrl + "/metadata/programs",
UserAgent = UserAgent,
CancellationToken = cancellationToken,
- RequestContent = imageIdString
+ RequestContent = imageIdString,
+ LogErrorResponseBody = true
};
List<ScheduleDirect.ShowImages> images;
using (var innerResponse2 = await _httpClient.Post(httpOptions))
@@ -440,7 +490,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
{
Url = ApiUrl + "/headends?country=" + country + "&postalcode=" + location,
UserAgent = UserAgent,
- CancellationToken = cancellationToken
+ CancellationToken = cancellationToken,
+ LogErrorResponseBody = true
};
options.RequestHeaders["token"] = token;
@@ -557,7 +608,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
Url = ApiUrl + "/token",
UserAgent = UserAgent,
RequestContent = "{\"username\":\"" + username + "\",\"password\":\"" + password + "\"}",
- CancellationToken = cancellationToken
+ CancellationToken = cancellationToken,
+ LogErrorResponseBody = true
};
//_logger.Info("Obtaining token from Schedules Direct from addres: " + httpOptions.Url + " with body " +
// httpOptions.RequestContent);
@@ -595,7 +647,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
{
Url = ApiUrl + "/lineups/" + info.ListingsId,
UserAgent = UserAgent,
- CancellationToken = cancellationToken
+ CancellationToken = cancellationToken,
+ LogErrorResponseBody = true
};
httpOptions.RequestHeaders["token"] = token;
@@ -635,7 +688,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
{
Url = ApiUrl + "/lineups",
UserAgent = UserAgent,
- CancellationToken = cancellationToken
+ CancellationToken = cancellationToken,
+ LogErrorResponseBody = true
};
options.RequestHeaders["token"] = token;
@@ -736,6 +790,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
{
public string stationID { get; set; }
public string channel { get; set; }
+ public string logicalChannelNumber { get; set; }
public int uhfVhf { get; set; }
public int atscMajor { get; set; }
public int atscMinor { get; set; }
diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTv.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTv.cs
index de107ced80..ac316f9a12 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTv.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/XmlTv.cs
@@ -20,7 +20,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
get { return "xmltv"; }
}
- public Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
+ public Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, string channelName, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index 7a26bf87c6..edfca0d6c4 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -29,6 +29,7 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using CommonIO;
namespace MediaBrowser.Server.Implementations.LiveTv
{
@@ -553,8 +554,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
isNew = true;
}
- item.ChannelType = channelInfo.ChannelType;
+ if (!string.Equals(channelInfo.Id, item.ExternalId))
+ {
+ isNew = true;
+ }
item.ExternalId = channelInfo.Id;
+
+ item.ChannelType = channelInfo.ChannelType;
item.ServiceName = serviceName;
item.Number = channelInfo.Number;
@@ -571,16 +577,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv
// replaceImages.Add(ImageType.Primary);
//}
- item.ProviderImageUrl = channelInfo.ImageUrl;
- item.HasProviderImage = channelInfo.HasImage;
- item.ProviderImagePath = channelInfo.ImagePath;
+ if (!string.IsNullOrWhiteSpace(channelInfo.ImagePath))
+ {
+ item.SetImagePath(ImageType.Primary, channelInfo.ImagePath);
+ }
+ else if (!string.IsNullOrWhiteSpace(channelInfo.ImageUrl))
+ {
+ item.SetImagePath(ImageType.Primary, channelInfo.ImageUrl);
+ }
if (string.IsNullOrEmpty(item.Name))
{
item.Name = channelInfo.Name;
}
- await item.RefreshMetadata(new MetadataRefreshOptions
+ await item.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
{
ForceSave = isNew,
ReplaceImages = replaceImages.Distinct().ToList()
@@ -606,7 +617,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
Id = id,
DateCreated = DateTime.UtcNow,
DateModified = DateTime.UtcNow,
- Etag = info.Etag
+ ExternalEtag = info.Etag
};
}
@@ -620,7 +631,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.EpisodeTitle = info.EpisodeTitle;
item.ExternalId = info.Id;
item.Genres = info.Genres;
- item.HasProviderImage = info.HasImage;
item.IsHD = info.IsHD;
item.IsKids = info.IsKids;
item.IsLive = info.IsLive;
@@ -633,33 +643,46 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.Name = info.Name;
item.OfficialRating = item.OfficialRating ?? info.OfficialRating;
item.Overview = item.Overview ?? info.Overview;
- item.OriginalAirDate = info.OriginalAirDate;
- item.ProviderImagePath = info.ImagePath;
- item.ProviderImageUrl = info.ImageUrl;
item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
item.StartDate = info.StartDate;
item.HomePageUrl = info.HomePageUrl;
item.ProductionYear = info.ProductionYear;
- item.PremiereDate = item.PremiereDate ?? info.OriginalAirDate;
+ item.PremiereDate = info.OriginalAirDate;
item.IndexNumber = info.EpisodeNumber;
item.ParentIndexNumber = info.SeasonNumber;
+ if (!string.IsNullOrWhiteSpace(info.ImagePath))
+ {
+ item.SetImagePath(ImageType.Primary, info.ImagePath);
+ }
+ else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
+ {
+ item.SetImagePath(ImageType.Primary, info.ImageUrl);
+ }
+
if (isNew)
{
await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
}
+ else if (string.IsNullOrWhiteSpace(info.Etag))
+ {
+ await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
+ }
else
{
- if (string.IsNullOrWhiteSpace(info.Etag) || !string.Equals(info.Etag, item.Etag, StringComparison.OrdinalIgnoreCase))
+ // Increment this whenver some internal change deems it necessary
+ var etag = info.Etag + "4";
+
+ if (!string.Equals(etag, item.ExternalEtag, StringComparison.OrdinalIgnoreCase))
{
- item.Etag = info.Etag;
+ item.ExternalEtag = etag;
await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
}
}
- _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions());
+ _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem));
return item;
}
@@ -705,18 +728,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv
item.Overview = info.Overview;
item.EndDate = info.EndDate;
item.Genres = info.Genres;
+ item.PremiereDate = info.OriginalAirDate;
var recording = (ILiveTvRecording)item;
recording.ExternalId = info.Id;
- recording.ProgramId = _tvDtoService.GetInternalProgramId(serviceName, info.ProgramId).ToString("N");
recording.Audio = info.Audio;
- recording.ChannelType = info.ChannelType;
recording.EndDate = info.EndDate;
recording.EpisodeTitle = info.EpisodeTitle;
- recording.ProviderImagePath = info.ImagePath;
- recording.ProviderImageUrl = info.ImageUrl;
recording.IsHD = info.IsHD;
recording.IsKids = info.IsKids;
recording.IsLive = info.IsLive;
@@ -726,40 +746,57 @@ namespace MediaBrowser.Server.Implementations.LiveTv
recording.IsRepeat = info.IsRepeat;
recording.IsSeries = info.IsSeries;
recording.IsSports = info.IsSports;
- recording.OriginalAirDate = info.OriginalAirDate;
recording.SeriesTimerId = info.SeriesTimerId;
recording.StartDate = info.StartDate;
+
+ if (!string.IsNullOrWhiteSpace(info.ImagePath))
+ {
+ item.SetImagePath(ImageType.Primary, info.ImagePath);
+ }
+ else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
+ {
+ item.SetImagePath(ImageType.Primary, info.ImageUrl);
+ }
+
+ var statusChanged = info.Status != recording.Status;
+
recording.Status = info.Status;
recording.ServiceName = serviceName;
- var originalPath = item.Path;
+ var pathChanged = false;
if (!string.IsNullOrEmpty(info.Path))
{
- item.Path = info.Path;
- var fileInfo = new FileInfo(info.Path);
+ pathChanged = !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))
{
+ pathChanged = !string.Equals(item.Path, info.Url);
item.Path = info.Url;
}
- var pathChanged = !string.Equals(originalPath, item.Path);
+ var metadataRefreshMode = MetadataRefreshMode.Default;
if (isNew)
{
await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
}
- else if (pathChanged || info.DateLastUpdated > recording.DateLastSaved)
+ else if (pathChanged || info.DateLastUpdated > recording.DateLastSaved || statusChanged)
{
+ metadataRefreshMode = MetadataRefreshMode.FullRefresh;
await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
}
- _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions());
+ _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(_fileSystem)
+ {
+ MetadataRefreshMode = metadataRefreshMode
+ });
return item.Id;
}
@@ -1163,6 +1200,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
foreach (var program in channelPrograms)
{
var programItem = await GetProgram(program, channelId, currentChannel.ChannelType, service.Name, cancellationToken).ConfigureAwait(false);
+
programs.Add(programItem.Id);
}
}
@@ -1200,6 +1238,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
cancellationToken.ThrowIfCancellationRequested();
+ if (itemId == Guid.Empty)
+ {
+ // Somehow some invalid data got into the db. It probably predates the boundary checking
+ continue;
+ }
+
if (!currentIdList.Contains(itemId))
{
var item = _libraryManager.GetItemById(itemId);
@@ -1286,7 +1330,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var idList = await Task.WhenAll(recordingTasks).ConfigureAwait(false);
- CleanDatabaseInternal(idList.ToList(), new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }, new Progress<double>(), cancellationToken).ConfigureAwait(false);
+ await CleanDatabaseInternal(idList.ToList(), new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }, new Progress<double>(), cancellationToken).ConfigureAwait(false);
_lastRecordingRefreshTime = DateTime.UtcNow;
}
@@ -1382,25 +1426,25 @@ namespace MediaBrowser.Server.Implementations.LiveTv
};
}
- public void AddInfoToProgramDto(BaseItem item, BaseItemDto dto, User user = null)
+ public void AddInfoToProgramDto(BaseItem item, BaseItemDto dto, bool addChannelInfo, User user = null)
{
var program = (LiveTvProgram)item;
var service = GetService(program);
- var channel = GetInternalChannel(program.ChannelId);
-
dto.Id = _tvDtoService.GetInternalProgramId(service.Name, program.ExternalId).ToString("N");
dto.StartDate = program.StartDate;
- dto.IsRepeat = program.IsRepeat;
dto.EpisodeTitle = program.EpisodeTitle;
- dto.ChannelType = program.ChannelType;
dto.Audio = program.Audio;
if (program.IsHD.HasValue && program.IsHD.Value)
{
dto.IsHD = program.IsHD;
}
+ if (program.IsRepeat)
+ {
+ dto.IsRepeat = program.IsRepeat;
+ }
if (program.IsMovie)
{
dto.IsMovie = program.IsMovie;
@@ -1430,15 +1474,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.IsPremiere = program.IsPremiere;
}
- dto.OriginalAirDate = program.OriginalAirDate;
-
- if (channel != null)
+ if (addChannelInfo)
{
- dto.ChannelName = channel.Name;
+ var channel = GetInternalChannel(program.ChannelId);
- if (!string.IsNullOrEmpty(channel.PrimaryImagePath))
+ if (channel != null)
{
- dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
+ dto.ChannelName = channel.Name;
+
+ if (channel.HasImage(ImageType.Primary))
+ {
+ dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
+ }
}
}
}
@@ -1461,7 +1508,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.RecordingStatus = info.Status;
dto.IsRepeat = info.IsRepeat;
dto.EpisodeTitle = info.EpisodeTitle;
- dto.ChannelType = info.ChannelType;
dto.Audio = info.Audio;
dto.IsHD = info.IsHD;
dto.IsMovie = info.IsMovie;
@@ -1471,7 +1517,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.IsNews = info.IsNews;
dto.IsKids = info.IsKids;
dto.IsPremiere = info.IsPremiere;
- dto.OriginalAirDate = info.OriginalAirDate;
dto.CanDelete = user == null
? recording.CanDelete()
@@ -1499,13 +1544,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.CompletionPercentage = pct;
}
- dto.ProgramId = info.ProgramId;
-
if (channel != null)
{
dto.ChannelName = channel.Name;
- if (!string.IsNullOrEmpty(channel.PrimaryImagePath))
+ if (channel.HasImage(ImageType.Primary))
{
dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
}
@@ -1601,7 +1644,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var service = GetService(recording.ServiceName);
- await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false);
+ try
+ {
+ await service.DeleteRecordingAsync(recording.ExternalId, CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (ResourceNotFoundException)
+ {
+
+ }
+
+ await _libraryManager.DeleteItem((BaseItem)recording).ConfigureAwait(false);
+
_lastRecordingRefreshTime = DateTime.MinValue;
}
@@ -1787,7 +1840,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
EndDate = program.EndDate ?? DateTime.MinValue,
EpisodeTitle = program.EpisodeTitle,
Genres = program.Genres,
- HasImage = program.HasProviderImage,
Id = program.ExternalId,
IsHD = program.IsHD,
IsKids = program.IsKids,
@@ -1801,8 +1853,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
OriginalAirDate = program.PremiereDate,
Overview = program.Overview,
StartDate = program.StartDate,
- ImagePath = program.ProviderImagePath,
- ImageUrl = program.ProviderImageUrl,
+ //ImagePath = program.ExternalImagePath,
Name = program.Name,
OfficialRating = program.OfficialRating
};
@@ -2352,4 +2403,4 @@ namespace MediaBrowser.Server.Implementations.LiveTv
});
}
}
-}
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs
index 134e24ef0e..ab8ec720b2 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs
@@ -1,9 +1,7 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
+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;
@@ -15,14 +13,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public class ProgramImageProvider : IDynamicImageProvider, IHasItemChangeMonitor, IHasOrder
{
private readonly ILiveTvManager _liveTvManager;
- private readonly IHttpClient _httpClient;
- private readonly ILogger _logger;
- public ProgramImageProvider(ILiveTvManager liveTvManager, IHttpClient httpClient, ILogger logger)
+ public ProgramImageProvider(ILiveTvManager liveTvManager)
{
_liveTvManager = liveTvManager;
- _httpClient = httpClient;
- _logger = logger;
}
public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
@@ -36,55 +30,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var imageResponse = new DynamicImageResponse();
- if (!string.IsNullOrEmpty(liveTvItem.ProviderImagePath))
- {
- imageResponse.Path = liveTvItem.ProviderImagePath;
- imageResponse.HasImage = true;
- }
- else if (!string.IsNullOrEmpty(liveTvItem.ProviderImageUrl))
- {
- var options = new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = liveTvItem.ProviderImageUrl
- };
+ var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
- var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
-
- if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
- {
- imageResponse.HasImage = true;
- imageResponse.Stream = response.Content;
- imageResponse.SetFormatFromMimeType(response.ContentType);
- }
- else
- {
- _logger.Error("Provider did not return an image content type.");
- }
- }
- else if (liveTvItem.HasProviderImage ?? true)
+ if (service != null)
{
- var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
-
- if (service != null)
+ try
{
- try
- {
- var channel = _liveTvManager.GetInternalChannel(liveTvItem.ChannelId);
+ var channel = _liveTvManager.GetInternalChannel(liveTvItem.ChannelId);
- var response = await service.GetProgramImageAsync(liveTvItem.ExternalId, channel.ExternalId, cancellationToken).ConfigureAwait(false);
+ var response = await service.GetProgramImageAsync(liveTvItem.ExternalId, channel.ExternalId, cancellationToken).ConfigureAwait(false);
- if (response != null)
- {
- imageResponse.HasImage = true;
- imageResponse.Stream = response.Stream;
- imageResponse.Format = response.Format;
- }
- }
- catch (NotImplementedException)
+ if (response != null)
{
+ imageResponse.HasImage = true;
+ imageResponse.Stream = response.Stream;
+ imageResponse.Format = response.Format;
}
}
+ catch (NotImplementedException)
+ {
+ }
}
return imageResponse;
@@ -115,7 +80,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
if (liveTvItem != null)
{
- return !liveTvItem.HasImage(ImageType.Primary) && (liveTvItem.HasProviderImage ?? true);
+ return !liveTvItem.HasImage(ImageType.Primary);
}
return false;
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs
index adf1e75169..fce3223ea9 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs
@@ -1,9 +1,7 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
+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;
@@ -15,14 +13,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public class RecordingImageProvider : IDynamicImageProvider, IHasItemChangeMonitor
{
private readonly ILiveTvManager _liveTvManager;
- private readonly IHttpClient _httpClient;
- private readonly ILogger _logger;
- public RecordingImageProvider(ILiveTvManager liveTvManager, IHttpClient httpClient, ILogger logger)
+ public RecordingImageProvider(ILiveTvManager liveTvManager)
{
_liveTvManager = liveTvManager;
- _httpClient = httpClient;
- _logger = logger;
}
public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
@@ -36,53 +30,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var imageResponse = new DynamicImageResponse();
- if (!string.IsNullOrEmpty(liveTvItem.ProviderImagePath))
- {
- imageResponse.Path = liveTvItem.ProviderImagePath;
- imageResponse.HasImage = true;
- }
- else if (!string.IsNullOrEmpty(liveTvItem.ProviderImageUrl))
- {
- var options = new HttpRequestOptions
- {
- CancellationToken = cancellationToken,
- Url = liveTvItem.ProviderImageUrl
- };
+ var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
- var response = await _httpClient.GetResponse(options).ConfigureAwait(false);
-
- if (response.ContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
- {
- imageResponse.HasImage = true;
- imageResponse.Stream = response.Content;
- imageResponse.SetFormatFromMimeType(response.ContentType);
- }
- else
- {
- _logger.Error("Provider did not return an image content type.");
- }
- }
- else
+ if (service != null)
{
- var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, liveTvItem.ServiceName, StringComparison.OrdinalIgnoreCase));
-
- if (service != null)
+ try
{
- try
- {
- var response = await service.GetRecordingImageAsync(liveTvItem.ExternalId, cancellationToken).ConfigureAwait(false);
+ 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)
+ if (response != null)
{
+ imageResponse.HasImage = true;
+ imageResponse.Stream = response.Stream;
+ imageResponse.Format = response.Format;
}
}
+ catch (NotImplementedException)
+ {
+ }
}
return imageResponse;
@@ -109,7 +74,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
if (liveTvItem != null)
{
- return !liveTvItem.HasImage(ImageType.Primary) && (!string.IsNullOrWhiteSpace(liveTvItem.ProviderImagePath) || !string.IsNullOrWhiteSpace(liveTvItem.ProviderImageUrl));
+ return !liveTvItem.HasImage(ImageType.Primary);
}
return false;
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs b/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
index d8d91c2f97..3fb1d96614 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/RefreshChannelsScheduledTask.cs
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.LiveTv
{
- class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask, IHasKey
+ public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask, IHasKey
{
private readonly ILiveTvManager _liveTvManager;
private readonly IConfigurationManager _config;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 909e2bba57..d811152c26 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -9,6 +9,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
@@ -16,14 +19,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
protected readonly IConfigurationManager Config;
protected readonly ILogger Logger;
+ protected IJsonSerializer JsonSerializer;
+ protected readonly IMediaEncoder MediaEncoder;
private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase);
- public BaseTunerHost(IConfigurationManager config, ILogger logger)
+ protected BaseTunerHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder)
{
Config = config;
Logger = logger;
+ JsonSerializer = jsonSerializer;
+ MediaEncoder = mediaEncoder;
}
protected abstract Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken);
@@ -44,8 +51,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
var result = await GetChannelsInternal(tuner, cancellationToken).ConfigureAwait(false);
var list = result.ToList();
+ Logger.Debug("Channels from {0}: {1}", tuner.Url, JsonSerializer.SerializeToString(list));
- if (!string.IsNullOrWhiteSpace(key))
+ if (!string.IsNullOrWhiteSpace(key) && list.Count > 0)
{
cache = cache ?? new ChannelCache();
cache.Date = DateTime.UtcNow;
@@ -109,23 +117,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
foreach (var host in hostsWithChannel)
{
- // Check to make sure the tuner is available
- // If there's only one tuner, don't bother with the check and just let the tuner be the one to throw an error
- if (hostsWithChannel.Count > 1 && !await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false))
+ try
{
- Logger.Error("Tuner is not currently available");
- continue;
- }
+ var mediaSources = await GetChannelStreamMediaSources(host, channelId, cancellationToken).ConfigureAwait(false);
- 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;
+ }
- // Prefix the id with the host Id so that we can easily find it
- foreach (var mediaSource in mediaSources)
+ return mediaSources;
+ }
+ catch (Exception ex)
{
- mediaSource.Id = host.Id + mediaSource.Id;
+ Logger.Error("Error opening tuner", ex);
}
-
- return mediaSources;
}
}
@@ -134,7 +141,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
protected abstract Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken);
- public async Task<MediaSourceInfo> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
+ public async Task<Tuple<MediaSourceInfo, SemaphoreSlim>> GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken)
{
if (IsValidChannelId(channelId))
{
@@ -163,23 +170,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
foreach (var host in hostsWithChannel)
{
- // Check to make sure the tuner is available
- // If there's only one tuner, don't bother with the check and just let the tuner be the one to throw an error
- // If a streamId is specified then availibility has already been checked in GetChannelStreamMediaSources
- if (string.IsNullOrWhiteSpace(streamId) && hostsWithChannel.Count > 1)
+ try
{
- if (!await IsAvailable(host, channelId, cancellationToken).ConfigureAwait(false))
- {
- Logger.Error("Tuner is not currently available");
- continue;
- }
- }
-
- var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
+ var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
+ var resourcePool = GetLock(host.Url);
- if (stream != null)
+ await AddMediaInfo(stream, false, resourcePool, cancellationToken).ConfigureAwait(false);
+ return new Tuple<MediaSourceInfo, SemaphoreSlim>(stream, resourcePool);
+ }
+ catch (Exception ex)
{
- return stream;
+ Logger.Error("Error opening tuner", ex);
}
}
}
@@ -187,20 +188,116 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
throw new LiveTvConflictException();
}
- protected async Task<bool> IsAvailable(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
+ /// <summary>
+ /// The _semaphoreLocks
+ /// </summary>
+ private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphoreLocks = new ConcurrentDictionary<string, SemaphoreSlim>(StringComparer.OrdinalIgnoreCase);
+ /// <summary>
+ /// Gets the lock.
+ /// </summary>
+ /// <param name="url">The filename.</param>
+ /// <returns>System.Object.</returns>
+ private SemaphoreSlim GetLock(string url)
+ {
+ return _semaphoreLocks.GetOrAdd(url, key => new SemaphoreSlim(1, 1));
+ }
+
+ private async Task AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
{
+ await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
try
{
- return await IsAvailableInternal(tuner, channelId, cancellationToken).ConfigureAwait(false);
+ await AddMediaInfoInternal(mediaSource, isAudio, cancellationToken).ConfigureAwait(false);
+
+ // Leave the resource locked. it will be released upstream
}
- catch (Exception ex)
+ catch (Exception)
{
- Logger.ErrorException("Error checking tuner availability", ex);
- return false;
+ // Release the resource if there's some kind of failure.
+ resourcePool.Release();
+
+ throw;
}
}
- protected abstract Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken);
+ private async Task AddMediaInfoInternal(MediaSourceInfo mediaSource, bool isAudio, CancellationToken cancellationToken)
+ {
+ var originalRuntime = mediaSource.RunTimeTicks;
+
+ var info = await MediaEncoder.GetMediaInfo(new MediaInfoRequest
+ {
+ InputPath = mediaSource.Path,
+ Protocol = mediaSource.Protocol,
+ MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
+ ExtractChapters = false
+
+ }, cancellationToken).ConfigureAwait(false);
+
+ 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 == 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 == Model.Entities.MediaStreamType.Video);
+ if (videoStream != null)
+ {
+ if (!videoStream.BitRate.HasValue)
+ {
+ var width = videoStream.Width ?? 1920;
+
+ if (width >= 1900)
+ {
+ videoStream.BitRate = 8000000;
+ }
+
+ else if (width >= 1260)
+ {
+ videoStream.BitRate = 3000000;
+ }
+
+ else if (width >= 700)
+ {
+ videoStream.BitRate = 1000000;
+ }
+ }
+ }
+
+ // Try to estimate this
+ if (!mediaSource.Bitrate.HasValue)
+ {
+ var total = mediaSource.MediaStreams.Select(i => i.BitRate ?? 0).Sum();
+
+ if (total > 0)
+ {
+ mediaSource.Bitrate = total;
+ }
+ }
+ }
protected abstract bool IsValidChannelId(string channelId);
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
index c742201371..92a33993a9 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
@@ -9,6 +9,7 @@ using MediaBrowser.Model.Logging;
using System;
using System.Linq;
using System.Threading;
+using MediaBrowser.Common.Net;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
@@ -19,13 +20,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly ILogger _logger;
private readonly ILiveTvManager _liveTvManager;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
+ private readonly IHttpClient _httpClient;
- public HdHomerunDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager)
+ public HdHomerunDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient)
{
_deviceDiscovery = deviceDiscovery;
_config = config;
_logger = logger;
_liveTvManager = liveTvManager;
+ _httpClient = httpClient;
}
public void Run()
@@ -75,6 +78,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
// Strip off the port
url = new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped).TrimEnd('/');
+ // Test it by pulling down the lineup
+ using (await _httpClient.Get(new HttpRequestOptions
+ {
+ Url = string.Format("{0}/lineup.json", url),
+ CancellationToken = CancellationToken.None
+ }))
+ {
+ }
+
await _liveTvManager.SaveTunerHost(new TunerHostInfo
{
Type = HdHomerunHost.DeviceType,
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index bccb0db0a0..eebb381af8 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -14,19 +14,18 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public class HdHomerunHost : BaseTunerHost, ITunerHost
{
private readonly IHttpClient _httpClient;
- private readonly IJsonSerializer _jsonSerializer;
- public HdHomerunHost(IConfigurationManager config, ILogger logger, IHttpClient httpClient, IJsonSerializer jsonSerializer)
- : base(config, logger)
+ public HdHomerunHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IHttpClient httpClient) : base(config, logger, jsonSerializer, mediaEncoder)
{
_httpClient = httpClient;
- _jsonSerializer = jsonSerializer;
}
public string Name
@@ -55,7 +54,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
};
using (var stream = await _httpClient.Get(options))
{
- var root = _jsonSerializer.DeserializeFromStream<List<Channels>>(stream);
+ var root = JsonSerializer.DeserializeFromStream<List<Channels>>(stream);
if (root != null)
{
@@ -88,7 +87,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
Url = string.Format("{0}/", GetApiUrl(info, false)),
CancellationToken = cancellationToken,
CacheLength = TimeSpan.FromDays(1),
- CacheMode = CacheMode.Unconditional
+ CacheMode = CacheMode.Unconditional,
+ TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
}))
{
using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
@@ -103,7 +103,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- return null;
+ return model;
}
public async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
@@ -113,7 +113,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
using (var stream = await _httpClient.Get(new HttpRequestOptions()
{
Url = string.Format("{0}/tuners.html", GetApiUrl(info, false)),
- CancellationToken = cancellationToken
+ CancellationToken = cancellationToken,
+ TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
}))
{
var tuners = new List<LiveTvTunerInfo>();
@@ -325,11 +326,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
BitRate = 128000
}
},
- RequiresOpening = false,
- RequiresClosing = false,
+ RequiresOpening = true,
+ RequiresClosing = true,
BufferMs = 1000,
Container = "ts",
- Id = profile
+ Id = profile,
+ SupportsDirectPlay = true,
+ SupportsDirectStream = true,
+ SupportsTranscoding = true
};
return mediaSource;
@@ -372,16 +376,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
protected override bool IsValidChannelId(string channelId)
{
+ if (string.IsNullOrWhiteSpace(channelId))
+ {
+ throw new ArgumentNullException("channelId");
+ }
+
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo info, string channelId, string streamId, CancellationToken cancellationToken)
{
- Logger.Debug("GetChannelStream: channel id: {0}. stream id: {1}", channelId, streamId ?? string.Empty);
+ Logger.Info("GetChannelStream: channel id: {0}. stream id: {1}", channelId, streamId ?? string.Empty);
if (!channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase))
{
- return null;
+ throw new ArgumentException("Channel not found");
}
channelId = channelId.Substring(ChannelIdPrefix.Length);
@@ -395,12 +404,5 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
await GetChannels(info, false, CancellationToken.None).ConfigureAwait(false);
}
}
-
- 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 || string.Equals(i.ChannelId, channelId, StringComparison.OrdinalIgnoreCase));
- }
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 3783e4b089..afd41e3d8d 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -12,14 +12,23 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using CommonIO;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
public class M3UTunerHost : BaseTunerHost, ITunerHost
{
- public M3UTunerHost(IConfigurationManager config, ILogger logger)
- : base(config, logger)
+ private readonly IFileSystem _fileSystem;
+ private readonly IHttpClient _httpClient;
+
+ public M3UTunerHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient) : base(config, logger, jsonSerializer, mediaEncoder)
{
+ _fileSystem = fileSystem;
+ _httpClient = httpClient;
}
public override string Type
@@ -41,47 +50,48 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
string line;
// Read the file and display it line by line.
- var file = new StreamReader(url);
- var channels = new List<M3UChannel>();
+ using (var file = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false)))
+ {
+ var channels = new List<M3UChannel>();
- string channnelName = null;
- string channelNumber = null;
+ string channnelName = null;
+ string channelNumber = null;
- while ((line = file.ReadLine()) != null)
- {
- line = line.Trim();
- if (string.IsNullOrWhiteSpace(line))
+ while ((line = file.ReadLine()) != null)
{
- continue;
- }
+ line = line.Trim();
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
- if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
+ if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
- if (line.StartsWith("#EXTINF:", StringComparison.OrdinalIgnoreCase))
- {
- var parts = line.Split(new[] { ':' }, 2).Last().Split(new[] { ',' }, 2);
- channelNumber = parts[0];
- channnelName = parts[1];
- }
- else if (!string.IsNullOrWhiteSpace(channelNumber))
- {
- channels.Add(new M3UChannel
+ if (line.StartsWith("#EXTINF:", StringComparison.OrdinalIgnoreCase))
{
- Name = channnelName,
- Number = channelNumber,
- Id = ChannelIdPrefix + urlHash + channelNumber,
- Path = line
- });
-
- channelNumber = null;
- channnelName = null;
+ var parts = line.Split(new[] { ':' }, 2).Last().Split(new[] { ',' }, 2);
+ channelNumber = parts[0];
+ channnelName = parts[1];
+ }
+ else if (!string.IsNullOrWhiteSpace(channelNumber))
+ {
+ channels.Add(new M3UChannel
+ {
+ Name = channnelName,
+ Number = channelNumber,
+ Id = ChannelIdPrefix + urlHash + channelNumber,
+ Path = line
+ });
+
+ channelNumber = null;
+ channnelName = null;
+ }
}
+ return channels;
}
- file.Close();
- return channels;
}
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
@@ -119,9 +129,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
public async Task Validate(TunerHostInfo info)
{
- if (!File.Exists(info.Url))
+ using (var stream = await GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
{
- throw new FileNotFoundException();
+
}
}
@@ -130,6 +140,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
+ private Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
+ {
+ if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ {
+ return _httpClient.Get(info.Url, cancellationToken);
+ }
+ return Task.FromResult(_fileSystem.OpenRead(info.Url));
+ }
+
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
{
var urlHash = info.Url.GetMD5().ToString("N");
@@ -190,10 +209,5 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
}
return new List<MediaSourceInfo> { };
}
-
- protected override Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
- {
- return Task.FromResult(true);
- }
}
}