aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs')
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs262
1 files changed, 239 insertions, 23 deletions
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index 3bc146bd4..b53b3b651 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -1,17 +1,21 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -23,30 +27,37 @@ namespace MediaBrowser.Server.Implementations.LiveTv
/// <summary>
/// Class LiveTvManager
/// </summary>
- public class LiveTvManager : ILiveTvManager
+ public class LiveTvManager : ILiveTvManager, IDisposable
{
- private readonly IServerApplicationPaths _appPaths;
+ private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
private readonly IUserManager _userManager;
+ private readonly IUserDataManager _userDataManager;
private readonly ILibraryManager _libraryManager;
+ private readonly IMediaEncoder _mediaEncoder;
private readonly LiveTvDtoService _tvDtoService;
private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
+ private readonly ConcurrentDictionary<string, LiveStreamInfo> _openStreams =
+ new ConcurrentDictionary<string, LiveStreamInfo>();
+
private List<Guid> _channelIdList = new List<Guid>();
private Dictionary<Guid, LiveTvProgram> _programs = new Dictionary<Guid, LiveTvProgram>();
- public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager)
+ public LiveTvManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, IMediaEncoder mediaEncoder)
{
- _appPaths = appPaths;
+ _config = config;
_fileSystem = fileSystem;
_logger = logger;
_itemRepo = itemRepo;
_userManager = userManager;
_libraryManager = libraryManager;
+ _mediaEncoder = mediaEncoder;
+ _userDataManager = userDataManager;
_tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger, _itemRepo);
}
@@ -180,7 +191,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var recording = recordings.First(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id));
- return await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false);
+ var result = await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false);
+
+ if (!string.IsNullOrEmpty(result.Id))
+ {
+ _openStreams.AddOrUpdate(result.Id, result, (key, info) => result);
+ }
+
+ return result;
}
public async Task<LiveStreamInfo> GetChannelStream(string id, CancellationToken cancellationToken)
@@ -189,12 +207,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var channel = GetInternalChannel(id);
- return await service.GetChannelStream(channel.ChannelInfo.Id, cancellationToken).ConfigureAwait(false);
+ var result = await service.GetChannelStream(channel.ChannelInfo.Id, cancellationToken).ConfigureAwait(false);
+
+ if (!string.IsNullOrEmpty(result.Id))
+ {
+ _openStreams.AddOrUpdate(result.Id, result, (key, info) => result);
+ }
+
+ return result;
}
private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
{
- var path = Path.Combine(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(serviceName), _fileSystem.GetValidFilename(channelInfo.Name));
+ var path = Path.Combine(_config.ApplicationPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(channelInfo.Name));
var fileInfo = new DirectoryInfo(path);
@@ -407,7 +432,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
var returnArray = programs
- .OrderBy(i => i.ProgramInfo.StartDate)
.Select(i =>
{
var channel = GetChannel(i);
@@ -429,6 +453,138 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return result;
}
+ public async Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query, CancellationToken cancellationToken)
+ {
+ IEnumerable<LiveTvProgram> programs = _programs.Values;
+
+ var user = _userManager.GetUserById(new Guid(query.UserId));
+
+ // Avoid implicitly captured closure
+ var currentUser = user;
+ programs = programs.Where(i => i.IsParentalAllowed(currentUser));
+
+ if (query.IsAiring.HasValue)
+ {
+ var val = query.IsAiring.Value;
+ programs = programs.Where(i => i.IsAiring == val);
+ }
+
+ if (query.HasAired.HasValue)
+ {
+ var val = query.HasAired.Value;
+ programs = programs.Where(i => i.HasAired == val);
+ }
+
+ var serviceName = ActiveService.Name;
+
+ var programList = programs.ToList();
+
+ var genres = programList.SelectMany(i => i.Genres)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .Select(i => _libraryManager.GetGenre(i))
+ .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
+
+ programs = programList.OrderByDescending(i => GetRecommendationScore(i.ProgramInfo, user.Id, serviceName, genres))
+ .ThenBy(i => i.ProgramInfo.StartDate);
+
+ if (query.Limit.HasValue)
+ {
+ programs = programs.Take(query.Limit.Value)
+ .OrderBy(i => i.ProgramInfo.StartDate);
+ }
+
+ var returnArray = programs
+ .Select(i =>
+ {
+ var channel = GetChannel(i);
+
+ var channelName = channel == null ? null : channel.ChannelInfo.Name;
+
+ return _tvDtoService.GetProgramInfoDto(i, channelName, user);
+ })
+ .ToArray();
+
+ await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false);
+
+ var result = new QueryResult<ProgramInfoDto>
+ {
+ Items = returnArray,
+ TotalRecordCount = returnArray.Length
+ };
+
+ return result;
+ }
+
+ private int GetRecommendationScore(ProgramInfo program, Guid userId, string serviceName, Dictionary<string, Genre> genres)
+ {
+ var score = 0;
+
+ if (program.IsLive)
+ {
+ score++;
+ }
+
+ if (program.IsSeries && !program.IsRepeat)
+ {
+ score++;
+ }
+
+ var internalChannelId = _tvDtoService.GetInternalChannelId(serviceName, program.ChannelId);
+ var channel = GetInternalChannel(internalChannelId);
+
+ var channelUserdata = _userDataManager.GetUserData(userId, channel.GetUserDataKey());
+
+ if ((channelUserdata.Likes ?? false))
+ {
+ score += 2;
+ }
+ else if (!(channelUserdata.Likes ?? true))
+ {
+ score -= 2;
+ }
+
+ if (channelUserdata.IsFavorite)
+ {
+ score += 3;
+ }
+
+ score += GetGenreScore(program.Genres, userId, genres);
+
+ return score;
+ }
+
+ private int GetGenreScore(IEnumerable<string> programGenres, Guid userId, Dictionary<string, Genre> genres)
+ {
+ return programGenres.Select(i =>
+ {
+ var score = 0;
+
+ Genre genre;
+
+ if (genres.TryGetValue(i, out genre))
+ {
+ var genreUserdata = _userDataManager.GetUserData(userId, genre.GetUserDataKey());
+
+ if ((genreUserdata.Likes ?? false))
+ {
+ score++;
+ }
+ else if (!(genreUserdata.Likes ?? true))
+ {
+ score--;
+ }
+
+ if (genreUserdata.IsFavorite)
+ {
+ score += 2;
+ }
+ }
+
+ return score;
+
+ }).Sum();
+ }
+
private async Task AddRecordingInfo(IEnumerable<ProgramInfoDto> programs, CancellationToken cancellationToken)
{
var timers = await ActiveService.GetTimersAsync(cancellationToken).ConfigureAwait(false);
@@ -505,6 +661,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
numComplete = 0;
var programs = new List<LiveTvProgram>();
+ var guideDays = GetGuideDays(list.Count);
+
foreach (var item in list)
{
// Avoid implicitly captured closure
@@ -512,7 +670,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
try
{
- var channelPrograms = await service.GetProgramsAsync(currentChannel.ChannelInfo.Id, cancellationToken).ConfigureAwait(false);
+ var start = DateTime.UtcNow.AddHours(-1);
+ var end = start.AddDays(guideDays);
+
+ var channelPrograms = await service.GetProgramsAsync(currentChannel.ChannelInfo.Id, start, end, cancellationToken).ConfigureAwait(false);
var programTasks = channelPrograms.Select(program => GetProgram(program, currentChannel.ChannelInfo.ChannelType, service.Name, cancellationToken));
var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false);
@@ -538,6 +699,23 @@ namespace MediaBrowser.Server.Implementations.LiveTv
_programs = programs.ToDictionary(i => i.Id);
}
+ private double GetGuideDays(int channelCount)
+ {
+ if (_config.Configuration.LiveTvOptions.GuideDays.HasValue)
+ {
+ return _config.Configuration.LiveTvOptions.GuideDays.Value;
+ }
+
+ var programsPerDay = channelCount * 48;
+
+ const int maxPrograms = 32000;
+
+ var days = Math.Round(((double)maxPrograms) / programsPerDay);
+
+ // No less than 2, no more than 14
+ return Math.Max(2, Math.Min(days, 14));
+ }
+
private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)
{
var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false);
@@ -779,14 +957,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
if (string.Equals(query.SortBy, "Priority", StringComparison.OrdinalIgnoreCase))
{
timers = query.SortOrder == SortOrder.Descending ?
- timers.OrderBy(i => i.Priority).ThenByDescending(i => i.Name) :
- timers.OrderByDescending(i => i.Priority).ThenBy(i => i.Name);
+ timers.OrderBy(i => i.Priority).ThenByStringDescending(i => i.Name) :
+ timers.OrderByDescending(i => i.Priority).ThenByString(i => i.Name);
}
else
{
timers = query.SortOrder == SortOrder.Descending ?
- timers.OrderByDescending(i => i.Name) :
- timers.OrderBy(i => i.Name);
+ timers.OrderByStringDescending(i => i.Name) :
+ timers.OrderByString(i => i.Name);
}
var returnArray = timers
@@ -833,24 +1011,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv
.FirstOrDefault();
}
- public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
+ private async Task<SeriesTimerInfo> GetNewTimerDefaultsInternal(CancellationToken cancellationToken, ProgramInfo program = null)
{
- var service = ActiveService;
+ var info = await ActiveService.GetNewTimerDefaultsAsync(cancellationToken, program).ConfigureAwait(false);
+
+ info.Id = null;
- var info = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
+ return info;
+ }
- var obj = _tvDtoService.GetSeriesTimerInfoDto(info, service, null);
+ public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
+ {
+ var info = await GetNewTimerDefaultsInternal(cancellationToken).ConfigureAwait(false);
- obj.Id = obj.ExternalId = string.Empty;
+ var obj = _tvDtoService.GetSeriesTimerInfoDto(info, ActiveService, null);
return obj;
}
public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
{
- var info = await GetNewTimerDefaults(cancellationToken).ConfigureAwait(false);
+ var program = GetInternalProgram(programId).ProgramInfo;
+ var programDto = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
- var program = await GetProgram(programId, cancellationToken).ConfigureAwait(false);
+ var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false);
+ var info = _tvDtoService.GetSeriesTimerInfoDto(defaults, ActiveService, null);
info.Days = new List<DayOfWeek>
{
@@ -861,13 +1046,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
info.Name = program.Name;
info.ChannelId = program.ChannelId;
- info.ChannelName = program.ChannelName;
+ info.ChannelName = programDto.ChannelName;
info.EndDate = program.EndDate;
info.StartDate = program.StartDate;
info.Name = program.Name;
info.Overview = program.Overview;
info.ProgramId = program.Id;
- info.ExternalProgramId = program.ExternalId;
+ info.ExternalProgramId = programDto.ExternalId;
return info;
}
@@ -977,7 +1162,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
.ToLookup(i => i.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
- groups.AddRange(series.OrderBy(i => i.Key).Select(i => new RecordingGroupDto
+ groups.AddRange(series.OrderByString(i => i.Key).Select(i => new RecordingGroupDto
{
Name = i.Key,
RecordingCount = i.Count()
@@ -1047,5 +1232,36 @@ namespace MediaBrowser.Server.Implementations.LiveTv
EndDate = endDate
};
}
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ private readonly object _disposeLock = new object();
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool dispose)
+ {
+ if (dispose)
+ {
+ lock (_disposeLock)
+ {
+ foreach (var stream in _openStreams.Values.ToList())
+ {
+ var task = CloseLiveStream(stream.Id, CancellationToken.None);
+
+ Task.WaitAll(task);
+ }
+
+ _openStreams.Clear();
+ }
+ }
+ }
}
}