simon 11 years ago
commit 37aab84b2a

@ -1,13 +1,13 @@
using System.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Logging;
using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ServiceStack.Web;
namespace MediaBrowser.Api
{
@ -52,11 +52,6 @@ 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>

@ -68,8 +68,6 @@ namespace MediaBrowser.Api.Library
var rootFolderPath = user != null ? user.RootFolderPath : appPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
ValidateNewMediaPath(fileSystem, rootFolderPath, path);
var shortcutFilename = Path.GetFileNameWithoutExtension(path);
var lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
@ -82,73 +80,5 @@ namespace MediaBrowser.Api.Library
fileSystem.CreateShortcut(lnk, path);
}
/// <summary>
/// Validates that a new media path can be added
/// </summary>
/// <param name="fileSystem">The file system.</param>
/// <param name="currentViewRootFolderPath">The current view root folder path.</param>
/// <param name="mediaPath">The media path.</param>
/// <exception cref="System.ArgumentException">
/// </exception>
private static void ValidateNewMediaPath(IFileSystem fileSystem, string currentViewRootFolderPath, string mediaPath)
{
var pathsInCurrentVIew = Directory.EnumerateFiles(currentViewRootFolderPath, ShortcutFileSearch, SearchOption.AllDirectories)
.Select(fileSystem.ResolveShortcut)
.ToList();
// Don't allow duplicate sub-paths within the same user library, or it will result in duplicate items
// See comments in IsNewPathValid
var duplicate = pathsInCurrentVIew
.FirstOrDefault(p => !IsNewPathValid(fileSystem, mediaPath, p));
if (!string.IsNullOrEmpty(duplicate))
{
throw new ArgumentException(string.Format("The path cannot be added to the library because {0} already exists.", duplicate));
}
// Make sure the current root folder doesn't already have a shortcut to the same path
duplicate = pathsInCurrentVIew
.FirstOrDefault(p => string.Equals(mediaPath, p, StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrEmpty(duplicate))
{
throw new ArgumentException(string.Format("The path {0} already exists in the library", mediaPath));
}
}
/// <summary>
/// Validates that a new path can be added based on an existing path
/// </summary>
/// <param name="fileSystem">The file system.</param>
/// <param name="newPath">The new path.</param>
/// <param name="existingPath">The existing path.</param>
/// <returns><c>true</c> if [is new path valid] [the specified new path]; otherwise, <c>false</c>.</returns>
private static bool IsNewPathValid(IFileSystem fileSystem, string newPath, string existingPath)
{
// Example: D:\Movies is the existing path
// D:\ cannot be added
// Neither can D:\Movies\Kids
// A D:\Movies duplicate is ok here since that will be caught later
if (string.Equals(newPath, existingPath, StringComparison.OrdinalIgnoreCase))
{
return true;
}
// If enforceSubPathRestriction is true, validate the D:\Movies\Kids scenario
if (fileSystem.ContainsSubPath(existingPath, newPath))
{
return false;
}
// Validate the D:\ scenario
if (fileSystem.ContainsSubPath(newPath, existingPath))
{
return false;
}
return true;
}
}
}

@ -154,6 +154,23 @@ namespace MediaBrowser.Api.LiveTv
public string MaxEndDate { get; set; }
}
[Route("/LiveTv/Programs/Recommended", "GET")]
[Api(Description = "Gets available live tv epgs..")]
public class GetRecommendedPrograms : IReturn<QueryResult<ProgramInfoDto>>
{
[ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")]
public string UserId { get; set; }
[ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? Limit { get; set; }
[ApiMember(Name = "IsAiring", Description = "Optional. Filter by programs that are currently airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? IsAiring { get; set; }
[ApiMember(Name = "HasAired", Description = "Optional. Filter by programs that have completed airing, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
public bool? HasAired { get; set; }
}
[Route("/LiveTv/Programs/{Id}", "GET")]
[Api(Description = "Gets a live tv program")]
public class GetProgram : IReturn<ProgramInfoDto>
@ -331,6 +348,21 @@ namespace MediaBrowser.Api.LiveTv
return ToOptimizedResult(result);
}
public object Get(GetRecommendedPrograms request)
{
var query = new RecommendedProgramQuery
{
UserId = request.UserId,
IsAiring = request.IsAiring,
Limit = request.Limit,
HasAired = request.HasAired
};
var result = _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).Result;
return ToOptimizedResult(result);
}
public object Post(GetPrograms request)
{
return Get(request);

@ -1,6 +1,5 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@ -734,7 +733,7 @@ namespace MediaBrowser.Api.Playback
return "-";
}
var type = InputType.AudioFile;
var type = InputType.File;
var inputPath = new[] { state.MediaPath };
@ -1044,6 +1043,7 @@ namespace MediaBrowser.Api.Playback
}
itemId = recording.Id;
//state.RunTimeTicks = recording.RunTimeTicks;
state.SendInputOverStandardInput = recording.RecordingInfo.Status == RecordingStatus.InProgress;
}
else if (string.Equals(request.Type, "Channel", StringComparison.OrdinalIgnoreCase))
@ -1092,6 +1092,7 @@ namespace MediaBrowser.Api.Playback
: video.PlayableStreamFileNames.ToList();
}
state.RunTimeTicks = item.RunTimeTicks;
itemId = item.Id;
}

