Пример #1
0
 /// <summary>
 /// Initializes a new instance of the <see cref="DynamicHlsHelper"/> class.
 /// </summary>
 /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
 /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
 /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> 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="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
 /// <param name="transcodingJobHelper">Instance of <see cref="TranscodingJobHelper"/>.</param>
 /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
 /// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsHelper}"/> interface.</param>
 /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
 /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
 public DynamicHlsHelper(
     ILibraryManager libraryManager,
     IUserManager userManager,
     IDlnaManager dlnaManager,
     IAuthorizationContext authContext,
     IMediaSourceManager mediaSourceManager,
     IServerConfigurationManager serverConfigurationManager,
     IMediaEncoder mediaEncoder,
     IDeviceManager deviceManager,
     TranscodingJobHelper transcodingJobHelper,
     INetworkManager networkManager,
     ILogger <DynamicHlsHelper> logger,
     IHttpContextAccessor httpContextAccessor,
     EncodingHelper encodingHelper)
 {
     _libraryManager             = libraryManager;
     _userManager                = userManager;
     _dlnaManager                = dlnaManager;
     _authContext                = authContext;
     _mediaSourceManager         = mediaSourceManager;
     _serverConfigurationManager = serverConfigurationManager;
     _mediaEncoder               = mediaEncoder;
     _deviceManager              = deviceManager;
     _transcodingJobHelper       = transcodingJobHelper;
     _networkManager             = networkManager;
     _logger = logger;
     _httpContextAccessor = httpContextAccessor;
     _encodingHelper      = encodingHelper;
 }
Пример #2
0
 /// <summary>
 /// Initializes a new instance of the <see cref="AudioHelper"/> class.
 /// </summary>
 /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
 /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> 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="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> 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="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
 /// <param name="transcodingJobHelper">Instance of <see cref="TranscodingJobHelper"/>.</param>
 /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
 /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
 public AudioHelper(
     IDlnaManager dlnaManager,
     IAuthorizationContext authContext,
     IUserManager userManager,
     ILibraryManager libraryManager,
     IMediaSourceManager mediaSourceManager,
     IServerConfigurationManager serverConfigurationManager,
     IMediaEncoder mediaEncoder,
     IFileSystem fileSystem,
     ISubtitleEncoder subtitleEncoder,
     IConfiguration configuration,
     IDeviceManager deviceManager,
     TranscodingJobHelper transcodingJobHelper,
     IHttpClientFactory httpClientFactory,
     IHttpContextAccessor httpContextAccessor)
 {
     _dlnaManager                = dlnaManager;
     _authContext                = authContext;
     _userManager                = userManager;
     _libraryManager             = libraryManager;
     _mediaSourceManager         = mediaSourceManager;
     _serverConfigurationManager = serverConfigurationManager;
     _mediaEncoder               = mediaEncoder;
     _fileSystem           = fileSystem;
     _subtitleEncoder      = subtitleEncoder;
     _configuration        = configuration;
     _deviceManager        = deviceManager;
     _transcodingJobHelper = transcodingJobHelper;
     _httpClientFactory    = httpClientFactory;
     _httpContextAccessor  = httpContextAccessor;
 }
Пример #3
0
 /// <summary>
 /// Initializes a new instance of the <see cref="DynamicHlsHelper"/> class.
 /// </summary>
 /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
 /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
 /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> 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="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
 /// <param name="transcodingJobHelper">Instance of <see cref="TranscodingJobHelper"/>.</param>
 /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
 /// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsHelper}"/> interface.</param>
 /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
 public DynamicHlsHelper(
     ILibraryManager libraryManager,
     IUserManager userManager,
     IDlnaManager dlnaManager,
     IAuthorizationContext authContext,
     IMediaSourceManager mediaSourceManager,
     IServerConfigurationManager serverConfigurationManager,
     IMediaEncoder mediaEncoder,
     IFileSystem fileSystem,
     ISubtitleEncoder subtitleEncoder,
     IConfiguration configuration,
     IDeviceManager deviceManager,
     TranscodingJobHelper transcodingJobHelper,
     INetworkManager networkManager,
     ILogger <DynamicHlsHelper> logger,
     IHttpContextAccessor httpContextAccessor)
 {
     _libraryManager             = libraryManager;
     _userManager                = userManager;
     _dlnaManager                = dlnaManager;
     _authContext                = authContext;
     _mediaSourceManager         = mediaSourceManager;
     _serverConfigurationManager = serverConfigurationManager;
     _mediaEncoder               = mediaEncoder;
     _fileSystem           = fileSystem;
     _subtitleEncoder      = subtitleEncoder;
     _configuration        = configuration;
     _deviceManager        = deviceManager;
     _transcodingJobHelper = transcodingJobHelper;
     _networkManager       = networkManager;
     _logger = logger;
     _httpContextAccessor = httpContextAccessor;
 }
