feat(YouTube - Playback speed): Add "Restore old playback speed menu" option (#5552)
This commit is contained in:
parent
0579a9f760
commit
e9e4cf39b6
11 changed files with 216 additions and 45 deletions
|
|
@ -16,7 +16,7 @@ import java.util.Objects;
|
||||||
|
|
||||||
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.youtube.patches.components.ReturnYouTubeDislikeFilterPatch;
|
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilter;
|
||||||
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
||||||
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.PlayerType;
|
||||||
|
|
@ -55,7 +55,7 @@ public class ReturnYouTubeDislikePatch {
|
||||||
private static volatile ReturnYouTubeDislike lastLithoShortsVideoData;
|
private static volatile ReturnYouTubeDislike lastLithoShortsVideoData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Because litho Shorts spans are created offscreen after {@link ReturnYouTubeDislikeFilterPatch}
|
* Because litho Shorts spans are created offscreen after {@link ReturnYouTubeDislikeFilter}
|
||||||
* detects the video ids, but the current Short can arbitrarily reload the same span,
|
* detects the video ids, but the current Short can arbitrarily reload the same span,
|
||||||
* then use the {@link #lastLithoShortsVideoData} if this value is greater than zero.
|
* then use the {@link #lastLithoShortsVideoData} if this value is greater than zero.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ package app.revanced.extension.youtube.patches.components;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class HideInfoCardsFilterPatch extends Filter {
|
public final class HideInfoCardsFilter extends Filter {
|
||||||
|
|
||||||
public HideInfoCardsFilterPatch() {
|
public HideInfoCardsFilter() {
|
||||||
addIdentifierCallbacks(
|
addIdentifierCallbacks(
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
Settings.HIDE_INFO_CARDS,
|
Settings.HIDE_INFO_CARDS,
|
||||||
|
|
@ -8,27 +8,44 @@ import app.revanced.extension.youtube.settings.Settings;
|
||||||
/**
|
/**
|
||||||
* Abuse LithoFilter for {@link CustomPlaybackSpeedPatch}.
|
* Abuse LithoFilter for {@link CustomPlaybackSpeedPatch}.
|
||||||
*/
|
*/
|
||||||
public final class PlaybackSpeedMenuFilterPatch extends Filter {
|
public final class PlaybackSpeedMenuFilter extends Filter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Old litho based speed selection menu.
|
||||||
|
*/
|
||||||
|
public static volatile boolean isOldPlaybackSpeedMenuVisible;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 0.05x speed selection menu.
|
* 0.05x speed selection menu.
|
||||||
*/
|
*/
|
||||||
public static volatile boolean isPlaybackRateSelectorMenuVisible;
|
public static volatile boolean isPlaybackRateSelectorMenuVisible;
|
||||||
|
|
||||||
public PlaybackSpeedMenuFilterPatch() {
|
private final StringFilterGroup oldPlaybackMenuGroup;
|
||||||
|
|
||||||
|
public PlaybackSpeedMenuFilter() {
|
||||||
// 0.05x litho speed menu.
|
// 0.05x litho speed menu.
|
||||||
var playbackRateSelectorGroup = new StringFilterGroup(
|
var playbackRateSelectorGroup = new StringFilterGroup(
|
||||||
Settings.CUSTOM_SPEED_MENU,
|
Settings.CUSTOM_SPEED_MENU,
|
||||||
"playback_rate_selector_menu_sheet.eml-js"
|
"playback_rate_selector_menu_sheet.eml-js"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Old litho based speed menu.
|
||||||
|
oldPlaybackMenuGroup = new StringFilterGroup(
|
||||||
|
Settings.CUSTOM_SPEED_MENU,
|
||||||
|
"playback_speed_sheet_content.eml-js");
|
||||||
|
|
||||||
|
|
||||||
addPathCallbacks(playbackRateSelectorGroup);
|
addPathCallbacks(playbackRateSelectorGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
isPlaybackRateSelectorMenuVisible = true;
|
if (matchedGroup == oldPlaybackMenuGroup) {
|
||||||
|
isOldPlaybackSpeedMenuVisible = true;
|
||||||
|
} else {
|
||||||
|
isPlaybackRateSelectorMenuVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +26,7 @@ import app.revanced.extension.youtube.TrieSearch;
|
||||||
*
|
*
|
||||||
* Once a way to asynchronously update litho text is found, this strategy will no longer be needed.
|
* Once a way to asynchronously update litho text is found, this strategy will no longer be needed.
|
||||||
*/
|
*/
|
||||||
public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
public final class ReturnYouTubeDislikeFilter extends Filter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last unique video id's loaded. Value is ignored and Map is treated as a Set.
|
* Last unique video id's loaded. Value is ignored and Map is treated as a Set.
|
||||||
|
|
@ -67,7 +67,7 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
||||||
|
|
||||||
private final ByteArrayFilterGroupList videoIdFilterGroup = new ByteArrayFilterGroupList();
|
private final ByteArrayFilterGroupList videoIdFilterGroup = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
public ReturnYouTubeDislikeFilterPatch() {
|
public ReturnYouTubeDislikeFilter() {
|
||||||
// When a new Short is opened, the like buttons always seem to load before the dislike.
|
// When a new Short is opened, the like buttons always seem to load before the dislike.
|
||||||
// But if swiping back to a previous video and liking/disliking, then only that single button reloads.
|
// But if swiping back to a previous video and liking/disliking, then only that single button reloads.
|
||||||
// So must check for both buttons.
|
// So must check for both buttons.
|
||||||
|
|
@ -23,7 +23,6 @@ import android.view.Gravity;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewParent;
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
|
|
@ -42,7 +41,7 @@ import java.util.function.Function;
|
||||||
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.youtube.patches.VideoInformation;
|
import app.revanced.extension.youtube.patches.VideoInformation;
|
||||||
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilterPatch;
|
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilter;
|
||||||
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.PlayerType;
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
|
|
@ -80,6 +79,16 @@ public class CustomPlaybackSpeedPatch {
|
||||||
*/
|
*/
|
||||||
public static final float[] customPlaybackSpeeds;
|
public static final float[] customPlaybackSpeeds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum and maximum custom playback speeds of {@link #customPlaybackSpeeds}.
|
||||||
|
*/
|
||||||
|
private static final float customPlaybackSpeedsMin, customPlaybackSpeedsMax;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last time the old playback menu was forcefully called.
|
||||||
|
*/
|
||||||
|
private static volatile long lastTimeOldPlaybackMenuInvoked;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats speeds to UI strings.
|
* Formats speeds to UI strings.
|
||||||
*/
|
*/
|
||||||
|
|
@ -90,11 +99,6 @@ public class CustomPlaybackSpeedPatch {
|
||||||
*/
|
*/
|
||||||
private static WeakReference<Dialog> currentDialog = new WeakReference<>(null);
|
private static WeakReference<Dialog> currentDialog = new WeakReference<>(null);
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimum and maximum custom playback speeds of {@link #customPlaybackSpeeds}.
|
|
||||||
*/
|
|
||||||
private static final float customPlaybackSpeedsMin, customPlaybackSpeedsMax;
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// Cap at 2 decimals (rounds automatically).
|
// Cap at 2 decimals (rounds automatically).
|
||||||
speedFormatter.setMaximumFractionDigits(2);
|
speedFormatter.setMaximumFractionDigits(2);
|
||||||
|
|
@ -174,25 +178,33 @@ public class CustomPlaybackSpeedPatch {
|
||||||
public static void onFlyoutMenuCreate(RecyclerView recyclerView) {
|
public static void onFlyoutMenuCreate(RecyclerView recyclerView) {
|
||||||
recyclerView.getViewTreeObserver().addOnDrawListener(() -> {
|
recyclerView.getViewTreeObserver().addOnDrawListener(() -> {
|
||||||
try {
|
try {
|
||||||
if (PlaybackSpeedMenuFilterPatch.isPlaybackRateSelectorMenuVisible) {
|
if (PlaybackSpeedMenuFilter.isPlaybackRateSelectorMenuVisible) {
|
||||||
if (hideLithoMenuAndShowCustomSpeedMenu(recyclerView, 5)) {
|
if (hideLithoMenuAndShowSpeedMenu(recyclerView, 5)) {
|
||||||
PlaybackSpeedMenuFilterPatch.isPlaybackRateSelectorMenuVisible = false;
|
PlaybackSpeedMenuFilter.isPlaybackRateSelectorMenuVisible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "onFlyoutMenuCreate failure", ex);
|
Logger.printException(() -> "isPlaybackRateSelectorMenuVisible failure", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (PlaybackSpeedMenuFilter.isOldPlaybackSpeedMenuVisible) {
|
||||||
|
if (hideLithoMenuAndShowSpeedMenu(recyclerView, 8)) {
|
||||||
|
PlaybackSpeedMenuFilter.isOldPlaybackSpeedMenuVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "isOldPlaybackSpeedMenuVisible failure", ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("SameParameterValue")
|
private static boolean hideLithoMenuAndShowSpeedMenu(RecyclerView recyclerView, int expectedChildCount) {
|
||||||
private static boolean hideLithoMenuAndShowCustomSpeedMenu(RecyclerView recyclerView, int expectedChildCount) {
|
|
||||||
if (recyclerView.getChildCount() == 0) {
|
if (recyclerView.getChildCount() == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
View firstChild = recyclerView.getChildAt(0);
|
if (!(recyclerView.getChildAt(0) instanceof ViewGroup playbackSpeedParentView)) {
|
||||||
if (!(firstChild instanceof ViewGroup playbackSpeedParentView)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -200,33 +212,49 @@ public class CustomPlaybackSpeedPatch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewParent parentView3rd = Utils.getParentView(recyclerView, 3);
|
if (!(Utils.getParentView(recyclerView, 3) instanceof ViewGroup parentView3rd)) {
|
||||||
if (!(parentView3rd instanceof ViewGroup)) {
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewParent parentView4th = parentView3rd.getParent();
|
if (!(parentView3rd.getParent() instanceof ViewGroup parentView4th)) {
|
||||||
if (!(parentView4th instanceof ViewGroup)) {
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dismiss View [R.id.touch_outside] is the 1st ChildView of the 4th ParentView.
|
// Dismiss View [R.id.touch_outside] is the 1st ChildView of the 4th ParentView.
|
||||||
// This only shows in phone layout.
|
// This only shows in phone layout.
|
||||||
final var touchInsidedView = ((ViewGroup) parentView4th).getChildAt(0);
|
var touchInsidedView = parentView4th.getChildAt(0);
|
||||||
touchInsidedView.setSoundEffectsEnabled(false);
|
touchInsidedView.setSoundEffectsEnabled(false);
|
||||||
touchInsidedView.performClick();
|
touchInsidedView.performClick();
|
||||||
|
|
||||||
// In tablet layout there is no Dismiss View, instead we just hide all two parent views.
|
// In tablet layout there is no Dismiss View, instead we just hide all two parent views.
|
||||||
((ViewGroup) parentView3rd).setVisibility(View.GONE);
|
parentView3rd.setVisibility(View.GONE);
|
||||||
((ViewGroup) parentView4th).setVisibility(View.GONE);
|
parentView4th.setVisibility(View.GONE);
|
||||||
|
|
||||||
// Close the litho speed menu and show the modern custom speed dialog.
|
// Close the litho speed menu and show the custom speeds.
|
||||||
showModernCustomPlaybackSpeedDialog(recyclerView.getContext());
|
if (Settings.RESTORE_OLD_SPEED_MENU.get()) {
|
||||||
Logger.printDebug(() -> "Modern playback speed dialog shown");
|
showOldPlaybackSpeedMenu();
|
||||||
|
Logger.printDebug(() -> "Old playback speed dialog shown");
|
||||||
|
} else {
|
||||||
|
showModernCustomPlaybackSpeedDialog(recyclerView.getContext());
|
||||||
|
Logger.printDebug(() -> "Modern playback speed dialog shown");
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void showOldPlaybackSpeedMenu() {
|
||||||
|
// This method is sometimes used multiple times.
|
||||||
|
// To prevent this, ignore method reuse within 1 second.
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
if (now - lastTimeOldPlaybackMenuInvoked < 1000) {
|
||||||
|
Logger.printDebug(() -> "Ignoring call to showOldPlaybackSpeedMenu");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastTimeOldPlaybackMenuInvoked = now;
|
||||||
|
|
||||||
|
// Rest of the implementation added by patch.
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a modern custom dialog for adjusting video playback speed.
|
* Displays a modern custom dialog for adjusting video playback speed.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
||||||
|
|
@ -68,8 +68,9 @@ public class Settings extends BaseSettings {
|
||||||
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);
|
||||||
public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_playback_speed_last_selected_toast", TRUE, false,
|
public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_playback_speed_last_selected_toast", TRUE, false,
|
||||||
parent(REMEMBER_PLAYBACK_SPEED_LAST_SELECTED));
|
parent(REMEMBER_PLAYBACK_SPEED_LAST_SELECTED));
|
||||||
public static final BooleanSetting CUSTOM_SPEED_MENU = new BooleanSetting("revanced_custom_speed_menu", TRUE);
|
|
||||||
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
|
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
|
||||||
|
public static final BooleanSetting CUSTOM_SPEED_MENU = new BooleanSetting("revanced_custom_speed_menu", TRUE);
|
||||||
|
public static final BooleanSetting RESTORE_OLD_SPEED_MENU = new BooleanSetting("revanced_restore_old_speed_menu", FALSE, parent(CUSTOM_SPEED_MENU));
|
||||||
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
||||||
"0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.5\n3.0\n4.0\n5.0\n6.0\n7.0\n8.0", true);
|
"0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.5\n3.0\n4.0\n5.0\n6.0\n7.0\n8.0", true);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ val hideInfoCardsPatch = bytecodePatch(
|
||||||
)
|
)
|
||||||
|
|
||||||
// Info cards can also appear as Litho components.
|
// Info cards can also appear as Litho components.
|
||||||
val filterClassDescriptor = "Lapp/revanced/extension/youtube/patches/components/HideInfoCardsFilterPatch;"
|
val filterClassDescriptor = "Lapp/revanced/extension/youtube/patches/components/HideInfoCardsFilter;"
|
||||||
addLithoFilter(filterClassDescriptor)
|
addLithoFilter(filterClassDescriptor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
"Lapp/revanced/extension/youtube/patches/ReturnYouTubeDislikePatch;"
|
"Lapp/revanced/extension/youtube/patches/ReturnYouTubeDislikePatch;"
|
||||||
|
|
||||||
private const val FILTER_CLASS_DESCRIPTOR =
|
private const val FILTER_CLASS_DESCRIPTOR =
|
||||||
"Lapp/revanced/extension/youtube/patches/components/ReturnYouTubeDislikeFilterPatch;"
|
"Lapp/revanced/extension/youtube/patches/components/ReturnYouTubeDislikeFilter;"
|
||||||
|
|
||||||
val returnYouTubeDislikePatch = bytecodePatch(
|
val returnYouTubeDislikePatch = bytecodePatch(
|
||||||
name = "Return YouTube Dislike",
|
name = "Return YouTube Dislike",
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
package app.revanced.patches.youtube.video.speed.custom
|
package app.revanced.patches.youtube.video.speed.custom
|
||||||
|
|
||||||
|
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.addInstructionsWithLabels
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patcher.patch.resourcePatch
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||||
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.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.InputType
|
import app.revanced.patches.shared.misc.settings.preference.InputType
|
||||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||||
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
||||||
|
|
@ -18,18 +26,34 @@ import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTr
|
||||||
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.settingsPatch
|
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||||
import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup
|
import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup
|
||||||
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
import app.revanced.util.indexOfFirstLiteralInstruction
|
import app.revanced.util.indexOfFirstLiteralInstruction
|
||||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||||
|
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
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
|
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||||
|
|
||||||
private const val FILTER_CLASS_DESCRIPTOR =
|
private const val FILTER_CLASS_DESCRIPTOR =
|
||||||
"Lapp/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilterPatch;"
|
"Lapp/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilter;"
|
||||||
|
|
||||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
internal const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
"Lapp/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch;"
|
"Lapp/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch;"
|
||||||
|
|
||||||
|
internal var speedUnavailableId = -1L
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val customPlaybackSpeedResourcePatch = resourcePatch {
|
||||||
|
dependsOn(resourceMappingPatch)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
speedUnavailableId = resourceMappings["string", "varispeed_unavailable_message"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal val customPlaybackSpeedPatch = bytecodePatch(
|
internal val customPlaybackSpeedPatch = bytecodePatch(
|
||||||
description = "Adds custom playback speed options.",
|
description = "Adds custom playback speed options.",
|
||||||
) {
|
) {
|
||||||
|
|
@ -39,7 +63,8 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
|
||||||
addResourcesPatch,
|
addResourcesPatch,
|
||||||
lithoFilterPatch,
|
lithoFilterPatch,
|
||||||
versionCheckPatch,
|
versionCheckPatch,
|
||||||
recyclerViewTreeHookPatch
|
recyclerViewTreeHookPatch,
|
||||||
|
customPlaybackSpeedResourcePatch
|
||||||
)
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
|
|
@ -48,6 +73,7 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
|
||||||
settingsMenuVideoSpeedGroup.addAll(
|
settingsMenuVideoSpeedGroup.addAll(
|
||||||
listOf(
|
listOf(
|
||||||
SwitchPreference("revanced_custom_speed_menu"),
|
SwitchPreference("revanced_custom_speed_menu"),
|
||||||
|
SwitchPreference("revanced_restore_old_speed_menu"),
|
||||||
TextPreference(
|
TextPreference(
|
||||||
"revanced_custom_playback_speeds",
|
"revanced_custom_playback_speeds",
|
||||||
inputType = InputType.TEXT_MULTI_LINE
|
inputType = InputType.TEXT_MULTI_LINE
|
||||||
|
|
@ -77,15 +103,88 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
|
||||||
replaceInstruction(limitMaxIndex, "const/high16 v$limitMaxRegister, 8.0f")
|
replaceInstruction(limitMaxIndex, "const/high16 v$limitMaxRegister, 8.0f")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Replace the speeds float array with custom speeds.
|
||||||
|
// These speeds are used if the speed menu is immediately opened after a video is opened.
|
||||||
|
speedArrayGeneratorFingerprint.method.apply {
|
||||||
|
val sizeCallIndex = indexOfFirstInstructionOrThrow { getReference<MethodReference>()?.name == "size" }
|
||||||
|
val sizeCallResultRegister = getInstruction<OneRegisterInstruction>(sizeCallIndex + 1).registerA
|
||||||
|
|
||||||
|
replaceInstruction(sizeCallIndex + 1, "const/4 v$sizeCallResultRegister, 0x0")
|
||||||
|
|
||||||
|
val arrayLengthConstIndex = indexOfFirstLiteralInstructionOrThrow(7)
|
||||||
|
val arrayLengthConstDestination = getInstruction<OneRegisterInstruction>(arrayLengthConstIndex).registerA
|
||||||
|
val playbackSpeedsArrayType = "$EXTENSION_CLASS_DESCRIPTOR->customPlaybackSpeeds:[F"
|
||||||
|
|
||||||
|
addInstructions(
|
||||||
|
arrayLengthConstIndex + 1,
|
||||||
|
"""
|
||||||
|
sget-object v$arrayLengthConstDestination, $playbackSpeedsArrayType
|
||||||
|
array-length v$arrayLengthConstDestination, v$arrayLengthConstDestination
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
|
val originalArrayFetchIndex = indexOfFirstInstructionOrThrow {
|
||||||
|
val reference = getReference<FieldReference>()
|
||||||
|
reference?.type == "[F" && reference.definingClass.endsWith("/PlayerConfigModel;")
|
||||||
|
}
|
||||||
|
val originalArrayFetchDestination =
|
||||||
|
getInstruction<OneRegisterInstruction>(originalArrayFetchIndex).registerA
|
||||||
|
|
||||||
|
replaceInstruction(
|
||||||
|
originalArrayFetchIndex,
|
||||||
|
"sget-object v$originalArrayFetchDestination, $playbackSpeedsArrayType",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// region Force old video quality menu.
|
||||||
|
|
||||||
|
// Add a static INSTANCE field to the class.
|
||||||
|
// This is later used to call "showOldPlaybackSpeedMenu" on the instance.
|
||||||
|
|
||||||
|
val instanceField = ImmutableField(
|
||||||
|
getOldPlaybackSpeedsFingerprint.originalClassDef.type,
|
||||||
|
"INSTANCE",
|
||||||
|
getOldPlaybackSpeedsFingerprint.originalClassDef.type,
|
||||||
|
AccessFlags.PUBLIC.value or AccessFlags.STATIC.value,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
).toMutable()
|
||||||
|
|
||||||
|
getOldPlaybackSpeedsFingerprint.classDef.staticFields.add(instanceField)
|
||||||
|
// Set the INSTANCE field to the instance of the class.
|
||||||
|
// In order to prevent a conflict with another patch, add the instruction at index 1.
|
||||||
|
getOldPlaybackSpeedsFingerprint.method.addInstruction(1, "sput-object p0, $instanceField")
|
||||||
|
|
||||||
|
// Get the "showOldPlaybackSpeedMenu" method.
|
||||||
|
// This is later called on the field INSTANCE.
|
||||||
|
val showOldPlaybackSpeedMenuMethod = showOldPlaybackSpeedMenuFingerprint.match(
|
||||||
|
getOldPlaybackSpeedsFingerprint.classDef,
|
||||||
|
).method
|
||||||
|
|
||||||
|
// Insert the call to the "showOldPlaybackSpeedMenu" method on the field INSTANCE.
|
||||||
|
showOldPlaybackSpeedMenuExtensionFingerprint.method.apply {
|
||||||
|
addInstructionsWithLabels(
|
||||||
|
instructions.lastIndex,
|
||||||
|
"""
|
||||||
|
sget-object v0, $instanceField
|
||||||
|
if-nez v0, :not_null
|
||||||
|
return-void
|
||||||
|
:not_null
|
||||||
|
invoke-virtual { v0 }, $showOldPlaybackSpeedMenuMethod
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
// Close the unpatched playback dialog and show the modern custom dialog.
|
// Close the unpatched playback dialog and show the modern custom dialog.
|
||||||
addRecyclerViewTreeHook(EXTENSION_CLASS_DESCRIPTOR)
|
addRecyclerViewTreeHook(EXTENSION_CLASS_DESCRIPTOR)
|
||||||
|
|
||||||
// Required to check if the playback speed menu is currently shown.
|
// Required to check if the playback speed menu is currently shown.
|
||||||
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
|
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
|
|
||||||
// region Custom tap and hold 2x speed.
|
// region Custom tap and hold 2x speed.
|
||||||
|
|
||||||
if (is_19_25_or_greater) {
|
if (is_19_25_or_greater) {
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,33 @@ package app.revanced.patches.youtube.video.speed.custom
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
import app.revanced.util.indexOfFirstInstruction
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
|
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
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||||
|
|
||||||
|
internal val getOldPlaybackSpeedsFingerprint = fingerprint {
|
||||||
|
parameters("[L", "I")
|
||||||
|
strings("menu_item_playback_speed")
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val showOldPlaybackSpeedMenuFingerprint = fingerprint {
|
||||||
|
literal { speedUnavailableId }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val showOldPlaybackSpeedMenuExtensionFingerprint = fingerprint {
|
||||||
|
custom { method, classDef ->
|
||||||
|
method.name == "showOldPlaybackSpeedMenu" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val speedArrayGeneratorFingerprint = fingerprint {
|
||||||
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||||
|
returns("[L")
|
||||||
|
parameters("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;")
|
||||||
|
strings("0.0#")
|
||||||
|
}
|
||||||
|
|
||||||
internal val speedLimiterFingerprint = fingerprint {
|
internal val speedLimiterFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||||
returns("V")
|
returns("V")
|
||||||
|
|
|
||||||
|
|
@ -1547,6 +1547,9 @@ Enabling this can unlock higher video qualities"</string>
|
||||||
<string name="revanced_custom_speed_menu_title">Custom playback speed menu</string>
|
<string name="revanced_custom_speed_menu_title">Custom playback speed menu</string>
|
||||||
<string name="revanced_custom_speed_menu_summary_on">Custom speed menu is shown</string>
|
<string name="revanced_custom_speed_menu_summary_on">Custom speed menu is shown</string>
|
||||||
<string name="revanced_custom_speed_menu_summary_off">Custom speed menu is not shown</string>
|
<string name="revanced_custom_speed_menu_summary_off">Custom speed menu is not shown</string>
|
||||||
|
<string name="revanced_restore_old_speed_menu_title">Restore old playback speed menu</string>
|
||||||
|
<string name="revanced_restore_old_speed_menu_summary_on">Old speed menu is shown</string>
|
||||||
|
<string name="revanced_restore_old_speed_menu_summary_off">Modern speed menu is shown</string>
|
||||||
<string name="revanced_custom_playback_speeds_title">Custom playback speeds</string>
|
<string name="revanced_custom_playback_speeds_title">Custom playback speeds</string>
|
||||||
<string name="revanced_custom_playback_speeds_summary">Add or change the custom playback speeds</string>
|
<string name="revanced_custom_playback_speeds_summary">Add or change the custom playback speeds</string>
|
||||||
<string name="revanced_custom_playback_speeds_invalid">Custom speeds must be less than %s</string>
|
<string name="revanced_custom_playback_speeds_invalid">Custom speeds must be less than %s</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue