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}" + ); + } } }