@ -1,9 +1,9 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
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;

@ -1,11 +1,11 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Common.Net;
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.Configuration;
using MediaBrowser.Model.Dto;
@ -75,18 +75,23 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.Object.</returns>
protected object ProcessRequest(StreamRequest request)
{
var state = GetState(request, CancellationToken.None).Result;
return ProcessRequestAsync(state).Result;
return ProcessRequestAsync(request).Result;
}
/// <summary>
/// Processes the request async.
/// </summary>
/// <param name="state">The state.</param>
/// <param name="request">The request.</param>
/// <returns>Task{System.Object}.</returns>
public async Task<object> ProcessRequestAsync(StreamState state)
/// <exception cref="ArgumentException">
/// A video bitrate is required
/// or
/// An audio bitrate is required
/// </exception>
private async Task<object> ProcessRequestAsync(StreamRequest request)
{
var state = GetState(request, CancellationToken.None).Result;
if (!state.VideoRequest.VideoBitRate.HasValue && (!state.VideoRequest.VideoCodec.HasValue || state.VideoRequest.VideoCodec.Value != VideoCodecs.Copy))
{
throw new ArgumentException("A video bitrate is required");
@ -155,7 +160,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// <param name="state">The state.</param>
/// <param name="audioBitrate">The audio bitrate.</param>
/// <param name="videoBitrate">The video bitrate.</param>
private void GetPlaylistBitrates(StreamState state, out int audioBitrate, out int videoBitrate)
protected void GetPlaylistBitrates(StreamState state, out int audioBitrate, out int videoBitrate)
{
var audioBitrateParam = GetAudioBitrateParam(state);
var videoBitrateParam = GetVideoBitrateParam(state);
@ -269,7 +274,7 @@ namespace MediaBrowser.Api.Playback.Hls
var threads = GetNumberOfThreads(false);
var args = string.Format("{0}{1} {2} {3} -i {4}{5} -map_metadata -1 -threads {6} {7} {8} -sc_threshold 0 {9} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{10}\"",
var args = string.Format("{0}{1} {2} {3} -i {4}{5} -map_metadata -1 -threads {6} {7} {8} -sc_threshold 0 {9} -hls_time {10} -start_number 0 -hls_list_size 1440 \"{11}\"",
itsOffset,
probeSize,
GetUserAgentParam(state.MediaPath),
@ -280,6 +285,7 @@ namespace MediaBrowser.Api.Playback.Hls
GetMapArgs(state),
GetVideoArguments(state, performSubtitleConversions),
GetAudioArguments(state),
state.SegmentLength.ToString(UsCulture),
outputPath
).Trim();
@ -291,10 +297,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 {2} -hls_time 10 -start_number 0 -hls_list_size 1440 \"{1}\"",
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}\"",
threads,
lowBitratePath,
bitrate / 2);
bitrate / 2,
state.SegmentLength.ToString(UsCulture),
lowBitratePath);
args += " " + lowBitrateParams;
}

@ -1,13 +1,18 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
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.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback.Hls
{
@ -28,6 +33,29 @@ 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
{
}
/// <summary>
/// Class VideoHlsService
/// </summary>
@ -38,6 +66,128 @@ 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;
}
private async Task<object> 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("hls/{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<string, string>());
}
private async Task<object> 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<string, string>());
}
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();
}
/// <summary>
/// Gets the specified request.
/// </summary>

@ -1,10 +1,10 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using ServiceStack;

@ -1,12 +1,12 @@
using System;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
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;

@ -1,10 +1,10 @@
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using ServiceStack;

@ -1,8 +1,8 @@
using System.Threading;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using System.Collections.Generic;
using System.IO;
using System.Threading;
namespace MediaBrowser.Api.Playback
{
@ -54,5 +54,9 @@ namespace MediaBrowser.Api.Playback
public CancellationTokenSource StandardInputCancellationTokenSource { get; set; }
public string LiveTvStreamId { get; set; }
public int SegmentLength = 10;
public long? RunTimeTicks;
}
}

@ -65,8 +65,6 @@
<Compile Include="IO\IFileSystem.cs" />
<Compile Include="IO\ProgressStream.cs" />
<Compile Include="IO\StreamDefaults.cs" />
<Compile Include="MediaInfo\MediaInfoResult.cs" />
<Compile Include="MediaInfo\IMediaEncoder.cs" />
<Compile Include="Net\BasePeriodicWebSocketListener.cs" />
<Compile Include="Configuration\IApplicationPaths.cs" />
<Compile Include="Net\HttpRequestOptions.cs" />

