diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/BackgroundPlaybackPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/BackgroundPlaybackPatch.java index d3fc82ae2c..7e5f59a6e1 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/BackgroundPlaybackPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/BackgroundPlaybackPatch.java @@ -1,5 +1,6 @@ package app.revanced.extension.youtube.patches; +import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.PlayerType; @SuppressWarnings("unused") @@ -8,10 +9,10 @@ public class BackgroundPlaybackPatch { /** * Injection point. */ - public static boolean allowBackgroundPlayback(boolean original) { + public static boolean isBackgroundPlaybackAllowed(boolean original) { if (original) return true; - // Steps to verify most edge cases: + // Steps to verify most edge cases (with Shorts background playback set to off): // 1. Open a regular video // 2. Minimize app (PIP should appear) // 3. Reopen app @@ -22,13 +23,13 @@ public static boolean allowBackgroundPlayback(boolean original) { // 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. } - // Might be a Short, or might be a prior regular video on screen again after a Short was closed. - // This incorrectly prevents PIP if player is in WATCH_WHILE_MINIMIZED after closing 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(); @@ -37,10 +38,7 @@ public static boolean allowBackgroundPlayback(boolean original) { /** * Injection point. */ - public static boolean overrideBackgroundPlaybackAvailable() { - // This could be done entirely in the patch, - // but having a unique method to search for makes manually inspecting the patched apk much easier. - return true; + public static boolean isBackgroundShortsPlaybackAllowed(boolean original) { + return !Settings.DISABLE_SHORTS_BACKGROUND_PLAYBACK.get(); } - } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 52fe03290d..fe5f70fd9a 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -214,6 +214,7 @@ public class Settings extends BaseSettings { public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true); // Shorts + public static final BooleanSetting DISABLE_SHORTS_BACKGROUND_PLAYBACK = new BooleanSetting("revanced_shorts_disable_background_playback", FALSE); public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", FALSE); public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE); public static final BooleanSetting HIDE_SHORTS_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_shorts_subscriptions", FALSE); diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt index 9f8a3f6a1b..a7911af0ee 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt @@ -1,30 +1,33 @@ package app.revanced.patches.youtube.misc.backgroundplayback -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.extensions.InstructionExtensions.instructions import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch 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.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.video.information.videoInformationPatch import app.revanced.util.addInstructionsAtControlFlowLabel import app.revanced.util.findInstructionIndicesReversedOrThrow +import app.revanced.util.getReference +import app.revanced.util.returnEarly 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.reference.MethodReference internal var prefBackgroundAndOfflineCategoryId = -1L private set private val backgroundPlaybackResourcePatch = resourcePatch { - dependsOn(resourceMappingPatch) + dependsOn(resourceMappingPatch, addResourcesPatch) execute { prefBackgroundAndOfflineCategoryId = resourceMappings["string", "pref_background_and_offline_category"] @@ -58,45 +61,54 @@ val backgroundPlaybackPatch = bytecodePatch( val backgroundPlaybackManagerMatch by backgroundPlaybackManagerFingerprint() val backgroundPlaybackSettingsMatch by backgroundPlaybackSettingsFingerprint() + + val shortsBackgroundPlaybackFeatureFlagMatch by shortsBackgroundPlaybackFeatureFlagFingerprint() + val backgroundPlaybackManagerShortsMatch by backgroundPlaybackManagerShortsFingerprint() + val kidsBackgroundPlaybackPolicyControllerMatch by kidsBackgroundPlaybackPolicyControllerFingerprint() execute { context -> - backgroundPlaybackManagerMatch.mutableMethod.apply { - findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index -> - val register = getInstruction(index).registerA - - addInstructionsAtControlFlowLabel( - index, - """ - invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->allowBackgroundPlayback(Z)Z - move-result v$register - """, - ) + addResources("youtube", "misc.backgroundplayback.backgroundPlaybackPatch") + + PreferenceScreen.SHORTS.addPreferences( + SwitchPreference("revanced_shorts_disable_background_playback") + ) + + arrayOf( + backgroundPlaybackManagerMatch to "isBackgroundPlaybackAllowed", + backgroundPlaybackManagerShortsMatch to "isBackgroundShortsPlaybackAllowed" + ).forEach { (match, integrationsMethod) -> + match.mutableMethod.apply { + findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index -> + val register = getInstruction(index).registerA + + addInstructionsAtControlFlowLabel( + index, + """ + invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->$integrationsMethod(Z)Z + move-result v$register + """ + ) + } } } // Enable background playback option in YouTube settings backgroundPlaybackSettingsMatch.mutableMethod.apply { - val booleanCalls = instructions.withIndex() - .filter { ((it.value as? ReferenceInstruction)?.reference as? MethodReference)?.returnType == "Z" } + val booleanCalls = instructions.withIndex().filter { + it.value.getReference()?.returnType == "Z" + } val settingsBooleanIndex = booleanCalls.elementAt(1).index val settingsBooleanMethod = context.navigate(this).at(settingsBooleanIndex).mutable() - settingsBooleanMethod.addInstructions( - 0, - """ - invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->overrideBackgroundPlaybackAvailable()Z - move-result v0 - return v0 - """, - ) + settingsBooleanMethod.returnEarly(true) } + // Force allowing background play for Shorts. + shortsBackgroundPlaybackFeatureFlagMatch.mutableMethod.returnEarly(true) + // Force allowing background play for videos labeled for kids. - kidsBackgroundPlaybackPolicyControllerMatch.mutableMethod.addInstruction( - 0, - "return-void", - ) + kidsBackgroundPlaybackPolicyControllerMatch.mutableMethod.returnEarly() } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/Fingerprints.kt index aa4f6f9220..b12c8157c6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/Fingerprints.kt @@ -70,3 +70,17 @@ internal val kidsBackgroundPlaybackPolicyControllerFingerprint = fingerprint { ) literal { 5 } } + +internal val backgroundPlaybackManagerShortsFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) + returns("Z") + parameters("L") + literal { 151635310 } +} + +internal val shortsBackgroundPlaybackFeatureFlagFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returns("Z") + parameters() + literal { 45415425 } +} \ No newline at end of file diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 7fda77ff62..d801d5f2a9 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -86,6 +86,11 @@ This is because Crowdin requires temporarily flattening this file and removing t Misc Video + + Disable Shorts background play + Shorts background play is disabled + Shorts background play is enabled + Debugging Enable or disable debugging options