aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2014-06-02 15:32:41 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2014-06-02 15:32:41 -0400
commit858c37b8607ff0698a94b9e7bfff6190d3bca56d (patch)
treeec673c5ebe7ffe813b6a16340471ac472a5dbf5b
parent36648d27082c1ee50c1483e17f14ba1ae838a00e (diff)
add channel downloading settings
-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 9a7254c94a..75c2d406d3 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 2a2d316d3c..a410a093e8 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 16f3f0c1d8..d407629644 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 62d5a6ce22..92bbb61300 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 5999a2b552..9f7c1a6c4f 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 553f023684..a7412e3d8c 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 c5405a3b5d..96ecfb055b 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 162ab741aa..e4ee682411 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 2e002cc9be..18cfbbdf2d 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 06e8f7b2c9..5e3e85140e 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 58f36a06c1..8ba504e14d 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 29bc3f8978..65a62aad3b 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 531f79a223..0000000000
--- 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 f9e861e8d4..295ee44e75 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 969916ef67..a4337bab46 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 bc03192092..10b5e1983c 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 7072d42848..8afaa73aca 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 5be30d7c3b..afc6493e4a 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 b38ef363a8..2e2f1912a1 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 572e316a0f..2f207c4201 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 27334e4b94..1d7c298fab 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 4c2d665e54..4be38870b8 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 357856a254..db8e2b2927 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 57a1a5129a..44da646d04 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 1b05e60b61..dceced14c4 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 98f9417d00..63cb9210a1 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 d0d93c26be..70124c3e1d 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 494ebe3a3c..2acbdf9c30 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 7c3f76fda9..bd2accf652 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 17c7e8b557..34707ecd7e 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 0000000000..b7921d993d
--- /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 660d4c09c8..598eacc21d 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 cd489bb360..6b8605b7a1 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 0a33591560..96020791e3 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 0000000000..21b5ba6dae
--- /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 b9b9b8327b..b9e4e73b0e 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 88bf902db9..520ffd417d 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 0d0707386b..df48e952f8 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 5c52a6095c..cf827d26de 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 a920b8e572..d7c923b58a 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 efc7ebb6ad..afe3762177 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 f1d4c3555c..a3a6d36305 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 265b00dfcd..cffbf8b07a 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 f2d4e4eecd..c2a74a1db0 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 6a8991ae20..6e12914ae4 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>