2
0
Эх сурвалжийг харах

Move AudioService to Jellyfin.Api

David 4 жил өмнө
parent
commit
2ce97c022e

+ 59 - 21
Jellyfin.Api/Controllers/AudioController.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
 using Jellyfin.Api.Helpers;
+using Jellyfin.Api.Models.StreamingDtos;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Devices;
@@ -141,10 +142,10 @@ namespace Jellyfin.Api.Controllers
         /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
         /// <param name="streamOptions">Optional. The streaming options.</param>
         /// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
-        [HttpGet("{itemId}/stream.{container}")]
-        [HttpGet("{itemId}/stream")]
-        [HttpHead("{itemId}/stream.{container}")]
+        [HttpGet("{itemId}/{stream=stream}.{container?}")]
         [HttpGet("{itemId}/stream")]
+        [HttpHead("{itemId}/{stream=stream}.{container?}")]
+        [HttpHead("{itemId}/stream")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult> GetAudioStream(
             [FromRoute] Guid itemId,
@@ -201,21 +202,61 @@ namespace Jellyfin.Api.Controllers
 
             var cancellationTokenSource = new CancellationTokenSource();
 
+            StreamingRequestDto streamingRequest = new StreamingRequestDto
+            {
+                Id = itemId,
+                Container = container,
+                Static = @static.HasValue ? @static.Value : true,
+                Params = @params,
+                Tag = tag,
+                DeviceProfileId = deviceProfileId,
+                PlaySessionId = playSessionId,
+                SegmentContainer = segmentContainer,
+                SegmentLength = segmentLength,
+                MinSegments = minSegments,
+                MediaSourceId = mediaSourceId,
+                DeviceId = deviceId,
+                AudioCodec = audioCodec,
+                EnableAutoStreamCopy = enableAutoStreamCopy.HasValue ? enableAutoStreamCopy.Value : true,
+                AllowAudioStreamCopy = allowAudioStreamCopy.HasValue ? allowAudioStreamCopy.Value : true,
+                AllowVideoStreamCopy = allowVideoStreamCopy.HasValue ? allowVideoStreamCopy.Value : true,
+                BreakOnNonKeyFrames = breakOnNonKeyFrames.HasValue ? breakOnNonKeyFrames.Value : false,
+                AudioSampleRate = audioSampleRate,
+                MaxAudioChannels = maxAudioChannels,
+                AudioBitRate = audioBitRate,
+                MaxAudioBitDepth = maxAudioBitDepth,
+                AudioChannels = audioChannels,
+                Profile = profile,
+                Level = level,
+                Framerate = framerate,
+                MaxFramerate = maxFramerate,
+                CopyTimestamps = copyTimestamps.HasValue ? copyTimestamps.Value : true,
+                StartTimeTicks = startTimeTicks,
+                Width = width,
+                Height = height,
+                VideoBitRate = videoBitRate,
+                SubtitleStreamIndex = subtitleStreamIndex,
+                SubtitleMethod = subtitleMethod,
+                MaxRefFrames = maxRefFrames,
+                MaxVideoBitDepth = maxVideoBitDepth,
+                RequireAvc = requireAvc.HasValue ? requireAvc.Value : true,
+                DeInterlace = deInterlace.HasValue ? deInterlace.Value : true,
+                RequireNonAnamorphic = requireNonAnamorphic.HasValue ? requireNonAnamorphic.Value : true,
+                TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
+                CpuCoreLimit = cpuCoreLimit,
+                LiveStreamId = liveStreamId,
+                EnableMpegtsM2TsMode = enableMpegtsM2TsMode.HasValue ? enableMpegtsM2TsMode.Value : true,
+                VideoCodec = videoCodec,
+                SubtitleCodec = subtitleCodec,
+                TranscodeReasons = transcodingReasons,
+                AudioStreamIndex = audioStreamIndex,
+                VideoStreamIndex = videoStreamIndex,
+                Context = context,
+                StreamOptions = streamOptions
+            };
+
             var state = await StreamingHelpers.GetStreamingState(
-                    itemId,
-                    startTimeTicks,
-                    audioCodec,
-                    subtitleCodec,
-                    videoCodec,
-                    @params,
-                    @static,
-                    container,
-                    liveStreamId,
-                    playSessionId,
-                    mediaSourceId,
-                    deviceId,
-                    deviceProfileId,
-                    audioBitRate,
+                    streamingRequest,
                     Request,
                     _authContext,
                     _mediaSourceManager,
@@ -230,7 +271,6 @@ namespace Jellyfin.Api.Controllers
                     _deviceManager,
                     _transcodingJobHelper,
                     _transcodingJobType,
-                    false,
                     cancellationTokenSource.Token)
                 .ConfigureAwait(false);
 
@@ -255,7 +295,7 @@ namespace Jellyfin.Api.Controllers
 
                 using (state)
                 {
-                    return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this, cancellationTokenSource).ConfigureAwait(false);
+                    return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, this).ConfigureAwait(false);
                 }
             }
 
