diff options
| author | Luke <luke.pulverenti@gmail.com> | 2015-10-26 18:50:19 -0400 |
|---|---|---|
| committer | Luke <luke.pulverenti@gmail.com> | 2015-10-26 18:50:19 -0400 |
| commit | 35778ebc02e5931142a1fe31a256b7488a07c5c2 (patch) | |
| tree | ced0290be8820f5e507b51ca4c5165212b1879d1 /MediaBrowser.Server.Implementations/LiveTv | |
| parent | c0dc8d055bfd4d2f58591083beb9e9128357aad6 (diff) | |
| parent | 8d77308593c3b16b733b0109323770d9dfe7e166 (diff) | |
Merge pull request #1222 from MediaBrowser/dev
3.0.5768.7
Diffstat (limited to 'MediaBrowser.Server.Implementations/LiveTv')
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); - } } } |
