support streaming of tv recordings

This commit is contained in:
Luke Pulverenti 2013-12-22 13:58:51 -05:00
parent cac101cc21
commit 4c2623d540
19 changed files with 191 additions and 47 deletions

View File

@ -29,12 +29,16 @@ namespace MediaBrowser.Api
/// <value>The logger.</value>
private ILogger Logger { get; set; }
/// <summary>
/// The application paths
/// </summary>
private readonly IServerApplicationPaths AppPaths;
/// <summary>
/// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="appPaths">The application paths.</param>
public ApiEntryPoint(ILogger logger, IServerApplicationPaths appPaths)
{
Logger = logger;
@ -52,6 +56,10 @@ namespace MediaBrowser.Api
{
DeleteEncodedMediaCache();
}
catch (DirectoryNotFoundException)
{
// Don't clutter the log
}
catch (IOException ex)
{
Logger.ErrorException("Error deleting encoded media cache", ex);

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Entities;
using System.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
@ -51,6 +52,11 @@ namespace MediaBrowser.Api
return ResultFactory.GetOptimizedResult(Request, result);
}
protected object ToStreamResult(Stream stream, string contentType)
{
return ResultFactory.GetResult(stream, contentType);
}
/// <summary>
/// To the optimized result using cache.
/// </summary>

View File

@ -174,6 +174,13 @@ namespace MediaBrowser.Api.LiveTv
{
}
[Route("/LiveTv/Recordings/{Id}/Stream", "GET")]
public class GetInternalRecordingStream
{
[ApiMember(Name = "Id", Description = "Recording Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public string Id { get; set; }
}
public class LiveTvService : BaseApiService
{
private readonly ILiveTvManager _liveTvManager;
@ -364,5 +371,12 @@ namespace MediaBrowser.Api.LiveTv
Task.WaitAll(task);
}
public object Get(GetInternalRecordingStream request)
{
var stream = _liveTvManager.GetRecordingStream(request.Id, CancellationToken.None).Result;
return ToStreamResult(stream.Stream, stream.MimeType);
}
}
}

View File

@ -359,7 +359,7 @@ namespace MediaBrowser.Api.Playback
if (request.Width.HasValue)
{
var widthParam = request.Width.Value.ToString(UsCulture);
return isH264Output ?
string.Format(" -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", widthParam, assSubtitleParam) :
string.Format(" -vf \"scale={0}:-1{1}\"", widthParam, assSubtitleParam);
@ -369,7 +369,7 @@ namespace MediaBrowser.Api.Playback
if (request.Height.HasValue)
{
var heightParam = request.Height.Value.ToString(UsCulture);
return isH264Output ?
string.Format(" -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", heightParam, assSubtitleParam) :
string.Format(" -vf \"scale=-1:{0}{1}\"", heightParam, assSubtitleParam);
@ -379,7 +379,7 @@ namespace MediaBrowser.Api.Playback
if (request.MaxWidth.HasValue && (!request.MaxHeight.HasValue || state.VideoStream == null))
{
var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
return isH264Output ?
string.Format(" -vf \"scale=min(iw\\,{0}):trunc(ow/a/2)*2{1}\"", maxWidthParam, assSubtitleParam) :
string.Format(" -vf \"scale=min(iw\\,{0}):-1{1}\"", maxWidthParam, assSubtitleParam);
@ -389,7 +389,7 @@ namespace MediaBrowser.Api.Playback
if (request.MaxHeight.HasValue && (!request.MaxWidth.HasValue || state.VideoStream == null))
{
var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
return isH264Output ?
string.Format(" -vf \"scale=trunc(oh*a*2)/2:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam) :
string.Format(" -vf \"scale=-1:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam);
@ -890,6 +890,14 @@ namespace MediaBrowser.Api.Playback
state.MediaPath = recording.RecordingInfo.Url;
state.IsRemote = true;
}
else
{
state.MediaPath = string.Format("http://localhost:{0}/mediabrowser/LiveTv/Recordings/{1}/Stream",
ServerConfigurationManager.Configuration.HttpServerPortNumber,
request.Id);
state.IsRemote = true;
}
item = recording;
}
@ -899,7 +907,7 @@ namespace MediaBrowser.Api.Playback
state.MediaPath = item.Path;
state.IsRemote = item.LocationType == LocationType.Remote;
var video = item as Video;
if (video != null)

View File

@ -124,6 +124,11 @@ namespace MediaBrowser.Controller.Library
{
var filename = Path.GetFileName(path);
if (string.Equals(path, "specials", StringComparison.OrdinalIgnoreCase))
{
return 0;
}
// Look for one of the season folder names
foreach (var name in SeasonFolderNames)
{

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Entities;
using System.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
using System.Collections.Generic;
@ -153,6 +154,14 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>LiveTvRecording.</returns>
Task<LiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken);
/// <summary>
/// Gets the recording stream.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
Task<StreamResponseInfo> GetRecordingStream(string id, CancellationToken cancellationToken);
/// <summary>
/// Gets the program.

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@ -85,7 +84,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="channelId">The channel identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
Task<ImageResponseInfo> GetChannelImageAsync(string channelId, CancellationToken cancellationToken);
Task<StreamResponseInfo> GetChannelImageAsync(string channelId, CancellationToken cancellationToken);
/// <summary>
/// Gets the recording image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to RecordingInfo
@ -93,7 +92,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="recordingId">The recording identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{ImageResponseInfo}.</returns>
Task<ImageResponseInfo> GetRecordingImageAsync(string recordingId, CancellationToken cancellationToken);
Task<StreamResponseInfo> GetRecordingImageAsync(string recordingId, CancellationToken cancellationToken);
/// <summary>
/// Gets the program image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to ProgramInfo
@ -102,7 +101,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="channelId">The channel identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{ImageResponseInfo}.</returns>
Task<ImageResponseInfo> GetProgramImageAsync(string programId, string channelId, CancellationToken cancellationToken);
Task<StreamResponseInfo> GetProgramImageAsync(string programId, string channelId, CancellationToken cancellationToken);
/// <summary>
/// Gets the recordings asynchronous.
@ -146,7 +145,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="recordingId">The recording identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
Task<Stream> GetRecordingStream(string recordingId, CancellationToken cancellationToken);
Task<StreamResponseInfo> GetRecordingStream(string recordingId, CancellationToken cancellationToken);
/// <summary>
/// Gets the channel stream.
@ -154,6 +153,6 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="recordingId">The recording identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{Stream}.</returns>
Task<Stream> GetChannelStream(string recordingId, CancellationToken cancellationToken);
Task<StreamResponseInfo> GetChannelStream(string recordingId, CancellationToken cancellationToken);
}
}

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.LiveTv;
namespace MediaBrowser.Controller.LiveTv
{
@ -15,13 +16,15 @@ namespace MediaBrowser.Controller.LiveTv
public ProgramInfo ProgramInfo { get; set; }
public ChannelType ChannelType { get; set; }
public string ServiceName { get; set; }
public override string MediaType
{
get
{
return ProgramInfo.IsVideo ? Model.Entities.MediaType.Video : Model.Entities.MediaType.Audio;
return ChannelType == ChannelType.TV ? Model.Entities.MediaType.Video : Model.Entities.MediaType.Audio;
}
}

View File

@ -127,12 +127,6 @@ namespace MediaBrowser.Controller.LiveTv
/// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
public bool IsSeries { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is video.
/// </summary>
/// <value><c>true</c> if this instance is video; otherwise, <c>false</c>.</value>
public bool IsVideo { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is live.
/// </summary>

View File

@ -2,7 +2,7 @@
namespace MediaBrowser.Controller.LiveTv
{
public class ImageResponseInfo
public class StreamResponseInfo
{
/// <summary>
/// Gets or sets the stream.

View File

@ -112,7 +112,7 @@
<Compile Include="LiveTv\ChannelInfo.cs" />
<Compile Include="LiveTv\ILiveTvManager.cs" />
<Compile Include="LiveTv\ILiveTvService.cs" />
<Compile Include="LiveTv\ImageResponseInfo.cs" />
<Compile Include="LiveTv\StreamResponseInfo.cs" />
<Compile Include="LiveTv\LiveTvProgram.cs" />
<Compile Include="LiveTv\LiveTvRecording.cs" />
<Compile Include="LiveTv\ProgramInfo.cs" />

View File

@ -134,6 +134,7 @@
<Compile Include="TV\ManualTvdbPersonImageProvider.cs" />
<Compile Include="TV\ManualTvdbSeasonImageProvider.cs" />
<Compile Include="TV\ManualTvdbSeriesImageProvider.cs" />
<Compile Include="TV\SeasonIndexNumberProvider.cs" />
<Compile Include="TV\TvdbEpisodeProvider.cs" />
<Compile Include="TV\TvdbSeasonProvider.cs" />
<Compile Include="TV\TvdbSeriesProvider.cs" />

View File

@ -50,7 +50,12 @@ namespace MediaBrowser.Providers.TV
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
return item is Episode && item.LocationType != LocationType.Virtual && item.LocationType != LocationType.Remote;
if (item is Episode)
{
var locationType = item.LocationType;
return locationType != LocationType.Virtual && locationType != LocationType.Remote;
}
return false;
}
/// <summary>

View File

@ -0,0 +1,83 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Providers.TV
{
class SeasonIndexNumberProvider : BaseMetadataProvider
{
/// <summary>
/// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class.
/// </summary>
/// <param name="logManager">The log manager.</param>
/// <param name="configurationManager">The configuration manager.</param>
public SeasonIndexNumberProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
: base(logManager, configurationManager)
{
}
protected override bool RefreshOnVersionChange
{
get
{
return true;
}
}
protected override string ProviderVersion
{
get
{
return "2";
}
}
/// <summary>
/// Supportses the specified item.
/// </summary>
/// <param name="item">The item.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public override bool Supports(BaseItem item)
{
if (item is Season)
{
var locationType = item.LocationType;
return locationType != LocationType.Virtual && locationType != LocationType.Remote;
}
return false;
}
/// <summary>
/// Fetches metadata and returns true or false indicating if any work that requires persistence was done
/// </summary>
/// <param name="item">The item.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="providerInfo">The provider information.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
{
item.IndexNumber = TVUtils.GetSeasonNumberFromPath(item.Path);
SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
return TrueTaskResult;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.First; }
}
}
}

View File

@ -42,7 +42,7 @@ namespace MediaBrowser.Providers.TV
/// <value>The priority.</value>
public override MetadataProviderPriority Priority
{
get { return MetadataProviderPriority.First; }
get { return MetadataProviderPriority.Second; }
}
private const string XmlFileName = "season.xml";

View File

@ -152,6 +152,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return await GetRecording(recording, service.Name, cancellationToken).ConfigureAwait(false);
}
public async Task<StreamResponseInfo> GetRecordingStream(string id, CancellationToken cancellationToken)
{
var service = ActiveService;
var recordings = await service.GetRecordingsAsync(cancellationToken).ConfigureAwait(false);
var recording = recordings.FirstOrDefault(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id));
return await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false);
}
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));
@ -202,7 +213,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return item;
}
private async Task<LiveTvProgram> GetProgram(ProgramInfo info, string serviceName, CancellationToken cancellationToken)
private async Task<LiveTvProgram> GetProgram(ProgramInfo info, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
{
var isNew = false;
@ -223,6 +234,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
isNew = true;
}
item.ChannelType = channelType;
item.ProgramInfo = info;
item.ServiceName = serviceName;
@ -283,7 +295,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
programs = programs.Where(i =>
{
var programChannelId = i.ProgramInfo.ChannelId;
var internalProgramChannelId = _tvDtoService.GetInternalChannelId(serviceName, programChannelId, i.ProgramInfo.ChannelName);
return guids.Contains(internalProgramChannelId);
@ -366,7 +378,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var channelPrograms = await service.GetProgramsAsync(channelInfo.Item2.Id, cancellationToken).ConfigureAwait(false);
var programTasks = channelPrograms.Select(program => GetProgram(program, service.Name, cancellationToken));
var programTasks = channelPrograms.Select(program => GetProgram(program, item.ChannelInfo.ChannelType, service.Name, cancellationToken));
var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false);
programs.AddRange(programEntities);
@ -433,7 +445,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
var returnArray = entities
.Select(i => _tvDtoService.GetRecordingInfoDto(i, ActiveService, user))
.Select(i => _tvDtoService.GetRecordingInfoDto(i, service, user))
.OrderByDescending(i => i.StartDate)
.ToArray();
@ -489,7 +501,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
var program = string.IsNullOrEmpty(i.ProgramId) ? null : GetInternalProgram(_tvDtoService.GetInternalProgramId(service.Name, i.ProgramId).ToString("N"));
return _tvDtoService.GetTimerInfoDto(i, ActiveService, program);
return _tvDtoService.GetTimerInfoDto(i, service, program);
})
.OrderBy(i => i.StartDate)
.ToArray();
@ -574,18 +586,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
{
var list = new List<SeriesTimerInfoDto>();
var service = ActiveService;
if (ActiveService != null)
{
var timers = await ActiveService.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
var timers = await service.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
var dtos = timers.Select(i => _tvDtoService.GetSeriesTimerInfoDto(i, ActiveService));
list.AddRange(dtos);
}
var returnArray = list.OrderByDescending(i => i.StartDate)
var returnArray = timers
.Select(i => _tvDtoService.GetSeriesTimerInfoDto(i, service))
.OrderByDescending(i => i.StartDate)
.ToArray();
return new QueryResult<SeriesTimerInfoDto>
@ -606,9 +613,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
{
var info = await ActiveService.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
var service = ActiveService;
var obj = _tvDtoService.GetSeriesTimerInfoDto(info, ActiveService);
var info = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
var obj = _tvDtoService.GetSeriesTimerInfoDto(info, service);
obj.Id = obj.ExternalId = string.Empty;

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
<version>3.0.288</version>
<version>3.0.289</version>
<title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners>
@ -12,7 +12,7 @@
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
<dependency id="MediaBrowser.Common" version="3.0.288" />
<dependency id="MediaBrowser.Common" version="3.0.289" />
<dependency id="NLog" version="2.1.0" />
<dependency id="SimpleInjector" version="2.4.0" />
<dependency id="sharpcompress" version="0.10.2" />

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common</id>
<version>3.0.288</version>
<version>3.0.289</version>
<title>MediaBrowser.Common</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
<version>3.0.288</version>
<version>3.0.289</version>
<title>Media Browser.Server.Core</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Media Browser Server.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
<dependency id="MediaBrowser.Common" version="3.0.288" />
<dependency id="MediaBrowser.Common" version="3.0.289" />
</dependencies>
</metadata>
<files>