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
/// The start time ticks.
/// The source path.
/// The device id.
- public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, long? startTimeTicks, string sourcePath, string deviceId)
+ /// The cancellation token source.
+ 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; }
}
///
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>
+ {
+ }
+
[Route("/Channels/{Id}/Items", "GET", Summary = "Gets channel items")]
public class GetChannelItems : IReturn>
{
@@ -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; }
- }
-
///
/// Class ImageByNameService
///
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 @@
-
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; }
///
/// Initializes a new instance of the class.
///
- /// The server configuration.
- /// The user manager.
- /// The library manager.
- /// The iso manager.
- /// The media encoder.
- /// The dto service.
- /// The file system.
- /// The item repository.
- 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
/// The state.
/// The output video codec.
/// if set to true [perform text subtitle conversion].
+ /// The cancellation token.
/// System.String.
- 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
///
/// The state.
/// if set to true [perform conversion].
+ /// The cancellation token.
/// System.String.
- 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
///
/// The state.
/// if set to true [perform conversion].
+ /// The cancellation token.
/// System.String.
- 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
///
/// Gets the converted ass path.
///
- /// The media path.
/// The subtitle stream.
/// if set to true [perform conversion].
+ /// The cancellation token.
/// System.String.
- 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
/// System.String.
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
///
/// The state.
/// The output path.
+ /// The cancellation token source.
/// Task.
- protected async Task StartFfMpeg(StreamState state, string outputPath)
+ /// ffmpeg was not found at + MediaEncoder.EncoderPath
+ 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
///
/// Gets the user agent param.
///
- /// The path.
+ /// The state.
/// System.String.
- 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);
- var video = item as Video;
+ state.MediaPath = mediaSource.Path;
+ state.IsRemote = mediaSource.LocationType == LocationType.Remote;
+
+ 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 GetMediaStreams(ChannelMediaInfo info)
- {
- var list = new List();
-
- 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 GetChannelMediaInfo(string id, CancellationToken cancellationToken)
+ private async Task 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
///
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
///
private async Task