diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsPatch.java index 1bf0f5bc5..ee598aacd 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsPatch.java @@ -56,20 +56,6 @@ public class ShortsPatch { NAVIGATION_BAR_HEIGHT_PERCENTAGE = heightPercentage / 100d; } - public static Enum repeat; - public static Enum singlePlay; - public static Enum endScreen; - - public static Enum changeShortsRepeatState(Enum currentState) { - switch (Settings.CHANGE_SHORTS_REPEAT_STATE.get()) { - case 1 -> currentState = repeat; - case 2 -> currentState = singlePlay; - case 3 -> currentState = endScreen; - } - - return currentState; - } - public static boolean disableResumingStartupShortsPlayer() { return Settings.DISABLE_RESUMING_SHORTS_PLAYER.get(); } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsRepeatStatePatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsRepeatStatePatch.java new file mode 100644 index 000000000..50933c1f7 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsRepeatStatePatch.java @@ -0,0 +1,101 @@ +package app.revanced.extension.youtube.patches.shorts; + +import android.app.Activity; + +import java.lang.ref.WeakReference; +import java.util.Objects; + +import app.revanced.extension.shared.utils.Logger; +import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.youtube.utils.ExtendedUtils; + +@SuppressWarnings("unused") +public class ShortsRepeatStatePatch { + + public enum ShortsLoopBehavior { + UNKNOWN, + /** + * Repeat the same Short forever! + */ + REPEAT, + /** + * Play once, then advanced to the next Short. + */ + SINGLE_PLAY, + /** + * Pause playback after 1 play. + */ + END_SCREEN; + + static void setYTEnumValue(Enum ytBehavior) { + for (ShortsLoopBehavior rvBehavior : values()) { + if (ytBehavior.name().endsWith(rvBehavior.name())) { + rvBehavior.ytEnumValue = ytBehavior; + + Logger.printDebug(() -> rvBehavior + " set to YT enum: " + ytBehavior.name()); + return; + } + } + + Logger.printException(() -> "Unknown Shorts loop behavior: " + ytBehavior.name()); + } + + /** + * YouTube enum value of the obfuscated enum type. + */ + private Enum ytEnumValue; + } + + private static WeakReference mainActivityRef = new WeakReference<>(null); + + + public static void setMainActivity(Activity activity) { + mainActivityRef = new WeakReference<>(activity); + } + + /** + * @return If the app is currently in background PiP mode. + */ + private static boolean isAppInBackgroundPiPMode() { + Activity activity = mainActivityRef.get(); + return activity != null && activity.isInPictureInPictureMode(); + } + + /** + * Injection point. + */ + public static void setYTShortsRepeatEnum(Enum ytEnum) { + try { + for (Enum ytBehavior : Objects.requireNonNull(ytEnum.getClass().getEnumConstants())) { + ShortsLoopBehavior.setYTEnumValue(ytBehavior); + } + } catch (Exception ex) { + Logger.printException(() -> "setYTShortsRepeatEnum failure", ex); + } + } + + /** + * Injection point. + */ + public static Enum changeShortsRepeatBehavior(Enum original) { + try { + final ShortsLoopBehavior behavior = ExtendedUtils.IS_19_34_OR_GREATER && + isAppInBackgroundPiPMode() + ? Settings.CHANGE_SHORTS_BACKGROUND_REPEAT_STATE.get() + : Settings.CHANGE_SHORTS_REPEAT_STATE.get(); + + if (behavior != ShortsLoopBehavior.UNKNOWN && behavior.ytEnumValue != null) { + Logger.printDebug(() -> behavior.ytEnumValue == original + ? "Changing Shorts repeat behavior from: " + original.name() + " to: " + behavior.ytEnumValue + : "Behavior setting is same as original. Using original: " + original.name() + ); + + return behavior.ytEnumValue; + } + } catch (Exception ex) { + Logger.printException(() -> "changeShortsRepeatState failure", ex); + } + + return original; + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 466fd03eb..a5863d1c7 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -39,6 +39,7 @@ import app.revanced.extension.youtube.patches.general.MiniplayerPatch; import app.revanced.extension.youtube.patches.general.YouTubeMusicActionsPatch; import app.revanced.extension.youtube.patches.misc.WatchHistoryPatch.WatchHistoryType; import app.revanced.extension.youtube.patches.shorts.AnimationFeedbackPatch.AnimationType; +import app.revanced.extension.youtube.patches.shorts.ShortsRepeatStatePatch.ShortsLoopBehavior; import app.revanced.extension.youtube.patches.utils.PatchStatus; import app.revanced.extension.youtube.shared.PlaylistIdPrefix; import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings; @@ -426,7 +427,8 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_SHORTS_SHELF_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_shorts_shelf_subscriptions", TRUE); public static final BooleanSetting HIDE_SHORTS_SHELF_SEARCH = new BooleanSetting("revanced_hide_shorts_shelf_search", TRUE); public static final BooleanSetting HIDE_SHORTS_SHELF_HISTORY = new BooleanSetting("revanced_hide_shorts_shelf_history", TRUE); - public static final IntegerSetting CHANGE_SHORTS_REPEAT_STATE = new IntegerSetting("revanced_change_shorts_repeat_state", 0); + public static final EnumSetting CHANGE_SHORTS_REPEAT_STATE = new EnumSetting<>("revanced_change_shorts_repeat_state", ShortsLoopBehavior.UNKNOWN); + public static final EnumSetting CHANGE_SHORTS_BACKGROUND_REPEAT_STATE = new EnumSetting<>("revanced_change_shorts_background_repeat_state", ShortsLoopBehavior.UNKNOWN); // PreferenceScreen: Shorts - Shorts player components public static final BooleanSetting HIDE_SHORTS_JOIN_BUTTON = new BooleanSetting("revanced_hide_shorts_join_button", TRUE); diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/Fingerprints.kt index 43b8900b9..7f281b150 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/Fingerprints.kt @@ -56,6 +56,7 @@ internal val reelEnumConstructorFingerprint = legacyFingerprint( name = "reelEnumConstructorFingerprint", returnType = "V", strings = listOf( + "REEL_LOOP_BEHAVIOR_UNKNOWN", "REEL_LOOP_BEHAVIOR_SINGLE_PLAY", "REEL_LOOP_BEHAVIOR_REPEAT", "REEL_LOOP_BEHAVIOR_END_SCREEN" diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt index e5dc79cab..9531d723f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt @@ -13,6 +13,7 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.litho.addLithoFilter import app.revanced.patches.shared.litho.lithoFilterPatch +import app.revanced.patches.shared.mainactivity.injectOnCreateMethodCall import app.revanced.patches.shared.textcomponent.hookSpannableString import app.revanced.patches.shared.textcomponent.textComponentPatch import app.revanced.patches.youtube.utils.bottomSheetMenuItemBuilderFingerprint @@ -24,16 +25,17 @@ import app.revanced.patches.youtube.utils.extension.Constants.UTILS_PATH import app.revanced.patches.youtube.utils.indexOfSpannedCharSequenceInstruction import app.revanced.patches.youtube.utils.lottie.LOTTIE_ANIMATION_VIEW_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.lottie.lottieAnimationViewHookPatch +import app.revanced.patches.youtube.utils.mainactivity.mainActivityResolvePatch import app.revanced.patches.youtube.utils.navigation.addBottomBarContainerHook import app.revanced.patches.youtube.utils.navigation.navigationBarHookPatch import app.revanced.patches.youtube.utils.patch.PatchList.SHORTS_COMPONENTS import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch import app.revanced.patches.youtube.utils.playservice.is_18_31_or_greater import app.revanced.patches.youtube.utils.playservice.is_18_34_or_greater -import app.revanced.patches.youtube.utils.playservice.is_18_49_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_02_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_28_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_34_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.recyclerview.bottomSheetRecyclerViewHook import app.revanced.patches.youtube.utils.recyclerview.bottomSheetRecyclerViewPatch @@ -67,6 +69,7 @@ import app.revanced.util.cloneMutable import app.revanced.util.copyResources import app.revanced.util.findMethodOrThrow import app.revanced.util.findMutableMethodOf +import app.revanced.util.fingerprint.definingClassOrThrow import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall import app.revanced.util.fingerprint.matchOrThrow import app.revanced.util.fingerprint.methodOrThrow @@ -368,25 +371,34 @@ private val shortsNavigationBarPatch = bytecodePatch( } } +private const val EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR = + "$SHORTS_PATH/ShortsRepeatStatePatch;" + private val shortsRepeatPatch = bytecodePatch( description = "shortsRepeatPatch" ) { execute { - reelEnumConstructorFingerprint.methodOrThrow().apply { - arrayOf( - "REEL_LOOP_BEHAVIOR_END_SCREEN" to "endScreen", - "REEL_LOOP_BEHAVIOR_REPEAT" to "repeat", - "REEL_LOOP_BEHAVIOR_SINGLE_PLAY" to "singlePlay" - ).map { (enumName, fieldName) -> - val stringIndex = indexOfFirstStringInstructionOrThrow(enumName) - val insertIndex = indexOfFirstInstructionOrThrow(stringIndex, Opcode.SPUT_OBJECT) - val insertRegister = getInstruction(insertIndex).registerA + dependsOn(mainActivityResolvePatch) - addInstruction( - insertIndex + 1, - "sput-object v$insertRegister, $SHORTS_CLASS_DESCRIPTOR->$fieldName:Ljava/lang/Enum;" - ) - } + injectOnCreateMethodCall( + EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR, + "setMainActivity" + ) + + val reelEnumClass = reelEnumConstructorFingerprint.definingClassOrThrow() + + reelEnumConstructorFingerprint.methodOrThrow().apply { + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID) + + addInstructions( + insertIndex, + """ + # Pass the first enum value to extension. + # Any enum value of this type will work. + sget-object v0, $reelEnumClass->a:$reelEnumClass + invoke-static { v0 }, $EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR->setYTShortsRepeatEnum(Ljava/lang/Enum;)V + """, + ) val endScreenStringIndex = indexOfFirstStringInstructionOrThrow("REEL_LOOP_BEHAVIOR_END_SCREEN") @@ -426,7 +438,7 @@ private val shortsRepeatPatch = bytecodePatch( addInstructions( index + 2, """ - invoke-static {v$register}, $SHORTS_CLASS_DESCRIPTOR->changeShortsRepeatState(Ljava/lang/Enum;)Ljava/lang/Enum; + invoke-static {v$register}, $EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR->changeShortsRepeatBehavior(Ljava/lang/Enum;)Ljava/lang/Enum; move-result-object v$register """ ) @@ -617,6 +629,10 @@ val shortsComponentPatch = bytecodePatch( settingArray += "SETTINGS: SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU" } + if (is_19_34_or_greater) { + settingArray += "SETTINGS: SHORTS_REPEAT_STATE_BACKGROUND" + } + // region patch for hide comments button (non-litho) shortsButtonFingerprint.hideButton(rightComment, "hideShortsCommentsButton", false) diff --git a/patches/src/main/resources/youtube/settings/host/values/arrays.xml b/patches/src/main/resources/youtube/settings/host/values/arrays.xml index 8420fed1d..f5b9235ed 100644 --- a/patches/src/main/resources/youtube/settings/host/values/arrays.xml +++ b/patches/src/main/resources/youtube/settings/host/values/arrays.xml @@ -81,10 +81,10 @@ @string/revanced_change_shorts_repeat_state_entry_pause - 0 - 1 - 2 - 3 + UNKNOWN + REPEAT + SINGLE_PLAY + END_SCREEN @string/quality_auto diff --git a/patches/src/main/resources/youtube/settings/host/values/strings.xml b/patches/src/main/resources/youtube/settings/host/values/strings.xml index b6c9bc6a7..7f4d6fef1 100644 --- a/patches/src/main/resources/youtube/settings/host/values/strings.xml +++ b/patches/src/main/resources/youtube/settings/host/values/strings.xml @@ -1238,6 +1238,7 @@ Info: Change Shorts repeat state + Change Shorts background repeat state Autoplay Default Pause diff --git a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml index b37c2b4fa..772e0cb2a 100644 --- a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -634,6 +634,9 @@ SETTINGS: SHORTS_COMPONENTS --> + +