From afdd03b2b9a46cd0149a7ad48a246b3836153f51 Mon Sep 17 00:00:00 2001 From: LuckyNoS7evin Date: Tue, 26 May 2026 18:00:04 +0100 Subject: [PATCH] At the moment errors from the CipherManifest mean we don't get the original exception that caused YTE to use the cipher fallback code. This should give people better error handling and better logs for those errors --- YoutubeExplode/Bridge/PlayerSource.cs | 19 +++++++--- YoutubeExplode/Videos/Streams/StreamClient.cs | 36 ++++++++++++------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/YoutubeExplode/Bridge/PlayerSource.cs b/YoutubeExplode/Bridge/PlayerSource.cs index 7d3d76e8..af918dc1 100644 --- a/YoutubeExplode/Bridge/PlayerSource.cs +++ b/YoutubeExplode/Bridge/PlayerSource.cs @@ -5,13 +5,14 @@ using Lazy; using PowerKit.Extensions; using YoutubeExplode.Bridge.Cipher; +using YoutubeExplode.Exceptions; namespace YoutubeExplode.Bridge; internal partial class PlayerSource(string content) { [Lazy] - public CipherManifest? CipherManifest + public CipherManifest CipherManifest { get { @@ -22,7 +23,9 @@ public CipherManifest? CipherManifest .Value.NullIfWhiteSpace(); if (string.IsNullOrWhiteSpace(signatureTimestamp)) - return null; + throw new YoutubeExplodeException( + "Failed to extract cipher signature timestamp from player source." + ); // Find where the player calls the cipher functions var cipherCallsite = Regex @@ -37,7 +40,9 @@ public CipherManifest? CipherManifest .Value.NullIfWhiteSpace(); if (string.IsNullOrWhiteSpace(cipherCallsite)) - return null; + throw new YoutubeExplodeException( + "Failed to find cipher callsite in player source." + ); // Find the object that defines the cipher functions var cipherContainerName = Regex @@ -46,7 +51,9 @@ public CipherManifest? CipherManifest .Value; if (string.IsNullOrWhiteSpace(cipherContainerName)) - return null; + throw new YoutubeExplodeException( + "Failed to extract cipher container name from player source." + ); // Find the definition of the cipher functions var cipherDefinition = Regex @@ -61,7 +68,9 @@ public CipherManifest? CipherManifest .Value.NullIfWhiteSpace(); if (string.IsNullOrWhiteSpace(cipherDefinition)) - return null; + throw new YoutubeExplodeException( + "Failed to find cipher function definitions in player source." + ); // Identify the swap cipher function var swapFuncName = Regex diff --git a/YoutubeExplode/Videos/Streams/StreamClient.cs b/YoutubeExplode/Videos/Streams/StreamClient.cs index 0441222b..fe97f29e 100644 --- a/YoutubeExplode/Videos/Streams/StreamClient.cs +++ b/YoutubeExplode/Videos/Streams/StreamClient.cs @@ -37,9 +37,7 @@ CancellationToken cancellationToken var playerSource = await _controller.GetPlayerSourceAsync(cancellationToken); - return _cipherManifest = - playerSource.CipherManifest - ?? throw new YoutubeExplodeException("Failed to extract the cipher manifest."); + return _cipherManifest = playerSource.CipherManifest; } private async ValueTask TryGetContentLengthAsync( @@ -277,20 +275,32 @@ private async ValueTask> GetStreamInfosAsync( return await GetStreamInfosAsync(videoId, playerResponse, cancellationToken); } // Retry with deciphering - catch (VideoUnplayableException ex) + catch (VideoUnplayableException originalEx) // Only retry on videos that are unplayable for reasons other than being unavailable (deleted, private, etc) - when (ex is not VideoUnavailableException) + when (originalEx is not VideoUnavailableException) { - var cipherManifest = await ResolveCipherManifestAsync(cancellationToken); + try + { + var cipherManifest = await ResolveCipherManifestAsync(cancellationToken); - // Try to get player response from a client with cipher - var playerResponse = await _controller.GetPlayerResponseAsync( - videoId, - cipherManifest.SignatureTimestamp, - cancellationToken - ); + // Try to get player response from a client with cipher + var playerResponse = await _controller.GetPlayerResponseAsync( + videoId, + cipherManifest.SignatureTimestamp, + cancellationToken + ); - return await GetStreamInfosAsync(videoId, playerResponse, cancellationToken); + return await GetStreamInfosAsync(videoId, playerResponse, cancellationToken); + } + catch (Exception fallbackEx) + when (fallbackEx is not (VideoUnavailableException or OperationCanceledException)) + { + throw new YoutubeExplodeException( + $"Failed to resolve streams for video '{videoId}'. " + + $"Initial error: {originalEx.Message} " + + $"Cipher fallback error: {fallbackEx.Message}" + ); + } } }