using System; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.PlaybackDtos; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; namespace Jellyfin.Api.Models.StreamingDtos; /// /// The stream state dto. /// public class StreamState : EncodingJobInfo, IDisposable { private readonly IMediaSourceManager _mediaSourceManager; private readonly TranscodingJobHelper _transcodingJobHelper; private bool _disposed; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// The . /// The singleton. public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType, TranscodingJobHelper transcodingJobHelper) : base(transcodingType) { _mediaSourceManager = mediaSourceManager; _transcodingJobHelper = transcodingJobHelper; } /// /// Gets or sets the requested url. /// public string? RequestedUrl { get; set; } /// /// Gets or sets the request. /// public StreamingRequestDto Request { get => (StreamingRequestDto)BaseRequest; set { BaseRequest = value; IsVideoRequest = VideoRequest is not null; } } /// /// Gets the video request. /// public VideoRequestDto? VideoRequest => Request as VideoRequestDto; /// /// Gets or sets the direct stream provicer. /// /// /// Deprecated. /// public IDirectStreamProvider? DirectStreamProvider { get; set; } /// /// Gets or sets the path to wait for. /// public string? WaitForPath { get; set; } /// /// Gets a value indicating whether the request outputs video. /// public bool IsOutputVideo => Request is VideoRequestDto; /// /// Gets the segment length. /// public int SegmentLength { get { if (Request.SegmentLength.HasValue) { return Request.SegmentLength.Value; } if (EncodingHelper.IsCopyCodec(OutputVideoCodec)) { var userAgent = UserAgent ?? string.Empty; if (userAgent.Contains("AppleTV", StringComparison.OrdinalIgnoreCase) || userAgent.Contains("cfnetwork", StringComparison.OrdinalIgnoreCase) || userAgent.Contains("ipad", StringComparison.OrdinalIgnoreCase) || userAgent.Contains("iphone", StringComparison.OrdinalIgnoreCase) || userAgent.Contains("ipod", StringComparison.OrdinalIgnoreCase)) { return 6; } if (IsSegmentedLiveStream) { return 3; } return 6; } return 3; } } /// /// Gets the minimum number of segments. /// public int MinSegments { get { if (Request.MinSegments.HasValue) { return Request.MinSegments.Value; } return SegmentLength >= 10 ? 2 : 3; } } /// /// Gets or sets the user agent. /// public string? UserAgent { get; set; } /// /// Gets or sets a value indicating whether to estimate the content length. /// public bool EstimateContentLength { get; set; } /// /// Gets or sets the transcode seek info. /// public TranscodeSeekInfo TranscodeSeekInfo { get; set; } /// /// Gets or sets the transcoding job. /// public TranscodingJobDto? TranscodingJob { get; set; } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) { _transcodingJobHelper.ReportTranscodingProgress(TranscodingJob!, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate); } /// /// Disposes the stream state. /// /// Whether the object is currently being disposed. protected virtual void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { // REVIEW: Is this the right place for this? if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId)) { _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult(); } } TranscodingJob = null; _disposed = true; } }