Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions YoutubeExplode/Bridge/PlayerSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
36 changes: 23 additions & 13 deletions YoutubeExplode/Videos/Streams/StreamClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<long?> TryGetContentLengthAsync(
Expand Down Expand Up @@ -277,20 +275,32 @@ private async ValueTask<IReadOnlyList<IStreamInfo>> 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}"
);
}
}
}

Expand Down