예제 #1
0
        public async Task StartPlay(
            string mrl,
            int videoStreamIndex,
            int audioStreamIndex,
            int subtitleStreamIndex,
            int quality,
            FFProbeFileInfo fileInfo,
            double seconds = 0)
        {
            if (fileInfo == null)
            {
                _logger.LogWarning($"{nameof(StartPlay)}: No file info was provided for mrl = {mrl}");
                throw new ArgumentNullException(nameof(fileInfo), "A file info must be provided");
            }
            _ffmpegService.KillTranscodeProcess();
            bool isLocal     = _fileService.IsLocalFile(mrl);
            bool isUrlFile   = _fileService.IsUrlFile(mrl);
            bool isVideoFile = _fileService.IsVideoFile(mrl);
            bool isMusicFile = _fileService.IsMusicFile(mrl);

            if (!isLocal && !isUrlFile)
            {
                var msg = "Invalid = {mrl}. Its not a local file and its not a url file";
                _logger.LogWarning($"{nameof(StartPlay)}: {msg}");
                throw new NotSupportedException(msg);
            }

            if (AvailableDevices.Count == 0)
            {
                _logger.LogWarning($"{nameof(StartPlay)}: No renders were found, file = {mrl}");
                throw new NoDevicesException();
            }

            if (_connecting)
            {
                _logger.LogWarning($"{nameof(StartPlay)}: We are in the middle of connecting to a device, can't play file = {mrl} right now");
                throw new ConnectingException();
            }

            if (!_renderWasSet && AvailableDevices.Count > 0)
            {
                await SetCastRenderer(AvailableDevices.First()).ConfigureAwait(false);
            }
            // create new media
            bool videoNeedsTranscode = isVideoFile && _ffmpegService.VideoNeedsTranscode(videoStreamIndex, _appSettings.ForceVideoTranscode, _appSettings.VideoScale, fileInfo);
            bool audioNeedsTranscode = _ffmpegService.AudioNeedsTranscode(audioStreamIndex, _appSettings.ForceAudioTranscode, fileInfo, isMusicFile);
            var  hwAccelToUse        = isVideoFile ? _ffmpegService.GetHwAccelToUse(videoStreamIndex, fileInfo, _appSettings.EnableHardwareAcceleration) : HwAccelDeviceType.None;

            _currentFilePath = mrl;
            string title = isLocal ? Path.GetFileName(mrl) : mrl;
            string url   = isLocal
                ? _appWebServer.GetMediaUrl(
                mrl,
                videoStreamIndex,
                audioStreamIndex,
                seconds,
                videoNeedsTranscode,
                audioNeedsTranscode,
                hwAccelToUse,
                _appSettings.VideoScale,
                fileInfo.Videos.Find(f => f.Index == videoStreamIndex)?.WidthAndHeightText)
                : mrl;

            var metadata = isVideoFile ? new MovieMetadata
            {
                Title = title,
            } : new GenericMediaMetadata
            {
                Title = title,
            };
            var media = new MediaInformation
            {
                ContentId = url,
                Metadata  = metadata,
                //You have to set the contenttype before hand, with that, the album art of a music file will be shown
                ContentType = _ffmpegService.GetOutputTranscodeMimeType(mrl)
            };

            var  activeTrackIds    = new List <int>();
            bool useSubTitleStream = subtitleStreamIndex >= 0;

            if (useSubTitleStream || !string.IsNullOrEmpty(GetSubTitles?.Invoke()))
            {
                _logger.LogInformation($"{nameof(StartPlay)}: Subtitles were specified, generating a compatible one...");
                string subtitleLocation = useSubTitleStream ? mrl : GetSubTitles.Invoke();
                string subTitleFilePath = _fileService.GetSubTitleFilePath();
                await _ffmpegService.GenerateSubTitles(
                    subtitleLocation,
                    subTitleFilePath,
                    seconds,
                    useSubTitleStream?subtitleStreamIndex : 0,
                    _appSettings.SubtitleDelayInSeconds,
                    default);

                _subtitle.TrackContentId = _appWebServer.GetSubTitlePath(subTitleFilePath);
                _logger.LogInformation($"{nameof(StartPlay)}: Subtitles were generated");
                media.Tracks.Add(_subtitle);
                media.TextTrackStyle = GetSubtitleStyle();
                activeTrackIds.Add(SubTitleDefaultTrackId);
            }

            string firstThumbnail = await GetFirstThumbnail().ConfigureAwait(false);

            string imgUrl = string.Empty;

            if (isLocal)
            {
                _logger.LogInformation($"{nameof(StartPlay)}: File is a local one, generating metadata...");
                imgUrl = _appWebServer.GetPreviewPath(firstThumbnail);

                if (isVideoFile)
                {
                    media.StreamType = StreamType.Live;
                }
                media.Duration = fileInfo.Format.Duration;
                if (isMusicFile)
                {
                    media.Metadata = new MusicTrackMediaMetadata
                    {
                        Title     = title,
                        AlbumName = fileInfo.Format.Tag?.Album,
                        Artist    = fileInfo.Format.Tag?.Artist,
                    };
                }
            }
            else if (_youtubeUrlDecoder.IsYoutubeUrl(media.ContentId))
            {
                _logger.LogInformation($"{nameof(StartPlay)}: File is a youtube link, parsing it...");
                var ytMedia = await _youtubeUrlDecoder.Parse(media.ContentId, quality);

                QualitiesChanged?.Invoke(ytMedia.SelectedQuality, ytMedia.Qualities);

                imgUrl                  = ytMedia.ThumbnailUrl;
                media.ContentId         = ytMedia.Url;
                media.Metadata.Title    = ytMedia.Title;
                media.Metadata.Subtitle = ytMedia.Description;
                if (ytMedia.IsHls)
                {
                    fileInfo = await _ffmpegService.GetFileInfo(ytMedia.Url, default);

                    if (fileInfo == null)
                    {
                        _logger.LogWarning($"{nameof(StartPlay)}: Couldn't get the file info for url = {ytMedia.Url}");
                        throw new Exception($"File info is null for yt hls = {ytMedia.Url}");
                    }

                    var closestQuality = fileInfo.Videos
                                         .Select(v => v.Height)
                                         .GetClosest(quality);
                    var videoInfo = fileInfo.Videos.First(v => v.Height == closestQuality);
                    videoStreamIndex = videoInfo.Index;
                    audioStreamIndex = fileInfo.Audios.Any()
                        ? fileInfo.Audios.Select(a => a.Index).GetClosest(videoStreamIndex)
                        : -1;

                    videoNeedsTranscode = _ffmpegService.VideoNeedsTranscode(videoStreamIndex, _appSettings.ForceVideoTranscode, _appSettings.VideoScale, fileInfo);
                    audioNeedsTranscode = _ffmpegService.AudioNeedsTranscode(audioStreamIndex, _appSettings.ForceAudioTranscode, fileInfo);

                    media.Duration   = -1;
                    media.StreamType = StreamType.Live;
                    media.ContentId  = _appWebServer.GetMediaUrl(
                        ytMedia.Url,
                        videoStreamIndex,
                        audioStreamIndex,
                        seconds,
                        videoNeedsTranscode,
                        audioNeedsTranscode,
                        HwAccelDeviceType.None,
                        VideoScaleType.Original,
                        videoInfo.WidthAndHeightText);
                    media.ContentType = _ffmpegService.GetOutputTranscodeMimeType(media.ContentId);
                }
            }

            if (!string.IsNullOrEmpty(imgUrl))
            {
                media.Metadata.Images.Add(new GoogleCast.Models.Image
                {
                    Url = imgUrl
                });
            }

            _logger.LogInformation($"{nameof(StartPlay)}: Trying to load url = {media.ContentId}");
            var status = await _player.LoadAsync(media, true, seconds, activeTrackIds.ToArray());

            if (status is null)
            {
                var msg = $"Couldn't load url = {media.ContentId}";
                _logger.LogWarning($"{nameof(StartPlay)}: {msg}");
                throw new Exception(msg);
            }
            _logger.LogInformation($"{nameof(StartPlay)}: Url was successfully loaded");

            FileLoaded(metadata.Title, imgUrl, _player.CurrentMediaDuration, _player.CurrentVolumeLevel, _player.IsMuted);
        }
