From 64af7fd8b6f1f03cd1d15c55ae2519d747b91147 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Tue, 1 Apr 2025 19:02:55 +0900 Subject: [PATCH] fix(YouTube - Video playback): Overridden to default playback speed in `onResume` callback https://github.com/inotia00/ReVanced_Extended/issues/2896 --- .../patches/video/PlaybackSpeedPatch.java | 131 +++++++++++------- .../utils/dismiss/DismissPlayerHookPatch.kt | 33 ++++- .../video/playback/VideoPlaybackPatch.kt | 6 +- 3 files changed, 119 insertions(+), 51 deletions(-) diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java index 58897b35d..895a793a2 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java @@ -3,10 +3,14 @@ package app.revanced.extension.youtube.patches.video; import static app.revanced.extension.shared.utils.StringRef.str; import static app.revanced.extension.youtube.shared.RootView.isShortsActive; +import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import org.apache.commons.lang3.BooleanUtils; +import java.util.LinkedHashMap; +import java.util.Map; + import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.FloatSetting; import app.revanced.extension.shared.utils.Logger; @@ -28,48 +32,61 @@ public class PlaybackSpeedPatch { Settings.DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC.get(); private static final long TOAST_DELAY_MILLISECONDS = 750; private static long lastTimeSpeedChanged; + + /** + * The last used playback speed. + * This value is used when the default playback speed is 'Auto'. + */ private static float lastSelectedPlaybackSpeed = 1.0f; + private static float lastSelectedShortsPlaybackSpeed = 1.0f; - private static volatile String channelId = ""; - private static volatile String videoId = ""; - private static boolean isLiveStream; + /** + * The last regular video id. + */ + private static String videoId = ""; - private static volatile String channelIdShorts = ""; - private static volatile String videoIdShorts = ""; - private static boolean isLiveStreamShorts; + @GuardedBy("itself") + private static final Map ignoredPlaybackSpeedVideoIds = new LinkedHashMap<>() { + private static final int NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK = 3; + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK; + } + }; /** * Injection point. + * This method is used to reset the playback speed to 1.0 when a general video is started, whether it is a live stream, music, or whitelist. */ public static void newVideoStarted(@NonNull String newlyLoadedChannelId, @NonNull String newlyLoadedChannelName, @NonNull String newlyLoadedVideoId, @NonNull String newlyLoadedVideoTitle, final long newlyLoadedVideoLength, boolean newlyLoadedLiveStreamValue) { if (isShortsActive()) { - channelIdShorts = newlyLoadedChannelId; - videoIdShorts = newlyLoadedVideoId; - isLiveStreamShorts = newlyLoadedLiveStreamValue; - - Logger.printDebug(() -> "newVideoStarted: " + newlyLoadedVideoId); - } else { - channelId = newlyLoadedChannelId; - videoId = newlyLoadedVideoId; - isLiveStream = newlyLoadedLiveStreamValue; - - Logger.printDebug(() -> "newShortsVideoStarted: " + newlyLoadedVideoId); + return; } - } + if (videoId.equals(newlyLoadedVideoId)) { + return; + } + videoId = newlyLoadedVideoId; - /** - * Injection point. - */ - public static void newShortsVideoStarted(@NonNull String newlyLoadedChannelId, @NonNull String newlyLoadedChannelName, - @NonNull String newlyLoadedVideoId, @NonNull String newlyLoadedVideoTitle, - final long newlyLoadedVideoLength, boolean newlyLoadedLiveStreamValue) { - channelIdShorts = newlyLoadedChannelId; - videoIdShorts = newlyLoadedVideoId; - isLiveStreamShorts = newlyLoadedLiveStreamValue; + boolean isMusic = isMusic(newlyLoadedVideoId); + boolean isWhitelisted = Whitelist.isChannelWhitelistedPlaybackSpeed(newlyLoadedVideoId); - Logger.printDebug(() -> "newShortsVideoStarted: " + newlyLoadedVideoId); + if (newlyLoadedLiveStreamValue || isMusic || isWhitelisted) { + synchronized(ignoredPlaybackSpeedVideoIds) { + if (!ignoredPlaybackSpeedVideoIds.containsKey(newlyLoadedVideoId)) { + lastSelectedPlaybackSpeed = 1.0f; + ignoredPlaybackSpeedVideoIds.put(newlyLoadedVideoId, lastSelectedPlaybackSpeed); + + VideoInformation.setPlaybackSpeed(lastSelectedPlaybackSpeed); + VideoInformation.overridePlaybackSpeed(lastSelectedPlaybackSpeed); + + Logger.printDebug(() -> "changing playback speed to: 1.0, isLiveStream: " + newlyLoadedLiveStreamValue + + ", isMusic: " + isMusic + ", isWhitelisted: " + isWhitelisted); + } + } + } } /** @@ -98,32 +115,29 @@ public class PlaybackSpeedPatch { /** * Injection point. + * This method is called every second for regular videos and Shorts. */ public static float getPlaybackSpeed(float playbackSpeed) { boolean isShorts = isShortsActive(); - String currentChannelId = isShorts ? channelIdShorts : channelId; - String currentVideoId = isShorts ? videoIdShorts : videoId; - boolean currentVideoIsLiveStream = isShorts ? isLiveStreamShorts : isLiveStream; - boolean currentVideoIsWhitelisted = Whitelist.isChannelWhitelistedPlaybackSpeed(currentChannelId); - boolean currentVideoIsMusic = !isShorts && isMusic(); - - if (currentVideoIsLiveStream || currentVideoIsWhitelisted || currentVideoIsMusic) { - Logger.printDebug(() -> "changing playback speed to: 1.0"); - VideoInformation.setPlaybackSpeed(1.0f); - return 1.0f; - } - float defaultPlaybackSpeed = isShorts ? DEFAULT_PLAYBACK_SPEED_SHORTS.get() : DEFAULT_PLAYBACK_SPEED.get(); - if (defaultPlaybackSpeed < 0) { - float finalPlaybackSpeed = isShorts ? playbackSpeed : lastSelectedPlaybackSpeed; + if (defaultPlaybackSpeed < 0) { // If the default playback speed is 'Auto', it will be overridden to the last used playback speed. + float finalPlaybackSpeed = isShorts ? lastSelectedShortsPlaybackSpeed : lastSelectedPlaybackSpeed; VideoInformation.overridePlaybackSpeed(finalPlaybackSpeed); Logger.printDebug(() -> "changing playback speed to: " + finalPlaybackSpeed); return finalPlaybackSpeed; - } else { - if (isShorts) { - VideoInformation.setPlaybackSpeed(defaultPlaybackSpeed); + } else { // Otherwise the default playback speed is used. + synchronized (ignoredPlaybackSpeedVideoIds) { + if (isShorts) { + // For Shorts, the VideoInformation.overridePlaybackSpeed() method is not used, so manually save the playback speed in VideoInformation. + VideoInformation.setPlaybackSpeed(defaultPlaybackSpeed); + } else if (ignoredPlaybackSpeedVideoIds.containsKey(videoId)) { + // For general videos, check whether the default video playback speed should not be applied. + Logger.printDebug(() -> "changing playback speed to: 1.0"); + return 1.0f; + } } + Logger.printDebug(() -> "changing playback speed to: " + defaultPlaybackSpeed); return defaultPlaybackSpeed; } @@ -138,6 +152,19 @@ public class PlaybackSpeedPatch { public static void userSelectedPlaybackSpeed(float playbackSpeed) { try { boolean isShorts = isShortsActive(); + + // Saves the user-selected playback speed in the method. + if (isShorts) { + lastSelectedShortsPlaybackSpeed = playbackSpeed; + } else { + lastSelectedPlaybackSpeed = playbackSpeed; + // If the user has manually changed the playback speed, the whitelist has already been applied. + // If there is a videoId on the map, it will be removed. + synchronized (ignoredPlaybackSpeedVideoIds) { + ignoredPlaybackSpeedVideoIds.remove(videoId); + } + } + if (PatchStatus.RememberPlaybackSpeed()) { BooleanSetting rememberPlaybackSpeedLastSelectedSetting = isShorts ? Settings.REMEMBER_PLAYBACK_SPEED_SHORTS_LAST_SELECTED @@ -178,15 +205,23 @@ public class PlaybackSpeedPatch { } }, TOAST_DELAY_MILLISECONDS); } - } else if (!isShorts) { - lastSelectedPlaybackSpeed = playbackSpeed; } } catch (Exception ex) { Logger.printException(() -> "userSelectedPlaybackSpeed failure", ex); } } - private static boolean isMusic() { + /** + * Injection point. + */ + public static void onDismiss() { + synchronized (ignoredPlaybackSpeedVideoIds) { + ignoredPlaybackSpeedVideoIds.remove(videoId); + videoId = ""; + } + } + + private static boolean isMusic(String videoId) { if (DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC && !videoId.isEmpty()) { try { MusicRequest request = MusicRequest.getRequestForVideoId(videoId); diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/dismiss/DismissPlayerHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/dismiss/DismissPlayerHookPatch.kt index fb390deff..45e71e6ef 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/dismiss/DismissPlayerHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/dismiss/DismissPlayerHookPatch.kt @@ -3,12 +3,14 @@ package app.revanced.patches.youtube.utils.dismiss import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patches.youtube.utils.extension.Constants.EXTENSION_PATH import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch import app.revanced.util.addStaticFieldToExtension import app.revanced.util.findMethodOrThrow import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.getReference +import app.revanced.util.getWalkerMethod import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.indexOfFirstLiteralInstructionOrThrow @@ -21,6 +23,8 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference private const val EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR = "$EXTENSION_PATH/utils/VideoUtils;" +private lateinit var dismissMethod: MutableMethod + val dismissPlayerHookPatch = bytecodePatch( description = "dismissPlayerHookPatch" ) { @@ -36,6 +40,21 @@ val dismissPlayerHookPatch = bytecodePatch( reference?.returnType == "V" && reference.parameterTypes.isEmpty() } + + getWalkerMethod(dismissPlayerIndex).apply { + val jumpIndex = indexOfFirstInstructionReversedOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.returnType == "V" + } + getWalkerMethod(jumpIndex).apply { + val jumpIndex = indexOfFirstInstructionReversedOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.returnType == "V" + } + dismissMethod = getWalkerMethod(jumpIndex) + } + } + val dismissPlayerReference = getInstruction(dismissPlayerIndex).reference as MethodReference val dismissPlayerClass = dismissPlayerReference.definingClass @@ -80,4 +99,16 @@ val dismissPlayerHookPatch = bytecodePatch( } } } -} \ No newline at end of file +} + +/** + * This method is called when the video is closed. + */ +internal fun hookDismissObserver(descriptor: String) = + dismissMethod.apply { + println("Class: $definingClass Name: $name") + addInstruction( + 0, + "invoke-static {}, $descriptor" + ) + } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt index 8052b44fd..faa0af987 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt @@ -11,6 +11,8 @@ import app.revanced.patches.shared.customspeed.customPlaybackSpeedPatch import app.revanced.patches.shared.litho.addLithoFilter import app.revanced.patches.shared.litho.lithoFilterPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.dismiss.dismissPlayerHookPatch +import app.revanced.patches.youtube.utils.dismiss.hookDismissObserver import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH import app.revanced.patches.youtube.utils.extension.Constants.PATCH_STATUS_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.extension.Constants.VIDEO_PATH @@ -25,7 +27,6 @@ import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.patches.youtube.video.information.hookBackgroundPlayVideoInformation -import app.revanced.patches.youtube.video.information.hookShortsVideoInformation import app.revanced.patches.youtube.video.information.hookVideoInformation import app.revanced.patches.youtube.video.information.onCreateHook import app.revanced.patches.youtube.video.information.speedSelectionInsertMethod @@ -87,6 +88,7 @@ val videoPlaybackPatch = bytecodePatch( ), flyoutMenuHookPatch, lithoFilterPatch, + dismissPlayerHookPatch, playerTypeHookPatch, recyclerViewTreeObserverPatch, shortsPlaybackPatch, @@ -183,9 +185,9 @@ val videoPlaybackPatch = bytecodePatch( } hookBackgroundPlayVideoInformation("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->newVideoStarted(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZ)V") - hookShortsVideoInformation("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->newShortsVideoStarted(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZ)V") hookVideoInformation("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->newVideoStarted(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZ)V") hookPlayerResponseVideoId("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->fetchMusicRequest(Ljava/lang/String;Z)V") + hookDismissObserver("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->onDismiss()V") updatePatchStatus(PATCH_STATUS_CLASS_DESCRIPTOR, "RememberPlaybackSpeed")