@ -109,7 +109,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <returns>System.String.</returns>
public override string GetUserDataKey()
{
var parent = Parent as MusicAlbum;
var parent = FindParent<MusicAlbum>();
if (parent != null)
{

@ -1,5 +1,4 @@
using System.IO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
using System.Collections.Generic;
@ -241,5 +240,14 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <returns>GuideInfo.</returns>
GuideInfo GetGuideInfo();
/// <summary>
/// Gets the recommended programs.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{QueryResult{ProgramInfoDto}}.</returns>
Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query,
CancellationToken cancellationToken);
}
}

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -37,7 +38,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken);
/// <summary>
/// Deletes the recording asynchronous.
/// </summary>
@ -77,7 +78,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken);
/// <summary>
/// Gets the channel image asynchronous. This only needs to be implemented if an image path or url cannot be supplied to ChannelInfo
/// </summary>
@ -102,7 +103,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{ImageResponseInfo}.</returns>
Task<StreamResponseInfo> GetProgramImageAsync(string programId, string channelId, CancellationToken cancellationToken);
/// <summary>
/// Gets the recordings asynchronous.
/// </summary>
@ -123,21 +124,23 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{TimerInfo}.</returns>
Task<SeriesTimerInfo> GetNewTimerDefaultsAsync(CancellationToken cancellationToken);
/// <summary>
/// Gets the series timers asynchronous.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{SeriesTimerInfo}}.</returns>
Task<IEnumerable<SeriesTimerInfo>> GetSeriesTimersAsync(CancellationToken cancellationToken);
/// <summary>
/// Gets the programs asynchronous.
/// </summary>
/// <param name="channelId">The channel identifier.</param>
/// <param name="startDateUtc">The start date UTC.</param>
/// <param name="endDateUtc">The end date UTC.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{ProgramInfo}}.</returns>
Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, CancellationToken cancellationToken);
Task<IEnumerable<ProgramInfo>> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken);
/// <summary>
/// Gets the recording stream.
@ -162,5 +165,13 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task CloseLiveStream(string id, CancellationToken cancellationToken);
/// <summary>
/// Records the live stream.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task RecordLiveStream(string id, CancellationToken cancellationToken);
}
}

@ -1,5 +1,6 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.LiveTv;
using System;
namespace MediaBrowser.Controller.LiveTv
{
@ -28,6 +29,26 @@ namespace MediaBrowser.Controller.LiveTv
}
}
public bool IsAiring
{
get
{
var now = DateTime.UtcNow;
return now >= ProgramInfo.StartDate && now < ProgramInfo.EndDate;
}
}
public bool HasAired
{
get
{
var now = DateTime.UtcNow;
return now >= ProgramInfo.EndDate;
}
}
public override string GetClientTypeName()
{
return "Program";

@ -125,6 +125,8 @@
<Compile Include="LiveTv\SeriesTimerInfo.cs" />
<Compile Include="LiveTv\TimerInfo.cs" />
<Compile Include="Localization\ILocalizationManager.cs" />
<Compile Include="MediaInfo\IMediaEncoder.cs" />
<Compile Include="MediaInfo\InternalMediaInfoResult.cs" />
<Compile Include="Net\IHasResultFactory.cs" />
<Compile Include="Net\IHttpResultFactory.cs" />
<Compile Include="Net\IHttpServer.cs" />

@ -1,6 +1,5 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
@ -178,7 +177,7 @@ namespace MediaBrowser.Controller.MediaInfo
Directory.CreateDirectory(parentPath);
await _encoder.ExtractImage(inputPath, type, video.Video3DFormat, time, path, cancellationToken).ConfigureAwait(false);
await _encoder.ExtractImage(inputPath, type, false, video.Video3DFormat, time, path, cancellationToken).ConfigureAwait(false);
chapter.ImagePath = path;
changesMade = true;
}

