feat(youtube): support version 18.23.35 (#433)

Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
oSumAtrIX 2023-07-08 01:02:42 +02:00 committed by GitHub
parent 7b61cc8567
commit dec7348203
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 266 additions and 83 deletions

View File

@ -1,11 +1,9 @@
package app.revanced.integrations.patches.components;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.utils.ReVancedUtils;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -14,6 +12,10 @@ import java.util.Iterator;
import java.util.Spliterator;
import java.util.function.Consumer;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.utils.ReVancedUtils;
abstract class FilterGroup<T> {
final static class FilterGroupResult {
private final boolean filtered;
@ -49,7 +51,7 @@ abstract class FilterGroup<T> {
}
public boolean isEnabled() {
return setting.getBoolean();
return setting == null || setting.getBoolean();
}
public abstract FilterGroupResult check(final T stack);
@ -208,7 +210,7 @@ abstract class Filter {
/**
* Check if the given path, identifier or protobuf buffer is filtered by any
* {@link FilterGroup}.
* {@link FilterGroup}. Method is called off the main thread.
*
* @return True if filtered, false otherwise.
*/
@ -239,9 +241,12 @@ public final class LithoFilterPatch {
new DummyFilter() // Replaced by patch.
};
/**
* Injection point. Called off the main thread.
*/
@SuppressWarnings("unused")
public static boolean filter(final StringBuilder pathBuilder, final String identifier,
final ByteBuffer protobufBuffer) {
final ByteBuffer protobufBuffer) {
// TODO: Maybe this can be moved to the Filter class, to prevent unnecessary
// string creation
// because some filters might not need the path.

View File

@ -0,0 +1,23 @@
package app.revanced.integrations.patches.components;
import app.revanced.integrations.settings.SettingsEnum;
// Abuse LithoFilter for OldVideoQualityMenuPatch.
public final class VideoQualityMenuFilterPatch 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 isVideoQualityMenuVisible;
public VideoQualityMenuFilterPatch() {
pathFilterGroups.addAll(new StringFilterGroup(
SettingsEnum.SHOW_OLD_VIDEO_QUALITY_MENU,
"quick_quality_sheet_content.eml-js"
));
}
@Override
boolean isFiltered(final String path, final String identifier, final byte[] protobufBufferArray) {
isVideoQualityMenuVisible = super.isFiltered(path, identifier, protobufBufferArray);
return false;
}
}

View File

@ -0,0 +1,21 @@
package app.revanced.integrations.patches.components;
// Abuse LithoFilter for CustomVideoSpeedPatch.
public final class VideoSpeedMenuFilterPatch 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 isVideoSpeedMenuVisible;
public VideoSpeedMenuFilterPatch() {
pathFilterGroups.addAll(new StringFilterGroup(
null,
"playback_speed_sheet_content.eml-js"
));
}
@Override
boolean isFiltered(final String path, final String identifier, final byte[] protobufBufferArray) {
isVideoSpeedMenuVisible = super.isFiltered(path, identifier, protobufBufferArray);
return false;
}
}

View File

@ -1,35 +0,0 @@
package app.revanced.integrations.patches.playback.quality;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.LogHelper;
public class OldQualityLayoutPatch {
public static void showOldQualityMenu(ListView listView)
{
if (!SettingsEnum.SHOW_OLD_VIDEO_MENU.getBoolean()) return;
listView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
@Override
public void onChildViewAdded(View parent, View child) {
LogHelper.printDebug(() -> "Added: " + child);
parent.setVisibility(View.GONE);
final var indexOfAdvancedQualityMenuItem = 4;
if (listView.indexOfChild(child) != indexOfAdvancedQualityMenuItem) return;
LogHelper.printDebug(() -> "Found advanced menu: " + child);
final var qualityItemMenuPosition = 4;
listView.performItemClick(null, qualityItemMenuPosition, 0);
}
@Override
public void onChildViewRemoved(View parent, View child) {}
});
}
}

View File

@ -0,0 +1,94 @@
package app.revanced.integrations.patches.playback.quality;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
import android.widget.ListView;
import androidx.annotation.NonNull;
import app.revanced.integrations.patches.components.VideoQualityMenuFilterPatch;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.LogHelper;
import com.facebook.litho.ComponentHost;
import kotlin.Deprecated;
// This patch contains the logic to show the old video quality menu.
// Two methods are required, because the quality menu is a RecyclerView in the new YouTube version
// and a ListView in the old one.
public final class OldVideoQualityMenuPatch {
public static void onFlyoutMenuCreate(final LinearLayout linearLayout) {
if (!SettingsEnum.SHOW_OLD_VIDEO_QUALITY_MENU.getBoolean()) return;
// The quality menu is a RecyclerView with 3 children. The third child is the "Advanced" quality menu.
addRecyclerListener(linearLayout, 3, 2, recyclerView -> {
// Check if the current view is the quality menu.
if (VideoQualityMenuFilterPatch.isVideoQualityMenuVisible) {// Hide the video quality menu.
linearLayout.setVisibility(View.GONE);
// Click the "Advanced" quality menu to show the "old" quality menu.
((ComponentHost) recyclerView.getChildAt(0)).getChildAt(3).performClick();
LogHelper.printDebug(() -> "Advanced quality menu in new type of quality menu clicked");
}
});
}
public static void addRecyclerListener(@NonNull LinearLayout linearLayout,
int expectedLayoutChildCount, int recyclerViewIndex,
@NonNull RecyclerViewGlobalLayoutListener listener) {
if (linearLayout.getChildCount() != expectedLayoutChildCount) return;
var layoutChild = linearLayout.getChildAt(recyclerViewIndex);
if (!(layoutChild instanceof RecyclerView)) return;
final var recyclerView = (RecyclerView) layoutChild;
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
try {
listener.recyclerOnGlobalLayout(recyclerView);
} catch (Exception ex) {
LogHelper.printException(() -> "addRecyclerListener failure", ex);
} finally {
// Remove the listener because it will be added again.
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
}
);
}
public interface RecyclerViewGlobalLayoutListener {
void recyclerOnGlobalLayout(@NonNull RecyclerView recyclerView);
}
@Deprecated(message = "This patch is deprecated because the quality menu is not a ListView anymore")
public static void showOldVideoQualityMenu(final ListView listView) {
if (!SettingsEnum.SHOW_OLD_VIDEO_QUALITY_MENU.getBoolean()) return;
listView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
@Override
public void onChildViewAdded(View parent, View child) {
LogHelper.printDebug(() -> "Added listener to old type of quality menu");
parent.setVisibility(View.GONE);
final var indexOfAdvancedQualityMenuItem = 4;
if (listView.indexOfChild(child) != indexOfAdvancedQualityMenuItem) return;
LogHelper.printDebug(() -> "Found advanced menu item in old type of quality menu");
final var qualityItemMenuPosition = 4;
listView.performItemClick(null, qualityItemMenuPosition, 0);
}
@Override
public void onChildViewRemoved(View parent, View child) {
}
});
}
}

