Example #1
0
        /// <summary>
        /// Get audio stream.
        /// </summary>
        /// <param name="transcodingJobType">Transcoding job type.</param>
        /// <param name="streamingRequest">Streaming controller.Request dto.</param>
        /// <returns>A <see cref="Task"/> containing the resulting <see cref="ActionResult"/>.</returns>
        public async Task <ActionResult> GetAudioStream(
            TranscodingJobType transcodingJobType,
            StreamingRequestDto streamingRequest)
        {
            if (_httpContextAccessor.HttpContext == null)
            {
                throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext));
            }

            bool isHeadRequest           = _httpContextAccessor.HttpContext.Request.Method == System.Net.WebRequestMethods.Http.Head;
            var  cancellationTokenSource = new CancellationTokenSource();

            using var state = await StreamingHelpers.GetStreamingState(
                      streamingRequest,
                      _httpContextAccessor.HttpContext.Request,
                      _authContext,
                      _mediaSourceManager,
                      _userManager,
                      _libraryManager,
                      _serverConfigurationManager,
                      _mediaEncoder,
                      _fileSystem,
                      _subtitleEncoder,
                      _configuration,
                      _dlnaManager,
                      _deviceManager,
                      _transcodingJobHelper,
                      transcodingJobType,
                      cancellationTokenSource.Token)
                              .ConfigureAwait(false);

            if (streamingRequest.Static && state.DirectStreamProvider != null)
            {
                StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);

                await new ProgressiveFileCopier(state.DirectStreamProvider, null, _transcodingJobHelper, CancellationToken.None)
                {
                    AllowEndOfFile = false
                }.WriteToAsync(_httpContextAccessor.HttpContext.Response.Body, CancellationToken.None)
                .ConfigureAwait(false);

                // TODO (moved from MediaBrowser.Api): Don't hardcode contentType
                return(new FileStreamResult(_httpContextAccessor.HttpContext.Response.Body, MimeTypes.GetMimeType("file.ts") !));
            }

            // Static remote stream
            if (streamingRequest.Static && state.InputProtocol == MediaProtocol.Http)
            {
                StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, true, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);

                var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
                return(await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, _httpContextAccessor.HttpContext).ConfigureAwait(false));
            }

            if (streamingRequest.Static && state.InputProtocol != MediaProtocol.File)
            {
                return(new BadRequestObjectResult($"Input protocol {state.InputProtocol} cannot be streamed statically"));
            }

            var outputPath       = state.OutputFilePath;
            var outputPathExists = System.IO.File.Exists(outputPath);

            var transcodingJob    = _transcodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
            var isTranscodeCached = outputPathExists && transcodingJob != null;

            StreamingHelpers.AddDlnaHeaders(state, _httpContextAccessor.HttpContext.Response.Headers, streamingRequest.Static || isTranscodeCached, streamingRequest.StartTimeTicks, _httpContextAccessor.HttpContext.Request, _dlnaManager);

            // Static stream
            if (streamingRequest.Static)
            {
                var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath);

                if (state.MediaSource.IsInfiniteStream)
                {
                    await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None)
                    {
                        AllowEndOfFile = false
                    }.WriteToAsync(_httpContextAccessor.HttpContext.Response.Body, CancellationToken.None)
                    .ConfigureAwait(false);

                    return(new FileStreamResult(_httpContextAccessor.HttpContext.Response.Body, contentType));
                }

                return(FileStreamResponseHelpers.GetStaticFileResult(
                           state.MediaPath,
                           contentType,
                           isHeadRequest,
                           _httpContextAccessor.HttpContext));
            }

            // Need to start ffmpeg (because media can't be returned directly)
            var encodingOptions            = _serverConfigurationManager.GetEncodingOptions();
            var encodingHelper             = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration);
            var ffmpegCommandLineArguments = encodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, outputPath);

            return(await FileStreamResponseHelpers.GetTranscodedFile(
                       state,
                       isHeadRequest,
                       _httpContextAccessor.HttpContext,
                       _transcodingJobHelper,
                       ffmpegCommandLineArguments,
                       transcodingJobType,
                       cancellationTokenSource).ConfigureAwait(false));
        }
