Skip to content

Commit

Permalink
feat(YouTube): Add Exit fullscreen mode patch
Browse files Browse the repository at this point in the history
  • Loading branch information
LisoUseInAIKyrios committed Dec 26, 2024
1 parent 85e8b27 commit fa8f514
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,11 @@ public static boolean isDarkModeEnabled(Context context) {
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.
*
Expand Down Expand Up @@ -595,7 +600,7 @@ public static boolean isNetworkConnected() {
|| 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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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;
import app.revanced.extension.youtube.shared.VideoState;

@SuppressWarnings("unused")
public class ExitFullscreenPatch {

public enum FullscreenMode {
DISABLED,
PORTRAIT,
LANDSCAPE,
PORTRAIT_LANDSCAPE,
}

/**
* Injection point.
*/
public static void setVideoTime(long millis) {
try {
FullscreenMode mode = Settings.EXIT_FULLSCREEN.get();
if (mode == FullscreenMode.DISABLED) {
return;
}

if (Utils.isLandscapeOrientation()) {
if (mode == FullscreenMode.PORTRAIT) {
return;
}
} else if (mode == FullscreenMode.LANDSCAPE) {
return;
}

if (PlayerType.getCurrent() == PlayerType.WATCH_WHILE_FULLSCREEN
&& VideoState.getCurrent() == VideoState.PLAYING
&& VideoInformation.isAtEndOfVideo()) {
ImageView fullscreenButton = PlayerControlsPatch.fullscreenButtonRef.get();

if (fullscreenButton != null) {
Logger.printDebug(() -> "Clicking fullscreen button");
fullscreenButton.performClick();
}
}
} catch (Exception ex) {
Logger.printException(() -> "setVideoTime failure", ex);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,29 @@
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() {
Expand All @@ -39,7 +53,7 @@ public void onGlobalLayout() {
}

// noinspection EmptyMethod
public static void fullscreenButtonVisibilityChanged(boolean isVisible) {
private static void fullscreenButtonVisibilityChanged(boolean isVisible) {
// Code added during patching.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;
Expand Down Expand Up @@ -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);
Expand All @@ -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 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 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 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ enum class PlayerType {
onChange(currentPlayerType)
}

@Volatile // value is read/write from different threads
@Volatile // Rread/write from different threads.
private var currentPlayerType = NONE

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ enum class VideoState {
currentVideoState = value
}

@Volatile // Read/write from different threads.
private var currentVideoState: VideoState? = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package app.revanced.patches.youtube.layout.player.fullscreen

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.video.information.videoInformationPatch
import app.revanced.patches.youtube.video.information.videoTimeHook

@Suppress("unused")
internal val exitFullscreenPatch = bytecodePatch(
name = "Exit fullscreen mode",
description = "Adds options to automatically exit fullscreen mode when a video reaches the end."
) {
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
videoInformationPatch,
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,
)
)

videoTimeHook(
EXTENSION_CLASS_DESCRIPTOR,
"setVideoTime",
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -266,6 +274,7 @@ val playerControlsPatch = bytecodePatch(
)
}

visibilityImmediateCallbacksExistMethod = playerControlsExtensionHookListenersExistFingerprint.method
visibilityImmediateMethod = playerControlsExtensionHookFingerprint.method

// A/B test for a slightly different bottom overlay controls,
Expand Down
15 changes: 15 additions & 0 deletions patches/src/main/resources/addresources/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
7 changes: 7 additions & 0 deletions patches/src/main/resources/addresources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,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>
Expand Down

0 comments on commit fa8f514

Please sign in to comment.