@@ -297,8 +337,6 @@ namespace Jellyfin.Api.Controllers
                     return FileStreamResponseHelpers.GetStaticFileResult(
                         state.MediaPath,
                         contentType,
-                        _fileSystem.GetLastWriteTimeUtc(state.MediaPath),
-                        cacheDuration,
                         isHeadRequest,
                         this);
                 }

+ 6 - 11
Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs

@@ -23,13 +23,11 @@ namespace Jellyfin.Api.Helpers
         /// <param name="state">The current <see cref="StreamState"/>.</param>
         /// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param>
         /// <param name="controller">The <see cref="ControllerBase"/> managing the response.</param>
-        /// <param name="cancellationTokenSource">The <see cref="CancellationTokenSource"/>.</param>
         /// <returns>A <see cref="Task{ActionResult}"/> containing the API response.</returns>
         public static async Task<ActionResult> GetStaticRemoteStreamResult(
             StreamState state,
             bool isHeadRequest,
-            ControllerBase controller,
-            CancellationTokenSource cancellationTokenSource)
+            ControllerBase controller)
         {
             HttpClient httpClient = new HttpClient();
 
@@ -59,16 +57,12 @@ namespace Jellyfin.Api.Helpers
         /// </summary>
         /// <param name="path">The path to the file.</param>
         /// <param name="contentType">The content type of the file.</param>
-        /// <param name="dateLastModified">The <see cref="DateTime"/> of the last modification of the file.</param>
-        /// <param name="cacheDuration">The cache duration of the file.</param>
         /// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param>
         /// <param name="controller">The <see cref="ControllerBase"/> managing the response.</param>
         /// <returns>An <see cref="ActionResult"/> the file.</returns>
         public static ActionResult GetStaticFileResult(
             string path,
             string contentType,
-            DateTime dateLastModified,
-            TimeSpan? cacheDuration,
             bool isHeadRequest,
             ControllerBase controller)
         {
@@ -135,10 +129,11 @@ namespace Jellyfin.Api.Helpers
                     state.Dispose();
                 }
 
-                Stream stream = new MemoryStream();
-
-                await new ProgressiveFileCopier(streamHelper, outputPath).WriteToAsync(stream, CancellationToken.None).ConfigureAwait(false);
-                return controller.File(stream, contentType);
+                using (var memoryStream = new MemoryStream())
+                {
+                    await new ProgressiveFileCopier(streamHelper, outputPath).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false);
+                    return controller.File(memoryStream, contentType);
+                }
             }
             finally
             {

+ 82 - 76
Jellyfin.Api/Helpers/StreamingHelpers.cs

@@ -30,22 +30,29 @@ namespace Jellyfin.Api.Helpers
     /// </summary>
     public static class StreamingHelpers
     {
+        /// <summary>
+        /// Gets the current streaming state.
+        /// </summary>
+        /// <param name="streamingRequest">The <see cref="StreamingRequestDto"/>.</param>
+        /// <param name="httpRequest">The <see cref="HttpRequest"/>.</param>
+        /// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
+        /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
+        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+        /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+        /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+        /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
+        /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
+        /// <param name="subtitleEncoder">Instance of the <see cref="ISubtitleEncoder"/> interface.</param>
+        /// <param name="configuration">Instance of the <see cref="IConfiguration"/> interface.</param>
+        /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
+        /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
+        /// <param name="transcodingJobHelper">Initialized <see cref="TranscodingJobHelper"/>.</param>
+        /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
+        /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+        /// <returns>A <see cref="Task"/> containing the current <see cref="StreamState"/>.</returns>
         public static async Task<StreamState> GetStreamingState(
-            Guid itemId,
-            long? startTimeTicks,
-            string? audioCodec,
-            string? subtitleCodec,
-            string? videoCodec,
-            string? @params,
-            bool? @static,
-            string? container,
-            string? liveStreamId,
-            string? playSessionId,
-            string? mediaSourceId,
-            string? deviceId,
-            string? deviceProfileId,
-            int? audioBitRate,
-            HttpRequest request,
+            StreamingRequestDto streamingRequest,
+            HttpRequest httpRequest,
             IAuthorizationContext authorizationContext,
             IMediaSourceManager mediaSourceManager,
             IUserManager userManager,
@@ -59,49 +66,43 @@ namespace Jellyfin.Api.Helpers
             IDeviceManager deviceManager,
             TranscodingJobHelper transcodingJobHelper,
             TranscodingJobType transcodingJobType,
-            bool isVideoRequest,
             CancellationToken cancellationToken)
         {
             EncodingHelper encodingHelper = new EncodingHelper(mediaEncoder, fileSystem, subtitleEncoder, configuration);
             // Parse the DLNA time seek header
-            if (!startTimeTicks.HasValue)
+            if (!streamingRequest.StartTimeTicks.HasValue)
             {
-                var timeSeek = request.Headers["TimeSeekRange.dlna.org"];
+                var timeSeek = httpRequest.Headers["TimeSeekRange.dlna.org"];
 
-                startTimeTicks = ParseTimeSeekHeader(timeSeek);
+                streamingRequest.StartTimeTicks = ParseTimeSeekHeader(timeSeek);
             }
 
-            if (!string.IsNullOrWhiteSpace(@params))
+            if (!string.IsNullOrWhiteSpace(streamingRequest.Params))
             {
-                // What is this?
-                ParseParams(request);
+                ParseParams(streamingRequest);
             }
 
-            var streamOptions = ParseStreamOptions(request.Query);
+            streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query);
 
-            var url = request.Path.Value.Split('.').Last();
+            var url = httpRequest.Path.Value.Split('.').Last();
 
-            if (string.IsNullOrEmpty(audioCodec))
+            if (string.IsNullOrEmpty(streamingRequest.AudioCodec))
             {
-                audioCodec = encodingHelper.InferAudioCodec(url);
+                streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url);
             }
 
-            var enableDlnaHeaders = !string.IsNullOrWhiteSpace(@params) ||
-                                    string.Equals(request.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase);
+            var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) ||
+                                    string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase);
 
             var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper)
             {
-                // TODO request was the StreamingRequest living in MediaBrowser.Api.Playback.Progressive
-                // Request = request,
-                DeviceId = deviceId,
-                PlaySessionId = playSessionId,
-                LiveStreamId = liveStreamId,
+                Request = streamingRequest,
                 RequestedUrl = url,
-                UserAgent = request.Headers[HeaderNames.UserAgent],
+                UserAgent = httpRequest.Headers[HeaderNames.UserAgent],
                 EnableDlnaHeaders = enableDlnaHeaders
             };
 
