Skip to content

Commit bb526bc

Browse files
fix(YouTube - Player controls): Show player control buttons with A/B layout (ReVanced#3901)
1 parent 53cb19f commit bb526bc

File tree

8 files changed

+106
-5
lines changed

8 files changed

+106
-5
lines changed

extensions/shared/src/main/java/app/revanced/extension/youtube/patches/EnableDebuggingPatch.java

+14
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,18 @@ public static long isLongFeatureFlagEnabled(long value, long flag, long defaultV
5353

5454
return value;
5555
}
56+
57+
/**
58+
* Injection point.
59+
*/
60+
public static String isStringFeatureFlagEnabled(String value, long flag, String defaultValue) {
61+
if (BaseSettings.DEBUG.get() && !defaultValue.equals(value)) {
62+
if (featureFlags.putIfAbsent(flag, true) == null) {
63+
Logger.printDebug(() -> " string feature is enabled: " + flag
64+
+ " value: " + value + (defaultValue.isEmpty() ? "" : " default: " + defaultValue));
65+
}
66+
}
67+
68+
return value;
69+
}
5670
}

extensions/shared/src/main/java/app/revanced/extension/youtube/patches/PlayerControlsPatch.java

+8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
@SuppressWarnings("unused")
1010
public class PlayerControlsPatch {
11+
1112
/**
1213
* Injection point.
1314
*/
@@ -41,4 +42,11 @@ public void onGlobalLayout() {
4142
public static void fullscreenButtonVisibilityChanged(boolean isVisible) {
4243
// Code added during patching.
4344
}
45+
46+
/**
47+
* Injection point.
48+
*/
49+
public static String getPlayerTopControlsLayoutResourceName(String original) {
50+
return "default";
51+
}
4452
}

patches/api/patches.api

+2
Original file line numberDiff line numberDiff line change
@@ -1444,10 +1444,12 @@ public final class app/revanced/util/BytecodeUtilsKt {
14441444
public static final fun indexOfFirstInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)I
14451445
public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I
14461446
public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
1447+
public static final fun indexOfFirstInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)I
14471448
public static final fun indexOfFirstInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;)I
14481449
public static final fun indexOfFirstInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I
14491450
public static synthetic fun indexOfFirstInstructionReversed$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I
14501451
public static synthetic fun indexOfFirstInstructionReversed$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
1452+
public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)I
14511453
public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;)I
14521454
public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I
14531455
public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I

patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt

+19-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
1111
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
1212
import app.revanced.patches.youtube.misc.settings.settingsPatch
1313
import app.revanced.util.indexOfFirstInstructionOrThrow
14+
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
1415
import com.android.tools.smali.dexlib2.Opcode
1516

1617
private const val EXTENSION_CLASS_DESCRIPTOR =
@@ -105,7 +106,23 @@ val enableDebuggingPatch = bytecodePatch(
105106
)
106107
}
107108

108-
// There exists other experimental accessor methods for String, byte[], and wrappers for obfuscated classes,
109-
// but currently none of those are hooked.
109+
experimentalStringFeatureFlagFingerprint.match(
110+
experimentalFeatureFlagParentFingerprint.originalClassDef
111+
).method.apply {
112+
val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT)
113+
114+
addInstructions(
115+
insertIndex,
116+
"""
117+
move-result-object v0
118+
invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String;
119+
move-result-object v0
120+
return-object v0
121+
"""
122+
)
123+
}
124+
125+
// There exists other experimental accessor methods for byte[]
126+
// and wrappers for obfuscated classes, but currently none of those are hooked.
110127
}
111128
}

patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/Fingerprints.kt

+6
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@ internal val experimentalLongFeatureFlagFingerprint = fingerprint {
2828
parameters("J", "J")
2929
}
3030

31+
internal val experimentalStringFeatureFlagFingerprint = fingerprint {
32+
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
33+
returns("Ljava/lang/String;")
34+
parameters("J", "Ljava/lang/String;")
35+
}
36+

patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/Fingerprints.kt

+8-1
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,17 @@ internal val controlsOverlayVisibilityFingerprint = fingerprint {
4747
parameters("Z", "Z")
4848
}
4949

