Răsfoiți Sursa

support streaming of tv recordings

Luke Pulverenti 11 ani în urmă
părinte
comite
4c2623d540

+ 9 - 1
MediaBrowser.Api/ApiEntryPoint.cs

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

+ 7 - 1
MediaBrowser.Api/BaseApiService.cs

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

+ 14 - 0
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -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
     public class LiveTvService : BaseApiService
     {
     {
         private readonly ILiveTvManager _liveTvManager;
         private readonly ILiveTvManager _liveTvManager;
@@ -364,5 +371,12 @@ namespace MediaBrowser.Api.LiveTv
 
 
             Task.WaitAll(task);
             Task.WaitAll(task);
         }
         }
+
+        public object Get(GetInternalRecordingStream request)
+        {
+            var stream = _liveTvManager.GetRecordingStream(request.Id, CancellationToken.None).Result;
+
+            return ToStreamResult(stream.Stream, stream.MimeType);
+        }
     }
     }
 }
 }

+ 13 - 5
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -359,7 +359,7 @@ namespace MediaBrowser.Api.Playback
             if (request.Width.HasValue)
             if (request.Width.HasValue)
             {
             {
                 var widthParam = request.Width.Value.ToString(UsCulture);
                 var widthParam = request.Width.Value.ToString(UsCulture);
-                
+
                 return isH264Output ?
                 return isH264Output ?
                     string.Format(" -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", widthParam, assSubtitleParam) :
                     string.Format(" -vf \"scale={0}:trunc(ow/a/2)*2{1}\"", widthParam, assSubtitleParam) :
                     string.Format(" -vf \"scale={0}:-1{1}\"", widthParam, assSubtitleParam);
                     string.Format(" -vf \"scale={0}:-1{1}\"", widthParam, assSubtitleParam);
@@ -369,7 +369,7 @@ namespace MediaBrowser.Api.Playback
             if (request.Height.HasValue)
             if (request.Height.HasValue)
             {
             {
                 var heightParam = request.Height.Value.ToString(UsCulture);
                 var heightParam = request.Height.Value.ToString(UsCulture);
-                
+
                 return isH264Output ?
                 return isH264Output ?
                     string.Format(" -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", heightParam, assSubtitleParam) :
                     string.Format(" -vf \"scale=trunc(oh*a*2)/2:{0}{1}\"", heightParam, assSubtitleParam) :
                     string.Format(" -vf \"scale=-1:{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))
             if (request.MaxWidth.HasValue && (!request.MaxHeight.HasValue || state.VideoStream == null))
             {
             {
                 var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
                 var maxWidthParam = request.MaxWidth.Value.ToString(UsCulture);
-                
+
                 return isH264Output ?
                 return isH264Output ?
                     string.Format(" -vf \"scale=min(iw\\,{0}):trunc(ow/a/2)*2{1}\"", maxWidthParam, assSubtitleParam) :
                     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);
                     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))
             if (request.MaxHeight.HasValue && (!request.MaxWidth.HasValue || state.VideoStream == null))
             {
             {
                 var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
                 var maxHeightParam = request.MaxHeight.Value.ToString(UsCulture);
-                
+
                 return isH264Output ?
                 return isH264Output ?
                     string.Format(" -vf \"scale=trunc(oh*a*2)/2:min(ih\\,{0}){1}\"", maxHeightParam, assSubtitleParam) :
                     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);
                     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.MediaPath = recording.RecordingInfo.Url;
                     state.IsRemote = true;
                     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;
                 item = recording;
             }
             }
@@ -899,7 +907,7 @@ namespace MediaBrowser.Api.Playback
 
 
                 state.MediaPath = item.Path;
                 state.MediaPath = item.Path;
                 state.IsRemote = item.LocationType == LocationType.Remote;
                 state.IsRemote = item.LocationType == LocationType.Remote;
-                
+
                 var video = item as Video;
                 var video = item as Video;
 
 
                 if (video != null)
                 if (video != null)

+ 5 - 0
MediaBrowser.Controller/Library/TVUtils.cs

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

+ 10 - 1
MediaBrowser.Controller/LiveTv/ILiveTvManager.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Entities;
+using System.IO;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -153,6 +154,14 @@ namespace MediaBrowser.Controller.LiveTv
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>LiveTvRecording.</returns>
         /// <returns>LiveTvRecording.</returns>
         Task<LiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken);
         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>
         /// <summary>
         /// Gets the program.
         /// Gets the program.

+ 5 - 6
MediaBrowser.Controller/LiveTv/ILiveTvService.cs

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

+ 4 - 1
MediaBrowser.Controller/LiveTv/LiveTvProgram.cs

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

+ 0 - 6
MediaBrowser.Controller/LiveTv/ProgramInfo.cs

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

+ 1 - 1
MediaBrowser.Controller/LiveTv/ImageResponseInfo.cs → MediaBrowser.Controller/LiveTv/StreamResponseInfo.cs

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

+ 1 - 1
MediaBrowser.Controller/MediaBrowser.Controller.csproj

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

+ 1 - 0
MediaBrowser.Providers/MediaBrowser.Providers.csproj

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

+ 6 - 1
MediaBrowser.Providers/TV/EpisodeIndexNumberProvider.cs

@@ -50,7 +50,12 @@ namespace MediaBrowser.Providers.TV
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         public override bool Supports(BaseItem item)
         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>
         /// <summary>

+ 83 - 0
MediaBrowser.Providers/TV/SeasonIndexNumberProvider.cs

@@ -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; }
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Providers/TV/SeasonProviderFromXml.cs

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

+ 26 - 17
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -152,6 +152,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return await GetRecording(recording, service.Name, cancellationToken).ConfigureAwait(false);
             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)
         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(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(serviceName), _fileSystem.GetValidFilename(channelInfo.Name));
