From 96b5aede482f7a69d6df17864a2e17568b0da880 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 21 Oct 2024 02:53:09 -0400 Subject: [PATCH] feat(YouTube): Add `Shorts autoplay` patch (#3794) --- api/revanced-patches.api | 6 + .../HideShortsComponentsResourcePatch.kt | 79 +++++++----- .../shortsautoplay/ShortsAutoplayPatch.kt | 119 ++++++++++++++++++ .../ReelEnumConstructorFingerprint.kt | 18 +++ .../ReelPlaybackRepeatFingerprint.kt | 9 ++ .../misc/playservice/VersionCheckPatch.kt | 2 + .../youtube/misc/settings/SettingsPatch.kt | 1 - .../resources/addresources/values/strings.xml | 10 ++ 8 files changed, 209 insertions(+), 35 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/fingerprints/ReelEnumConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/fingerprints/ReelPlaybackRepeatFingerprint.kt diff --git a/api/revanced-patches.api b/api/revanced-patches.api index ed2f78f371..152dc8346f 100644 --- a/api/revanced-patches.api +++ b/api/revanced-patches.api @@ -1832,6 +1832,12 @@ public final class app/revanced/patches/youtube/layout/seekbar/RestoreOldSeekbar public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V } +public final class app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch : app/revanced/patcher/patch/BytecodePatch { + public static final field INSTANCE Lapp/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch; + public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V + public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V +} + public final class app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch : app/revanced/patcher/patch/BytecodePatch { public static final field INSTANCE Lapp/revanced/patches/youtube/layout/sponsorblock/SponsorBlockBytecodePatch; public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsResourcePatch.kt index 7c1f10ccc4..c548cf2f18 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsResourcePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsResourcePatch.kt @@ -5,6 +5,8 @@ import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.all.misc.resources.AddResourcesPatch import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.layout.hide.shorts.HideShortsComponentsPatch.hideShortsAppShortcut import app.revanced.patches.youtube.layout.hide.shorts.HideShortsComponentsPatch.hideShortsWidget @@ -34,40 +36,49 @@ object HideShortsComponentsResourcePatch : ResourcePatch() { SwitchPreference("revanced_hide_shorts_subscriptions"), SwitchPreference("revanced_hide_shorts_search"), - // Shorts player components. - // Ideally each group should be ordered similar to how they appear in the UI - // since this Setting menu currently uses the ordering used here. - - // Vertical row of buttons on right side of the screen. - SwitchPreference("revanced_hide_shorts_like_fountain"), - SwitchPreference("revanced_hide_shorts_like_button"), - SwitchPreference("revanced_hide_shorts_dislike_button"), - SwitchPreference("revanced_hide_shorts_comments_button"), - SwitchPreference("revanced_hide_shorts_share_button"), - SwitchPreference("revanced_hide_shorts_remix_button"), - SwitchPreference("revanced_hide_shorts_sound_button"), - - // Everything else. - SwitchPreference("revanced_hide_shorts_join_button"), - SwitchPreference("revanced_hide_shorts_subscribe_button"), - SwitchPreference("revanced_hide_shorts_paused_overlay_buttons"), - SwitchPreference("revanced_hide_shorts_save_sound_button"), - SwitchPreference("revanced_hide_shorts_use_template_button"), - SwitchPreference("revanced_hide_shorts_upcoming_button"), - SwitchPreference("revanced_hide_shorts_green_screen_button"), - SwitchPreference("revanced_hide_shorts_hashtag_button"), - SwitchPreference("revanced_hide_shorts_shop_button"), - SwitchPreference("revanced_hide_shorts_tagged_products"), - SwitchPreference("revanced_hide_shorts_stickers"), - SwitchPreference("revanced_hide_shorts_search_suggestions"), - SwitchPreference("revanced_hide_shorts_super_thanks_button"), - SwitchPreference("revanced_hide_shorts_location_label"), - SwitchPreference("revanced_hide_shorts_channel_bar"), - SwitchPreference("revanced_hide_shorts_info_panel"), - SwitchPreference("revanced_hide_shorts_full_video_link_label"), - SwitchPreference("revanced_hide_shorts_video_title"), - SwitchPreference("revanced_hide_shorts_sound_metadata_label"), - SwitchPreference("revanced_hide_shorts_navigation_bar"), + PreferenceScreen( + key = "revanced_shorts_player_screen", + sorting = Sorting.UNSORTED, + preferences = setOf( + // Shorts player components. + // Ideally each group should be ordered similar to how they appear in the UI + + // Vertical row of buttons on right side of the screen. + SwitchPreference("revanced_hide_shorts_like_fountain"), + SwitchPreference("revanced_hide_shorts_like_button"), + SwitchPreference("revanced_hide_shorts_dislike_button"), + SwitchPreference("revanced_hide_shorts_comments_button"), + SwitchPreference("revanced_hide_shorts_share_button"), + SwitchPreference("revanced_hide_shorts_remix_button"), + SwitchPreference("revanced_hide_shorts_sound_button"), + + // Upper and middle area of the player. + SwitchPreference("revanced_hide_shorts_join_button"), + SwitchPreference("revanced_hide_shorts_subscribe_button"), + SwitchPreference("revanced_hide_shorts_paused_overlay_buttons"), + + // Suggested actions. + SwitchPreference("revanced_hide_shorts_save_sound_button"), + SwitchPreference("revanced_hide_shorts_use_template_button"), + SwitchPreference("revanced_hide_shorts_upcoming_button"), + SwitchPreference("revanced_hide_shorts_green_screen_button"), + SwitchPreference("revanced_hide_shorts_hashtag_button"), + SwitchPreference("revanced_hide_shorts_shop_button"), + SwitchPreference("revanced_hide_shorts_tagged_products"), + SwitchPreference("revanced_hide_shorts_search_suggestions"), + SwitchPreference("revanced_hide_shorts_super_thanks_button"), + SwitchPreference("revanced_hide_shorts_stickers"), + + // Bottom of the screen. + SwitchPreference("revanced_hide_shorts_location_label"), + SwitchPreference("revanced_hide_shorts_channel_bar"), + SwitchPreference("revanced_hide_shorts_info_panel"), + SwitchPreference("revanced_hide_shorts_full_video_link_label"), + SwitchPreference("revanced_hide_shorts_video_title"), + SwitchPreference("revanced_hide_shorts_sound_metadata_label"), + SwitchPreference("revanced_hide_shorts_navigation_bar"), + ) + ) ) if (hideShortsAppShortcut == true) { diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt new file mode 100644 index 0000000000..c8be358972 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/ShortsAutoplayPatch.kt @@ -0,0 +1,119 @@ +package app.revanced.patches.youtube.layout.shortsautoplay + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotation.CompatiblePackage +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patches.all.misc.resources.AddResourcesPatch +import app.revanced.patches.all.misc.resources.AddResourcesPatch.invoke +import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.patches.youtube.layout.shortsautoplay.fingerprints.ReelEnumConstructorFingerprint +import app.revanced.patches.youtube.layout.shortsautoplay.fingerprints.ReelPlaybackRepeatFingerprint +import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch +import app.revanced.patches.youtube.misc.playservice.VersionCheckPatch +import app.revanced.patches.youtube.misc.settings.SettingsPatch +import app.revanced.patches.youtube.shared.fingerprints.MainActivityOnCreateFingerprint +import app.revanced.util.findOpcodeIndicesReversed +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +@Patch( + name = "Shorts autoplay", + description = "Adds options to automatically play the next Short.", + dependencies = [ + IntegrationsPatch::class, + SettingsPatch::class, + ResourceMappingPatch::class, + VersionCheckPatch::class, + ], + compatiblePackages = [ + CompatiblePackage( + "com.google.android.youtube", + [ + "18.49.37", + "19.16.39", + "19.25.37", + "19.34.42", + ], + ), + ], +) +@Suppress("unused") +object ShortsAutoplayPatch : BytecodePatch( + setOf( + MainActivityOnCreateFingerprint, + ReelEnumConstructorFingerprint, + ReelPlaybackRepeatFingerprint + ) +) { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = + "Lapp/revanced/integrations/youtube/patches/ShortsAutoplayPatch;" + + override fun execute(context: BytecodeContext) { + AddResourcesPatch(this::class) + + SettingsPatch.PreferenceScreen.SHORTS.addPreferences( + SwitchPreference("revanced_shorts_autoplay") + ) + + if (VersionCheckPatch.is_19_34_or_greater) { + SettingsPatch.PreferenceScreen.SHORTS.addPreferences( + SwitchPreference("revanced_shorts_autoplay_background") + ) + } + + // Main activity is used to check if app is in pip mode. + MainActivityOnCreateFingerprint.resultOrThrow().mutableMethod.addInstructions( + 0, + "invoke-static/range { p0 .. p0 }, $INTEGRATIONS_CLASS_DESCRIPTOR->" + + "setMainActivity(Landroid/app/Activity;)V", + ) + + val reelEnumClass: String + + ReelEnumConstructorFingerprint.resultOrThrow().let { + reelEnumClass = it.classDef.type + + it.mutableMethod.apply { + val insertIndex = it.scanResult.patternScanResult!!.startIndex + + addInstructions( + insertIndex, + """ + # Pass the first enum value to integrations. + # Any enum value of this type will work. + sget-object v0, $reelEnumClass->a:$reelEnumClass + invoke-static { v0 }, $INTEGRATIONS_CLASS_DESCRIPTOR->setYTShortsRepeatEnum(Ljava/lang/Enum;)V + """ + ) + } + } + + ReelPlaybackRepeatFingerprint.resultOrThrow().mutableMethod.apply { + // The behavior enums are looked up from an ordinal value to an enum type. + findOpcodeIndicesReversed { + val reference = getReference() + reference?.definingClass == reelEnumClass + && reference.parameterTypes.firstOrNull() == "I" + && reference.returnType == reelEnumClass + }.forEach { index -> + val register = getInstruction(index + 1).registerA + + addInstructions( + index + 2, + """ + invoke-static {v$register}, $INTEGRATIONS_CLASS_DESCRIPTOR->changeShortsRepeatBehavior(Ljava/lang/Enum;)Ljava/lang/Enum; + move-result-object v$register + """ + ) + } + } + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/fingerprints/ReelEnumConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/fingerprints/ReelEnumConstructorFingerprint.kt new file mode 100644 index 0000000000..b9bc8bfed2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/fingerprints/ReelEnumConstructorFingerprint.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.youtube.layout.shortsautoplay.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import kotlin.collections.listOf + +internal object ReelEnumConstructorFingerprint : MethodFingerprint( + accessFlags = AccessFlags.STATIC or AccessFlags.CONSTRUCTOR, + opcodes = listOf(Opcode.RETURN_VOID), + strings = listOf( + "REEL_LOOP_BEHAVIOR_UNKNOWN", + "REEL_LOOP_BEHAVIOR_SINGLE_PLAY", + "REEL_LOOP_BEHAVIOR_REPEAT", + "REEL_LOOP_BEHAVIOR_END_SCREEN" + ) +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/fingerprints/ReelPlaybackRepeatFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/fingerprints/ReelPlaybackRepeatFingerprint.kt new file mode 100644 index 0000000000..793516be97 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/shortsautoplay/fingerprints/ReelPlaybackRepeatFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.youtube.layout.shortsautoplay.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint + +internal object ReelPlaybackRepeatFingerprint : MethodFingerprint( + parameters = listOf("L"), + returnType = "V", + strings = listOf("YoutubePlayerState is in throwing an Error.") +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt index 5ef04463c6..4a704ca541 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt @@ -20,6 +20,7 @@ internal object VersionCheckPatch : ResourcePatch() { var is_19_29_or_greater by Delegates.notNull() var is_19_32_or_greater by Delegates.notNull() var is_19_33_or_greater by Delegates.notNull() + var is_19_34_or_greater by Delegates.notNull() var is_19_36_or_greater by Delegates.notNull() var is_19_41_or_greater by Delegates.notNull() @@ -46,6 +47,7 @@ internal object VersionCheckPatch : ResourcePatch() { is_19_29_or_greater = 243005000 <= playStoreServicesVersion is_19_32_or_greater = 243199000 <= playStoreServicesVersion is_19_33_or_greater = 243405000 <= playStoreServicesVersion + is_19_34_or_greater = 243499000 <= playStoreServicesVersion is_19_36_or_greater = 243705000 <= playStoreServicesVersion is_19_41_or_greater = 244305000 <= playStoreServicesVersion } diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt index ade624eaf9..2cc3142ff9 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt @@ -151,7 +151,6 @@ object SettingsPatch : val SHORTS = Screen( key = "revanced_settings_screen_06_shorts", summaryKey = null, - sorting = Sorting.UNSORTED, ) // Don't sort, because title sorting scatters the custom color preferences. val SEEKBAR = Screen( diff --git a/src/main/resources/addresources/values/strings.xml b/src/main/resources/addresources/values/strings.xml index 712d8d2fdf..d08ae90ea1 100644 --- a/src/main/resources/addresources/values/strings.xml +++ b/src/main/resources/addresources/values/strings.xml @@ -602,6 +602,8 @@ This is because Crowdin requires temporarily flattening this file and removing t Thumbnail seekbar is shown + Shorts player + Hide or show components in the Shorts player Hide Shorts in home feed Shorts in home feed are hidden @@ -997,6 +999,14 @@ This is because Crowdin requires temporarily flattening this file and removing t Shorts player will not resume on app startup Shorts player will resume on app startup + + Autoplay Shorts + Shorts will autoplay + Shorts will repeat + Autoplay Shorts background play + Shorts background play will autoplay + Shorts background play will repeat + Enable tablet layout Tablet layout is enabled