feat(YouTube): Add Exit fullscreen mode patch (#4223)

This commit is contained in:
LisoUseInAIKyrios 2024-12-27 10:25:17 +04:00 committed by GitHub
parent 119092fafa
commit bb5d03bd89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 190 additions and 26 deletions

View File

@ -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) {

View File

@ -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);
}
}
}

View File

@ -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<ImageView> 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.
}

View File

@ -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<FullscreenMode> 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<MiniplayerType> 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);

View File

@ -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
/**

View File

@ -46,6 +46,7 @@ enum class VideoState {
currentVideoState = value
}
@Volatile // Read/write from different threads.
private var currentVideoState: VideoState? = null
}
}

View File

@ -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;
}

View File

@ -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)
}

View File

@ -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,
) {

View File

@ -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",
)
}
}

View File

@ -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",

View File

@ -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
}
}

View File

@ -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,

View File

@ -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;")

View File

@ -148,6 +148,21 @@
<item>17.33.42</item>
</string-array>
</patch>
<patch id="layout.player.fullscreen.exitFullscreenPatch">
<string-array name="revanced_exit_fullscreen_entries">
<item>@string/revanced_exit_fullscreen_entry_1</item>
<item>@string/revanced_exit_fullscreen_entry_2</item>
<item>@string/revanced_exit_fullscreen_entry_3</item>
<item>@string/revanced_exit_fullscreen_entry_4</item>
</string-array>
<string-array name="revanced_exit_fullscreen_entry_values">
<!-- Enum names from the extension. -->
<item>DISABLED</item>
<item>PORTRAIT</item>
<item>LANDSCAPE</item>
<item>PORTRAIT_LANDSCAPE</item>
</string-array>
</patch>
<patch id="layout.miniplayer.miniplayerPatch">
<string-array name="revanced_miniplayer_type_entries">
<item>@string/revanced_miniplayer_type_entry_0</item>

View File

@ -748,6 +748,13 @@ Note: Enabling this also forcibly hides video ads"</string>
<string name="revanced_hide_player_popup_panels_summary_on">Player popup panels are hidden</string>
<string name="revanced_hide_player_popup_panels_summary_off">Player popup panels are shown</string>
</patch>
<patch id="layout.player.fullscreen.exitFullscreenPatch">
<string name="revanced_exit_fullscreen_title">Exit fullscreen mode at end of video</string>
<string name="revanced_exit_fullscreen_entry_1">Disabled</string>
<string name="revanced_exit_fullscreen_entry_2">Portrait</string>
<string name="revanced_exit_fullscreen_entry_3">Landscape</string>
<string name="revanced_exit_fullscreen_entry_4">Portrait and landscape</string>
</patch>
<patch id="layout.player.fullscreen.openVideosFullscreen">
<string name="revanced_open_videos_fullscreen_portrait_title">Open videos in fullscreen portrait</string>
<string name="revanced_open_videos_fullscreen_portrait_summary_on">Videos open fullscreen</string>