add playback of in-progress recordings

This commit is contained in:
Luke Pulverenti
2016-10-09 03:18:43 -04:00
parent b3595eab6a
commit daaae69df5
49 changed files with 570 additions and 397 deletions

View File

@@ -28,6 +28,7 @@ using System.Threading.Tasks;
using System.Xml;
using CommonIO;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Model.Configuration;
@@ -38,7 +39,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
public class EmbyTV : ILiveTvService, ISupportsDirectStreamProvider, ISupportsNewTimerIds, IDisposable
{
private readonly IApplicationHost _appHpst;
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly IHttpClient _httpClient;
private readonly IServerConfigurationManager _config;
@@ -64,11 +65,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder)
public EmbyTV(IServerApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder)
{
Current = this;
_appHpst = appHost;
_appHost = appHost;
_logger = logger;
_httpClient = httpClient;
_config = config;
@@ -293,7 +294,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
status.Tuners = list;
status.Status = LiveTvServiceStatus.Ok;
status.Version = _appHpst.ApplicationVersion.ToString();
status.Version = _appHost.ApplicationVersion.ToString();
status.IsVisible = false;
return status;
}
@@ -659,7 +660,63 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public async Task<IEnumerable<RecordingInfo>> GetRecordingsAsync(CancellationToken cancellationToken)
{
return new List<RecordingInfo>();
return _activeRecordings.Values.ToList().Select(GetRecordingInfo).ToList();
}
public string GetActiveRecordingPath(string id)
{
ActiveRecordingInfo info;
if (_activeRecordings.TryGetValue(id, out info))
{
return info.Path;
}
return null;
}
private RecordingInfo GetRecordingInfo(ActiveRecordingInfo info)
{
var timer = info.Timer;
var program = info.Program;
var result = new RecordingInfo
{
ChannelId = timer.ChannelId,
CommunityRating = timer.CommunityRating,
DateLastUpdated = DateTime.UtcNow,
EndDate = timer.EndDate,
EpisodeTitle = timer.EpisodeTitle,
Genres = timer.Genres,
Id = "recording" + timer.Id,
IsKids = timer.IsKids,
IsMovie = timer.IsMovie,
IsNews = timer.IsNews,
IsRepeat = timer.IsRepeat,
IsSeries = timer.IsProgramSeries,
IsSports = timer.IsSports,
Name = timer.Name,
OfficialRating = timer.OfficialRating,
OriginalAirDate = timer.OriginalAirDate,
Overview = timer.Overview,
ProgramId = timer.ProgramId,
SeriesTimerId = timer.SeriesTimerId,
StartDate = timer.StartDate,
Status = RecordingStatus.InProgress,
TimerId = timer.Id
};
if (program != null)
{
result.Audio = program.Audio;
result.ImagePath = program.ImagePath;
result.ImageUrl = program.ImageUrl;
result.IsHD = program.IsHD;
result.IsLive = program.IsLive;
result.IsPremiere = program.IsPremiere;
result.ShowId = program.ShowId;
}
return result;
}
public Task<IEnumerable<TimerInfo>> GetTimersAsync(CancellationToken cancellationToken)
@@ -954,7 +1011,31 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(string recordingId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
ActiveRecordingInfo info;
recordingId = recordingId.Replace("recording", string.Empty);
if (_activeRecordings.TryGetValue(recordingId, out info))
{
return Task.FromResult(new List<MediaSourceInfo>
{
new MediaSourceInfo
{
Path = _appHost.GetLocalApiUrl("localhost") + "/LiveTv/LiveRecordings/" + recordingId + "/stream",
Id = recordingId,
SupportsDirectPlay = false,
SupportsDirectStream = true,
SupportsTranscoding = true,
IsInfiniteStream = true,
RequiresOpening = false,
RequiresClosing = false,
Protocol = Model.MediaInfo.MediaProtocol.Http,
BufferMs = 0
}
});
}
throw new FileNotFoundException();
}
public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
@@ -1031,7 +1112,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var activeRecordingInfo = new ActiveRecordingInfo
{
CancellationTokenSource = new CancellationTokenSource(),
TimerId = timer.Id
Timer = timer
};
if (_activeRecordings.TryAdd(timer.Id, activeRecordingInfo))
@@ -1168,6 +1249,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
if (programInfo != null)
{
RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer);
activeRecordingInfo.Program = programInfo;
}
string seriesPath = null;
@@ -1394,7 +1476,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return true;
}
var hasRecordingAtPath = _activeRecordings.Values.ToList().Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.TimerId, timerId, StringComparison.OrdinalIgnoreCase));
var hasRecordingAtPath = _activeRecordings
.Values
.ToList()
.Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
if (hasRecordingAtPath)
{
@@ -1878,7 +1963,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
class ActiveRecordingInfo
{
public string Path { get; set; }
public string TimerId { get; set; }
public TimerInfo Timer { get; set; }
public ProgramInfo Program { get; set; }
public CancellationTokenSource CancellationTokenSource { get; set; }
}
}

