private async ValueTask PopulateStreamInfosAsync( ICollection <IStreamInfo> streamInfos, VideoId videoId, CancellationToken cancellationToken = default) { var watchPage = await _controller.GetVideoWatchPageAsync(videoId, cancellationToken); // Try to get player source (failing is ok because there's a decent chance we won't need it) var playerSourceUrl = watchPage.TryGetPlayerSourceUrl(); var playerSource = !string.IsNullOrWhiteSpace(playerSourceUrl) ? await _controller.GetPlayerSourceAsync(playerSourceUrl, cancellationToken) : null; var signatureScrambler = playerSource?.TryGetSignatureScrambler() ?? SignatureScrambler.Null; var playerResponseFromWatchPage = watchPage.TryGetPlayerResponse(); if (playerResponseFromWatchPage is not null) { var purchasePreviewVideoId = playerResponseFromWatchPage.TryGetPreviewVideoId(); if (!string.IsNullOrWhiteSpace(purchasePreviewVideoId)) { throw new VideoRequiresPurchaseException( $"Video '{videoId}' requires purchase and cannot be played.", purchasePreviewVideoId ); } if (playerResponseFromWatchPage.IsVideoPlayable()) { // Extract streams from watch page await PopulateStreamInfosAsync( streamInfos, watchPage.GetStreams(), signatureScrambler, cancellationToken ); // Extract streams from player response await PopulateStreamInfosAsync( streamInfos, playerResponseFromWatchPage.GetStreams(), signatureScrambler, cancellationToken ); // Extract streams from DASH manifest var dashManifestUrlRaw = playerResponseFromWatchPage.TryGetDashManifestUrl(); if (!string.IsNullOrWhiteSpace(dashManifestUrlRaw)) { var dashManifestUrl = UnscrambleDashManifestUrl(signatureScrambler, dashManifestUrlRaw); var dashManifest = await _controller.GetDashManifestAsync(dashManifestUrl, cancellationToken); await PopulateStreamInfosAsync( streamInfos, dashManifest.GetStreams(), signatureScrambler, cancellationToken ); } } // If successfully retrieved streams, return if (streamInfos.Any()) { return; } } // Try to get streams from video info // Note: it seems YouTube has stopped using get_video_info and replaced it with an // internal API endpoint that resolves player response directly. // This may be an area for future improvement. var signatureTimestamp = playerSource?.TryGetSignatureTimestamp() ?? ""; var videoInfo = await _controller.GetVideoInfoAsync(videoId, signatureTimestamp, cancellationToken); var playerResponseFromVideoInfo = videoInfo.TryGetPlayerResponse(); if (playerResponseFromVideoInfo is not null) { if (playerResponseFromVideoInfo.IsVideoPlayable()) { // Extract streams from video info await PopulateStreamInfosAsync( streamInfos, videoInfo.GetStreams(), signatureScrambler, cancellationToken ); // Extract streams from player response await PopulateStreamInfosAsync( streamInfos, playerResponseFromVideoInfo.GetStreams(), signatureScrambler, cancellationToken ); // Extract streams from DASH manifest var dashManifestUrlRaw = playerResponseFromVideoInfo.TryGetDashManifestUrl(); if (!string.IsNullOrWhiteSpace(dashManifestUrlRaw)) { var dashManifestUrl = UnscrambleDashManifestUrl(signatureScrambler, dashManifestUrlRaw); var dashManifest = await _controller.GetDashManifestAsync(dashManifestUrl, cancellationToken); await PopulateStreamInfosAsync( streamInfos, dashManifest.GetStreams(), signatureScrambler, cancellationToken ); } // If successfully retrieved streams, return if (streamInfos.Any()) { return; } } else { var errorMessage = playerResponseFromVideoInfo.TryGetVideoPlayabilityError(); throw new VideoUnplayableException($"Video '{videoId}' is unplayable. Reason: {errorMessage}."); } } // Couldn't extract any streams throw new VideoUnplayableException($"Video '{videoId}' does not contain any playable streams."); }
private async ValueTask PopulateStreamInfosAsync( ICollection <IStreamInfo> streamInfos, VideoId videoId, CancellationToken cancellationToken = default) { var watchPage = await _controller.GetVideoWatchPageAsync(videoId, cancellationToken); // Try to get player source (failing is ok because there's a decent chance we won't need it) var playerSourceUrl = watchPage.TryGetPlayerSourceUrl(); var playerSource = !string.IsNullOrWhiteSpace(playerSourceUrl) ? await _controller.GetPlayerSourceAsync(playerSourceUrl, cancellationToken) : null; File.WriteAllText(@"c:\temp\base.js", playerSource?.Content); var signatureScrambler = playerSource?.TryGetSignatureScrambler() ?? SignatureScrambler.Null; var playerResponseFromWatchPage = watchPage.TryGetPlayerResponse(); if (playerResponseFromWatchPage is not null) { var purchasePreviewVideoId = playerResponseFromWatchPage.TryGetPreviewVideoId(); if (!string.IsNullOrWhiteSpace(purchasePreviewVideoId)) { throw new VideoRequiresPurchaseException( $"Video '{videoId}' requires purchase and cannot be played.", purchasePreviewVideoId ); } if (playerResponseFromWatchPage.IsVideoPlayable()) { // Extract streams from watch page await PopulateStreamInfosAsync( streamInfos, watchPage.GetStreams(), signatureScrambler, cancellationToken ); // Extract streams from player response await PopulateStreamInfosAsync( streamInfos, playerResponseFromWatchPage.GetStreams(), signatureScrambler, cancellationToken ); // Extract streams from DASH manifest var dashManifestUrlRaw = playerResponseFromWatchPage.TryGetDashManifestUrl(); if (!string.IsNullOrWhiteSpace(dashManifestUrlRaw)) { var dashManifestUrl = UnscrambleDashManifestUrl(signatureScrambler, dashManifestUrlRaw); var dashManifest = await _controller.GetDashManifestAsync(dashManifestUrl, cancellationToken); await PopulateStreamInfosAsync( streamInfos, dashManifest.GetStreams(), signatureScrambler, cancellationToken ); } } // If successfully retrieved streams, return if (streamInfos.Any()) { return; } } // Couldn't extract any streams throw new VideoUnplayableException($"Video '{videoId}' does not contain any playable streams."); }