diff --git a/Emby.Drawing/ImageMagick/ImageMagickEncoder.cs b/Emby.Drawing/ImageMagick/ImageMagickEncoder.cs
index ff4a8f55bc..78633472b6 100644
--- a/Emby.Drawing/ImageMagick/ImageMagickEncoder.cs
+++ b/Emby.Drawing/ImageMagick/ImageMagickEncoder.cs
@@ -115,9 +115,17 @@ namespace Emby.Drawing.ImageMagick
}
}
+ private bool HasTransparency(string path)
+ {
+ var ext = Path.GetExtension(path);
+
+ return string.Equals(ext, ".png", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(ext, ".webp", StringComparison.OrdinalIgnoreCase);
+ }
+
public void EncodeImage(string inputPath, string outputPath, int width, int height, int quality, ImageProcessingOptions options)
{
- if (string.IsNullOrWhiteSpace(options.BackgroundColor))
+ if (string.IsNullOrWhiteSpace(options.BackgroundColor) || !HasTransparency(inputPath))
{
using (var originalImage = new MagickWand(inputPath))
{
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 2cd9007544..05ff503e47 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -250,19 +250,19 @@ namespace MediaBrowser.Api
return GetTranscodingJob(path, type) != null;
}
- public TranscodingJob GetTranscodingJob(string path, TranscodingJobType type)
+ public TranscodingJob GetTranscodingJobByPlaySessionId(string playSessionId)
{
lock (_activeTranscodingJobs)
{
- return _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
+ return _activeTranscodingJobs.FirstOrDefault(j => j.PlaySessionId.Equals(playSessionId, StringComparison.OrdinalIgnoreCase));
}
}
- public TranscodingJob GetTranscodingJob(string id)
+ public TranscodingJob GetTranscodingJob(string path, TranscodingJobType type)
{
lock (_activeTranscodingJobs)
{
- return _activeTranscodingJobs.FirstOrDefault(j => j.Id.Equals(id, StringComparison.OrdinalIgnoreCase));
+ return _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && j.Path.Equals(path, StringComparison.OrdinalIgnoreCase));
}
}
@@ -339,14 +339,17 @@ namespace MediaBrowser.Api
return;
}
- var timerDuration = job.Type == TranscodingJobType.Progressive ?
- 1000 :
- 1800000;
+ var timerDuration = 1000;
- // We can really reduce the timeout for apps that are using the newer api
- if (!string.IsNullOrWhiteSpace(job.PlaySessionId) && job.Type != TranscodingJobType.Progressive)
+ if (job.Type != TranscodingJobType.Progressive)
{
- timerDuration = 50000;
+ timerDuration = 1800000;
+
+ // We can really reduce the timeout for apps that are using the newer api
+ if (!string.IsNullOrWhiteSpace(job.PlaySessionId))
+ {
+ timerDuration = 60000;
+ }
}
job.PingTimeout = timerDuration;
@@ -628,6 +631,9 @@ namespace MediaBrowser.Api
///
/// The live stream identifier.
public string LiveStreamId { get; set; }
+
+ public bool IsLiveOutput { get; set; }
+
///
/// Gets or sets the path.
///
diff --git a/MediaBrowser.Api/Dlna/DlnaServerService.cs b/MediaBrowser.Api/Dlna/DlnaServerService.cs
index bdf7d6b074..4f5e2ab259 100644
--- a/MediaBrowser.Api/Dlna/DlnaServerService.cs
+++ b/MediaBrowser.Api/Dlna/DlnaServerService.cs
@@ -109,7 +109,7 @@ namespace MediaBrowser.Api.Dlna
private readonly IMediaReceiverRegistrar _mediaReceiverRegistrar;
// TODO: Add utf-8
- private const string XMLContentType = "text/xml";
+ private const string XMLContentType = "text/xml; charset=UTF-8";
public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IMediaReceiverRegistrar mediaReceiverRegistrar)
{
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index 639c1f54b0..8c6cc0a18e 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -625,6 +625,8 @@ namespace MediaBrowser.Api.Images
var file = await _imageProcessor.ProcessImage(options).ConfigureAwait(false);
+ headers["Vary"] = "Accept";
+
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
{
CacheDuration = cacheDuration,
@@ -659,8 +661,10 @@ namespace MediaBrowser.Api.Images
return ImageFormat.Png;
}
- if (string.Equals(Path.GetExtension(image.Path), ".jpg", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(Path.GetExtension(image.Path), ".jpeg", StringComparison.OrdinalIgnoreCase))
+ var extension = Path.GetExtension(image.Path);
+
+ if (string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase))
{
return ImageFormat.Jpg;
}
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index 0138380915..bab02de356 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -389,22 +389,28 @@ namespace MediaBrowser.Api
game.PlayersSupported = request.Players;
}
- var hasAlbumArtists = item as IHasAlbumArtist;
- if (hasAlbumArtists != null)
+ if (request.AlbumArtists != null)
{
- hasAlbumArtists.AlbumArtists = request
- .AlbumArtists
- .Select(i => i.Name)
- .ToList();
+ var hasAlbumArtists = item as IHasAlbumArtist;
+ if (hasAlbumArtists != null)
+ {
+ hasAlbumArtists.AlbumArtists = request
+ .AlbumArtists
+ .Select(i => i.Name)
+ .ToList();
+ }
}
- var hasArtists = item as IHasArtist;
- if (hasArtists != null)
+ if (request.ArtistItems != null)
{
- hasArtists.Artists = request
- .ArtistItems
- .Select(i => i.Name)
- .ToList();
+ var hasArtists = item as IHasArtist;
+ if (hasArtists != null)
+ {
+ hasArtists.Artists = request
+ .ArtistItems
+ .Select(i => i.Name)
+ .ToList();
+ }
}
var song = item as Audio;
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj
index ab205d6ebf..0dfd812c3a 100644
--- a/MediaBrowser.Api/MediaBrowser.Api.csproj
+++ b/MediaBrowser.Api/MediaBrowser.Api.csproj
@@ -84,10 +84,28 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs
index 78c6a8bf41..46034dc61a 100644
--- a/MediaBrowser.Api/Music/InstantMixService.cs
+++ b/MediaBrowser.Api/Music/InstantMixService.cs
@@ -50,7 +50,7 @@ namespace MediaBrowser.Api.Music
[Route("/MusicGenres/InstantMix", "GET", Summary = "Creates an instant playlist based on a music genre")]
public class GetInstantMixFromMusicGenreId : BaseGetSimilarItems
{
- [ApiMember(Name = "Id", Description = "The genre Id", IsRequired = true, DataType = "string", ParameterType = "querypath", Verb = "GET")]
+ [ApiMember(Name = "Id", Description = "The genre Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
public string Id { get; set; }
}
diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs
index 1d792fbc16..5ef8b09871 100644
--- a/MediaBrowser.Api/PackageService.cs
+++ b/MediaBrowser.Api/PackageService.cs
@@ -56,6 +56,8 @@ namespace MediaBrowser.Api
[ApiMember(Name = "IsAdult", Description = "Optional. Filter by package that contain adult content.", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? IsAdult { get; set; }
+
+ public bool? IsAppStoreEnabled { get; set; }
}
///
@@ -207,6 +209,11 @@ namespace MediaBrowser.Api
packages = packages.Where(p => p.adult == request.IsAdult.Value);
}
+ if (request.IsAppStoreEnabled.HasValue)
+ {
+ packages = packages.Where(p => p.enableInAppStore == request.IsAppStoreEnabled.Value);
+ }
+
return ToOptimizedResult(packages.ToList());
}
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 5e06ab1d00..31679aad3c 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -157,11 +157,11 @@ namespace MediaBrowser.Api.Playback
/// The fast seek command line parameter.
protected string GetFastSeekCommandLineParameter(StreamRequest request)
{
- var time = request.StartTimeTicks;
+ var time = request.StartTimeTicks ?? 0;
- if (time.HasValue && time.Value > 0)
+ if (time > 0)
{
- return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time.Value));
+ return string.Format("-ss {0}", MediaEncoder.GetTimeParameter(time));
}
return string.Empty;
@@ -690,7 +690,7 @@ namespace MediaBrowser.Api.Playback
// TODO: Perhaps also use original_size=1920x800 ??
return string.Format("subtitles=filename='{0}'{1},setpts=PTS -{2}/TB",
- subtitlePath.Replace('\\', '/').Replace(":/", "\\:/"),
+ subtitlePath.Replace("'", "\\'").Replace('\\', '/').Replace(":/", "\\:/"),
charsetParam,
seconds.ToString(UsCulture));
}
@@ -698,7 +698,7 @@ namespace MediaBrowser.Api.Playback
var mediaPath = state.MediaPath ?? string.Empty;
return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB",
- mediaPath.Replace('\\', '/').Replace(":/", "\\:/"),
+ mediaPath.Replace("'", "\\'").Replace('\\', '/').Replace(":/", "\\:/"),
state.InternalSubtitleStreamOffset.ToString(UsCulture),
seconds.ToString(UsCulture));
}
@@ -769,26 +769,31 @@ namespace MediaBrowser.Api.Playback
/// System.Nullable{System.Int32}.
private int? GetNumAudioChannelsParam(StreamRequest request, MediaStream audioStream, string outputAudioCodec)
{
- if (audioStream != null)
- {
- var codec = outputAudioCodec ?? string.Empty;
+ var inputChannels = audioStream == null
+ ? null
+ : audioStream.Channels;
- if (audioStream.Channels > 2 && codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
- {
- // wmav2 currently only supports two channel output
- return 2;
- }
+ var codec = outputAudioCodec ?? string.Empty;
+
+ if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ // wmav2 currently only supports two channel output
+ return Math.Min(2, inputChannels ?? 2);
}
if (request.MaxAudioChannels.HasValue)
{
- if (audioStream != null && audioStream.Channels.HasValue)
+ if (inputChannels.HasValue)
{
- return Math.Min(request.MaxAudioChannels.Value, audioStream.Channels.Value);
+ return Math.Min(request.MaxAudioChannels.Value, inputChannels.Value);
}
+ var channelLimit = codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1
+ ? 2
+ : 5;
+
// If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels
- return Math.Min(request.MaxAudioChannels.Value, 5);
+ return Math.Min(request.MaxAudioChannels.Value, channelLimit);
}
return request.AudioChannels;
@@ -1055,7 +1060,7 @@ namespace MediaBrowser.Api.Playback
private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
{
- if (state.InputProtocol == MediaProtocol.File &&
+ if (EnableThrottling(state) && state.InputProtocol == MediaProtocol.File &&
state.RunTimeTicks.HasValue &&
state.VideoType == VideoType.VideoFile &&
!string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
@@ -1068,6 +1073,11 @@ namespace MediaBrowser.Api.Playback
}
}
+ protected virtual bool EnableThrottling(StreamState state)
+ {
+ return true;
+ }
+
private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
{
try
@@ -1690,6 +1700,11 @@ namespace MediaBrowser.Api.Playback
private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest)
{
+ if (!EnableStreamCopy)
+ {
+ return;
+ }
+
if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream))
{
state.OutputVideoCodec = "copy";
@@ -1701,6 +1716,14 @@ namespace MediaBrowser.Api.Playback
}
}
+ protected virtual bool EnableStreamCopy
+ {
+ get
+ {
+ return true;
+ }
+ }
+
private void AttachMediaSourceInfo(StreamState state,
MediaSourceInfo mediaSource,
VideoStreamRequest videoRequest,
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index b10c02e17c..b2ffeca3db 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -7,13 +7,13 @@ using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Api.Playback.Hls
{
@@ -100,6 +100,7 @@ namespace MediaBrowser.Api.Playback.Hls
try
{
job = await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false);
+ job.IsLiveOutput = isLive;
}
catch
{
@@ -133,7 +134,7 @@ namespace MediaBrowser.Api.Playback.Hls
var appendBaselineStream = false;
var baselineStreamBitrate = 64000;
- var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
+ var hlsVideoRequest = state.VideoRequest as GetHlsVideoStreamLegacy;
if (hlsVideoRequest != null)
{
appendBaselineStream = hlsVideoRequest.AppendBaselineStream;
@@ -244,7 +245,7 @@ namespace MediaBrowser.Api.Playback.Hls
protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding)
{
- var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream;
+ var hlsVideoRequest = state.VideoRequest as GetHlsVideoStreamLegacy;
var itsOffsetMs = hlsVideoRequest == null
? 0
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 1f6bc242df..fdddc0c37f 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -13,6 +13,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using ServiceStack;
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@@ -29,27 +30,60 @@ namespace MediaBrowser.Api.Playback.Hls
///
[Route("/Videos/{Id}/master.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
[Route("/Videos/{Id}/master.m3u8", "HEAD", Summary = "Gets a video stream using HTTP live streaming.")]
- public class GetMasterHlsVideoStream : VideoStreamRequest
+ public class GetMasterHlsVideoPlaylist : VideoStreamRequest, IMasterHlsRequest
{
public bool EnableAdaptiveBitrateStreaming { get; set; }
- public GetMasterHlsVideoStream()
+ public GetMasterHlsVideoPlaylist()
{
EnableAdaptiveBitrateStreaming = true;
}
}
+ [Route("/Audio/{Id}/master.m3u8", "GET", Summary = "Gets an audio stream using HTTP live streaming.")]
+ [Route("/Audio/{Id}/master.m3u8", "HEAD", Summary = "Gets an audio stream using HTTP live streaming.")]
+ public class GetMasterHlsAudioPlaylist : StreamRequest, IMasterHlsRequest
+ {
+ public bool EnableAdaptiveBitrateStreaming { get; set; }
+
+ public GetMasterHlsAudioPlaylist()
+ {
+ EnableAdaptiveBitrateStreaming = true;
+ }
+ }
+
+ public interface IMasterHlsRequest
+ {
+ bool EnableAdaptiveBitrateStreaming { get; set; }
+ }
+
[Route("/Videos/{Id}/main.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
- public class GetMainHlsVideoStream : VideoStreamRequest
+ public class GetVariantHlsVideoPlaylist : VideoStreamRequest
+ {
+ }
+
+ [Route("/Audio/{Id}/main.m3u8", "GET", Summary = "Gets an audio stream using HTTP live streaming.")]
+ public class GetVariantHlsAudioPlaylist : StreamRequest
{
}
- ///
- /// Class GetHlsVideoSegment
- ///
[Route("/Videos/{Id}/hlsdynamic/{PlaylistId}/{SegmentId}.ts", "GET")]
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
- public class GetDynamicHlsVideoSegment : VideoStreamRequest
+ public class GetHlsVideoSegment : VideoStreamRequest
+ {
+ public string PlaylistId { get; set; }
+
+ ///
+ /// Gets or sets the segment id.
+ ///
+ /// The segment id.
+ public string SegmentId { get; set; }
+ }
+
+ [Route("/Audio/{Id}/hlsdynamic/{PlaylistId}/{SegmentId}.aac", "GET")]
+ [Route("/Audio/{Id}/hlsdynamic/{PlaylistId}/{SegmentId}.ts", "GET")]
+ [Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
+ public class GetHlsAudioSegment : StreamRequest
{
public string PlaylistId { get; set; }
@@ -62,34 +96,55 @@ namespace MediaBrowser.Api.Playback.Hls
public class DynamicHlsService : BaseHlsService
{
- public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer)
+ public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IDlnaManager dlnaManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IZipClient zipClient, IJsonSerializer jsonSerializer, INetworkManager networkManager)
+ : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, dlnaManager, subtitleEncoder, deviceManager, mediaSourceManager, zipClient, jsonSerializer)
{
NetworkManager = networkManager;
}
protected INetworkManager NetworkManager { get; private set; }
- public Task