예제 #2
0
        protected override async Task OnRequestAsync(IHttpContext context)
        {
            var query = context.GetRequestQueryData();

            if (query.Count == 0 || !query.AllKeys.All(q => AppWebServerConstants.AllowedQueryParameters.Contains(q)))
            {
                context.Response.StatusCode        = (int)HttpStatusCode.BadRequest;
                context.Response.StatusDescription = "You need to provide all the query params";
                context.SetHandled();
                _logger.LogWarning($"{nameof(OnRequestAsync)}: Query params does not contain the all the allowed values.");
                return;
            }
            try
            {
                string filepath    = query[AppWebServerConstants.FileQueryParameter];
                bool   isVideoFile = _fileService.IsVideoFile(filepath);
                bool   isMusicFile = _fileService.IsMusicFile(filepath);
                bool   isHls       = _fileService.IsHls(filepath);

                if (!isVideoFile && !isMusicFile && !isHls)
                {
                    _logger.LogWarning($"{nameof(OnRequestAsync)}: File = {filepath} is not a video nor music file.");
                    return;
                }

                context.Response.ContentType = _ffmpegService.GetOutputTranscodeMimeType(filepath);

                context.Response.DisableCaching();

                if (_checkTranscodeProcess)
                {
                    _tokenSource.Cancel();
                    _tokenSource = new CancellationTokenSource();
                    _ffmpegService.KillTranscodeProcess();
                }

                _checkTranscodeProcess = true;
                if (isVideoFile || isHls)
                {
                    var options = GetVideoFileOptions(filepath, query);
                    _logger.LogInformation($"{nameof(OnRequestAsync)}: Handling request for video file with options = {JsonConvert.SerializeObject(options)}");
                    await _ffmpegService.TranscodeVideo(context.Response.OutputStream, options, _tokenSource.Token).ConfigureAwait(false);
                }
                else
                {
                    var options = GetMusicFileOptions(filepath, query);
                    _logger.LogInformation($"{nameof(OnRequestAsync)}: Handling request for music file with options = {JsonConvert.SerializeObject(options)}");
                    await using var memoryStream = await _ffmpegService.TranscodeMusic(options, _tokenSource.Token).ConfigureAwait(false);

                    //TODO: THIS LENGTH IS NOT WORKING PROPERLY
                    context.Response.ContentLength64 = memoryStream.Length;
                    await memoryStream.CopyToAsync(context.Response.OutputStream, _tokenSource.Token)
                    .ConfigureAwait(false);
                }
                _logger.LogInformation($"{nameof(OnRequestAsync)}: Request was successfully handled for file = {filepath}.");
            }
            catch (Exception e)
            {
                if (e is IOException || e is TaskCanceledException)
                {
                    return;
                }
                _logger.LogError(e, $"{nameof(OnRequestAsync)}: Unknown error occurred");
                _telemetryService.TrackError(e);
            }
            finally
            {
                context.SetHandled();
            }
        }