From 19f146c01dc381b3cccd61e61ba4901872ff12d8 Mon Sep 17 00:00:00 2001 From: ekaunt <62402760+ekaunt@users.noreply.github.com> Date: Mon, 12 Jan 2026 17:18:09 -0500 Subject: [PATCH] feat(YouTube): Add `Pause on audio interrupt` patch (#6464) Co-authored-by: bengross Co-authored-by: oSumAtrIX --- .../patches/PauseOnAudioInterruptPatch.java | 30 +++++++++ .../extension/youtube/settings/Settings.java | 1 + patches/api/patches.api | 4 ++ .../youtube/misc/audiofocus/Fingerprints.kt | 14 ++++ .../audiofocus/PauseOnAudioInterruptPatch.kt | 66 +++++++++++++++++++ .../resources/addresources/values/strings.xml | 5 ++ 6 files changed, 120 insertions(+) create mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PauseOnAudioInterruptPatch.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PauseOnAudioInterruptPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PauseOnAudioInterruptPatch.java new file mode 100644 index 000000000..cf010a151 --- /dev/null +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/PauseOnAudioInterruptPatch.java @@ -0,0 +1,30 @@ +package app.revanced.extension.youtube.patches; + +import app.revanced.extension.youtube.settings.Settings; + +@SuppressWarnings("unused") +public class PauseOnAudioInterruptPatch { + + private static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK = -3; + private static final int AUDIOFOCUS_LOSS_TRANSIENT = -2; + + /** + * Injection point for AudioFocusRequest builder. + * Returns true if audio ducking should be disabled (willPauseWhenDucked = true). + */ + public static boolean shouldPauseOnAudioInterrupt() { + return Settings.PAUSE_ON_AUDIO_INTERRUPT.get(); + } + + /** + * Injection point for onAudioFocusChange callback. + * Converts AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK to AUDIOFOCUS_LOSS_TRANSIENT + * when the setting is enabled, causing YouTube to pause instead of ducking. + */ + public static int overrideAudioFocusChange(int focusChange) { + if (Settings.PAUSE_ON_AUDIO_INTERRUPT.get() && focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { + return AUDIOFOCUS_LOSS_TRANSIENT; + } + return focusChange; + } +} 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 62fa7bcae..21f3184b5 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 @@ -356,6 +356,7 @@ public class Settings extends BaseSettings { public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false); public static final BooleanSetting LOOP_VIDEO = new BooleanSetting("revanced_loop_video", FALSE); public static final BooleanSetting LOOP_VIDEO_BUTTON = new BooleanSetting("revanced_loop_video_button", FALSE); + public static final BooleanSetting PAUSE_ON_AUDIO_INTERRUPT = new BooleanSetting("revanced_pause_on_audio_interrupt", FALSE, true); public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE); public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_CHAPTERS = new BooleanSetting("revanced_disable_haptic_feedback_chapters", FALSE); public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_PRECISE_SEEKING = new BooleanSetting("revanced_disable_haptic_feedback_precise_seeking", FALSE); diff --git a/patches/api/patches.api b/patches/api/patches.api index 86d8f980b..8142a54dc 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -1696,6 +1696,10 @@ public final class app/revanced/patches/youtube/misc/announcements/Announcements public static final fun getAnnouncementsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatchKt { + public static final fun getPauseOnAudioInterruptPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/youtube/misc/autorepeat/AutoRepeatPatchKt { public static final fun getAutoRepeatPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/Fingerprints.kt new file mode 100644 index 000000000..5fb44f20f --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/Fingerprints.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.youtube.misc.audiofocus + +import app.revanced.patcher.fingerprint + +internal val audioFocusChangeListenerFingerprint = fingerprint { + strings( + "AudioFocus DUCK", + "AudioFocus loss; Will lower volume", + ) +} + +internal val audioFocusRequestBuilderFingerprint = fingerprint { + strings("Can't build an AudioFocusRequestCompat instance without a listener") +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt new file mode 100644 index 000000000..2f6317d6e --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/audiofocus/PauseOnAudioInterruptPatch.kt @@ -0,0 +1,66 @@ +package app.revanced.patches.youtube.misc.audiofocus + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +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 + +private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/PauseOnAudioInterruptPatch;" + +val pauseOnAudioInterruptPatch = bytecodePatch( + name = "Pause on audio interrupt", + description = "Adds an option to pause playback instead of lowering volume when other audio plays.", +) { + dependsOn( + sharedExtensionPatch, + settingsPatch, + addResourcesPatch, + ) + + compatibleWith( + "com.google.android.youtube"( + "20.14.43", + ) + ) + + execute { + addResources("youtube", "misc.audiofocus.pauseOnAudioInterruptPatch") + + PreferenceScreen.MISC.addPreferences( + SwitchPreference("revanced_pause_on_audio_interrupt"), + ) + + // Hook the builder method that creates AudioFocusRequest. + // At the start, set the willPauseWhenDucked field (b) to true if setting is enabled. + val builderMethod = audioFocusRequestBuilderFingerprint.method + val builderClass = builderMethod.definingClass + + builderMethod.addInstructionsWithLabels( + 0, + """ + invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->shouldPauseOnAudioInterrupt()Z + move-result v0 + if-eqz v0, :skip_override + const/4 v0, 0x1 + iput-boolean v0, p0, $builderClass->b:Z + """, + ExternalLabel("skip_override", builderMethod.getInstruction(0)), + ) + + // Also hook the audio focus change listener as a backup. + audioFocusChangeListenerFingerprint.method.addInstructions( + 0, + """ + invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->overrideAudioFocusChange(I)I + move-result p1 + """ + ) + } +} diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 53e99d70d..9c2a55e0e 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1558,6 +1558,11 @@ Tap here to learn more about DeArrow" Loop video is on Loop video is off + + Pause on audio interrupt + Playback pauses when other audio plays (e.g. navigation) + Volume lowers when other audio plays + Spoof device dimensions "Device dimensions spoofed