Пример #4
0
 /// <summary>
 /// Initializes a new instance of the <see cref="AudioHelper"/> class.
 /// </summary>
 /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
 /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> 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="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> 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="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
 /// <param name="transcodingJobHelper">Instance of <see cref="TranscodingJobHelper"/>.</param>
 /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
 /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
 /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
 public AudioHelper(
     IDlnaManager dlnaManager,
     IAuthorizationContext authContext,
     IUserManager userManager,
     ILibraryManager libraryManager,
     IMediaSourceManager mediaSourceManager,
     IServerConfigurationManager serverConfigurationManager,
     IMediaEncoder mediaEncoder,
     IDeviceManager deviceManager,
     TranscodingJobHelper transcodingJobHelper,
     IHttpClientFactory httpClientFactory,
     IHttpContextAccessor httpContextAccessor,
     EncodingHelper encodingHelper)
 {
     _dlnaManager                = dlnaManager;
     _authContext                = authContext;
     _userManager                = userManager;
     _libraryManager             = libraryManager;
     _mediaSourceManager         = mediaSourceManager;
     _serverConfigurationManager = serverConfigurationManager;
     _mediaEncoder               = mediaEncoder;
     _deviceManager              = deviceManager;
     _transcodingJobHelper       = transcodingJobHelper;
     _httpClientFactory          = httpClientFactory;
     _httpContextAccessor        = httpContextAccessor;
     _encodingHelper             = encodingHelper;
 }
Пример #5
0
 /// <summary>
 /// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
 /// </summary>
 /// <param name="directStreamProvider">Instance of the <see cref="IDirectStreamProvider"/> interface.</param>
 /// <param name="job">The transcoding job.</param>
 /// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/>.</param>
 /// <param name="cancellationToken">The cancellation token.</param>
 public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, TranscodingJobDto?job, TranscodingJobHelper transcodingJobHelper, CancellationToken cancellationToken)
 {
     _directStreamProvider = directStreamProvider;
     _job = job;
     _cancellationToken    = cancellationToken;
     _transcodingJobHelper = transcodingJobHelper;
 }
Пример #6
0
 /// <summary>
 /// Initializes a new instance of the <see cref="ProgressiveFileCopier"/> class.
 /// </summary>
 /// <param name="path">The path to copy from.</param>
 /// <param name="job">The transcoding job.</param>
 /// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/>.</param>
 /// <param name="cancellationToken">The cancellation token.</param>
 public ProgressiveFileCopier(string path, TranscodingJobDto?job, TranscodingJobHelper transcodingJobHelper, CancellationToken cancellationToken)
 {
     _path = path;
     _job  = job;
     _cancellationToken    = cancellationToken;
     _transcodingJobHelper = transcodingJobHelper;
 }
Пример #7
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ProgressiveFileStream"/> class.
        /// </summary>
        /// <param name="filePath">The path to the transcoded file.</param>
        /// <param name="job">The transcoding job information.</param>
        /// <param name="transcodingJobHelper">The transcoding job helper.</param>
        /// <param name="timeoutMs">The timeout duration in milliseconds.</param>
        public ProgressiveFileStream(string filePath, TranscodingJobDto?job, TranscodingJobHelper transcodingJobHelper, int timeoutMs = 30000)
        {
            _job = job;
            _transcodingJobHelper = transcodingJobHelper;
            _timeoutMs            = timeoutMs;

            _stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous | FileOptions.SequentialScan);
        }