Example #2
0
        private async Task <ActionResult> GetMasterPlaylistInternal(
            StreamingRequestDto streamingRequest,
            bool isHeadRequest,
            bool enableAdaptiveBitrateStreaming,
            TranscodingJobType transcodingJobType,
            CancellationTokenSource cancellationTokenSource)
        {
            if (_httpContextAccessor.HttpContext == null)
            {
                throw new ResourceNotFoundException(nameof(_httpContextAccessor.HttpContext));
            }

            using var state = await StreamingHelpers.GetStreamingState(
                      streamingRequest,
                      _httpContextAccessor.HttpContext.Request,
                      _authContext,
                      _mediaSourceManager,
                      _userManager,
                      _libraryManager,
                      _serverConfigurationManager,
                      _mediaEncoder,
                      _encodingHelper,
                      _dlnaManager,
                      _deviceManager,
                      _transcodingJobHelper,
                      transcodingJobType,
                      cancellationTokenSource.Token)
                              .ConfigureAwait(false);

            _httpContextAccessor.HttpContext.Response.Headers.Add(HeaderNames.Expires, "0");
            if (isHeadRequest)
            {
                return(new FileContentResult(Array.Empty <byte>(), MimeTypes.GetMimeType("playlist.m3u8")));
            }

            var totalBitrate = (state.OutputAudioBitrate ?? 0) + (state.OutputVideoBitrate ?? 0);

            var builder = new StringBuilder();

            builder.AppendLine("#EXTM3U");

            var isLiveStream = state.IsSegmentedLiveStream;

            var queryString = _httpContextAccessor.HttpContext.Request.QueryString.ToString();

            // from universal audio service
            if (!string.IsNullOrWhiteSpace(state.Request.SegmentContainer) &&
                !queryString.Contains("SegmentContainer", StringComparison.OrdinalIgnoreCase))
            {
                queryString += "&SegmentContainer=" + state.Request.SegmentContainer;
            }

            // from universal audio service
            if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) &&
                !queryString.Contains("TranscodeReasons=", StringComparison.OrdinalIgnoreCase))
            {
                queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons;
            }

            // Main stream
            var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8";

            playlistUrl += queryString;

            var subtitleStreams = state.MediaSource
                                  .MediaStreams
                                  .Where(i => i.IsTextSubtitleStream)
                                  .ToList();

            var subtitleGroup = subtitleStreams.Count > 0 && (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Hls || state.VideoRequest !.EnableSubtitlesInManifest)
                ? "subs"
                : null;

            // If we're burning in subtitles then don't add additional subs to the manifest
            if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
            {
                subtitleGroup = null;
            }

            if (!string.IsNullOrWhiteSpace(subtitleGroup))
            {
                AddSubtitles(state, subtitleStreams, builder, _httpContextAccessor.HttpContext.User);
            }

            var basicPlaylist = AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);

            if (state.VideoStream != null && state.VideoRequest != null)
            {
                // Provide SDR HEVC entrance for backward compatibility.
                if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) &&
                    !string.IsNullOrEmpty(state.VideoStream.VideoRange) &&
                    string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) &&
                    string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
                {
                    var requestedVideoProfiles = state.GetRequestedProfiles("hevc");
                    if (requestedVideoProfiles != null && requestedVideoProfiles.Length > 0)
                    {
                        // Force HEVC Main Profile and disable video stream copy.
                        state.OutputVideoCodec = "hevc";
                        var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main");
                        sdrVideoUrl += "&AllowVideoStreamCopy=false";

                        var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec) ?? 0;
                        var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0;
                        var sdrTotalBitrate       = sdrOutputAudioBitrate + sdrOutputVideoBitrate;

                        AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup);

                        // Restore the video codec
                        state.OutputVideoCodec = "copy";
                    }
                }

                // Provide Level 5.0 entrance for backward compatibility.
                // e.g. Apple A10 chips refuse the master playlist containing SDR HEVC Main Level 5.1 video,
                // but in fact it is capable of playing videos up to Level 6.1.
                if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) &&
                    state.VideoStream.Level.HasValue &&
                    state.VideoStream.Level > 150 &&
                    !string.IsNullOrEmpty(state.VideoStream.VideoRange) &&
                    string.Equals(state.VideoStream.VideoRange, "SDR", StringComparison.OrdinalIgnoreCase) &&
                    string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
                {
                    var playlistCodecsField = new StringBuilder();
                    AppendPlaylistCodecsField(playlistCodecsField, state);

                    // Force the video level to 5.0.
                    var originalLevel = state.VideoStream.Level;
                    state.VideoStream.Level = 150;
                    var newPlaylistCodecsField = new StringBuilder();
                    AppendPlaylistCodecsField(newPlaylistCodecsField, state);

                    // Restore the video level.
                    state.VideoStream.Level = originalLevel;
                    var newPlaylist = ReplacePlaylistCodecsField(basicPlaylist, playlistCodecsField, newPlaylistCodecsField);
                    builder.Append(newPlaylist);
                }
            }

            if (EnableAdaptiveBitrateStreaming(state, isLiveStream, enableAdaptiveBitrateStreaming, _httpContextAccessor.HttpContext.GetNormalizedRemoteIp()))
            {
                var requestedVideoBitrate = state.VideoRequest == null ? 0 : state.VideoRequest.VideoBitRate ?? 0;

                // By default, vary by just 200k
                var variation = GetBitrateVariation(totalBitrate);

                var newBitrate = totalBitrate - variation;
                var variantUrl = ReplaceVideoBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
                AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);

                variation *= 2;
                newBitrate = totalBitrate - variation;
                variantUrl = ReplaceVideoBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
                AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
            }

            return(new FileContentResult(Encoding.UTF8.GetBytes(builder.ToString()), MimeTypes.GetMimeType("playlist.m3u8")));
        }
