aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs15
-rw-r--r--MediaBrowser.Api/ChannelService.cs12
-rw-r--r--MediaBrowser.Api/Images/ImageByNameService.cs10
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj1
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs179
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs24
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs10
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs9
-rw-r--r--MediaBrowser.Api/Playback/Progressive/AudioService.cs4
-rw-r--r--MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs6
-rw-r--r--MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs1
-rw-r--r--MediaBrowser.Api/Playback/Progressive/VideoService.cs7
-rw-r--r--MediaBrowser.Api/Playback/ProgressiveStreamService.cs73
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs9
-rw-r--r--MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs4
-rw-r--r--MediaBrowser.Common/Net/HttpResponseInfo.cs6
-rw-r--r--MediaBrowser.Controller/Channels/ChannelAudioItem.cs5
-rw-r--r--MediaBrowser.Controller/Channels/ChannelFolderItem.cs5
-rw-r--r--MediaBrowser.Controller/Channels/ChannelMediaInfo.cs5
-rw-r--r--MediaBrowser.Controller/Channels/ChannelVideoItem.cs2
-rw-r--r--MediaBrowser.Controller/Channels/IChannel.cs8
-rw-r--r--MediaBrowser.Controller/Channels/IChannelManager.cs22
-rw-r--r--MediaBrowser.Controller/Channels/IChannelMediaItem.cs3
-rw-r--r--MediaBrowser.Controller/Channels/InternalChannelFeatures.cs6
-rw-r--r--MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs5
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj3
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj3
-rw-r--r--MediaBrowser.Model/Channels/ChannelFeatures.cs24
-rw-r--r--MediaBrowser.Model/Channels/ChannelQuery.cs29
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs11
-rw-r--r--MediaBrowser.Model/Dto/ImageByNameInfo.cs32
-rw-r--r--MediaBrowser.Model/Dto/MediaSourceInfo.cs3
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj1
-rw-r--r--MediaBrowser.Providers/Music/AlbumMetadataService.cs61
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs277
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelManager.cs264
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs33
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs53
-rw-r--r--MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json4
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/server.json12
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj1
-rw-r--r--MediaBrowser.Server.Implementations/Session/HttpSessionController.cs21
-rw-r--r--Nuget/MediaBrowser.Common.Internal.nuspec4
-rw-r--r--Nuget/MediaBrowser.Common.nuspec2
-rw-r--r--Nuget/MediaBrowser.Server.Core.nuspec4
45 files changed, 975 insertions, 298 deletions
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 9a7254c94..75c2d406d 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -117,7 +117,8 @@ namespace MediaBrowser.Api
/// <param name="startTimeTicks">The start time ticks.</param>
/// <param name="sourcePath">The source path.</param>
/// <param name="deviceId">The device id.</param>
- public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, long? startTimeTicks, string sourcePath, string deviceId)
+ /// <param name="cancellationTokenSource">The cancellation token source.</param>
+ public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, long? startTimeTicks, string sourcePath, string deviceId, CancellationTokenSource cancellationTokenSource)
{
lock (_activeTranscodingJobs)
{
@@ -129,7 +130,8 @@ namespace MediaBrowser.Api
ActiveRequestCount = 1,
StartTimeTicks = startTimeTicks,
SourcePath = sourcePath,
- DeviceId = deviceId
+ DeviceId = deviceId,
+ CancellationTokenSource = cancellationTokenSource
});
}
}
@@ -276,6 +278,11 @@ namespace MediaBrowser.Api
{
_activeTranscodingJobs.Remove(job);
+ if (!job.CancellationTokenSource.IsCancellationRequested)
+ {
+ job.CancellationTokenSource.Cancel();
+ }
+
if (job.KillTimer != null)
{
job.KillTimer.Dispose();
@@ -329,7 +336,7 @@ namespace MediaBrowser.Api
private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
{
- if (retryCount >= 10)
+ if (retryCount >= 8)
{
return;
}
@@ -432,6 +439,8 @@ namespace MediaBrowser.Api
public long? StartTimeTicks { get; set; }
public string SourcePath { get; set; }
public string DeviceId { get; set; }
+
+ public CancellationTokenSource CancellationTokenSource { get; set; }
}
/// <summary>
diff --git a/MediaBrowser.Api/ChannelService.cs b/MediaBrowser.Api/ChannelService.cs
index 2a2d316d3..a410a093e 100644
--- a/MediaBrowser.Api/ChannelService.cs
+++ b/MediaBrowser.Api/ChannelService.cs
@@ -43,6 +43,11 @@ namespace MediaBrowser.Api
public string Id { get; set; }
}
+ [Route("/Channels/Features", "GET", Summary = "Gets features for a channel")]
+ public class GetAllChannelFeatures : IReturn<List<ChannelFeatures>>
+ {
+ }
+
[Route("/Channels/{Id}/Items", "GET", Summary = "Gets channel items")]
public class GetChannelItems : IReturn<QueryResult<BaseItemDto>>
{
@@ -108,6 +113,13 @@ namespace MediaBrowser.Api
_channelManager = channelManager;
}
+ public object Get(GetAllChannelFeatures request)
+ {
+ var result = _channelManager.GetAllChannelFeatures().ToList();
+
+ return ToOptimizedResult(result);
+ }
+
public object Get(GetChannelFeatures request)
{
var result = _channelManager.GetChannelFeatures(request.Id);
diff --git a/MediaBrowser.Api/Images/ImageByNameService.cs b/MediaBrowser.Api/Images/ImageByNameService.cs
index 16f3f0c1d..d40762964 100644
--- a/MediaBrowser.Api/Images/ImageByNameService.cs
+++ b/MediaBrowser.Api/Images/ImageByNameService.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Dto;
using ServiceStack;
using System;
using System.Collections.Generic;
@@ -89,15 +90,6 @@ namespace MediaBrowser.Api.Images
{
}
- public class ImageByNameInfo
- {
- public string Name { get; set; }
- public string Theme { get; set; }
- public string Context { get; set; }
- public long FileLength { get; set; }
- public string Format { get; set; }
- }
-
/// <summary>
/// Class ImageByNameService
/// </summary>
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index 62d5a6ce2..92bbb6130 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -105,7 +105,6 @@
<Compile Include="Playback\Hls\DynamicHlsService.cs" />
<Compile Include="Playback\Hls\HlsSegmentService.cs" />
<Compile Include="Playback\Hls\VideoHlsService.cs" />
- <Compile Include="Playback\ProgressiveStreamService.cs" />
<Compile Include="Playback\Progressive\AudioService.cs" />
<Compile Include="Playback\Progressive\BaseProgressiveStreamingService.cs" />
<Compile Include="Playback\BaseStreamingService.cs" />
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 5999a2b55..9f7c1a6c4 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@@ -13,6 +14,7 @@ using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Library;
@@ -72,20 +74,14 @@ namespace MediaBrowser.Api.Playback
protected ILiveTvManager LiveTvManager { get; private set; }
protected IDlnaManager DlnaManager { get; private set; }
protected IChannelManager ChannelManager { get; private set; }
+ protected IHttpClient HttpClient { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
- /// <param name="serverConfig">The server configuration.</param>
- /// <param name="userManager">The user manager.</param>
- /// <param name="libraryManager">The library manager.</param>
- /// <param name="isoManager">The iso manager.</param>
- /// <param name="mediaEncoder">The media encoder.</param>
- /// <param name="dtoService">The dto service.</param>
- /// <param name="fileSystem">The file system.</param>
- /// <param name="itemRepository">The item repository.</param>
- protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager)
+ protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient)
{
+ HttpClient = httpClient;
ChannelManager = channelManager;
DlnaManager = dlnaManager;
EncodingManager = encodingManager;
@@ -483,8 +479,12 @@ namespace MediaBrowser.Api.Playback
/// <param name="state">The state.</param>
/// <param name="outputVideoCodec">The output video codec.</param>
/// <param name="performTextSubtitleConversion">if set to <c>true</c> [perform text subtitle conversion].</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
- protected string GetOutputSizeParam(StreamState state, string outputVideoCodec, bool performTextSubtitleConversion)
+ protected string GetOutputSizeParam(StreamState state,
+ string outputVideoCodec,
+ bool performTextSubtitleConversion,
+ CancellationToken cancellationToken)
{
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
@@ -496,7 +496,7 @@ namespace MediaBrowser.Api.Playback
if (state.SubtitleStream != null && !state.SubtitleStream.IsGraphicalSubtitleStream)
{
- assSubtitleParam = GetTextSubtitleParam(state, performTextSubtitleConversion);
+ assSubtitleParam = GetTextSubtitleParam(state, performTextSubtitleConversion, cancellationToken);
copyTsParam = " -copyts";
}
@@ -592,11 +592,15 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="state">The state.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
- protected string GetTextSubtitleParam(StreamState state, bool performConversion)
+ protected string GetTextSubtitleParam(StreamState state,
+ bool performConversion,
+ CancellationToken cancellationToken)
{
- var path = state.SubtitleStream.IsExternal ? GetConvertedAssPath(state.MediaPath, state.SubtitleStream, performConversion) :
- GetExtractedAssPath(state, performConversion);
+ var path = state.SubtitleStream.IsExternal ?
+ GetConvertedAssPath(state.SubtitleStream, performConversion, cancellationToken) :
+ GetExtractedAssPath(state, performConversion, cancellationToken);
if (string.IsNullOrEmpty(path))
{
@@ -615,8 +619,11 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="state">The state.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
- private string GetExtractedAssPath(StreamState state, bool performConversion)
+ private string GetExtractedAssPath(StreamState state,
+ bool performConversion,
+ CancellationToken cancellationToken)
{
var path = EncodingManager.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream.Index, ".ass");
@@ -636,7 +643,7 @@ namespace MediaBrowser.Api.Playback
// See https://lists.ffmpeg.org/pipermail/ffmpeg-cvslog/2013-April/063616.html
var isAssSubtitle = string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase) || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase);
- var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, isAssSubtitle, path, CancellationToken.None);
+ var task = MediaEncoder.ExtractTextSubtitle(inputPath, type, state.SubtitleStream.Index, isAssSubtitle, path, cancellationToken);
Task.WaitAll(task);
}
@@ -652,11 +659,13 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the converted ass path.
/// </summary>
- /// <param name="mediaPath">The media path.</param>
/// <param name="subtitleStream">The subtitle stream.</param>
/// <param name="performConversion">if set to <c>true</c> [perform conversion].</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
- private string GetConvertedAssPath(string mediaPath, MediaStream subtitleStream, bool performConversion)
+ private string GetConvertedAssPath(MediaStream subtitleStream,
+ bool performConversion,
+ CancellationToken cancellationToken)
{
var path = EncodingManager.GetSubtitleCachePath(subtitleStream.Path, ".ass");
@@ -668,7 +677,7 @@ namespace MediaBrowser.Api.Playback
Directory.CreateDirectory(parentPath);
- var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, CancellationToken.None);
+ var task = MediaEncoder.ConvertTextSubtitleToAss(subtitleStream.Path, path, subtitleStream.Language, cancellationToken);
Task.WaitAll(task);
}
@@ -696,7 +705,7 @@ namespace MediaBrowser.Api.Playback
// Add resolution params, if specified
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
{
- outputSizeParam = GetOutputSizeParam(state, outputVideoCodec, false).TrimEnd('"');
+ outputSizeParam = GetOutputSizeParam(state, outputVideoCodec, false, CancellationToken.None).TrimEnd('"');
outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
}
@@ -842,7 +851,7 @@ namespace MediaBrowser.Api.Playback
/// <returns>System.String.</returns>
protected string GetInputArgument(StreamState state)
{
- var type = InputType.File;
+ var type = state.IsRemote ? InputType.Url : InputType.File;
var inputPath = new[] { state.MediaPath };
@@ -862,8 +871,10 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="state">The state.</param>
/// <param name="outputPath">The output path.</param>
+ /// <param name="cancellationTokenSource">The cancellation token source.</param>
/// <returns>Task.</returns>
- protected async Task StartFfMpeg(StreamState state, string outputPath)
+ /// <exception cref="System.InvalidOperationException">ffmpeg was not found at + MediaEncoder.EncoderPath</exception>
+ protected async Task StartFfMpeg(StreamState state, string outputPath, CancellationTokenSource cancellationTokenSource)
{
if (!File.Exists(MediaEncoder.EncoderPath))
{
@@ -874,7 +885,7 @@ namespace MediaBrowser.Api.Playback
if (state.IsInputVideo && state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
{
- state.IsoMount = await IsoManager.Mount(state.MediaPath, CancellationToken.None).ConfigureAwait(false);
+ state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
}
var commandLineArgs = GetCommandLineArguments(outputPath, state, true);
@@ -906,7 +917,7 @@ namespace MediaBrowser.Api.Playback
EnableRaisingEvents = true
};
- ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId);
+ ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId, cancellationTokenSource);
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
Logger.Info(commandLineLogMessage);
@@ -918,7 +929,7 @@ namespace MediaBrowser.Api.Playback
state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine);
- await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length).ConfigureAwait(false);
+ await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
process.Exited += (sender, args) => OnFfMpegProcessExited(process, state);
@@ -946,19 +957,19 @@ namespace MediaBrowser.Api.Playback
// Wait for the file to exist before proceeeding
while (!File.Exists(outputPath))
{
- await Task.Delay(100).ConfigureAwait(false);
+ await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
}
// Allow a small amount of time to buffer a little
if (state.IsInputVideo)
{
- await Task.Delay(500).ConfigureAwait(false);
+ await Task.Delay(500, cancellationTokenSource.Token).ConfigureAwait(false);
}
// This is arbitrary, but add a little buffer time when internet streaming
if (state.IsRemote)
{
- await Task.Delay(3000).ConfigureAwait(false);
+ await Task.Delay(3000, cancellationTokenSource.Token).ConfigureAwait(false);
}
}
@@ -1050,13 +1061,19 @@ namespace MediaBrowser.Api.Playback
/// <summary>
/// Gets the user agent param.
/// </summary>
- /// <param name="path">The path.</param>
+ /// <param name="state">The state.</param>
/// <returns>System.String.</returns>
- private string GetUserAgentParam(string path)
+ private string GetUserAgentParam(StreamState state)
{
- var useragent = GetUserAgent(path);
+ string useragent;
+ state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent);
- if (!string.IsNullOrEmpty(useragent))
+ if (string.IsNullOrWhiteSpace(useragent))
+ {
+ useragent = GetUserAgent(state.MediaPath);
+ }
+
+ if (!string.IsNullOrWhiteSpace(useragent))
{
return "-user-agent \"" + useragent + "\"";
}
@@ -1337,9 +1354,7 @@ namespace MediaBrowser.Api.Playback
state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault();
}
- var item = string.IsNullOrEmpty(request.MediaSourceId) ?
- LibraryManager.GetItemById(request.Id) :
- LibraryManager.GetItemById(request.MediaSourceId);
+ var item = LibraryManager.GetItemById(request.Id);
if (user != null && item.GetPlayAccess(user) != PlayAccess.Full)
{
@@ -1427,19 +1442,24 @@ namespace MediaBrowser.Api.Playback
}
else if (item is IChannelMediaItem)
{
- var source = await GetChannelMediaInfo(request.Id, CancellationToken.None).ConfigureAwait(false);
+ var source = await GetChannelMediaInfo(request.Id, request.MediaSourceId, cancellationToken).ConfigureAwait(false);
state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
- state.IsRemote = source.IsRemote;
+ state.IsRemote = source.LocationType == LocationType.Remote;
state.MediaPath = source.Path;
state.RunTimeTicks = item.RunTimeTicks;
- mediaStreams = GetMediaStreams(source).ToList();
+ state.RemoteHttpHeaders = source.RequiredHttpHeaders;
+ mediaStreams = source.MediaStreams;
}
else
{
- state.MediaPath = item.Path;
- state.IsRemote = item.LocationType == LocationType.Remote;
+ var mediaSource = string.IsNullOrWhiteSpace(request.MediaSourceId)
+ ? item
+ : LibraryManager.GetItemById(request.MediaSourceId);
+
+ state.MediaPath = mediaSource.Path;
+ state.IsRemote = mediaSource.LocationType == LocationType.Remote;
- var video = item as Video;
+ var video = mediaSource as Video;
if (video != null)
{
@@ -1461,20 +1481,20 @@ namespace MediaBrowser.Api.Playback
state.InputContainer = video.Container;
}
- var audio = item as Audio;
+ var audio = mediaSource as Audio;
if (audio != null)
{
state.InputContainer = audio.Container;
}
- state.RunTimeTicks = item.RunTimeTicks;
+ state.RunTimeTicks = mediaSource.RunTimeTicks;
}
var videoRequest = request as VideoStreamRequest;
mediaStreams = mediaStreams ?? ItemRepository.GetMediaStreams(new MediaStreamQuery
{
- ItemId = item.Id
+ ItemId = new Guid(string.IsNullOrWhiteSpace(request.MediaSourceId) ? request.Id : request.MediaSourceId)
}).ToList();
@@ -1545,65 +1565,32 @@ namespace MediaBrowser.Api.Playback
}
}
- private IEnumerable<MediaStream> GetMediaStreams(ChannelMediaInfo info)
- {
- var list = new List<MediaStream>();
-
- if (!string.IsNullOrWhiteSpace(info.VideoCodec) &&
- !string.IsNullOrWhiteSpace(info.AudioCodec))
- {
- list.Add(new MediaStream
- {
- Type = MediaStreamType.Video,
- Width = info.Width,
- RealFrameRate = info.Framerate,
- Profile = info.VideoProfile,
- Level = info.VideoLevel,
- Index = -1,
- Height = info.Height,
- Codec = info.VideoCodec,
- BitRate = info.VideoBitrate,
- AverageFrameRate = info.Framerate
- });
-
- list.Add(new MediaStream
- {
- Type = MediaStreamType.Audio,
- Index = -1,
- Codec = info.AudioCodec,
- BitRate = info.AudioBitrate,
- Channels = info.AudioChannels,
- SampleRate = info.AudioSampleRate
- });
- }
-
- return list;
- }
-
- private async Task<ChannelMediaInfo> GetChannelMediaInfo(string id, CancellationToken cancellationToken)
+ private async Task<MediaSourceInfo> GetChannelMediaInfo(string id,
+ string mediaSourceId,
+ CancellationToken cancellationToken)
{
var channelMediaSources = await ChannelManager.GetChannelItemMediaSources(id, cancellationToken)
.ConfigureAwait(false);
var list = channelMediaSources.ToList();
- var preferredWidth = ServerConfigurationManager.Configuration.ChannelOptions.PreferredStreamingWidth;
-
- if (preferredWidth.HasValue)
+ if (!string.IsNullOrWhiteSpace(mediaSourceId))
{
- var val = preferredWidth.Value;
+ var source = list
+ .FirstOrDefault(i => string.Equals(mediaSourceId, i.Id));
- return list
- .OrderBy(i => Math.Abs(i.Width ?? 0 - val))
- .ThenByDescending(i => i.Width ?? 0)
- .ThenBy(list.IndexOf)
- .First();
+ if (source != null)
+ {
+ return source;
+ }
+
+ Logger.Warn("Invalid channel MediaSourceId requested, defaulting to first. Item: {0}. Requested MediaSourceId: {1}.",
+ id,
+ mediaSourceId
+ );
}
- return list
- .OrderByDescending(i => i.Width ?? 0)
- .ThenBy(list.IndexOf)
- .First();
+ return list.First();
}
private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream)
@@ -1932,7 +1919,11 @@ namespace MediaBrowser.Api.Playback
inputModifier += " " + probeSize;
inputModifier = inputModifier.Trim();
- inputModifier += " " + GetUserAgentParam(state.MediaPath);
+ if (state.IsRemote)
+ {
+ inputModifier += " " + GetUserAgentParam(state);
+ }
+
inputModifier = inputModifier.Trim();
inputModifier += " " + GetFastSeekCommandLineParameter(state.Request);
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 553f02368..a7412e3d8 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -25,7 +25,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public abstract class BaseHlsService : BaseStreamingService
{
- protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager)
+ protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient)
{
}
@@ -82,7 +82,9 @@ namespace MediaBrowser.Api.Playback.Hls
/// </exception>
private async Task<object> ProcessRequestAsync(StreamRequest request)
{
- var state = GetState(request, CancellationToken.None).Result;
+ var cancellationTokenSource = new CancellationTokenSource();
+
+ var state = GetState(request, cancellationTokenSource.Token).Result;
var playlist = GetOutputFilePath(state);
@@ -92,7 +94,7 @@ namespace MediaBrowser.Api.Playback.Hls
}
else
{
- await FfmpegStartLock.WaitAsync().ConfigureAwait(false);
+ await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
try
{
if (File.Exists(playlist))
@@ -104,16 +106,16 @@ namespace MediaBrowser.Api.Playback.Hls
// If the playlist doesn't already exist, startup ffmpeg
try
{
- await StartFfMpeg(state, playlist).ConfigureAwait(false);
+ await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false);
}
catch
{
state.Dispose();
throw;
}
- }
- await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false);
+ await WaitForMinimumSegmentCount(playlist, GetSegmentWait(), cancellationTokenSource.Token).ConfigureAwait(false);
+ }
}
finally
{
@@ -220,10 +222,12 @@ namespace MediaBrowser.Api.Playback.Hls
return builder.ToString();
}
- protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount)
+ protected async Task WaitForMinimumSegmentCount(string playlist, int segmentCount, CancellationToken cancellationToken)
{
while (true)
{
+ cancellationToken.ThrowIfCancellationRequested();
+
string fileText;
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
@@ -240,7 +244,7 @@ namespace MediaBrowser.Api.Playback.Hls
break;
}
- await Task.Delay(25).ConfigureAwait(false);
+ await Task.Delay(25, cancellationToken).ConfigureAwait(false);
}
}
@@ -287,7 +291,7 @@ namespace MediaBrowser.Api.Playback.Hls
// 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} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9} \"{10}\"",
+ var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10}\"",
itsOffset,
inputModifier,
GetInputArgument(state),
@@ -309,7 +313,7 @@ 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 {3} -hls_list_size {4} \"{5}\"",
+ var lowBitrateParams = string.Format(" -threads {0} -vn -codec:a:0 libmp3lame -ac 2 -ab {1} -hls_time {2} -start_number {3} -hls_list_size {4} -y \"{5}\"",
threads,
bitrate / 2,
state.SegmentLength.ToString(UsCulture),
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index c5405a3b5..96ecfb055 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@@ -16,6 +17,7 @@ using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using MimeTypes = ServiceStack.MimeTypes;
namespace MediaBrowser.Api.Playback.Hls
{
@@ -60,7 +62,7 @@ namespace MediaBrowser.Api.Playback.Hls
public class DynamicHlsService : BaseHlsService
{
- public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager)
+ public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient)
{
}
@@ -98,9 +100,9 @@ namespace MediaBrowser.Api.Playback.Hls
if (!File.Exists(playlistPath))
{
- await StartFfMpeg(state, playlistPath).ConfigureAwait(false);
+ await StartFfMpeg(state, playlistPath, new CancellationTokenSource()).ConfigureAwait(false);
- await WaitForMinimumSegmentCount(playlistPath, GetSegmentWait()).ConfigureAwait(false);
+ await WaitForMinimumSegmentCount(playlistPath, GetSegmentWait(), CancellationToken.None).ConfigureAwait(false);
}
return GetSegementResult(path);
@@ -283,7 +285,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue)
{
- args += GetOutputSizeParam(state, codec, performSubtitleConversion);
+ args += GetOutputSizeParam(state, codec, performSubtitleConversion, CancellationToken.None);
}
}
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index 162ab741a..e4ee68241 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -1,4 +1,6 @@
+using System.Threading;
using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@@ -54,7 +56,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
public class VideoHlsService : BaseHlsService
{
- public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager)
+ public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient)
{
}
@@ -155,7 +157,8 @@ namespace MediaBrowser.Api.Playback.Hls
/// <param name="state">The state.</param>
/// <param name="performSubtitleConversion">if set to <c>true</c> [perform subtitle conversion].</param>
/// <returns>System.String.</returns>
- protected override string GetVideoArguments(StreamState state, bool performSubtitleConversion)
+ protected override string GetVideoArguments(StreamState state,
+ bool performSubtitleConversion)
{
var codec = GetVideoCodec(state.VideoRequest);
@@ -178,7 +181,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
if (state.VideoRequest.Width.HasValue || state.VideoRequest.Height.HasValue || state.VideoRequest.MaxHeight.HasValue || state.VideoRequest.MaxWidth.HasValue)
{
- args += GetOutputSizeParam(state, codec, performSubtitleConversion);
+ args += GetOutputSizeParam(state, codec, performSubtitleConversion, CancellationToken.None);
}
}
diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
index 2e002cc9b..18cfbbdf2 100644
--- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs
@@ -44,7 +44,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
public class AudioService : BaseProgressiveStreamingService
{
- public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, imageProcessor, httpClient)
+ public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient, imageProcessor)
{
}
@@ -105,7 +105,7 @@ namespace MediaBrowser.Api.Playback.Progressive
var inputModifier = GetInputModifier(state);
- return string.Format("{0} -i {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 \"{5}\"",
+ return string.Format("{0} -i {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
inputModifier,
GetInputArgument(state),
threads,
diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
index 06e8f7b2c..5e3e85140 100644
--- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
@@ -25,12 +25,10 @@ namespace MediaBrowser.Api.Playback.Progressive
public abstract class BaseProgressiveStreamingService : BaseStreamingService
{
protected readonly IImageProcessor ImageProcessor;
- protected readonly IHttpClient HttpClient;
- protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager)
+ protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient)
{
ImageProcessor = imageProcessor;
- HttpClient = httpClient;
}
/// <summary>
@@ -280,7 +278,7 @@ namespace MediaBrowser.Api.Playback.Progressive
if (!File.Exists(outputPath))
{
- await StartFfMpeg(state, outputPath).ConfigureAwait(false);
+ await StartFfMpeg(state, outputPath, new CancellationTokenSource()).ConfigureAwait(false);
}
else
{
diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
index 58f36a06c..8ba504e14 100644
--- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
+++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
@@ -32,6 +32,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
/// <param name="path">The path.</param>
/// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The file system.</param>
public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem)
{
Path = path;
diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
index 29bc3f897..65a62aad3 100644
--- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs
+++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs
@@ -1,3 +1,4 @@
+using System.Threading;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
@@ -60,7 +61,7 @@ namespace MediaBrowser.Api.Playback.Progressive
/// </summary>
public class VideoService : BaseProgressiveStreamingService
{
- public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, imageProcessor, httpClient)
+ public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem, IItemRepository itemRepository, ILiveTvManager liveTvManager, IEncodingManager encodingManager, IDlnaManager dlnaManager, IChannelManager channelManager, IHttpClient httpClient, IImageProcessor imageProcessor) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem, itemRepository, liveTvManager, encodingManager, dlnaManager, channelManager, httpClient, imageProcessor)
{
}
@@ -108,7 +109,7 @@ namespace MediaBrowser.Api.Playback.Progressive
var inputModifier = GetInputModifier(state);
- return string.Format("{0} -i {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} \"{8}\"",
+ return string.Format("{0} -i {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
inputModifier,
GetInputArgument(state),
keyFrame,
@@ -156,7 +157,7 @@ namespace MediaBrowser.Api.Playback.Progressive
{
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
{
- args += GetOutputSizeParam(state, codec, performSubtitleConversion);
+ args += GetOutputSizeParam(state, codec, performSubtitleConversion, CancellationToken.None);
}
}
diff --git a/MediaBrowser.Api/Playback/ProgressiveStreamService.cs b/MediaBrowser.Api/Playback/ProgressiveStreamService.cs
deleted file mode 100644
index 531f79a22..000000000
--- a/MediaBrowser.Api/Playback/ProgressiveStreamService.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Api.Playback.Progressive;
-
-namespace MediaBrowser.Api.Playback
-{
- //public class GetProgressiveAudioStream : StreamRequest
- //{
-
- //}
-
- //public class ProgressiveStreamService : BaseApiService
- //{
- // public object Get(GetProgressiveAudioStream request)
- // {
- // return ProcessRequest(request, false);
- // }
-
- // /// <summary>
- // /// Gets the specified request.
- // /// </summary>
- // /// <param name="request">The request.</param>
- // /// <returns>System.Object.</returns>
- // public object Head(GetProgressiveAudioStream request)
- // {
- // return ProcessRequest(request, true);
- // }
-
- // protected object ProcessRequest(StreamRequest request, bool isHeadRequest)
- // {
- // var state = GetState(request, CancellationToken.None).Result;
-
- // var responseHeaders = new Dictionary<string, string>();
-
- // if (request.Static && state.IsRemote)
- // {
- // AddDlnaHeaders(state, responseHeaders, true);
-
- // return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result;
- // }
-
- // var outputPath = GetOutputFilePath(state);
- // var outputPathExists = File.Exists(outputPath);
-
- // var isStatic = request.Static ||
- // (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive));
-
- // AddDlnaHeaders(state, responseHeaders, isStatic);
-
- // if (request.Static)
- // {
- // var contentType = state.GetMimeType(state.MediaPath);
-
- // return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
- // }
-
- // if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive))
- // {
- // var contentType = state.GetMimeType(outputPath);
-
- // return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest);
- // }
-
- // return GetStreamResult(state, responseHeaders, isHeadRequest).Result;
- // }
-
- //}
-}
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index f9e861e8d..295ee44e7 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -31,8 +31,11 @@ namespace MediaBrowser.Api.Playback
public StreamState()
{
PlayableStreamFileNames = new List<string>();
+ RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
+ public Dictionary<string, string> RemoteHttpHeaders { get; set; }
+
/// <summary>
/// Gets or sets the log file stream.
/// </summary>
@@ -40,7 +43,7 @@ namespace MediaBrowser.Api.Playback
public Stream LogFileStream { get; set; }
public string InputContainer { get; set; }
-
+
public MediaStream AudioStream { get; set; }
public MediaStream VideoStream { get; set; }
public MediaStream SubtitleStream { get; set; }
@@ -277,8 +280,8 @@ namespace MediaBrowser.Api.Playback
{
get
{
- var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ?
- TransportStreamTimestamp.Valid :
+ var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ?
+ TransportStreamTimestamp.Valid :
TransportStreamTimestamp.None;
return !Request.Static
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
index 969916ef6..a4337bab4 100644
--- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
@@ -371,7 +371,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
Headers = new NameValueCollection(httpResponse.Headers),
- ContentLength = contentLength
+ ContentLength = contentLength,
+
+ ResponseUrl = httpResponse.ResponseUri.ToString()
};
}
diff --git a/MediaBrowser.Common/Net/HttpResponseInfo.cs b/MediaBrowser.Common/Net/HttpResponseInfo.cs
index bc0319209..10b5e1983 100644
--- a/MediaBrowser.Common/Net/HttpResponseInfo.cs
+++ b/MediaBrowser.Common/Net/HttpResponseInfo.cs
@@ -16,6 +16,12 @@ namespace MediaBrowser.Common.Net
public string ContentType { get; set; }
/// <summary>
+ /// Gets or sets the response URL.
+ /// </summary>
+ /// <value>The response URL.</value>
+ public string ResponseUrl { get; set; }
+
+ /// <summary>
/// Gets or sets the content.
/// </summary>
/// <value>The content.</value>
diff --git a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs
index 7072d4284..8afaa73ac 100644
--- a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs
+++ b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs
@@ -28,6 +28,11 @@ namespace MediaBrowser.Controller.Channels
return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent);
}
+ public override string GetUserDataKey()
+ {
+ return ExternalId;
+ }
+
public override bool SupportsLocalMetadata
{
get
diff --git a/MediaBrowser.Controller/Channels/ChannelFolderItem.cs b/MediaBrowser.Controller/Channels/ChannelFolderItem.cs
index 5be30d7c3..afc6493e4 100644
--- a/MediaBrowser.Controller/Channels/ChannelFolderItem.cs
+++ b/MediaBrowser.Controller/Channels/ChannelFolderItem.cs
@@ -35,5 +35,10 @@ namespace MediaBrowser.Controller.Channels
{
Tags = new List<string>();
}
+
+ public override string GetUserDataKey()
+ {
+ return ExternalId;
+ }
}
}
diff --git a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs
index b38ef363a..2e2f1912a 100644
--- a/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs
+++ b/MediaBrowser.Controller/Channels/ChannelMediaInfo.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
namespace MediaBrowser.Controller.Channels
{
@@ -27,7 +28,7 @@ namespace MediaBrowser.Controller.Channels
public ChannelMediaInfo()
{
- RequiredHttpHeaders = new Dictionary<string, string>();
+ RequiredHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
IsRemote = true;
}
}
diff --git a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs
index 572e316a0..2f207c420 100644
--- a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs
+++ b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs
@@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Channels
}
}
- return base.GetUserDataKey();
+ return ExternalId;
}
protected override bool GetBlockUnratedValue(UserConfiguration config)
diff --git a/MediaBrowser.Controller/Channels/IChannel.cs b/MediaBrowser.Controller/Channels/IChannel.cs
index 27334e4b9..1d7c298fa 100644
--- a/MediaBrowser.Controller/Channels/IChannel.cs
+++ b/MediaBrowser.Controller/Channels/IChannel.cs
@@ -50,6 +50,14 @@ namespace MediaBrowser.Controller.Channels
Task<IEnumerable<ChannelItemInfo>> Search(ChannelSearchInfo searchInfo, User user, CancellationToken cancellationToken);
/// <summary>
+ /// Gets all media.
+ /// </summary>
+ /// <param name="query">The query.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{ChannelItemResult}.</returns>
+ Task<ChannelItemResult> GetAllMedia(InternalAllChannelMediaQuery query, CancellationToken cancellationToken);
+
+ /// <summary>
/// Gets the channel items.
/// </summary>
/// <param name="query">The query.</param>
diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs
index 4c2d665e5..4be38870b 100644
--- a/MediaBrowser.Controller/Channels/IChannelManager.cs
+++ b/MediaBrowser.Controller/Channels/IChannelManager.cs
@@ -17,6 +17,12 @@ namespace MediaBrowser.Controller.Channels
void AddParts(IEnumerable<IChannel> channels, IEnumerable<IChannelFactory> factories);
/// <summary>
+ /// Gets the channel download path.
+ /// </summary>
+ /// <value>The channel download path.</value>
+ string ChannelDownloadPath { get; }
+
+ /// <summary>
/// Gets the channel features.
/// </summary>
/// <param name="id">The identifier.</param>
@@ -24,6 +30,12 @@ namespace MediaBrowser.Controller.Channels
ChannelFeatures GetChannelFeatures(string id);
/// <summary>
+ /// Gets all channel features.
+ /// </summary>
+ /// <returns>IEnumerable{ChannelFeatures}.</returns>
+ IEnumerable<ChannelFeatures> GetAllChannelFeatures();
+
+ /// <summary>
/// Gets the channel.
/// </summary>
/// <param name="id">The identifier.</param>
@@ -39,6 +51,14 @@ namespace MediaBrowser.Controller.Channels
Task<QueryResult<BaseItemDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken);
/// <summary>
+ /// Gets all media.
+ /// </summary>
+ /// <param name="query">The query.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task{QueryResult{BaseItemDto}}.</returns>
+ Task<QueryResult<BaseItemDto>> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken);
+
+ /// <summary>
/// Gets the channel items.
/// </summary>
/// <param name="query">The query.</param>
@@ -52,6 +72,6 @@ namespace MediaBrowser.Controller.Channels
/// <param name="id">The identifier.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{ChannelMediaInfo}}.</returns>
- Task<IEnumerable<ChannelMediaInfo>> GetChannelItemMediaSources(string id, CancellationToken cancellationToken);
+ Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/Channels/IChannelMediaItem.cs b/MediaBrowser.Controller/Channels/IChannelMediaItem.cs
index 357856a25..db8e2b292 100644
--- a/MediaBrowser.Controller/Channels/IChannelMediaItem.cs
+++ b/MediaBrowser.Controller/Channels/IChannelMediaItem.cs
@@ -7,6 +7,9 @@ namespace MediaBrowser.Controller.Channels
{
bool IsInfiniteStream { get; set; }
+ long? RunTimeTicks { get; set; }
+ string MediaType { get; }
+
ChannelMediaContentType ContentType { get; set; }
List<ChannelMediaInfo> ChannelMediaSources { get; set; }
diff --git a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs
index 57a1a5129..44da646d0 100644
--- a/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs
+++ b/MediaBrowser.Controller/Channels/InternalChannelFeatures.cs
@@ -12,6 +12,12 @@ namespace MediaBrowser.Controller.Channels
public bool CanSearch { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether this instance can get all media.
+ /// </summary>
+ /// <value><c>true</c> if this instance can get all media; otherwise, <c>false</c>.</value>
+ public bool CanGetAllMedia { get; set; }
+
+ /// <summary>
/// Gets or sets the media types.
/// </summary>
/// <value>The media types.</value>
diff --git a/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs b/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs
index 1b05e60b6..dceced14c 100644
--- a/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs
+++ b/MediaBrowser.Controller/Channels/InternalChannelItemQuery.cs
@@ -17,4 +17,9 @@ namespace MediaBrowser.Controller.Channels
public bool SortDescending { get; set; }
}
+
+ public class InternalAllChannelMediaQuery
+ {
+ public User User { get; set; }
+ }
} \ No newline at end of file
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index 98f9417d0..63cb9210a 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -284,6 +284,9 @@
<Compile Include="..\MediaBrowser.Model\Dto\IItemDto.cs">
<Link>Dto\IItemDto.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Dto\ImageByNameInfo.cs">
+ <Link>Dto\ImageByNameInfo.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Dto\ImageInfo.cs">
<Link>Dto\ImageInfo.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index d0d93c26b..70124c3e1 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -271,6 +271,9 @@
<Compile Include="..\MediaBrowser.Model\Dto\IItemDto.cs">
<Link>Dto\IItemDto.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Dto\ImageByNameInfo.cs">
+ <Link>Dto\ImageByNameInfo.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Dto\ImageInfo.cs">
<Link>Dto\ImageInfo.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model/Channels/ChannelFeatures.cs b/MediaBrowser.Model/Channels/ChannelFeatures.cs
index 494ebe3a3..2acbdf9c3 100644
--- a/MediaBrowser.Model/Channels/ChannelFeatures.cs
+++ b/MediaBrowser.Model/Channels/ChannelFeatures.cs
@@ -5,12 +5,30 @@ namespace MediaBrowser.Model.Channels
public class ChannelFeatures
{
/// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name { get; set; }
+
+ /// <summary>
+ /// Gets or sets the identifier.
+ /// </summary>
+ /// <value>The identifier.</value>
+ public string Id { get; set; }
+
+ /// <summary>
/// Gets or sets a value indicating whether this instance can search.
/// </summary>
/// <value><c>true</c> if this instance can search; otherwise, <c>false</c>.</value>
public bool CanSearch { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether this instance can get all media.
+ /// </summary>
+ /// <value><c>true</c> if this instance can get all media; otherwise, <c>false</c>.</value>
+ public bool CanGetAllMedia { get; set; }
+
+ /// <summary>
/// Gets or sets the media types.
/// </summary>
/// <value>The media types.</value>
@@ -44,6 +62,12 @@ namespace MediaBrowser.Model.Channels
/// <value><c>true</c> if this instance can filter; otherwise, <c>false</c>.</value>
public bool CanFilter { get; set; }
+ /// <summary>
+ /// Gets or sets a value indicating whether this instance can download all media.
+ /// </summary>
+ /// <value><c>true</c> if this instance can download all media; otherwise, <c>false</c>.</value>
+ public bool CanDownloadAllMedia { get; set; }
+
public ChannelFeatures()
{
MediaTypes = new List<ChannelMediaType>();
diff --git a/MediaBrowser.Model/Channels/ChannelQuery.cs b/MediaBrowser.Model/Channels/ChannelQuery.cs
index 7c3f76fda..bd2accf65 100644
--- a/MediaBrowser.Model/Channels/ChannelQuery.cs
+++ b/MediaBrowser.Model/Channels/ChannelQuery.cs
@@ -20,4 +20,33 @@
/// <value>The limit.</value>
public int? Limit { get; set; }
}
+
+ public class AllChannelMediaQuery
+ {
+ public string[] ChannelIds { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user identifier.
+ /// </summary>
+ /// <value>The user identifier.</value>
+ public string UserId { get; set; }
+
+ /// <summary>
+ /// Skips over a given number of items within the results. Use for paging.
+ /// </summary>
+ /// <value>The start index.</value>
+ public int? StartIndex { get; set; }
+
+ /// <summary>
+ /// The maximum number of items to return
+ /// </summary>
+ /// <value>The limit.</value>
+ public int? Limit { get; set; }
+
+ public AllChannelMediaQuery()
+ {
+ ChannelIds = new string[] { };
+ }
+ }
+
}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 17c7e8b55..34707ecd7 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -303,5 +303,16 @@ namespace MediaBrowser.Model.Configuration
public class ChannelOptions
{
public int? PreferredStreamingWidth { get; set; }
+
+ public string DownloadPath { get; set; }
+ public int? MaxDownloadAge { get; set; }
+
+ public string[] DownloadingChannels { get; set; }
+
+ public ChannelOptions()
+ {
+ DownloadingChannels = new string[] { };
+ MaxDownloadAge = 30;
+ }
}
}
diff --git a/MediaBrowser.Model/Dto/ImageByNameInfo.cs b/MediaBrowser.Model/Dto/ImageByNameInfo.cs
new file mode 100644
index 000000000..b7921d993
--- /dev/null
+++ b/MediaBrowser.Model/Dto/ImageByNameInfo.cs
@@ -0,0 +1,32 @@
+
+namespace MediaBrowser.Model.Dto
+{
+ public class ImageByNameInfo
+ {
+ /// <summary>
+ /// Gets or sets the name.
+ /// </summary>
+ /// <value>The name.</value>
+ public string Name { get; set; }
+ /// <summary>
+ /// Gets or sets the theme.
+ /// </summary>
+ /// <value>The theme.</value>
+ public string Theme { get; set; }
+ /// <summary>
+ /// Gets or sets the context.
+ /// </summary>
+ /// <value>The context.</value>
+ public string Context { get; set; }
+ /// <summary>
+ /// Gets or sets the length of the file.
+ /// </summary>
+ /// <value>The length of the file.</value>
+ public long FileLength { get; set; }
+ /// <summary>
+ /// Gets or sets the format.
+ /// </summary>
+ /// <value>The format.</value>
+ public string Format { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Dto/MediaSourceInfo.cs b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
index 660d4c09c..598eacc21 100644
--- a/MediaBrowser.Model/Dto/MediaSourceInfo.cs
+++ b/MediaBrowser.Model/Dto/MediaSourceInfo.cs
@@ -1,7 +1,6 @@
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.MediaInfo;
-using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
@@ -35,11 +34,13 @@ namespace MediaBrowser.Model.Dto
public int? Bitrate { get; set; }
public TransportStreamTimestamp? Timestamp { get; set; }
+ public Dictionary<string, string> RequiredHttpHeaders { get; set; }
public MediaSourceInfo()
{
Formats = new List<string>();
MediaStreams = new List<MediaStream>();
+ RequiredHttpHeaders = new Dictionary<string, string>();
}
public int? DefaultAudioStreamIndex { get; set; }
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index cd489bb36..6b8605b7a 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -125,6 +125,7 @@
<Compile Include="Dto\ChapterInfoDto.cs" />
<Compile Include="Dto\GameSystemSummary.cs" />
<Compile Include="Dto\IItemDto.cs" />
+ <Compile Include="Dto\ImageByNameInfo.cs" />
<Compile Include="Dto\ImageInfo.cs" />
<Compile Include="Dto\ItemByNameCounts.cs" />
<Compile Include="Dto\ItemCounts.cs" />
diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
index 0a3359156..96020791e 100644
--- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AlbumMetadataService.cs
@@ -46,46 +46,49 @@ namespace MediaBrowser.Providers.Music
{
var updateType = base.BeforeSave(item);
- var songs = item.RecursiveChildren.OfType<Audio>().ToList();
-
- if (!item.LockedFields.Contains(MetadataFields.Genres))
+ var songs = item.RecursiveChildren.OfType<Audio>().ToList();
+
+ if (!item.IsLocked)
{
- var currentList = item.Genres.ToList();
+ if (!item.LockedFields.Contains(MetadataFields.Genres))
+ {
+ var currentList = item.Genres.ToList();
- item.Genres = songs.SelectMany(i => i.Genres)
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .ToList();
+ item.Genres = songs.SelectMany(i => i.Genres)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
- if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
- {
- updateType = updateType | ItemUpdateType.MetadataDownload;
+ if (currentList.Count != item.Genres.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Genres.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
+ {
+ updateType = updateType | ItemUpdateType.MetadataDownload;
+ }
}
- }
- if (!item.LockedFields.Contains(MetadataFields.Studios))
- {
- var currentList = item.Studios.ToList();
+ if (!item.LockedFields.Contains(MetadataFields.Studios))
+ {
+ var currentList = item.Studios.ToList();
- item.Studios = songs.SelectMany(i => i.Studios)
- .Distinct(StringComparer.OrdinalIgnoreCase)
- .ToList();
+ item.Studios = songs.SelectMany(i => i.Studios)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToList();
- if (currentList.Count != item.Studios.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
- {
- updateType = updateType | ItemUpdateType.MetadataDownload;
+ if (currentList.Count != item.Studios.Count || !currentList.OrderBy(i => i).SequenceEqual(item.Studios.OrderBy(i => i), StringComparer.OrdinalIgnoreCase))
+ {
+ updateType = updateType | ItemUpdateType.MetadataDownload;
+ }
}
- }
-
- if (!item.LockedFields.Contains(MetadataFields.Name))
- {
- var name = songs.Select(i => i.Album).FirstOrDefault(i => !string.IsNullOrEmpty(i));
- if (!string.IsNullOrEmpty(name))
+ if (!item.LockedFields.Contains(MetadataFields.Name))
{
- if (!string.Equals(item.Name, name, StringComparison.Ordinal))
+ var name = songs.Select(i => i.Album).FirstOrDefault(i => !string.IsNullOrEmpty(i));
+
+ if (!string.IsNullOrEmpty(name))
{
- item.Name = name;
- updateType = updateType | ItemUpdateType.MetadataDownload;
+ if (!string.Equals(item.Name, name, StringComparison.Ordinal))
+ {
+ item.Name = name;
+ updateType = updateType | ItemUpdateType.MetadataDownload;
+ }
}
}
}
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
new file mode 100644
index 000000000..21b5ba6da
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
@@ -0,0 +1,277 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Channels;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Channels
+{
+ public class ChannelDownloadScheduledTask : IScheduledTask, IConfigurableScheduledTask
+ {
+ private readonly IChannelManager _manager;
+ private readonly IServerConfigurationManager _config;
+ private readonly ILogger _logger;
+ private readonly IHttpClient _httpClient;
+ private readonly IFileSystem _fileSystem;
+ private readonly ILibraryManager _libraryManager;
+
+ public ChannelDownloadScheduledTask(IChannelManager manager, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem, ILibraryManager libraryManager)
+ {
+ _manager = manager;
+ _config = config;
+ _logger = logger;
+ _httpClient = httpClient;
+ _fileSystem = fileSystem;
+ _libraryManager = libraryManager;
+ }
+
+ public string Name
+ {
+ get { return "Download channel content"; }
+ }
+
+ public string Description
+ {
+ get { return "Downloads channel content based on configuration."; }
+ }
+
+ public string Category
+ {
+ get { return "Channels"; }
+ }
+
+ public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
+ {
+ CleanChannelContent(cancellationToken);
+ progress.Report(5);
+
+ await DownloadChannelContent(cancellationToken, progress).ConfigureAwait(false);
+ progress.Report(100);
+ }
+
+ private void CleanChannelContent(CancellationToken cancellationToken)
+ {
+ if (!_config.Configuration.ChannelOptions.MaxDownloadAge.HasValue)
+ {
+ return;
+ }
+
+ var minDateModified = DateTime.UtcNow.AddDays(0 - _config.Configuration.ChannelOptions.MaxDownloadAge.Value);
+
+ var path = _manager.ChannelDownloadPath;
+
+ try
+ {
+ DeleteCacheFilesFromDirectory(cancellationToken, path, minDateModified, new Progress<double>());
+ }
+ catch (DirectoryNotFoundException)
+ {
+ // No biggie here. Nothing to delete
+ }
+ }
+
+ private async Task DownloadChannelContent(CancellationToken cancellationToken, IProgress<double> progress)
+ {
+ if (_config.Configuration.ChannelOptions.DownloadingChannels.Length == 0)
+ {
+ return;
+ }
+
+ var result = await _manager.GetAllMedia(new AllChannelMediaQuery
+ {
+ ChannelIds = _config.Configuration.ChannelOptions.DownloadingChannels
+
+ }, cancellationToken).ConfigureAwait(false);
+
+ var path = _manager.ChannelDownloadPath;
+
+ var numComplete = 0;
+
+ foreach (var item in result.Items)
+ {
+ try
+ {
+ await DownloadChannelItem(item, cancellationToken, path);
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error downloading channel content for {0}", ex, item.Name);
+ }
+
+ numComplete++;
+ double percent = numComplete;
+ percent /= result.Items.Length;
+ progress.Report(percent * 95 + 5);
+ }
+ }
+
+ private async Task DownloadChannelItem(BaseItemDto item,
+ CancellationToken cancellationToken,
+ string path)
+ {
+ var sources = await _manager.GetChannelItemMediaSources(item.Id, cancellationToken)
+ .ConfigureAwait(false);
+
+ var list = sources.ToList();
+
+ var cachedVersions = list.Where(i => i.LocationType == LocationType.FileSystem).ToList();
+
+ if (cachedVersions.Count > 0)
+ {
+ await RefreshMediaSourceItems(cachedVersions, item.IsVideo, cancellationToken).ConfigureAwait(false);
+ return;
+ }
+
+ var source = list.First();
+
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = source.Path,
+ Progress = new Progress<double>()
+ };
+
+ foreach (var header in source.RequiredHttpHeaders)
+ {
+ options.RequestHeaders[header.Key] = header.Value;
+ }
+
+ var destination = Path.Combine(path, item.ChannelId, source.Path.GetMD5().ToString("N"));
+ Directory.CreateDirectory(Path.GetDirectoryName(destination));
+
+ // Determine output extension
+ var response = await _httpClient.GetTempFileResponse(options).ConfigureAwait(false);
+
+ if (item.IsVideo && response.ContentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
+ {
+ var extension = response.ContentType.Split('/')
+ .Last();
+
+ destination += "." + extension;
+ }
+ else if (item.IsAudio && response.ContentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase))
+ {
+ var extension = response.ContentType.Replace("audio/mpeg", "audio/mp3", StringComparison.OrdinalIgnoreCase)
+ .Split('/')
+ .Last();
+
+ destination += "." + extension;
+ }
+ else
+ {
+ throw new ApplicationException("Unexpected response type encountered: " + response.ContentType);
+ }
+
+ File.Move(response.TempFilePath, destination);
+
+ await RefreshMediaSourceItem(destination, item.IsVideo, cancellationToken).ConfigureAwait(false);
+ }
+
+ private async Task RefreshMediaSourceItems(IEnumerable<MediaSourceInfo> items, bool isVideo, CancellationToken cancellationToken)
+ {
+ foreach (var item in items)
+ {
+ await RefreshMediaSourceItem(item.Path, isVideo, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private async Task RefreshMediaSourceItem(string path, bool isVideo, CancellationToken cancellationToken)
+ {
+ var item = _libraryManager.ResolvePath(new FileInfo(path));
+
+ if (item != null)
+ {
+ await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ public IEnumerable<ITaskTrigger> GetDefaultTriggers()
+ {
+ return new ITaskTrigger[]
+ {
+ new DailyTrigger { TimeOfDay = TimeSpan.FromHours(3) },
+ };
+ }
+
+ /// <summary>
+ /// Deletes the cache files from directory with a last write time less than a given date
+ /// </summary>
+ /// <param name="cancellationToken">The task cancellation token.</param>
+ /// <param name="directory">The directory.</param>
+ /// <param name="minDateModified">The min date modified.</param>
+ /// <param name="progress">The progress.</param>
+ private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress)
+ {
+ var filesToDelete = new DirectoryInfo(directory).EnumerateFiles("*", SearchOption.AllDirectories)
+ .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
+ .ToList();
+
+ var index = 0;
+
+ foreach (var file in filesToDelete)
+ {
+ double percent = index;
+ percent /= filesToDelete.Count;
+
+ progress.Report(100 * percent);
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ DeleteFile(file.FullName);
+
+ index++;
+ }
+
+ progress.Report(100);
+ }
+
+ /// <summary>
+ /// Deletes the file.
+ /// </summary>
+ /// <param name="path">The path.</param>
+ private void DeleteFile(string path)
+ {
+ try
+ {
+ File.Delete(path);
+ }
+ catch (IOException ex)
+ {
+ _logger.ErrorException("Error deleting file {0}", ex, path);
+ }
+ }
+
+ public bool IsHidden
+ {
+ get
+ {
+ return !_manager.GetAllChannelFeatures()
+ .Any(i => i.CanDownloadAllMedia && _config.Configuration.ChannelOptions.DownloadingChannels.Contains(i.Id));
+ }
+ }
+
+ public bool IsEnabled
+ {
+ get
+ {
+ return true;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
index b9b9b8327..b9e4e73b0 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
@@ -54,6 +54,19 @@ namespace MediaBrowser.Server.Implementations.Channels
_factories = factories.ToArray();
}
+ public string ChannelDownloadPath
+ {
+ get
+ {
+ if (!string.IsNullOrWhiteSpace(_config.Configuration.ChannelOptions.DownloadPath))
+ {
+ return _config.Configuration.ChannelOptions.DownloadPath;
+ }
+
+ return Path.Combine(_config.ApplicationPaths.ProgramDataPath, "channels");
+ }
+ }
+
private IEnumerable<IChannel> GetAllChannels()
{
return _factories
@@ -156,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Channels
progress.Report(100);
}
- public Task<IEnumerable<ChannelMediaInfo>> GetChannelItemMediaSources(string id, CancellationToken cancellationToken)
+ public async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSources(string id, CancellationToken cancellationToken)
{
var item = (IChannelMediaItem)_libraryManager.GetItemById(id);
@@ -166,12 +179,149 @@ namespace MediaBrowser.Server.Implementations.Channels
var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
+ IEnumerable<ChannelMediaInfo> results;
+
if (requiresCallback != null)
{
- return requiresCallback.GetChannelItemMediaInfo(item.ExternalId, cancellationToken);
+ results = await requiresCallback.GetChannelItemMediaInfo(item.ExternalId, cancellationToken)
+ .ConfigureAwait(false);
+ }
+ else
+ {
+ results = item.ChannelMediaSources;
}
- return Task.FromResult<IEnumerable<ChannelMediaInfo>>(item.ChannelMediaSources);
+ var sources = SortMediaInfoResults(results).Select(i => GetMediaSource(item, i))
+ .ToList();
+
+ var channelIdString = channel.Id.ToString("N");
+ var isVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
+
+ var cachedVersionTasks = sources
+ .Select(i => GetCachedVersion(channelIdString, i, isVideo, cancellationToken));
+
+ var cachedVersions = await Task.WhenAll(cachedVersionTasks).ConfigureAwait(false);
+
+ sources.InsertRange(0, cachedVersions.Where(i => i != null));
+
+ return sources;
+ }
+
+ private MediaSourceInfo GetMediaSource(IChannelMediaItem item, ChannelMediaInfo info)
+ {
+ var id = info.Path.GetMD5().ToString("N");
+
+ var source = new MediaSourceInfo
+ {
+ MediaStreams = GetMediaStreams(info).ToList(),
+
+ Container = info.Container,
+ LocationType = info.IsRemote ? LocationType.Remote : LocationType.FileSystem,
+ Path = info.Path,
+ RequiredHttpHeaders = info.RequiredHttpHeaders,
+ RunTimeTicks = item.RunTimeTicks,
+ Name = id,
+ Id = id
+ };
+
+ return source;
+ }
+
+ private async Task<MediaSourceInfo> GetCachedVersion(string channelId,
+ MediaSourceInfo info,
+ bool isVideo,
+ CancellationToken cancellationToken)
+ {
+ var filename = info.Path.GetMD5().ToString("N");
+
+ var path = Path.Combine(ChannelDownloadPath, channelId, filename);
+
+ try
+ {
+ var file = Directory.EnumerateFiles(Path.GetDirectoryName(path), "*", SearchOption.TopDirectoryOnly)
+ .FirstOrDefault(i => (Path.GetFileName(i) ?? string.Empty).StartsWith(filename, StringComparison.OrdinalIgnoreCase));
+
+ if (!string.IsNullOrWhiteSpace(file))
+ {
+ var source = new MediaSourceInfo
+ {
+ Path = file,
+ LocationType = LocationType.FileSystem,
+ Name = "Cached " + info.Name,
+ Id = file.GetMD5().ToString("N")
+ };
+
+ if (isVideo)
+ {
+ source.VideoType = VideoType.VideoFile;
+ }
+
+ return source;
+ }
+ }
+ catch (DirectoryNotFoundException)
+ {
+ return null;
+ }
+
+ return null;
+ }
+
+ private IEnumerable<MediaStream> GetMediaStreams(ChannelMediaInfo info)
+ {
+ var list = new List<MediaStream>();
+
+ if (!string.IsNullOrWhiteSpace(info.VideoCodec) &&
+ !string.IsNullOrWhiteSpace(info.AudioCodec))
+ {
+ list.Add(new MediaStream
+ {
+ Type = MediaStreamType.Video,
+ Width = info.Width,
+ RealFrameRate = info.Framerate,
+ Profile = info.VideoProfile,
+ Level = info.VideoLevel,
+ Index = -1,
+ Height = info.Height,
+ Codec = info.VideoCodec,
+ BitRate = info.VideoBitrate,
+ AverageFrameRate = info.Framerate
+ });
+
+ list.Add(new MediaStream
+ {
+ Type = MediaStreamType.Audio,
+ Index = -1,
+ Codec = info.AudioCodec,
+ BitRate = info.AudioBitrate,
+ Channels = info.AudioChannels,
+ SampleRate = info.AudioSampleRate
+ });
+ }
+
+ return list;
+ }
+
+ private IEnumerable<ChannelMediaInfo> SortMediaInfoResults(IEnumerable<ChannelMediaInfo> channelMediaSources)
+ {
+ var list = channelMediaSources.ToList();
+
+ var width = _config.Configuration.ChannelOptions.PreferredStreamingWidth;
+
+ if (width.HasValue)
+ {
+ var val = width.Value;
+
+ return list
+ .OrderBy(i => i.Width.HasValue && i.Width.Value <= val)
+ .ThenBy(i => Math.Abs(i.Width ?? 0 - val))
+ .ThenByDescending(i => i.Width ?? 0)
+ .ThenBy(list.IndexOf);
+ }
+
+ return list
+ .OrderByDescending(i => i.Width ?? 0)
+ .ThenBy(list.IndexOf);
}
private async Task<Channel> GetChannel(IChannel channelInfo, CancellationToken cancellationToken)
@@ -237,26 +387,37 @@ namespace MediaBrowser.Server.Implementations.Channels
return (Channel)_libraryManager.GetItemById(new Guid(id));
}
+ public IEnumerable<ChannelFeatures> GetAllChannelFeatures()
+ {
+ return _channelEntities
+ .OrderBy(i => i.SortName)
+ .Select(i => GetChannelFeatures(i.Id.ToString("N")));
+ }
+
public ChannelFeatures GetChannelFeatures(string id)
{
var channel = GetChannel(id);
var channelProvider = GetChannelProvider(channel);
- return GetChannelFeaturesDto(channelProvider.GetChannelFeatures());
+ return GetChannelFeaturesDto(channel, channelProvider.GetChannelFeatures());
}
- public ChannelFeatures GetChannelFeaturesDto(InternalChannelFeatures features)
+ public ChannelFeatures GetChannelFeaturesDto(Channel channel, InternalChannelFeatures features)
{
return new ChannelFeatures
{
CanFilter = !features.MaxPageSize.HasValue,
+ CanGetAllMedia = features.CanGetAllMedia,
CanSearch = features.CanSearch,
ContentTypes = features.ContentTypes,
DefaultSortFields = features.DefaultSortFields,
MaxPageSize = features.MaxPageSize,
MediaTypes = features.MediaTypes,
- SupportsSortOrderToggle = features.SupportsSortOrderToggle
+ SupportsSortOrderToggle = features.SupportsSortOrderToggle,
+ Name = channel.Name,
+ Id = channel.Id.ToString("N"),
+ CanDownloadAllMedia = features.CanGetAllMedia
};
}
@@ -270,6 +431,85 @@ namespace MediaBrowser.Server.Implementations.Channels
return ("Channel " + name).GetMBId(typeof(Channel));
}
+ public async Task<QueryResult<BaseItemDto>> GetAllMedia(AllChannelMediaQuery query, CancellationToken cancellationToken)
+ {
+ var user = string.IsNullOrWhiteSpace(query.UserId)
+ ? null
+ : _userManager.GetUserById(new Guid(query.UserId));
+
+ var channels = _channels;
+
+ if (query.ChannelIds.Length > 0)
+ {
+ channels = channels
+ .Where(i => query.ChannelIds.Contains(GetInternalChannelId(i.Name).ToString("N")))
+ .ToArray();
+ }
+
+ var tasks = channels
+ .Where(i => i.GetChannelFeatures().CanGetAllMedia)
+ .Select(async i =>
+ {
+ try
+ {
+ var result = await i.GetAllMedia(new InternalAllChannelMediaQuery
+ {
+ User = user
+
+ }, cancellationToken).ConfigureAwait(false);
+
+ return new Tuple<IChannel, ChannelItemResult>(i, result);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting all media from {0}", ex, i.Name);
+ return new Tuple<IChannel, ChannelItemResult>(i, new ChannelItemResult { });
+ }
+ });
+
+ var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+
+ var totalCount = results.Length;
+
+ IEnumerable<Tuple<IChannel, ChannelItemInfo>> items = results
+ .SelectMany(i => i.Item2.Items.Select(m => new Tuple<IChannel, ChannelItemInfo>(i.Item1, m)))
+ .OrderBy(i => i.Item2.Name);
+
+ if (query.StartIndex.HasValue)
+ {
+ items = items.Skip(query.StartIndex.Value);
+ }
+ if (query.Limit.HasValue)
+ {
+ items = items.Take(query.Limit.Value);
+ }
+
+ // Avoid implicitly captured closure
+ var token = cancellationToken;
+ var itemTasks = items.Select(i =>
+ {
+ var channelProvider = i.Item1;
+ var channel = GetChannel(GetInternalChannelId(channelProvider.Name).ToString("N"));
+ return GetChannelItemEntity(i.Item2, channelProvider, channel, token);
+ });
+
+ var internalItems = await Task.WhenAll(itemTasks).ConfigureAwait(false);
+
+ // Get everything
+ var fields = Enum.GetNames(typeof(ItemFields))
+ .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
+ .ToList();
+
+ var returnItemArray = internalItems.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
+ .ToArray();
+
+ return new QueryResult<BaseItemDto>
+ {
+ TotalRecordCount = totalCount,
+ Items = returnItemArray
+ };
+ }
+
public async Task<QueryResult<BaseItemDto>> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken)
{
var queryChannelId = query.ChannelId;
@@ -301,7 +541,7 @@ namespace MediaBrowser.Server.Implementations.Channels
ChannelItemSortField? sortField = null;
ChannelItemSortField parsedField;
- if (query.SortBy.Length == 1 &&
+ if (query.SortBy.Length == 1 &&
Enum.TryParse(query.SortBy[0], true, out parsedField))
{
sortField = parsedField;
@@ -309,11 +549,11 @@ namespace MediaBrowser.Server.Implementations.Channels
var sortDescending = query.SortOrder.HasValue && query.SortOrder.Value == SortOrder.Descending;
- var itemsResult = await GetChannelItems(channelProvider,
- user,
- query.FolderId,
- providerStartIndex,
- providerLimit,
+ var itemsResult = await GetChannelItems(channelProvider,
+ user,
+ query.FolderId,
+ providerStartIndex,
+ providerLimit,
sortField,
sortDescending,
cancellationToken)
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index 88bf902db..520ffd417 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -846,16 +846,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (fields.Contains(ItemFields.Path))
{
- var locationType = item.LocationType;
-
- if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
- {
- dto.Path = GetMappedPath(item.Path);
- }
- else
- {
- dto.Path = item.Path;
- }
+ dto.Path = GetMappedPath(item);
}
dto.PremiereDate = item.PremiereDate;
@@ -1315,14 +1306,12 @@ namespace MediaBrowser.Server.Implementations.Dto
var locationType = item.LocationType;
- if (locationType != LocationType.FileSystem && locationType != LocationType.Offline)
- {
- return path;
- }
-
- foreach (var map in _config.Configuration.PathSubstitutions)
+ if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
{
- path = _fileSystem.SubstitutePath(path, map.From, map.To);
+ foreach (var map in _config.Configuration.PathSubstitutions)
+ {
+ path = _fileSystem.SubstitutePath(path, map.From, map.To);
+ }
}
return path;
@@ -1418,16 +1407,6 @@ namespace MediaBrowser.Server.Implementations.Dto
return string.Join("/", terms.ToArray());
}
- private string GetMappedPath(string path)
- {
- foreach (var map in _config.Configuration.PathSubstitutions)
- {
- path = _fileSystem.SubstitutePath(path, map.From, map.To);
- }
-
- return path;
- }
-
private void SetProductionLocations(BaseItem item, BaseItemDto dto)
{
var hasProductionLocations = item as IHasProductionLocations;
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index 0d0707386..df48e952f 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -297,6 +297,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var result = await service.GetRecordingStream(recording.Id, cancellationToken).ConfigureAwait(false);
+ Sanitize(result);
+
_logger.Debug("Live stream info: " + _json.SerializeToString(result));
if (!string.IsNullOrEmpty(result.Id))
@@ -332,6 +334,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
var result = await service.GetChannelStream(channel.ExternalId, cancellationToken).ConfigureAwait(false);
+ Sanitize(result);
+
_logger.Debug("Live stream info: " + _json.SerializeToString(result));
if (!string.IsNullOrEmpty(result.Id))
@@ -353,6 +357,55 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
+ private void Sanitize(LiveStreamInfo info)
+ {
+ // Clean some bad data coming from providers
+
+ foreach (var stream in info.MediaStreams)
+ {
+ if (stream.BitDepth.HasValue && stream.BitDepth <= 0)
+ {
+ stream.BitDepth = null;
+ }
+ if (stream.BitRate.HasValue && stream.BitRate <= 0)
+ {
+ stream.BitRate = null;
+ }
+ if (stream.Channels.HasValue && stream.Channels <= 0)
+ {
+ stream.Channels = null;
+ }
+ if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0)
+ {
+ stream.AverageFrameRate = null;
+ }
+ if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0)
+ {
+ stream.RealFrameRate = null;
+ }
+ if (stream.Width.HasValue && stream.Width <= 0)
+ {
+ stream.Width = null;
+ }
+ if (stream.Height.HasValue && stream.Height <= 0)
+ {
+ stream.Height = null;
+ }
+ if (stream.SampleRate.HasValue && stream.SampleRate <= 0)
+ {
+ stream.SampleRate = null;
+ }
+ if (stream.Level.HasValue && stream.Level <= 0)
+ {
+ stream.Level = null;
+ }
+ if (stream.PacketLength.HasValue && stream.PacketLength <= 0)
+ {
+ stream.PacketLength = null;
+ }
+ }
+ }
+
private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, CancellationToken cancellationToken)
{
var path = Path.Combine(_config.ApplicationPaths.ItemsByNamePath, "tvchannels", _fileSystem.GetValidFilename(channelInfo.Name));
diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
index 5c52a6095..cf827d26d 100644
--- a/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
+++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
@@ -136,5 +136,7 @@
"HeaderSelectServerCachePathHelp": "Browse or enter the path to use for server cache files. The folder must be writeable. The location of this folder will directly impact server performance and should ideally be placed on a solid state drive.",
"HeaderSelectTranscodingPathHelp": "Browse or enter the path to use for transcoding temporary files. The folder must be writeable.",
"HeaderSelectImagesByNamePathHelp": "Browse or enter the path to your items by name folder. The folder must be writeable.",
- "HeaderSelectMetadataPathHelp": "Browse or enter the path you'd like to store metadata within. The folder must be writeable."
+ "HeaderSelectMetadataPathHelp": "Browse or enter the path you'd like to store metadata within. The folder must be writeable.",
+ "HeaderSelectChannelDownloadPath": "Select Channel Download Path",
+ "HeaderSelectChannelDownloadPathHelp": "Browse or enter the path to use for storing channel cache files. The folder must be writeable."
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json
index a920b8e57..d7c923b58 100644
--- a/MediaBrowser.Server.Implementations/Localization/Server/server.json
+++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json
@@ -798,7 +798,13 @@
"ButtonDismiss": "Dismiss",
"MessageLearnHowToCustomize": "Learn how to customize this page to your own personal tastes. Click your user icon in the top right corner of the screen to view and update your preferences.",
"ButtonEditOtherUserPreferences": "Edit this user's personal preferences.",
- "ChannelStreamOptionBestAvailable": "Best available",
- "LabelChannelStreamOptionBestAvailable": "Preferred streaming quality:",
- "LabelChannelStreamOptionBestAvailableHelp": "Determines the selected quality when channel content is available in multiple resolutions."
+ "LabelChannelStreamQuality": "Preferred internet stream quality:",
+ "LabelChannelStreamQualityHelp": "In a low bandwidth environment, limiting quality can help ensure a smooth streaming experience.",
+ "OptionBestAvailableStreamQuality": "Best available",
+ "LabelEnableChannelContentDownloadingFor": "Enable channel content downloading for:",
+ "LabelEnableChannelContentDownloadingForHelp": "Some channels support downloading content prior to viewing. Enable this in low bandwidth enviornments to download channel content during off hours.",
+ "LabelChannelDownloadPath": "Channel content download path:",
+ "LabelChannelDownloadPathHelp": "Specify a custom download path if desired. Leave empty to download to an internal program data folder.",
+ "LabelChannelDownloadAge": "Delete content after: (days)",
+ "LabelChannelDownloadAgeHelp": "Downloaded content older than this will be deleted. It will remain playable via internet streaming."
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index efc7ebb6a..afe376217 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -98,6 +98,7 @@
<Compile Include="..\SharedVersion.cs">
<Link>Properties\SharedVersion.cs</Link>
</Compile>
+ <Compile Include="Channels\ChannelDownloadScheduledTask.cs" />
<Compile Include="Channels\ChannelImageProvider.cs" />
<Compile Include="Channels\ChannelItemImageProvider.cs" />
<Compile Include="Channels\ChannelManager.cs" />
diff --git a/MediaBrowser.Server.Implementations/Session/HttpSessionController.cs b/MediaBrowser.Server.Implementations/Session/HttpSessionController.cs
index f1d4c3555..a3a6d3630 100644
--- a/MediaBrowser.Server.Implementations/Session/HttpSessionController.cs
+++ b/MediaBrowser.Server.Implementations/Session/HttpSessionController.cs
@@ -6,6 +6,7 @@ using MediaBrowser.Model.Session;
using MediaBrowser.Model.System;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading;
@@ -125,13 +126,16 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
{
- return Task.FromResult(true);
- //return SendMessage(new WebSocketMessage<PlayRequest>
- //{
- // MessageType = "Play",
- // Data = command
+ var dict = new Dictionary<string, string>();
+
+ dict["ItemIds"] = string.Join(",", command.ItemIds);
+
+ if (command.StartPositionTicks.HasValue)
+ {
+ dict["StartPositionTicks"] = command.StartPositionTicks.Value.ToString(CultureInfo.InvariantCulture);
+ }
- //}, cancellationToken);
+ return SendMessage(command.PlayCommand.ToString(), dict, cancellationToken);
}
public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
@@ -140,7 +144,12 @@ namespace MediaBrowser.Server.Implementations.Session
if (command.Command == PlaystateCommand.Seek)
{
+ if (!command.SeekPositionTicks.HasValue)
+ {
+ throw new ArgumentException("SeekPositionTicks cannot be null");
+ }
+ args["StartPositionTicks"] = command.SeekPositionTicks.Value.ToString(CultureInfo.InvariantCulture);
}
return SendMessage(command.Command.ToString(), cancellationToken);
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index 265b00dfc..cffbf8b07 100644
--- a/Nuget/MediaBrowser.Common.Internal.nuspec
+++ b/Nuget/MediaBrowser.Common.Internal.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
- <version>3.0.391</version>
+ <version>3.0.393</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.391" />
+ <dependency id="MediaBrowser.Common" version="3.0.393" />
<dependency id="NLog" version="2.1.0" />
<dependency id="SimpleInjector" version="2.5.0" />
<dependency id="sharpcompress" version="0.10.2" />
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index f2d4e4eec..c2a74a1db 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/Nuget/MediaBrowser.Common.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common</id>
- <version>3.0.391</version>
+ <version>3.0.393</version>
<title>MediaBrowser.Common</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index 6a8991ae2..6e12914ae 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/Nuget/MediaBrowser.Server.Core.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
- <version>3.0.391</version>
+ <version>3.0.393</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.391" />
+ <dependency id="MediaBrowser.Common" version="3.0.393" />
</dependencies>
</metadata>
<files>