Пример #8
0
        /// <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="encodingHelper">Instance of <see cref="EncodingHelper"/>.</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(
            StreamingRequestDto streamingRequest,
            HttpRequest httpRequest,
            IAuthorizationContext authorizationContext,
            IMediaSourceManager mediaSourceManager,
            IUserManager userManager,
            ILibraryManager libraryManager,
            IServerConfigurationManager serverConfigurationManager,
            IMediaEncoder mediaEncoder,
            EncodingHelper encodingHelper,
            IDlnaManager dlnaManager,
            IDeviceManager deviceManager,
            TranscodingJobHelper transcodingJobHelper,
            TranscodingJobType transcodingJobType,
            CancellationToken cancellationToken)
        {
            // Parse the DLNA time seek header
            if (!streamingRequest.StartTimeTicks.HasValue)
            {
                var timeSeek = httpRequest.Headers["TimeSeekRange.dlna.org"];

                streamingRequest.StartTimeTicks = ParseTimeSeekHeader(timeSeek.ToString());
            }

            if (!string.IsNullOrWhiteSpace(streamingRequest.Params))
            {
                ParseParams(streamingRequest);
            }

            streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query);
            if (httpRequest.Path.Value == null)
            {
                throw new ResourceNotFoundException(nameof(httpRequest.Path));
            }

            var url = httpRequest.Path.Value.AsSpan().RightPart('.').ToString();

            if (string.IsNullOrEmpty(streamingRequest.AudioCodec))
            {
                streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url);
            }

            var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) ||
                                    streamingRequest.StreamOptions.ContainsKey("dlnaheaders") ||
                                    string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase);

            var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper)
            {
                Request           = streamingRequest,
                RequestedUrl      = url,
                UserAgent         = httpRequest.Headers[HeaderNames.UserAgent],
                EnableDlnaHeaders = enableDlnaHeaders
            };

            var auth = await authorizationContext.GetAuthorizationInfo(httpRequest).ConfigureAwait(false);

            if (!auth.UserId.Equals(default))
Пример #9
0
        /// <summary>
        /// Returns a transcoded file from the server.
        /// </summary>
        /// <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="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
        /// <param name="ffmpegCommandLineArguments">The command line arguments to start ffmpeg.</param>
        /// <param name="request">The <see cref="HttpRequest"/> starting the transcoding.</param>
        /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
        /// <param name="cancellationTokenSource">The <see cref="CancellationTokenSource"/>.</param>
        /// <returns>A <see cref="Task{ActionResult}"/> containing the transcoded file.</returns>
        public static async Task <ActionResult> GetTranscodedFile(
            StreamState state,
            bool isHeadRequest,
            ControllerBase controller,
            TranscodingJobHelper transcodingJobHelper,
            string ffmpegCommandLineArguments,
            HttpRequest request,
            TranscodingJobType transcodingJobType,
            CancellationTokenSource cancellationTokenSource)
        {
            // Use the command line args with a dummy playlist path
            var outputPath = state.OutputFilePath;

            controller.Response.Headers[HeaderNames.AcceptRanges] = "none";

            var contentType = state.GetMimeType(outputPath);

            // Headers only
            if (isHeadRequest)
            {
                return(controller.File(Array.Empty <byte>(), contentType));
            }

            var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath);
            await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);

            try
            {
                TranscodingJobDto?job;
                if (!File.Exists(outputPath))
                {
                    job = await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false);
                }
                else
                {
                    job = transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
                    state.Dispose();
                }

                var memoryStream = new MemoryStream();
                await new ProgressiveFileCopier(outputPath, job, transcodingJobHelper, CancellationToken.None).WriteToAsync(memoryStream, CancellationToken.None).ConfigureAwait(false);
                memoryStream.Position = 0;
                return(controller.File(memoryStream, contentType));
            }
            finally
            {
                transcodingLock.Release();
            }
        }