Example #3
0
        private async Task <ActionResult> GetMasterPlaylistInternal(
            StreamingRequestDto streamingRequest,
            bool isHeadRequest,
            bool enableAdaptiveBitrateStreaming,
            TranscodingJobType transcodingJobType,
            CancellationTokenSource cancellationTokenSource)
        {
            using var state = await StreamingHelpers.GetStreamingState(
                      streamingRequest,
                      _httpContextAccessor.HttpContext.Request,
                      _authContext,
                      _mediaSourceManager,
                      _userManager,
                      _libraryManager,
                      _serverConfigurationManager,
                      _mediaEncoder,
                      _fileSystem,
                      _subtitleEncoder,
                      _configuration,
                      _dlnaManager,
                      _deviceManager,
                      _transcodingJobHelper,
                      transcodingJobType,
                      cancellationTokenSource.Token)
                              .ConfigureAwait(false);

            _httpContextAccessor.HttpContext.Response.Headers.Add(HeaderNames.Expires, "0");
            if (isHeadRequest)
            {
                return(new FileContentResult(Array.Empty <byte>(), MimeTypes.GetMimeType("playlist.m3u8")));
            }

            var totalBitrate = (state.OutputAudioBitrate ?? 0) + (state.OutputVideoBitrate ?? 0);

            var builder = new StringBuilder();

            builder.AppendLine("#EXTM3U");

            var isLiveStream = state.IsSegmentedLiveStream;

            var queryString = _httpContextAccessor.HttpContext.Request.QueryString.ToString();

            // from universal audio service
            if (queryString.IndexOf("SegmentContainer", StringComparison.OrdinalIgnoreCase) == -1 && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer))
            {
                queryString += "&SegmentContainer=" + state.Request.SegmentContainer;
            }

            // from universal audio service
            if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) && queryString.IndexOf("TranscodeReasons=", StringComparison.OrdinalIgnoreCase) == -1)
            {
                queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons;
            }

            // Main stream
            var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8";

            playlistUrl += queryString;

            var subtitleStreams = state.MediaSource
                                  .MediaStreams
                                  .Where(i => i.IsTextSubtitleStream)
                                  .ToList();

            var subtitleGroup = subtitleStreams.Count > 0 && (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Hls || state.VideoRequest !.EnableSubtitlesInManifest)
                ? "subs"
                : null;

            // If we're burning in subtitles then don't add additional subs to the manifest
            if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
            {
                subtitleGroup = null;
            }

            if (!string.IsNullOrWhiteSpace(subtitleGroup))
            {
                AddSubtitles(state, subtitleStreams, builder, _httpContextAccessor.HttpContext.User);
            }

            AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);

            if (EnableAdaptiveBitrateStreaming(state, isLiveStream, enableAdaptiveBitrateStreaming, _httpContextAccessor.HttpContext.GetNormalizedRemoteIp()))
            {
                var requestedVideoBitrate = state.VideoRequest == null ? 0 : state.VideoRequest.VideoBitRate ?? 0;

                // By default, vary by just 200k
                var variation = GetBitrateVariation(totalBitrate);

                var newBitrate = totalBitrate - variation;
                var variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
                AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);

                variation *= 2;
                newBitrate = totalBitrate - variation;
                variantUrl = ReplaceBitrate(playlistUrl, requestedVideoBitrate, requestedVideoBitrate - variation);
                AppendPlaylist(builder, state, variantUrl, newBitrate, subtitleGroup);
            }

            return(new FileContentResult(Encoding.UTF8.GetBytes(builder.ToString()), MimeTypes.GetMimeType("playlist.m3u8")));
        }