Skip to content

Commit bb5d03b

Browse files
feat(YouTube): Add Exit fullscreen mode patch (#4223)
1 parent 119092f commit bb5d03b

File tree

16 files changed

+189
-25
lines changed

16 files changed

+189
-25
lines changed

extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,11 @@ public static boolean isDarkModeEnabled(Context context) {
523523
return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
524524
}
525525

526+
public static boolean isLandscapeOrientation() {
527+
final int orientation = context.getResources().getConfiguration().orientation;
528+
return orientation == Configuration.ORIENTATION_LANDSCAPE;
529+
}
530+
526531
/**
527532
* Automatically logs any exceptions the runnable throws.
528533
*
@@ -595,7 +600,7 @@ public static boolean isNetworkConnected() {
595600
|| networkType == NetworkType.OTHER;
596601
}
597602

598-
@SuppressLint("MissingPermission") // permission already included in YouTube
603+
@SuppressLint({"MissingPermission", "deprecation"}) // Permission already included in YouTube.
599604
public static NetworkType getNetworkType() {
600605
Context networkContext = getContext();
601606
if (networkContext == null) {
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package app.revanced.extension.youtube.patches;
2+
3+
import android.widget.ImageView;
4+
5+
import app.revanced.extension.shared.Logger;
6+
import app.revanced.extension.shared.Utils;
7+
import app.revanced.extension.youtube.settings.Settings;
8+
import app.revanced.extension.youtube.shared.PlayerType;
9+
10+
@SuppressWarnings("unused")
11+
public class ExitFullscreenPatch {
12+
13+
public enum FullscreenMode {
14+
DISABLED,
15+
PORTRAIT,
16+
LANDSCAPE,
17+
PORTRAIT_LANDSCAPE,
18+
}
19+
20+
/**
21+
* Injection point.
22+
*/
23+
public static void endOfVideoReached() {
24+
try {
25+
FullscreenMode mode = Settings.EXIT_FULLSCREEN.get();
26+
if (mode == FullscreenMode.DISABLED) {
27+
return;
28+
}
29+
30+
if (PlayerType.getCurrent() == PlayerType.WATCH_WHILE_FULLSCREEN) {
31+
if (Utils.isLandscapeOrientation()) {
32+
if (mode == FullscreenMode.PORTRAIT) {
33+
return;
34+
}
35+
} else if (mode == FullscreenMode.LANDSCAPE) {
36+
return;
37+
}
38+
39+
ImageView fullscreenButton = PlayerControlsPatch.fullscreenButtonRef.get();
40+
if (fullscreenButton != null) {
41+
Logger.printDebug(() -> "Clicking fullscreen button");
42+
fullscreenButton.performClick();
43+
}
44+
}
45+
} catch (Exception ex) {
46+
Logger.printException(() -> "endOfVideoReached failure", ex);
47+
}
48+
}
49+
}

extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerControlsPatch.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,29 @@
44
import android.view.ViewTreeObserver;
55
import android.widget.ImageView;
66

7+
import java.lang.ref.WeakReference;
8+
79
import app.revanced.extension.shared.Logger;
810

911
@SuppressWarnings("unused")
1012
public class PlayerControlsPatch {
1113

14+
public static WeakReference<ImageView> fullscreenButtonRef = new WeakReference<>(null);
15+
16+
private static boolean fullscreenButtonVisibilityCallbacksExist() {
17+
return false; // Modified during patching if needed.
18+
}
19+
1220
/**
1321
* Injection point.
1422
*/
1523
public static void setFullscreenCloseButton(ImageView imageButton) {
24+
fullscreenButtonRef = new WeakReference<>(imageButton);
25+
26+
if (!fullscreenButtonVisibilityCallbacksExist()) {
27+
return;
28+
}
29+
1630
// Add a global listener, since the protected method
1731
// View#onVisibilityChanged() does not have any call backs.
1832
imageButton.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@@ -39,7 +53,7 @@ public void onGlobalLayout() {
3953
}
4054

4155
// noinspection EmptyMethod
42-
public static void fullscreenButtonVisibilityChanged(boolean isVisible) {
56+
private static void fullscreenButtonVisibilityChanged(boolean isVisible) {
4357
// Code added during patching.
4458
}
4559

extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static app.revanced.extension.shared.settings.Setting.parent;
99
import static app.revanced.extension.shared.settings.Setting.parentsAny;
1010
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage;
11+
import static app.revanced.extension.youtube.patches.ExitFullscreenPatch.FullscreenMode;
1112
import static app.revanced.extension.youtube.patches.ForceOriginalAudioPatch.ForceOriginalAudioAvailability;
1213
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideExpandCloseAvailability;
1314
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability;
@@ -120,6 +121,7 @@ public class Settings extends BaseSettings {
120121
public static final BooleanSetting DISABLE_LIKE_SUBSCRIBE_GLOW = new BooleanSetting("revanced_disable_like_subscribe_glow", FALSE);
121122
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
122123
public static final BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE, true);
124+
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
123125
public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true);
124126
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
125127
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
@@ -139,10 +141,10 @@ public class Settings extends BaseSettings {
139141
public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE);
140142
public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE);
141143
public static final BooleanSetting HIDE_VIDEO_CHANNEL_WATERMARK = new BooleanSetting("revanced_hide_channel_watermark", TRUE);
144+
public static final BooleanSetting OPEN_VIDEOS_FULLSCREEN_PORTRAIT = new BooleanSetting("revanced_open_videos_fullscreen_portrait", FALSE);
142145
public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE);
143-
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
144146
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true);
145-
public static final BooleanSetting OPEN_VIDEOS_FULLSCREEN_PORTRAIT = new BooleanSetting("revanced_open_videos_fullscreen_portrait", FALSE);
147+
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
146148
// Miniplayer
147149
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.DEFAULT, true);
148150
private static final Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4);

extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ enum class PlayerType {
7373
onChange(currentPlayerType)
7474
}
7575

76-
@Volatile // value is read/write from different threads
76+
@Volatile // Read/write from different threads.
7777
private var currentPlayerType = NONE
7878

7979
/**

extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/VideoState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ enum class VideoState {
4646
currentVideoState = value
4747
}
4848

49+
@Volatile // Read/write from different threads.
4950
private var currentVideoState: VideoState? = null
5051
}
5152
}

patches/api/patches.api

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,6 @@ public final class app/revanced/patches/reddit/customclients/joeyforreddit/detec
461461
public static final fun getDisablePiracyDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
462462
}
463463

464-
public final class app/revanced/patches/reddit/customclients/redditisfun/api/FingerprintsKt {
465-
public static final fun baseClientIdFingerprint (Ljava/lang/String;)Lapp/revanced/patcher/Fingerprint;
466-
}
467-
468464
public final class app/revanced/patches/reddit/customclients/redditisfun/api/SpoofClientPatchKt {
469465
public static final fun getSpoofClientPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
470466
}
@@ -548,7 +544,6 @@ public final class app/revanced/patches/shared/misc/checks/BaseCheckEnvironmentP
548544
}
549545

550546
public final class app/revanced/patches/shared/misc/extension/ExtensionHook {
551-
public final fun getFingerprint ()Lapp/revanced/patcher/Fingerprint;
552547
public final fun invoke (Lapp/revanced/patcher/patch/BytecodePatchContext;Ljava/lang/String;)V
553548
}
554549

@@ -1268,10 +1263,6 @@ public final class app/revanced/patches/youtube/misc/backgroundplayback/Backgrou
12681263
public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
12691264
}
12701265

1271-
public final class app/revanced/patches/youtube/misc/check/CheckEnvironmentPatchKt {
1272-
public static final fun getCheckEnvironmentPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
1273-
}
1274-
12751266
public final class app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatchKt {
12761267
public static final fun getEnableDebuggingPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
12771268
}
@@ -1408,10 +1399,6 @@ public final class app/revanced/patches/youtube/misc/zoomhaptics/ZoomHapticsPatc
14081399
public static final fun getZoomHapticsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
14091400
}
14101401

1411-
public final class app/revanced/patches/youtube/shared/FingerprintsKt {
1412-
public static final fun getRollingNumberTextViewAnimationUpdateFingerprint ()Lapp/revanced/patcher/Fingerprint;
1413-
}
1414-
14151402
public final class app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatchKt {
14161403
public static final fun getForceOriginalAudioPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
14171404
}

patches/src/main/kotlin/app/revanced/patches/reddit/customclients/redditisfun/api/Fingerprints.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import app.revanced.patcher.fingerprint
44
import com.android.tools.smali.dexlib2.AccessFlags
55
import com.android.tools.smali.dexlib2.Opcode
66

7-
fun baseClientIdFingerprint(string: String) = fingerprint {
7+
internal fun baseClientIdFingerprint(string: String) = fingerprint {
88
strings("yyOCBp.RHJhDKd", string)
99
}
1010

patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ fun sharedExtensionPatch(
9292
}
9393

9494
class ExtensionHook internal constructor(
95-
val fingerprint: Fingerprint,
95+
private val fingerprint: Fingerprint,
9696
private val insertIndexResolver: ((Method) -> Int),
9797
private val contextRegisterResolver: (Method) -> String,
9898
) {
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package app.revanced.patches.youtube.layout.player.fullscreen
2+
3+
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
4+
import app.revanced.patcher.patch.bytecodePatch
5+
import app.revanced.patches.all.misc.resources.addResources
6+
import app.revanced.patches.all.misc.resources.addResourcesPatch
7+
import app.revanced.patches.shared.misc.settings.preference.ListPreference
8+
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
9+
import app.revanced.patches.youtube.misc.playercontrols.playerControlsPatch
10+
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
11+
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
12+
import app.revanced.patches.youtube.misc.settings.settingsPatch
13+
import app.revanced.patches.youtube.shared.autoRepeatFingerprint
14+
import app.revanced.patches.youtube.shared.autoRepeatParentFingerprint
15+
16+
@Suppress("unused")
17+
internal val exitFullscreenPatch = bytecodePatch(
18+
name = "Exit fullscreen mode",
19+
description = "Adds options to automatically exit fullscreen mode when a video reaches the end."
20+
) {
21+
22+
compatibleWith(
23+
"com.google.android.youtube"(
24+
"18.38.44",
25+
"18.49.37",
26+
"19.16.39",
27+
"19.25.37",
28+
"19.34.42",
29+
"19.43.41",
30+
"19.45.38",
31+
"19.46.42",
32+
"19.47.53",
33+
)
34+
)
35+
36+
dependsOn(
37+
sharedExtensionPatch,
38+
settingsPatch,
39+
addResourcesPatch,
40+
playerTypeHookPatch,
41+
playerControlsPatch
42+
)
43+
44+
// Cannot declare as top level since this patch is in the same package as
45+
// other patches that declare same constant name with internal visibility.
46+
@Suppress("LocalVariableName")
47+
val EXTENSION_CLASS_DESCRIPTOR =
48+
"Lapp/revanced/extension/youtube/patches/ExitFullscreenPatch;"
49+
50+
execute {
51+
addResources("youtube", "layout.player.fullscreen.exitFullscreenPatch")
52+
53+
PreferenceScreen.PLAYER.addPreferences(
54+
ListPreference(
55+
"revanced_exit_fullscreen",
56+
summaryKey = null,
57+
)
58+
)
59+
60+
autoRepeatFingerprint.match(autoRepeatParentFingerprint.originalClassDef).method.addInstruction(
61+
0,
62+
"invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->endOfVideoReached()V",
63+
)
64+
}
65+
}

patches/src/main/kotlin/app/revanced/patches/youtube/misc/check/CheckEnvironmentPatch.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import app.revanced.patches.shared.misc.checks.checkEnvironmentPatch
44
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
55
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
66

7-
val checkEnvironmentPatch = checkEnvironmentPatch(
7+
internal val checkEnvironmentPatch = checkEnvironmentPatch(
88
mainActivityOnCreateFingerprint = mainActivityOnCreateFingerprint,
99
extensionPatch = sharedExtensionPatch,
1010
"com.google.android.youtube",

patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/Fingerprints.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,23 @@ internal val playerTopControlsInflateFingerprint = fingerprint {
1212
literal { controlsLayoutStub }
1313
}
1414

15+
internal val playerControlsExtensionHookListenersExistFingerprint = fingerprint {
16+
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
17+
returns("Z")
18+
parameters()
19+
custom { methodDef, classDef ->
20+
methodDef.name == "fullscreenButtonVisibilityCallbacksExist" &&
21+
classDef.type == EXTENSION_CLASS_DESCRIPTOR
22+
}
23+
}
24+
1525
internal val playerControlsExtensionHookFingerprint = fingerprint {
16-
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
26+
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
1727
returns("V")
1828
parameters("Z")
1929
custom { methodDef, classDef ->
2030
methodDef.name == "fullscreenButtonVisibilityChanged" &&
21-
classDef.type == "Lapp/revanced/extension/youtube/patches/PlayerControlsPatch;"
31+
classDef.type == EXTENSION_CLASS_DESCRIPTOR
2232
}
2333
}
2434

patches/src/main/kotlin/app/revanced/patches/youtube/misc/playercontrols/PlayerControlsPatch.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,18 @@ fun injectVisibilityCheckCall(descriptor: String) {
189189
"invoke-static { p1 , p2 }, $descriptor->changeVisibility(ZZ)V",
190190
)
191191

192+
if (!visibilityImmediateCallbacksExistModified) {
193+
visibilityImmediateCallbacksExistModified = true
194+
visibilityImmediateCallbacksExistMethod.returnEarly(true)
195+
}
196+
192197
visibilityImmediateMethod.addInstruction(
193198
visibilityImmediateInsertIndex++,
194199
"invoke-static { p0 }, $descriptor->changeVisibilityImmediate(Z)V",
195200
)
196201
}
197202

198-
private const val EXTENSION_CLASS_DESCRIPTOR =
203+
internal const val EXTENSION_CLASS_DESCRIPTOR =
199204
"Lapp/revanced/extension/youtube/patches/PlayerControlsPatch;"
200205

201206
private lateinit var inflateTopControlMethod: MutableMethod
@@ -209,6 +214,9 @@ private var inflateBottomControlRegister: Int = -1
209214
private lateinit var visibilityMethod: MutableMethod
210215
private var visibilityInsertIndex: Int = 0
211216

217+
private var visibilityImmediateCallbacksExistModified = false
218+
private lateinit var visibilityImmediateCallbacksExistMethod : MutableMethod
219+
212220
private lateinit var visibilityImmediateMethod: MutableMethod
213221
private var visibilityImmediateInsertIndex: Int = 0
214222

@@ -266,6 +274,7 @@ val playerControlsPatch = bytecodePatch(
266274
)
267275
}
268276

277+
visibilityImmediateCallbacksExistMethod = playerControlsExtensionHookListenersExistFingerprint.method
269278
visibilityImmediateMethod = playerControlsExtensionHookFingerprint.method
270279

271280
// A/B test for a slightly different bottom overlay controls,

patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ internal val mainActivityOnCreateFingerprint = fingerprint {
5151
}
5252
}
5353

54-
val rollingNumberTextViewAnimationUpdateFingerprint = fingerprint {
54+
internal val rollingNumberTextViewAnimationUpdateFingerprint = fingerprint {
5555
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
5656
returns("V")
5757
parameters("Landroid/graphics/Bitmap;")

patches/src/main/resources/addresources/values/arrays.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,21 @@
148148
<item>17.33.42</item>
149149
</string-array>
150150
</patch>
151+
<patch id="layout.player.fullscreen.exitFullscreenPatch">
152+
<string-array name="revanced_exit_fullscreen_entries">
153+
<item>@string/revanced_exit_fullscreen_entry_1</item>
154+
<item>@string/revanced_exit_fullscreen_entry_2</item>
155+
<item>@string/revanced_exit_fullscreen_entry_3</item>
156+
<item>@string/revanced_exit_fullscreen_entry_4</item>
157+
</string-array>
158+
<string-array name="revanced_exit_fullscreen_entry_values">
159+
<!-- Enum names from the extension. -->
160+
<item>DISABLED</item>
161+
<item>PORTRAIT</item>
162+
<item>LANDSCAPE</item>
163+
<item>PORTRAIT_LANDSCAPE</item>
164+
</string-array>
165+
</patch>
151166
<patch id="layout.miniplayer.miniplayerPatch">
152167
<string-array name="revanced_miniplayer_type_entries">
153168
<item>@string/revanced_miniplayer_type_entry_0</item>

patches/src/main/resources/addresources/values/strings.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,13 @@ Note: Enabling this also forcibly hides video ads"</string>
748748
<string name="revanced_hide_player_popup_panels_summary_on">Player popup panels are hidden</string>
749749
<string name="revanced_hide_player_popup_panels_summary_off">Player popup panels are shown</string>
750750
</patch>
751+
<patch id="layout.player.fullscreen.exitFullscreenPatch">
752+
<string name="revanced_exit_fullscreen_title">Exit fullscreen mode at end of video</string>
753+
<string name="revanced_exit_fullscreen_entry_1">Disabled</string>
754+
<string name="revanced_exit_fullscreen_entry_2">Portrait</string>
755+
<string name="revanced_exit_fullscreen_entry_3">Landscape</string>
756+
<string name="revanced_exit_fullscreen_entry_4">Portrait and landscape</string>
757+
</patch>
751758
<patch id="layout.player.fullscreen.openVideosFullscreen">
752759
<string name="revanced_open_videos_fullscreen_portrait_title">Open videos in fullscreen portrait</string>
753760
<string name="revanced_open_videos_fullscreen_portrait_summary_on">Videos open fullscreen</string>

0 commit comments

Comments
 (0)