@@ -202,7 +213,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             return item;
             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;
             var isNew = false;
 
 
@@ -223,6 +234,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 isNew = true;
                 isNew = true;
             }
             }
 
 
+            item.ChannelType = channelType;
             item.ProgramInfo = info;
             item.ProgramInfo = info;
             item.ServiceName = serviceName;
             item.ServiceName = serviceName;
 
 
@@ -283,7 +295,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 programs = programs.Where(i =>
                 programs = programs.Where(i =>
                 {
                 {
                     var programChannelId = i.ProgramInfo.ChannelId;
                     var programChannelId = i.ProgramInfo.ChannelId;
-
+                    
                     var internalProgramChannelId = _tvDtoService.GetInternalChannelId(serviceName, programChannelId, i.ProgramInfo.ChannelName);
                     var internalProgramChannelId = _tvDtoService.GetInternalChannelId(serviceName, programChannelId, i.ProgramInfo.ChannelName);
 
 
                     return guids.Contains(internalProgramChannelId);
                     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 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);
                     var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false);
 
 
                     programs.AddRange(programEntities);
                     programs.AddRange(programEntities);
@@ -433,7 +445,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             }
             }
 
 
             var returnArray = entities
             var returnArray = entities
-                .Select(i => _tvDtoService.GetRecordingInfoDto(i, ActiveService, user))
+                .Select(i => _tvDtoService.GetRecordingInfoDto(i, service, user))
                 .OrderByDescending(i => i.StartDate)
                 .OrderByDescending(i => i.StartDate)
                 .ToArray();
                 .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"));
                     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)
                 .OrderBy(i => i.StartDate)
                 .ToArray();
                 .ToArray();
@@ -574,18 +586,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
 
         public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
         public async Task<QueryResult<SeriesTimerInfoDto>> GetSeriesTimers(SeriesTimerQuery query, CancellationToken cancellationToken)
         {
         {
-            var list = new List<SeriesTimerInfoDto>();
-
-            if (ActiveService != null)
-            {
-                var timers = await ActiveService.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
-
-                var dtos = timers.Select(i => _tvDtoService.GetSeriesTimerInfoDto(i, ActiveService));
+            var service = ActiveService;
 
 
-                list.AddRange(dtos);
-            }
+            var timers = await service.GetSeriesTimersAsync(cancellationToken).ConfigureAwait(false);
 
 
-            var returnArray = list.OrderByDescending(i => i.StartDate)
+            var returnArray = timers
+                .Select(i => _tvDtoService.GetSeriesTimerInfoDto(i, service))
+                .OrderByDescending(i => i.StartDate)
                 .ToArray();
                 .ToArray();
 
 
             return new QueryResult<SeriesTimerInfoDto>
             return new QueryResult<SeriesTimerInfoDto>
@@ -606,9 +613,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
 
         public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
         public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(CancellationToken cancellationToken)
         {
         {
-            var info = await ActiveService.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
+            var service = ActiveService;
+
+            var info = await service.GetNewTimerDefaultsAsync(cancellationToken).ConfigureAwait(false);
 
 
-            var obj = _tvDtoService.GetSeriesTimerInfoDto(info, ActiveService);
+            var obj = _tvDtoService.GetSeriesTimerInfoDto(info, service);
 
 
             obj.Id = obj.ExternalId = string.Empty;
             obj.Id = obj.ExternalId = string.Empty;
 
 

+ 2 - 2
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.288</version>
+        <version>3.0.289</version>
         <title>MediaBrowser.Common.Internal</title>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <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>
         <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>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
         <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="NLog" version="2.1.0" />
             <dependency id="SimpleInjector" version="2.4.0" />
             <dependency id="SimpleInjector" version="2.4.0" />
             <dependency id="sharpcompress" version="0.10.2" />
             <dependency id="sharpcompress" version="0.10.2" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

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

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

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