@ -3,7 +3,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Common.MediaInfo
namespace MediaBrowser.Controller.MediaInfo
{
/// <summary>
/// Interface IMediaEncoder
@ -27,12 +27,13 @@ namespace MediaBrowser.Common.MediaInfo
/// </summary>
/// <param name="inputFiles">The input files.</param>
/// <param name="type">The type.</param>
/// <param name="isAudio">if set to <c>true</c> [is audio].</param>
/// <param name="threedFormat">The threed format.</param>
/// <param name="offset">The offset.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task ExtractImage(string[] inputFiles, InputType type, Video3DFormat? threedFormat, TimeSpan? offset, string outputPath, CancellationToken cancellationToken);
Task ExtractImage(string[] inputFiles, InputType type, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, string outputPath, CancellationToken cancellationToken);
/// <summary>
/// Extracts the text subtitle.
@ -62,7 +63,7 @@ namespace MediaBrowser.Common.MediaInfo
/// <param name="type">The type.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task<MediaInfoResult> GetMediaInfo(string[] inputFiles, InputType type, CancellationToken cancellationToken);
Task<InternalMediaInfoResult> GetMediaInfo(string[] inputFiles, InputType type, CancellationToken cancellationToken);
/// <summary>
/// Gets the probe size argument.
@ -86,13 +87,9 @@ namespace MediaBrowser.Common.MediaInfo
public enum InputType
{
/// <summary>
/// The audio file
/// The file
/// </summary>
AudioFile,
/// <summary>
/// The video file
/// </summary>
VideoFile,
File,
/// <summary>
/// The bluray
/// </summary>

@ -1,12 +1,12 @@
using MediaBrowser.Model.Entities;
using System.Collections.Generic;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Common.MediaInfo
namespace MediaBrowser.Controller.MediaInfo
{
/// <summary>
/// Class MediaInfoResult
/// </summary>
public class MediaInfoResult
public class InternalMediaInfoResult
{
/// <summary>
/// Gets or sets the streams.

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
@ -29,7 +28,7 @@ namespace MediaBrowser.Controller.MediaInfo
{
var inputPath = isoMount == null ? new[] { videoPath } : new[] { isoMount.MountedPath };
type = InputType.VideoFile;
type = InputType.File;
switch (videoType)
{
@ -87,7 +86,7 @@ namespace MediaBrowser.Controller.MediaInfo
/// <returns>InputType.</returns>
public static InputType GetInputType(VideoType? videoType, IsoType? isoType)
{
var type = InputType.AudioFile;
var type = InputType.File;
if (videoType.HasValue)
{
@ -119,12 +118,22 @@ namespace MediaBrowser.Controller.MediaInfo
return type;
}
public static IEnumerable<MediaStream> GetMediaStreams(MediaInfoResult data)
public static Model.Entities.MediaInfo GetMediaInfo(InternalMediaInfoResult data)
{
var internalStreams = data.streams ?? new MediaStreamInfo[] { };
return internalStreams.Select(s => GetMediaStream(s, data.format))
.Where(i => i != null);
var info = new Model.Entities.MediaInfo();
info.MediaStreams = internalStreams.Select(s => GetMediaStream(s, data.format))
.Where(i => i != null)
.ToList();
if (data.format != null)
{
info.Format = data.format.format_name;
}
return info;
}
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");

@ -223,7 +223,10 @@ namespace MediaBrowser.Model.Configuration
public string TranscodingTempPath { get; set; }
public bool EnableAutomaticRestart { get; set; }
public LiveTvOptions LiveTvOptions { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
/// </summary>
@ -287,6 +290,8 @@ namespace MediaBrowser.Model.Configuration
{
MaxBackdrops = 1
};
LiveTvOptions = new LiveTvOptions();
}
}
@ -303,4 +308,9 @@ namespace MediaBrowser.Model.Configuration
HighQuality,
MaxQuality
}
public class LiveTvOptions
{
public int? GuideDays { get; set; }
}
}

@ -1,4 +1,5 @@
using MediaBrowser.Model.Entities;
using System.Diagnostics;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@ -10,6 +11,7 @@ namespace MediaBrowser.Model.Dto
/// This is strictly used as a data transfer object from the api layer.
/// This holds information about a BaseItem in a format that is convenient for the client.
/// </summary>
[DebuggerDisplay("Name = {Name}, ID = {Id}, Type = {Type}")]
public class BaseItemDto : IHasProviderIds, INotifyPropertyChanged, IItemDto
{
/// <summary>

@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.Serialization;
namespace MediaBrowser.Model.Dto
@ -7,6 +8,7 @@ namespace MediaBrowser.Model.Dto
/// <summary>
/// This is used by the api to get information about a Person within a BaseItem
/// </summary>
[DebuggerDisplay("Name = {Name}, Role = {Role}, Type = {Type}")]
public class BaseItemPerson : INotifyPropertyChanged
{
/// <summary>

@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.Serialization;
namespace MediaBrowser.Model.Dto
@ -7,6 +8,7 @@ namespace MediaBrowser.Model.Dto
/// <summary>
/// Class ChapterInfo
/// </summary>
[DebuggerDisplay("Name = {Name}")]
public class ChapterInfoDto : INotifyPropertyChanged
{
/// <summary>

@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.Serialization;
namespace MediaBrowser.Model.Dto
@ -7,6 +8,7 @@ namespace MediaBrowser.Model.Dto
/// <summary>
/// Class StudioDto
/// </summary>
[DebuggerDisplay("Name = {Name}")]
public class StudioDto
{
/// <summary>

@ -1,4 +1,5 @@
using System.ComponentModel;
using System.Diagnostics;
using MediaBrowser.Model.Configuration;
using System;
using System.Runtime.Serialization;
@ -8,6 +9,7 @@ namespace MediaBrowser.Model.Dto
/// <summary>
/// Class UserDto
/// </summary>
[DebuggerDisplay("Name = {Name}, ID = {Id}, HasPassword = {HasPassword}")]
public class UserDto : INotifyPropertyChanged, IItemDto
{
/// <summary>

@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.Runtime.Serialization;
namespace MediaBrowser.Model.Entities
@ -6,6 +7,7 @@ namespace MediaBrowser.Model.Entities
/// <summary>
/// This is a stub class containing only basic information about an item
/// </summary>
[DebuggerDisplay("Name = {Name}, ID = {Id}, Type = {Type}")]
public class BaseItemInfo
{
/// <summary>

@ -1,9 +1,12 @@

using System.Collections.Generic;
using System.Diagnostics;
namespace MediaBrowser.Model.Entities
{
/// <summary>
/// Class MediaStream
/// </summary>
[DebuggerDisplay("StreamType = {Type}")]
public class MediaStream
{
/// <summary>
@ -145,4 +148,24 @@ namespace MediaBrowser.Model.Entities
/// </summary>
Subtitle
}
public class MediaInfo
{
/// <summary>
/// Gets or sets the media streams.
/// </summary>
/// <value>The media streams.</value>
public List<MediaStream> MediaStreams { get; set; }
/// <summary>
/// Gets or sets the format.
/// </summary>
/// <value>The format.</value>
public string Format { get; set; }
public MediaInfo()
{
MediaStreams = new List<MediaStream>();
}
}
}

@ -32,4 +32,31 @@ namespace MediaBrowser.Model.LiveTv
ChannelIdList = new string[] { };
}
}
public class RecommendedProgramQuery
{
/// <summary>
/// Gets or sets the user identifier.
/// </summary>
/// <value>The user identifier.</value>
public string UserId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is airing.
/// </summary>
/// <value><c>true</c> if this instance is airing; otherwise, <c>false</c>.</value>
public bool? IsAiring { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance has aired.
/// </summary>
/// <value><c>null</c> if [has aired] contains no value, <c>true</c> if [has aired]; otherwise, <c>false</c>.</value>
public bool? HasAired { get; set; }
/// <summary>
/// The maximum number of items to return
/// </summary>
/// <value>The limit.</value>
public int? Limit { get; set; }
}
}

@ -1,10 +1,12 @@
using MediaBrowser.Model.Entities;
using System.Diagnostics;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace MediaBrowser.Model.Session
{
[DebuggerDisplay("Client = {Client}, Username = {UserName}")]
public class SessionInfoDto : INotifyPropertyChanged
{
/// <summary>

@ -1,9 +1,9 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
@ -163,7 +163,7 @@ namespace MediaBrowser.Providers.MediaInfo
Directory.CreateDirectory(parentPath);
await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.AudioFile, null, null, path, cancellationToken).ConfigureAwait(false);
await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.File, true, null, null, path, cancellationToken).ConfigureAwait(false);
}
finally
{

@ -1,5 +1,4 @@
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Providers;
@ -104,11 +103,11 @@ namespace MediaBrowser.Providers.MediaInfo
/// <exception cref="System.ArgumentNullException">inputPath
/// or
/// cache</exception>
protected async Task<MediaInfoResult> GetMediaInfo(BaseItem item, IIsoMount isoMount, CancellationToken cancellationToken)
protected async Task<InternalMediaInfoResult> GetMediaInfo(BaseItem item, IIsoMount isoMount, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var type = InputType.AudioFile;
var type = InputType.File;
var inputPath = isoMount == null ? new[] { item.Path } : new[] { isoMount.MountedPath };
var video = item as Video;
@ -146,7 +145,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// Normalizes the FF probe result.
/// </summary>
/// <param name="result">The result.</param>
protected void NormalizeFFProbeResult(MediaInfoResult result)
protected void NormalizeFFProbeResult(InternalMediaInfoResult result)
{
if (result.format != null && result.format.tags != null)
{

@ -1,5 +1,4 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
@ -58,9 +57,9 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="data">The data.</param>
/// <returns>Task.</returns>
protected Task Fetch(Audio audio, CancellationToken cancellationToken, MediaInfoResult data)
protected Task Fetch(Audio audio, CancellationToken cancellationToken, InternalMediaInfoResult data)
{
var mediaStreams = MediaEncoderHelpers.GetMediaStreams(data).ToList();
var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams;
audio.HasEmbeddedImage = mediaStreams.Any(i => i.Type == MediaStreamType.Video);

@ -1,5 +1,4 @@
using DvdLib.Ifo;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Localization;
@ -310,7 +309,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="data">The data.</param>
/// <param name="isoMount">The iso mount.</param>
/// <returns>Task.</returns>
protected async Task Fetch(Video video, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken, MediaInfoResult data, IIsoMount isoMount)
protected async Task Fetch(Video video, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount)
{
if (data.format != null)
{
@ -323,7 +322,7 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
var mediaStreams = MediaEncoderHelpers.GetMediaStreams(data).ToList();
var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams;
var chapters = data.Chapters ?? new List<ChapterInfo>();
@ -370,7 +369,7 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="video">The video.</param>
/// <param name="force">if set to <c>true</c> [force].</param>
/// <param name="data">The data.</param>
private void FetchWtvInfo(Video video, bool force, MediaInfoResult data)
private void FetchWtvInfo(Video video, bool force, InternalMediaInfoResult data)
{
if (data.format == null || data.format.tags == null)
{

@ -1,5 +1,4 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@ -255,7 +254,7 @@ namespace MediaBrowser.Providers.MediaInfo
var inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type);
await _mediaEncoder.ExtractImage(inputPath, type, video.Video3DFormat, imageOffset, path, cancellationToken).ConfigureAwait(false);
await _mediaEncoder.ExtractImage(inputPath, type, false, video.Video3DFormat, imageOffset, path, cancellationToken).ConfigureAwait(false);
video.PrimaryImagePath = path;
}

@ -360,19 +360,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
var compress = ShouldCompressResponse(requestContext, contentType);
var hasOptions = GetStaticResult(requestContext, responseHeaders, contentType, factoryFn, compress, isHeadRequest);
var hasOptions = GetStaticResult(requestContext, responseHeaders, contentType, factoryFn, compress, isHeadRequest).Result;
return GetStaticResultTask(hasOptions, responseHeaders);
}
private async Task<object> GetStaticResultTask(Task<IHasOptions> optionsTask,
IEnumerable<KeyValuePair<string, string>> responseHeaders)
{
var options = await optionsTask.ConfigureAwait(false);
AddResponseHeaders(options, responseHeaders);
AddResponseHeaders(hasOptions, responseHeaders);
return options;
return hasOptions;
}
/// <summary>
@ -670,4 +662,4 @@ namespace MediaBrowser.Server.Implementations.HttpServer
throw error;
}
}
}
}

@ -2,6 +2,7 @@
using ServiceStack.Web;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
@ -13,6 +14,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
public class StreamWriter : IStreamWriter, IHasOptions
{
private ILogger Logger { get; set; }
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
/// <summary>
/// Gets or sets the source stream.
@ -50,6 +53,11 @@ namespace MediaBrowser.Server.Implementations.HttpServer
Logger = logger;
Options["Content-Type"] = contentType;
if (source.CanSeek)
{
Options["Content-Length"] = source.Length.ToString(UsCulture);
}
}
/// <summary>

@ -368,7 +368,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return null;
}
private const string InternalVersionNumber = "2";
private const string InternalVersionNumber = "3";
public Guid GetInternalChannelId(string serviceName, string externalId)
{

@ -1,17 +1,20 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -23,30 +26,37 @@ namespace MediaBrowser.Server.Implementations.LiveTv
/// <summary>
/// Class LiveTvManager
/// </summary>
public class LiveTvManager : ILiveTvManager
public class LiveTvManager : ILiveTvManager, IDisposable
{
private readonly IServerApplicationPaths _appPaths;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
private readonly ILibraryManager _libraryManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly LiveTvDtoService _tvDtoService;
private readonly List<ILiveTvService> _services = new List<ILiveTvService>();
private readonly ConcurrentDictionary<string, LiveStreamInfo> _openStreams =
new ConcurrentDictionary<string, LiveStreamInfo>();
private List<Guid> _channelIdList = new List<Guid>();
private Dictionary<Guid, LiveTvProgram> _programs = new Dictionary<Guid, LiveTvProgram>();
public LiveTvManager(IServerApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager)
public LiveTvManager(IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, IMediaEncoder mediaEncoder)
{
_appPaths = appPaths;
_config = config;
_fileSystem = fileSystem;
_logger = logger;
_itemRepo = itemRepo;
_userManager = userManager;
_libraryManager = libraryManager;
_mediaEncoder = mediaEncoder;
_userDataManager = userDataManager;
_tvDtoService = new LiveTvDtoService(dtoService, userDataManager, imageProcessor, logger, _itemRepo);
}
@ -180,7 +190,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var recording = recordings.First(i => _tvDtoService.GetInternalRecordingId(service.Name, i.Id) == new Guid(id));
return await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false);
var result = await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(result.Id))
{
_openStreams.AddOrUpdate(result.Id, result, (key, info) => result);
}
return result;
}
public async Task<LiveStreamInfo> GetChannelStream(string id, CancellationToken cancellationToken)
@ -189,12 +206,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var channel = GetInternalChannel(id);
return await service.GetChannelStream(channel.ChannelInfo.Id, cancellationToken).ConfigureAwait(false);
var result = await service.GetChannelStream(channel.ChannelInfo.Id, cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(result.Id))
{
_openStreams.AddOrUpdate(result.Id, result, (key, info) => result);
}
return result;
}
private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
{
var path = Path.Combine(_appPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(serviceName), _fileSystem.GetValidFilename(channelInfo.Name));
var path = Path.Combine(_config.ApplicationPaths.ItemsByNamePath, "channels", _fileSystem.GetValidFilename(channelInfo.Name));
var fileInfo = new DirectoryInfo(path);
@ -407,7 +431,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
var returnArray = programs
.OrderBy(i => i.ProgramInfo.StartDate)
.Select(i =>
{
var channel = GetChannel(i);
@ -429,6 +452,138 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return result;
}
public async Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query, CancellationToken cancellationToken)
{
IEnumerable<LiveTvProgram> programs = _programs.Values;
var user = _userManager.GetUserById(new Guid(query.UserId));
// Avoid implicitly captured closure
var currentUser = user;
programs = programs.Where(i => i.IsParentalAllowed(currentUser));
if (query.IsAiring.HasValue)
{
var val = query.IsAiring.Value;
programs = programs.Where(i => i.IsAiring == val);
}
if (query.HasAired.HasValue)
{
var val = query.HasAired.Value;
programs = programs.Where(i => i.HasAired == val);
}
var serviceName = ActiveService.Name;
var programList = programs.ToList();
var genres = programList.SelectMany(i => i.Genres)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Select(i => _libraryManager.GetGenre(i))
.ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
programs = programList.OrderByDescending(i => GetRecommendationScore(i.ProgramInfo, user.Id, serviceName, genres))
.ThenBy(i => i.ProgramInfo.StartDate);
if (query.Limit.HasValue)
{
programs = programs.Take(query.Limit.Value)
.OrderBy(i => i.ProgramInfo.StartDate);
}
var returnArray = programs
.Select(i =>
{
var channel = GetChannel(i);
var channelName = channel == null ? null : channel.ChannelInfo.Name;
return _tvDtoService.GetProgramInfoDto(i, channelName, user);
})
.ToArray();
await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false);
var result = new QueryResult<ProgramInfoDto>
{
Items = returnArray,
TotalRecordCount = returnArray.Length
};
return result;
}
private int GetRecommendationScore(ProgramInfo program, Guid userId, string serviceName, Dictionary<string, Genre> genres)
{
var score = 0;
if (program.IsLive)
{
score++;
}
if (program.IsSeries && !program.IsRepeat)
{
score++;
}
var internalChannelId = _tvDtoService.GetInternalChannelId(serviceName, program.ChannelId);
var channel = GetInternalChannel(internalChannelId);
var channelUserdata = _userDataManager.GetUserData(userId, channel.GetUserDataKey());
if ((channelUserdata.Likes ?? false))
{
score += 2;
}
else if (!(channelUserdata.Likes ?? true))
{
score -= 2;
}
if (channelUserdata.IsFavorite)
{
score += 3;
}
score += GetGenreScore(program.Genres, userId, genres);
return score;
}
private int GetGenreScore(IEnumerable<string> programGenres, Guid userId, Dictionary<string, Genre> genres)
{
return programGenres.Select(i =>
{
var score = 0;
Genre genre;
if (genres.TryGetValue(i, out genre))
{
var genreUserdata = _userDataManager.GetUserData(userId, genre.GetUserDataKey());
if ((genreUserdata.Likes ?? false))
{
score++;
}
else if (!(genreUserdata.Likes ?? true))
{
score--;
}
if (genreUserdata.IsFavorite)
{
score += 2;
}
}
return score;
}).Sum();
}
private async Task AddRecordingInfo(IEnumerable<ProgramInfoDto> programs, CancellationToken cancellationToken)
{
var timers = await ActiveService.GetTimersAsync(cancellationToken).ConfigureAwait(false);
@ -505,6 +660,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
numComplete = 0;
var programs = new List<LiveTvProgram>();
var guideDays = GetGuideDays(list.Count);
foreach (var item in list)
{
// Avoid implicitly captured closure
@ -512,7 +669,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv
try
{
var channelPrograms = await service.GetProgramsAsync(currentChannel.ChannelInfo.Id, cancellationToken).ConfigureAwait(false);
var start = DateTime.UtcNow.AddHours(-1);
var end = start.AddDays(guideDays);
var channelPrograms = await service.GetProgramsAsync(currentChannel.ChannelInfo.Id, start, end, cancellationToken).ConfigureAwait(false);
var programTasks = channelPrograms.Select(program => GetProgram(program, currentChannel.ChannelInfo.ChannelType, service.Name, cancellationToken));
var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false);
@ -538,6 +698,23 @@ namespace MediaBrowser.Server.Implementations.LiveTv
_programs = programs.ToDictionary(i => i.Id);
}
private double GetGuideDays(int channelCount)
{
if (_config.Configuration.LiveTvOptions.GuideDays.HasValue)
{
return _config.Configuration.LiveTvOptions.GuideDays.Value;
}
var programsPerDay = channelCount * 48;
const int maxPrograms = 32000;
var days = Math.Round(((double)maxPrograms) / programsPerDay);
// No less than 2, no more than 14
return Math.Max(2, Math.Min(days, 14));
}
private async Task<IEnumerable<Tuple<string, ChannelInfo>>> GetChannels(ILiveTvService service, CancellationToken cancellationToken)
{
var channels = await service.GetChannelsAsync(cancellationToken).ConfigureAwait(false);
@ -1047,5 +1224,36 @@ namespace MediaBrowser.Server.Implementations.LiveTv
EndDate = endDate
};
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
}
private readonly object _disposeLock = new object();
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
lock (_disposeLock)
{
foreach (var stream in _openStreams.Values.ToList())
{
var task = CloseLiveStream(stream.Id, CancellationToken.None);
Task.WaitAll(task);
}
_openStreams.Clear();
}
}
}
}
}

@ -1,6 +1,6 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Controller.MediaInfo;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
@ -104,10 +104,10 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// <param name="type">The type.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task<MediaInfoResult> GetMediaInfo(string[] inputFiles, InputType type,
public Task<InternalMediaInfoResult> GetMediaInfo(string[] inputFiles, InputType type,
CancellationToken cancellationToken)
{
return GetMediaInfoInternal(GetInputArgument(inputFiles, type), type != InputType.AudioFile,
return GetMediaInfoInternal(GetInputArgument(inputFiles, type), type != InputType.File,
GetProbeSizeArgument(type), cancellationToken);
}
@ -125,8 +125,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
switch (type)
{
case InputType.Dvd:
case InputType.VideoFile:
case InputType.AudioFile:
case InputType.File:
inputPath = GetConcatInputArgument(inputFiles);
break;
case InputType.Bluray:
@ -173,7 +172,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{MediaInfoResult}.</returns>
/// <exception cref="System.ApplicationException"></exception>
private async Task<MediaInfoResult> GetMediaInfoInternal(string inputPath, bool extractChapters,
private async Task<InternalMediaInfoResult> GetMediaInfoInternal(string inputPath, bool extractChapters,
string probeSizeArgument,
CancellationToken cancellationToken)
{
@ -206,7 +205,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
await _ffProbeResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
MediaInfoResult result;
InternalMediaInfoResult result;
string standardError = null;
try
@ -236,7 +235,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
process.BeginErrorReadLine();
}
result = _jsonSerializer.DeserializeFromStream<MediaInfoResult>(process.StandardOutput.BaseStream);
result = _jsonSerializer.DeserializeFromStream<InternalMediaInfoResult>(process.StandardOutput.BaseStream);
if (extractChapters)
{
@ -307,7 +306,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// </summary>
/// <param name="result">The result.</param>
/// <param name="standardError">The standard error.</param>
private void AddChapters(MediaInfoResult result, string standardError)
private void AddChapters(InternalMediaInfoResult result, string standardError)
{
var lines = standardError.Split('\n').Select(l => l.TrimStart());
@ -797,19 +796,20 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// </summary>
/// <param name="inputFiles">The input files.</param>
/// <param name="type">The type.</param>
/// <param name="isAudio">if set to <c>true</c> [is audio].</param>
/// <param name="threedFormat">The threed format.</param>
/// <param name="offset">The offset.</param>
/// <param name="outputPath">The output path.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentException">Must use inputPath list overload</exception>
public async Task ExtractImage(string[] inputFiles, InputType type, Video3DFormat? threedFormat, TimeSpan? offset, string outputPath, CancellationToken cancellationToken)
public async Task ExtractImage(string[] inputFiles, InputType type, bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, string outputPath, CancellationToken cancellationToken)
{
var resourcePool = type == InputType.AudioFile ? _audioImageResourcePool : _videoImageResourcePool;
var resourcePool = isAudio ? _audioImageResourcePool : _videoImageResourcePool;
var inputArgument = GetInputArgument(inputFiles, type);
if (type != InputType.AudioFile)
if (!isAudio)
{
try
{

@ -6,7 +6,6 @@ using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Implementations;
using MediaBrowser.Common.Implementations.ScheduledTasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller;
@ -284,9 +283,6 @@ namespace MediaBrowser.ServerApplication
DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor);
RegisterSingleInstance(DtoService);
LiveTvManager = new LiveTvManager(ApplicationPaths, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager);
RegisterSingleInstance(LiveTvManager);
progress.Report(15);
var innerProgress = new ActionableProgress<double>();
@ -295,6 +291,9 @@ namespace MediaBrowser.ServerApplication
await RegisterMediaEncoder(innerProgress).ConfigureAwait(false);
progress.Report(90);
LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, MediaEncoder);
RegisterSingleInstance(LiveTvManager);
var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false));
var itemsTask = Task.Run(async () => await ConfigureItemRepositories().ConfigureAwait(false));
var userdataTask = Task.Run(async () => await ConfigureUserDataRepositories().ConfigureAwait(false));

@ -505,6 +505,7 @@ namespace MediaBrowser.WebDashboard.Api
"livetvtimer.js",
"livetvseriestimer.js",
"livetvseriestimers.js",
"livetvsettings.js",
"livetvsuggested.js",
"livetvtimers.js",
"loginpage.js",

@ -438,7 +438,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
options = options || {};
if (options.channelIds) {
if (options.channelIds && options.channelIds.length > 1800) {
return self.ajax({
type: "POST",
@ -458,6 +458,17 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
}
};
self.getLiveTvRecommendedPrograms = function (options) {
options = options || {};
return self.ajax({
type: "GET",
url: self.getUrl("LiveTv/Programs/Recommended", options),
dataType: "json"
});
};
self.getLiveTvRecordings = function (options) {
var url = self.getUrl("LiveTv/Recordings", options || {});

@ -184,6 +184,9 @@
<Content Include="dashboard-ui\livetvseriestimer.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\livetvsettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\livetvtimers.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@ -433,6 +436,9 @@
<Content Include="dashboard-ui\scripts\livetvseriestimer.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\livetvsettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\livetvtimer.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MediaBrowser.ApiClient.Javascript" version="3.0.224" targetFramework="net45" />
<package id="MediaBrowser.ApiClient.Javascript" version="3.0.226" targetFramework="net45" />
</packages>

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
<version>3.0.298</version>
<version>3.0.299</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.298" />
<dependency id="MediaBrowser.Common" version="3.0.299" />
<dependency id="NLog" version="2.1.0" />
<dependency id="SimpleInjector" version="2.4.0" />
<dependency id="sharpcompress" version="0.10.2" />

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

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
<version>3.0.298</version>
<version>3.0.299</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.298" />
<dependency id="MediaBrowser.Common" version="3.0.299" />
</dependencies>
</metadata>
<files>

Loading…
Cancel
Save