From 145285784bfb6a2fcd0e9234d569d7ad5083516f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 23 Jan 2014 13:05:41 -0500 Subject: [PATCH] trim tv objects --- MediaBrowser.Api/MediaBrowser.Api.csproj | 1 + .../Playback/Hls/BaseHlsService.cs | 43 ++- .../Playback/Hls/DynamicHlsService.cs | 356 ++++++++++++++++++ .../Playback/Hls/VideoHlsService.cs | 179 +-------- .../LiveTv/LiveTvChannel.cs | 42 ++- .../LiveTv/LiveTvProgram.cs | 112 +++++- MediaBrowser.Controller/LiveTv/ProgramInfo.cs | 18 +- .../LiveTv/ChannelImageProvider.cs | 18 +- .../LiveTv/LiveTvDtoService.cs | 63 ++-- .../LiveTv/LiveTvManager.cs | 138 +++++-- .../LiveTv/ProgramImageProvider.cs | 18 +- 11 files changed, 709 insertions(+), 279 deletions(-) create mode 100644 MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index f9dfadd1f9..f4b68810b8 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -101,6 +101,7 @@ + diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index b162d582d8..736d2122d1 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -117,15 +117,7 @@ namespace MediaBrowser.Api.Playback.Hls if (isPlaylistNewlyCreated) { - var minimumSegmentCount = 3; - var quality = GetQualitySetting(); - - if (quality == EncodingQuality.HighSpeed || quality == EncodingQuality.HighQuality) - { - minimumSegmentCount = 2; - } - - await WaitForMinimumSegmentCount(playlist, minimumSegmentCount).ConfigureAwait(false); + await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false); } int audioBitrate; @@ -154,6 +146,23 @@ namespace MediaBrowser.Api.Playback.Hls } } + /// + /// Gets the segment wait. + /// + /// System.Int32. + protected int GetSegmentWait() + { + var minimumSegmentCount = 3; + var quality = GetQualitySetting(); + + if (quality == EncodingQuality.HighSpeed || quality == EncodingQuality.HighQuality) + { + minimumSegmentCount = 2; + } + + return minimumSegmentCount; + } + /// /// Gets the playlist bitrates. /// @@ -210,7 +219,7 @@ namespace MediaBrowser.Api.Playback.Hls return builder.ToString(); } - private async Task WaitForMinimumSegmentCount(string playlist, int segmentCount) + protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount) { while (true) { @@ -273,8 +282,11 @@ namespace MediaBrowser.Api.Playback.Hls var threads = GetNumberOfThreads(false); var inputModifier = GetInputModifier(state); + + // If performSubtitleConversions is true we're actually starting ffmpeg + var startNumberParam = performSubtitleConversions ? GetStartNumber(state).ToString(UsCulture) : "0"; - var args = string.Format("{0} {1} -i {2}{3} -map_metadata -1 -threads {4} {5} {6} -sc_threshold 0 {7} -hls_time {8} -start_number 0 -hls_list_size 1440 \"{9}\"", + var args = string.Format("{0} {1} -i {2}{3} -map_metadata -1 -threads {4} {5} {6} -sc_threshold 0 {7} -hls_time {8} -start_number {9} -hls_list_size 1440 \"{10}\"", itsOffset, inputModifier, GetInputArgument(state), @@ -284,6 +296,7 @@ namespace MediaBrowser.Api.Playback.Hls GetVideoArguments(state, performSubtitleConversions), GetAudioArguments(state), state.SegmentLength.ToString(UsCulture), + startNumberParam, outputPath ).Trim(); @@ -295,10 +308,11 @@ namespace MediaBrowser.Api.Playback.Hls var bitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? 64000; - var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time {2} -start_number 0 -hls_list_size 1440 \"{3}\"", + var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time {2} -start_number {3} -hls_list_size 1440 \"{4}\"", threads, bitrate / 2, state.SegmentLength.ToString(UsCulture), + startNumberParam, lowBitratePath); args += " " + lowBitrateParams; @@ -307,5 +321,10 @@ namespace MediaBrowser.Api.Playback.Hls return args; } + + protected virtual int GetStartNumber(StreamState state) + { + return 0; + } } } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs new file mode 100644 index 0000000000..f064a13c6b --- /dev/null +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -0,0 +1,356 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.MediaInfo; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.IO; +using ServiceStack; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Playback.Hls +{ + [Route("/Videos/{Id}/master.m3u8", "GET")] + [Api(Description = "Gets a video stream using HTTP live streaming.")] + public class GetMasterHlsVideoStream : VideoStreamRequest + { + [ApiMember(Name = "BaselineStreamAudioBitRate", Description = "Optional. Specify the audio bitrate for the baseline stream.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? BaselineStreamAudioBitRate { get; set; } + + [ApiMember(Name = "AppendBaselineStream", Description = "Optional. Whether or not to include a baseline audio-only stream in the master playlist.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool AppendBaselineStream { get; set; } + } + + [Route("/Videos/{Id}/main.m3u8", "GET")] + [Api(Description = "Gets a video stream using HTTP live streaming.")] + public class GetMainHlsVideoStream : VideoStreamRequest + { + } + + [Route("/Videos/{Id}/baseline.m3u8", "GET")] + [Api(Description = "Gets a video stream using HTTP live streaming.")] + public class GetBaselineHlsVideoStream : VideoStreamRequest + { + } + + /// + /// Class GetHlsVideoSegment + /// + [Route("/Videos/{Id}/hlsdynamic/{PlaylistId}/{SegmentId}.ts", "GET")] + [Api(Description = "Gets an Http live streaming segment file. Internal use only.")] + public class GetDynamicHlsVideoSegment : VideoStreamRequest + { + public string PlaylistId { get; set; } + + /// + /// Gets or sets the segment id. + /// + /// The segment id. + public string SegmentId { get; set; } + } + + public class DynamicHlsService : BaseHlsService + { + public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager) + : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager) + { + } + + protected override string GetOutputFilePath(StreamState state) + { + var folder = (state.MediaPath + state.Request.DeviceId).GetMD5().ToString("N"); + + folder = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, folder); + + var outputFileExtension = GetOutputFileExtension(state); + + return Path.Combine(folder, GetCommandLineArguments("dummy\\dummy", state, false).GetMD5() + (outputFileExtension ?? string.Empty).ToLower()); + } + + public object Get(GetMasterHlsVideoStream request) + { + var result = GetAsync(request).Result; + + return result; + } + + public object Get(GetDynamicHlsVideoSegment request) + { + if (string.Equals("baseline", request.PlaylistId, StringComparison.OrdinalIgnoreCase)) + { + return GetDynamicSegment(request, false).Result; + } + + return GetDynamicSegment(request, true).Result; + } + + private async Task GetDynamicSegment(GetDynamicHlsVideoSegment request, bool isMain) + { + var index = int.Parse(request.SegmentId, NumberStyles.Integer, UsCulture); + + var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); + + var playlistPath = Path.ChangeExtension(GetOutputFilePath(state), ".m3u8"); + + var path = GetSegmentPath(playlistPath, index); + + if (File.Exists(path)) + { + return GetSegementResult(path); + } + + if (!File.Exists(playlistPath)) + { + await StartFfMpeg(state, playlistPath).ConfigureAwait(false); + + await WaitForMinimumSegmentCount(playlistPath, GetSegmentWait()).ConfigureAwait(false); + } + + return GetSegementResult(path); + } + + private string GetSegmentPath(string playlist, int index) + { + var folder = Path.GetDirectoryName(playlist); + + var filename = Path.GetFileNameWithoutExtension(playlist); + + return Path.Combine(folder, filename + index.ToString(UsCulture) + ".ts"); + } + + private object GetSegementResult(string path) + { + // TODO: Handle if it's currently being written to + return ResultFactory.GetStaticFileResult(Request, path); + } + + private async Task GetAsync(GetMasterHlsVideoStream request) + { + var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); + + if (!state.VideoRequest.VideoBitRate.HasValue && (!state.VideoRequest.VideoCodec.HasValue || state.VideoRequest.VideoCodec.Value != VideoCodecs.Copy)) + { + throw new ArgumentException("A video bitrate is required"); + } + if (!state.Request.AudioBitRate.HasValue && (!state.Request.AudioCodec.HasValue || state.Request.AudioCodec.Value != AudioCodecs.Copy)) + { + throw new ArgumentException("An audio bitrate is required"); + } + + int audioBitrate; + int videoBitrate; + GetPlaylistBitrates(state, out audioBitrate, out videoBitrate); + + var appendBaselineStream = false; + var baselineStreamBitrate = 64000; + + var hlsVideoRequest = state.VideoRequest as GetMasterHlsVideoStream; + if (hlsVideoRequest != null) + { + appendBaselineStream = hlsVideoRequest.AppendBaselineStream; + baselineStreamBitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? baselineStreamBitrate; + } + + var playlistText = GetMasterPlaylistFileText(videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate); + + return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); + } + + private string GetMasterPlaylistFileText(int bitrate, bool includeBaselineStream, int baselineStreamBitrate) + { + var builder = new StringBuilder(); + + builder.AppendLine("#EXTM3U"); + + // Pad a little to satisfy the apple hls validator + var paddedBitrate = Convert.ToInt32(bitrate * 1.05); + + var queryStringIndex = Request.RawUrl.IndexOf('?'); + var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex); + + // Main stream + builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(UsCulture)); + var playlistUrl = "main.m3u8" + queryString; + builder.AppendLine(playlistUrl); + + // Low bitrate stream + if (includeBaselineStream) + { + builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + baselineStreamBitrate.ToString(UsCulture)); + playlistUrl = "baseline.m3u8" + queryString; + builder.AppendLine(playlistUrl); + } + + return builder.ToString(); + } + + public object Get(GetMainHlsVideoStream request) + { + var result = GetPlaylistAsync(request, "main").Result; + + return result; + } + + public object Get(GetBaselineHlsVideoStream request) + { + var result = GetPlaylistAsync(request, "baseline").Result; + + return result; + } + + private async Task GetPlaylistAsync(VideoStreamRequest request, string name) + { + var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); + + var builder = new StringBuilder(); + + builder.AppendLine("#EXTM3U"); + builder.AppendLine("#EXT-X-VERSION:3"); + builder.AppendLine("#EXT-X-TARGETDURATION:" + state.SegmentLength.ToString(UsCulture)); + builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); + + var queryStringIndex = Request.RawUrl.IndexOf('?'); + var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex); + + var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds; + + var index = 0; + + while (seconds > 0) + { + var length = seconds >= state.SegmentLength ? state.SegmentLength : seconds; + + builder.AppendLine("#EXTINF:" + length.ToString(UsCulture)); + + builder.AppendLine(string.Format("hlsdynamic/{0}/{1}.ts{2}", + + name, + index.ToString(UsCulture), + queryString)); + + seconds -= state.SegmentLength; + index++; + } + + builder.AppendLine("#EXT-X-ENDLIST"); + + var playlistText = builder.ToString(); + + return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); + } + + protected override string GetAudioArguments(StreamState state) + { + var codec = GetAudioCodec(state.Request); + + if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) + { + return "-codec:a:0 copy"; + } + + var args = "-codec:a:0 " + codec; + + if (state.AudioStream != null) + { + var channels = GetNumAudioChannelsParam(state.Request, state.AudioStream); + + if (channels.HasValue) + { + args += " -ac " + channels.Value; + } + + var bitrate = GetAudioBitrateParam(state); + + if (bitrate.HasValue) + { + args += " -ab " + bitrate.Value.ToString(UsCulture); + } + + args += " " + GetAudioFilterParam(state, true); + + return args; + } + + return args; + } + + protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion) + { + var codec = GetVideoCodec(state.VideoRequest); + + // See if we can save come cpu cycles by avoiding encoding + if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) + { + return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf h264_mp4toannexb" : "-codec:v:0 copy"; + } + + const string keyFrameArg = " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))"; + + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsExternal && + (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 || + state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1); + + var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264") + keyFrameArg; + + var bitrate = GetVideoBitrateParam(state); + + if (bitrate.HasValue) + { + args += string.Format(" -b:v {0} -maxrate ({0}*.80) -bufsize {0}", bitrate.Value.ToString(UsCulture)); + } + + // Add resolution params, if specified + if (!hasGraphicalSubs) + { + if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue) + { + args += GetOutputSizeParam(state, codec, performSubtitleConversion); + } + } + + if (state.VideoRequest.Framerate.HasValue) + { + args += string.Format(" -r {0}", state.VideoRequest.Framerate.Value); + } + + args += " -vsync vfr"; + + if (!string.IsNullOrEmpty(state.VideoRequest.Profile)) + { + args += " -profile:v " + state.VideoRequest.Profile; + } + + if (!string.IsNullOrEmpty(state.VideoRequest.Level)) + { + args += " -level " + state.VideoRequest.Level; + } + + // This is for internal graphical subs + if (hasGraphicalSubs) + { + args += GetInternalGraphicalSubtitleParam(state, codec); + } + + return args; + } + + /// + /// Gets the segment file extension. + /// + /// The state. + /// System.String. + protected override string GetSegmentFileExtension(StreamState state) + { + return ".ts"; + } + } +} diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 174cbe9ba0..7e7e8ba5bd 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -5,15 +5,11 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaInfo; using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using ServiceStack; using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; -using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Api.Playback.Hls @@ -35,35 +31,12 @@ namespace MediaBrowser.Api.Playback.Hls public int TimeStampOffsetMs { get; set; } } - [Route("/Videos/{Id}/master.m3u8", "GET")] - [Api(Description = "Gets a video stream using HTTP live streaming.")] - public class GetMasterHlsVideoStream : VideoStreamRequest - { - [ApiMember(Name = "BaselineStreamAudioBitRate", Description = "Optional. Specify the audio bitrate for the baseline stream.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? BaselineStreamAudioBitRate { get; set; } - - [ApiMember(Name = "AppendBaselineStream", Description = "Optional. Whether or not to include a baseline audio-only stream in the master playlist.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool AppendBaselineStream { get; set; } - } - - [Route("/Videos/{Id}/main.m3u8", "GET")] - [Api(Description = "Gets a video stream using HTTP live streaming.")] - public class GetMainHlsVideoStream : VideoStreamRequest - { - } - - [Route("/Videos/{Id}/baseline.m3u8", "GET")] - [Api(Description = "Gets a video stream using HTTP live streaming.")] - public class GetBaselineHlsVideoStream : VideoStreamRequest - { - } - /// /// Class GetHlsVideoSegment /// [Route("/Videos/{Id}/hls/{PlaylistId}/{SegmentId}.ts", "GET")] [Api(Description = "Gets an Http live streaming segment file. Internal use only.")] - public class GetHlsVideoSegment + public class GetHlsVideoSegment : VideoStreamRequest { /// /// Gets or sets the id. @@ -90,27 +63,6 @@ namespace MediaBrowser.Api.Playback.Hls { } - public object Get(GetMasterHlsVideoStream request) - { - var result = GetAsync(request).Result; - - return result; - } - - public object Get(GetMainHlsVideoStream request) - { - var result = GetPlaylistAsync(request, "main").Result; - - return result; - } - - public object Get(GetBaselineHlsVideoStream request) - { - var result = GetPlaylistAsync(request, "baseline").Result; - - return result; - } - /// /// Gets the specified request. /// @@ -127,105 +79,29 @@ namespace MediaBrowser.Api.Playback.Hls return ResultFactory.GetStaticFileResult(Request, file); } - private async Task GetPlaylistAsync(VideoStreamRequest request, string name) + /// + /// Called when [begin request]. + /// + /// The playlist id. + protected void OnBeginRequest(string playlistId) { - var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); + var normalizedPlaylistId = playlistId.Replace("-low", string.Empty); - var builder = new StringBuilder(); - - builder.AppendLine("#EXTM3U"); - builder.AppendLine("#EXT-X-VERSION:3"); - builder.AppendLine("#EXT-X-TARGETDURATION:" + state.SegmentLength.ToString(UsCulture)); - builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); - - var queryStringIndex = Request.RawUrl.IndexOf('?'); - var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex); - - var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds; - - var index = 0; - - while (seconds > 0) + foreach (var playlist in Directory.EnumerateFiles(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, "*.m3u8") + .Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1) + .ToList()) { - var length = seconds >= state.SegmentLength ? state.SegmentLength : seconds; - - builder.AppendLine("#EXTINF:" + length.ToString(UsCulture)); - - builder.AppendLine(string.Format("hls/{0}/{1}.ts{2}" , - - name, - index.ToString(UsCulture), - queryString)); - - seconds -= state.SegmentLength; - index++; + ExtendPlaylistTimer(playlist); } - - builder.AppendLine("#EXT-X-ENDLIST"); - - var playlistText = builder.ToString(); - - return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); } - private async Task GetAsync(GetMasterHlsVideoStream request) + private async void ExtendPlaylistTimer(string playlist) { - var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); + ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); - if (!state.VideoRequest.VideoBitRate.HasValue && (!state.VideoRequest.VideoCodec.HasValue || state.VideoRequest.VideoCodec.Value != VideoCodecs.Copy)) - { - throw new ArgumentException("A video bitrate is required"); - } - if (!state.Request.AudioBitRate.HasValue && (!state.Request.AudioCodec.HasValue || state.Request.AudioCodec.Value != AudioCodecs.Copy)) - { - throw new ArgumentException("An audio bitrate is required"); - } + await Task.Delay(20000).ConfigureAwait(false); - int audioBitrate; - int videoBitrate; - GetPlaylistBitrates(state, out audioBitrate, out videoBitrate); - - var appendBaselineStream = false; - var baselineStreamBitrate = 64000; - - var hlsVideoRequest = state.VideoRequest as GetMasterHlsVideoStream; - if (hlsVideoRequest != null) - { - appendBaselineStream = hlsVideoRequest.AppendBaselineStream; - baselineStreamBitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? baselineStreamBitrate; - } - - var playlistText = GetMasterPlaylistFileText(videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate); - - return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary()); - } - - private string GetMasterPlaylistFileText(int bitrate, bool includeBaselineStream, int baselineStreamBitrate) - { - var builder = new StringBuilder(); - - builder.AppendLine("#EXTM3U"); - - // Pad a little to satisfy the apple hls validator - var paddedBitrate = Convert.ToInt32(bitrate * 1.05); - - var queryStringIndex = Request.RawUrl.IndexOf('?'); - var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex); - - // Main stream - builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + paddedBitrate.ToString(UsCulture)); - var playlistUrl = "main.m3u8" + queryString; - builder.AppendLine(playlistUrl); - - // Low bitrate stream - if (includeBaselineStream) - { - builder.AppendLine("#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=" + baselineStreamBitrate.ToString(UsCulture)); - playlistUrl = "baseline.m3u8" + queryString; - builder.AppendLine(playlistUrl); - } - - return builder.ToString(); + ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls); } /// @@ -353,30 +229,5 @@ namespace MediaBrowser.Api.Playback.Hls { return ".ts"; } - - /// - /// Called when [begin request]. - /// - /// The playlist id. - protected void OnBeginRequest(string playlistId) - { - var normalizedPlaylistId = playlistId.Replace("-low", string.Empty); - - foreach (var playlist in Directory.EnumerateFiles(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, "*.m3u8") - .Where(i => i.IndexOf(normalizedPlaylistId, StringComparison.OrdinalIgnoreCase) != -1) - .ToList()) - { - ExtendPlaylistTimer(playlist); - } - } - - private async void ExtendPlaylistTimer(string playlist) - { - ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); - - await Task.Delay(20000).ConfigureAwait(false); - - ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls); - } } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index 1e6d74ce82..f37e947140 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -25,17 +25,51 @@ namespace MediaBrowser.Controller.LiveTv [IgnoreDataMember] public List UserItemCountList { get; set; } - public ChannelInfo ChannelInfo { get; set; } + /// + /// Gets or sets the number. + /// + /// The number. + public string Number { get; set; } + + /// + /// Gets or sets the external identifier. + /// + /// The external identifier. + public string ExternalId { get; set; } + + /// + /// Gets or sets the type of the channel. + /// + /// The type of the channel. + public ChannelType ChannelType { get; set; } public string ServiceName { get; set; } + /// + /// Supply the image path if it can be accessed directly from the file system + /// + /// The image path. + public string ProviderImagePath { get; set; } + + /// + /// Supply the image url if it can be downloaded + /// + /// The image URL. + public string ProviderImageUrl { get; set; } + + /// + /// Gets or sets a value indicating whether this instance has image. + /// + /// null if [has image] contains no value, true if [has image]; otherwise, false. + public bool? HasProviderImage { get; set; } + protected override string CreateSortName() { double number = 0; - if (!string.IsNullOrEmpty(ChannelInfo.Number)) + if (!string.IsNullOrEmpty(Number)) { - double.TryParse(ChannelInfo.Number, out number); + double.TryParse(Number, out number); } return number.ToString("000-") + (Name ?? string.Empty); @@ -45,7 +79,7 @@ namespace MediaBrowser.Controller.LiveTv { get { - return ChannelInfo.ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; + return ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index aceb32885e..6a00607e41 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -15,12 +15,118 @@ namespace MediaBrowser.Controller.LiveTv return GetClientTypeName() + "-" + Name; } - public ProgramInfo ProgramInfo { get; set; } + /// + /// Id of the program. + /// + public string ExternalId { get; set; } + /// + /// Gets or sets the channel identifier. + /// + /// The channel identifier. + public string ExternalChannelId { get; set; } + + /// + /// Gets or sets the type of the channel. + /// + /// The type of the channel. public ChannelType ChannelType { get; set; } + /// + /// The start date of the program, in UTC. + /// + public DateTime StartDate { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is hd. + /// + /// true if this instance is hd; otherwise, false. + public bool? IsHD { get; set; } + + /// + /// Gets or sets the audio. + /// + /// The audio. + public ProgramAudio? Audio { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is repeat. + /// + /// true if this instance is repeat; otherwise, false. + public bool IsRepeat { get; set; } + + /// + /// Gets or sets the episode title. + /// + /// The episode title. + public string EpisodeTitle { get; set; } + + /// + /// Gets or sets the name of the service. + /// + /// The name of the service. public string ServiceName { get; set; } + /// + /// Supply the image path if it can be accessed directly from the file system + /// + /// The image path. + public string ProviderImagePath { get; set; } + + /// + /// Supply the image url if it can be downloaded + /// + /// The image URL. + public string ProviderImageUrl { get; set; } + + /// + /// Gets or sets a value indicating whether this instance has image. + /// + /// null if [has image] contains no value, true if [has image]; otherwise, false. + public bool? HasProviderImage { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is movie. + /// + /// true if this instance is movie; otherwise, false. + public bool IsMovie { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is sports. + /// + /// true if this instance is sports; otherwise, false. + public bool IsSports { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is series. + /// + /// true if this instance is series; otherwise, false. + public bool IsSeries { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is live. + /// + /// true if this instance is live; otherwise, false. + public bool IsLive { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is news. + /// + /// true if this instance is news; otherwise, false. + public bool IsNews { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is kids. + /// + /// true if this instance is kids; otherwise, false. + public bool IsKids { get; set; } + + /// + /// Gets or sets a value indicating whether this instance is premiere. + /// + /// true if this instance is premiere; otherwise, false. + public bool IsPremiere { get; set; } + public override string MediaType { get @@ -35,7 +141,7 @@ namespace MediaBrowser.Controller.LiveTv { var now = DateTime.UtcNow; - return now >= ProgramInfo.StartDate && now < ProgramInfo.EndDate; + return now >= StartDate && now < EndDate; } } @@ -45,7 +151,7 @@ namespace MediaBrowser.Controller.LiveTv { var now = DateTime.UtcNow; - return now >= ProgramInfo.EndDate; + return now >= EndDate; } } diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index 0368c5f2f6..4d7e5ee63e 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -17,12 +17,6 @@ namespace MediaBrowser.Controller.LiveTv /// The channel identifier. public string ChannelId { get; set; } - /// - /// Gets or sets the channel primary image tag. - /// - /// The channel primary image tag. - public Guid? ChannelPrimaryImageTag { get; set; } - /// /// Name of the program /// @@ -103,6 +97,12 @@ namespace MediaBrowser.Controller.LiveTv /// The image URL. public string ImageUrl { get; set; } + /// + /// Gets or sets a value indicating whether this instance has image. + /// + /// null if [has image] contains no value, true if [has image]; otherwise, false. + public bool? HasImage { get; set; } + /// /// Gets or sets a value indicating whether this instance is movie. /// @@ -145,12 +145,6 @@ namespace MediaBrowser.Controller.LiveTv /// true if this instance is premiere; otherwise, false. public bool IsPremiere { get; set; } - /// - /// Gets or sets a value indicating whether this instance has image. - /// - /// null if [has image] contains no value, true if [has image]; otherwise, false. - public bool? HasImage { get; set; } - public ProgramInfo() { Genres = new List(); diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs index d04ebe32d6..f1e10e175d 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs @@ -76,22 +76,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv private async Task DownloadImage(LiveTvChannel item, CancellationToken cancellationToken) { - var channelInfo = item.ChannelInfo; - Stream imageStream = null; string contentType = null; - if (!string.IsNullOrEmpty(channelInfo.ImagePath)) + if (!string.IsNullOrEmpty(item.ProviderImagePath)) { - contentType = "image/" + Path.GetExtension(channelInfo.ImagePath).ToLower(); - imageStream = _fileSystem.GetFileStream(channelInfo.ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true); + contentType = "image/" + Path.GetExtension(item.ProviderImagePath).ToLower(); + imageStream = _fileSystem.GetFileStream(item.ProviderImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true); } - else if (!string.IsNullOrEmpty(channelInfo.ImageUrl)) + else if (!string.IsNullOrEmpty(item.ProviderImageUrl)) { var options = new HttpRequestOptions { CancellationToken = cancellationToken, - Url = channelInfo.ImageUrl + Url = item.ProviderImageUrl }; var response = await _httpClient.GetResponse(options).ConfigureAwait(false); @@ -105,7 +103,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv imageStream = response.Content; contentType = response.ContentType; } - else if (channelInfo.HasImage ?? true) + else if (item.HasProviderImage ?? true) { var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase)); @@ -113,7 +111,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { try { - var response = await service.GetChannelImageAsync(channelInfo.Id, cancellationToken).ConfigureAwait(false); + var response = await service.GetChannelImageAsync(item.ExternalId, cancellationToken).ConfigureAwait(false); if (response != null) { @@ -131,7 +129,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (imageStream != null) { // Dummy up the original url - var url = item.ServiceName + channelInfo.Id; + var url = item.ServiceName + item.ExternalId; await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); return true; diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs index 2fea919b0c..094506199b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs @@ -74,7 +74,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (channel != null) { - dto.ChannelName = channel.ChannelInfo.Name; + dto.ChannelName = channel.Name; } return dto; @@ -280,18 +280,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv /// ChannelInfoDto. public ChannelInfoDto GetChannelInfoDto(LiveTvChannel info, LiveTvProgram currentProgram, User user = null) { - var channelInfo = info.ChannelInfo; - var dto = new ChannelInfoDto { Name = info.Name, ServiceName = info.ServiceName, - ChannelType = channelInfo.ChannelType, - Number = channelInfo.Number, + ChannelType = info.ChannelType, + Number = info.Number, Type = info.GetClientTypeName(), Id = info.Id.ToString("N"), MediaType = info.MediaType, - ExternalId = channelInfo.Id + ExternalId = info.ExternalId }; if (user != null) @@ -316,37 +314,40 @@ namespace MediaBrowser.Server.Implementations.LiveTv public ProgramInfoDto GetProgramInfoDto(LiveTvProgram item, LiveTvChannel channel, User user = null) { - var program = item.ProgramInfo; - var dto = new ProgramInfoDto { - Id = GetInternalProgramId(item.ServiceName, program.Id).ToString("N"), - ChannelId = GetInternalChannelId(item.ServiceName, program.ChannelId).ToString("N"), - Overview = program.Overview, - EndDate = program.EndDate, - Genres = program.Genres, - ExternalId = program.Id, - Name = program.Name, + Id = GetInternalProgramId(item.ServiceName, item.ExternalId).ToString("N"), + ChannelId = GetInternalChannelId(item.ServiceName, item.ExternalChannelId).ToString("N"), + Overview = item.Overview, + Genres = item.Genres, + ExternalId = item.ExternalId, + Name = item.Name, ServiceName = item.ServiceName, - StartDate = program.StartDate, - OfficialRating = program.OfficialRating, - IsHD = program.IsHD, - OriginalAirDate = program.OriginalAirDate, - Audio = program.Audio, - CommunityRating = GetClientCommunityRating(program.CommunityRating), - IsRepeat = program.IsRepeat, - EpisodeTitle = program.EpisodeTitle, - IsMovie = program.IsMovie, - IsSeries = program.IsSeries, - IsSports = program.IsSports, - IsLive = program.IsLive, - IsNews = program.IsNews, - IsKids = program.IsKids, - IsPremiere = program.IsPremiere, - RunTimeTicks = (program.EndDate - program.StartDate).Ticks, + StartDate = item.StartDate, + OfficialRating = item.OfficialRating, + IsHD = item.IsHD, + OriginalAirDate = item.PremiereDate, + Audio = item.Audio, + CommunityRating = GetClientCommunityRating(item.CommunityRating), + IsRepeat = item.IsRepeat, + EpisodeTitle = item.EpisodeTitle, + IsMovie = item.IsMovie, + IsSeries = item.IsSeries, + IsSports = item.IsSports, + IsLive = item.IsLive, + IsNews = item.IsNews, + IsKids = item.IsKids, + IsPremiere = item.IsPremiere, Type = "Program" }; + if (item.EndDate.HasValue) + { + dto.EndDate = item.EndDate.Value; + + dto.RunTimeTicks = (item.EndDate.Value - item.StartDate).Ticks; + } + if (channel != null) { dto.ChannelName = channel.Name; diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index f62efd9da6..92df78fb91 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -126,9 +126,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv { double number = 0; - if (!string.IsNullOrEmpty(i.ChannelInfo.Number)) + if (!string.IsNullOrEmpty(i.Number)) { - double.TryParse(i.ChannelInfo.Number, out number); + double.TryParse(i.Number, out number); } return number; @@ -140,9 +140,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv { double number = 0; - if (!string.IsNullOrEmpty(i.ChannelInfo.Number)) + if (!string.IsNullOrEmpty(i.Number)) { - double.TryParse(i.ChannelInfo.Number, out number); + double.TryParse(i.Number, out number); } return number; @@ -163,7 +163,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv } var returnChannels = allEnumerable - .Select(i => _tvDtoService.GetChannelInfoDto(i, GetCurrentProgram(i.ChannelInfo.Id), user)) + .Select(i => _tvDtoService.GetChannelInfoDto(i, GetCurrentProgram(i.ExternalId), user)) .ToArray(); var result = new QueryResult @@ -251,9 +251,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv var channel = GetInternalChannel(id); - _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ChannelInfo.Id); + _logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId); - var result = await service.GetChannelStream(channel.ChannelInfo.Id, cancellationToken).ConfigureAwait(false); + var result = await service.GetChannelStream(channel.ExternalId, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(result.Id)) { @@ -313,8 +313,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv isNew = true; } - item.ChannelInfo = channelInfo; + item.ChannelType = channelInfo.ChannelType; + item.ProviderImageUrl = channelInfo.ImageUrl; + item.HasProviderImage = channelInfo.HasImage; + item.ProviderImagePath = channelInfo.ImagePath; + item.ExternalId = channelInfo.Id; item.ServiceName = serviceName; + item.Number = channelInfo.Number; + + if (string.IsNullOrEmpty(item.Name)) + { + item.Name = channelInfo.Name; + } // Set this now so we don't cause additional file system access during provider executions item.ResetResolveArgs(fileInfo); @@ -346,9 +356,34 @@ namespace MediaBrowser.Server.Implementations.LiveTv } item.ChannelType = channelType; - item.ProgramInfo = info; item.ServiceName = serviceName; + item.Audio = info.Audio; + item.ExternalChannelId = info.ChannelId; + item.CommunityRating = info.CommunityRating; + item.EndDate = info.EndDate; + 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; + item.IsMovie = info.IsMovie; + item.IsNews = info.IsNews; + item.IsPremiere = info.IsPremiere; + item.IsRepeat = info.IsRepeat; + item.IsSeries = info.IsSeries; + item.IsSports = info.IsSports; + item.Name = info.Name; + item.OfficialRating = info.OfficialRating; + item.Overview = info.Overview; + item.PremiereDate = info.OriginalAirDate; + item.ProviderImagePath = info.ImagePath; + item.ProviderImageUrl = info.ImageUrl; + item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks; + item.StartDate = info.StartDate; + await item.RefreshMetadata(cancellationToken, forceSave: isNew, resetResolveArgs: false); return item; @@ -410,7 +445,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv private LiveTvChannel GetChannel(LiveTvProgram program) { - var programChannelId = program.ProgramInfo.ChannelId; + var programChannelId = program.ExternalChannelId; var internalProgramChannelId = _tvDtoService.GetInternalChannelId(program.ServiceName, programChannelId); @@ -438,28 +473,28 @@ namespace MediaBrowser.Server.Implementations.LiveTv { var val = query.MinEndDate.Value; - programs = programs.Where(i => i.ProgramInfo.EndDate >= val); + programs = programs.Where(i => i.EndDate.HasValue && i.EndDate.Value >= val); } if (query.MinStartDate.HasValue) { var val = query.MinStartDate.Value; - programs = programs.Where(i => i.ProgramInfo.StartDate >= val); + programs = programs.Where(i => i.StartDate >= val); } if (query.MaxEndDate.HasValue) { var val = query.MaxEndDate.Value; - programs = programs.Where(i => i.ProgramInfo.EndDate <= val); + programs = programs.Where(i => i.EndDate.HasValue && i.EndDate.Value <= val); } if (query.MaxStartDate.HasValue) { var val = query.MaxStartDate.Value; - programs = programs.Where(i => i.ProgramInfo.StartDate <= val); + programs = programs.Where(i => i.StartDate <= val); } if (query.ChannelIdList.Length > 0) @@ -469,7 +504,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv programs = programs.Where(i => { - var programChannelId = i.ProgramInfo.ChannelId; + var programChannelId = i.ExternalChannelId; var internalProgramChannelId = _tvDtoService.GetInternalChannelId(serviceName, programChannelId); @@ -537,13 +572,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv .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); + programs = programList.OrderByDescending(i => GetRecommendationScore(i, user.Id, serviceName, genres)) + .ThenBy(i => i.StartDate); if (query.Limit.HasValue) { programs = programs.Take(query.Limit.Value) - .OrderBy(i => i.ProgramInfo.StartDate); + .OrderBy(i => i.StartDate); } var returnArray = programs @@ -566,7 +601,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return result; } - private int GetRecommendationScore(ProgramInfo program, Guid userId, string serviceName, Dictionary genres) + private int GetRecommendationScore(LiveTvProgram program, Guid userId, string serviceName, Dictionary genres) { var score = 0; @@ -580,7 +615,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv score++; } - var internalChannelId = _tvDtoService.GetInternalChannelId(serviceName, program.ChannelId); + var internalChannelId = _tvDtoService.GetInternalChannelId(serviceName, program.ExternalChannelId); var channel = GetInternalChannel(internalChannelId); var channelUserdata = _userDataManager.GetUserData(userId, channel.GetUserDataKey()); @@ -724,9 +759,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv 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 channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false); - var programTasks = channelPrograms.Select(program => GetProgram(program, currentChannel.ChannelInfo.ChannelType, service.Name, cancellationToken)); + var programTasks = channelPrograms.Select(program => GetProgram(program, currentChannel.ChannelType, service.Name, cancellationToken)); var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false); programs.AddRange(programEntities); @@ -1033,7 +1068,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { var internalChannelId = _tvDtoService.GetInternalChannelId(service.Name, i.ChannelId); var channel = GetInternalChannel(internalChannelId); - channelName = channel == null ? null : channel.ChannelInfo.Name; + channelName = channel == null ? null : channel.Name; } return _tvDtoService.GetSeriesTimerInfoDto(i, service, channelName); @@ -1052,7 +1087,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { var channel = GetInternalChannel(id); - var dto = _tvDtoService.GetChannelInfoDto(channel, GetCurrentProgram(channel.ChannelInfo.Id), user); + var dto = _tvDtoService.GetChannelInfoDto(channel, GetCurrentProgram(channel.ExternalId), user); return Task.FromResult(dto); } @@ -1062,15 +1097,48 @@ namespace MediaBrowser.Server.Implementations.LiveTv var now = DateTime.UtcNow; return _programs.Values - .Where(i => string.Equals(externalChannelId, i.ProgramInfo.ChannelId, StringComparison.OrdinalIgnoreCase)) - .OrderBy(i => i.ProgramInfo.StartDate) - .SkipWhile(i => now >= i.ProgramInfo.EndDate) + .Where(i => string.Equals(externalChannelId, i.ExternalChannelId, StringComparison.OrdinalIgnoreCase)) + .OrderBy(i => i.StartDate) + .SkipWhile(i => now >= (i.EndDate ?? DateTime.MinValue)) .FirstOrDefault(); } - private async Task GetNewTimerDefaultsInternal(CancellationToken cancellationToken, ProgramInfo program = null) + private async Task GetNewTimerDefaultsInternal(CancellationToken cancellationToken, LiveTvProgram program = null) { - var info = await ActiveService.GetNewTimerDefaultsAsync(cancellationToken, program).ConfigureAwait(false); + ProgramInfo programInfo = null; + + if (program != null) + { + programInfo = new ProgramInfo + { + Audio = program.Audio, + ChannelId = program.ExternalChannelId, + CommunityRating = program.CommunityRating, + EndDate = program.EndDate ?? DateTime.MinValue, + EpisodeTitle = program.EpisodeTitle, + Genres = program.Genres, + HasImage = program.HasProviderImage, + Id = program.ExternalId, + IsHD = program.IsHD, + IsKids = program.IsKids, + IsLive = program.IsLive, + IsMovie = program.IsMovie, + IsNews = program.IsNews, + IsPremiere = program.IsPremiere, + IsRepeat = program.IsRepeat, + IsSeries = program.IsSeries, + IsSports = program.IsSports, + OriginalAirDate = program.PremiereDate, + Overview = program.Overview, + StartDate = program.StartDate, + ImagePath = program.ProviderImagePath, + ImageUrl = program.ProviderImageUrl, + Name = program.Name, + OfficialRating = program.OfficialRating + }; + } + + var info = await ActiveService.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false); info.Id = null; @@ -1088,7 +1156,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task GetNewTimerDefaults(string programId, CancellationToken cancellationToken) { - var program = GetInternalProgram(programId).ProgramInfo; + var program = GetInternalProgram(programId); var programDto = await GetProgram(programId, cancellationToken).ConfigureAwait(false); var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false); @@ -1104,13 +1172,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv info.Name = program.Name; info.ChannelId = programDto.ChannelId; info.ChannelName = programDto.ChannelName; - info.EndDate = program.EndDate; info.StartDate = program.StartDate; info.Name = program.Name; info.Overview = program.Overview; info.ProgramId = programDto.Id; info.ExternalProgramId = programDto.ExternalId; + if (program.EndDate.HasValue) + { + info.EndDate = program.EndDate.Value; + } + return info; } @@ -1299,8 +1371,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv { var programs = _programs.ToList(); - var startDate = programs.Select(i => i.Value.ProgramInfo.StartDate).Min(); - var endDate = programs.Select(i => i.Value.ProgramInfo.StartDate).Max(); + var startDate = programs.Select(i => i.Value.StartDate).Min(); + var endDate = programs.Select(i => i.Value.StartDate).Max(); return new GuideInfo { diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs index 7c343f77c6..041925cdd7 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs @@ -76,22 +76,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv private async Task DownloadImage(LiveTvProgram item, CancellationToken cancellationToken) { - var programInfo = item.ProgramInfo; - Stream imageStream = null; string contentType = null; - if (!string.IsNullOrEmpty(programInfo.ImagePath)) + if (!string.IsNullOrEmpty(item.ProviderImagePath)) { - contentType = "image/" + Path.GetExtension(programInfo.ImagePath).ToLower(); - imageStream = _fileSystem.GetFileStream(programInfo.ImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true); + contentType = "image/" + Path.GetExtension(item.ProviderImagePath).ToLower(); + imageStream = _fileSystem.GetFileStream(item.ProviderImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true); } - else if (!string.IsNullOrEmpty(programInfo.ImageUrl)) + else if (!string.IsNullOrEmpty(item.ProviderImageUrl)) { var options = new HttpRequestOptions { CancellationToken = cancellationToken, - Url = programInfo.ImageUrl + Url = item.ProviderImageUrl }; var response = await _httpClient.GetResponse(options).ConfigureAwait(false); @@ -105,7 +103,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv imageStream = response.Content; contentType = response.ContentType; } - else if (programInfo.HasImage ?? true) + else if (item.HasProviderImage ?? true) { var service = _liveTvManager.Services.FirstOrDefault(i => string.Equals(i.Name, item.ServiceName, StringComparison.OrdinalIgnoreCase)); @@ -113,7 +111,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv { try { - var response = await service.GetProgramImageAsync(programInfo.Id, programInfo.ChannelId, cancellationToken).ConfigureAwait(false); + var response = await service.GetProgramImageAsync(item.ExternalId, item.ExternalChannelId, cancellationToken).ConfigureAwait(false); if (response != null) { @@ -131,7 +129,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv if (imageStream != null) { // Dummy up the original url - var url = item.ServiceName + programInfo.Id; + var url = item.ServiceName + item.ExternalId; await _providerManager.SaveImage(item, imageStream, contentType, ImageType.Primary, null, url, cancellationToken).ConfigureAwait(false); return true;