View File

@@ -10,6 +10,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -125,6 +126,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.DayPattern = info.Days == null ? null : GetDayPattern(info.Days);
if (!string.IsNullOrWhiteSpace(info.SeriesId))
{
var program = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
ExternalSeriesId = info.SeriesId,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary }
}).FirstOrDefault();
if (program != null)
{
var image = program.GetImageInfo(ImageType.Primary, 0);
if (image != null)
{
try
{
dto.ParentPrimaryImageTag = _imageProcessor.GetImageCacheTag(program, image);
dto.ParentPrimaryImageItemId = program.Id.ToString("N");
}
catch (Exception ex)
{
}
}
}
}
return dto;
}

View File

@@ -235,20 +235,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return await GetLiveStream(id, mediaSourceId, true, cancellationToken).ConfigureAwait(false);
}
public async Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(string id, CancellationToken cancellationToken)
public async Task<IEnumerable<MediaSourceInfo>> GetRecordingMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
{
var item = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
var service = GetService(item);
var baseItem = (BaseItem)item;
var service = GetService(baseItem);
return await service.GetRecordingStreamMediaSources(id, cancellationToken).ConfigureAwait(false);
return await service.GetRecordingStreamMediaSources(baseItem.ExternalId, cancellationToken).ConfigureAwait(false);
}
public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(string id, CancellationToken cancellationToken)
public async Task<IEnumerable<MediaSourceInfo>> GetChannelMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
{
var item = GetInternalChannel(id);
var service = GetService(item);
var baseItem = (LiveTvChannel)item;
var service = GetService(baseItem);
var sources = await service.GetChannelStreamMediaSources(item.ExternalId, cancellationToken).ConfigureAwait(false);
var sources = await service.GetChannelStreamMediaSources(baseItem.ExternalId, cancellationToken).ConfigureAwait(false);
if (sources.Count == 0)
{
@@ -259,7 +259,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
foreach (var source in list)
{
Normalize(source, service, item.ChannelType == ChannelType.TV);
Normalize(source, service, baseItem.ChannelType == ChannelType.TV);
}
return list;
@@ -738,6 +738,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
recording.IsRepeat = info.IsRepeat;
recording.IsSports = info.IsSports;
recording.SeriesTimerId = info.SeriesTimerId;
recording.TimerId = info.TimerId;
recording.StartDate = info.StartDate;
if (!dataChanged)
@@ -1083,10 +1084,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
if (timer != null)
{
program.TimerId = _tvDtoService.GetInternalTimerId(serviceName, timer.Id)
.ToString("N");
if (timer.Status != RecordingStatus.Cancelled && timer.Status != RecordingStatus.Error)
{
program.TimerId = _tvDtoService.GetInternalTimerId(serviceName, timer.Id)
.ToString("N");
program.TimerStatus = timer.Status;
program.Status = timer.Status.ToString();
}
if (!string.IsNullOrEmpty(timer.SeriesTimerId))
{
@@ -1432,7 +1436,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private DateTime _lastRecordingRefreshTime;
private async Task RefreshRecordings(CancellationToken cancellationToken)
{
const int cacheMinutes = 5;
const int cacheMinutes = 3;
if ((DateTime.UtcNow - _lastRecordingRefreshTime).TotalMinutes < cacheMinutes)
{
@@ -1482,7 +1486,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private QueryResult<BaseItem> GetEmbyRecordings(RecordingQuery query, DtoOptions dtoOptions, User user)
{
if (user == null || (query.IsInProgress ?? false))
if (user == null)
{
return new QueryResult<BaseItem>();
}
if ((query.IsInProgress ?? false))
{
return new QueryResult<BaseItem>();
}
@@ -1628,7 +1637,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return new QueryResult<BaseItem>();
}
if (_services.Count == 1)
if (_services.Count == 1 && !(query.IsInProgress ?? false))
{
return GetEmbyRecordings(query, new DtoOptions(), user);
}
@@ -1824,6 +1833,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
? null
: _tvDtoService.GetInternalSeriesTimerId(service.Name, info.SeriesTimerId).ToString("N");
dto.TimerId = string.IsNullOrEmpty(info.TimerId)
? null
: _tvDtoService.GetInternalTimerId(service.Name, info.TimerId).ToString("N");
dto.StartDate = info.StartDate;
dto.RecordingStatus = info.Status;
dto.IsRepeat = info.IsRepeat;

View File

@@ -65,12 +65,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
if (item is ILiveTvRecording)
{
sources = await _liveTvManager.GetRecordingMediaSources(item.Id.ToString("N"), cancellationToken)
sources = await _liveTvManager.GetRecordingMediaSources(item, cancellationToken)
.ConfigureAwait(false);
}
else
{
sources = await _liveTvManager.GetChannelMediaSources(item.Id.ToString("N"), cancellationToken)
sources = await _liveTvManager.GetChannelMediaSources(item, cancellationToken)
.ConfigureAwait(false);
}
}