diff --git a/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java b/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java index 114640e1..93ca4c4d 100644 --- a/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java @@ -169,7 +169,17 @@ public class SpoofSignaturePatch { * if {@link SettingsEnum#SPOOF_STORYBOARD_RENDERER} is not enabled. */ public static boolean getSeekbarThumbnailOverrideValue() { - return SettingsEnum.SPOOF_SIGNATURE.getBoolean(); + if (!SettingsEnum.SPOOF_SIGNATURE.getBoolean()) { + return false; + } + StoryboardRenderer renderer = videoRenderer; + if (renderer == null) { + // Spoof storyboard renderer is turned off, + // video is paid, or the storyboard fetch timed out. + // Show empty thumbnails so the seek time and chapters still show up. + return true; + } + return renderer.getSpec() != null; } /** diff --git a/app/src/main/java/app/revanced/integrations/patches/spoof/StoryboardRenderer.java b/app/src/main/java/app/revanced/integrations/patches/spoof/StoryboardRenderer.java index 32f5608c..3adc5f67 100644 --- a/app/src/main/java/app/revanced/integrations/patches/spoof/StoryboardRenderer.java +++ b/app/src/main/java/app/revanced/integrations/patches/spoof/StoryboardRenderer.java @@ -1,23 +1,23 @@ package app.revanced.integrations.patches.spoof; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.jetbrains.annotations.NotNull; public final class StoryboardRenderer { + @Nullable private final String spec; private final boolean isLiveStream; @Nullable private final Integer recommendedLevel; - public StoryboardRenderer(String spec, boolean isLiveStream, @Nullable Integer recommendedLevel) { + public StoryboardRenderer(@Nullable String spec, boolean isLiveStream, @Nullable Integer recommendedLevel) { this.spec = spec; this.isLiveStream = isLiveStream; this.recommendedLevel = recommendedLevel; } - @NonNull + @Nullable public String getSpec() { return spec; } diff --git a/app/src/main/java/app/revanced/integrations/patches/spoof/requests/StoryboardRendererRequester.java b/app/src/main/java/app/revanced/integrations/patches/spoof/requests/StoryboardRendererRequester.java index addae2d7..f5f7ce27 100644 --- a/app/src/main/java/app/revanced/integrations/patches/spoof/requests/StoryboardRendererRequester.java +++ b/app/src/main/java/app/revanced/integrations/patches/spoof/requests/StoryboardRendererRequester.java @@ -24,6 +24,14 @@ import app.revanced.integrations.utils.ReVancedUtils; public class StoryboardRendererRequester { + /** + * For videos that have no storyboard. + * Usually for low resolution videos as old as YouTube itself. + * Does not include paid videos where the renderer fetch fails. + */ + private static final StoryboardRenderer emptyStoryboard + = new StoryboardRenderer(null, false, null); + private StoryboardRendererRequester() { } @@ -105,6 +113,10 @@ public class StoryboardRendererRequester { private static StoryboardRenderer getStoryboardRendererUsingResponse(@NonNull JSONObject playerResponse) { try { LogHelper.printDebug(() -> "Parsing response: " + playerResponse); + if (!playerResponse.has("storyboards")) { + LogHelper.printDebug(() -> "Using empty storyboard"); + return emptyStoryboard; + } final JSONObject storyboards = playerResponse.getJSONObject("storyboards"); final boolean isLiveStream = storyboards.has("playerLiveStoryboardSpecRenderer"); final String storyboardsRendererTag = isLiveStream