Пример #10
0
        /// <summary>
        /// Returns a transcoded file from the server.
        /// </summary>
        /// <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="httpContext">The current http context.</param>
        /// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
        /// <param name="ffmpegCommandLineArguments">The command line arguments to start ffmpeg.</param>
        /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
        /// <param name="cancellationTokenSource">The <see cref="CancellationTokenSource"/>.</param>
        /// <returns>A <see cref="Task{ActionResult}"/> containing the transcoded file.</returns>
        public static async Task <ActionResult> GetTranscodedFile(
            StreamState state,
            bool isHeadRequest,
            HttpContext httpContext,
            TranscodingJobHelper transcodingJobHelper,
            string ffmpegCommandLineArguments,
            TranscodingJobType transcodingJobType,
            CancellationTokenSource cancellationTokenSource)
        {
            // Use the command line args with a dummy playlist path
            var outputPath = state.OutputFilePath;

            httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none";

            var contentType = state.GetMimeType(outputPath);

            // Headers only
            if (isHeadRequest)
            {
                httpContext.Response.Headers[HeaderNames.ContentType] = contentType;
                return(new OkResult());
            }

            var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath);
            await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);

            try
            {
                TranscodingJobDto?job;
                if (!File.Exists(outputPath))
                {
                    job = await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, httpContext.Request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false);
                }
                else
                {
                    job = transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
                    state.Dispose();
                }

                var stream = new ProgressiveFileStream(outputPath, job, transcodingJobHelper);
                return(new FileStreamResult(stream, contentType));
            }
            finally
            {
                transcodingLock.Release();
            }
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="ProgressiveFileStream"/> class.
        /// </summary>
        /// <param name="filePath">The path to the transcoded file.</param>
        /// <param name="job">The transcoding job information.</param>
        /// <param name="transcodingJobHelper">The transcoding job helper.</param>
        /// <param name="timeoutMs">The timeout duration in milliseconds.</param>
        public ProgressiveFileStream(string filePath, TranscodingJobDto?job, TranscodingJobHelper transcodingJobHelper, int timeoutMs = 30000)
        {
            _job = job;
            _transcodingJobHelper = transcodingJobHelper;
            _timeoutMs            = timeoutMs;

            var fileOptions = FileOptions.SequentialScan;

            _allowAsyncFileRead = false;

            // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
            if (AsyncFile.UseAsyncIO)
            {
                fileOptions        |= FileOptions.Asynchronous;
                _allowAsyncFileRead = true;
            }

            _stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions);
        }
Пример #12
0
        /// <summary>
        /// Initializes a new instance of the <see cref="ProgressiveFileStream"/> class.
        /// </summary>
        /// <param name="filePath">The path to the transcoded file.</param>
        /// <param name="job">The transcoding job information.</param>
        /// <param name="transcodingJobHelper">The transcoding job helper.</param>
        public ProgressiveFileStream(string filePath, TranscodingJobDto?job, TranscodingJobHelper transcodingJobHelper)
        {
            _job = job;
            _transcodingJobHelper = transcodingJobHelper;
            _bytesWritten         = 0;

            var fileOptions = FileOptions.SequentialScan;

            _allowAsyncFileRead = false;

            // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                fileOptions        |= FileOptions.Asynchronous;
                _allowAsyncFileRead = true;
            }

            _fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, fileOptions);
        }
