From 11f97ac354344aac3d101f8874e38273da15f9e6 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 11 Nov 2023 20:22:41 +0200 Subject: [PATCH 1/2] fix(YouTube - Client spoof): Fix low resolution precise seeking thumbnails (#513) --- .../patches/spoof/SpoofSignaturePatch.java | 104 ++++++++++++++---- .../patches/spoof/StoryboardRenderer.java | 11 +- .../requests/StoryboardRendererRequester.java | 8 +- .../integrations/settings/SettingsEnum.java | 3 + 4 files changed, 99 insertions(+), 27 deletions(-) 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 16a284d049..06534d03c9 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 @@ -3,6 +3,10 @@ import static app.revanced.integrations.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer; import static app.revanced.integrations.utils.ReVancedUtils.containsAny; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + import androidx.annotation.Nullable; import java.util.concurrent.ExecutionException; @@ -51,11 +55,15 @@ public class SpoofSignaturePatch { private static volatile Future rendererFuture; + private static volatile boolean useOriginalStoryboardRenderer; + + private static volatile boolean isPlayingShorts; + @Nullable private static StoryboardRenderer getRenderer() { if (rendererFuture != null) { try { - return rendererFuture.get(5000, TimeUnit.MILLISECONDS); + return rendererFuture.get(4000, TimeUnit.MILLISECONDS); } catch (TimeoutException ex) { LogHelper.printDebug(() -> "Could not get renderer (get timed out)"); } catch (ExecutionException | InterruptedException ex) { @@ -81,27 +89,38 @@ public static String spoofParameter(String parameters) { // Clip's player parameters contain a lot of information (e.g. video start and end time or whether it loops) // For this reason, the player parameters of a clip are usually very long (150~300 characters). // Clips are 60 seconds or less in length, so no spoofing. - var isClip = parameters.length() > 150; - if (isClip) return parameters; + if (useOriginalStoryboardRenderer = parameters.length() > 150) return parameters; // Shorts do not need to be spoofed. - if (parameters.startsWith(SHORTS_PLAYER_PARAMETERS)) return parameters; - - boolean isPlayingFeed = PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL && containsAny(parameters, AUTOPLAY_PARAMETERS); - if (isPlayingFeed) return SettingsEnum.SPOOF_SIGNATURE_IN_FEED.getBoolean() ? - // Prepend the scrim parameter to mute videos in feed. - SCRIM_PARAMETER + INCOGNITO_PARAMETERS : - // In order to prevent videos that are auto-played in feed to be added to history, - // only spoof the parameter if the video is not playing in the feed. - // This will cause playback issues in the feed, but it's better than manipulating the history. - parameters; + if (useOriginalStoryboardRenderer = parameters.startsWith(SHORTS_PLAYER_PARAMETERS)) { + isPlayingShorts = true; + return parameters; + } + isPlayingShorts = false; + + boolean isPlayingFeed = PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL + && containsAny(parameters, AUTOPLAY_PARAMETERS); + if (isPlayingFeed) { + if (useOriginalStoryboardRenderer = !SettingsEnum.SPOOF_SIGNATURE_IN_FEED.getBoolean()) { + // Don't spoof the feed video playback. This will cause video playback issues, + // but only if user continues watching for more than 1 minute. + return parameters; + } + // Spoof the feed video. Video will show up in watch history and video subtitles are missing. + fetchStoryboardRenderer(); + return SCRIM_PARAMETER + INCOGNITO_PARAMETERS; + } fetchStoryboardRenderer(); - return INCOGNITO_PARAMETERS; } private static void fetchStoryboardRenderer() { + if (!SettingsEnum.SPOOF_STORYBOARD_RENDERER.getBoolean()) { + lastPlayerResponseVideoId = null; + rendererFuture = null; + return; + } String videoId = VideoInformation.getPlayerResponseVideoId(); if (!videoId.equals(lastPlayerResponseVideoId)) { rendererFuture = ReVancedUtils.submitOnBackgroundThread(() -> getStoryboardRenderer(videoId)); @@ -115,32 +134,43 @@ private static void fetchStoryboardRenderer() { getRenderer(); } + private static String getStoryboardRendererSpec(String originalStoryboardRendererSpec, + boolean returnNullIfLiveStream) { + if (SettingsEnum.SPOOF_SIGNATURE.getBoolean() && !useOriginalStoryboardRenderer) { + StoryboardRenderer renderer = getRenderer(); + if (renderer != null) { + if (returnNullIfLiveStream && renderer.isLiveStream()) return null; + return renderer.getSpec(); + } + } + + return originalStoryboardRendererSpec; + } + /** * Injection point. + * Called from background threads and from the main thread. */ - public static boolean getSeekbarThumbnailOverrideValue() { - return SettingsEnum.SPOOF_SIGNATURE.getBoolean(); + @Nullable + public static String getStoryboardRendererSpec(String originalStoryboardRendererSpec) { + return getStoryboardRendererSpec(originalStoryboardRendererSpec, false); } /** * Injection point. + * Uses additional check to handle live streams. * Called from background threads and from the main thread. */ @Nullable - public static String getStoryboardRendererSpec(String originalStoryboardRendererSpec) { - if (SettingsEnum.SPOOF_SIGNATURE.getBoolean()) { - StoryboardRenderer renderer = getRenderer(); - if (renderer != null) return renderer.getSpec(); - } - - return originalStoryboardRendererSpec; + public static String getStoryboardDecoderRendererSpec(String originalStoryboardRendererSpec) { + return getStoryboardRendererSpec(originalStoryboardRendererSpec, true); } /** * Injection point. */ public static int getRecommendedLevel(int originalLevel) { - if (SettingsEnum.SPOOF_SIGNATURE.getBoolean()) { + if (SettingsEnum.SPOOF_SIGNATURE.getBoolean() && !useOriginalStoryboardRenderer) { StoryboardRenderer renderer = getRenderer(); if (renderer != null) { Integer recommendedLevel = renderer.getRecommendedLevel(); @@ -150,4 +180,30 @@ public static int getRecommendedLevel(int originalLevel) { return originalLevel; } + + /** + * Injection point. Forces seekbar to be shown for paid videos or + * if {@link SettingsEnum#SPOOF_STORYBOARD_RENDERER} is not enabled. + */ + public static boolean getSeekbarThumbnailOverrideValue() { + return SettingsEnum.SPOOF_SIGNATURE.getBoolean(); + } + + /** + * Injection point. + * + * @param view seekbar thumbnail view. Includes both shorts and regular videos. + */ + public static void seekbarImageViewCreated(ImageView view) { + if (!SettingsEnum.SPOOF_SIGNATURE.getBoolean() + || SettingsEnum.SPOOF_STORYBOARD_RENDERER.getBoolean()) { + return; + } + if (isPlayingShorts) return; + + view.setVisibility(View.GONE); + // Also hide the border around the thumbnail (otherwise a 1 pixel wide bordered frame is visible). + ViewGroup parentLayout = (ViewGroup) view.getParent(); + parentLayout.setPadding(0, 0, 0, 0); + } } 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 d0e70988bf..32f5608ccf 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 @@ -7,11 +7,13 @@ public final class StoryboardRenderer { private final String spec; + private final boolean isLiveStream; @Nullable private final Integer recommendedLevel; - public StoryboardRenderer(String spec, @Nullable Integer recommendedLevel) { + public StoryboardRenderer(String spec, boolean isLiveStream, @Nullable Integer recommendedLevel) { this.spec = spec; + this.isLiveStream = isLiveStream; this.recommendedLevel = recommendedLevel; } @@ -20,6 +22,10 @@ public String getSpec() { return spec; } + public boolean isLiveStream() { + return isLiveStream; + } + /** * @return Recommended image quality level, or NULL if no recommendation exists. */ @@ -32,7 +38,8 @@ public Integer getRecommendedLevel() { @Override public String toString() { return "StoryboardRenderer{" + - "spec='" + spec + '\'' + + "isLiveStream=" + isLiveStream + + ", spec='" + spec + '\'' + ", recommendedLevel=" + recommendedLevel + '}'; } 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 61828a04bb..38904dc26c 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 @@ -22,6 +22,7 @@ private StoryboardRendererRequester() { @Nullable private static JSONObject fetchPlayerResponse(@NonNull String requestBody) { + final long startTime = System.currentTimeMillis(); try { ReVancedUtils.verifyOffMainThread(); Objects.requireNonNull(requestBody); @@ -40,6 +41,8 @@ private static JSONObject fetchPlayerResponse(@NonNull String requestBody) { LogHelper.printException(() -> "API timed out", ex); } catch (Exception ex) { LogHelper.printException(() -> "Failed to fetch storyboard URL", ex); + } finally { + LogHelper.printDebug(() -> "Request took: " + (System.currentTimeMillis() - startTime) + "ms"); } return null; @@ -72,14 +75,17 @@ private static StoryboardRenderer getStoryboardRendererUsingBody(@NonNull String @Nullable private static StoryboardRenderer getStoryboardRendererUsingResponse(@NonNull JSONObject playerResponse) { try { + LogHelper.printDebug(() -> "Parsing response: " + playerResponse); final JSONObject storyboards = playerResponse.getJSONObject("storyboards"); - final String storyboardsRendererTag = storyboards.has("playerLiveStoryboardSpecRenderer") + final boolean isLiveStream = storyboards.has("playerLiveStoryboardSpecRenderer"); + final String storyboardsRendererTag = isLiveStream ? "playerLiveStoryboardSpecRenderer" : "playerStoryboardSpecRenderer"; final var rendererElement = storyboards.getJSONObject(storyboardsRendererTag); StoryboardRenderer renderer = new StoryboardRenderer( rendererElement.getString("spec"), + isLiveStream, rendererElement.has("recommendedLevel") ? rendererElement.getInt("recommendedLevel") : null diff --git a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java index 53a911e57d..105bea5431 100644 --- a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java +++ b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java @@ -182,6 +182,9 @@ public enum SettingsEnum { "revanced_spoof_signature_verification_enabled_user_dialog_message"), SPOOF_SIGNATURE_IN_FEED("revanced_spoof_signature_in_feed_enabled", BOOLEAN, FALSE, false, parents(SPOOF_SIGNATURE)), + SPOOF_STORYBOARD_RENDERER("revanced_spoof_storyboard", BOOLEAN, TRUE, true, + parents(SPOOF_SIGNATURE)), + SPOOF_DEVICE_DIMENSIONS("revanced_spoof_device_dimensions", BOOLEAN, FALSE, true), BYPASS_URL_REDIRECTS("revanced_bypass_url_redirects", BOOLEAN, TRUE), ANNOUNCEMENTS("revanced_announcements", BOOLEAN, TRUE), From db0fb46a396ad5f3301b32773d19638ca0af92cb Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 11 Nov 2023 18:26:36 +0000 Subject: [PATCH 2/2] chore(release): 0.121.1-dev.4 [skip ci] ## [0.121.1-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v0.121.1-dev.3...v0.121.1-dev.4) (2023-11-11) ### Bug Fixes * **YouTube - Client spoof:** Fix low resolution precise seeking thumbnails ([#513](https://github.com/ReVanced/revanced-integrations/issues/513)) ([11f97ac](https://github.com/ReVanced/revanced-integrations/commit/11f97ac354344aac3d101f8874e38273da15f9e6)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a2992b9ae..5768a39130 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.121.1-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v0.121.1-dev.3...v0.121.1-dev.4) (2023-11-11) + + +### Bug Fixes + +* **YouTube - Client spoof:** Fix low resolution precise seeking thumbnails ([#513](https://github.com/ReVanced/revanced-integrations/issues/513)) ([11f97ac](https://github.com/ReVanced/revanced-integrations/commit/11f97ac354344aac3d101f8874e38273da15f9e6)) + ## [0.121.1-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v0.121.1-dev.2...v0.121.1-dev.3) (2023-11-10) diff --git a/gradle.properties b/gradle.properties index c9a867d8af..e24192a9a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 0.121.1-dev.3 +version = 0.121.1-dev.4