-            var auth = authorizationContext.GetAuthorizationInfo(request);
+            var auth = authorizationContext.GetAuthorizationInfo(httpRequest);
             if (!auth.UserId.Equals(Guid.Empty))
             {
                 state.User = userManager.GetUserById(auth.UserId);
@@ -116,27 +117,27 @@ namespace Jellyfin.Api.Helpers
             }
             */
 
-            if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.VideoCodec))
+            if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.Request.VideoCodec))
             {
-                state.SupportedVideoCodecs = state.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
-                state.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
+                state.SupportedVideoCodecs = state.Request.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
+                state.Request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
             }
 
-            if (!string.IsNullOrWhiteSpace(audioCodec))
+            if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec))
             {
-                state.SupportedAudioCodecs = audioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
-                state.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToAudioCodec(i))
+                state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
+                state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToAudioCodec(i))
                                            ?? state.SupportedAudioCodecs.FirstOrDefault();
             }
 
-            if (!string.IsNullOrWhiteSpace(subtitleCodec))
+            if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec))
             {
-                state.SupportedSubtitleCodecs = subtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
-                state.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToSubtitleCodec(i))
+                state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
+                state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => mediaEncoder.CanEncodeToSubtitleCodec(i))
                                               ?? state.SupportedSubtitleCodecs.FirstOrDefault();
             }
 
