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); }
/// <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); }
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); }
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)); } }
/// <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); }