fix(YouTube - Video playback): Overridden to default playback speed in onResume callback https://github.com/inotia00/ReVanced_Extended/issues/2896

This commit is contained in:
inotia00 2025-04-01 19:02:55 +09:00
parent 457dbfec6c
commit 64af7fd8b6
3 changed files with 119 additions and 51 deletions

View File

@ -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<String, Float> 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;
return;
}
if (videoId.equals(newlyLoadedVideoId)) {
return;
}
videoId = newlyLoadedVideoId;
isLiveStream = newlyLoadedLiveStreamValue;
Logger.printDebug(() -> "newShortsVideoStarted: " + newlyLoadedVideoId);
boolean isMusic = isMusic(newlyLoadedVideoId);
boolean isWhitelisted = Whitelist.isChannelWhitelistedPlaybackSpeed(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);
}
}
}
/**
* 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;
Logger.printDebug(() -> "newShortsVideoStarted: " + newlyLoadedVideoId);
}
/**
@ -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 {
} 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);

View File

@ -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<MethodReference>()?.returnType == "V"
}
getWalkerMethod(jumpIndex).apply {
val jumpIndex = indexOfFirstInstructionReversedOrThrow {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.returnType == "V"
}
dismissMethod = getWalkerMethod(jumpIndex)
}
}
val dismissPlayerReference =
getInstruction<ReferenceInstruction>(dismissPlayerIndex).reference as MethodReference
val dismissPlayerClass = dismissPlayerReference.definingClass
@ -81,3 +100,15 @@ val dismissPlayerHookPatch = bytecodePatch(
}
}
}
/**
* 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"
)
}

View File

@ -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")