feat(YouTube - Remember video quality): Add separate Shorts default quality settings (#4543)

This commit is contained in:
alieRN 2025-03-06 09:46:33 +03:00 committed by GitHub
parent 74ec83f964
commit 88142ab464
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 312 additions and 87 deletions

View File

@ -158,16 +158,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
/** /**
* Syncs all UI Preferences to any {@link Setting} they represent. * 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 syncSettingValue,
boolean applySettingToPreference) { boolean applySettingToPreference) {
// Alternatively this could iterate thru all Settings and check for any matching Preferences, // 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 // but there are many more Settings than UI preferences so it's more efficient to only check
// the Preferences. // the Preferences.
for (int i = 0, prefCount = screen.getPreferenceCount(); i < prefCount; i++) { for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
Preference pref = screen.getPreference(i); Preference pref = group.getPreference(i);
if (pref instanceof PreferenceScreen) { if (pref instanceof PreferenceGroup subGroup) {
updatePreferenceScreen((PreferenceScreen) pref, syncSettingValue, applySettingToPreference); updatePreferenceScreen(subGroup, syncSettingValue, applySettingToPreference);
} else if (pref.hasKey()) { } else if (pref.hasKey()) {
String key = pref.getKey(); String key = pref.getKey();
Setting<?> setting = Setting.getSettingFromPath(key); Setting<?> setting = Setting.getSettingFromPath(key);

View File

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

View File

@ -1,7 +1,7 @@
package app.revanced.extension.youtube.patches; package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.ShortsPlayerState;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class BackgroundPlaybackPatch { public class BackgroundPlaybackPatch {
@ -23,16 +23,7 @@ public class BackgroundPlaybackPatch {
// 7. Close the Short // 7. Close the Short
// 8. Resume playing the regular video // 8. Resume playing the regular video
// 9. Minimize the app (PIP should appear) // 9. Minimize the app (PIP should appear)
if (!VideoInformation.lastVideoIdIsShort()) { return !ShortsPlayerState.isOpen();
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();
} }
/** /**

View File

@ -1,7 +1,7 @@
package app.revanced.extension.youtube.patches; package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.ShortsPlayerState;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class DisableAutoCaptionsPatch { public class DisableAutoCaptionsPatch {
@ -14,7 +14,7 @@ public class DisableAutoCaptionsPatch {
public static boolean autoCaptionsEnabled() { public static boolean autoCaptionsEnabled() {
return Settings.AUTO_CAPTIONS.get() return Settings.AUTO_CAPTIONS.get()
// Do not use auto captions for Shorts. // Do not use auto captions for Shorts.
&& !PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized(); && ShortsPlayerState.isOpen();
} }
} }

View File

@ -1,8 +1,11 @@
package app.revanced.extension.youtube.patches; package app.revanced.extension.youtube.patches;
import android.view.View;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
import app.revanced.extension.youtube.shared.VideoState; import app.revanced.extension.youtube.shared.VideoState;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -24,4 +27,26 @@ public class PlayerTypeHookPatch {
VideoState.setFromString(youTubeVideoState.name()); 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);
}
});
}
} }

View File

@ -12,15 +12,19 @@ import java.util.List;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.IntegerSetting; import app.revanced.extension.shared.settings.IntegerSetting;
import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class RememberVideoQualityPatch { public class RememberVideoQualityPatch {
private static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2; private static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2;
private static final IntegerSetting wifiQualitySetting = Settings.VIDEO_QUALITY_DEFAULT_WIFI; private static final IntegerSetting videoQualityWifi = Settings.VIDEO_QUALITY_DEFAULT_WIFI;
private static final IntegerSetting mobileQualitySetting = Settings.VIDEO_QUALITY_DEFAULT_MOBILE; 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; private static boolean qualityNeedsUpdating;
@ -41,17 +45,29 @@ public class RememberVideoQualityPatch {
@Nullable @Nullable
private static List<Integer> videoQualities; private static List<Integer> 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) { private static void changeDefaultQuality(int defaultQuality) {
String networkTypeMessage; String networkTypeMessage;
boolean useShortsPreference = ShortsPlayerState.isOpen();
if (Utils.getNetworkType() == NetworkType.MOBILE) { if (Utils.getNetworkType() == NetworkType.MOBILE) {
mobileQualitySetting.save(defaultQuality); if (useShortsPreference) shortsQualityMobile.save(defaultQuality);
else videoQualityMobile.save(defaultQuality);
networkTypeMessage = str("revanced_remember_video_quality_mobile"); networkTypeMessage = str("revanced_remember_video_quality_mobile");
} else { } else {
wifiQualitySetting.save(defaultQuality); if (useShortsPreference) shortsQualityWifi.save(defaultQuality);
else videoQualityWifi.save(defaultQuality);
networkTypeMessage = str("revanced_remember_video_quality_wifi"); networkTypeMessage = str("revanced_remember_video_quality_wifi");
} }
Utils.showToastShort( Utils.showToastShort(str(
str("revanced_remember_video_quality_toast", networkTypeMessage, (defaultQuality + "p"))); 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) { public static int setVideoQuality(Object[] qualities, final int originalQualityIndex, Object qInterface, String qIndexMethod) {
try { try {
boolean useShortsPreference = ShortsPlayerState.isOpen();
final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE
? mobileQualitySetting.get() ? (useShortsPreference ? shortsQualityMobile : videoQualityMobile).get()
: wifiQualitySetting.get(); : (useShortsPreference ? shortsQualityWifi : videoQualityWifi).get();
if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) { if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) {
return originalQualityIndex; // Nothing to do. return originalQualityIndex; // Nothing to do.
@ -141,17 +158,17 @@ public class RememberVideoQualityPatch {
* Injection point. Old quality menu. * Injection point. Old quality menu.
*/ */
public static void userChangedQuality(int selectedQualityIndex) { public static void userChangedQuality(int selectedQualityIndex) {
if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.get()) return; if (shouldRememberVideoQuality()) {
userSelectedQualityIndex = selectedQualityIndex; userSelectedQualityIndex = selectedQualityIndex;
userChangedDefaultQuality = true; userChangedDefaultQuality = true;
} }
}
/** /**
* Injection point. New quality menu. * Injection point. New quality menu.
*/ */
public static void userChangedQualityInNewFlyout(int selectedQuality) { 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). changeDefaultQuality(selectedQuality); // Quality is human readable resolution (ie: 1080).
} }

View File

@ -48,10 +48,13 @@ import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
public class Settings extends BaseSettings { public class Settings extends BaseSettings {
// Video // Video
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE); 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_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 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 // Speed
public static final FloatSetting SPEED_TAP_AND_HOLD = new FloatSetting("revanced_speed_tap_and_hold", 2.0f, true); 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); public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", FALSE);

View File

@ -5,7 +5,7 @@ import app.revanced.extension.youtube.Event
import app.revanced.extension.youtube.patches.VideoInformation import app.revanced.extension.youtube.patches.VideoInformation
/** /**
* Main player type. * Regular player type.
*/ */
enum class PlayerType { 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, * 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. * 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]. * To include those situations instead use [isNoneHiddenOrMinimized].
*
* @see VideoInformation
*/ */
fun isNoneOrHidden(): Boolean { fun isNoneOrHidden(): Boolean {
return this == NONE || this == HIDDEN return this == NONE || this == HIDDEN
@ -107,8 +105,11 @@ enum class PlayerType {
* when spoofing to an old version this will return false even * when spoofing to an old version this will return false even
* though a Short is being opened or is on screen (see [isNoneHiddenOrMinimized]). * 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. * @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 { fun isNoneHiddenOrSlidingMinimized(): Boolean {
return isNoneOrHidden() || this == WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED 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, * 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. * 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, * @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). * a regular video is minimized (and a new video is not being opened).
* @see VideoInformation * @see ShortsPlayerState
*/ */
fun isNoneHiddenOrMinimized(): Boolean { fun isNoneHiddenOrMinimized(): Boolean {
return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED

View File

@ -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<Boolean>()
/**
* If the Shorts player is currently open.
*/
@JvmStatic
fun isOpen(): Boolean {
return isOpen
}
}
}

View File

@ -17,7 +17,7 @@ import org.w3c.dom.Element
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
abstract class BasePreference( abstract class BasePreference(
val key: String? = null, val key: String? = null,
val titleKey: String = "${key}_title", val titleKey: String? = "${key}_title",
val summaryKey: String? = "${key}_summary", val summaryKey: String? = "${key}_summary",
val icon: String? = null, val icon: String? = null,
val layout: String? = null, val layout: String? = null,
@ -35,7 +35,7 @@ abstract class BasePreference(
open fun serialize(ownerDocument: Document, resourceCallback: (BaseResource) -> Unit): Element = open fun serialize(ownerDocument: Document, resourceCallback: (BaseResource) -> Unit): Element =
ownerDocument.createElement(tag).apply { ownerDocument.createElement(tag).apply {
key?.let { setAttribute("android:key", it) } key?.let { setAttribute("android:key", it) }
setAttribute("android:title", "@string/${titleKey}") titleKey?.let { setAttribute("android:title", "@string/${titleKey}") }
summaryKey?.let { addSummary(it) } summaryKey?.let { addSummary(it) }
icon?.let { icon?.let {
setAttribute("android:icon", it) setAttribute("android:icon", it)

View File

@ -17,7 +17,7 @@ import org.w3c.dom.Document
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
open class PreferenceCategory( open class PreferenceCategory(
key: String? = null, key: String? = null,
titleKey: String = "${key}_title", titleKey: String? = "${key}_title",
icon: String? = null, icon: String? = null,
layout: String? = null, layout: String? = null,
sorting: Sorting = Sorting.BY_TITLE, sorting: Sorting = Sorting.BY_TITLE,

View File

@ -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.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings 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.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.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_17_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_17_or_greater
@ -71,6 +73,15 @@ val spoofAppVersionPatch = bytecodePatch(
addResources("youtube", "layout.spoofappversion.spoofAppVersionPatch") addResources("youtube", "layout.spoofappversion.spoofAppVersionPatch")
PreferenceScreen.GENERAL_LAYOUT.addPreferences( PreferenceScreen.GENERAL_LAYOUT.addPreferences(
// 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"), SwitchPreference("revanced_spoof_app_version"),
if (is_19_17_or_greater) { if (is_19_17_or_greater) {
ListPreference( ListPreference(
@ -86,6 +97,8 @@ val spoofAppVersionPatch = bytecodePatch(
) )
} }
) )
)
)
/** /**
* If a user really wants to spoof to very old versions with the latest app target * If a user really wants to spoof to very old versions with the latest app target

View File

@ -1,6 +1,7 @@
package app.revanced.patches.youtube.misc.playertype package app.revanced.patches.youtube.misc.playertype
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
@ -15,6 +16,12 @@ internal val playerTypeFingerprint = fingerprint {
custom { _, classDef -> classDef.endsWith("/YouTubePlayerOverlaysLayout;") } custom { _, classDef -> classDef.endsWith("/YouTubePlayerOverlaysLayout;") }
} }
internal val reelWatchPagerFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("Landroid/view/View;")
literal { reelWatchPlayerId }
}
internal val videoStateFingerprint = fingerprint { internal val videoStateFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V") returns("V")

View File

@ -4,15 +4,34 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch 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.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 import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/PlayerTypeHookPatch;" 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( val playerTypeHookPatch = bytecodePatch(
description = "Hook to get the current player type and video playback state.", description = "Hook to get the current player type and video playback state.",
) { ) {
dependsOn(sharedExtensionPatch) dependsOn(sharedExtensionPatch, playerTypeHookResourcePatch)
execute { execute {
playerTypeFingerprint.method.addInstruction( playerTypeFingerprint.method.addInstruction(
@ -20,6 +39,17 @@ val playerTypeHookPatch = bytecodePatch(
"invoke-static {p1}, $EXTENSION_CLASS_DESCRIPTOR->setPlayerType(Ljava/lang/Enum;)V", "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<OneRegisterInstruction>(registerIndex).registerA
addInstruction(
registerIndex + 1,
"invoke-static { v$viewRegister }, $EXTENSION_CLASS_DESCRIPTOR->onShortsCreate(Landroid/view/View;)V"
)
}
videoStateFingerprint.method.apply { videoStateFingerprint.method.apply {
val endIndex = videoStateFingerprint.patternMatch!!.endIndex val endIndex = videoStateFingerprint.patternMatch!!.endIndex
val videoStateFieldName = getInstruction<ReferenceInstruction>(endIndex).reference val videoStateFieldName = getInstruction<ReferenceInstruction>(endIndex).reference
@ -29,7 +59,7 @@ val playerTypeHookPatch = bytecodePatch(
""" """
iget-object v0, p1, $videoStateFieldName # copy VideoState parameter field iget-object v0, p1, $videoStateFieldName # copy VideoState parameter field
invoke-static {v0}, $EXTENSION_CLASS_DESCRIPTOR->setVideoState(Ljava/lang/Enum;)V invoke-static {v0}, $EXTENSION_CLASS_DESCRIPTOR->setVideoState(Ljava/lang/Enum;)V
""", """
) )
} }
} }

View File

@ -323,6 +323,7 @@ object PreferenceScreen : BasePreferenceScreen() {
val VIDEO = Screen( val VIDEO = Screen(
key = "revanced_settings_screen_12_video", key = "revanced_settings_screen_12_video",
summaryKey = null, summaryKey = null,
sorting = Sorting.BY_KEY,
) )
override fun commit(screen: PreferenceScreenPreference) { override fun commit(screen: PreferenceScreenPreference) {

View File

@ -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.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch 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.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.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch 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.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint
@ -29,6 +32,7 @@ val rememberVideoQualityPatch = bytecodePatch(
dependsOn( dependsOn(
sharedExtensionPatch, sharedExtensionPatch,
videoInformationPatch, videoInformationPatch,
playerTypeHookPatch,
settingsPatch, settingsPatch,
addResourcesPatch, addResourcesPatch,
) )
@ -49,19 +53,42 @@ val rememberVideoQualityPatch = bytecodePatch(
addResources("youtube", "video.quality.rememberVideoQualityPatch") addResources("youtube", "video.quality.rememberVideoQualityPatch")
PreferenceScreen.VIDEO.addPreferences( PreferenceScreen.VIDEO.addPreferences(
SwitchPreference("revanced_remember_video_quality_last_selected"), // Keep the preferences organized together.
ListPreference( PreferenceCategory(
key = "revanced_video_quality_default_wifi", key = "revanced_01_video_key", // Dummy key to force the quality preferences first.
summaryKey = null, titleKey = null,
entriesKey = "revanced_video_quality_default_entries", sorting = Sorting.UNSORTED,
entryValuesKey = "revanced_video_quality_default_entry_values", tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
), preferences = setOf(
ListPreference( ListPreference(
key = "revanced_video_quality_default_mobile", key = "revanced_video_quality_default_mobile",
summaryKey = null, summaryKey = null,
entriesKey = "revanced_video_quality_default_entries", entriesKey = "revanced_video_quality_default_entries",
entryValuesKey = "revanced_video_quality_default_entry_values", 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")
)
)
) )
/* /*

View File

@ -1,10 +1,19 @@
package app.revanced.patches.youtube.video.speed package app.revanced.patches.youtube.video.speed
import app.revanced.patcher.patch.bytecodePatch 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.button.playbackSpeedButtonPatch
import app.revanced.patches.youtube.video.speed.custom.customPlaybackSpeedPatch import app.revanced.patches.youtube.video.speed.custom.customPlaybackSpeedPatch
import app.revanced.patches.youtube.video.speed.remember.rememberPlaybackSpeedPatch import app.revanced.patches.youtube.video.speed.remember.rememberPlaybackSpeedPatch
/**
* Speed menu settings. Used to organize all speed related settings together.
*/
internal val settingsMenuVideoSpeedGroup = mutableSetOf<BasePreference>()
@Suppress("unused") @Suppress("unused")
val playbackSpeedPatch = bytecodePatch( val playbackSpeedPatch = bytecodePatch(
name = "Playback speed", name = "Playback speed",
@ -26,6 +35,18 @@ val playbackSpeedPatch = bytecodePatch(
"19.45.38", "19.45.38",
"19.46.42", "19.46.42",
"19.47.53", "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
)
) )
} }
}

View File

@ -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.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTreeHook import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTreeHook
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch 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.misc.settings.settingsPatch
import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup
import app.revanced.util.* import app.revanced.util.*
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction
@ -71,13 +71,18 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
execute { execute {
addResources("youtube", "video.speed.custom.customPlaybackSpeedPatch") addResources("youtube", "video.speed.custom.customPlaybackSpeedPatch")
PreferenceScreen.VIDEO.addPreferences( settingsMenuVideoSpeedGroup.addAll(
listOf(
SwitchPreference("revanced_custom_speed_menu"), SwitchPreference("revanced_custom_speed_menu"),
TextPreference("revanced_custom_playback_speeds", inputType = InputType.TEXT_MULTI_LINE), TextPreference(
"revanced_custom_playback_speeds",
inputType = InputType.TEXT_MULTI_LINE
),
)
) )
if (is_19_25_or_greater) { if (is_19_25_or_greater) {
PreferenceScreen.VIDEO.addPreferences( settingsMenuVideoSpeedGroup.add(
TextPreference("revanced_speed_tap_and_hold", inputType = InputType.NUMBER_DECIMAL), TextPreference("revanced_speed_tap_and_hold", inputType = InputType.NUMBER_DECIMAL),
) )
} }

View File

@ -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.ListPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch 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.misc.settings.settingsPatch
import app.revanced.patches.youtube.video.information.* import app.revanced.patches.youtube.video.information.*
import app.revanced.patches.youtube.video.speed.custom.customPlaybackSpeedPatch 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 import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
private const val EXTENSION_CLASS_DESCRIPTOR = private const val EXTENSION_CLASS_DESCRIPTOR =
@ -30,8 +30,8 @@ internal val rememberPlaybackSpeedPatch = bytecodePatch {
execute { execute {
addResources("youtube", "video.speed.remember.rememberPlaybackSpeedPatch") addResources("youtube", "video.speed.remember.rememberPlaybackSpeedPatch")
PreferenceScreen.VIDEO.addPreferences( settingsMenuVideoSpeedGroup.addAll(
SwitchPreference("revanced_remember_playback_speed_last_selected"), listOf(
ListPreference( ListPreference(
key = "revanced_playback_speed_default", key = "revanced_playback_speed_default",
summaryKey = null, summaryKey = null,
@ -39,6 +39,8 @@ internal val rememberPlaybackSpeedPatch = bytecodePatch {
entriesKey = null, entriesKey = null,
entryValuesKey = null, entryValuesKey = null,
), ),
SwitchPreference("revanced_remember_playback_speed_last_selected")
)
) )
onCreateHook(EXTENSION_CLASS_DESCRIPTOR, "newVideoStarted") onCreateHook(EXTENSION_CLASS_DESCRIPTOR, "newVideoStarted")

View File

@ -1373,15 +1373,22 @@ Enabling this can unlock higher video qualities"</string>
</patch> </patch>
<patch id="video.quality.rememberVideoQualityPatch"> <patch id="video.quality.rememberVideoQualityPatch">
<!-- Translations should use the same text as revanced_custom_playback_speeds_auto --> <!-- Translations should use the same text as revanced_custom_playback_speeds_auto -->
<string name="revanced_video_quality_screen_title">Video quality</string>
<string name="revanced_video_quality_default_entry_1">Auto</string> <string name="revanced_video_quality_default_entry_1">Auto</string>
<string name="revanced_remember_video_quality_last_selected_title">Remember video quality changes</string> <string name="revanced_remember_video_quality_last_selected_title">Remember video quality changes</string>
<string name="revanced_remember_video_quality_last_selected_summary_on">Quality changes apply to all videos</string> <string name="revanced_remember_video_quality_last_selected_summary_on">Quality changes apply to all videos</string>
<string name="revanced_remember_video_quality_last_selected_summary_off">Quality changes only apply to the current video</string> <string name="revanced_remember_video_quality_last_selected_summary_off">Quality changes only apply to the current video</string>
<string name="revanced_video_quality_default_wifi_title">Default video quality on Wi-Fi network</string> <string name="revanced_video_quality_default_wifi_title">Default video quality on Wi-Fi network</string>
<string name="revanced_video_quality_default_mobile_title">Default video quality on mobile network</string> <string name="revanced_video_quality_default_mobile_title">Default video quality on mobile network</string>
<string name="revanced_remember_shorts_quality_last_selected_title">Remember Shorts quality changes</string>
<string name="revanced_remember_shorts_quality_last_selected_summary_on">Quality changes apply to all Shorts videos</string>
<string name="revanced_remember_shorts_quality_last_selected_summary_off">Quality changes only apply to the current Shorts video</string>
<string name="revanced_shorts_quality_default_wifi_title">Default Shorts quality on Wi-Fi network</string>
<string name="revanced_shorts_quality_default_mobile_title">Default Shorts quality on mobile network</string>
<string name="revanced_remember_video_quality_mobile">mobile</string> <string name="revanced_remember_video_quality_mobile">mobile</string>
<string name="revanced_remember_video_quality_wifi">wifi</string> <string name="revanced_remember_video_quality_wifi">wifi</string>
<string name="revanced_remember_video_quality_toast">Changed default %1$s quality to: %2$s</string> <string name="revanced_remember_video_quality_toast">Changed default %1$s quality to: %2$s</string>
<string name="revanced_remember_video_quality_toast_shorts">Changed Shorts %1$s quality to: %2$s</string>
</patch> </patch>
<patch id="video.speed.button.playbackSpeedButtonPatch"> <patch id="video.speed.button.playbackSpeedButtonPatch">
<string name="revanced_playback_speed_dialog_button_title">Show speed dialog button</string> <string name="revanced_playback_speed_dialog_button_title">Show speed dialog button</string>