Пример #13
0
        /// <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(
            StreamingRequestDto streamingRequest,
            HttpRequest httpRequest,
            IAuthorizationContext authorizationContext,
            IMediaSourceManager mediaSourceManager,
            IUserManager userManager,
            ILibraryManager libraryManager,
            IServerConfigurationManager serverConfigurationManager,
            IMediaEncoder mediaEncoder,
            IFileSystem fileSystem,
            ISubtitleEncoder subtitleEncoder,
            IConfiguration configuration,
            IDlnaManager dlnaManager,
            IDeviceManager deviceManager,
            TranscodingJobHelper transcodingJobHelper,
            TranscodingJobType transcodingJobType,
            CancellationToken cancellationToken)
        {
            EncodingHelper encodingHelper = new EncodingHelper(mediaEncoder, fileSystem, subtitleEncoder, configuration);

            // Parse the DLNA time seek header
            if (!streamingRequest.StartTimeTicks.HasValue)
            {
                var timeSeek = httpRequest.Headers["TimeSeekRange.dlna.org"];

                streamingRequest.StartTimeTicks = ParseTimeSeekHeader(timeSeek.ToString());
            }

            if (!string.IsNullOrWhiteSpace(streamingRequest.Params))
            {
                ParseParams(streamingRequest);
            }

            streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query);
            if (httpRequest.Path.Value == null)
            {
                throw new ResourceNotFoundException(nameof(httpRequest.Path));
            }

            var url = httpRequest.Path.Value.Split('.')[^ 1];
