diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 9e7b23b78a..311c0a3b9d 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Net.Http; using System.Threading.Tasks; +using Jellyfin.Api.Constants; using Jellyfin.Api.Helpers; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -15,6 +16,8 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -40,8 +43,26 @@ namespace Jellyfin.Api.Controllers private readonly TranscodingJobHelper _transcodingJobHelper; private readonly IConfiguration _configuration; private readonly ISubtitleEncoder _subtitleEncoder; - private readonly IStreamHelper _streamHelper; + private readonly IHttpClientFactory _httpClientFactory; + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. public UniversalAudioController( ILoggerFactory loggerFactory, IServerConfigurationManager serverConfigurationManager, @@ -57,7 +78,7 @@ namespace Jellyfin.Api.Controllers TranscodingJobHelper transcodingJobHelper, IConfiguration configuration, ISubtitleEncoder subtitleEncoder, - IStreamHelper streamHelper) + IHttpClientFactory httpClientFactory) { _userManager = userManager; _libraryManager = libraryManager; @@ -73,13 +94,39 @@ namespace Jellyfin.Api.Controllers _transcodingJobHelper = transcodingJobHelper; _configuration = configuration; _subtitleEncoder = subtitleEncoder; - _streamHelper = streamHelper; + _httpClientFactory = httpClientFactory; } + /// + /// Gets an audio stream. + /// + /// The item id. + /// Optional. The audio container. + /// The media version id, if playing an alternate version. + /// The device id of the client requesting. Used to stop encoding processes when needed. + /// Optional. The user id. + /// Optional. The audio codec to transcode to. + /// Optional. The maximum number of audio channels. + /// Optional. The number of how many audio channels to transcode to. + /// Optional. The maximum streaming bitrate. + /// Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms. + /// Optional. The container to transcode to. + /// Optional. The transcoding protocol. + /// Optional. The maximum audio sample rate. + /// Optional. The maximum audio bit depth. + /// Optional. Whether to enable remote media. + /// Optional. Whether to break on non key frames. + /// Whether to enable redirection. Defaults to true. + /// Audio stream returned. + /// Redirected to remote audio stream. + /// A containing the audio file. [HttpGet("/Audio/{itemId}/universal")] [HttpGet("/Audio/{itemId}/{universal=universal}.{container?}")] [HttpHead("/Audio/{itemId}/universal")] [HttpHead("/Audio/{itemId}/{universal=universal}.{container?}")] + [Authorize(Policy = Policies.DefaultAuthorization)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status302Found)] public async Task GetUniversalAudioStream( [FromRoute] Guid itemId, [FromRoute] string? container, @@ -121,44 +168,138 @@ namespace Jellyfin.Api.Controllers var isStatic = mediaSource.SupportsDirectStream; if (!isStatic && string.Equals(mediaSource.TranscodingSubProtocol, "hls", StringComparison.OrdinalIgnoreCase)) { - // TODO new DynamicHlsController - // var dynamicHlsController = new DynamicHlsController(); + var dynamicHlsController = new DynamicHlsController( + _libraryManager, + _userManager, + _dlnaManager, + _authorizationContext, + _mediaSourceManager, + _serverConfigurationManager, + _mediaEncoder, + _fileSystem, + _subtitleEncoder, + _configuration, + _deviceManager, + _transcodingJobHelper, + _networkManager, + _loggerFactory.CreateLogger()); var transcodingProfile = deviceProfile.TranscodingProfiles[0]; // hls segment container can only be mpegts or fmp4 per ffmpeg documentation // TODO: remove this when we switch back to the segment muxer - var supportedHLSContainers = new[] { "mpegts", "fmp4" }; - - /* - var newRequest = new GetMasterHlsAudioPlaylist - { - AudioBitRate = isStatic ? (int?)null : Convert.ToInt32(Math.Min(request.MaxStreamingBitrate ?? 192000, int.MaxValue)), - AudioCodec = transcodingProfile.AudioCodec, - Container = ".m3u8", - DeviceId = request.DeviceId, - Id = request.Id, - MaxAudioChannels = request.MaxAudioChannels, - MediaSourceId = mediaSource.Id, - PlaySessionId = playbackInfoResult.PlaySessionId, - StartTimeTicks = request.StartTimeTicks, - Static = isStatic, - // fallback to mpegts if device reports some weird value unsupported by hls - SegmentContainer = Array.Exists(supportedHLSContainers, element => element == request.TranscodingContainer) ? request.TranscodingContainer : "mpegts", - AudioSampleRate = request.MaxAudioSampleRate, - MaxAudioBitDepth = request.MaxAudioBitDepth, - BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames, - TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()) - }; + var supportedHlsContainers = new[] { "mpegts", "fmp4" }; if (isHeadRequest) { - audioController.Request.Method = HttpMethod.Head.Method; - return await service.Head(newRequest).ConfigureAwait(false); + dynamicHlsController.Request.Method = HttpMethod.Head.Method; + return await dynamicHlsController.GetMasterHlsAudioPlaylist( + itemId, + ".m3u8", + isStatic, + null, + null, + null, + playbackInfoResult.Value.PlaySessionId, + // fallback to mpegts if device reports some weird value unsupported by hls + Array.Exists(supportedHlsContainers, element => element == transcodingContainer) ? transcodingContainer : "mpegts", + null, + null, + mediaSource.Id, + deviceId, + transcodingProfile.AudioCodec, + null, + null, + transcodingProfile.BreakOnNonKeyFrames, + maxAudioSampleRate, + maxAudioBitDepth, + null, + isStatic ? (int?)null : Convert.ToInt32(Math.Min(maxStreamingBitrate ?? 192000, int.MaxValue)), + null, + maxAudioChannels, + null, + null, + null, + null, + null, + startTimeTicks, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()), + null, + null, + null, + null, + null, + null) + .ConfigureAwait(false); } - return await service.Get(newRequest).ConfigureAwait(false);*/ - // TODO remove this line - return Content(string.Empty); + return await dynamicHlsController.GetMasterHlsAudioPlaylist( + itemId, + ".m3u8", + isStatic, + null, + null, + null, + playbackInfoResult.Value.PlaySessionId, + // fallback to mpegts if device reports some weird value unsupported by hls + Array.Exists(supportedHlsContainers, element => element == transcodingContainer) ? transcodingContainer : "mpegts", + null, + null, + mediaSource.Id, + deviceId, + transcodingProfile.AudioCodec, + null, + null, + transcodingProfile.BreakOnNonKeyFrames, + maxAudioSampleRate, + maxAudioBitDepth, + null, + isStatic ? (int?)null : Convert.ToInt32(Math.Min(maxStreamingBitrate ?? 192000, int.MaxValue)), + null, + maxAudioChannels, + null, + null, + null, + null, + null, + startTimeTicks, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()), + null, + null, + null, + null, + null, + null) + .ConfigureAwait(false); } else { @@ -170,14 +311,12 @@ namespace Jellyfin.Api.Controllers _mediaSourceManager, _serverConfigurationManager, _mediaEncoder, - _streamHelper, _fileSystem, _subtitleEncoder, _configuration, _deviceManager, _transcodingJobHelper, - // TODO HttpClient - new HttpClient()); + _httpClientFactory); if (isHeadRequest) { @@ -304,11 +443,11 @@ namespace Jellyfin.Api.Controllers var directPlayProfiles = new List(); - var containers = (container ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var containers = RequestHelpers.Split(container, ',', true); foreach (var cont in containers) { - var parts = cont.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + var parts = RequestHelpers.Split(cont, ',', true); var audioCodecs = parts.Length == 1 ? null : string.Join(",", parts.Skip(1).ToArray());