From 8e1bee11132a38c629a7e218fe03c90ed5801767 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sat, 18 Jan 2025 20:25:19 +0900 Subject: [PATCH] feat(YouTube - Shorts components): Add `Open Shorts in regular player` setting --- .../youtube/patches/shorts/ShortsPatch.java | 36 +++++++++++++++ .../extension/youtube/settings/Settings.java | 1 + .../youtube/shorts/components/Fingerprints.kt | 38 +++++++++++++++ .../shorts/components/ShortsComponentPatch.kt | 46 +++++++++++++++++++ .../youtube/settings/host/values/strings.xml | 3 ++ .../youtube/settings/xml/revanced_prefs.xml | 3 +- 6 files changed, 126 insertions(+), 1 deletion(-) 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 5f799fdba..b610cad70 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 @@ -16,9 +16,11 @@ import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar; import java.lang.ref.WeakReference; +import app.revanced.extension.shared.utils.Logger; import app.revanced.extension.shared.utils.ResourceUtils; import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; import app.revanced.extension.youtube.shared.ShortsPlayerState; import app.revanced.extension.youtube.utils.VideoUtils; import kotlin.Unit; @@ -216,4 +218,38 @@ public class ShortsPatch { return !Settings.RESTORE_SHORTS_OLD_PLAYER_LAYOUT.get(); } + public static boolean openShortInRegularPlayer(String videoId) { + try { + if (!Settings.OPEN_SHORTS_IN_REGULAR_PLAYER.get()) { + return false; // Default unpatched behavior. + } + + if (videoId.isEmpty()) { + // Shorts was opened using launcher app shortcut. + // + // This check will not detect if the Shorts app shortcut is used + // while the app is running in the background (instead the regular player is opened). + // To detect that the hooked method map parameter can be checked + // if integer key 'com.google.android.apps.youtube.app.endpoint.flags' + // has bitmask 16 set. + // + // This use case seems unlikely if the user has the Shorts + // set to open in the regular player, so it's ignored as + // checking the map makes the patch more complicated. + Logger.printDebug(() -> "Ignoring Short with no videoId"); + return false; + } + + if (NavigationButton.getSelectedNavigationButton() == NavigationButton.SHORTS) { + return false; // Always use Shorts player for the Shorts nav button. + } + + VideoUtils.openVideo(videoId, true); + return true; + } catch (Exception ex) { + Logger.printException(() -> "openShortInRegularPlayer failure", ex); + return false; + } + } + } 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 c59c3bb6b..d0308ca88 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 @@ -433,6 +433,7 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_SHORTS_SHELF_HISTORY = new BooleanSetting("revanced_hide_shorts_shelf_history", TRUE); public static final EnumSetting CHANGE_SHORTS_BACKGROUND_REPEAT_STATE = new EnumSetting<>("revanced_change_shorts_background_repeat_state", ShortsLoopBehavior.UNKNOWN); public static final EnumSetting CHANGE_SHORTS_REPEAT_STATE = new EnumSetting<>("revanced_change_shorts_repeat_state", ShortsLoopBehavior.UNKNOWN); + public static final BooleanSetting OPEN_SHORTS_IN_REGULAR_PLAYER = new BooleanSetting("revanced_open_shorts_in_regular_player", FALSE); // PreferenceScreen: Shorts - Shorts player components public static final BooleanSetting HIDE_SHORTS_CHANNEL_BAR = new BooleanSetting("revanced_hide_shorts_channel_bar", FALSE); 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 3ebc3e3db..573c63f4a 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 @@ -21,6 +21,7 @@ import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import kotlin.collections.listOf internal val bottomSheetMenuListBuilderFingerprint = legacyFingerprint( name = "bottomSheetMenuListBuilderFingerprint", @@ -187,3 +188,40 @@ internal val shortsFullscreenFeatureFingerprint = legacyFingerprint( literals = listOf(FULLSCREEN_FEATURE_FLAG), ) +// Pre 19.25 +internal val shortsPlaybackIntentLegacyFingerprint = legacyFingerprint( + name = "shortsPlaybackIntentLegacyFingerprint", + returnType = "V", + parameters = listOf( + "L", + "Ljava/util/Map;", + "J", + "Ljava/lang/String;", + "Z", + "Ljava/util/Map;" + ), + strings = listOf( + // None of these strings are unique. + "com.google.android.apps.youtube.app.endpoint.flags", + "ReelWatchFragmentArgs", + "reels_fragment_descriptor" + ) +) + +internal val shortsPlaybackIntentFingerprint = legacyFingerprint( + name = "shortsPlaybackIntentFingerprint", + accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL, + returnType = "V", + parameters = listOf( + "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;", + "Ljava/util/Map;", + "J", + "Ljava/lang/String;" + ), + strings = listOf( + // None of these strings are unique. + "com.google.android.apps.youtube.app.endpoint.flags", + "ReelWatchFragmentArgs", + "reels_fragment_descriptor" + ) +) 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 76363b6df..d27ce1118 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 @@ -62,6 +62,9 @@ import app.revanced.patches.youtube.utils.toolbar.hookToolBar import app.revanced.patches.youtube.utils.toolbar.toolBarHookPatch import app.revanced.patches.youtube.video.information.hookShortsVideoInformation import app.revanced.patches.youtube.video.information.videoInformationPatch +import app.revanced.patches.youtube.video.playbackstart.PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR +import app.revanced.patches.youtube.video.playbackstart.playbackStartDescriptorPatch +import app.revanced.patches.youtube.video.playbackstart.playbackStartVideoIdReference import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId import app.revanced.patches.youtube.video.videoid.videoIdPatch import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT @@ -569,6 +572,8 @@ val shortsComponentPatch = bytecodePatch( shortsToolBarPatch, lithoFilterPatch, + navigationBarHookPatch, + playbackStartDescriptorPatch, playerTypeHookPatch, sharedResourceIdPatch, textComponentPatch, @@ -874,6 +879,47 @@ val shortsComponentPatch = bytecodePatch( // endregion + // region patch for open Shorts in regular player + + fun extensionInstructions(playbackStartRegister: Int, freeRegister: Int) = + """ + invoke-virtual { v$playbackStartRegister }, $playbackStartVideoIdReference + move-result-object v$freeRegister + invoke-static { v$freeRegister }, $SHORTS_CLASS_DESCRIPTOR->openShortInRegularPlayer(Ljava/lang/String;)Z + move-result v$freeRegister + if-eqz v$freeRegister, :disabled + return-void + :disabled + nop + """ + + if (!is_19_25_or_greater) { + shortsPlaybackIntentLegacyFingerprint.methodOrThrow().apply { + val index = indexOfFirstInstructionOrThrow { + getReference()?.returnType == PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR + } + val freeRegister = getInstruction(index).registerC + val playbackStartRegister = getInstruction(index + 1).registerA + + addInstructionsWithLabels( + index + 2, + extensionInstructions(playbackStartRegister, freeRegister) + ) + } + + return@execute + } + + shortsPlaybackIntentFingerprint.methodOrThrow().addInstructionsWithLabels( + 0, + """ + move-object/from16 v0, p1 + ${extensionInstructions(0, 1)} + """ + ) + + // endregion + addLithoFilter(BUTTON_FILTER_CLASS_DESCRIPTOR) addLithoFilter(SHELF_FILTER_CLASS_DESCRIPTOR) addLithoFilter(RETURN_YOUTUBE_CHANNEL_NAME_FILTER_CLASS_DESCRIPTOR) 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 ad4abd47e..b527e7274 100644 --- a/patches/src/main/resources/youtube/settings/host/values/strings.xml +++ b/patches/src/main/resources/youtube/settings/host/values/strings.xml @@ -1318,6 +1318,9 @@ Info: Default Pause Repeat + Open Shorts in regular player + Open Shorts in the regular player. + Do not open Shorts in the regular player. Shorts player 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 b07c083ab..81521f565 100644 --- a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -653,7 +653,8 @@ SETTINGS: SHORTS_REPEAT_STATE_BACKGROUND --> + + SETTINGS: SHORTS_COMPONENTS -->