Пример #14
0
        /// <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="encodingHelper">Instance of <see cref="EncodingHelper"/>.</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(
            StreamingRequestDto streamingRequest,
            HttpRequest httpRequest,
            IAuthorizationContext authorizationContext,
            IMediaSourceManager mediaSourceManager,
            IUserManager userManager,
            ILibraryManager libraryManager,
            IServerConfigurationManager serverConfigurationManager,
            IMediaEncoder mediaEncoder,
            EncodingHelper encodingHelper,
            IDlnaManager dlnaManager,
            IDeviceManager deviceManager,
            TranscodingJobHelper transcodingJobHelper,
            TranscodingJobType transcodingJobType,
            CancellationToken cancellationToken)
        {
            // Parse the DLNA time seek header
            if (!streamingRequest.StartTimeTicks.HasValue)
            {
                var timeSeek = httpRequest.Headers["TimeSeekRange.dlna.org"];

                streamingRequest.StartTimeTicks = ParseTimeSeekHeader(timeSeek.ToString());
            }

            if (!string.IsNullOrWhiteSpace(streamingRequest.Params))
            {
                ParseParams(streamingRequest);
            }

            streamingRequest.StreamOptions = ParseStreamOptions(httpRequest.Query);
            if (httpRequest.Path.Value == null)
            {
                throw new ResourceNotFoundException(nameof(httpRequest.Path));
            }

            var url = httpRequest.Path.Value.AsSpan().RightPart('.').ToString();

            if (string.IsNullOrEmpty(streamingRequest.AudioCodec))
            {
                streamingRequest.AudioCodec = encodingHelper.InferAudioCodec(url);
            }

            var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) ||
                                    string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase);

            var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper)
            {
                Request           = streamingRequest,
                RequestedUrl      = url,
                UserAgent         = httpRequest.Headers[HeaderNames.UserAgent],
                EnableDlnaHeaders = enableDlnaHeaders
            };

            var auth = await authorizationContext.GetAuthorizationInfo(httpRequest).ConfigureAwait(false);

            if (!auth.UserId.Equals(Guid.Empty))
            {
                state.User = userManager.GetUserById(auth.UserId);
            }

            if (state.IsVideoRequest && !string.IsNullOrWhiteSpace(state.Request.VideoCodec))
            {
                state.SupportedVideoCodecs = state.Request.VideoCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
                state.Request.VideoCodec   = state.SupportedVideoCodecs.FirstOrDefault();
            }

            if (!string.IsNullOrWhiteSpace(streamingRequest.AudioCodec))
            {
                state.SupportedAudioCodecs = streamingRequest.AudioCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
                state.Request.AudioCodec   = state.SupportedAudioCodecs.FirstOrDefault(mediaEncoder.CanEncodeToAudioCodec)
                                             ?? state.SupportedAudioCodecs.FirstOrDefault();
            }

            if (!string.IsNullOrWhiteSpace(streamingRequest.SubtitleCodec))
            {
                state.SupportedSubtitleCodecs = streamingRequest.SubtitleCodec.Split(',', StringSplitOptions.RemoveEmptyEntries);
                state.Request.SubtitleCodec   = state.SupportedSubtitleCodecs.FirstOrDefault(mediaEncoder.CanEncodeToSubtitleCodec)
                                                ?? state.SupportedSubtitleCodecs.FirstOrDefault();
            }

            var item = libraryManager.GetItemById(streamingRequest.Id);

            state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);

            MediaSourceInfo?mediaSource = null;

            if (string.IsNullOrWhiteSpace(streamingRequest.LiveStreamId))
            {
                var currentJob = !string.IsNullOrWhiteSpace(streamingRequest.PlaySessionId)
                    ? transcodingJobHelper.GetTranscodingJob(streamingRequest.PlaySessionId)
                    : null;

                if (currentJob != null)
                {
                    mediaSource = currentJob.MediaSource;
                }

                if (mediaSource == null)
                {
                    var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById(streamingRequest.Id), null, false, false, cancellationToken).ConfigureAwait(false);

                    mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId)
                        ? mediaSources[0]
                        : mediaSources.Find(i => string.Equals(i.Id, streamingRequest.MediaSourceId, StringComparison.Ordinal));

                    if (mediaSource == null && Guid.Parse(streamingRequest.MediaSourceId) == streamingRequest.Id)
                    {
                        mediaSource = mediaSources[0];
                    }
                }
            }
            else
            {
                var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStreamId, cancellationToken).ConfigureAwait(false);

                mediaSource = liveStreamInfo.Item1;
                state.DirectStreamProvider = liveStreamInfo.Item2;
            }

            var encodingOptions = serverConfigurationManager.GetEncodingOptions();

            encodingHelper.AttachMediaSourceInfo(state, encodingOptions, mediaSource, url);

            string?containerInternal = Path.GetExtension(state.RequestedUrl);

            if (!string.IsNullOrEmpty(streamingRequest.Container))
            {
                containerInternal = streamingRequest.Container;
            }

            if (string.IsNullOrEmpty(containerInternal))
            {
                containerInternal = streamingRequest.Static ?
                                    StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.Audio)
                    : GetOutputFileExtension(state);
            }

            state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');

            state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream);

            state.OutputAudioCodec = streamingRequest.AudioCodec;

            state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec);

            if (state.VideoRequest != null)
            {
                state.OutputVideoCodec   = state.Request.VideoCodec;
                state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);

                encodingHelper.TryStreamCopy(state);

                if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && state.OutputVideoBitrate.HasValue)
                {
                    var isVideoResolutionNotRequested = !state.VideoRequest.Width.HasValue &&
                                                        !state.VideoRequest.Height.HasValue &&
                                                        !state.VideoRequest.MaxWidth.HasValue &&
                                                        !state.VideoRequest.MaxHeight.HasValue;

                    if (isVideoResolutionNotRequested &&
                        state.VideoStream != null &&
                        state.VideoRequest.VideoBitRate.HasValue &&
                        state.VideoStream.BitRate.HasValue &&
                        state.VideoRequest.VideoBitRate.Value >= state.VideoStream.BitRate.Value)
                    {
                        // Don't downscale the resolution if the width/height/MaxWidth/MaxHeight is not requested,
                        // and the requested video bitrate is higher than source video bitrate.
                        if (state.VideoStream.Width.HasValue || state.VideoStream.Height.HasValue)
                        {
                            state.VideoRequest.MaxWidth  = state.VideoStream?.Width;
                            state.VideoRequest.MaxHeight = state.VideoStream?.Height;
                        }
                    }
                    else
                    {
                        var resolution = ResolutionNormalizer.Normalize(
                            state.VideoStream?.BitRate,
                            state.OutputVideoBitrate.Value,
                            state.VideoRequest.MaxWidth,
                            state.VideoRequest.MaxHeight);

                        state.VideoRequest.MaxWidth  = resolution.MaxWidth;
                        state.VideoRequest.MaxHeight = resolution.MaxHeight;
                    }
                }
            }

            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, streamingRequest.DeviceId, streamingRequest.PlaySessionId);

            return(state);
        }