-            var item = libraryManager.GetItemById(itemId);
+            var item = libraryManager.GetItemById(streamingRequest.Id);
 
             state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
 
@@ -150,10 +151,10 @@ namespace Jellyfin.Api.Helpers
             */
 
             MediaSourceInfo? mediaSource = null;
-            if (string.IsNullOrWhiteSpace(liveStreamId))
+            if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId))
             {
-                var currentJob = !string.IsNullOrWhiteSpace(playSessionId)
-                    ? transcodingJobHelper.GetTranscodingJob(playSessionId)
+                var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId)
+                    ? transcodingJobHelper.GetTranscodingJob(streamingRequest.PlaySessionId)
                     : null;
 
                 if (currentJob != null)
@@ -163,13 +164,13 @@ namespace Jellyfin.Api.Helpers
 
                 if (mediaSource == null)
                 {
-                    var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById(itemId), null, false, false, cancellationToken).ConfigureAwait(false);
+                    var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById(streamingRequest.Id), null, false, false, cancellationToken).ConfigureAwait(false);
 
-                    mediaSource = string.IsNullOrEmpty(mediaSourceId)
+                    mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId)
                         ? mediaSources[0]
-                        : mediaSources.Find(i => string.Equals(i.Id, mediaSourceId, StringComparison.InvariantCulture));
+                        : mediaSources.Find(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.InvariantCulture));
 
-                    if (mediaSource == null && Guid.Parse(mediaSourceId) == itemId)
+                    if (mediaSource == null && Guid.Parse(streamingRequest.MediaSourceId) == streamingRequest.Id)
                     {
                         mediaSource = mediaSources[0];
                     }
@@ -177,7 +178,7 @@ namespace Jellyfin.Api.Helpers
             }
             else
             {
-                var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(liveStreamId, cancellationToken).ConfigureAwait(false);
+                var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStreamId, cancellationToken).ConfigureAwait(false);
                 mediaSource = liveStreamInfo.Item1;
                 state.DirectStreamProvider = liveStreamInfo.Item2;
             }
@@ -186,28 +187,28 @@ namespace Jellyfin.Api.Helpers
 
             var containerInternal = Path.GetExtension(state.RequestedUrl);
 
-            if (string.IsNullOrEmpty(container))
+            if (string.IsNullOrEmpty(streamingRequest.Container))
             {
-                containerInternal = container;
+                containerInternal = streamingRequest.Container;
             }
 
             if (string.IsNullOrEmpty(containerInternal))
             {
-                containerInternal = (@static.HasValue && @static.Value) ? StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, state.MediaPath, null, DlnaProfileType.Audio) : GetOutputFileExtension(state);
+                containerInternal = (streamingRequest.Static && streamingRequest.Static) ? StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, state.MediaPath, null, DlnaProfileType.Audio) : GetOutputFileExtension(state);
             }
 
             state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
 
-            state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(audioBitRate, state.AudioStream);
+            state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, state.AudioStream);
 
-            state.OutputAudioCodec = audioCodec;
+            state.OutputAudioCodec = streamingRequest.AudioCodec;
 
             state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec);
 
-            if (isVideoRequest)
+            if (state.VideoRequest != null)
             {
-                state.OutputVideoCodec = state.VideoCodec;
-                state.OutputVideoBitrate = EncodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
+                state.OutputVideoCodec = state.Request.VideoCodec;
+                state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
 
                 encodingHelper.TryStreamCopy(state);
 
@@ -220,21 +221,21 @@ namespace Jellyfin.Api.Helpers
                         state.OutputVideoBitrate.Value,
                         state.VideoStream?.Codec,
                         state.OutputVideoCodec,
-                        videoRequest.MaxWidth,
-                        videoRequest.MaxHeight);
+                        state.VideoRequest.MaxWidth,
+                        state.VideoRequest.MaxHeight);
 
