Skip to content

Commit

Permalink
fix(YouTube - Video playback): Custom playback speed not working due …
Browse files Browse the repository at this point in the history
…to A/B tests
  • Loading branch information
anddea committed Oct 29, 2024
1 parent afe17f6 commit 9ad3df4
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,10 @@ public static ViewParent getParentView(@NonNull View view, int nthParent) {
return parent;
}

final int currentDepthLog = currentDepth;
final int finalDepthLog = currentDepth;
final ViewParent finalParent = parent;
Logger.printDebug(() -> "Could not find parent view of depth: " + nthParent
+ " and instead found at: " + currentDepthLog + " view: " + view);
+ " and instead found at: " + finalDepthLog + " view: " + finalParent);
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,41 @@
* Abuse LithoFilter for {@link CustomPlaybackSpeedPatch}.
*/
public final class PlaybackSpeedMenuFilter extends Filter {
// Must be volatile or synchronized, as litho filtering runs off main thread and this field is then access from the main thread.
public static volatile boolean isPlaybackSpeedMenuVisible;
/**
* Old litho based speed selection menu.
*/
public static volatile boolean isOldPlaybackSpeedMenuVisible;

/**
* 0.05x speed selection menu.
*/
public static volatile boolean isPlaybackRateSelectorMenuVisible;

private final StringFilterGroup oldPlaybackMenuGroup;

public PlaybackSpeedMenuFilter() {
addPathCallbacks(
new StringFilterGroup(
Settings.ENABLE_CUSTOM_PLAYBACK_SPEED,
"playback_speed_sheet_content.eml-js"
)
// 0.05x litho speed menu.
final StringFilterGroup playbackRateSelectorGroup = new StringFilterGroup(
Settings.ENABLE_CUSTOM_PLAYBACK_SPEED,
"playback_rate_selector_menu_sheet.eml-js"
);

// Old litho based speed menu.
oldPlaybackMenuGroup = new StringFilterGroup(
Settings.ENABLE_CUSTOM_PLAYBACK_SPEED,
"playback_speed_sheet_content.eml-js");

addPathCallbacks(playbackRateSelectorGroup, oldPlaybackMenuGroup);
}

@Override
public boolean isFiltered(String path, @Nullable String identifier, String allValue, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
isPlaybackSpeedMenuVisible = true;
if (matchedGroup == oldPlaybackMenuGroup) {
isOldPlaybackSpeedMenuVisible = true;
} else {
isPlaybackRateSelectorMenuVisible = true;
}

return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@
public class CustomPlaybackSpeedPatch {
/**
* Maximum playback speed, exclusive value. Custom speeds must be less than this value.
* <p>
* Going over 8x does not increase the actual playback speed any higher,
* and the UI selector starts flickering and acting weird.
* Over 10x and the speeds show up out of order in the UI selector.
*/
private static final float MAXIMUM_PLAYBACK_SPEED = 8;
public static final float MAXIMUM_PLAYBACK_SPEED = 8;
private static final String[] defaultSpeedEntries;
private static final String[] defaultSpeedEntryValues;
/**
Expand All @@ -45,7 +49,7 @@ public class CustomPlaybackSpeedPatch {
defaultSpeedEntries = new String[]{getString("quality_auto"), "0.25x", "0.5x", "0.75x", getString("revanced_playback_speed_normal"), "1.25x", "1.5x", "1.75x", "2.0x"};
defaultSpeedEntryValues = new String[]{"-2.0", "0.25", "0.5", "0.75", "1.0", "1.25", "1.5", "1.75", "2.0"};

loadSpeeds();
loadCustomSpeeds();
}

/**
Expand Down Expand Up @@ -105,27 +109,33 @@ private static void resetCustomSpeeds(@NonNull String toastMessage) {
Settings.CUSTOM_PLAYBACK_SPEEDS.resetToDefault();
}

private static void loadSpeeds() {
private static void loadCustomSpeeds() {
try {
if (!Settings.ENABLE_CUSTOM_PLAYBACK_SPEED.get()) return;
if (!Settings.ENABLE_CUSTOM_PLAYBACK_SPEED.get()) {
return;
}

String[] speedStrings = Settings.CUSTOM_PLAYBACK_SPEEDS.get().split("\\s+");
Arrays.sort(speedStrings);
if (speedStrings.length == 0) {
throw new IllegalArgumentException();
}
playbackSpeeds = new float[speedStrings.length];
for (int i = 0, length = speedStrings.length; i < length; i++) {
final float speed = Float.parseFloat(speedStrings[i]);
if (speed <= 0 || arrayContains(playbackSpeeds, speed)) {
int i = 0;
for (String speedString : speedStrings) {
final float speedFloat = Float.parseFloat(speedString);
if (speedFloat <= 0 || arrayContains(playbackSpeeds, speedFloat)) {
throw new IllegalArgumentException();
}
if (speed > MAXIMUM_PLAYBACK_SPEED) {
resetCustomSpeeds(str("revanced_custom_playback_speeds_invalid", MAXIMUM_PLAYBACK_SPEED + ""));
loadSpeeds();

if (speedFloat > MAXIMUM_PLAYBACK_SPEED) {
resetCustomSpeeds(str("revanced_custom_playback_speeds_invalid", MAXIMUM_PLAYBACK_SPEED));
loadCustomSpeeds();
return;
}
playbackSpeeds[i] = speed;

playbackSpeeds[i] = speedFloat;
i++;
}

if (customSpeedEntries != null) return;
Expand All @@ -135,7 +145,7 @@ private static void loadSpeeds() {
customSpeedEntries[0] = getString("quality_auto");
customSpeedEntryValues[0] = "-2.0";

int i = 1;
i = 1;
for (float speed : playbackSpeeds) {
String speedString = String.valueOf(speed);
customSpeedEntries[i] = speed != 1.0f
Expand All @@ -147,7 +157,7 @@ private static void loadSpeeds() {
} catch (Exception ex) {
Logger.printInfo(() -> "parse error", ex);
resetCustomSpeeds(str("revanced_custom_playback_speeds_parse_exception"));
loadSpeeds();
loadCustomSpeeds();
}
}

Expand All @@ -165,52 +175,68 @@ private static boolean isCustomPlaybackSpeedEnabled() {
/**
* Injection point.
*/
public static void onFlyoutMenuCreate(final RecyclerView recyclerView) {
if (!Settings.ENABLE_CUSTOM_PLAYBACK_SPEED.get())
public static void onFlyoutMenuCreate(RecyclerView recyclerView) {
if (!Settings.ENABLE_CUSTOM_PLAYBACK_SPEED.get()) {
return;
}

recyclerView.getViewTreeObserver().addOnDrawListener(() -> {
try {
// Check if the current view is the playback speed menu.
if (!PlaybackSpeedMenuFilter.isPlaybackSpeedMenuVisible || recyclerView.getChildCount() == 0) {
if (PlaybackSpeedMenuFilter.isOldPlaybackSpeedMenuVisible) {
if (hideLithoMenuAndShowOldSpeedMenu(recyclerView, 8)) {
PlaybackSpeedMenuFilter.isOldPlaybackSpeedMenuVisible = false;
}
return;
}
} catch (Exception ex) {
Logger.printException(() -> "isOldPlaybackSpeedMenuVisible failure", ex);
}

if (!(recyclerView.getChildAt(0) instanceof ViewGroup playbackSpeedParentView)) {
return;
try {
if (PlaybackSpeedMenuFilter.isPlaybackRateSelectorMenuVisible) {
if (hideLithoMenuAndShowOldSpeedMenu(recyclerView, 5)) {
PlaybackSpeedMenuFilter.isPlaybackRateSelectorMenuVisible = false;
}
}
} catch (Exception ex) {
Logger.printException(() -> "isPlaybackRateSelectorMenuVisible failure", ex);
}
});
}

// For some reason, the custom playback speed flyout panel is activated when the user opens the share panel. (A/B tests)
// Check the child count of playback speed flyout panel to prevent this issue.
// Child count of playback speed flyout panel is always 8.
if (playbackSpeedParentView.getChildCount() != 8) {
return;
}
private static boolean hideLithoMenuAndShowOldSpeedMenu(RecyclerView recyclerView, int expectedChildCount) {
if (recyclerView.getChildCount() == 0) {
return false;
}

PlaybackSpeedMenuFilter.isPlaybackSpeedMenuVisible = false;
if (!(recyclerView.getChildAt(0) instanceof ViewGroup PlaybackSpeedParentView)) {
return false;
}

if (!(Utils.getParentView(recyclerView, 3) instanceof ViewGroup parentView3rd)) {
return;
}
if (PlaybackSpeedParentView.getChildCount() != expectedChildCount) {
return false;
}

if (!(parentView3rd.getParent() instanceof ViewGroup parentView4th)) {
return;
}
if (!(Utils.getParentView(recyclerView, 3) instanceof ViewGroup parentView3rd)) {
return false;
}

// Dismiss View [R.id.touch_outside] is the 1st ChildView of the 4th ParentView.
// This only shows in phone layout
Utils.clickView(parentView4th.getChildAt(0));
if (!(parentView3rd.getParent() instanceof ViewGroup parentView4th)) {
return false;
}

// In tablet layout, there is no Dismiss View, instead we just hide all two parent views.
parentView3rd.setVisibility(View.GONE);
parentView4th.setVisibility(View.GONE);
// Dismiss View [R.id.touch_outside] is the 1st ChildView of the 4th ParentView.
// This only shows in phone layout.
Utils.clickView(parentView4th.getChildAt(0));

// Show custom playback speed menu.
showCustomPlaybackSpeedMenu(recyclerView.getContext());
} catch (Exception ex) {
Logger.printException(() -> "onFlyoutMenuCreate failure", ex);
}
});
// In tablet layout there is no Dismiss View, instead we just hide all two parent views.
parentView3rd.setVisibility(View.GONE);
parentView4th.setVisibility(View.GONE);

// Show old playback speed menu.
showCustomPlaybackSpeedMenu(recyclerView.getContext());

return true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package app.revanced.integrations.youtube.patches.video;

import static app.revanced.integrations.shared.utils.StringRef.str;
import static app.revanced.integrations.shared.utils.Utils.showToastShort;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.apache.commons.lang3.BooleanUtils;

import app.revanced.integrations.shared.utils.Logger;
import app.revanced.integrations.shared.utils.Utils;
import app.revanced.integrations.youtube.patches.misc.requests.PlaylistRequest;
import app.revanced.integrations.youtube.patches.utils.PatchStatus;
import app.revanced.integrations.youtube.settings.Settings;
Expand All @@ -17,6 +17,8 @@

@SuppressWarnings("unused")
public class PlaybackSpeedPatch {
private static final long TOAST_DELAY_MILLISECONDS = 750;
private static long lastTimeSpeedChanged;
private static boolean isLiveStream;

/**
Expand Down Expand Up @@ -77,18 +79,38 @@ public static float getPlaybackSpeedInShorts(final float playbackSpeed) {
* @param playbackSpeed The playback speed the user selected
*/
public static void userSelectedPlaybackSpeed(float playbackSpeed) {
if (!Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED.get())
return;

if (!PatchStatus.RememberPlaybackSpeed())
return;

Settings.DEFAULT_PLAYBACK_SPEED.save(playbackSpeed);
if (PatchStatus.RememberPlaybackSpeed() &&
Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED.get()) {
// With the 0.05x menu, if the speed is set by integrations to higher than 2.0x
// then the menu will allow increasing without bounds but the max speed is
// still capped to under 8.0x.
playbackSpeed = Math.min(playbackSpeed, CustomPlaybackSpeedPatch.MAXIMUM_PLAYBACK_SPEED - 0.05f);

// Prevent toast spamming if using the 0.05x adjustments.
// Show exactly one toast after the user stops interacting with the speed menu.
final long now = System.currentTimeMillis();
lastTimeSpeedChanged = now;

final float finalPlaybackSpeed = playbackSpeed;
Utils.runOnMainThreadDelayed(() -> {
if (lastTimeSpeedChanged != now) {
// The user made additional speed adjustments and this call is outdated.
return;
}

if (!Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST.get())
return;
if (Settings.DEFAULT_PLAYBACK_SPEED.get() == finalPlaybackSpeed) {
// User changed to a different speed and immediately changed back.
// Or the user is going past 8.0x in the glitched out 0.05x menu.
return;
}
Settings.DEFAULT_PLAYBACK_SPEED.save(finalPlaybackSpeed);

showToastShort(str("revanced_remember_playback_speed_toast", playbackSpeed + "x"));
if (!Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST.get()) {
return;
}
Utils.showToastShort(str("revanced_remember_playback_speed_toast", (finalPlaybackSpeed + "x")));
}, TOAST_DELAY_MILLISECONDS);
}
}

private static float getDefaultPlaybackSpeed(@NonNull String channelId, @Nullable String videoId) {
Expand Down

0 comments on commit 9ad3df4

Please sign in to comment.