diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java index 4e63e359f..f2003f6c0 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java @@ -523,6 +523,11 @@ public class Utils { return currentNightMode == Configuration.UI_MODE_NIGHT_YES; } + public static boolean isLandscapeOrientation() { + final int orientation = context.getResources().getConfiguration().orientation; + return orientation == Configuration.ORIENTATION_LANDSCAPE; + } + /** * Automatically logs any exceptions the runnable throws. * @@ -595,7 +600,7 @@ public class Utils { || networkType == NetworkType.OTHER; } - @SuppressLint("MissingPermission") // permission already included in YouTube + @SuppressLint({"MissingPermission", "deprecation"}) // Permission already included in YouTube. public static NetworkType getNetworkType() { Context networkContext = getContext(); if (networkContext == null) { diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ExitFullscreenPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ExitFullscreenPatch.java new file mode 100644 index 000000000..7a330df0c --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ExitFullscreenPatch.java @@ -0,0 +1,49 @@ +package app.revanced.extension.youtube.patches; + +import android.widget.ImageView; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; +import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.youtube.shared.PlayerType; + +@SuppressWarnings("unused") +public class ExitFullscreenPatch { + + public enum FullscreenMode { + DISABLED, + PORTRAIT, + LANDSCAPE, + PORTRAIT_LANDSCAPE, + } + + /** + * Injection point. + */ + public static void endOfVideoReached() { + try { + FullscreenMode mode = Settings.EXIT_FULLSCREEN.get(); + if (mode == FullscreenMode.DISABLED) { + return; + } + + if (PlayerType.getCurrent() == PlayerType.WATCH_WHILE_FULLSCREEN) { + if (Utils.isLandscapeOrientation()) { + if (mode == FullscreenMode.PORTRAIT) { + return; + } + } else if (mode == FullscreenMode.LANDSCAPE) { + return; + } + + ImageView fullscreenButton = PlayerControlsPatch.fullscreenButtonRef.get(); + if (fullscreenButton != null) { + Logger.printDebug(() -> "Clicking fullscreen button"); + fullscreenButton.performClick(); + } + } + } catch (Exception ex) { + Logger.printException(() -> "endOfVideoReached failure", ex); + } + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerControlsPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerControlsPatch.java index 9ce74d118..1cd9b508d 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerControlsPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerControlsPatch.java @@ -4,15 +4,29 @@ import android.view.View; import android.view.ViewTreeObserver; import android.widget.ImageView; +import java.lang.ref.WeakReference; + import app.revanced.extension.shared.Logger; @SuppressWarnings("unused") public class PlayerControlsPatch { + public static WeakReference fullscreenButtonRef = new WeakReference<>(null); + + private static boolean fullscreenButtonVisibilityCallbacksExist() { + return false; // Modified during patching if needed. + } + /** * Injection point. */ public static void setFullscreenCloseButton(ImageView imageButton) { + fullscreenButtonRef = new WeakReference<>(imageButton); + + if (!fullscreenButtonVisibilityCallbacksExist()) { + return; + } + // Add a global listener, since the protected method // View#onVisibilityChanged() does not have any call backs. imageButton.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @@ -39,7 +53,7 @@ public class PlayerControlsPatch { } // noinspection EmptyMethod - public static void fullscreenButtonVisibilityChanged(boolean isVisible) { + private static void fullscreenButtonVisibilityChanged(boolean isVisible) { // Code added during patching. } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index dfaac87b9..841c4fb63 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -8,6 +8,7 @@ import static app.revanced.extension.shared.settings.Setting.migrateOldSettingTo import static app.revanced.extension.shared.settings.Setting.parent; import static app.revanced.extension.shared.settings.Setting.parentsAny; import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage; +import static app.revanced.extension.youtube.patches.ExitFullscreenPatch.FullscreenMode; import static app.revanced.extension.youtube.patches.ForceOriginalAudioPatch.ForceOriginalAudioAvailability; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideExpandCloseAvailability; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability; @@ -120,6 +121,7 @@ public class Settings extends BaseSettings { public static final BooleanSetting DISABLE_LIKE_SUBSCRIBE_GLOW = new BooleanSetting("revanced_disable_like_subscribe_glow", FALSE); public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE); public static final BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE, true); + public static final EnumSetting EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED); public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true); public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE); public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true); @@ -139,10 +141,10 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE); public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE); public static final BooleanSetting HIDE_VIDEO_CHANNEL_WATERMARK = new BooleanSetting("revanced_hide_channel_watermark", TRUE); - public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE); - public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE); - public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true); public static final BooleanSetting OPEN_VIDEOS_FULLSCREEN_PORTRAIT = new BooleanSetting("revanced_open_videos_fullscreen_portrait", FALSE); + public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE); + public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true); + public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE); // Miniplayer public static final EnumSetting MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.DEFAULT, true); private static final Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt index 05edee353..5964e5fe6 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt @@ -73,7 +73,7 @@ enum class PlayerType { onChange(currentPlayerType) } - @Volatile // value is read/write from different threads + @Volatile // Read/write from different threads. private var currentPlayerType = NONE /** diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/VideoState.kt b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/VideoState.kt index d1506aa14..3c0da9552 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/VideoState.kt +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/VideoState.kt @@ -46,6 +46,7 @@ enum class VideoState { currentVideoState = value } + @Volatile // Read/write from different threads. private var currentVideoState: VideoState? = null } } diff --git a/patches/api/patches.api b/patches/api/patches.api index b76994c3a..e8e7cfc70 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -461,10 +461,6 @@ public final class app/revanced/patches/reddit/customclients/joeyforreddit/detec public static final fun getDisablePiracyDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } -public final class app/revanced/patches/reddit/customclients/redditisfun/api/FingerprintsKt { - public static final fun baseClientIdFingerprint (Ljava/lang/String;)Lapp/revanced/patcher/Fingerprint; -} - public final class app/revanced/patches/reddit/customclients/redditisfun/api/SpoofClientPatchKt { public static final fun getSpoofClientPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } @@ -548,7 +544,6 @@ public final class app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentP } public final class app/revanced/patches/shared/misc/extension/ExtensionHook { - public final fun getFingerprint ()Lapp/revanced/patcher/Fingerprint; public final fun invoke (Lapp/revanced/patcher/patch/BytecodePatchContext;Ljava/lang/String;)V } @@ -1268,10 +1263,6 @@ public final class app/revanced/patches/youtube/misc/backgroundplayback/Backgrou public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } -public final class app/revanced/patches/youtube/misc/check/CheckEnvironmentPatchKt { - public static final fun getCheckEnvironmentPatch ()Lapp/revanced/patcher/patch/BytecodePatch; -} - public final class app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatchKt { public static final fun getEnableDebuggingPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } @@ -1408,10 +1399,6 @@ public final class app/revanced/patches/youtube/misc/zoomhaptics/ZoomHapticsPatc public static final fun getZoomHapticsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } -public final class app/revanced/patches/youtube/shared/FingerprintsKt { - public static final fun getRollingNumberTextViewAnimationUpdateFingerprint ()Lapp/revanced/patcher/Fingerprint; -} - public final class app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatchKt { public static final fun getForceOriginalAudioPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/customclients/redditisfun/api/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/customclients/redditisfun/api/Fingerprints.kt index 14d0f4766..5b3029094 100644 --- a/patches/src/main/kotlin/app/revanced/patches/reddit/customclients/redditisfun/api/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/reddit/customclients/redditisfun/api/Fingerprints.kt @@ -4,7 +4,7 @@ import app.revanced.patcher.fingerprint import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode -fun baseClientIdFingerprint(string: String) = fingerprint { +internal fun baseClientIdFingerprint(string: String) = fingerprint { strings("yyOCBp.RHJhDKd", string) } diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt index cfcf9d0b1..5233f186f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt @@ -92,7 +92,7 @@ fun sharedExtensionPatch( } class ExtensionHook internal constructor( - val fingerprint: Fingerprint, + private val fingerprint: Fingerprint, private val insertIndexResolver: ((Method) -> Int), private val contextRegisterResolver: (Method) -> String, ) { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt new file mode 100644 index 000000000..d1d93748d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/ExitFullscreenPatch.kt @@ -0,0 +1,65 @@ +package app.revanced.patches.youtube.layout.player.fullscreen + +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.misc.settings.preference.ListPreference +import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +import app.revanced.patches.youtube.misc.playercontrols.playerControlsPatch +import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch +import app.revanced.patches.youtube.misc.settings.PreferenceScreen +import app.revanced.patches.youtube.misc.settings.settingsPatch +import app.revanced.patches.youtube.shared.autoRepeatFingerprint +import app.revanced.patches.youtube.shared.autoRepeatParentFingerprint + +@Suppress("unused") +internal val exitFullscreenPatch = bytecodePatch( + name = "Exit fullscreen mode", + description = "Adds options to automatically exit fullscreen mode when a video reaches the end." +) { + + compatibleWith( + "com.google.android.youtube"( + "18.38.44", + "18.49.37", + "19.16.39", + "19.25.37", + "19.34.42", + "19.43.41", + "19.45.38", + "19.46.42", + "19.47.53", + ) + ) + + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + playerTypeHookPatch, + playerControlsPatch + ) + + // Cannot declare as top level since this patch is in the same package as + // other patches that declare same constant name with internal visibility. + @Suppress("LocalVariableName") + val EXTENSION_CLASS_DESCRIPTOR = + "Lapp/revanced/extension/youtube/patches/ExitFullscreenPatch;" + + execute { + addResources("youtube", "layout.player.fullscreen.exitFullscreenPatch") + + PreferenceScreen.PLAYER.addPreferences( + ListPreference( + "revanced_exit_fullscreen", + summaryKey = null, + ) + ) + + autoRepeatFingerprint.match(autoRepeatParentFingerprint.originalClassDef).method.addInstruction( + 0, + "invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->endOfVideoReached()V", + ) + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/check/CheckEnvironmentPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/check/CheckEnvironmentPatch.kt index 6b389625c..9624721bb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/check/CheckEnvironmentPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/check/CheckEnvironmentPatch.kt @@ -4,7 +4,7 @@ import app.revanced.patches.shared.misc.checks.checkEnvironmentPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint -val checkEnvironmentPatch = checkEnvironmentPatch( +internal val checkEnvironmentPatch = checkEnvironmentPatch( mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint, extensionPatch = sharedExtensionPatch, "com.google.android.youtube", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/Fingerprints.kt index 824022590..95dbd54ec 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/Fingerprints.kt @@ -12,13 +12,23 @@ internal val playerTopControlsInflateFingerprint = fingerprint { literal { controlsLayoutStub } } +internal val playerControlsExtensionHookListenersExistFingerprint = fingerprint { + accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC) + returns("Z") + parameters() + custom { methodDef, classDef -> + methodDef.name == "fullscreenButtonVisibilityCallbacksExist" && + classDef.type == EXTENSION_CLASS_DESCRIPTOR + } +} + internal val playerControlsExtensionHookFingerprint = fingerprint { - accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) + accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC) returns("V") parameters("Z") custom { methodDef, classDef -> methodDef.name == "fullscreenButtonVisibilityChanged" && - classDef.type == "Lapp/revanced/extension/youtube/patches/PlayerControlsPatch;" + classDef.type == EXTENSION_CLASS_DESCRIPTOR } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt index f167a3bd5..6beeaf242 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt @@ -189,13 +189,18 @@ fun injectVisibilityCheckCall(descriptor: String) { "invoke-static { p1 , p2 }, $descriptor->changeVisibility(ZZ)V", ) + if (!visibilityImmediateCallbacksExistModified) { + visibilityImmediateCallbacksExistModified = true + visibilityImmediateCallbacksExistMethod.returnEarly(true) + } + visibilityImmediateMethod.addInstruction( visibilityImmediateInsertIndex++, "invoke-static { p0 }, $descriptor->changeVisibilityImmediate(Z)V", ) } -private const val EXTENSION_CLASS_DESCRIPTOR = +internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/PlayerControlsPatch;" private lateinit var inflateTopControlMethod: MutableMethod @@ -209,6 +214,9 @@ private var inflateBottomControlRegister: Int = -1 private lateinit var visibilityMethod: MutableMethod private var visibilityInsertIndex: Int = 0 +private var visibilityImmediateCallbacksExistModified = false +private lateinit var visibilityImmediateCallbacksExistMethod : MutableMethod + private lateinit var visibilityImmediateMethod: MutableMethod private var visibilityImmediateInsertIndex: Int = 0 @@ -266,6 +274,7 @@ val playerControlsPatch = bytecodePatch( ) } + visibilityImmediateCallbacksExistMethod = playerControlsExtensionHookListenersExistFingerprint.method visibilityImmediateMethod = playerControlsExtensionHookFingerprint.method // A/B test for a slightly different bottom overlay controls, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt index a7c72504e..a242d6fad 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt @@ -51,7 +51,7 @@ internal val mainActivityOnCreateFingerprint = fingerprint { } } -val rollingNumberTextViewAnimationUpdateFingerprint = fingerprint { +internal val rollingNumberTextViewAnimationUpdateFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) returns("V") parameters("Landroid/graphics/Bitmap;") diff --git a/patches/src/main/resources/addresources/values/arrays.xml b/patches/src/main/resources/addresources/values/arrays.xml index d317a36b7..4a0d6463d 100644 --- a/patches/src/main/resources/addresources/values/arrays.xml +++ b/patches/src/main/resources/addresources/values/arrays.xml @@ -148,6 +148,21 @@ 17.33.42 + + + @string/revanced_exit_fullscreen_entry_1 + @string/revanced_exit_fullscreen_entry_2 + @string/revanced_exit_fullscreen_entry_3 + @string/revanced_exit_fullscreen_entry_4 + + + + DISABLED + PORTRAIT + LANDSCAPE + PORTRAIT_LANDSCAPE + + @string/revanced_miniplayer_type_entry_0 diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index edd2222c2..d2e07dd35 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -748,6 +748,13 @@ Note: Enabling this also forcibly hides video ads" Player popup panels are hidden Player popup panels are shown + + Exit fullscreen mode at end of video + Disabled + Portrait + Landscape + Portrait and landscape + Open videos in fullscreen portrait Videos open fullscreen