mirror of
https://github.com/revanced/revanced-patches
synced 2025-02-16 03:26:50 +01:00
fix(YouTube - Client spoof): Fix low resolution precise seeking thumbnails (#513)
This commit is contained in:
parent
4b8a9b9b13
commit
5ef20a8133
@ -3,6 +3,10 @@ package app.revanced.integrations.patches.spoof;
|
|||||||
import static app.revanced.integrations.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer;
|
import static app.revanced.integrations.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer;
|
||||||
import static app.revanced.integrations.utils.ReVancedUtils.containsAny;
|
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 androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@ -51,11 +55,15 @@ public class SpoofSignaturePatch {
|
|||||||
|
|
||||||
private static volatile Future<StoryboardRenderer> rendererFuture;
|
private static volatile Future<StoryboardRenderer> rendererFuture;
|
||||||
|
|
||||||
|
private static volatile boolean useOriginalStoryboardRenderer;
|
||||||
|
|
||||||
|
private static volatile boolean isPlayingShorts;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static StoryboardRenderer getRenderer() {
|
private static StoryboardRenderer getRenderer() {
|
||||||
if (rendererFuture != null) {
|
if (rendererFuture != null) {
|
||||||
try {
|
try {
|
||||||
return rendererFuture.get(5000, TimeUnit.MILLISECONDS);
|
return rendererFuture.get(4000, TimeUnit.MILLISECONDS);
|
||||||
} catch (TimeoutException ex) {
|
} catch (TimeoutException ex) {
|
||||||
LogHelper.printDebug(() -> "Could not get renderer (get timed out)");
|
LogHelper.printDebug(() -> "Could not get renderer (get timed out)");
|
||||||
} catch (ExecutionException | InterruptedException ex) {
|
} catch (ExecutionException | InterruptedException ex) {
|
||||||
@ -81,27 +89,38 @@ public class SpoofSignaturePatch {
|
|||||||
// Clip's player parameters contain a lot of information (e.g. video start and end time or whether it loops)
|
// 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).
|
// 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.
|
// Clips are 60 seconds or less in length, so no spoofing.
|
||||||
var isClip = parameters.length() > 150;
|
if (useOriginalStoryboardRenderer = parameters.length() > 150) return parameters;
|
||||||
if (isClip) return parameters;
|
|
||||||
|
|
||||||
// Shorts do not need to be spoofed.
|
// Shorts do not need to be spoofed.
|
||||||
if (parameters.startsWith(SHORTS_PLAYER_PARAMETERS)) return 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);
|
boolean isPlayingFeed = PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL
|
||||||
if (isPlayingFeed) return SettingsEnum.SPOOF_SIGNATURE_IN_FEED.getBoolean() ?
|
&& containsAny(parameters, AUTOPLAY_PARAMETERS);
|
||||||
// Prepend the scrim parameter to mute videos in feed.
|
if (isPlayingFeed) {
|
||||||
SCRIM_PARAMETER + INCOGNITO_PARAMETERS :
|
if (useOriginalStoryboardRenderer = !SettingsEnum.SPOOF_SIGNATURE_IN_FEED.getBoolean()) {
|
||||||
// In order to prevent videos that are auto-played in feed to be added to history,
|
// Don't spoof the feed video playback. This will cause video playback issues,
|
||||||
// only spoof the parameter if the video is not playing in the feed.
|
// but only if user continues watching for more than 1 minute.
|
||||||
// This will cause playback issues in the feed, but it's better than manipulating the history.
|
return parameters;
|
||||||
parameters;
|
}
|
||||||
|
// Spoof the feed video. Video will show up in watch history and video subtitles are missing.
|
||||||
|
fetchStoryboardRenderer();
|
||||||
|
return SCRIM_PARAMETER + INCOGNITO_PARAMETERS;
|
||||||
|
}
|
||||||
|
|
||||||
fetchStoryboardRenderer();
|
fetchStoryboardRenderer();
|
||||||
|
|
||||||
return INCOGNITO_PARAMETERS;
|
return INCOGNITO_PARAMETERS;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void fetchStoryboardRenderer() {
|
private static void fetchStoryboardRenderer() {
|
||||||
|
if (!SettingsEnum.SPOOF_STORYBOARD_RENDERER.getBoolean()) {
|
||||||
|
lastPlayerResponseVideoId = null;
|
||||||
|
rendererFuture = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
String videoId = VideoInformation.getPlayerResponseVideoId();
|
String videoId = VideoInformation.getPlayerResponseVideoId();
|
||||||
if (!videoId.equals(lastPlayerResponseVideoId)) {
|
if (!videoId.equals(lastPlayerResponseVideoId)) {
|
||||||
rendererFuture = ReVancedUtils.submitOnBackgroundThread(() -> getStoryboardRenderer(videoId));
|
rendererFuture = ReVancedUtils.submitOnBackgroundThread(() -> getStoryboardRenderer(videoId));
|
||||||
@ -115,11 +134,17 @@ public class SpoofSignaturePatch {
|
|||||||
getRenderer();
|
getRenderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static String getStoryboardRendererSpec(String originalStoryboardRendererSpec,
|
||||||
* Injection point.
|
boolean returnNullIfLiveStream) {
|
||||||
*/
|
if (SettingsEnum.SPOOF_SIGNATURE.getBoolean() && !useOriginalStoryboardRenderer) {
|
||||||
public static boolean getSeekbarThumbnailOverrideValue() {
|
StoryboardRenderer renderer = getRenderer();
|
||||||
return SettingsEnum.SPOOF_SIGNATURE.getBoolean();
|
if (renderer != null) {
|
||||||
|
if (returnNullIfLiveStream && renderer.isLiveStream()) return null;
|
||||||
|
return renderer.getSpec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalStoryboardRendererSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,19 +153,24 @@ public class SpoofSignaturePatch {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static String getStoryboardRendererSpec(String originalStoryboardRendererSpec) {
|
public static String getStoryboardRendererSpec(String originalStoryboardRendererSpec) {
|
||||||
if (SettingsEnum.SPOOF_SIGNATURE.getBoolean()) {
|
return getStoryboardRendererSpec(originalStoryboardRendererSpec, false);
|
||||||
StoryboardRenderer renderer = getRenderer();
|
}
|
||||||
if (renderer != null) return renderer.getSpec();
|
|
||||||
}
|
|
||||||
|
|
||||||
return originalStoryboardRendererSpec;
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* Uses additional check to handle live streams.
|
||||||
|
* Called from background threads and from the main thread.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static String getStoryboardDecoderRendererSpec(String originalStoryboardRendererSpec) {
|
||||||
|
return getStoryboardRendererSpec(originalStoryboardRendererSpec, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static int getRecommendedLevel(int originalLevel) {
|
public static int getRecommendedLevel(int originalLevel) {
|
||||||
if (SettingsEnum.SPOOF_SIGNATURE.getBoolean()) {
|
if (SettingsEnum.SPOOF_SIGNATURE.getBoolean() && !useOriginalStoryboardRenderer) {
|
||||||
StoryboardRenderer renderer = getRenderer();
|
StoryboardRenderer renderer = getRenderer();
|
||||||
if (renderer != null) {
|
if (renderer != null) {
|
||||||
Integer recommendedLevel = renderer.getRecommendedLevel();
|
Integer recommendedLevel = renderer.getRecommendedLevel();
|
||||||
@ -150,4 +180,30 @@ public class SpoofSignaturePatch {
|
|||||||
|
|
||||||
return 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,13 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
public final class StoryboardRenderer {
|
public final class StoryboardRenderer {
|
||||||
private final String spec;
|
private final String spec;
|
||||||
|
private final boolean isLiveStream;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final Integer recommendedLevel;
|
private final Integer recommendedLevel;
|
||||||
|
|
||||||
public StoryboardRenderer(String spec, @Nullable Integer recommendedLevel) {
|
public StoryboardRenderer(String spec, boolean isLiveStream, @Nullable Integer recommendedLevel) {
|
||||||
this.spec = spec;
|
this.spec = spec;
|
||||||
|
this.isLiveStream = isLiveStream;
|
||||||
this.recommendedLevel = recommendedLevel;
|
this.recommendedLevel = recommendedLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,6 +22,10 @@ public final class StoryboardRenderer {
|
|||||||
return spec;
|
return spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isLiveStream() {
|
||||||
|
return isLiveStream;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Recommended image quality level, or NULL if no recommendation exists.
|
* @return Recommended image quality level, or NULL if no recommendation exists.
|
||||||
*/
|
*/
|
||||||
@ -32,7 +38,8 @@ public final class StoryboardRenderer {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "StoryboardRenderer{" +
|
return "StoryboardRenderer{" +
|
||||||
"spec='" + spec + '\'' +
|
"isLiveStream=" + isLiveStream +
|
||||||
|
", spec='" + spec + '\'' +
|
||||||
", recommendedLevel=" + recommendedLevel +
|
", recommendedLevel=" + recommendedLevel +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ public class StoryboardRendererRequester {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static JSONObject fetchPlayerResponse(@NonNull String requestBody) {
|
private static JSONObject fetchPlayerResponse(@NonNull String requestBody) {
|
||||||
|
final long startTime = System.currentTimeMillis();
|
||||||
try {
|
try {
|
||||||
ReVancedUtils.verifyOffMainThread();
|
ReVancedUtils.verifyOffMainThread();
|
||||||
Objects.requireNonNull(requestBody);
|
Objects.requireNonNull(requestBody);
|
||||||
@ -40,6 +41,8 @@ public class StoryboardRendererRequester {
|
|||||||
LogHelper.printException(() -> "API timed out", ex);
|
LogHelper.printException(() -> "API timed out", ex);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Failed to fetch storyboard URL", ex);
|
LogHelper.printException(() -> "Failed to fetch storyboard URL", ex);
|
||||||
|
} finally {
|
||||||
|
LogHelper.printDebug(() -> "Request took: " + (System.currentTimeMillis() - startTime) + "ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -72,14 +75,17 @@ public class StoryboardRendererRequester {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private static StoryboardRenderer getStoryboardRendererUsingResponse(@NonNull JSONObject playerResponse) {
|
private static StoryboardRenderer getStoryboardRendererUsingResponse(@NonNull JSONObject playerResponse) {
|
||||||
try {
|
try {
|
||||||
|
LogHelper.printDebug(() -> "Parsing response: " + playerResponse);
|
||||||
final JSONObject storyboards = playerResponse.getJSONObject("storyboards");
|
final JSONObject storyboards = playerResponse.getJSONObject("storyboards");
|
||||||
final String storyboardsRendererTag = storyboards.has("playerLiveStoryboardSpecRenderer")
|
final boolean isLiveStream = storyboards.has("playerLiveStoryboardSpecRenderer");
|
||||||
|
final String storyboardsRendererTag = isLiveStream
|
||||||
? "playerLiveStoryboardSpecRenderer"
|
? "playerLiveStoryboardSpecRenderer"
|
||||||
: "playerStoryboardSpecRenderer";
|
: "playerStoryboardSpecRenderer";
|
||||||
|
|
||||||
final var rendererElement = storyboards.getJSONObject(storyboardsRendererTag);
|
final var rendererElement = storyboards.getJSONObject(storyboardsRendererTag);
|
||||||
StoryboardRenderer renderer = new StoryboardRenderer(
|
StoryboardRenderer renderer = new StoryboardRenderer(
|
||||||
rendererElement.getString("spec"),
|
rendererElement.getString("spec"),
|
||||||
|
isLiveStream,
|
||||||
rendererElement.has("recommendedLevel")
|
rendererElement.has("recommendedLevel")
|
||||||
? rendererElement.getInt("recommendedLevel")
|
? rendererElement.getInt("recommendedLevel")
|
||||||
: null
|
: null
|
||||||
|
@ -182,6 +182,9 @@ public enum SettingsEnum {
|
|||||||
"revanced_spoof_signature_verification_enabled_user_dialog_message"),
|
"revanced_spoof_signature_verification_enabled_user_dialog_message"),
|
||||||
SPOOF_SIGNATURE_IN_FEED("revanced_spoof_signature_in_feed_enabled", BOOLEAN, FALSE, false,
|
SPOOF_SIGNATURE_IN_FEED("revanced_spoof_signature_in_feed_enabled", BOOLEAN, FALSE, false,
|
||||||
parents(SPOOF_SIGNATURE)),
|
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),
|
SPOOF_DEVICE_DIMENSIONS("revanced_spoof_device_dimensions", BOOLEAN, FALSE, true),
|
||||||
BYPASS_URL_REDIRECTS("revanced_bypass_url_redirects", BOOLEAN, TRUE),
|
BYPASS_URL_REDIRECTS("revanced_bypass_url_redirects", BOOLEAN, TRUE),
|
||||||
ANNOUNCEMENTS("revanced_announcements", BOOLEAN, TRUE),
|
ANNOUNCEMENTS("revanced_announcements", BOOLEAN, TRUE),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user