-                    videoRequest.MaxWidth = resolution.MaxWidth;
-                    videoRequest.MaxHeight = resolution.MaxHeight;
+                    state.VideoRequest.MaxWidth = resolution.MaxWidth;
+                    state.VideoRequest.MaxHeight = resolution.MaxHeight;
                 }
             }
 
-            ApplyDeviceProfileSettings(state, dlnaManager, deviceManager, request, deviceProfileId, @static);
+            ApplyDeviceProfileSettings(state, dlnaManager, deviceManager, httpRequest, streamingRequest.DeviceProfileId, streamingRequest.Static);
 
             var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
                 ? GetOutputFileExtension(state)
                 : ('.' + state.OutputContainer);
 
-            state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, deviceId, playSessionId);
+            state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId);
 
             return state;
         }
@@ -319,7 +320,7 @@ namespace Jellyfin.Api.Helpers
         /// </summary>
         /// <param name="value">The time seek header string.</param>
         /// <returns>A nullable <see cref="long"/> representing the seek time in ticks.</returns>
-        public static long? ParseTimeSeekHeader(string value)
+        private static long? ParseTimeSeekHeader(string value)
         {
             if (string.IsNullOrWhiteSpace(value))
             {
@@ -375,7 +376,7 @@ namespace Jellyfin.Api.Helpers
         /// </summary>
         /// <param name="queryString">The query string.</param>
         /// <returns>A <see cref="Dictionary{String,String}"/> containing the stream options.</returns>
-        public static Dictionary<string, string> ParseStreamOptions(IQueryCollection queryString)
+        private static Dictionary<string, string> ParseStreamOptions(IQueryCollection queryString)
         {
             Dictionary<string, string> streamOptions = new Dictionary<string, string>();
             foreach (var param in queryString)
@@ -398,7 +399,7 @@ namespace Jellyfin.Api.Helpers
         /// <param name="state">The current <see cref="StreamState"/>.</param>
         /// <param name="responseHeaders">The <see cref="IHeaderDictionary"/> of the response.</param>
         /// <param name="startTimeTicks">The start time in ticks.</param>
-        public static void AddTimeSeekResponseHeaders(StreamState state, IHeaderDictionary responseHeaders, long? startTimeTicks)
+        private static void AddTimeSeekResponseHeaders(StreamState state, IHeaderDictionary responseHeaders, long? startTimeTicks)
         {
             var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks!.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture);
             var startSeconds = TimeSpan.FromTicks(startTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture);
@@ -420,7 +421,7 @@ namespace Jellyfin.Api.Helpers
         /// </summary>
         /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
-        public static string? GetOutputFileExtension(StreamState state)
+        private static string? GetOutputFileExtension(StreamState state)
         {
             var ext = Path.GetExtension(state.RequestedUrl);
 
@@ -432,7 +433,7 @@ namespace Jellyfin.Api.Helpers
             // Try to infer based on the desired video codec
             if (state.IsVideoRequest)
             {
-                var videoCodec = state.VideoCodec;
+                var videoCodec = state.Request.VideoCodec;
 
                 if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) ||
                     string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase))
@@ -459,7 +460,7 @@ namespace Jellyfin.Api.Helpers
             // Try to infer based on the desired audio codec
             if (!state.IsVideoRequest)
             {
-                var audioCodec = state.AudioCodec;
+                var audioCodec = state.Request.AudioCodec;
 
                 if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
                 {
@@ -570,7 +571,7 @@ namespace Jellyfin.Api.Helpers
                     // state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
                     state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
 
-                    if (!state.IsVideoRequest)
+                    if (state.VideoRequest != null)
                     {
                         state.VideoRequest.CopyTimestamps = transcodingProfile.CopyTimestamps;
                         state.VideoRequest.EnableSubtitlesInManifest = transcodingProfile.EnableSubtitlesInManifest;
@@ -583,11 +584,16 @@ namespace Jellyfin.Api.Helpers
         /// Parses the parameters.
         /// </summary>
         /// <param name="request">The request.</param>
-        private void ParseParams(StreamRequest request)
+        private static void ParseParams(StreamingRequestDto request)
         {
+            if (string.IsNullOrEmpty(request.Params))
+            {
+                return;
+            }
+
             var vals = request.Params.Split(';');
 
-            var videoRequest = request as VideoStreamRequest;
+            var videoRequest = request as VideoRequestDto;
 
             for (var i = 0; i < vals.Length; i++)
             {

+ 7 - 7
Jellyfin.Api/Helpers/TranscodingJobHelper.cs

@@ -443,7 +443,7 @@ namespace Jellyfin.Api.Helpers
                 job.BitRate = bitRate;
             }
 
-            var deviceId = state.DeviceId;
+            var deviceId = state.Request.DeviceId;
 
             if (!string.IsNullOrWhiteSpace(deviceId))
             {
@@ -486,7 +486,7 @@ namespace Jellyfin.Api.Helpers
             HttpRequest request,
             TranscodingJobType transcodingJobType,
             CancellationTokenSource cancellationTokenSource,
-            string workingDirectory = null)
+            string? workingDirectory = null)
         {
             Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
 
@@ -525,12 +525,12 @@ namespace Jellyfin.Api.Helpers
 
             var transcodingJob = this.OnTranscodeBeginning(
                 outputPath,
-                state.PlaySessionId,
+                state.Request.PlaySessionId,
                 state.MediaSource.LiveStreamId,
                 Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture),
                 transcodingJobType,
                 process,
-                state.DeviceId,
+                state.Request.DeviceId,
                 state,
                 cancellationTokenSource);
 
@@ -706,9 +706,9 @@ namespace Jellyfin.Api.Helpers
                 _transcodingLocks.Remove(path);
             }
 
-            if (!string.IsNullOrWhiteSpace(state.DeviceId))
+            if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
             {
-                _sessionManager.ClearTranscodingInfo(state.DeviceId);
+                _sessionManager.ClearTranscodingInfo(state.Request.DeviceId);
             }
         }
 
@@ -747,7 +747,7 @@ namespace Jellyfin.Api.Helpers
                 state.IsoMount = await _isoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false);
             }
 
-            if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.LiveStreamId))
+            if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId))
             {
                 var liveStreamResponse = await _mediaSourceManager.OpenLiveStream(
                     new LiveStreamRequest { OpenToken = state.MediaSource.OpenToken },

+ 18 - 49
Jellyfin.Api/Models/StreamingDtos/StreamState.cs

@@ -19,7 +19,7 @@ namespace Jellyfin.Api.Models.StreamingDtos
         /// <summary>
         /// Initializes a new instance of the <see cref="StreamState" /> class.
         /// </summary>
-        /// <param name="mediaSourceManager">Instance of the <see cref="mediaSourceManager" /> interface.</param>
+        /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager" /> interface.</param>
         /// <param name="transcodingType">The <see cref="TranscodingJobType" />.</param>
         /// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper" /> singleton.</param>
         public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, TranscodingJobHelper transcodingJobHelper)
@@ -34,29 +34,28 @@ namespace Jellyfin.Api.Models.StreamingDtos
         /// </summary>
         public string? RequestedUrl { get; set; }
 
-        // /// <summary>
-        // /// Gets or sets the request.
-        // /// </summary>
-        // public StreamRequest Request
-        // {
-        //     get => (StreamRequest)BaseRequest;
-        //     set
-        //     {
-        //         BaseRequest = value;
-        //
-        //         IsVideoRequest = VideoRequest != null;
-        //     }
-        // }
+        /// <summary>
+        /// Gets or sets the request.
+        /// </summary>
+        public StreamingRequestDto Request
+        {
+            get => (StreamingRequestDto)BaseRequest;
+            set
+            {
+                BaseRequest = value;
+                IsVideoRequest = VideoRequest != null;
+            }
+        }
 
         /// <summary>
         /// Gets or sets the transcoding throttler.
         /// </summary>
         public TranscodingThrottler? TranscodingThrottler { get; set; }
 
-        /*/// <summary>
+        /// <summary>
         /// Gets the video request.
         /// </summary>
-        public VideoStreamRequest VideoRequest => Request as VideoStreamRequest;*/
+        public VideoRequestDto? VideoRequest => Request! as VideoRequestDto;
 
         /// <summary>
         /// Gets or sets the direct stream provicer.
@@ -68,10 +67,10 @@ namespace Jellyfin.Api.Models.StreamingDtos
         /// </summary>
         public string? WaitForPath { get; set; }
 
-        /*/// <summary>
+        /// <summary>
         /// Gets a value indicating whether the request outputs video.
         /// </summary>
-        public bool IsOutputVideo => Request is VideoStreamRequest;*/
+        public bool IsOutputVideo => Request is VideoRequestDto;
 
         /// <summary>
         /// Gets the segment length.
@@ -161,36 +160,6 @@ namespace Jellyfin.Api.Models.StreamingDtos
         /// </summary>
         public TranscodingJobDto? TranscodingJob { get; set; }
 
-        /// <summary>
-        /// Gets or sets the device id.
-        /// </summary>
-        public string? DeviceId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the play session id.
-        /// </summary>
-        public string? PlaySessionId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the live stream id.
-        /// </summary>
-        public string? LiveStreamId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the video coded.
-        /// </summary>
-        public string? VideoCodec { get; set; }
-
-        /// <summary>
-        /// Gets or sets the audio codec.
-        /// </summary>
-        public string? AudioCodec { get; set; }
-
-        /// <summary>
-        /// Gets or sets the subtitle codec.
-        /// </summary>
-        public string? SubtitleCodec { get; set; }
-
         /// <inheritdoc />
         public void Dispose()
         {
@@ -219,7 +188,7 @@ namespace Jellyfin.Api.Models.StreamingDtos
             {
                 // REVIEW: Is this the right place for this?
                 if (MediaSource.RequiresClosing
-                    && string.IsNullOrWhiteSpace(LiveStreamId)
+                    && string.IsNullOrWhiteSpace(Request.LiveStreamId)
                     && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
                 {
                     _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult();

+ 45 - 0
Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs

@@ -0,0 +1,45 @@
+using MediaBrowser.Controller.MediaEncoding;
+
+namespace Jellyfin.Api.Models.StreamingDtos
+{
+    /// <summary>
+    /// The audio streaming request dto.
+    /// </summary>
+    public class StreamingRequestDto : BaseEncodingJobOptions
+    {
+        /// <summary>
+        /// Gets or sets the device profile.
+        /// </summary>
+        public string? DeviceProfileId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the params.
+        /// </summary>
+        public string? Params { get; set; }
+
+        /// <summary>
+        /// Gets or sets the play session id.
+        /// </summary>
+        public string? PlaySessionId { get; set; }
+
+        /// <summary>
+        /// Gets or sets the tag.
+        /// </summary>
+        public string? Tag { get; set; }
+
+        /// <summary>
+        /// Gets or sets the segment container.
+        /// </summary>
+        public string? SegmentContainer { get; set; }
+
+        /// <summary>
+        /// Gets or sets the segment length.
+        /// </summary>
+        public int? SegmentLength { get; set; }
+
+        /// <summary>
+        /// Gets or sets the min segments.
+        /// </summary>
+        public int? MinSegments { get; set; }
+    }
+}

+ 19 - 0
Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs

@@ -0,0 +1,19 @@
+namespace Jellyfin.Api.Models.StreamingDtos
+{
+    /// <summary>
+    /// The video request dto.
+    /// </summary>
+    public class VideoRequestDto : StreamingRequestDto
+    {
+        /// <summary>
+        /// Gets a value indicating whether this instance has fixed resolution.
+        /// </summary>
+        /// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
+        public bool HasFixedResolution => Width.HasValue || Height.HasValue;
+
+        /// <summary>
+        /// Gets or sets a value indicating whether to enable subtitles in the manifest.
+        /// </summary>
+        public bool EnableSubtitlesInManifest { get; set; }
+    }
+}