From 88142ab464192b564b1b8d56a6b45663f77f5e00 Mon Sep 17 00:00:00 2001 From: alieRN <45766489+aliernfrog@users.noreply.github.com> Date: Thu, 6 Mar 2025 09:46:33 +0300 Subject: [PATCH] feat(YouTube - Remember video quality): Add separate Shorts default quality settings (#4543) --- .../AbstractPreferenceFragment.java | 10 ++-- .../preference/NoTitlePreferenceCategory.java | 34 ++++++++++++ .../patches/BackgroundPlaybackPatch.java | 13 +---- .../patches/DisableAutoCaptionsPatch.java | 4 +- .../youtube/patches/PlayerTypeHookPatch.java | 25 +++++++++ .../quality/RememberVideoQualityPatch.java | 43 ++++++++++----- .../extension/youtube/settings/Settings.java | 7 ++- .../extension/youtube/shared/PlayerType.kt | 14 +++-- .../youtube/shared/ShortsPlayerState.kt | 38 +++++++++++++ .../settings/preference/BasePreference.kt | 4 +- .../settings/preference/PreferenceCategory.kt | 2 +- .../spoofappversion/SpoofAppVersionPatch.kt | 39 +++++++++----- .../youtube/misc/playertype/Fingerprints.kt | 7 +++ .../misc/playertype/PlayerTypeHookPatch.kt | 38 +++++++++++-- .../youtube/misc/settings/SettingsPatch.kt | 1 + .../quality/RememberVideoQualityPatch.kt | 53 ++++++++++++++----- .../youtube/video/speed/PlaybackSpeedPatch.kt | 23 +++++++- .../speed/custom/CustomPlaybackSpeedPatch.kt | 15 ++++-- .../remember/RememberPlaybackSpeedPatch.kt | 22 ++++---- .../resources/addresources/values/strings.xml | 7 +++ 20 files changed, 312 insertions(+), 87 deletions(-) create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/NoTitlePreferenceCategory.java create mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/ShortsPlayerState.kt diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java index c7b570a85..2aeab6134 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java @@ -158,16 +158,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { /** * Syncs all UI Preferences to any {@link Setting} they represent. */ - private void updatePreferenceScreen(@NonNull PreferenceScreen screen, + private void updatePreferenceScreen(@NonNull PreferenceGroup group, boolean syncSettingValue, boolean applySettingToPreference) { // Alternatively this could iterate thru all Settings and check for any matching Preferences, // but there are many more Settings than UI preferences so it's more efficient to only check // the Preferences. - for (int i = 0, prefCount = screen.getPreferenceCount(); i < prefCount; i++) { - Preference pref = screen.getPreference(i); - if (pref instanceof PreferenceScreen) { - updatePreferenceScreen((PreferenceScreen) pref, syncSettingValue, applySettingToPreference); + for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) { + Preference pref = group.getPreference(i); + if (pref instanceof PreferenceGroup subGroup) { + updatePreferenceScreen(subGroup, syncSettingValue, applySettingToPreference); } else if (pref.hasKey()) { String key = pref.getKey(); Setting setting = Setting.getSettingFromPath(key); diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/NoTitlePreferenceCategory.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/NoTitlePreferenceCategory.java new file mode 100644 index 000000000..2c94c54a3 --- /dev/null +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/NoTitlePreferenceCategory.java @@ -0,0 +1,34 @@ +package app.revanced.extension.shared.settings.preference; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.preference.PreferenceCategory; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +/** + * Empty preference category with no title, used to organize and group related preferences together. + */ +@SuppressWarnings({"unused", "deprecation"}) +public class NoTitlePreferenceCategory extends PreferenceCategory { + public NoTitlePreferenceCategory(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public NoTitlePreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public NoTitlePreferenceCategory(Context context) { + super(context); + } + + @Override + @SuppressLint("MissingSuperCall") + protected View onCreateView(ViewGroup parent) { + // Return an empty, zero-height view to eliminate spacing + return new View(getContext()); + } +} + diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/BackgroundPlaybackPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/BackgroundPlaybackPatch.java index 7e5f59a6e..1ab3374d7 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/BackgroundPlaybackPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/BackgroundPlaybackPatch.java @@ -1,7 +1,7 @@ package app.revanced.extension.youtube.patches; import app.revanced.extension.youtube.settings.Settings; -import app.revanced.extension.youtube.shared.PlayerType; +import app.revanced.extension.youtube.shared.ShortsPlayerState; @SuppressWarnings("unused") public class BackgroundPlaybackPatch { @@ -23,16 +23,7 @@ public class BackgroundPlaybackPatch { // 7. Close the Short // 8. Resume playing the regular video // 9. Minimize the app (PIP should appear) - if (!VideoInformation.lastVideoIdIsShort()) { - return true; // Definitely is not a Short. - } - - // TODO: Add better hook. - // Might be a Shorts, or might be a prior regular video on screen again after a Shorts was closed. - // This incorrectly prevents PIP if player is in WATCH_WHILE_MINIMIZED after closing a Shorts, - // But there's no way around this unless an additional hook is added to definitively detect - // the Shorts player is on screen. This use case is unusual anyways so it's not a huge concern. - return !PlayerType.getCurrent().isNoneHiddenOrMinimized(); + return !ShortsPlayerState.isOpen(); } /** diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/DisableAutoCaptionsPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/DisableAutoCaptionsPatch.java index 9d43159a5..38cbf00a0 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/DisableAutoCaptionsPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/DisableAutoCaptionsPatch.java @@ -1,7 +1,7 @@ package app.revanced.extension.youtube.patches; import app.revanced.extension.youtube.settings.Settings; -import app.revanced.extension.youtube.shared.PlayerType; +import app.revanced.extension.youtube.shared.ShortsPlayerState; @SuppressWarnings("unused") public class DisableAutoCaptionsPatch { @@ -14,7 +14,7 @@ public class DisableAutoCaptionsPatch { public static boolean autoCaptionsEnabled() { return Settings.AUTO_CAPTIONS.get() // Do not use auto captions for Shorts. - && !PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized(); + && ShortsPlayerState.isOpen(); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerTypeHookPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerTypeHookPatch.java index 3f1591950..79ffd7257 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerTypeHookPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PlayerTypeHookPatch.java @@ -1,8 +1,11 @@ package app.revanced.extension.youtube.patches; +import android.view.View; + import androidx.annotation.Nullable; import app.revanced.extension.youtube.shared.PlayerType; +import app.revanced.extension.youtube.shared.ShortsPlayerState; import app.revanced.extension.youtube.shared.VideoState; @SuppressWarnings("unused") @@ -24,4 +27,26 @@ public class PlayerTypeHookPatch { VideoState.setFromString(youTubeVideoState.name()); } + + /** + * Injection point. + * + * Add a listener to the shorts player overlay View. + * Triggered when a shorts player is attached or detached to Windows. + * + * @param view shorts player overlay (R.id.reel_watch_player). + */ + public static void onShortsCreate(View view) { + view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(@Nullable View v) { + ShortsPlayerState.setOpen(true); + } + + @Override + public void onViewDetachedFromWindow(@Nullable View v) { + ShortsPlayerState.setOpen(false); + } + }); + } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index f55b36d61..706c7c0d1 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -12,15 +12,19 @@ import java.util.List; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.IntegerSetting; import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.youtube.shared.ShortsPlayerState; @SuppressWarnings("unused") public class RememberVideoQualityPatch { private static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2; - private static final IntegerSetting wifiQualitySetting = Settings.VIDEO_QUALITY_DEFAULT_WIFI; - private static final IntegerSetting mobileQualitySetting = Settings.VIDEO_QUALITY_DEFAULT_MOBILE; + private static final IntegerSetting videoQualityWifi = Settings.VIDEO_QUALITY_DEFAULT_WIFI; + private static final IntegerSetting videoQualityMobile = Settings.VIDEO_QUALITY_DEFAULT_MOBILE; + private static final IntegerSetting shortsQualityWifi = Settings.SHORTS_QUALITY_DEFAULT_WIFI; + private static final IntegerSetting shortsQualityMobile = Settings.SHORTS_QUALITY_DEFAULT_MOBILE; private static boolean qualityNeedsUpdating; @@ -41,17 +45,29 @@ public class RememberVideoQualityPatch { @Nullable private static List videoQualities; + private static boolean shouldRememberVideoQuality() { + BooleanSetting preference = ShortsPlayerState.isOpen() ? + Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED + : Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED; + return preference.get(); + } + private static void changeDefaultQuality(int defaultQuality) { String networkTypeMessage; + boolean useShortsPreference = ShortsPlayerState.isOpen(); if (Utils.getNetworkType() == NetworkType.MOBILE) { - mobileQualitySetting.save(defaultQuality); + if (useShortsPreference) shortsQualityMobile.save(defaultQuality); + else videoQualityMobile.save(defaultQuality); networkTypeMessage = str("revanced_remember_video_quality_mobile"); } else { - wifiQualitySetting.save(defaultQuality); + if (useShortsPreference) shortsQualityWifi.save(defaultQuality); + else videoQualityWifi.save(defaultQuality); networkTypeMessage = str("revanced_remember_video_quality_wifi"); } - Utils.showToastShort( - str("revanced_remember_video_quality_toast", networkTypeMessage, (defaultQuality + "p"))); + Utils.showToastShort(str( + useShortsPreference ? "revanced_remember_video_quality_toast_shorts" : "revanced_remember_video_quality_toast", + networkTypeMessage, (defaultQuality + "p") + )); } /** @@ -62,9 +78,10 @@ public class RememberVideoQualityPatch { */ public static int setVideoQuality(Object[] qualities, final int originalQualityIndex, Object qInterface, String qIndexMethod) { try { + boolean useShortsPreference = ShortsPlayerState.isOpen(); final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE - ? mobileQualitySetting.get() - : wifiQualitySetting.get(); + ? (useShortsPreference ? shortsQualityMobile : videoQualityMobile).get() + : (useShortsPreference ? shortsQualityWifi : videoQualityWifi).get(); if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { return originalQualityIndex; // Nothing to do. @@ -141,17 +158,17 @@ public class RememberVideoQualityPatch { * Injection point. Old quality menu. */ public static void userChangedQuality(int selectedQualityIndex) { - if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.get()) return; - - userSelectedQualityIndex = selectedQualityIndex; - userChangedDefaultQuality = true; + if (shouldRememberVideoQuality()) { + userSelectedQualityIndex = selectedQualityIndex; + userChangedDefaultQuality = true; + } } /** * Injection point. New quality menu. */ public static void userChangedQualityInNewFlyout(int selectedQuality) { - if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.get()) return; + if (!shouldRememberVideoQuality()) return; changeDefaultQuality(selectedQuality); // Quality is human readable resolution (ie: 1080). } 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 3b6b24759..b8263829d 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 @@ -48,10 +48,13 @@ import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings; public class Settings extends BaseSettings { // Video public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE); - public static final BooleanSetting RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE); - public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE); public static final IntegerSetting VIDEO_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2); public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2); + public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE); + public static final IntegerSetting SHORTS_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_shorts_quality_default_wifi", -2, true); + public static final IntegerSetting SHORTS_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_shorts_quality_default_mobile", -2, true); + public static final BooleanSetting REMEMBER_SHORTS_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_shorts_quality_last_selected", FALSE); + public static final BooleanSetting RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE); // Speed public static final FloatSetting SPEED_TAP_AND_HOLD = new FloatSetting("revanced_speed_tap_and_hold", 2.0f, true); public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", FALSE); 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 a08a19730..147abc13f 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 @@ -5,7 +5,7 @@ import app.revanced.extension.youtube.Event import app.revanced.extension.youtube.patches.VideoInformation /** - * Main player type. + * Regular player type. */ enum class PlayerType { /** @@ -90,8 +90,6 @@ enum class PlayerType { * Does not include the first moment after a short is opened when a regular video is minimized on screen, * or while watching a short with a regular video present on a spoofed 16.x version of YouTube. * To include those situations instead use [isNoneHiddenOrMinimized]. - * - * @see VideoInformation */ fun isNoneOrHidden(): Boolean { return this == NONE || this == HIDDEN @@ -107,8 +105,11 @@ enum class PlayerType { * when spoofing to an old version this will return false even * though a Short is being opened or is on screen (see [isNoneHiddenOrMinimized]). * + * Instead of this method, consider using {@link ShortsPlayerState} + * which may work better for some situations. + * * @return If nothing, a Short, or a regular video is sliding off screen to a dismissed or hidden state. - * @see VideoInformation + * @see ShortsPlayerState */ fun isNoneHiddenOrSlidingMinimized(): Boolean { return isNoneOrHidden() || this == WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED @@ -125,9 +126,12 @@ enum class PlayerType { * Typically used to detect if a Short is playing when the player cannot be in a minimized state, * such as the user interacting with a button or element of the player. * + * Instead of this method, consider using {@link ShortsPlayerState} + * which may work better for some situations. + * * @return If nothing, a Short, a regular video is sliding off screen to a dismissed or hidden state, * a regular video is minimized (and a new video is not being opened). - * @see VideoInformation + * @see ShortsPlayerState */ fun isNoneHiddenOrMinimized(): Boolean { return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/ShortsPlayerState.kt b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/ShortsPlayerState.kt new file mode 100644 index 000000000..48927d88a --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/shared/ShortsPlayerState.kt @@ -0,0 +1,38 @@ +package app.revanced.extension.youtube.shared + +import app.revanced.extension.shared.Logger +import app.revanced.extension.youtube.Event + +/** + * Shorts player state. + */ +class ShortsPlayerState { + companion object { + + @JvmStatic + fun setOpen(open: Boolean) { + if (isOpen != open) { + Logger.printDebug { "ShortsPlayerState open changed to: $isOpen" } + isOpen = open + onChange(open) + } + } + + @Volatile + private var isOpen = false + + /** + * Shorts player state change listener. + */ + @JvmStatic + val onChange = Event() + + /** + * If the Shorts player is currently open. + */ + @JvmStatic + fun isOpen(): Boolean { + return isOpen + } + } +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreference.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreference.kt index 7c5cbf2cd..a07bdc4fe 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreference.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/BasePreference.kt @@ -17,7 +17,7 @@ import org.w3c.dom.Element @Suppress("MemberVisibilityCanBePrivate") abstract class BasePreference( val key: String? = null, - val titleKey: String = "${key}_title", + val titleKey: String? = "${key}_title", val summaryKey: String? = "${key}_summary", val icon: String? = null, val layout: String? = null, @@ -35,7 +35,7 @@ abstract class BasePreference( open fun serialize(ownerDocument: Document, resourceCallback: (BaseResource) -> Unit): Element = ownerDocument.createElement(tag).apply { key?.let { setAttribute("android:key", it) } - setAttribute("android:title", "@string/${titleKey}") + titleKey?.let { setAttribute("android:title", "@string/${titleKey}") } summaryKey?.let { addSummary(it) } icon?.let { setAttribute("android:icon", it) diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceCategory.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceCategory.kt index 8b2deb96e..ef51eca8c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceCategory.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/preference/PreferenceCategory.kt @@ -17,7 +17,7 @@ import org.w3c.dom.Document @Suppress("MemberVisibilityCanBePrivate") open class PreferenceCategory( key: String? = null, - titleKey: String = "${key}_title", + titleKey: String? = "${key}_title", icon: String? = null, layout: String? = null, sorting: Sorting = Sorting.BY_TITLE, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt index 48caf9c1b..6ce2982a1 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt @@ -12,6 +12,8 @@ import app.revanced.patches.shared.misc.mapping.get import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappings import app.revanced.patches.shared.misc.settings.preference.ListPreference +import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.playservice.is_19_17_or_greater @@ -71,20 +73,31 @@ val spoofAppVersionPatch = bytecodePatch( addResources("youtube", "layout.spoofappversion.spoofAppVersionPatch") PreferenceScreen.GENERAL_LAYOUT.addPreferences( - SwitchPreference("revanced_spoof_app_version"), - if (is_19_17_or_greater) { - ListPreference( - key = "revanced_spoof_app_version_target", - summaryKey = null, + // Group the switch and list preference together, since General menu is sorted by name + // and the preferences can be scattered apart with non English langauges. + PreferenceCategory( + key = null, + // The title does not show, but is used for sorting the group. + titleKey = "revanced_spoof_app_version_title", + sorting = Sorting.UNSORTED, + tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory", + preferences = setOf( + SwitchPreference("revanced_spoof_app_version"), + if (is_19_17_or_greater) { + ListPreference( + key = "revanced_spoof_app_version_target", + summaryKey = null, + ) + } else { + ListPreference( + key = "revanced_spoof_app_version_target", + summaryKey = null, + entriesKey = "revanced_spoof_app_version_target_legacy_entries", + entryValuesKey = "revanced_spoof_app_version_target_legacy_entry_values" + ) + } ) - } else { - ListPreference( - key = "revanced_spoof_app_version_target", - summaryKey = null, - entriesKey = "revanced_spoof_app_version_target_legacy_entries", - entryValuesKey = "revanced_spoof_app_version_target_legacy_entry_values" - ) - } + ) ) /** diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/Fingerprints.kt index 2faae3acc..522d06b20 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/Fingerprints.kt @@ -1,6 +1,7 @@ package app.revanced.patches.youtube.misc.playertype import app.revanced.patcher.fingerprint +import app.revanced.util.literal import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -15,6 +16,12 @@ internal val playerTypeFingerprint = fingerprint { custom { _, classDef -> classDef.endsWith("/YouTubePlayerOverlaysLayout;") } } +internal val reelWatchPagerFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returns("Landroid/view/View;") + literal { reelWatchPlayerId } +} + internal val videoStateFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) returns("V") diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/PlayerTypeHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/PlayerTypeHookPatch.kt index e021928a8..f8dcf8b7e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/PlayerTypeHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/playertype/PlayerTypeHookPatch.kt @@ -4,15 +4,34 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.patch.resourcePatch +import app.revanced.patches.shared.misc.mapping.get +import app.revanced.patches.shared.misc.mapping.resourceMappingPatch +import app.revanced.patches.shared.misc.mapping.resourceMappings import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstLiteralInstructionOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/PlayerTypeHookPatch;" +internal var reelWatchPlayerId = -1L + private set + +private val playerTypeHookResourcePatch = resourcePatch { + dependsOn(resourceMappingPatch) + + execute { + reelWatchPlayerId = resourceMappings["id", "reel_watch_player"] + } +} + val playerTypeHookPatch = bytecodePatch( description = "Hook to get the current player type and video playback state.", ) { - dependsOn(sharedExtensionPatch) + dependsOn(sharedExtensionPatch, playerTypeHookResourcePatch) execute { playerTypeFingerprint.method.addInstruction( @@ -20,6 +39,17 @@ val playerTypeHookPatch = bytecodePatch( "invoke-static {p1}, $EXTENSION_CLASS_DESCRIPTOR->setPlayerType(Ljava/lang/Enum;)V", ) + reelWatchPagerFingerprint.method.apply { + val literalIndex = indexOfFirstLiteralInstructionOrThrow(reelWatchPlayerId) + val registerIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT_OBJECT) + val viewRegister = getInstruction(registerIndex).registerA + + addInstruction( + registerIndex + 1, + "invoke-static { v$viewRegister }, $EXTENSION_CLASS_DESCRIPTOR->onShortsCreate(Landroid/view/View;)V" + ) + } + videoStateFingerprint.method.apply { val endIndex = videoStateFingerprint.patternMatch!!.endIndex val videoStateFieldName = getInstruction(endIndex).reference @@ -27,9 +57,9 @@ val playerTypeHookPatch = bytecodePatch( addInstructions( 0, """ - iget-object v0, p1, $videoStateFieldName # copy VideoState parameter field - invoke-static {v0}, $EXTENSION_CLASS_DESCRIPTOR->setVideoState(Ljava/lang/Enum;)V - """, + iget-object v0, p1, $videoStateFieldName # copy VideoState parameter field + invoke-static {v0}, $EXTENSION_CLASS_DESCRIPTOR->setVideoState(Ljava/lang/Enum;)V + """ ) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt index 683062863..47eee24ee 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt @@ -323,6 +323,7 @@ object PreferenceScreen : BasePreferenceScreen() { val VIDEO = Screen( key = "revanced_settings_screen_12_video", summaryKey = null, + sorting = Sorting.BY_KEY, ) override fun commit(screen: PreferenceScreenPreference) { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt index f4299dc65..d7c5a354a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt @@ -8,8 +8,11 @@ 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.shared.misc.settings.preference.PreferenceCategory +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +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.newVideoQualityChangedFingerprint @@ -29,6 +32,7 @@ val rememberVideoQualityPatch = bytecodePatch( dependsOn( sharedExtensionPatch, videoInformationPatch, + playerTypeHookPatch, settingsPatch, addResourcesPatch, ) @@ -49,19 +53,42 @@ val rememberVideoQualityPatch = bytecodePatch( addResources("youtube", "video.quality.rememberVideoQualityPatch") PreferenceScreen.VIDEO.addPreferences( - SwitchPreference("revanced_remember_video_quality_last_selected"), - ListPreference( - key = "revanced_video_quality_default_wifi", - summaryKey = null, - entriesKey = "revanced_video_quality_default_entries", - entryValuesKey = "revanced_video_quality_default_entry_values", - ), - ListPreference( - key = "revanced_video_quality_default_mobile", - summaryKey = null, - entriesKey = "revanced_video_quality_default_entries", - entryValuesKey = "revanced_video_quality_default_entry_values", - ), + // Keep the preferences organized together. + PreferenceCategory( + key = "revanced_01_video_key", // Dummy key to force the quality preferences first. + titleKey = null, + sorting = Sorting.UNSORTED, + tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory", + preferences = setOf( + ListPreference( + key = "revanced_video_quality_default_mobile", + summaryKey = null, + entriesKey = "revanced_video_quality_default_entries", + entryValuesKey = "revanced_video_quality_default_entry_values", + ), + ListPreference( + key = "revanced_video_quality_default_wifi", + summaryKey = null, + entriesKey = "revanced_video_quality_default_entries", + entryValuesKey = "revanced_video_quality_default_entry_values", + ), + SwitchPreference("revanced_remember_video_quality_last_selected"), + + ListPreference( + key = "revanced_shorts_quality_default_mobile", + summaryKey = null, + entriesKey = "revanced_video_quality_default_entries", + entryValuesKey = "revanced_video_quality_default_entry_values", + ), + ListPreference( + key = "revanced_shorts_quality_default_wifi", + summaryKey = null, + entriesKey = "revanced_video_quality_default_entries", + entryValuesKey = "revanced_video_quality_default_entry_values", + ), + SwitchPreference("revanced_remember_shorts_quality_last_selected") + ) + ) ) /* diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt index 476d43157..a5868887e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/PlaybackSpeedPatch.kt @@ -1,10 +1,19 @@ package app.revanced.patches.youtube.video.speed import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.shared.misc.settings.preference.BasePreference +import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting +import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.video.speed.button.playbackSpeedButtonPatch import app.revanced.patches.youtube.video.speed.custom.customPlaybackSpeedPatch import app.revanced.patches.youtube.video.speed.remember.rememberPlaybackSpeedPatch +/** + * Speed menu settings. Used to organize all speed related settings together. + */ +internal val settingsMenuVideoSpeedGroup = mutableSetOf() + @Suppress("unused") val playbackSpeedPatch = bytecodePatch( name = "Playback speed", @@ -26,6 +35,18 @@ val playbackSpeedPatch = bytecodePatch( "19.45.38", "19.46.42", "19.47.53", - ), + ) ) + + finalize { + PreferenceScreen.VIDEO.addPreferences( + PreferenceCategory( + key = "revanced_zz_key", // Dummy key to force the speed settings last. + titleKey = null, + sorting = Sorting.UNSORTED, + tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory", + preferences = settingsMenuVideoSpeedGroup + ) + ) + } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt index 695642c5a..84a560894 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt @@ -25,8 +25,8 @@ import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTreeHook import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch -import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch +import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup import app.revanced.util.* import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction @@ -71,13 +71,18 @@ internal val customPlaybackSpeedPatch = bytecodePatch( execute { addResources("youtube", "video.speed.custom.customPlaybackSpeedPatch") - PreferenceScreen.VIDEO.addPreferences( - SwitchPreference("revanced_custom_speed_menu"), - TextPreference("revanced_custom_playback_speeds", inputType = InputType.TEXT_MULTI_LINE), + settingsMenuVideoSpeedGroup.addAll( + listOf( + SwitchPreference("revanced_custom_speed_menu"), + TextPreference( + "revanced_custom_playback_speeds", + inputType = InputType.TEXT_MULTI_LINE + ), + ) ) if (is_19_25_or_greater) { - PreferenceScreen.VIDEO.addPreferences( + settingsMenuVideoSpeedGroup.add( TextPreference("revanced_speed_tap_and_hold", inputType = InputType.NUMBER_DECIMAL), ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/remember/RememberPlaybackSpeedPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/remember/RememberPlaybackSpeedPatch.kt index 75fcf392a..94c69dd69 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/remember/RememberPlaybackSpeedPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/remember/RememberPlaybackSpeedPatch.kt @@ -9,10 +9,10 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.shared.misc.settings.preference.ListPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch -import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.video.information.* import app.revanced.patches.youtube.video.speed.custom.customPlaybackSpeedPatch +import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction private const val EXTENSION_CLASS_DESCRIPTOR = @@ -30,15 +30,17 @@ internal val rememberPlaybackSpeedPatch = bytecodePatch { execute { addResources("youtube", "video.speed.remember.rememberPlaybackSpeedPatch") - PreferenceScreen.VIDEO.addPreferences( - SwitchPreference("revanced_remember_playback_speed_last_selected"), - ListPreference( - key = "revanced_playback_speed_default", - summaryKey = null, - // Entries and values are set by the extension code based on the actual speeds available. - entriesKey = null, - entryValuesKey = null, - ), + settingsMenuVideoSpeedGroup.addAll( + listOf( + ListPreference( + key = "revanced_playback_speed_default", + summaryKey = null, + // Entries and values are set by the extension code based on the actual speeds available. + entriesKey = null, + entryValuesKey = null, + ), + SwitchPreference("revanced_remember_playback_speed_last_selected") + ) ) onCreateHook(EXTENSION_CLASS_DESCRIPTOR, "newVideoStarted") diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 08e3b475c..5fa38af85 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1373,15 +1373,22 @@ Enabling this can unlock higher video qualities" + Video quality Auto Remember video quality changes Quality changes apply to all videos Quality changes only apply to the current video Default video quality on Wi-Fi network Default video quality on mobile network + Remember Shorts quality changes + Quality changes apply to all Shorts videos + Quality changes only apply to the current Shorts video + Default Shorts quality on Wi-Fi network + Default Shorts quality on mobile network mobile wifi Changed default %1$s quality to: %2$s + Changed Shorts %1$s quality to: %2$s Show speed dialog button