fix(YouTube - Spoof video streams): Make it work on 21.x (#6705)

Co-authored-by: wowitsjack <no@email>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
Jack 2026-03-06 21:27:41 +10:00 committed by GitHub
parent f045923cef
commit fdfed3c9dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 24 additions and 22 deletions

View file

@ -1,6 +1,6 @@
package app.revanced.extension.youtube.patches; package app.revanced.extension.youtube.patches;
import android.widget.ImageView; import android.view.View;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
@ -46,7 +46,7 @@ public class ExitFullscreenPatch {
// To fix this, push the perform click to the back fo the main thread, // To fix this, push the perform click to the back fo the main thread,
// and by then the overlay controls will be visible since the video is now finished. // and by then the overlay controls will be visible since the video is now finished.
Utils.runOnMainThreadDelayed(() -> { Utils.runOnMainThreadDelayed(() -> {
ImageView button = PlayerControlsPatch.fullscreenButtonRef.get(); View button = PlayerControlsPatch.fullscreenButtonRef.get();
if (button == null) { if (button == null) {
Logger.printDebug(() -> "Fullscreen button is null, cannot click"); Logger.printDebug(() -> "Fullscreen button is null, cannot click");
} else { } else {

View file

@ -109,13 +109,13 @@ public final class HidePlayerOverlayButtonsPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static ImageView hideFullscreenButton(ImageView imageView) { public static View hideFullscreenButton(View view) {
if (!Settings.HIDE_FULLSCREEN_BUTTON.get()) { if (!Settings.HIDE_FULLSCREEN_BUTTON.get()) {
return imageView; return view;
} }
if (imageView != null) { if (view != null) {
imageView.setVisibility(View.GONE); view.setVisibility(View.GONE);
} }
return null; return null;

View file

@ -2,7 +2,6 @@ package app.revanced.extension.youtube.patches;
import android.view.View; import android.view.View;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.widget.ImageView;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
@ -11,7 +10,7 @@ import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class PlayerControlsPatch { public class PlayerControlsPatch {
public static WeakReference<ImageView> fullscreenButtonRef = new WeakReference<>(null); public static WeakReference<View> fullscreenButtonRef = new WeakReference<>(null);
private static boolean fullscreenButtonVisibilityCallbacksExist() { private static boolean fullscreenButtonVisibilityCallbacksExist() {
return false; // Modified during patching if needed. return false; // Modified during patching if needed.
@ -20,8 +19,8 @@ public class PlayerControlsPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static void setFullscreenCloseButton(ImageView imageButton) { public static void setFullscreenCloseButton(View button) {
fullscreenButtonRef = new WeakReference<>(imageButton); fullscreenButtonRef = new WeakReference<>(button);
Logger.printDebug(() -> "Fullscreen button set"); Logger.printDebug(() -> "Fullscreen button set");
if (!fullscreenButtonVisibilityCallbacksExist()) { if (!fullscreenButtonVisibilityCallbacksExist()) {
@ -30,13 +29,13 @@ public class PlayerControlsPatch {
// Add a global listener, since the protected method // Add a global listener, since the protected method
// View#onVisibilityChanged() does not have any call backs. // View#onVisibilityChanged() does not have any call backs.
imageButton.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { button.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
int lastVisibility = View.VISIBLE; int lastVisibility = View.VISIBLE;
@Override @Override
public void onGlobalLayout() { public void onGlobalLayout() {
try { try {
final int visibility = imageButton.getVisibility(); final int visibility = button.getVisibility();
if (lastVisibility != visibility) { if (lastVisibility != visibility) {
lastVisibility = visibility; lastVisibility = visibility;

View file

@ -216,10 +216,10 @@ internal fun spoofVideoStreamsPatch(
// A proper fix may include modifying the request body to match the platforms expected body. // A proper fix may include modifying the request body to match the platforms expected body.
buildMediaDataSourceMethod.apply { buildMediaDataSourceMethod.apply {
val targetIndex = instructions.count() - 1 // Find return-void, not the last instruction, which may be a throw in an error branch
// where p0 is overwritten by new-instance IllegalArgumentException.
val targetIndex = indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID)
// Instructions are added just before the method returns,
// so there's no concern of clobbering in-use registers.
addInstructions( addInstructions(
targetIndex, targetIndex,
""" """

View file

@ -38,7 +38,8 @@ internal val BytecodePatchContext.fullscreenButtonMethodMatch by composingFirstM
returnType("V") returnType("V")
instructions( instructions(
ResourceType.ID("fullscreen_button"), ResourceType.ID("fullscreen_button"),
Opcode.CHECK_CAST() Opcode.INVOKE_VIRTUAL(), // findViewById call.
Opcode.MOVE_RESULT_OBJECT() // The actual view, not a check-cast in 21.x.
) )
} }

View file

@ -169,14 +169,16 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
fullscreenButtonMethodMatch.let { fullscreenButtonMethodMatch.let {
it.method.apply { it.method.apply {
val castIndex = it[1] // Youtube 21.x doesn't cast the fullscreen button to ImageView anymore,
val insertIndex = castIndex + 1 // so match on move-result-object after findViewById instead of check-cast.
val insertRegister = getInstruction<OneRegisterInstruction>(castIndex).registerA val moveResultIndex = it[2]
val insertIndex = moveResultIndex + 1
val insertRegister = getInstruction<OneRegisterInstruction>(moveResultIndex).registerA
addInstructionsWithLabels( addInstructionsWithLabels(
insertIndex, insertIndex,
""" """
invoke-static { v$insertRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->hideFullscreenButton(Landroid/widget/ImageView;)Landroid/widget/ImageView; invoke-static { v$insertRegister }, ${EXTENSION_CLASS_DESCRIPTOR}->hideFullscreenButton(Landroid/view/View;)Landroid/view/View;
move-result-object v$insertRegister move-result-object v$insertRegister
if-nez v$insertRegister, :show if-nez v$insertRegister, :show
return-void return-void

View file

@ -78,7 +78,7 @@ internal val BytecodePatchContext.overlayViewInflateMethodMatch by composingFirs
instructions( instructions(
ResourceType.ID("heatseeker_viewstub"), ResourceType.ID("heatseeker_viewstub"),
ResourceType.ID("fullscreen_button"), ResourceType.ID("fullscreen_button"),
allOf(Opcode.CHECK_CAST(), type("Landroid/widget/ImageView;")), Opcode.CHECK_CAST(),
) )
} }

View file

@ -261,7 +261,7 @@ val playerControlsPatch = bytecodePatch(
addInstruction( addInstruction(
index + 1, index + 1,
"invoke-static { v$register }, " + "invoke-static { v$register }, " +
"$EXTENSION_CLASS_DESCRIPTOR->setFullscreenCloseButton(Landroid/widget/ImageView;)V", "$EXTENSION_CLASS_DESCRIPTOR->setFullscreenCloseButton(Landroid/view/View;)V",
) )
} }