示例#1
0
        public async Task <(MediaAttachment Attachment, Stream Stream)> GetAttachment(BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken)
        {
            if (item == null)
            {
                throw new ArgumentNullException(nameof(item));
            }

            if (string.IsNullOrWhiteSpace(mediaSourceId))
            {
                throw new ArgumentNullException(nameof(mediaSourceId));
            }

            var mediaSources = await _mediaSourceManager.GetPlaybackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false);

            var mediaSource = mediaSources
                              .FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));

            if (mediaSource == null)
            {
                throw new ResourceNotFoundException($"MediaSource {mediaSourceId} not found");
            }

            var mediaAttachment = mediaSource.MediaAttachments
                                  .FirstOrDefault(i => i.Index == attachmentStreamIndex);

            if (mediaAttachment == null)
            {
                throw new ResourceNotFoundException($"MediaSource {mediaSourceId} has no attachment with stream index {attachmentStreamIndex}");
            }

            var attachmentStream = await GetAttachmentStream(mediaSource, mediaAttachment, cancellationToken)
                                   .ConfigureAwait(false);

            return(mediaAttachment, attachmentStream);
        }
示例#2
0
        /// <summary>
        /// Get playback info.
        /// </summary>
        /// <param name="id">Item id.</param>
        /// <param name="userId">User Id.</param>
        /// <param name="mediaSourceId">Media source id.</param>
        /// <param name="liveStreamId">Live stream id.</param>
        /// <returns>A <see cref="Task"/> containing the <see cref="PlaybackInfoResponse"/>.</returns>
        public async Task <PlaybackInfoResponse> GetPlaybackInfo(
            Guid id,
            Guid?userId,
            string?mediaSourceId = null,
            string?liveStreamId  = null)
        {
            var user = userId.HasValue && !userId.Equals(Guid.Empty)
                ? _userManager.GetUserById(userId.Value)
                : null;
            var item   = _libraryManager.GetItemById(id);
            var result = new PlaybackInfoResponse();

            MediaSourceInfo[] mediaSources;
            if (string.IsNullOrWhiteSpace(liveStreamId))
            {
                // TODO (moved from MediaBrowser.Api) handle supportedLiveMediaTypes?
                var mediaSourcesList = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, true, CancellationToken.None).ConfigureAwait(false);

                if (string.IsNullOrWhiteSpace(mediaSourceId))
                {
                    mediaSources = mediaSourcesList.ToArray();
                }
                else
                {
                    mediaSources = mediaSourcesList
                                   .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
                                   .ToArray();
                }
            }
            else
            {
                var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);

                mediaSources = new[] { mediaSource };
            }

            if (mediaSources.Length == 0)
            {
                result.MediaSources = Array.Empty <MediaSourceInfo>();

                result.ErrorCode ??= PlaybackErrorCode.NoCompatibleStream;
            }
            else
            {
                // Since we're going to be setting properties on MediaSourceInfos that come out of _mediaSourceManager, we should clone it
                // Should we move this directly into MediaSourceManager?
                var mediaSourcesClone = JsonSerializer.Deserialize <MediaSourceInfo[]>(JsonSerializer.SerializeToUtf8Bytes(mediaSources));
                if (mediaSourcesClone != null)
                {
                    result.MediaSources = mediaSourcesClone;
                }

                result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
            }

            return(result);
        }
示例#3
0
        private async Task <PlaybackInfoResponse> GetPlaybackInfo(Guid id, Guid userId, string[] supportedLiveMediaTypes, string mediaSourceId = null, string liveStreamId = null)
        {
            var user   = _userManager.GetUserById(userId);
            var item   = _libraryManager.GetItemById(id);
            var result = new PlaybackInfoResponse();

            if (string.IsNullOrWhiteSpace(liveStreamId))
            {
                IEnumerable <MediaSourceInfo> mediaSources;
                try
                {
                    // TODO handle supportedLiveMediaTypes ?
                    mediaSources = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, false, CancellationToken.None).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    mediaSources = new List <MediaSourceInfo>();
                    Logger.LogError(ex, "Could not find media sources for item id {id}", id);
                    // TODO PlaybackException ??
                    //result.ErrorCode = ex.ErrorCode;
                }

                result.MediaSources = mediaSources.ToArray();

                if (!string.IsNullOrWhiteSpace(mediaSourceId))
                {
                    result.MediaSources = result.MediaSources
                                          .Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
                                          .ToArray();
                }
            }
            else
            {
                var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);

                result.MediaSources = new MediaSourceInfo[] { mediaSource };
            }

            if (result.MediaSources.Count == 0)
            {
                if (!result.ErrorCode.HasValue)
                {
                    result.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
                }
            }
            else
            {
                result.MediaSources = Clone(result.MediaSources);

                result.PlaySessionId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
            }

            return(result);
        }
示例#4
0
        async Task <Stream> ISubtitleEncoder.GetSubtitles(BaseItem item, string mediaSourceId, int subtitleStreamIndex, string outputFormat, long startTimeTicks, long endTimeTicks, bool preserveOriginalTimestamps, CancellationToken cancellationToken)
        {
            if (item == null)
            {
                throw new ArgumentNullException(nameof(item));
            }

            if (string.IsNullOrWhiteSpace(mediaSourceId))
            {
                throw new ArgumentNullException(nameof(mediaSourceId));
            }

            var mediaSources = await _mediaSourceManager.GetPlaybackMediaSources(item, null, true, false, cancellationToken).ConfigureAwait(false);

            var mediaSource = mediaSources
                              .First(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));

            var subtitleStream = mediaSource.MediaStreams
                                 .First(i => i.Type == MediaStreamType.Subtitle && i.Index == subtitleStreamIndex);

            var subtitle = await GetSubtitleStream(mediaSource, subtitleStream, cancellationToken)
                           .ConfigureAwait(false);

            var inputFormat = subtitle.format;
            var writer      = TryGetWriter(outputFormat);

            // Return the original if we don't have any way of converting it
            if (writer == null)
            {
                return(subtitle.stream);
            }

            // Return the original if the same format is being requested
            // Character encoding was already handled in GetSubtitleStream
            if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase))
            {
                return(subtitle.stream);
            }

            using (var stream = subtitle.stream)
            {
                return(ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken));
            }
        }
示例#5
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);
        }