View File

@ -38,13 +38,8 @@ public class RememberVideoQualityPatch {
private static List<Integer> videoQualities;
private static void changeDefaultQuality(int defaultQuality) {
NetworkType networkType = ReVancedUtils.getNetworkType();
if (networkType == NetworkType.NONE) {
ReVancedUtils.showToastShort("No internet connection");
return;
}
String networkTypeMessage;
if (networkType == NetworkType.MOBILE) {
if (ReVancedUtils.getNetworkType() == NetworkType.MOBILE) {
mobileQualitySetting.saveValue(defaultQuality);
networkTypeMessage = "mobile";
} else {
@ -139,15 +134,24 @@ public class RememberVideoQualityPatch {
}
/**
* Injection point.
* Injection point. Old quality menu.
*/
public static void userChangedQuality(int selectedQuality) {
public static void userChangedQuality(int selectedQualityIndex) {
if (!SettingsEnum.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.getBoolean()) return;
userSelectedQualityIndex = selectedQuality;
userSelectedQualityIndex = selectedQualityIndex;
userChangedDefaultQuality = true;
}
/**
* Injection point. New quality menu.
*/
public static void userChangedQualityInNewFlyout(int selectedQuality) {
if (!SettingsEnum.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.getBoolean()) return;
changeDefaultQuality(selectedQuality); // Quality is human readable resolution (ie: 1080).
}
/**
* Injection point.
*/

View File

@ -1,11 +1,19 @@
package app.revanced.integrations.patches.playback.speed;
import static app.revanced.integrations.patches.playback.quality.OldVideoQualityMenuPatch.addRecyclerListener;
import android.preference.ListPreference;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import com.facebook.litho.ComponentHost;
import java.util.Arrays;
import app.revanced.integrations.patches.components.VideoSpeedMenuFilterPatch;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.utils.ReVancedUtils;
@ -37,7 +45,7 @@ public class CustomVideoSpeedPatch {
private static String[] preferenceListEntries, preferenceListEntryValues;
static {
loadSpeeds();
loadCustomSpeeds();
}
private static void resetCustomSpeeds(@NonNull String toastMessage) {
@ -45,7 +53,7 @@ public class CustomVideoSpeedPatch {
SettingsEnum.CUSTOM_PLAYBACK_SPEEDS.saveValue(SettingsEnum.CUSTOM_PLAYBACK_SPEEDS.defaultValue);
}
private static void loadSpeeds() {
private static void loadCustomSpeeds() {
try {
String[] speedStrings = SettingsEnum.CUSTOM_PLAYBACK_SPEEDS.getString().split("\\s+");
Arrays.sort(speedStrings);
@ -61,7 +69,7 @@ public class CustomVideoSpeedPatch {
if (speed >= MAXIMUM_PLAYBACK_SPEED) {
resetCustomSpeeds("Custom speeds must be less than " + MAXIMUM_PLAYBACK_SPEED
+ ". Using default values.");
loadSpeeds();
loadCustomSpeeds();
return;
}
minVideoSpeed = Math.min(minVideoSpeed, speed);
@ -71,7 +79,7 @@ public class CustomVideoSpeedPatch {
} catch (Exception ex) {
LogHelper.printInfo(() -> "parse error", ex);
resetCustomSpeeds("Invalid custom video speeds. Using default values.");
loadSpeeds();
loadCustomSpeeds();
}
}
@ -100,4 +108,32 @@ public class CustomVideoSpeedPatch {
preference.setEntries(preferenceListEntries);
preference.setEntryValues(preferenceListEntryValues);
}
/*
* To reduce copy paste between two similar code paths.
*/
public static void onFlyoutMenuCreate(final LinearLayout linearLayout) {
// The playback rate menu is a RecyclerView with 2 children. The third child is the "Advanced" quality menu.
addRecyclerListener(linearLayout, 2, 1, recyclerView -> {
if (VideoSpeedMenuFilterPatch.isVideoSpeedMenuVisible &&
recyclerView.getChildCount() == 1 &&
recyclerView.getChildAt(0) instanceof ComponentHost
) {
linearLayout.setVisibility(View.GONE);
// Close the new video speed menu and instead show the old one.
showOldVideoSpeedMenu();
// DismissView [R.id.touch_outside] is the 1st ChildView of the 3rd ParentView.
((ViewGroup) linearLayout.getParent().getParent().getParent())
.getChildAt(0).performClick();
}
});
}
public static void showOldVideoSpeedMenu() {
LogHelper.printDebug(() -> "Old video quality menu shown");
// Rest of the implementation added by patch.
}
}

View File

@ -42,7 +42,9 @@ public enum SettingsEnum {
// Video
HDR_AUTO_BRIGHTNESS("revanced_hdr_auto_brightness", BOOLEAN, TRUE),
SHOW_OLD_VIDEO_MENU("revanced_show_old_video_menu", BOOLEAN, TRUE),
SHOW_OLD_VIDEO_QUALITY_MENU("revanced_show_old_video_quality_menu", BOOLEAN, TRUE),
@Deprecated
DEPRECATED_SHOW_OLD_VIDEO_QUALITY_MENU("revanced_show_old_video_menu", BOOLEAN, TRUE),
REMEMBER_VIDEO_QUALITY_LAST_SELECTED("revanced_remember_video_quality_last_selected", BOOLEAN, TRUE),
VIDEO_QUALITY_DEFAULT_WIFI("revanced_video_quality_default_wifi", INTEGER, -2),
VIDEO_QUALITY_DEFAULT_MOBILE("revanced_video_quality_default_mobile", INTEGER, -2),
@ -51,9 +53,6 @@ public enum SettingsEnum {
CUSTOM_PLAYBACK_SPEEDS("revanced_custom_playback_speeds", STRING,
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true),
// Whitelist
//WHITELIST("revanced_whitelist_ads", BOOLEAN, FALSE), // TODO: Unused currently
// Ads
HIDE_BUTTONED_ADS("revanced_hide_buttoned_ads", BOOLEAN, TRUE),
HIDE_GENERAL_ADS("revanced_hide_general_ads", BOOLEAN, TRUE),
@ -349,31 +348,31 @@ public enum SettingsEnum {
setting.load();
}
// TODO: eventually delete this.
// region Migration
SettingsEnum[][] renamedSettings = {
// TODO: do _not_ delete this SB private user id migration property until sometime in 2024.
// This is the only setting that cannot be reconfigured if lost,
// and more time should be given for users who rarely upgrade.
{DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, SB_PRIVATE_USER_ID},
};
// TODO: do _not_ delete this SB private user id migration property until sometime in 2024.
// This is the only setting that cannot be reconfigured if lost,
// and more time should be given for users who rarely upgrade.
migrateOldSettingToNew(DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, SB_PRIVATE_USER_ID);
for (SettingsEnum[] oldNewSetting : renamedSettings) {
SettingsEnum oldSetting = oldNewSetting[0];
SettingsEnum newSetting = oldNewSetting[1];
if (!oldSetting.isSetToDefault()) {
LogHelper.printInfo(() -> "Migrating old setting of '" + oldSetting.value
+ "' from: " + oldSetting + " into replacement setting: " + newSetting);
newSetting.saveValue(oldSetting.value);
oldSetting.saveValue(oldSetting.defaultValue); // reset old value
}
}
// TODO: delete DEPRECATED_SHOW_OLD_VIDEO_QUALITY_MENU (When? anytime).
migrateOldSettingToNew(DEPRECATED_SHOW_OLD_VIDEO_QUALITY_MENU, SHOW_OLD_VIDEO_QUALITY_MENU);
// endregion
}
/**
* Migrate a setting value if the path is renamed but otherwise the old and new settings are identical.
*/
private static void migrateOldSettingToNew(SettingsEnum oldSetting, SettingsEnum newSetting) {
if (!oldSetting.isSetToDefault()) {
LogHelper.printInfo(() -> "Migrating old setting of '" + oldSetting.value
+ "' from: " + oldSetting + " into replacement setting: " + newSetting);
newSetting.saveValue(oldSetting.value);
oldSetting.saveValue(oldSetting.defaultValue); // reset old value
}
}
private void load() {
switch (returnType) {
case BOOLEAN:

View File

@ -180,6 +180,11 @@ public class ReVancedSettingsFragment extends PreferenceFragment {
if (entryIndex >= 0) {
listPreference.setSummary(listPreference.getEntries()[entryIndex]);
listPreference.setValue(objectStringValue);
} else {
// Value is not an available option.
// User manually edited import data, or options changed and current selection is no longer available.
// Still show the value in the summary so it's clear that something is selected.
listPreference.setSummary(objectStringValue);
}
}

View File

@ -8,15 +8,16 @@ import app.revanced.integrations.utils.LogHelper
*/
enum class PlayerType {
/**
* Includes Shorts and Stories playback.
* Either no video, or a Short is playing.
*/
NONE,
/**
* A Shorts or Stories, if a regular video is minimized and a Short/Story is then opened.
* A Short is playing. Occurs if a regular video is first opened
* and then a Short is opened (without first closing the regular video).
*/
HIDDEN,
/**
* When spoofing to an old version of YouTube, and watching a short with a regular video in the background,
* When spoofing to 16.x YouTube and watching a short with a regular video in the background,
* the type will be this (and not [HIDDEN]).
*/
WATCH_WHILE_MINIMIZED,
@ -76,7 +77,7 @@ enum class PlayerType {
* Useful to check if a short is currently playing.
*
* Does not include the first moment after a short is opened when a regular video is minimized on screen,
* or while watching a short with a regular video present on a spoofed old version of YouTube.
* or while watching a short with a regular video present on a spoofed 16.x version of YouTube.
* To include those situations instead use [isNoneHiddenOrMinimized].
*/
fun isNoneOrHidden(): Boolean {
@ -84,12 +85,13 @@ enum class PlayerType {
}
/**
* Check if the current player type is [NONE], [HIDDEN], [WATCH_WHILE_MINIMIZED], [WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED].
* Check if the current player type is
* [NONE], [HIDDEN], [WATCH_WHILE_MINIMIZED], [WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED].
*
* Useful to check if a Short is being played,
* although can return false positive if the player is minimized.
* although will return false positive if a regular video is opened and minimized (and no short is playing).
*
* @return If nothing, a Short, a Story,
* @return If nothing, a Short,
* or a regular video is minimized video or sliding off screen to a dismissed or hidden state.
*/
fun isNoneHiddenOrMinimized(): Boolean {

View File

@ -0,0 +1,19 @@
package android.support.v7.widget;
import android.content.Context;
import android.view.View;
public class RecyclerView extends View {
public RecyclerView(Context context) {
super(context);
}
public View getChildAt(@SuppressWarnings("unused") final int index) {
return null;
}
public int getChildCount() {
return 0;
}
}

View File

@ -0,0 +1,10 @@
package com.facebook.litho;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
public final class ComponentHost extends RecyclerView {
public ComponentHost(Context context) {
super(context);
}
}