From 2ce97c022e9ceadea4b9b72053626eff7439ff91 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 22 Jul 2020 16:57:06 +0200 Subject: [PATCH] Move AudioService to Jellyfin.Api --- Jellyfin.Api/Controllers/AudioController.cs | 80 ++++++--- .../Helpers/FileStreamResponseHelpers.cs | 17 +- Jellyfin.Api/Helpers/StreamingHelpers.cs | 158 +++++++++--------- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 14 +- .../Models/StreamingDtos/StreamState.cs | 67 ++------ .../StreamingDtos/StreamingRequestDto.cs | 45 +++++ .../Models/StreamingDtos/VideoRequestDto.cs | 19 +++ 7 files changed, 236 insertions(+), 164 deletions(-) create mode 100644 Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs create mode 100644 Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 4d29d38807..81492ed4aa 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/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 /// Optional. The . /// Optional. The streaming options. /// A containing the audio file. - [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 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); } diff --git a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs index 6ba74d5901..9f16b53236 100644 --- a/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs +++ b/Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs @@ -23,13 +23,11 @@ namespace Jellyfin.Api.Helpers /// The current . /// Whether the current request is a HTTP HEAD request so only the headers get returned. /// The managing the response. - /// The . /// A containing the API response. public static async Task GetStaticRemoteStreamResult( StreamState state, bool isHeadRequest, - ControllerBase controller, - CancellationTokenSource cancellationTokenSource) + ControllerBase controller) { HttpClient httpClient = new HttpClient(); @@ -59,16 +57,12 @@ namespace Jellyfin.Api.Helpers /// /// The path to the file. /// The content type of the file. - /// The of the last modification of the file. - /// The cache duration of the file. /// Whether the current request is a HTTP HEAD request so only the headers get returned. /// The managing the response. /// An the file. 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 { diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index ee1f1efce2..71bf053f58 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -30,22 +30,29 @@ namespace Jellyfin.Api.Helpers /// public static class StreamingHelpers { + /// + /// Gets the current streaming state. + /// + /// The . + /// The . + /// 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. + /// Initialized . + /// The . + /// The . + /// A containing the current . public static async Task 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 /// /// The time seek header string. /// A nullable representing the seek time in ticks. - public static long? ParseTimeSeekHeader(string value) + private static long? ParseTimeSeekHeader(string value) { if (string.IsNullOrWhiteSpace(value)) { @@ -375,7 +376,7 @@ namespace Jellyfin.Api.Helpers /// /// The query string. /// A containing the stream options. - public static Dictionary ParseStreamOptions(IQueryCollection queryString) + private static Dictionary ParseStreamOptions(IQueryCollection queryString) { Dictionary streamOptions = new Dictionary(); foreach (var param in queryString) @@ -398,7 +399,7 @@ namespace Jellyfin.Api.Helpers /// The current . /// The of the response. /// The start time in ticks. - 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 /// /// The state. /// System.String. - 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. /// /// The request. - 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++) { diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index 4605c01831..c84135085f 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/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 }, diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs index db7cc6a75c..70a13d745f 100644 --- a/Jellyfin.Api/Models/StreamingDtos/StreamState.cs +++ b/Jellyfin.Api/Models/StreamingDtos/StreamState.cs @@ -19,7 +19,7 @@ namespace Jellyfin.Api.Models.StreamingDtos /// /// Initializes a new instance of the class. /// - /// Instance of the interface. + /// Instance of the interface. /// The . /// The singleton. public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, TranscodingJobHelper transcodingJobHelper) @@ -34,29 +34,28 @@ namespace Jellyfin.Api.Models.StreamingDtos /// public string? RequestedUrl { get; set; } - // /// - // /// Gets or sets the request. - // /// - // public StreamRequest Request - // { - // get => (StreamRequest)BaseRequest; - // set - // { - // BaseRequest = value; - // - // IsVideoRequest = VideoRequest != null; - // } - // } + /// + /// Gets or sets the request. + /// + public StreamingRequestDto Request + { + get => (StreamingRequestDto)BaseRequest; + set + { + BaseRequest = value; + IsVideoRequest = VideoRequest != null; + } + } /// /// Gets or sets the transcoding throttler. /// public TranscodingThrottler? TranscodingThrottler { get; set; } - /*/// + /// /// Gets the video request. /// - public VideoStreamRequest VideoRequest => Request as VideoStreamRequest;*/ + public VideoRequestDto? VideoRequest => Request! as VideoRequestDto; /// /// Gets or sets the direct stream provicer. @@ -68,10 +67,10 @@ namespace Jellyfin.Api.Models.StreamingDtos /// public string? WaitForPath { get; set; } - /*/// + /// /// Gets a value indicating whether the request outputs video. /// - public bool IsOutputVideo => Request is VideoStreamRequest;*/ + public bool IsOutputVideo => Request is VideoRequestDto; /// /// Gets the segment length. @@ -161,36 +160,6 @@ namespace Jellyfin.Api.Models.StreamingDtos /// public TranscodingJobDto? TranscodingJob { get; set; } - /// - /// Gets or sets the device id. - /// - public string? DeviceId { get; set; } - - /// - /// Gets or sets the play session id. - /// - public string? PlaySessionId { get; set; } - - /// - /// Gets or sets the live stream id. - /// - public string? LiveStreamId { get; set; } - - /// - /// Gets or sets the video coded. - /// - public string? VideoCodec { get; set; } - - /// - /// Gets or sets the audio codec. - /// - public string? AudioCodec { get; set; } - - /// - /// Gets or sets the subtitle codec. - /// - public string? SubtitleCodec { get; set; } - /// 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(); diff --git a/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs new file mode 100644 index 0000000000..1791b03706 --- /dev/null +++ b/Jellyfin.Api/Models/StreamingDtos/StreamingRequestDto.cs @@ -0,0 +1,45 @@ +using MediaBrowser.Controller.MediaEncoding; + +namespace Jellyfin.Api.Models.StreamingDtos +{ + /// + /// The audio streaming request dto. + /// + public class StreamingRequestDto : BaseEncodingJobOptions + { + /// + /// Gets or sets the device profile. + /// + public string? DeviceProfileId { get; set; } + + /// + /// Gets or sets the params. + /// + public string? Params { get; set; } + + /// + /// Gets or sets the play session id. + /// + public string? PlaySessionId { get; set; } + + /// + /// Gets or sets the tag. + /// + public string? Tag { get; set; } + + /// + /// Gets or sets the segment container. + /// + public string? SegmentContainer { get; set; } + + /// + /// Gets or sets the segment length. + /// + public int? SegmentLength { get; set; } + + /// + /// Gets or sets the min segments. + /// + public int? MinSegments { get; set; } + } +} diff --git a/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs b/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs new file mode 100644 index 0000000000..cce2a89d49 --- /dev/null +++ b/Jellyfin.Api/Models/StreamingDtos/VideoRequestDto.cs @@ -0,0 +1,19 @@ +namespace Jellyfin.Api.Models.StreamingDtos +{ + /// + /// The video request dto. + /// + public class VideoRequestDto : StreamingRequestDto + { + /// + /// Gets a value indicating whether this instance has fixed resolution. + /// + /// true if this instance has fixed resolution; otherwise, false. + public bool HasFixedResolution => Width.HasValue || Height.HasValue; + + /// + /// Gets or sets a value indicating whether to enable subtitles in the manifest. + /// + public bool EnableSubtitlesInManifest { get; set; } + } +}