50-
internal val playerControlsExploderFeatureFlagFingerprint = fingerprint {
50+
internal val playerBottomControlsExploderFeatureFlagFingerprint = fingerprint {
5151
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
5252
returns("Z")
5353
parameters()
5454
literal { 45643739L }
5555
}
5656

57+
internal val playerTopControlsExperimentalLayoutFeatureFlagFingerprint = fingerprint {
58+
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
59+
returns("I")
60+
parameters()
61+
literal { 45629424L }
62+
}
63+

patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt

+28-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package app.revanced.patches.youtube.misc.playercontrols
22

33
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
4+
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
45
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
56
import app.revanced.patcher.patch.PatchException
67
import app.revanced.patcher.patch.bytecodePatch
@@ -10,6 +11,7 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
1011
import app.revanced.patches.shared.misc.mapping.get
1112
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
1213
import app.revanced.patches.shared.misc.mapping.resourceMappings
14+
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
1315
import app.revanced.patches.youtube.misc.playservice.is_19_35_or_greater
1416
import app.revanced.util.*
1517
import com.android.tools.smali.dexlib2.Opcode
@@ -263,12 +265,36 @@ val playerControlsPatch = bytecodePatch(
263265

264266
visibilityImmediateMethod = playerControlsExtensionHookFingerprint.method
265267

266-
// A/B test for a slightly different overlay controls,
268+
// A/B test for a slightly different bottom overlay controls,
267269
// that uses layout file youtube_video_exploder_controls_bottom_ui_container.xml
268270
// The change to support this is simple and only requires adding buttons to both layout files,
269271
// but for now force this different layout off since it's still an experimental test.
270272
if (is_19_35_or_greater) {
271-
playerControlsExploderFeatureFlagFingerprint.method.returnEarly()
273+
playerBottomControlsExploderFeatureFlagFingerprint.method.returnEarly()
274+
}
275+
276+
// A/B test of new top overlay controls. Two different layouts can be used:
277+
// youtube_cf_navigation_improvement_controls_layout.xml
278+
// youtube_cf_minimal_impact_controls_layout.xml
279+
//
280+
// Visually there is no noticeable difference between either of these compared to the default.
281+
// There is additional logic that is active when youtube_cf_navigation_improvement_controls_layout
282+
// is active, but what it does is not entirely clear.
283+
//
284+
// For now force this a/b feature off as it breaks the top player buttons.
285+
if (is_19_25_or_greater) {
286+
playerTopControlsExperimentalLayoutFeatureFlagFingerprint.method.apply {
287+
val index = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT_OBJECT)
288+
val register = getInstruction<OneRegisterInstruction>(index).registerA
289+
290+
addInstructions(
291+
index + 1,
292+
"""
293+
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getPlayerTopControlsLayoutResourceName(Ljava/lang/String;)Ljava/lang/String;
294+
move-result-object v$register
295+
"""
296+
)
297+
}
272298
}
273299
}
274300
}

patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt

+21
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,17 @@ fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, filter: Inst
309309
return instructions.indexOfLast(filter)
310310
}
311311

312+
/**
313+
* Get the index of matching instruction,
314+
* starting from the end of the method and searching down.
315+
*
316+
* @return -1 if the instruction is not found.
317+
*/
318+
fun Method.indexOfFirstInstructionReversed(targetOpcode: Opcode): Int =
319+
indexOfFirstInstructionReversed {
320+
opcode == targetOpcode
321+
}
322+
312323
/**
313324
* Get the index of matching instruction,
314325
* starting from and [startIndex] and searching down.
@@ -322,6 +333,16 @@ fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, targe
322333
opcode == targetOpcode
323334
}
324335

336+
/**
337+
* Get the index of matching instruction,
338+
* starting from the end of the method and searching down.
339+
*
340+
* @return -1 if the instruction is not found.
341+
*/
342+
fun Method.indexOfFirstInstructionReversedOrThrow(targetOpcode: Opcode): Int =
343+
indexOfFirstInstructionReversedOrThrow {
344+
opcode == targetOpcode
345+
}
325346
/**
326347
* Get the index of matching instruction,
327348
* starting from and [startIndex] and searching down.

0 commit comments

Comments
 (0)