Merge branch 'dev' into revanced-extended

This commit is contained in:
inotia00 2025-02-13 11:21:24 +09:00
commit 1926becdfc
308 changed files with 9597 additions and 6014 deletions

115
README.md
View File

@ -30,6 +30,7 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm
| `Disable forced auto audio tracks` | Adds an option to disable audio tracks from being automatically enabled. | 18.29.38 ~ 19.44.39 |
| `Disable forced auto captions` | Adds an option to disable captions from being automatically enabled. | 18.29.38 ~ 19.44.39 |
| `Disable haptic feedback` | Adds options to disable haptic feedback when swiping in the video player. | 18.29.38 ~ 19.44.39 |
| `Disable resuming Miniplayer on startup` | Adds an option to disable the Miniplayer 'Continue watching' from resuming on app startup. | 18.29.38 ~ 19.44.39 |
| `Disable resuming Shorts on startup` | Adds an option to disable the Shorts player from resuming on app startup when Shorts were last being watched. | 18.29.38 ~ 19.44.39 |
| `Disable splash animation` | Adds an option to disable the splash animation on app startup. | 18.29.38 ~ 19.44.39 |
| `Enable OPUS codec` | Adds an option to enable the OPUS audio codec if the player response includes it. | 18.29.38 ~ 19.44.39 |
@ -39,6 +40,7 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm
| `Fullscreen components` | Adds options to hide or change components related to fullscreen. | 18.29.38 ~ 19.44.39 |
| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 18.29.38 ~ 19.44.39 |
| `Hide Shorts dimming` | Removes, at compile time, the dimming effect at the top and bottom of Shorts videos. | 18.29.38 ~ 19.44.39 |
| `Hide accessibility controls dialog` | Removes, at compile time, accessibility controls dialog 'Turn on accessibility controls for the video player?'. | 18.29.38 ~ 19.44.39 |
| `Hide action buttons` | Adds options to hide action buttons under videos. | 18.29.38 ~ 19.44.39 |
| `Hide ads` | Adds options to hide ads. | 18.29.38 ~ 19.44.39 |
| `Hide comments components` | Adds options to hide components related to comments. | 18.29.38 ~ 19.44.39 |
@ -82,47 +84,47 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `Bitrate default value` | Sets the audio quality to 'Always High' when you first install the app. | 6.20.51 ~ 8.02.53 |
| `Bypass image region restrictions` | Adds an option to use a different host for static images, so that images blocked in some countries can be received. | 6.20.51 ~ 8.02.53 |
| `Certificate spoof` | Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate. | 6.20.51 ~ 8.02.53 |
| `Change share sheet` | Adds an option to change the in-app share sheet to the system share sheet. | 6.20.51 ~ 8.02.53 |
| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 6.20.51 ~ 8.02.53 |
| `Custom branding icon for YouTube Music` | Changes the YouTube Music app icon to the icon specified in patch options. | 6.20.51 ~ 8.02.53 |
| `Custom branding name for YouTube Music` | Changes the YouTube Music app name to the name specified in patch options. | 6.20.51 ~ 8.02.53 |
| `Custom header for YouTube Music` | Applies a custom header in the top left corner within the app. | 6.20.51 ~ 8.02.53 |
| `Dark theme` | Changes the app's dark theme to the values specified in patch options. | 6.20.51 ~ 8.02.53 |
| `Bitrate default value` | Sets the audio quality to 'Always High' when you first install the app. | 6.20.51 ~ 8.05.51 |
| `Bypass image region restrictions` | Adds an option to use a different host for static images, so that images blocked in some countries can be received. | 6.20.51 ~ 8.05.51 |
| `Certificate spoof` | Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate. | 6.20.51 ~ 8.05.51 |
| `Change share sheet` | Adds an option to change the in-app share sheet to the system share sheet. | 6.20.51 ~ 8.05.51 |
| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 6.20.51 ~ 8.05.51 |
| `Custom branding icon for YouTube Music` | Changes the YouTube Music app icon to the icon specified in patch options. | 6.20.51 ~ 8.05.51 |
| `Custom branding name for YouTube Music` | Changes the YouTube Music app name to the name specified in patch options. | 6.20.51 ~ 8.05.51 |
| `Custom header for YouTube Music` | Applies a custom header in the top left corner within the app. | 6.20.51 ~ 8.05.51 |
| `Dark theme` | Changes the app's dark theme to the values specified in patch options. | 6.20.51 ~ 8.05.51 |
| `Disable Cairo splash animation` | Adds an option to disable Cairo splash animation. | 7.06.54 ~ 8.02.53 |
| `Disable DRC audio` | Adds an option to disable DRC (Dynamic Range Compression) audio. | 6.20.51 ~ 8.02.53 |
| `Disable dislike redirection` | Adds an option to disable redirection to the next track when clicking the Dislike button. | 6.20.51 ~ 8.02.53 |
| `Disable forced auto captions` | Adds an option to disable captions from being automatically enabled. | 6.20.51 ~ 8.02.53 |
| `Disable music video in album` | Adds option to redirect music videos from albums for non-premium users. | 6.20.51 ~ 8.02.53 |
| `Enable OPUS codec` | Adds an option to enable the OPUS audio codec if the player response includes it. | 6.20.51 ~ 8.02.53 |
| `Enable debug logging` | Adds an option to enable debug logging. | 6.20.51 ~ 8.02.53 |
| `Enable landscape mode` | Adds an option to enable landscape mode when rotating the screen on phones. | 6.20.51 ~ 8.02.53 |
| `Flyout menu components` | Adds options to hide or change flyout menu components. | 6.20.51 ~ 8.02.53 |
| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 6.20.51 ~ 8.02.53 |
| `Hide account components` | Adds options to hide components related to the account menu. | 6.20.51 ~ 8.02.53 |
| `Hide action bar components` | Adds options to hide action bar components and replace the offline download button with an external download button. | 6.20.51 ~ 8.02.53 |
| `Hide ads` | Adds options to hide ads. | 6.20.51 ~ 8.02.53 |
| `Hide layout components` | Adds options to hide general layout components. | 6.20.51 ~ 8.02.53 |
| `Hide overlay filter` | Removes, at compile time, the dark overlay that appears when player flyout menus are open. | 6.20.51 ~ 8.02.53 |
| `Hide player overlay filter` | Removes, at compile time, the dark overlay that appears when single-tapping in the player. | 6.20.51 ~ 8.02.53 |
| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 6.20.51 ~ 8.02.53 |
| `Player components` | Adds options to hide or change components related to the player. | 6.20.51 ~ 8.02.53 |
| `Remove background playback restrictions` | Removes restrictions on background playback, including for kids videos. | 6.20.51 ~ 8.02.53 |
| `Remove viewer discretion dialog` | Adds an option to remove the dialog that appears when opening a video that has been age-restricted by accepting it automatically. This does not bypass the age restriction. | 6.20.51 ~ 8.02.53 |
| `Restore old style library shelf` | Adds an option to return the Library tab to the old style. | 6.20.51 ~ 8.02.53 |
| `Return YouTube Dislike` | Adds an option to show the dislike count of songs using the Return YouTube Dislike API. | 6.20.51 ~ 8.02.53 |
| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 6.20.51 ~ 8.02.53 |
| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 6.20.51 ~ 8.02.53 |
| `Settings for YouTube Music` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 6.20.51 ~ 8.02.53 |
| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections. | 6.20.51 ~ 8.02.53 |
| `Spoof app version` | Adds options to spoof the YouTube Music client version. This can remove the radio mode restriction in Canadian regions or disable real-time lyrics. | 6.20.51 ~ 7.16.53 |
| `Spoof client` | Adds options to spoof the client to allow playback. | 6.20.51 ~ 7.16.53 |
| `Spoof streaming data` | Adds options to spoof the streaming data to allow playback. | 6.20.51 ~ 8.02.53 |
| `Translations for YouTube Music` | Add translations or remove string resources. | 6.20.51 ~ 8.02.53 |
| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 6.20.51 ~ 8.02.53 |
| `Visual preferences icons for YouTube Music` | Adds icons to specific preferences in the settings. | 6.20.51 ~ 8.02.53 |
| `Disable DRC audio` | Adds an option to disable DRC (Dynamic Range Compression) audio. | 6.20.51 ~ 8.05.51 |
| `Disable dislike redirection` | Adds an option to disable redirection to the next track when clicking the Dislike button. | 6.20.51 ~ 8.05.51 |
| `Disable forced auto captions` | Adds an option to disable captions from being automatically enabled. | 6.20.51 ~ 8.05.51 |
| `Disable music video in album` | Adds option to redirect music videos from albums for non-premium users. | 6.20.51 ~ 8.05.51 |
| `Enable OPUS codec` | Adds an option to enable the OPUS audio codec if the player response includes it. | 6.20.51 ~ 8.05.51 |
| `Enable debug logging` | Adds an option to enable debug logging. | 6.20.51 ~ 8.05.51 |
| `Enable landscape mode` | Adds an option to enable landscape mode when rotating the screen on phones. | 6.20.51 ~ 8.05.51 |
| `Flyout menu components` | Adds options to hide or change flyout menu components. | 6.20.51 ~ 8.05.51 |
| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 6.20.51 ~ 8.05.51 |
| `Hide account components` | Adds options to hide components related to the account menu. | 6.20.51 ~ 8.05.51 |
| `Hide action bar components` | Adds options to hide action bar components and replace the offline download button with an external download button. | 6.20.51 ~ 8.05.51 |
| `Hide ads` | Adds options to hide ads. | 6.20.51 ~ 8.05.51 |
| `Hide layout components` | Adds options to hide general layout components. | 6.20.51 ~ 8.05.51 |
| `Hide overlay filter` | Removes, at compile time, the dark overlay that appears when player flyout menus are open. | 6.20.51 ~ 8.05.51 |
| `Hide player overlay filter` | Removes, at compile time, the dark overlay that appears when single-tapping in the player. | 6.20.51 ~ 8.05.51 |
| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 6.20.51 ~ 8.05.51 |
| `Player components` | Adds options to hide or change components related to the player. | 6.20.51 ~ 8.05.51 |
| `Remove background playback restrictions` | Removes restrictions on background playback, including for kids videos. | 6.20.51 ~ 8.05.51 |
| `Remove viewer discretion dialog` | Adds an option to remove the dialog that appears when opening a video that has been age-restricted by accepting it automatically. This does not bypass the age restriction. | 6.20.51 ~ 8.05.51 |
| `Restore old style library shelf` | Adds an option to return the Library tab to the old style. | 6.20.51 ~ 8.05.51 |
| `Return YouTube Dislike` | Adds an option to show the dislike count of songs using the Return YouTube Dislike API. | 6.20.51 ~ 8.05.51 |
| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 6.20.51 ~ 8.05.51 |
| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 6.20.51 ~ 8.05.51 |
| `Settings for YouTube Music` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 6.20.51 ~ 8.05.51 |
| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections. | 6.20.51 ~ 8.05.51 |
| `Spoof app version` | Adds options to spoof the YouTube Music client version. This can be used to restore old UI elements and features. | 6.51.53 ~ 7.16.53 |
| `Spoof client` | Adds options to spoof the client to allow playback. | 6.20.51 ~ 8.05.51 |
| `Translations for YouTube Music` | Add translations or remove string resources. | 6.20.51 ~ 8.05.51 |
| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 6.20.51 ~ 8.05.51 |
| `Visual preferences icons for YouTube Music` | Adds icons to specific preferences in the settings. | 6.20.51 ~ 8.05.51 |
| `Watch history` | Adds an option to change the domain of the watch history or check its status. | 6.20.51 ~ 8.05.51 |
</details>
### [📦 `com.reddit.frontpage`](https://play.google.com/store/apps/details?id=com.reddit.frontpage)
@ -130,19 +132,19 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
| `Change package name` | Changes the package name for Reddit to the name specified in patch options. | ALL |
| `Custom branding name for Reddit` | Changes the Reddit app name to the name specified in patch options. | ALL |
| `Disable screenshot popup` | Adds an option to disable the popup that appears when taking a screenshot. | ALL |
| `Hide Recently Visited shelf` | Adds an option to hide the Recently Visited shelf in the sidebar. | ALL |
| `Hide ads` | Adds options to hide ads. | ALL |
| `Hide navigation buttons` | Adds options to hide buttons in the navigation bar. | ALL |
| `Hide recommended communities shelf` | Adds an option to hide the recommended communities shelves in subreddits. | ALL |
| `Open links directly` | Adds an option to skip over redirection URLs in external links. | ALL |
| `Open links externally` | Adds an option to always open links in your browser instead of in the in-app-browser. | ALL |
| `Premium icon` | Unlocks premium app icons. | ALL |
| `Remove subreddit dialog` | Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically. | ALL |
| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | ALL |
| `Settings for Reddit` | Applies mandatory patches to implement ReVanced Extended settings into the application. | ALL |
| `Change package name` | Changes the package name for Reddit to the name specified in patch options. | 2024.17.0 ~ 2025.05.1 |
| `Custom branding name for Reddit` | Changes the Reddit app name to the name specified in patch options. | 2024.17.0 ~ 2025.05.1 |
| `Disable screenshot popup` | Adds an option to disable the popup that appears when taking a screenshot. | 2024.17.0 ~ 2025.05.1 |
| `Hide Recently Visited shelf` | Adds an option to hide the Recently Visited shelf in the sidebar. | 2024.17.0 ~ 2025.05.1 |
| `Hide ads` | Adds options to hide ads. | 2024.17.0 ~ 2025.05.1 |
| `Hide navigation buttons` | Adds options to hide buttons in the navigation bar. | 2024.17.0 ~ 2025.05.1 |
| `Hide recommended communities shelf` | Adds an option to hide the recommended communities shelves in subreddits. | 2024.17.0 ~ 2025.05.1 |
| `Open links directly` | Adds an option to skip over redirection URLs in external links. | 2024.17.0 ~ 2025.05.1 |
| `Open links externally` | Adds an option to always open links in your browser instead of in the in-app-browser. | 2024.17.0 ~ 2025.05.1 |
| `Premium icon` | Unlocks premium app icons. | 2024.17.0 ~ 2025.05.1 |
| `Remove subreddit dialog` | Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically. | 2024.17.0 ~ 2025.05.1 |
| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 2024.17.0 ~ 2025.05.1 |
| `Settings for Reddit` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 2024.17.0 ~ 2025.05.1 |
</details>
@ -184,7 +186,7 @@ Example:
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -194,7 +196,10 @@ Example:
"description": "Adds options to hide ads.",
"use":true,
"compatiblePackages": {
"com.reddit.frontpage": "ALL"
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": []
}

View File

@ -6,18 +6,46 @@ import static app.revanced.extension.shared.utils.Utils.hideViewUnderCondition;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.litho.ComponentHost;
import java.util.Map;
import app.revanced.extension.music.settings.Settings;
import app.revanced.extension.music.utils.VideoUtils;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.PackageUtils;
@SuppressWarnings("unused")
public class ActionBarPatch {
private static final boolean CHANGE_ACTION_BAR_POSITION =
Settings.CHANGE_ACTION_BAR_POSITION.get();
private static final boolean HIDE_ACTION_BUTTON_LABEL =
Settings.HIDE_ACTION_BUTTON_LABEL.get();
private static final boolean HIDE_ACTION_BUTTON_LIKE_DISLIKE =
Settings.HIDE_ACTION_BUTTON_LIKE_DISLIKE.get() || PackageUtils.getAppVersionName().compareTo("7.25.00") >= 0;
private static final boolean EXTERNAL_DOWNLOADER_ACTION_BUTTON =
Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get();
private static final boolean SETTINGS_INITIALIZED =
Settings.SETTINGS_INITIALIZED.get();
private static final String ELEMENTS_SENDER_VIEW =
"com.google.android.libraries.youtube.rendering.elements.sender_view";
private static final String EXTERNAL_DOWNLOADER_LAUNCHED =
"external_downloader_launched";
private static String downloadButtonLabel = "";
@NonNull
private static String buttonType = "";
public static boolean changeActionBarPosition(boolean original) {
return SETTINGS_INITIALIZED
? CHANGE_ACTION_BAR_POSITION
: original;
}
public static boolean hideActionBarLabel() {
return Settings.HIDE_ACTION_BUTTON_LABEL.get();
return HIDE_ACTION_BUTTON_LABEL;
}
public static boolean hideActionButton() {
@ -29,24 +57,54 @@ public class ActionBarPatch {
}
public static void hideLikeDislikeButton(View view) {
final boolean enabled = Settings.HIDE_ACTION_BUTTON_LIKE_DISLIKE.get();
hideViewUnderCondition(
enabled,
HIDE_ACTION_BUTTON_LIKE_DISLIKE,
view
);
hideViewBy0dpUnderCondition(
enabled,
HIDE_ACTION_BUTTON_LIKE_DISLIKE,
view
);
}
public static void inAppDownloadButtonOnClick(View view) {
if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) {
return;
if (EXTERNAL_DOWNLOADER_ACTION_BUTTON &&
buttonType.equals(ActionButton.DOWNLOAD.name)) {
view.setOnClickListener(imageView -> VideoUtils.launchExternalDownloader());
}
}
public static boolean inAppDownloadButtonOnClick(@Nullable Map<Object, Object> map) {
try {
if (EXTERNAL_DOWNLOADER_ACTION_BUTTON &&
!downloadButtonLabel.isEmpty() &&
map != null &&
map.get(ELEMENTS_SENDER_VIEW) instanceof ComponentHost componentHost &&
componentHost.getContentDescription().toString().equals(downloadButtonLabel)
) {
if (!map.containsKey(EXTERNAL_DOWNLOADER_LAUNCHED)) {
map.put(EXTERNAL_DOWNLOADER_LAUNCHED, Boolean.TRUE);
VideoUtils.runOnMainThreadDelayed(VideoUtils::launchExternalDownloader, 0);
}
return true;
}
} catch (Exception ex) {
Logger.printException(() -> "inAppDownloadButtonOnClick failed", ex);
}
if (buttonType.equals(ActionButton.DOWNLOAD.name))
view.setOnClickListener(imageView -> VideoUtils.launchExternalDownloader());
return false;
}
public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext,
@NonNull CharSequence original) {
if (EXTERNAL_DOWNLOADER_ACTION_BUTTON &&
downloadButtonLabel.isEmpty() &&
conversionContext.toString().contains("music_download_button.eml")) {
downloadButtonLabel = original.toString();
Logger.printDebug(() -> "set download button label: " + original);
}
return original;
}
public static void setButtonType(@NonNull Object obj) {

View File

@ -1,40 +1,60 @@
package app.revanced.extension.music.patches.ads;
import static app.revanced.extension.music.patches.general.GeneralPatch.disableDimBehind;
import static app.revanced.extension.shared.utils.StringRef.str;
import android.app.Dialog;
import android.content.DialogInterface;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import app.revanced.extension.music.settings.Settings;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.Utils;
@SuppressWarnings("unused")
public class PremiumPromotionPatch {
private static final boolean HIDE_PREMIUM_PROMOTION =
Settings.HIDE_PREMIUM_PROMOTION.get();
public static void hidePremiumPromotion(View view) {
if (!Settings.HIDE_PREMIUM_PROMOTION.get())
return;
public static void hidePremiumPromotionBottomSheet(View view) {
if (HIDE_PREMIUM_PROMOTION) {
view.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
try {
if (!(view instanceof ViewGroup viewGroup)) {
return;
}
if (!(viewGroup.getChildAt(0) instanceof ViewGroup mealBarLayoutRoot)) {
return;
}
if (!(mealBarLayoutRoot.getChildAt(0) instanceof LinearLayout linearLayout)) {
return;
}
if (!(linearLayout.getChildAt(0) instanceof ImageView imageView)) {
return;
}
if (imageView.getVisibility() == View.VISIBLE) {
view.setVisibility(View.GONE);
}
} catch (Exception ex) {
Logger.printException(() -> "hidePremiumPromotionBottomSheet failure", ex);
}
});
}
}
view.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
try {
if (!(view instanceof ViewGroup viewGroup)) {
return;
}
if (!(viewGroup.getChildAt(0) instanceof ViewGroup mealBarLayoutRoot)) {
return;
}
if (!(mealBarLayoutRoot.getChildAt(0) instanceof LinearLayout linearLayout)) {
return;
}
if (!(linearLayout.getChildAt(0) instanceof ImageView imageView)) {
return;
}
if (imageView.getVisibility() == View.VISIBLE) {
view.setVisibility(View.GONE);
}
} catch (Exception ex) {
Logger.printException(() -> "hideGetPremium failure", ex);
public static void hidePremiumPromotionDialog(Dialog dialog, View contentView) {
if (HIDE_PREMIUM_PROMOTION) {
disableDimBehind(dialog.getWindow());
dialog.setOnShowListener(DialogInterface::dismiss);
if (BaseSettings.ENABLE_DEBUG_LOGGING.get()) {
Utils.showToastShort(str("revanced_hide_premium_promotion_closed_toast"));
}
});
} else {
dialog.setContentView(contentView);
}
}
}

View File

@ -8,32 +8,43 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import app.revanced.extension.music.settings.Settings;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.music.shared.NavigationBar;
import app.revanced.extension.shared.utils.Utils;
@SuppressWarnings("unused")
public class PremiumRenewalPatch {
private static final String dialogGotItText =
str("dialog_got_it_text");
public static void hidePremiumRenewal(LinearLayout buttonContainerView) {
if (!Settings.HIDE_PREMIUM_RENEWAL.get())
return;
buttonContainerView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
try {
Utils.runOnMainThreadDelayed(() -> {
if (!(buttonContainerView.getChildAt(0) instanceof ViewGroup closeButtonParentView))
return;
if (!(closeButtonParentView.getChildAt(0) instanceof TextView closeButtonView))
return;
if (closeButtonView.getText().toString().equals(str("dialog_got_it_text")))
Utils.clickView(closeButtonView);
else
Utils.hideViewByLayoutParams((View) buttonContainerView.getParent());
}, 0
);
} catch (Exception ex) {
Logger.printException(() -> "hidePremiumRenewal failure", ex);
if (NavigationBar.getNavigationTabIndex() == 0) {
// Always hide the banner when the navigation bar index is 0.
hideParentViewByLayoutParams(buttonContainerView);
} else {
// This banner is exposed to the library as well as the home.
// In this case, it is necessary to check whether the text of the button is 'Got it' or not.
if (!(buttonContainerView.getChildAt(0) instanceof ViewGroup closeButtonParentView))
return;
if (!(closeButtonParentView.getChildAt(0) instanceof TextView closeButtonView))
return;
// If the text of the button is 'Got it', just click the button.
// If not, tab sometimes becomes freezing.
if (closeButtonView.getText().toString().equals(dialogGotItText)) {
Utils.clickView(closeButtonView);
} else {
hideParentViewByLayoutParams(buttonContainerView);
}
}
});
}
private static void hideParentViewByLayoutParams(View view) {
if (view.getParent() instanceof View parentView) {
Utils.hideViewByLayoutParams(parentView);
}
}
}

View File

@ -35,10 +35,15 @@ public final class ActionButtonsFilter extends Filter {
Settings.HIDE_ACTION_BUTTON_LIKE_DISLIKE,
"segmented_like_dislike_button.eml"
);
final StringFilterGroup songVideoButton = new StringFilterGroup(
Settings.HIDE_ACTION_BUTTON_SONG_VIDEO,
"music_audio_video_button.eml"
);
addPathCallbacks(
bufferFilterPathRule,
downloadButton,
likeDislikeContainer
likeDislikeContainer,
songVideoButton
);
bufferButtonsGroupList.addAll(

View File

@ -13,6 +13,10 @@ public final class PlayerFlyoutMenuFilter extends Filter {
Settings.HIDE_FLYOUT_MENU_3_COLUMN_COMPONENT,
"music_highlight_menu_item_carousel.eml",
"tile_button_carousel.eml"
),
new StringFilterGroup(
Settings.HIDE_FLYOUT_MENU_DOWNLOAD,
"list_item.eml"
)
);
}

View File

@ -22,61 +22,68 @@ import java.lang.ref.WeakReference;
import app.revanced.extension.music.settings.Settings;
import app.revanced.extension.music.shared.VideoType;
import app.revanced.extension.music.utils.VideoUtils;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.ResourceUtils.ResourceType;
@SuppressWarnings("unused")
public class FlyoutPatch {
private static final BooleanSetting ENABLE_COMPACT_DIALOG =
Settings.ENABLE_COMPACT_DIALOG;
private static final BooleanSetting ENABLE_TRIM_SILENCE =
Settings.ENABLE_TRIM_SILENCE;
private static final BooleanSetting REPLACE_FLYOUT_MENU_DISMISS_QUEUE =
Settings.REPLACE_FLYOUT_MENU_DISMISS_QUEUE;
private static final BooleanSetting REPLACE_FLYOUT_MENU_REPORT =
Settings.REPLACE_FLYOUT_MENU_REPORT;
private static final BooleanSetting REPLACE_FLYOUT_MENU_REPORT_ONLY_PLAYER =
Settings.REPLACE_FLYOUT_MENU_REPORT_ONLY_PLAYER;
private static final boolean HIDE_FLYOUT_MENU_LIKE_DISLIKE =
Settings.HIDE_FLYOUT_MENU_LIKE_DISLIKE.get();
private static volatile boolean lastMenuWasDismissQueue = false;
private static WeakReference<View> touchOutSideViewRef = new WeakReference<>(null);
private static final ColorFilter cf = new PorterDuffColorFilter(Color.parseColor("#ffffffff"), PorterDuff.Mode.SRC_ATOP);
public static int enableCompactDialog(int original) {
if (!Settings.ENABLE_COMPACT_DIALOG.get())
return original;
return Math.max(original, 600);
return ENABLE_COMPACT_DIALOG.get()
? Math.max(original, 600)
: original;
}
public static boolean enableTrimSilence(boolean original) {
if (!Settings.ENABLE_TRIM_SILENCE.get())
if (!ENABLE_TRIM_SILENCE.get())
return original;
return VideoType.getCurrent().isPodCast() || original;
}
public static boolean enableTrimSilenceSwitch(boolean original) {
if (!Settings.ENABLE_TRIM_SILENCE.get())
if (!ENABLE_TRIM_SILENCE.get())
return original;
return VideoType.getCurrent().isPodCast() && original;
}
public static boolean hideComponents(@Nullable Enum<?> flyoutMenuEnum) {
if (flyoutMenuEnum == null)
return false;
if (flyoutMenuEnum != null) {
final String flyoutMenuName = flyoutMenuEnum.name();
Logger.printDebug(() -> "flyoutMenu loaded: " + flyoutMenuName);
final String flyoutMenuName = flyoutMenuEnum.name();
Logger.printDebug(() -> "flyoutMenu: " + flyoutMenuName);
for (FlyoutPanelComponent component : FlyoutPanelComponent.values())
if (component.name.equals(flyoutMenuName) && component.enabled)
return true;
for (FlyoutPanelComponent component : FlyoutPanelComponent.values())
if (component.name().equals(flyoutMenuName) && component.setting.get())
return true;
}
return false;
}
public static void hideLikeDislikeContainer(View view) {
if (!Settings.HIDE_FLYOUT_MENU_LIKE_DISLIKE.get())
return;
if (view.getParent() instanceof ViewGroup viewGroup) {
if (HIDE_FLYOUT_MENU_LIKE_DISLIKE &&
view.getParent() instanceof ViewGroup viewGroup) {
viewGroup.removeView(view);
}
}
private static volatile boolean lastMenuWasDismissQueue = false;
private static WeakReference<View> touchOutSideViewRef = new WeakReference<>(null);
public static void setTouchOutSideView(View touchOutSideView) {
touchOutSideViewRef = new WeakReference<>(touchOutSideView);
}
@ -98,80 +105,74 @@ public class FlyoutPatch {
}
private static void replaceDismissQueue(@NonNull TextView textView, @NonNull ImageView imageView) {
if (!Settings.REPLACE_FLYOUT_MENU_DISMISS_QUEUE.get())
return;
if (!(textView.getParent() instanceof ViewGroup clickAbleArea))
return;
runOnMainThreadDelayed(() -> {
textView.setText(str("revanced_replace_flyout_menu_dismiss_queue_watch_on_youtube_label"));
imageView.setImageResource(getIdentifier("yt_outline_youtube_logo_icon_vd_theme_24", ResourceType.DRAWABLE, clickAbleArea.getContext()));
clickAbleArea.setOnClickListener(viewGroup -> VideoUtils.openInYouTube());
if (REPLACE_FLYOUT_MENU_DISMISS_QUEUE.get() &&
textView.getParent() instanceof ViewGroup clickAbleArea) {
runOnMainThreadDelayed(() -> {
textView.setText(str("revanced_replace_flyout_menu_dismiss_queue_watch_on_youtube_label"));
imageView.setImageResource(getIdentifier("yt_outline_youtube_logo_icon_vd_theme_24", ResourceType.DRAWABLE, clickAbleArea.getContext()));
clickAbleArea.setOnClickListener(view -> {
clickView(touchOutSideViewRef.get());
VideoUtils.openInYouTube();
});
}, 0L
);
);
}
}
private static final ColorFilter cf = new PorterDuffColorFilter(Color.parseColor("#ffffffff"), PorterDuff.Mode.SRC_ATOP);
private static void replaceReport(@NonNull TextView textView, @NonNull ImageView imageView, boolean wasDismissQueue) {
if (!Settings.REPLACE_FLYOUT_MENU_REPORT.get())
return;
if (Settings.REPLACE_FLYOUT_MENU_REPORT_ONLY_PLAYER.get() && !wasDismissQueue)
return;
if (!(textView.getParent() instanceof ViewGroup clickAbleArea))
return;
runOnMainThreadDelayed(() -> {
textView.setText(str("playback_rate_title"));
imageView.setImageResource(getIdentifier("yt_outline_play_arrow_half_circle_black_24", ResourceType.DRAWABLE, clickAbleArea.getContext()));
imageView.setColorFilter(cf);
clickAbleArea.setOnClickListener(view -> {
clickView(touchOutSideViewRef.get());
VideoUtils.showPlaybackSpeedFlyoutMenu();
});
private static void replaceReport(@NonNull TextView textView, @NonNull ImageView imageView,
boolean wasDismissQueue) {
if (REPLACE_FLYOUT_MENU_REPORT.get() &&
(!REPLACE_FLYOUT_MENU_REPORT_ONLY_PLAYER.get() || wasDismissQueue) &&
textView.getParent() instanceof ViewGroup clickAbleArea
) {
runOnMainThreadDelayed(() -> {
textView.setText(str("playback_rate_title"));
imageView.setImageResource(getIdentifier("yt_outline_play_arrow_half_circle_black_24", ResourceType.DRAWABLE, clickAbleArea.getContext()));
imageView.setColorFilter(cf);
clickAbleArea.setOnClickListener(view -> {
clickView(touchOutSideViewRef.get());
VideoUtils.showPlaybackSpeedFlyoutMenu();
});
}, 0L
);
);
}
}
private enum FlyoutPanelComponent {
SAVE_EPISODE_FOR_LATER("BOOKMARK_BORDER", Settings.HIDE_FLYOUT_MENU_SAVE_EPISODE_FOR_LATER.get()),
SHUFFLE_PLAY("SHUFFLE", Settings.HIDE_FLYOUT_MENU_SHUFFLE_PLAY.get()),
RADIO("MIX", Settings.HIDE_FLYOUT_MENU_START_RADIO.get()),
SUBSCRIBE("SUBSCRIBE", Settings.HIDE_FLYOUT_MENU_SUBSCRIBE.get()),
EDIT_PLAYLIST("EDIT", Settings.HIDE_FLYOUT_MENU_EDIT_PLAYLIST.get()),
DELETE_PLAYLIST("DELETE", Settings.HIDE_FLYOUT_MENU_DELETE_PLAYLIST.get()),
PLAY_NEXT("QUEUE_PLAY_NEXT", Settings.HIDE_FLYOUT_MENU_PLAY_NEXT.get()),
ADD_TO_QUEUE("QUEUE_MUSIC", Settings.HIDE_FLYOUT_MENU_ADD_TO_QUEUE.get()),
SAVE_TO_LIBRARY("LIBRARY_ADD", Settings.HIDE_FLYOUT_MENU_SAVE_TO_LIBRARY.get()),
REMOVE_FROM_LIBRARY("LIBRARY_REMOVE", Settings.HIDE_FLYOUT_MENU_REMOVE_FROM_LIBRARY.get()),
SAVE_TO_PLAYLIST("ADD_TO_PLAYLIST", Settings.HIDE_FLYOUT_MENU_SAVE_TO_PLAYLIST.get()),
REMOVE_FROM_PLAYLIST("REMOVE_FROM_PLAYLIST", Settings.HIDE_FLYOUT_MENU_REMOVE_FROM_PLAYLIST.get()),
DOWNLOAD("OFFLINE_DOWNLOAD", Settings.HIDE_FLYOUT_MENU_DOWNLOAD.get()),
GO_TO_EPISODE("INFO", Settings.HIDE_FLYOUT_MENU_GO_TO_EPISODE.get()),
GO_TO_PODCAST("BROADCAST", Settings.HIDE_FLYOUT_MENU_GO_TO_PODCAST.get()),
GO_TO_ALBUM("ALBUM", Settings.HIDE_FLYOUT_MENU_GO_TO_ALBUM.get()),
GO_TO_ARTIST("ARTIST", Settings.HIDE_FLYOUT_MENU_GO_TO_ARTIST.get()),
VIEW_SONG_CREDIT("PEOPLE_GROUP", Settings.HIDE_FLYOUT_MENU_VIEW_SONG_CREDIT.get()),
PIN_TO_SPEED_DIAL("KEEP", Settings.HIDE_FLYOUT_MENU_PIN_TO_SPEED_DIAL.get()),
UNPIN_FROM_SPEED_DIAL("KEEP_OFF", Settings.HIDE_FLYOUT_MENU_UNPIN_FROM_SPEED_DIAL.get()),
SHARE("SHARE", Settings.HIDE_FLYOUT_MENU_SHARE.get()),
DISMISS_QUEUE("DISMISS_QUEUE", Settings.HIDE_FLYOUT_MENU_DISMISS_QUEUE.get()),
HELP("HELP_OUTLINE", Settings.HIDE_FLYOUT_MENU_HELP.get()),
REPORT("FLAG", Settings.HIDE_FLYOUT_MENU_REPORT.get()),
QUALITY("SETTINGS_MATERIAL", Settings.HIDE_FLYOUT_MENU_QUALITY.get()),
CAPTIONS("CAPTIONS", Settings.HIDE_FLYOUT_MENU_CAPTIONS.get()),
STATS_FOR_NERDS("PLANNER_REVIEW", Settings.HIDE_FLYOUT_MENU_STATS_FOR_NERDS.get()),
SLEEP_TIMER("MOON_Z", Settings.HIDE_FLYOUT_MENU_SLEEP_TIMER.get());
ADD_TO_PLAYLIST(Settings.HIDE_FLYOUT_MENU_SAVE_TO_PLAYLIST),
ALBUM(Settings.HIDE_FLYOUT_MENU_GO_TO_ALBUM),
ARTIST(Settings.HIDE_FLYOUT_MENU_GO_TO_ARTIST),
BOOKMARK_BORDER(Settings.HIDE_FLYOUT_MENU_SAVE_EPISODE_FOR_LATER),
BROADCAST(Settings.HIDE_FLYOUT_MENU_GO_TO_PODCAST),
CAPTIONS(Settings.HIDE_FLYOUT_MENU_CAPTIONS),
DELETE(Settings.HIDE_FLYOUT_MENU_DELETE_PLAYLIST),
DISMISS_QUEUE(Settings.HIDE_FLYOUT_MENU_DISMISS_QUEUE),
EDIT(Settings.HIDE_FLYOUT_MENU_EDIT_PLAYLIST),
FLAG(Settings.HIDE_FLYOUT_MENU_REPORT),
HELP_OUTLINE(Settings.HIDE_FLYOUT_MENU_HELP),
HIDE(Settings.HIDE_FLYOUT_MENU_NOT_INTERESTED),
INFO(Settings.HIDE_FLYOUT_MENU_GO_TO_EPISODE),
KEEP(Settings.HIDE_FLYOUT_MENU_PIN_TO_SPEED_DIAL),
KEEP_OFF(Settings.HIDE_FLYOUT_MENU_UNPIN_FROM_SPEED_DIAL),
LIBRARY_ADD(Settings.HIDE_FLYOUT_MENU_SAVE_TO_LIBRARY),
LIBRARY_REMOVE(Settings.HIDE_FLYOUT_MENU_REMOVE_FROM_LIBRARY),
MIX(Settings.HIDE_FLYOUT_MENU_START_RADIO),
MOON_Z(Settings.HIDE_FLYOUT_MENU_SLEEP_TIMER),
OFFLINE_DOWNLOAD(Settings.HIDE_FLYOUT_MENU_DOWNLOAD),
PEOPLE_GROUP(Settings.HIDE_FLYOUT_MENU_VIEW_SONG_CREDIT),
PLANNER_REVIEW(Settings.HIDE_FLYOUT_MENU_STATS_FOR_NERDS),
QUEUE_MUSIC(Settings.HIDE_FLYOUT_MENU_ADD_TO_QUEUE),
QUEUE_PLAY_NEXT(Settings.HIDE_FLYOUT_MENU_PLAY_NEXT),
REMOVE_FROM_PLAYLIST(Settings.HIDE_FLYOUT_MENU_REMOVE_FROM_PLAYLIST),
SETTINGS_MATERIAL(Settings.HIDE_FLYOUT_MENU_QUALITY),
SHARE(Settings.HIDE_FLYOUT_MENU_SHARE),
SHUFFLE(Settings.HIDE_FLYOUT_MENU_SHUFFLE_PLAY),
SUBSCRIBE(Settings.HIDE_FLYOUT_MENU_SUBSCRIBE);
private final boolean enabled;
private final String name;
private final BooleanSetting setting;
FlyoutPanelComponent(String name, boolean enabled) {
this.enabled = enabled;
this.name = name;
FlyoutPanelComponent(BooleanSetting setting) {
this.setting = setting;
}
}
}

View File

@ -0,0 +1,115 @@
package app.revanced.extension.music.patches.general;
import android.app.Activity;
import android.content.Intent;
import androidx.annotation.NonNull;
import org.apache.commons.lang3.StringUtils;
import app.revanced.extension.music.settings.Settings;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.ResourceUtils;
@SuppressWarnings("unused")
public final class ChangeStartPagePatch {
public enum StartPage {
/**
* Unmodified type, and same as un-patched.
*/
ORIGINAL(""),
/**
* Browse id.
*/
CHARTS("FEmusic_charts"),
EXPLORE("FEmusic_explore"),
HISTORY("FEmusic_history"),
LIBRARY("FEmusic_library_landing"),
PODCASTS("FEmusic_non_music_audio"),
SAMPLES("FEmusic_immersive"),
SUBSCRIPTIONS("FEmusic_library_corpus_artists"),
/**
* Playlist id, this can be used as a browseId.
*/
EPISODES_FOR_LATER("VLSE"),
LIKED_MUSIC("VLLM"),
/**
* Intent extra.
*/
SEARCH("", 1, "Eh4IBRDTnQEYmgMiEwiZn+H0r5WLAxVV5OcDHcHRBmPqpd25AQA=");
@NonNull
final String browseId;
final int shortcutType;
/**
* Unique identifier for shortcut (Base64).
*/
@NonNull
final String shortcutId;
StartPage(@NonNull String browseId) {
this(browseId, 0, "");
}
StartPage(@NonNull String browseId, int shortcutType, @NonNull String shortcutId) {
this.browseId = browseId;
this.shortcutType = shortcutType;
this.shortcutId = shortcutId;
}
}
/**
* Intent action when YouTube is cold started from the launcher.
*/
private static final String ACTION_MAIN = "android.intent.action.MAIN";
private static final String SHORTCUT_ACTION = "com.google.android.youtube.music.action.shortcut";
private static final String SHORTCUT_CLASS_DESCRIPTOR = "com.google.android.apps.youtube.music.activities.InternalMusicActivity";
private static final String SHORTCUT_TYPE = "com.google.android.youtube.music.action.shortcut_type";
private static final StartPage START_PAGE = Settings.CHANGE_START_PAGE.get();
public static String overrideBrowseId(@NonNull String browseId) {
if (!browseId.equals("FEmusic_home")) {
return browseId;
}
final String overrideBrowseId = START_PAGE.browseId;
if (overrideBrowseId.isEmpty()) {
return browseId;
}
Logger.printDebug(() -> "Changing browseId to " + START_PAGE.name());
return overrideBrowseId;
}
public static void overrideIntent(@NonNull Intent intent) {
if (!StringUtils.equals(intent.getAction(), ACTION_MAIN)) {
Logger.printDebug(() -> "Ignore override intent action" +
" as the current activity is not the entry point of the application");
return;
}
final String overrideShortcutId = START_PAGE.shortcutId;
if (overrideShortcutId.isEmpty()) {
return;
}
Activity mActivity = ResourceUtils.getActivity();
if (mActivity == null) {
return;
}
Logger.printDebug(() -> "Changing intent action to " + START_PAGE.name());
intent.setAction(SHORTCUT_ACTION);
intent.setClassName(mActivity, SHORTCUT_CLASS_DESCRIPTOR);
intent.setPackage(mActivity.getPackageName());
intent.putExtra(SHORTCUT_TYPE, START_PAGE.shortcutType);
intent.putExtra(SHORTCUT_ACTION, overrideShortcutId);
}
}

View File

@ -1,6 +1,5 @@
package app.revanced.extension.music.patches.general;
import static app.revanced.extension.music.utils.ExtendedUtils.isSpoofingToLessThan;
import static app.revanced.extension.shared.utils.Utils.hideViewBy0dpUnderCondition;
import android.app.AlertDialog;
@ -32,17 +31,6 @@ public class GeneralPatch {
// endregion
// region [Change start page] patch
public static String changeStartPage(final String browseId) {
if (!browseId.equals("FEmusic_home"))
return browseId;
return Settings.CHANGE_START_PAGE.get();
}
// endregion
// region [Disable dislike redirection] patch
public static boolean disableDislikeRedirection() {
@ -172,7 +160,7 @@ public class GeneralPatch {
public static String restoreOldStyleLibraryShelf(final String browseId) {
final boolean oldStyleLibraryShelfEnabled =
Settings.RESTORE_OLD_STYLE_LIBRARY_SHELF.get() || isSpoofingToLessThan("5.38.00");
Settings.RESTORE_OLD_STYLE_LIBRARY_SHELF.get();
return oldStyleLibraryShelfEnabled && browseId.equals("FEmusic_library_landing")
? "FEmusic_liked"
: browseId;

View File

@ -4,38 +4,42 @@ import androidx.preference.PreferenceScreen;
import app.revanced.extension.music.settings.Settings;
import app.revanced.extension.shared.patches.BaseSettingsMenuPatch;
import app.revanced.extension.shared.settings.BooleanSetting;
@SuppressWarnings("unused")
public final class SettingsMenuPatch extends BaseSettingsMenuPatch {
private static final BooleanSetting HIDE_SETTINGS_MENU_PARENT_TOOLS =
Settings.HIDE_SETTINGS_MENU_PARENT_TOOLS;
public static void hideSettingsMenu(PreferenceScreen mPreferenceScreen) {
if (mPreferenceScreen == null) return;
for (SettingsMenuComponent component : SettingsMenuComponent.values())
if (component.enabled)
removePreference(mPreferenceScreen, component.key);
if (mPreferenceScreen != null) {
for (SettingsMenuComponent component : SettingsMenuComponent.values())
if (component.setting.get())
removePreference(mPreferenceScreen, component.key);
}
}
public static boolean hideParentToolsMenu(boolean original) {
return !Settings.HIDE_SETTINGS_MENU_PARENT_TOOLS.get() && original;
return !HIDE_SETTINGS_MENU_PARENT_TOOLS.get() && original;
}
private enum SettingsMenuComponent {
GENERAL("settings_header_general", Settings.HIDE_SETTINGS_MENU_GENERAL.get()),
PLAYBACK("settings_header_playback", Settings.HIDE_SETTINGS_MENU_PLAYBACK.get()),
DATA_SAVING("settings_header_data_saving", Settings.HIDE_SETTINGS_MENU_DATA_SAVING.get()),
DOWNLOADS_AND_STORAGE("settings_header_downloads_and_storage", Settings.HIDE_SETTINGS_MENU_DOWNLOADS_AND_STORAGE.get()),
NOTIFICATIONS("settings_header_notifications", Settings.HIDE_SETTINGS_MENU_NOTIFICATIONS.get()),
PRIVACY_AND_LOCATION("settings_header_privacy_and_location", Settings.HIDE_SETTINGS_MENU_PRIVACY_AND_LOCATION.get()),
RECOMMENDATIONS("settings_header_recommendations", Settings.HIDE_SETTINGS_MENU_RECOMMENDATIONS.get()),
PAID_MEMBERSHIPS("settings_header_paid_memberships", Settings.HIDE_SETTINGS_MENU_PAID_MEMBERSHIPS.get()),
ABOUT("settings_header_about_youtube_music", Settings.HIDE_SETTINGS_MENU_ABOUT.get());
GENERAL("settings_header_general", Settings.HIDE_SETTINGS_MENU_GENERAL),
PLAYBACK("settings_header_playback", Settings.HIDE_SETTINGS_MENU_PLAYBACK),
DATA_SAVING("settings_header_data_saving", Settings.HIDE_SETTINGS_MENU_DATA_SAVING),
DOWNLOADS_AND_STORAGE("settings_header_downloads_and_storage", Settings.HIDE_SETTINGS_MENU_DOWNLOADS_AND_STORAGE),
NOTIFICATIONS("settings_header_notifications", Settings.HIDE_SETTINGS_MENU_NOTIFICATIONS),
PRIVACY_AND_LOCATION("settings_header_privacy_and_location", Settings.HIDE_SETTINGS_MENU_PRIVACY_AND_LOCATION),
RECOMMENDATIONS("settings_header_recommendations", Settings.HIDE_SETTINGS_MENU_RECOMMENDATIONS),
PAID_MEMBERSHIPS("settings_header_paid_memberships", Settings.HIDE_SETTINGS_MENU_PAID_MEMBERSHIPS),
ABOUT("settings_header_about_youtube_music", Settings.HIDE_SETTINGS_MENU_ABOUT);
private final String key;
private final boolean enabled;
private final BooleanSetting setting;
SettingsMenuComponent(String key, boolean enabled) {
SettingsMenuComponent(String key, BooleanSetting setting) {
this.key = key;
this.enabled = enabled;
this.setting = setting;
}
}
}

View File

@ -1,87 +0,0 @@
package app.revanced.extension.music.patches.misc;
import app.revanced.extension.music.patches.misc.client.AppClient.ClientType;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class SpoofClientPatch {
private static final ClientType CLIENT_TYPE = Settings.SPOOF_CLIENT_TYPE.get();
public static final boolean SPOOF_CLIENT = Settings.SPOOF_CLIENT.get();
/**
* Injection point.
*/
public static int getClientTypeId(int originalClientTypeId) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.id;
}
return originalClientTypeId;
}
/**
* Injection point.
*/
public static String getClientVersion(String originalClientVersion) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.clientVersion;
}
return originalClientVersion;
}
/**
* Injection point.
*/
public static String getClientModel(String originalClientModel) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.deviceModel;
}
return originalClientModel;
}
/**
* Injection point.
*/
public static String getOsVersion(String originalOsVersion) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.osVersion;
}
return originalOsVersion;
}
/**
* Injection point.
*/
public static String getUserAgent(String originalUserAgent) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.userAgent;
}
return originalUserAgent;
}
/**
* Injection point.
*/
public static boolean isClientSpoofingEnabled() {
return SPOOF_CLIENT;
}
/**
* Injection point.
* <p>
* When spoofing the client to iOS, the playback speed menu is missing from the player response.
* This fix is required because playback speed is not available in YouTube Music Podcasts.
* <p>
* Return true to force create the playback speed menu.
*/
public static boolean forceCreatePlaybackSpeedMenu(boolean original) {
if (SPOOF_CLIENT) {
return true;
}
return original;
}
}

View File

@ -1,118 +0,0 @@
package app.revanced.extension.music.patches.misc.client;
import android.os.Build;
public class AppClient {
// Audio codec is MP4A.
private static final String CLIENT_VERSION_ANDROID_MUSIC_4_27 = "4.27.53";
// Audio codec is OPUS.
private static final String CLIENT_VERSION_ANDROID_MUSIC_5_29 = "5.29.53";
private static final String PACKAGE_NAME_ANDROID_MUSIC = "com.google.android.apps.youtube.music";
private static final String DEVICE_MODEL_ANDROID_MUSIC = Build.MODEL;
private static final String OS_VERSION_ANDROID_MUSIC = Build.VERSION.RELEASE;
// Audio codec is MP4A.
private static final String CLIENT_VERSION_IOS_MUSIC_6_21 = "6.21";
// Audio codec is OPUS.
private static final String CLIENT_VERSION_IOS_MUSIC_7_04 = "7.04";
private static final String PACKAGE_NAME_IOS_MUSIC = "com.google.ios.youtubemusic";
private static final String DEVICE_MODEL_IOS_MUSIC = "iPhone14,3";
private static final String OS_VERSION_IOS_MUSIC = "15.7.1.19H117";
private static final String USER_AGENT_VERSION_IOS_MUSIC = "15_7_1";
private AppClient() {
}
private static String androidUserAgent(String clientVersion) {
return PACKAGE_NAME_ANDROID_MUSIC +
"/" +
clientVersion +
" (Linux; U; Android " +
OS_VERSION_ANDROID_MUSIC +
"; GB) gzip";
}
private static String iOSUserAgent(String clientVersion) {
return PACKAGE_NAME_IOS_MUSIC +
"/" +
clientVersion +
"(" +
DEVICE_MODEL_IOS_MUSIC +
"; U; CPU iOS " +
USER_AGENT_VERSION_IOS_MUSIC +
" like Mac OS X)";
}
public enum ClientType {
ANDROID_MUSIC_4_27(21,
DEVICE_MODEL_ANDROID_MUSIC,
OS_VERSION_ANDROID_MUSIC,
androidUserAgent(CLIENT_VERSION_ANDROID_MUSIC_4_27),
CLIENT_VERSION_ANDROID_MUSIC_4_27
),
ANDROID_MUSIC_5_29(21,
DEVICE_MODEL_ANDROID_MUSIC,
OS_VERSION_ANDROID_MUSIC,
androidUserAgent(CLIENT_VERSION_ANDROID_MUSIC_5_29),
CLIENT_VERSION_ANDROID_MUSIC_5_29
),
IOS_MUSIC_6_21(
26,
DEVICE_MODEL_IOS_MUSIC,
OS_VERSION_IOS_MUSIC,
iOSUserAgent(CLIENT_VERSION_IOS_MUSIC_6_21),
CLIENT_VERSION_IOS_MUSIC_6_21
),
IOS_MUSIC_7_04(
26,
DEVICE_MODEL_IOS_MUSIC,
OS_VERSION_IOS_MUSIC,
iOSUserAgent(CLIENT_VERSION_IOS_MUSIC_7_04),
CLIENT_VERSION_IOS_MUSIC_7_04
);
/**
* YouTube
* <a href="https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients">client type</a>
*/
public final int id;
/**
* Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model)
*/
public final String deviceModel;
/**
* Device OS version.
*/
public final String osVersion;
/**
* Player user-agent.
*/
public final String userAgent;
/**
* App version.
*/
public final String clientVersion;
ClientType(int id,
String deviceModel,
String osVersion,
String userAgent,
String clientVersion
) {
this.id = id;
this.deviceModel = deviceModel;
this.clientVersion = clientVersion;
this.osVersion = osVersion;
this.userAgent = userAgent;
}
}
}

View File

@ -1,153 +1,237 @@
package app.revanced.extension.music.patches.player;
import static app.revanced.extension.shared.utils.StringRef.str;
import static app.revanced.extension.shared.utils.Utils.hideViewByRemovingFromParentUnderCondition;
import static app.revanced.extension.shared.utils.Utils.hideViewUnderCondition;
import static app.revanced.extension.shared.utils.Utils.isSDKAbove;
import static app.revanced.extension.shared.utils.Utils.runOnMainThreadDelayed;
import android.annotation.SuppressLint;
import android.graphics.Color;
import android.view.View;
import org.apache.commons.lang3.ArrayUtils;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import app.revanced.extension.music.settings.Settings;
import app.revanced.extension.music.shared.VideoType;
import app.revanced.extension.music.utils.VideoUtils;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.Utils;
@SuppressWarnings({"unused"})
@SuppressWarnings({"unused", "SpellCheckingInspection"})
public class PlayerPatch {
private static final int MUSIC_VIDEO_GREY_BACKGROUND_COLOR = -12566464;
private static final int MUSIC_VIDEO_ORIGINAL_BACKGROUND_COLOR = -16579837;
private static final boolean ADD_MINIPLAYER_NEXT_BUTTON =
Settings.ADD_MINIPLAYER_NEXT_BUTTON.get();
private static final boolean ADD_MINIPLAYER_PREVIOUS_BUTTON =
Settings.ADD_MINIPLAYER_PREVIOUS_BUTTON.get();
private static final boolean CHANGE_PLAYER_BACKGROUND_COLOR =
Settings.CHANGE_PLAYER_BACKGROUND_COLOR.get();
private static final boolean CHANGE_SEEK_BAR_POSITION =
Settings.CHANGE_SEEK_BAR_POSITION.get();
private static final boolean DISABLE_PLAYER_GESTURE =
Settings.DISABLE_PLAYER_GESTURE.get();
private static final boolean ENABLE_SWIPE_TO_DISMISS_MINIPLAYER =
Settings.ENABLE_SWIPE_TO_DISMISS_MINIPLAYER.get();
private static final boolean ENABLE_THICK_SEEKBAR =
Settings.ENABLE_THICK_SEEKBAR.get();
private static final boolean ENABLE_ZEN_MODE =
Settings.ENABLE_ZEN_MODE.get();
private static final boolean ENABLE_ZEN_MODE_PODCAST =
Settings.ENABLE_ZEN_MODE_PODCAST.get();
private static final boolean HIDE_DOUBLE_TAP_OVERLAY_FILTER =
Settings.HIDE_DOUBLE_TAP_OVERLAY_FILTER.get();
private static final boolean HIDE_FULLSCREEN_SHARE_BUTTON =
Settings.HIDE_FULLSCREEN_SHARE_BUTTON.get();
private static final boolean HIDE_SONG_VIDEO_TOGGLE =
Settings.HIDE_SONG_VIDEO_TOGGLE.get();
private static final boolean RESTORE_OLD_COMMENTS_POPUP_PANELS =
Settings.RESTORE_OLD_COMMENTS_POPUP_PANELS.get();
private static final boolean SETTINGS_INITIALIZED =
Settings.SETTINGS_INITIALIZED.get();
@SuppressLint("StaticFieldLeak")
public static View previousButton;
@SuppressLint("StaticFieldLeak")
public static View nextButton;
private static final StringSetting CUSTOM_PLAYER_BACKGROUND_COLOR_PRIMARY =
Settings.CUSTOM_PLAYER_BACKGROUND_COLOR_PRIMARY;
private static final StringSetting CUSTOM_PLAYER_BACKGROUND_COLOR_SECONDARY =
Settings.CUSTOM_PLAYER_BACKGROUND_COLOR_SECONDARY;
public static boolean disableMiniPlayerGesture() {
return Settings.DISABLE_MINI_PLAYER_GESTURE.get();
private static final int ZEN_MODE_BACKGROUND_COLOR = 0xFF404040;
private static final int MUSIC_VIDEO_BACKGROUND_COLOR = 0xFF030303;
private static final int[] MUSIC_VIDEO_GRADIENT_COLORS = {MUSIC_VIDEO_BACKGROUND_COLOR, MUSIC_VIDEO_BACKGROUND_COLOR};
private static final int[] ZEN_MODE_GRADIENT_COLORS = {ZEN_MODE_BACKGROUND_COLOR, ZEN_MODE_BACKGROUND_COLOR};
private static final int[] customColorGradient = new int[2];
private static boolean colorInitalized = false;
private static WeakReference<View> previousButtonViewRef = new WeakReference<>(null);
private static WeakReference<View> nextButtonViewRef = new WeakReference<>(null);
static {
if (CHANGE_PLAYER_BACKGROUND_COLOR)
loadPlayerbackgroundColor();
}
public static boolean disablePlayerGesture() {
return Settings.DISABLE_PLAYER_GESTURE.get();
}
private static void loadPlayerbackgroundColor() {
try {
customColorGradient[0] = Color.parseColor(CUSTOM_PLAYER_BACKGROUND_COLOR_PRIMARY.get());
customColorGradient[1] = Color.parseColor(CUSTOM_PLAYER_BACKGROUND_COLOR_SECONDARY.get());
colorInitalized = true;
} catch (Exception ex) {
Utils.showToastShort(str("revanced_custom_player_background_invalid_toast"));
Utils.showToastShort(str("revanced_extended_reset_to_default_toast"));
CUSTOM_PLAYER_BACKGROUND_COLOR_PRIMARY.resetToDefault();
CUSTOM_PLAYER_BACKGROUND_COLOR_SECONDARY.resetToDefault();
public static boolean enableColorMatchPlayer() {
return Settings.ENABLE_COLOR_MATCH_PLAYER.get();
}
public static int enableBlackPlayerBackground(int originalColor) {
return Settings.ENABLE_BLACK_PLAYER_BACKGROUND.get()
&& originalColor != MUSIC_VIDEO_GREY_BACKGROUND_COLOR
? Color.BLACK
: originalColor;
}
public static boolean enableForceMinimizedPlayer(boolean original) {
return Settings.ENABLE_FORCE_MINIMIZED_PLAYER.get() || original;
}
public static boolean enableMiniPlayerNextButton(boolean original) {
return !Settings.ENABLE_MINI_PLAYER_NEXT_BUTTON.get() && original;
}
public static View[] getViewArray(View[] oldViewArray) {
if (previousButton != null) {
if (nextButton != null) {
return getViewArray(getViewArray(oldViewArray, previousButton), nextButton);
} else {
return getViewArray(oldViewArray, previousButton);
}
} else {
return oldViewArray;
loadPlayerbackgroundColor();
}
}
private static View[] getViewArray(View[] oldViewArray, View newView) {
final int oldViewArrayLength = oldViewArray.length;
View[] newViewArray = Arrays.copyOf(oldViewArray, oldViewArrayLength + 1);
newViewArray[oldViewArrayLength] = newView;
return newViewArray;
public static boolean addMiniPlayerNextButton(boolean original) {
return !ADD_MINIPLAYER_NEXT_BUTTON && original;
}
public static void setNextButton(View nextButtonView) {
if (nextButtonView == null)
return;
public static boolean changeMiniPlayerColor() {
return Settings.CHANGE_MINIPLAYER_COLOR.get();
}
hideViewUnderCondition(
!Settings.ENABLE_MINI_PLAYER_NEXT_BUTTON.get(),
nextButtonView
);
public static int[] changePlayerBackgroundColor(int[] colors) {
if (Arrays.equals(MUSIC_VIDEO_GRADIENT_COLORS, colors)) {
final VideoType videoType = VideoType.getCurrent();
final boolean isZenMode = ENABLE_ZEN_MODE &&
(videoType.isMusicVideo() || (videoType.isPodCast() && ENABLE_ZEN_MODE_PODCAST));
if (isZenMode) {
return ZEN_MODE_GRADIENT_COLORS;
}
}
if (CHANGE_PLAYER_BACKGROUND_COLOR && colorInitalized) {
return customColorGradient;
}
nextButtonView.setOnClickListener(PlayerPatch::setNextButtonOnClickListener);
return colors;
}
public static boolean changeSeekBarPosition(boolean original) {
return SETTINGS_INITIALIZED
? CHANGE_SEEK_BAR_POSITION
: original;
}
public static boolean disableMiniPlayerGesture() {
return Settings.DISABLE_MINIPLAYER_GESTURE.get();
}
public static boolean disablePlayerGesture() {
return DISABLE_PLAYER_GESTURE;
}
public static boolean enableForcedMiniPlayer(boolean original) {
return Settings.ENABLE_FORCED_MINIPLAYER.get() || original;
}
public static View[] getViewArray(View[] oldViewArray) {
View previousButtonView = previousButtonViewRef.get();
if (previousButtonView != null) {
oldViewArray = ArrayUtils.add(oldViewArray, previousButtonView);
View nextButtonView = nextButtonViewRef.get();
if (nextButtonView != null) {
oldViewArray = ArrayUtils.add(oldViewArray, nextButtonView);
}
}
return oldViewArray;
}
public static void setNextButtonView(View nextButtonView) {
nextButtonViewRef = new WeakReference<>(nextButtonView);
}
public static void setNextButtonOnClickListener(View nextButtonView) {
if (nextButtonView != null) {
hideViewUnderCondition(
!ADD_MINIPLAYER_NEXT_BUTTON,
nextButtonView
);
nextButtonView.setOnClickListener(v -> nextButtonClicked(nextButtonView));
}
}
// rest of the implementation added by patch.
private static void setNextButtonOnClickListener(View view) {
if (Settings.ENABLE_MINI_PLAYER_NEXT_BUTTON.get())
view.getClass();
private static void nextButtonClicked(View view) {
// These instructions are ignored by patch.
Logger.printDebug(() -> "next button clicked: " + view);
}
public static void setPreviousButton(View previousButtonView) {
if (previousButtonView == null)
return;
public static void setPreviousButtonView(View previousButtonView) {
previousButtonViewRef = new WeakReference<>(previousButtonView);
}
hideViewUnderCondition(
!Settings.ENABLE_MINI_PLAYER_PREVIOUS_BUTTON.get(),
previousButtonView
);
public static void setPreviousButtonOnClickListener(View previousButtonView) {
if (previousButtonView != null) {
hideViewUnderCondition(
!ADD_MINIPLAYER_PREVIOUS_BUTTON,
previousButtonView
);
previousButtonView.setOnClickListener(PlayerPatch::setPreviousButtonOnClickListener);
previousButtonView.setOnClickListener(v -> previousButtonClicked(previousButtonView));
}
}
// rest of the implementation added by patch.
private static void setPreviousButtonOnClickListener(View view) {
if (Settings.ENABLE_MINI_PLAYER_PREVIOUS_BUTTON.get())
view.getClass();
private static void previousButtonClicked(View view) {
// These instructions are ignored by patch.
Logger.printDebug(() -> "previous button clicked: " + view);
}
public static boolean enableSwipeToDismissMiniPlayer() {
return Settings.ENABLE_SWIPE_TO_DISMISS_MINI_PLAYER.get();
return ENABLE_SWIPE_TO_DISMISS_MINIPLAYER;
}
public static boolean enableSwipeToDismissMiniPlayer(boolean original) {
return !Settings.ENABLE_SWIPE_TO_DISMISS_MINI_PLAYER.get() && original;
return !ENABLE_SWIPE_TO_DISMISS_MINIPLAYER && original;
}
public static Object enableSwipeToDismissMiniPlayer(Object object) {
return Settings.ENABLE_SWIPE_TO_DISMISS_MINI_PLAYER.get() ? null : object;
return ENABLE_SWIPE_TO_DISMISS_MINIPLAYER ? null : object;
}
public static boolean enableThickSeekBar(boolean original) {
return SETTINGS_INITIALIZED
? ENABLE_THICK_SEEKBAR
: original;
}
public static int enableZenMode(int originalColor) {
if (Settings.ENABLE_ZEN_MODE.get() && originalColor == MUSIC_VIDEO_ORIGINAL_BACKGROUND_COLOR) {
if (Settings.ENABLE_ZEN_MODE_PODCAST.get() || !VideoType.getCurrent().isPodCast()) {
return MUSIC_VIDEO_GREY_BACKGROUND_COLOR;
if (ENABLE_ZEN_MODE && originalColor == MUSIC_VIDEO_BACKGROUND_COLOR) {
final VideoType videoType = VideoType.getCurrent();
if (videoType.isMusicVideo() || (videoType.isPodCast() && ENABLE_ZEN_MODE_PODCAST)) {
return ZEN_MODE_BACKGROUND_COLOR;
}
}
return originalColor;
}
public static void hideAudioVideoSwitchToggle(View view, int originalVisibility) {
if (Settings.HIDE_AUDIO_VIDEO_SWITCH_TOGGLE.get()) {
originalVisibility = View.GONE;
}
view.setVisibility(originalVisibility);
public static void hideSongVideoToggle(View view, int originalVisibility) {
view.setVisibility(
HIDE_SONG_VIDEO_TOGGLE
? View.GONE
: originalVisibility
);
}
public static void hideDoubleTapOverlayFilter(View view) {
hideViewByRemovingFromParentUnderCondition(Settings.HIDE_DOUBLE_TAP_OVERLAY_FILTER, view);
hideViewByRemovingFromParentUnderCondition(HIDE_DOUBLE_TAP_OVERLAY_FILTER, view);
}
public static int hideFullscreenShareButton(int original) {
return Settings.HIDE_FULLSCREEN_SHARE_BUTTON.get() ? 0 : original;
return HIDE_FULLSCREEN_SHARE_BUTTON ? 0 : original;
}
public static void setShuffleState(Enum<?> shuffleState) {
if (!Settings.REMEMBER_SHUFFLE_SATE.get())
return;
Settings.ALWAYS_SHUFFLE.save(shuffleState.ordinal() == 1);
if (Settings.REMEMBER_SHUFFLE_SATE.get()) {
Settings.ALWAYS_SHUFFLE.save(shuffleState.ordinal() == 1);
}
}
public static void shuffleTracks() {
@ -163,7 +247,7 @@ public class PlayerPatch {
return;
if (needDelay) {
Utils.runOnMainThreadDelayed(VideoUtils::shuffleTracks, 1000);
runOnMainThreadDelayed(VideoUtils::shuffleTracks, 1000);
} else {
VideoUtils.shuffleTracks();
}
@ -182,14 +266,13 @@ public class PlayerPatch {
}
public static boolean restoreOldCommentsPopUpPanels(boolean original) {
if (!Settings.SETTINGS_INITIALIZED.get()) {
return original;
}
return !Settings.RESTORE_OLD_COMMENTS_POPUP_PANELS.get() && original;
return SETTINGS_INITIALIZED
? !RESTORE_OLD_COMMENTS_POPUP_PANELS && original
: original;
}
public static boolean restoreOldPlayerBackground(boolean original) {
if (!Settings.SETTINGS_INITIALIZED.get()) {
if (!SETTINGS_INITIALIZED) {
return original;
}
if (!isSDKAbove(23)) {
@ -203,7 +286,7 @@ public class PlayerPatch {
}
public static boolean restoreOldPlayerLayout(boolean original) {
if (!Settings.SETTINGS_INITIALIZED.get()) {
if (!SETTINGS_INITIALIZED) {
return original;
}
return !Settings.RESTORE_OLD_PLAYER_LAYOUT.get();

View File

@ -1,9 +1,10 @@
package app.revanced.extension.music.patches.utils;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import org.apache.commons.lang3.ArrayUtils;
@ -21,10 +22,10 @@ public class DrawableColorPatch {
// background colors
private static final Drawable headerGradient =
ResourceUtils.getDrawable("revanced_header_gradient");
private static final Drawable transparentDrawable =
new ColorDrawable(Color.TRANSPARENT);
private static final int blackColor =
ResourceUtils.getColor("yt_black1");
private static final int elementsContainerIdentifier =
ResourceUtils.getIdIdentifier("elements_container");
public static int getLithoColor(int colorValue) {
return ArrayUtils.contains(DARK_COLORS, colorValue)
@ -34,29 +35,36 @@ public class DrawableColorPatch {
public static void setHeaderGradient(ViewGroup viewGroup) {
viewGroup.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
if (!(viewGroup instanceof FrameLayout frameLayout))
return;
if (!(frameLayout.getChildAt(0) instanceof ViewGroup firstChildView))
if (!(viewGroup.getChildAt(0) instanceof ViewGroup firstChildView))
return;
View secondChildView = firstChildView.getChildAt(0);
if (secondChildView instanceof ImageView gradientView) {
// Album
setHeaderGradient(viewGroup, gradientView);
setHeaderGradient(gradientView);
} else if (secondChildView instanceof ViewGroup thirdChildView &&
thirdChildView.getChildCount() == 1 &&
thirdChildView.getChildAt(0) instanceof ImageView gradientView) {
// Playlist
setHeaderGradient(viewGroup, gradientView);
setHeaderGradient(gradientView);
}
});
}
private static void setHeaderGradient(ViewGroup viewGroup, ImageView gradientView) {
// For some reason, it sometimes applies to other lithoViews.
// To prevent this, check the viewId before applying the gradient.
if (headerGradient != null && viewGroup.getId() == elementsContainerIdentifier) {
private static void setHeaderGradient(ImageView gradientView) {
// headerGradient is litho, so this view is sometimes used elsewhere, like the button of the action bar.
// In order to prevent the gradient to be applied to the button of the action bar,
// Add a layout listener to the ImageView.
if (headerGradient != null && gradientView.getForeground() == null) {
gradientView.setForeground(headerGradient);
gradientView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
if (gradientView.getParent() instanceof View view &&
view.getContentDescription() != null &&
gradientView.getForeground() == headerGradient
) {
gradientView.setForeground(transparentDrawable);
}
});
}
}
}

View File

@ -12,6 +12,6 @@ public class PatchStatus {
}
public static String SpoofAppVersionDefaultString() {
return "6.11.52";
return "6.42.55";
}
}

View File

@ -20,6 +20,7 @@ import app.revanced.extension.shared.utils.Logger;
*/
@SuppressWarnings("unused")
public class ReturnYouTubeDislikePatch {
private static volatile boolean isNewActionBar = false;
/**
* Injection point.
@ -52,7 +53,7 @@ public class ReturnYouTubeDislikePatch {
if (!(original instanceof Spanned)) {
original = new SpannableString(original);
}
return videoData.getDislikesSpan((Spanned) original, true);
return videoData.getDislikesSpan((Spanned) original, true, isNewActionBar);
} catch (Exception ex) {
Logger.printException(() -> "onLithoTextLoaded failure", ex);
}
@ -90,13 +91,21 @@ public class ReturnYouTubeDislikePatch {
if (videoData == null) {
return original; // User enabled RYD while a video was on screen.
}
return videoData.getDislikesSpan(original, false);
return videoData.getDislikesSpan(original, false, false);
} catch (Exception ex) {
Logger.printException(() -> "onSpannedCreated failure", ex);
}
return original;
}
/**
* Injection point.
*/
public static boolean actionBarFeatureFlagLoaded(boolean original) {
isNewActionBar = original;
return original;
}
/**
* Injection point.
*/

View File

@ -159,7 +159,8 @@ public class ReturnYouTubeDislike {
@NonNull
private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable,
@NonNull RYDVoteData voteData,
boolean isLithoText) {
boolean isLithoText,
boolean isNewActionBar) {
CharSequence oldLikes = oldSpannable;
// YouTube creators can hide the like count on a video,
@ -185,22 +186,33 @@ public class ReturnYouTubeDislike {
SpannableStringBuilder builder = new SpannableStringBuilder("\u2009");
if (!isLithoText) {
builder.append("\u2009");
builder.append("\u2009\u2009\u2009");
}
final boolean compactLayout = Settings.RYD_COMPACT_LAYOUT.get();
if (middleSeparatorBounds == null) {
final DisplayMetrics dp = Utils.getResources().getDisplayMetrics();
final int unit;
if (isNewActionBar) {
unit = 15;
} else if (isLithoText) {
unit = 23;
} else {
unit = 25;
}
leftSeparatorBounds = new Rect(0, 0,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1.2f, dp),
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, isLithoText ? 23 : 25, dp));
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, unit, dp));
final int middleSeparatorSize =
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize);
}
if (!compactLayout) {
String leftSeparatorString = "\u200E "; // u200E = left to right character
// u200E = left to right character
String leftSeparatorString = isLithoText
? "\u200E "
: "\u200E ";
Spannable leftSeparatorSpan = new SpannableString(leftSeparatorString);
ShapeDrawable shapeDrawable = new ShapeDrawable(new RectShape());
shapeDrawable.getPaint().setColor(getSeparatorColor(isLithoText));
@ -436,12 +448,12 @@ public class ReturnYouTubeDislike {
* @return the replacement span containing dislikes, or the original span if RYD is not available.
*/
@NonNull
public synchronized Spanned getDislikesSpan(@NonNull Spanned original, boolean isLithoText) {
return waitForFetchAndUpdateReplacementSpan(original, isLithoText);
public synchronized Spanned getDislikesSpan(@NonNull Spanned original, boolean isLithoText, boolean isNewActionBar) {
return waitForFetchAndUpdateReplacementSpan(original, isLithoText, isNewActionBar);
}
@NonNull
private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, boolean isLithoText) {
private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, boolean isLithoText, boolean isNewActionBar) {
try {
RYDVoteData votingData = getFetchData(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH);
if (votingData == null) {
@ -476,7 +488,7 @@ public class ReturnYouTubeDislike {
votingData.updateUsingVote(userVote);
}
originalDislikeSpan = original;
replacementLikeDislikeSpan = createDislikeSpan(original, votingData, isLithoText);
replacementLikeDislikeSpan = createDislikeSpan(original, votingData, isLithoText, isNewActionBar);
Logger.printDebug(() -> "Replaced: '" + originalDislikeSpan + "' with: '"
+ replacementLikeDislikeSpan + "'" + " using video: " + videoId);

View File

@ -6,8 +6,8 @@ import static app.revanced.extension.music.sponsorblock.objects.CategoryBehaviou
import androidx.annotation.NonNull;
import app.revanced.extension.music.patches.general.ChangeStartPagePatch.StartPage;
import app.revanced.extension.music.patches.misc.AlbumMusicVideoPatch.RedirectType;
import app.revanced.extension.music.patches.misc.client.AppClient.ClientType;
import app.revanced.extension.music.patches.utils.PatchStatus;
import app.revanced.extension.music.sponsorblock.SponsorBlockSettings;
import app.revanced.extension.shared.settings.BaseSettings;
@ -18,6 +18,7 @@ import app.revanced.extension.shared.settings.IntegerSetting;
import app.revanced.extension.shared.settings.LongSetting;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.Utils;
@ -32,11 +33,13 @@ public class Settings extends BaseSettings {
// PreferenceScreen: Action Bar
public static final BooleanSetting CHANGE_ACTION_BAR_POSITION = new BooleanSetting("revanced_change_action_bar_position", FALSE, true);
public static final BooleanSetting HIDE_ACTION_BUTTON_LIKE_DISLIKE = new BooleanSetting("revanced_hide_action_button_like_dislike", FALSE, true);
public static final BooleanSetting HIDE_ACTION_BUTTON_COMMENT = new BooleanSetting("revanced_hide_action_button_comment", FALSE, true);
public static final BooleanSetting HIDE_ACTION_BUTTON_ADD_TO_PLAYLIST = new BooleanSetting("revanced_hide_action_button_add_to_playlist", FALSE, true);
public static final BooleanSetting HIDE_ACTION_BUTTON_DOWNLOAD = new BooleanSetting("revanced_hide_action_button_download", FALSE, true);
public static final BooleanSetting HIDE_ACTION_BUTTON_SHARE = new BooleanSetting("revanced_hide_action_button_share", FALSE, true);
public static final BooleanSetting HIDE_ACTION_BUTTON_SONG_VIDEO = new BooleanSetting("revanced_hide_action_button_song_video", FALSE, true);
public static final BooleanSetting HIDE_ACTION_BUTTON_RADIO = new BooleanSetting("revanced_hide_action_button_radio", FALSE, true);
public static final BooleanSetting HIDE_ACTION_BUTTON_LABEL = new BooleanSetting("revanced_hide_action_button_label", FALSE, true);
public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action", FALSE, true);
@ -52,46 +55,47 @@ public class Settings extends BaseSettings {
// PreferenceScreen: Flyout menu
public static final BooleanSetting ENABLE_TRIM_SILENCE = new BooleanSetting("revanced_enable_trim_silence", FALSE, true);
public static final BooleanSetting ENABLE_TRIM_SILENCE = new BooleanSetting("revanced_enable_trim_silence", FALSE);
public static final BooleanSetting ENABLE_COMPACT_DIALOG = new BooleanSetting("revanced_enable_compact_dialog", TRUE);
public static final BooleanSetting HIDE_FLYOUT_MENU_LIKE_DISLIKE = new BooleanSetting("revanced_hide_flyout_menu_like_dislike", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_3_COLUMN_COMPONENT = new BooleanSetting("revanced_hide_flyout_menu_3_column_component", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_ADD_TO_QUEUE = new BooleanSetting("revanced_hide_flyout_menu_add_to_queue", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_CAPTIONS = new BooleanSetting("revanced_hide_flyout_menu_captions", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_DELETE_PLAYLIST = new BooleanSetting("revanced_hide_flyout_menu_delete_playlist", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_DISMISS_QUEUE = new BooleanSetting("revanced_hide_flyout_menu_dismiss_queue", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_DOWNLOAD = new BooleanSetting("revanced_hide_flyout_menu_download", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_EDIT_PLAYLIST = new BooleanSetting("revanced_hide_flyout_menu_edit_playlist", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_GO_TO_ALBUM = new BooleanSetting("revanced_hide_flyout_menu_go_to_album", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_GO_TO_ARTIST = new BooleanSetting("revanced_hide_flyout_menu_go_to_artist", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_GO_TO_EPISODE = new BooleanSetting("revanced_hide_flyout_menu_go_to_episode", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_GO_TO_PODCAST = new BooleanSetting("revanced_hide_flyout_menu_go_to_podcast", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_HELP = new BooleanSetting("revanced_hide_flyout_menu_help", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_PIN_TO_SPEED_DIAL = new BooleanSetting("revanced_hide_flyout_menu_pin_to_speed_dial", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_PLAY_NEXT = new BooleanSetting("revanced_hide_flyout_menu_play_next", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_QUALITY = new BooleanSetting("revanced_hide_flyout_menu_quality", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_REMOVE_FROM_LIBRARY = new BooleanSetting("revanced_hide_flyout_menu_remove_from_library", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_REMOVE_FROM_PLAYLIST = new BooleanSetting("revanced_hide_flyout_menu_remove_from_playlist", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_REPORT = new BooleanSetting("revanced_hide_flyout_menu_report", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_SAVE_EPISODE_FOR_LATER = new BooleanSetting("revanced_hide_flyout_menu_save_episode_for_later", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_SAVE_TO_LIBRARY = new BooleanSetting("revanced_hide_flyout_menu_save_to_library", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_SAVE_TO_PLAYLIST = new BooleanSetting("revanced_hide_flyout_menu_save_to_playlist", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_SHARE = new BooleanSetting("revanced_hide_flyout_menu_share", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_SHUFFLE_PLAY = new BooleanSetting("revanced_hide_flyout_menu_shuffle_play", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_SLEEP_TIMER = new BooleanSetting("revanced_hide_flyout_menu_sleep_timer", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_START_RADIO = new BooleanSetting("revanced_hide_flyout_menu_start_radio", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_STATS_FOR_NERDS = new BooleanSetting("revanced_hide_flyout_menu_stats_for_nerds", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_SUBSCRIBE = new BooleanSetting("revanced_hide_flyout_menu_subscribe", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_UNPIN_FROM_SPEED_DIAL = new BooleanSetting("revanced_hide_flyout_menu_unpin_from_speed_dial", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_VIEW_SONG_CREDIT = new BooleanSetting("revanced_hide_flyout_menu_view_song_credit", FALSE, true);
public static final BooleanSetting REPLACE_FLYOUT_MENU_DISMISS_QUEUE = new BooleanSetting("revanced_replace_flyout_menu_dismiss_queue", FALSE, true);
public static final BooleanSetting HIDE_FLYOUT_MENU_3_COLUMN_COMPONENT = new BooleanSetting("revanced_hide_flyout_menu_3_column_component", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_ADD_TO_QUEUE = new BooleanSetting("revanced_hide_flyout_menu_add_to_queue", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_CAPTIONS = new BooleanSetting("revanced_hide_flyout_menu_captions", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_DELETE_PLAYLIST = new BooleanSetting("revanced_hide_flyout_menu_delete_playlist", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_DISMISS_QUEUE = new BooleanSetting("revanced_hide_flyout_menu_dismiss_queue", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_DOWNLOAD = new BooleanSetting("revanced_hide_flyout_menu_download", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_EDIT_PLAYLIST = new BooleanSetting("revanced_hide_flyout_menu_edit_playlist", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_GO_TO_ALBUM = new BooleanSetting("revanced_hide_flyout_menu_go_to_album", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_GO_TO_ARTIST = new BooleanSetting("revanced_hide_flyout_menu_go_to_artist", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_GO_TO_EPISODE = new BooleanSetting("revanced_hide_flyout_menu_go_to_episode", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_GO_TO_PODCAST = new BooleanSetting("revanced_hide_flyout_menu_go_to_podcast", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_HELP = new BooleanSetting("revanced_hide_flyout_menu_help", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_NOT_INTERESTED = new BooleanSetting("revanced_hide_flyout_menu_not_interested", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_PIN_TO_SPEED_DIAL = new BooleanSetting("revanced_hide_flyout_menu_pin_to_speed_dial", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_PLAY_NEXT = new BooleanSetting("revanced_hide_flyout_menu_play_next", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_QUALITY = new BooleanSetting("revanced_hide_flyout_menu_quality", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_REMOVE_FROM_LIBRARY = new BooleanSetting("revanced_hide_flyout_menu_remove_from_library", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_REMOVE_FROM_PLAYLIST = new BooleanSetting("revanced_hide_flyout_menu_remove_from_playlist", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_REPORT = new BooleanSetting("revanced_hide_flyout_menu_report", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_SAVE_EPISODE_FOR_LATER = new BooleanSetting("revanced_hide_flyout_menu_save_episode_for_later", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_SAVE_TO_LIBRARY = new BooleanSetting("revanced_hide_flyout_menu_save_to_library", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_SAVE_TO_PLAYLIST = new BooleanSetting("revanced_hide_flyout_menu_save_to_playlist", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_SHARE = new BooleanSetting("revanced_hide_flyout_menu_share", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_SHUFFLE_PLAY = new BooleanSetting("revanced_hide_flyout_menu_shuffle_play", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_SLEEP_TIMER = new BooleanSetting("revanced_hide_flyout_menu_sleep_timer", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_START_RADIO = new BooleanSetting("revanced_hide_flyout_menu_start_radio", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_STATS_FOR_NERDS = new BooleanSetting("revanced_hide_flyout_menu_stats_for_nerds", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_SUBSCRIBE = new BooleanSetting("revanced_hide_flyout_menu_subscribe", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_UNPIN_FROM_SPEED_DIAL = new BooleanSetting("revanced_hide_flyout_menu_unpin_from_speed_dial", FALSE);
public static final BooleanSetting HIDE_FLYOUT_MENU_VIEW_SONG_CREDIT = new BooleanSetting("revanced_hide_flyout_menu_view_song_credit", FALSE);
public static final BooleanSetting REPLACE_FLYOUT_MENU_DISMISS_QUEUE = new BooleanSetting("revanced_replace_flyout_menu_dismiss_queue", FALSE);
public static final BooleanSetting REPLACE_FLYOUT_MENU_DISMISS_QUEUE_CONTINUE_WATCH = new BooleanSetting("revanced_replace_flyout_menu_dismiss_queue_continue_watch", TRUE);
public static final BooleanSetting REPLACE_FLYOUT_MENU_REPORT = new BooleanSetting("revanced_replace_flyout_menu_report", TRUE, true);
public static final BooleanSetting REPLACE_FLYOUT_MENU_REPORT_ONLY_PLAYER = new BooleanSetting("revanced_replace_flyout_menu_report_only_player", TRUE, true);
public static final BooleanSetting REPLACE_FLYOUT_MENU_REPORT = new BooleanSetting("revanced_replace_flyout_menu_report", TRUE);
public static final BooleanSetting REPLACE_FLYOUT_MENU_REPORT_ONLY_PLAYER = new BooleanSetting("revanced_replace_flyout_menu_report_only_player", TRUE);
// PreferenceScreen: General
public static final StringSetting CHANGE_START_PAGE = new StringSetting("revanced_change_start_page", "FEmusic_home", true);
public static final EnumSetting<StartPage> CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.ORIGINAL, true);
public static final BooleanSetting DISABLE_DISLIKE_REDIRECTION = new BooleanSetting("revanced_disable_dislike_redirection", FALSE);
public static final BooleanSetting ENABLE_LANDSCAPE_MODE = new BooleanSetting("revanced_enable_landscape_mode", FALSE, true);
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
@ -129,21 +133,25 @@ public class Settings extends BaseSettings {
// PreferenceScreen: Player
public static final BooleanSetting ENABLE_MINI_PLAYER_NEXT_BUTTON = new BooleanSetting("revanced_enable_mini_player_next_button", TRUE, true);
public static final BooleanSetting ENABLE_MINI_PLAYER_PREVIOUS_BUTTON = new BooleanSetting("revanced_enable_mini_player_previous_button", TRUE, true);
public static final BooleanSetting ENABLE_COLOR_MATCH_PLAYER = new BooleanSetting("revanced_enable_color_match_player", TRUE);
public static final BooleanSetting ENABLE_BLACK_PLAYER_BACKGROUND = new BooleanSetting("revanced_enable_black_player_background", FALSE, true);
public static final BooleanSetting DISABLE_MINI_PLAYER_GESTURE = new BooleanSetting("revanced_disable_mini_player_gesture", FALSE, true);
public static final BooleanSetting ADD_MINIPLAYER_NEXT_BUTTON = new BooleanSetting("revanced_add_miniplayer_next_button", TRUE, true);
public static final BooleanSetting ADD_MINIPLAYER_PREVIOUS_BUTTON = new BooleanSetting("revanced_add_miniplayer_previous_button", TRUE, true);
public static final BooleanSetting CHANGE_MINIPLAYER_COLOR = new BooleanSetting("revanced_change_miniplayer_color", TRUE);
public static final BooleanSetting CHANGE_PLAYER_BACKGROUND_COLOR = new BooleanSetting("revanced_change_player_background_color", FALSE, true);
public static final StringSetting CUSTOM_PLAYER_BACKGROUND_COLOR_PRIMARY = new StringSetting("revanced_custom_player_background_color_primary", "#000000", true);
public static final StringSetting CUSTOM_PLAYER_BACKGROUND_COLOR_SECONDARY = new StringSetting("revanced_custom_player_background_color_secondary", "#000000", true);
public static final BooleanSetting CHANGE_SEEK_BAR_POSITION = new BooleanSetting("revanced_change_seekbar_position", FALSE, true);
public static final BooleanSetting DISABLE_MINIPLAYER_GESTURE = new BooleanSetting("revanced_disable_miniplayer_gesture", FALSE, true);
public static final BooleanSetting DISABLE_PLAYER_GESTURE = new BooleanSetting("revanced_disable_player_gesture", FALSE, true);
public static final BooleanSetting ENABLE_FORCE_MINIMIZED_PLAYER = new BooleanSetting("revanced_enable_force_minimized_player", TRUE);
public static final BooleanSetting ENABLE_SWIPE_TO_DISMISS_MINI_PLAYER = new BooleanSetting("revanced_enable_swipe_to_dismiss_mini_player", TRUE, true);
public static final BooleanSetting ENABLE_ZEN_MODE = new BooleanSetting("revanced_enable_zen_mode", FALSE);
public static final BooleanSetting ENABLE_ZEN_MODE_PODCAST = new BooleanSetting("revanced_enable_zen_mode_podcast", FALSE);
public static final BooleanSetting ENABLE_FORCED_MINIPLAYER = new BooleanSetting("revanced_enable_forced_miniplayer", TRUE);
public static final BooleanSetting ENABLE_SWIPE_TO_DISMISS_MINIPLAYER = new BooleanSetting("revanced_enable_swipe_to_dismiss_miniplayer", TRUE, true);
public static final BooleanSetting ENABLE_THICK_SEEKBAR = new BooleanSetting("revanced_enable_thick_seekbar", TRUE, true);
public static final BooleanSetting ENABLE_ZEN_MODE = new BooleanSetting("revanced_enable_zen_mode", FALSE, true);
public static final BooleanSetting ENABLE_ZEN_MODE_PODCAST = new BooleanSetting("revanced_enable_zen_mode_podcast", FALSE, true);
public static final BooleanSetting HIDE_COMMENT_CHANNEL_GUIDELINES = new BooleanSetting("revanced_hide_comment_channel_guidelines", TRUE);
public static final BooleanSetting HIDE_DOUBLE_TAP_OVERLAY_FILTER = new BooleanSetting("revanced_hide_double_tap_overlay_filter", FALSE, true);
public static final BooleanSetting HIDE_COMMENT_TIMESTAMP_AND_EMOJI_BUTTONS = new BooleanSetting("revanced_hide_comment_timestamp_and_emoji_buttons", FALSE);
public static final BooleanSetting HIDE_FULLSCREEN_SHARE_BUTTON = new BooleanSetting("revanced_hide_fullscreen_share_button", FALSE, true);
public static final BooleanSetting HIDE_AUDIO_VIDEO_SWITCH_TOGGLE = new BooleanSetting("revanced_hide_audio_video_switch_toggle", FALSE, true);
public static final BooleanSetting HIDE_SONG_VIDEO_TOGGLE = new BooleanSetting("revanced_hide_song_video_toggle", FALSE, true);
public static final BooleanSetting REMEMBER_REPEAT_SATE = new BooleanSetting("revanced_remember_repeat_state", TRUE);
public static final BooleanSetting REMEMBER_SHUFFLE_SATE = new BooleanSetting("revanced_remember_shuffle_state", TRUE);
public static final BooleanSetting ALWAYS_SHUFFLE = new BooleanSetting("revanced_always_shuffle", FALSE);
@ -153,16 +161,16 @@ public class Settings extends BaseSettings {
// PreferenceScreen: Settings menu
public static final BooleanSetting HIDE_SETTINGS_MENU_PARENT_TOOLS = new BooleanSetting("revanced_hide_settings_menu_parent_tools", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_GENERAL = new BooleanSetting("revanced_hide_settings_menu_general", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_PLAYBACK = new BooleanSetting("revanced_hide_settings_menu_playback", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_DATA_SAVING = new BooleanSetting("revanced_hide_settings_menu_data_saving", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_DOWNLOADS_AND_STORAGE = new BooleanSetting("revanced_hide_settings_menu_downloads_and_storage", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_NOTIFICATIONS = new BooleanSetting("revanced_hide_settings_menu_notification", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_PRIVACY_AND_LOCATION = new BooleanSetting("revanced_hide_settings_menu_privacy_and_location", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_RECOMMENDATIONS = new BooleanSetting("revanced_hide_settings_menu_recommendations", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_PAID_MEMBERSHIPS = new BooleanSetting("revanced_hide_settings_menu_paid_memberships", TRUE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_ABOUT = new BooleanSetting("revanced_hide_settings_menu_about", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_PARENT_TOOLS = new BooleanSetting("revanced_hide_settings_menu_parent_tools", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_GENERAL = new BooleanSetting("revanced_hide_settings_menu_general", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_PLAYBACK = new BooleanSetting("revanced_hide_settings_menu_playback", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_DATA_SAVING = new BooleanSetting("revanced_hide_settings_menu_data_saving", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_DOWNLOADS_AND_STORAGE = new BooleanSetting("revanced_hide_settings_menu_downloads_and_storage", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_NOTIFICATIONS = new BooleanSetting("revanced_hide_settings_menu_notification", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_PRIVACY_AND_LOCATION = new BooleanSetting("revanced_hide_settings_menu_privacy_and_location", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_RECOMMENDATIONS = new BooleanSetting("revanced_hide_settings_menu_recommendations", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_PAID_MEMBERSHIPS = new BooleanSetting("revanced_hide_settings_menu_paid_memberships", TRUE);
public static final BooleanSetting HIDE_SETTINGS_MENU_ABOUT = new BooleanSetting("revanced_hide_settings_menu_about", FALSE);
// PreferenceScreen: Video
@ -184,9 +192,6 @@ public class Settings extends BaseSettings {
public static final EnumSetting<RedirectType> DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE = new EnumSetting<>("revanced_disable_music_video_in_album_redirect_type", RedirectType.REDIRECT, true);
public static final BooleanSetting ENABLE_OPUS_CODEC = new BooleanSetting("revanced_enable_opus_codec", FALSE, true);
public static final BooleanSetting SETTINGS_IMPORT_EXPORT = new BooleanSetting("revanced_extended_settings_import_export", FALSE, false);
public static final BooleanSetting SPOOF_CLIENT = new BooleanSetting("revanced_spoof_client", FALSE, true);
public static final EnumSetting<ClientType> SPOOF_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_client_type", ClientType.IOS_MUSIC_6_21, true);
// PreferenceScreen: Return YouTube Dislike
public static final BooleanSetting RYD_ENABLED = new BooleanSetting("revanced_ryd_enabled", TRUE);
@ -194,7 +199,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting RYD_DISLIKE_PERCENTAGE = new BooleanSetting("revanced_ryd_dislike_percentage", FALSE);
public static final BooleanSetting RYD_COMPACT_LAYOUT = new BooleanSetting("revanced_ryd_compact_layout", FALSE);
public static final BooleanSetting RYD_ESTIMATED_LIKE = new BooleanSetting("revanced_ryd_estimated_like", FALSE, true);
public static final BooleanSetting RYD_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("revanced_ryd_toast_on_connection_error", FALSE);
public static final BooleanSetting RYD_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("revanced_ryd_toast_on_connection_error", TRUE);
// PreferenceScreen: Return YouTube Username
public static final BooleanSetting RETURN_YOUTUBE_USERNAME_ABOUT = new BooleanSetting("revanced_return_youtube_username_youtube_data_api_v3_about", FALSE, false);
@ -202,7 +207,7 @@ public class Settings extends BaseSettings {
// PreferenceScreen: SponsorBlock
public static final BooleanSetting SB_ENABLED = new BooleanSetting("sb_enabled", TRUE);
public static final BooleanSetting SB_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("sb_toast_on_connection_error", FALSE);
public static final BooleanSetting SB_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("sb_toast_on_connection_error", TRUE);
public static final BooleanSetting SB_TOAST_ON_SKIP = new BooleanSetting("sb_toast_on_skip", TRUE);
public static final StringSetting SB_API_URL = new StringSetting("sb_api_url", "https://sponsor.ajay.app");
public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id", "");
@ -229,6 +234,16 @@ public class Settings extends BaseSettings {
public static final LongSetting SB_LAST_VIP_CHECK = new LongSetting("sb_last_vip_check", 0L, false, false);
static {
// region Migration
// Old spoof versions that no longer work reliably.
if (SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0) {
Logger.printInfo(() -> "Resetting spoof app version target");
SPOOF_APP_VERSION_TARGET.resetToDefault();
}
// endregion
// region SB import/export callbacks
Setting.addImportExportCallback(SponsorBlockSettings.SB_IMPORT_EXPORT_CALLBACK);
@ -251,20 +266,22 @@ public class Settings extends BaseSettings {
CHANGE_START_PAGE.key,
CUSTOM_FILTER_STRINGS.key,
CUSTOM_PLAYBACK_SPEEDS.key,
CUSTOM_PLAYER_BACKGROUND_COLOR_PRIMARY.key,
CUSTOM_PLAYER_BACKGROUND_COLOR_SECONDARY.key,
DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE.key,
ENABLE_CUSTOM_NAVIGATION_BAR_COLOR_VALUE.key,
EXTERNAL_DOWNLOADER_PACKAGE_NAME.key,
HIDE_ACCOUNT_MENU_FILTER_STRINGS.key,
OPEN_DEFAULT_APP_SETTINGS,
OPTIONAL_SPONSOR_BLOCK_SETTINGS_PREFIX,
RETURN_YOUTUBE_USERNAME_ABOUT.key,
RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT.key,
RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY.key,
SB_API_URL.key,
SETTINGS_IMPORT_EXPORT.key,
SPOOF_APP_VERSION_TARGET.key,
SPOOF_CLIENT_TYPE.key,
SPOOF_STREAMING_DATA_TYPE.key,
RETURN_YOUTUBE_USERNAME_ABOUT.key,
RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT.key,
RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY.key,
OPEN_DEFAULT_APP_SETTINGS,
OPTIONAL_SPONSOR_BLOCK_SETTINGS_PREFIX
WATCH_HISTORY_TYPE.key,
};
/**

View File

@ -4,6 +4,8 @@ import static app.revanced.extension.music.settings.Settings.BYPASS_IMAGE_REGION
import static app.revanced.extension.music.settings.Settings.CHANGE_START_PAGE;
import static app.revanced.extension.music.settings.Settings.CUSTOM_FILTER_STRINGS;
import static app.revanced.extension.music.settings.Settings.CUSTOM_PLAYBACK_SPEEDS;
import static app.revanced.extension.music.settings.Settings.CUSTOM_PLAYER_BACKGROUND_COLOR_PRIMARY;
import static app.revanced.extension.music.settings.Settings.CUSTOM_PLAYER_BACKGROUND_COLOR_SECONDARY;
import static app.revanced.extension.music.settings.Settings.DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE;
import static app.revanced.extension.music.settings.Settings.ENABLE_CUSTOM_NAVIGATION_BAR_COLOR_VALUE;
import static app.revanced.extension.music.settings.Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME;
@ -15,12 +17,12 @@ import static app.revanced.extension.music.settings.Settings.SB_API_URL;
import static app.revanced.extension.music.settings.Settings.SETTINGS_IMPORT_EXPORT;
import static app.revanced.extension.music.settings.Settings.SPOOF_APP_VERSION_TARGET;
import static app.revanced.extension.music.settings.Settings.SPOOF_CLIENT_TYPE;
import static app.revanced.extension.music.settings.Settings.WATCH_HISTORY_TYPE;
import static app.revanced.extension.music.utils.ExtendedUtils.getDialogBuilder;
import static app.revanced.extension.music.utils.ExtendedUtils.getLayoutParams;
import static app.revanced.extension.music.utils.RestartUtils.showRestartDialog;
import static app.revanced.extension.shared.settings.BaseSettings.RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT;
import static app.revanced.extension.shared.settings.BaseSettings.RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY;
import static app.revanced.extension.shared.settings.BaseSettings.SPOOF_STREAMING_DATA_TYPE;
import static app.revanced.extension.shared.settings.Setting.getSettingFromPath;
import static app.revanced.extension.shared.utils.ResourceUtils.getStringArray;
import static app.revanced.extension.shared.utils.StringRef.str;
@ -136,11 +138,11 @@ public class ReVancedPreferenceFragment extends PreferenceFragment {
final Setting<?> settings = getSettingFromPath(dataString);
if (settings instanceof StringSetting stringSetting) {
if (settings.equals(CHANGE_START_PAGE)) {
ResettableListPreference.showDialog(mActivity, stringSetting, 2);
} else if (settings.equals(BYPASS_IMAGE_REGION_RESTRICTIONS_DOMAIN)
if (settings.equals(BYPASS_IMAGE_REGION_RESTRICTIONS_DOMAIN)
|| settings.equals(CUSTOM_FILTER_STRINGS)
|| settings.equals(CUSTOM_PLAYBACK_SPEEDS)
|| settings.equals(CUSTOM_PLAYER_BACKGROUND_COLOR_PRIMARY)
|| settings.equals(CUSTOM_PLAYER_BACKGROUND_COLOR_SECONDARY)
|| settings.equals(ENABLE_CUSTOM_NAVIGATION_BAR_COLOR_VALUE)
|| settings.equals(HIDE_ACCOUNT_MENU_FILTER_STRINGS)
|| settings.equals(RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY)) {
@ -163,10 +165,11 @@ public class ReVancedPreferenceFragment extends PreferenceFragment {
Logger.printDebug(() -> "Failed to find the right value: " + dataString);
}
} else if (settings instanceof EnumSetting<?> enumSetting) {
if (settings.equals(DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE)
if (settings.equals(CHANGE_START_PAGE)
|| settings.equals(DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE)
|| settings.equals(RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT)
|| settings.equals(SPOOF_CLIENT_TYPE)
|| settings.equals(SPOOF_STREAMING_DATA_TYPE)
|| settings.equals(WATCH_HISTORY_TYPE)
) {
ResettableListPreference.showDialog(mActivity, enumSetting, 0);
}

View File

@ -10,7 +10,6 @@ import androidx.annotation.NonNull;
import org.apache.commons.lang3.ArrayUtils;
import java.util.Arrays;
import java.util.Locale;
import app.revanced.extension.shared.settings.EnumSetting;
@ -29,7 +28,7 @@ public class ResettableListPreference {
final String[] mEntries = getStringArray(entryKey);
final String[] mEntryValues = getStringArray(entryValueKey);
final int findIndex = Arrays.binarySearch(mEntryValues, setting.get());
final int findIndex = ArrayUtils.indexOf(mEntryValues, setting.get());
mClickedDialogEntryIndex = findIndex >= 0 ? findIndex : defaultIndex;
getDialogBuilder(mActivity)

View File

@ -0,0 +1,19 @@
package app.revanced.extension.music.shared;
@SuppressWarnings("unused")
public final class NavigationBar {
private static volatile int lastIndex = 0;
/**
* Injection point.
*/
public static void navigationTabSelected(int index, boolean isSelected) {
if (isSelected) {
lastIndex = index;
}
}
public static int getNavigationTabIndex() {
return lastIndex;
}
}

View File

@ -12,6 +12,7 @@ import app.revanced.extension.shared.utils.PackageUtils;
public class ExtendedUtils extends PackageUtils {
@SuppressWarnings("unused")
public static boolean isSpoofingToLessThan(@NonNull String versionName) {
if (!Settings.SPOOF_APP_VERSION.get())
return false;

View File

@ -7,9 +7,7 @@ import android.widget.LinearLayout;
import app.revanced.extension.reddit.settings.preference.ReVancedPreferenceFragment;
/**
* @noinspection ALL
*/
@SuppressWarnings("all")
public class ActivityHook {
public static void initialize(Activity activity) {
SettingsStatus.load();

View File

@ -1,56 +1,120 @@
package app.revanced.extension.shared.patches;
import static app.revanced.extension.shared.utils.StringRef.str;
import static app.revanced.extension.shared.utils.Utils.hideViewBy0dpUnderCondition;
import android.app.Dialog;
import android.content.DialogInterface;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.Utils;
@SuppressWarnings("unused")
public class FullscreenAdsPatch {
private static final boolean hideFullscreenAdsEnabled = BaseSettings.HIDE_FULLSCREEN_ADS.get();
private static final boolean HIDE_FULLSCREEN_ADS =
BaseSettings.HIDE_FULLSCREEN_ADS.get();
private static final ByteArrayFilterGroup exception =
new ByteArrayFilterGroup(
null,
"post_image_lightbox.eml" // Community post image in fullscreen
);
public static boolean disableFullscreenAds(final byte[] bytes, int type) {
if (!hideFullscreenAdsEnabled) {
return false;
}
/**
* Whether the last built dialog contains an ad
*/
private static boolean isFullscreenAds = false;
/**
* {@link Dialog#dismiss()} can be used after {@link Dialog#show()}
* When {@link Dialog#show()} is invoked, byte buffer register may not exist
* (byte buffer register has already been assigned to another value)
* <p>
* Therefore, make sure that the dialog contains the ads at the beginning of the Method
*
* @param bytes proto buffer array
* @param type dialog type (similar to {@link Enum#ordinal()})
*/
public static void checkDialog(byte[] bytes, int type) {
if (!HIDE_FULLSCREEN_ADS) {
return;
}
final DialogType dialogType = DialogType.getDialogType(type);
final String dialogName = dialogType.name();
// The dialog type of a fullscreen dialog is always {@code DialogType.FULLSCREEN}
if (dialogType != DialogType.FULLSCREEN) {
Logger.printDebug(() -> "Ignoring dialogType " + dialogName);
return false;
Logger.printDebug(() -> "Ignoring dialogType: " + dialogName);
isFullscreenAds = false;
return;
}
// Image in community post in fullscreen is not filtered
// Whether dialog is community post image (not ads)
final boolean isException = bytes != null &&
exception.check(bytes).isFiltered();
if (isException) {
Logger.printDebug(() -> "Ignoring exception");
} else {
Logger.printDebug(() -> "Blocked fullscreen ads");
}
return !isException;
isFullscreenAds = !isException;
}
/**
* Called after {@link #checkDialog(byte[], int)}
*
* @param customDialog Custom dialog which bound by litho
* Can be cast as {@link Dialog} or {@link DialogInterface}
*/
public static void dismissDialog(Object customDialog) {
if (isFullscreenAds && customDialog instanceof Dialog dialog) {
Window window = dialog.getWindow();
if (window != null) {
WindowManager.LayoutParams params = window.getAttributes();
params.height = 0;
params.width = 0;
// Change the size of dialog to 0.
window.setAttributes(params);
// Disable dialog's background dim.
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
// Hide DecorView.
View decorView = window.getDecorView();
decorView.setVisibility(View.GONE);
// Dismiss dialog.
dialog.dismiss();
if (BaseSettings.ENABLE_DEBUG_LOGGING.get()) {
Utils.showToastShort(str("revanced_fullscreen_ads_closed_toast"));
}
}
} else {
Logger.printDebug(() -> "customDialog type: " + customDialog.getClass().getName());
}
}
/**
* Injection point.
* Invoke only in old clients.
*/
public static void hideFullscreenAds(View view) {
hideViewBy0dpUnderCondition(
hideFullscreenAdsEnabled,
HIDE_FULLSCREEN_ADS,
view
);
}
/**
* YouTube and YouTube Music do not have Enum class for DialogType,
* but they have structures similar to Enum
* It can also be replaced by {@link Enum#ordinal()}
*/
private enum DialogType {
NULL(0),
ALERT(1),
@ -64,8 +128,8 @@ public class FullscreenAdsPatch {
}
private static DialogType getDialogType(int type) {
for (DialogType val : values())
if (type == val.type) return val;
for (DialogType dialogType : values())
if (type == dialogType.type) return dialogType;
return DialogType.NULL;
}

View File

@ -2,12 +2,13 @@ package app.revanced.extension.shared.patches;
@SuppressWarnings("unused")
public class PatchStatus {
public static boolean HideFullscreenAdsDefaultBoolean() {
public static boolean SpoofClient() {
// Replace this with true If the Spoof client patch succeeds in YouTube Music.
return false;
}
public static boolean SpoofStreamingDataMusic() {
// Replace this with true If the Spoof streaming data patch succeeds in YouTube Music
public static boolean SpoofStreamingData() {
// Replace this with true If the Spoof streaming data patch succeeds in YouTube.
return false;
}
}

View File

@ -14,12 +14,14 @@ import app.revanced.extension.shared.utils.Logger;
@SuppressWarnings("unused")
public class ReturnYouTubeUsernamePatch {
private static final boolean RETURN_YOUTUBE_USERNAME_ENABLED = BaseSettings.RETURN_YOUTUBE_USERNAME_ENABLED.get();
private static final Boolean RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT = BaseSettings.RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT.get().userNameFirst;
private static final String YOUTUBE_API_KEY = BaseSettings.RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY.get();
private static final String YOUTUBE_API_KEY =
BaseSettings.RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY.get();
private static final boolean RETURN_YOUTUBE_USERNAME_ENABLED =
BaseSettings.RETURN_YOUTUBE_USERNAME_ENABLED.get() && !YOUTUBE_API_KEY.isEmpty();
private static final Boolean RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT =
BaseSettings.RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT.get().userNameFirst;
private static final String AUTHOR_BADGE_PATH = "|author_badge.eml|";
private static volatile String lastFetchedHandle = "";
/**
* Injection point.
@ -51,18 +53,12 @@ public class ReturnYouTubeUsernamePatch {
if (!RETURN_YOUTUBE_USERNAME_ENABLED) {
return original;
}
if (YOUTUBE_API_KEY.isEmpty()) {
Logger.printDebug(() -> "API key is empty");
return original;
}
// In comments, the path to YouTube Handle(@youtube) always includes [AUTHOR_BADGE_PATH].
if (!conversionContext.toString().contains(AUTHOR_BADGE_PATH)) {
return original;
}
String handle = original.toString();
if (fetchNeeded && !handle.equals(lastFetchedHandle)) {
lastFetchedHandle = handle;
// Get the original username using YouTube Data API v3.
if (fetchNeeded) {
ChannelRequest.fetchRequestIfNeeded(handle, YOUTUBE_API_KEY, RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT);
return original;
}
@ -77,9 +73,7 @@ public class ReturnYouTubeUsernamePatch {
Logger.printDebug(() -> "ChannelRequest Stream is null, handle:" + handle);
return original;
}
final CharSequence copiedSpannableString = copySpannableString(original, userName);
Logger.printDebug(() -> "Replaced: '" + original + "' with: '" + copiedSpannableString + "'");
return copiedSpannableString;
return copySpannableString(original, userName);
} catch (Exception ex) {
Logger.printException(() -> "onLithoTextLoaded failure", ex);
}

View File

@ -1,9 +1,9 @@
package app.revanced.extension.youtube.patches.misc;
package app.revanced.extension.shared.patches;
import android.net.Uri;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.settings.BaseSettings;
@SuppressWarnings("unused")
public final class WatchHistoryPatch {
@ -18,7 +18,7 @@ public final class WatchHistoryPatch {
private static final String WWW_TRACKING_URL_AUTHORITY = "www.youtube.com";
public static Uri replaceTrackingUrl(Uri trackingUrl) {
final WatchHistoryType watchHistoryType = Settings.WATCH_HISTORY_TYPE.get();
final WatchHistoryType watchHistoryType = BaseSettings.WATCH_HISTORY_TYPE.get();
if (watchHistoryType != WatchHistoryType.ORIGINAL) {
try {
if (watchHistoryType == WatchHistoryType.REPLACE) {

View File

@ -0,0 +1,196 @@
package app.revanced.extension.shared.patches.client;
import android.os.Build;
import java.util.Locale;
public class MusicAppClient {
// Response to the '/next' request is 'Please update to continue using the app':
// https://github.com/inotia00/ReVanced_Extended/issues/2743
// Nevertheless, '/player' request is still valid.
// Audio codec is MP4A.
private static final String CLIENT_VERSION_ANDROID_MUSIC_4_27 = "4.27.53";
// Audio codec is OPUS.
private static final String CLIENT_VERSION_ANDROID_MUSIC_5_29 = "5.29.53";
private static final String PACKAGE_NAME_ANDROID_MUSIC = "com.google.android.apps.youtube.music";
private static final String DEVICE_BRAND_ANDROID_MUSIC = Build.BRAND;
private static final String DEVICE_MAKE_ANDROID_MUSIC = Build.MANUFACTURER;
private static final String DEVICE_MODEL_ANDROID_MUSIC = Build.MODEL;
private static final String BUILD_ID_ANDROID_MUSIC = Build.ID;
// In YouTube, this OS name is used to hide ads in Shorts.
private static final String OS_NAME_ANDROID_MUSIC = "Android Automotive";
private static final String OS_VERSION_ANDROID_MUSIC = Build.VERSION.RELEASE;
// Audio codec is MP4A.
private static final String CLIENT_VERSION_IOS_MUSIC_6_21 = "6.21";
private static final String DEVICE_MODEL_IOS_MUSIC_6_21 = "iPhone14,3";
private static final String OS_VERSION_IOS_MUSIC_6_21 = "15.7.1.19H117";
private static final String USER_AGENT_VERSION_IOS_MUSIC_6_21 = "15_7_1";
// Audio codec is OPUS.
private static final String CLIENT_VERSION_IOS_MUSIC_7_04 = "7.04";
// Release date for iOS YouTube Music 7.04 is June 4, 2024.
// Release date for iOS 18 is September 16, 2024.
// Since iOS cannot downgrade the OS version or app version in the usual way,
// 17.7.2 is used as an iOS version that matches iOS YouTube Music 7.04.
private static final String DEVICE_MODEL_IOS_MUSIC_7_04 = "iPhone16,2";
private static final String OS_VERSION_IOS_MUSIC_7_04 = "17.7.2.21H221";
private static final String USER_AGENT_VERSION_IOS_MUSIC_7_04 = "17_7_2";
private static final String PACKAGE_NAME_IOS_MUSIC = "com.google.ios.youtubemusic";
private static final String DEVICE_BRAND_IOS_MUSIC = "Apple";
private static final String DEVICE_MAKE_IOS_MUSIC = "Apple";
private static final String OS_NAME_IOS_MUSIC = "iOS";
private MusicAppClient() {
}
private static String androidUserAgent(String clientVersion) {
return PACKAGE_NAME_ANDROID_MUSIC +
"/" +
clientVersion +
"(Linux; U; Android " +
OS_VERSION_ANDROID_MUSIC +
"; " +
Locale.getDefault() +
"; " +
DEVICE_MODEL_ANDROID_MUSIC +
" Build/" +
BUILD_ID_ANDROID_MUSIC +
") gzip";
}
private static String iOSUserAgent(String clientVersion, String deviceModel, String osVersion) {
return PACKAGE_NAME_IOS_MUSIC +
"/" +
clientVersion +
" (" +
deviceModel +
"; U; CPU iOS " +
osVersion +
" like Mac OS X; " +
Locale.getDefault() +
")";
}
public enum ActionButtonType {
NONE, // No action button (~ 6.14)
YOUTUBE_BUTTON, // Type of action button is YouTubeButton (6.15 ~ 7.16)
LITHO // Type of action button is ComponentHost (7.17 ~)
}
public enum ClientType {
ANDROID_MUSIC_4_27(21,
DEVICE_BRAND_ANDROID_MUSIC,
DEVICE_MAKE_ANDROID_MUSIC,
DEVICE_MODEL_ANDROID_MUSIC,
OS_NAME_ANDROID_MUSIC,
OS_VERSION_ANDROID_MUSIC,
androidUserAgent(CLIENT_VERSION_ANDROID_MUSIC_4_27),
CLIENT_VERSION_ANDROID_MUSIC_4_27,
ActionButtonType.NONE
),
ANDROID_MUSIC_5_29(21,
DEVICE_BRAND_ANDROID_MUSIC,
DEVICE_MAKE_ANDROID_MUSIC,
DEVICE_MODEL_ANDROID_MUSIC,
OS_NAME_ANDROID_MUSIC,
OS_VERSION_ANDROID_MUSIC,
androidUserAgent(CLIENT_VERSION_ANDROID_MUSIC_5_29),
CLIENT_VERSION_ANDROID_MUSIC_5_29,
ActionButtonType.NONE
),
IOS_MUSIC_6_21(26,
DEVICE_BRAND_IOS_MUSIC,
DEVICE_MAKE_IOS_MUSIC,
DEVICE_MODEL_IOS_MUSIC_6_21,
OS_NAME_IOS_MUSIC,
OS_VERSION_IOS_MUSIC_6_21,
iOSUserAgent(CLIENT_VERSION_IOS_MUSIC_6_21, DEVICE_MODEL_IOS_MUSIC_6_21, USER_AGENT_VERSION_IOS_MUSIC_6_21),
CLIENT_VERSION_IOS_MUSIC_6_21,
ActionButtonType.YOUTUBE_BUTTON
),
IOS_MUSIC_7_04(26,
DEVICE_BRAND_IOS_MUSIC,
DEVICE_MAKE_IOS_MUSIC,
DEVICE_MODEL_IOS_MUSIC_7_04,
OS_NAME_IOS_MUSIC,
OS_VERSION_IOS_MUSIC_7_04,
iOSUserAgent(CLIENT_VERSION_IOS_MUSIC_7_04, DEVICE_MODEL_IOS_MUSIC_7_04, USER_AGENT_VERSION_IOS_MUSIC_7_04),
CLIENT_VERSION_IOS_MUSIC_7_04,
ActionButtonType.LITHO
);
/**
* YouTube
* <a href="https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients">client type</a>
*/
public final int id;
/**
* Device brand, equivalent to {@link Build#BRAND} (System property: ro.product.vendor.brand)
*/
public final String deviceBrand;
/**
* Device make, equivalent to {@link Build#MANUFACTURER} (System property: ro.product.vendor.manufacturer)
*/
public final String deviceMake;
/**
* Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model)
*/
public final String deviceModel;
/**
* Device OS name.
*/
public final String osName;
/**
* Device OS version.
*/
public final String osVersion;
/**
* Player user-agent.
*/
public final String userAgent;
/**
* App version.
*/
public final String clientVersion;
private final ActionButtonType actionButtonType;
ClientType(int id,
String deviceBrand,
String deviceMake,
String deviceModel,
String osName,
String osVersion,
String userAgent,
String clientVersion,
ActionButtonType actionButtonType
) {
this.id = id;
this.deviceBrand = deviceBrand;
this.deviceMake = deviceMake;
this.deviceModel = deviceModel;
this.clientVersion = clientVersion;
this.osName = osName;
this.osVersion = osVersion;
this.userAgent = userAgent;
this.actionButtonType = actionButtonType;
}
public boolean isYouTubeButton() {
return actionButtonType == ActionButtonType.YOUTUBE_BUTTON;
}
}
}

View File

@ -1,15 +1,15 @@
package app.revanced.extension.shared.patches.client
import android.os.Build
import app.revanced.extension.shared.patches.PatchStatus
import app.revanced.extension.shared.settings.BaseSettings
import app.revanced.extension.shared.utils.PackageUtils
import org.apache.commons.lang3.ArrayUtils
import java.util.Locale
/**
* Used to fetch streaming data.
*/
object AppClient {
object YouTubeAppClient {
// IOS
/**
* Video not playable: Paid / Movie / Private / Age-restricted
@ -74,6 +74,15 @@ object AppClient {
iOSUserAgent(PACKAGE_NAME_IOS_UNPLUGGED, CLIENT_VERSION_IOS_UNPLUGGED)
// ANDROID
private const val PACKAGE_NAME_ANDROID = "com.google.android.youtube"
private val CLIENT_VERSION_ANDROID = PackageUtils.getAppVersionName()
private val USER_AGENT_ANDROID = androidUserAgent(
packageName = PACKAGE_NAME_ANDROID,
clientVersion = CLIENT_VERSION_ANDROID,
)
// ANDROID VR
/**
* Video not playable: Kids
@ -92,7 +101,7 @@ object AppClient {
* [the App Store page of the YouTube app](https://www.meta.com/en-us/experiences/2002317119880945/),
* in the `Additional details` section.
*/
private const val CLIENT_VERSION_ANDROID_VR = "1.61.48"
private const val CLIENT_VERSION_ANDROID_VR = "1.62.27"
/**
* The device machine id for the Meta Quest 3, used to get opus codec with the Android VR client.
@ -108,7 +117,6 @@ object AppClient {
*/
private const val ANDROID_SDK_VERSION_ANDROID_VR = "32"
private const val BUILD_ID_ANDROID_VR = "SQ3A.220605.009.A1"
private const val CHIPSET_ANDROID_VR = "Qualcomm;SXR2230P"
private val USER_AGENT_ANDROID_VR = androidUserAgent(
packageName = PACKAGE_NAME_ANDROID_VR,
@ -137,7 +145,6 @@ object AppClient {
private const val ANDROID_SDK_VERSION_ANDROID_UNPLUGGED = "34"
private const val BUILD_ID_ANDROID_UNPLUGGED = "UTT3.240625.001.K5"
private const val GMS_CORE_VERSION_CODE_ANDROID_UNPLUGGED = "244336107"
private const val CHIPSET_ANDROID_UNPLUGGED = "Mediatek;MT8696"
private val USER_AGENT_ANDROID_UNPLUGGED = androidUserAgent(
packageName = PACKAGE_NAME_ANDROID_UNPLUGGED,
@ -166,7 +173,6 @@ object AppClient {
private const val ANDROID_SDK_VERSION_ANDROID_CREATOR = "35"
private const val BUILD_ID_ANDROID_CREATOR = "AP3A.241005.015.A2"
private const val GMS_CORE_VERSION_CODE_ANDROID_CREATOR = "244738035"
private const val CHIPSET_ANDROID_CREATOR = "Google;Tensor G4"
private val USER_AGENT_ANDROID_CREATOR = androidUserAgent(
packageName = PACKAGE_NAME_ANDROID_CREATOR,
@ -177,39 +183,6 @@ object AppClient {
)
// ANDROID MUSIC
/**
* Video not playable: All videos that can't be played on YouTube Music
*/
private const val PACKAGE_NAME_ANDROID_MUSIC = "com.google.android.apps.youtube.music"
/**
* Older client versions don't seem to require poToken.
* It is not the default client yet, as it requires sufficient testing.
*/
private const val CLIENT_VERSION_ANDROID_MUSIC = "4.27.53"
/**
* The device machine id for the Google Pixel 4.
* See [this GitLab](https://dumps.tadiphone.dev/dumps/google/flame) for more information.
*/
private const val DEVICE_MODEL_ANDROID_MUSIC = "Pixel 4"
private const val DEVICE_MAKE_ANDROID_MUSIC = "Google"
private const val OS_VERSION_ANDROID_MUSIC = "11"
private const val ANDROID_SDK_VERSION_ANDROID_MUSIC = "30"
private const val BUILD_ID_ANDROID_MUSIC = "SPP2.210219.008"
private const val GMS_CORE_VERSION_CODE_ANDROID_MUSIC = "244738022"
private const val CHIPSET_ANDROID_MUSIC = "Qualcomm;SM8150"
private val USER_AGENT_ANDROID_MUSIC = androidUserAgent(
packageName = PACKAGE_NAME_ANDROID_MUSIC,
clientVersion = CLIENT_VERSION_ANDROID_MUSIC,
osVersion = OS_VERSION_ANDROID_MUSIC,
deviceModel = DEVICE_MODEL_ANDROID_MUSIC,
buildId = BUILD_ID_ANDROID_MUSIC
)
/**
* Same format as Android YouTube User-Agent.
* Example: 'com.google.android.youtube/19.46.40(Linux; U; Android 13; in_ID; 21061110AG Build/TP1A.220624.014) gzip'
@ -240,10 +213,7 @@ object AppClient {
}
fun availableClientTypes(preferredClient: ClientType): Array<ClientType> {
val availableClientTypes = if (PatchStatus.SpoofStreamingDataMusic())
ClientType.CLIENT_ORDER_TO_USE_YOUTUBE_MUSIC
else
ClientType.CLIENT_ORDER_TO_USE_YOUTUBE
val availableClientTypes = ClientType.CLIENT_ORDER_TO_USE_YOUTUBE
if (ArrayUtils.contains(availableClientTypes, preferredClient)) {
val clientToUse: Array<ClientType?> = arrayOfNulls(availableClientTypes.size)
@ -321,6 +291,14 @@ object AppClient {
*/
val friendlyName: String
) {
ANDROID(
id = 3,
userAgent = USER_AGENT_ANDROID,
androidSdkVersion = Build.VERSION.SDK,
clientVersion = CLIENT_VERSION_ANDROID,
clientName = "ANDROID",
friendlyName = "Android"
),
ANDROID_VR(
id = 28,
deviceMake = DEVICE_MAKE_ANDROID_VR,
@ -400,34 +378,16 @@ object AppClient {
"iOS Force AVC"
else
"iOS"
),
ANDROID_MUSIC(
id = 21,
deviceMake = DEVICE_MAKE_ANDROID_MUSIC,
deviceModel = DEVICE_MODEL_ANDROID_MUSIC,
osVersion = OS_VERSION_ANDROID_MUSIC,
userAgent = USER_AGENT_ANDROID_MUSIC,
androidSdkVersion = ANDROID_SDK_VERSION_ANDROID_MUSIC,
clientVersion = CLIENT_VERSION_ANDROID_MUSIC,
gmscoreVersionCode = GMS_CORE_VERSION_CODE_ANDROID_MUSIC,
requireAuth = true,
clientName = "ANDROID_MUSIC",
friendlyName = "Android Music"
);
companion object {
val CLIENT_ORDER_TO_USE_YOUTUBE: Array<ClientType> = arrayOf(
ANDROID_VR,
ANDROID_UNPLUGGED,
IOS_UNPLUGGED,
ANDROID_UNPLUGGED,
ANDROID_CREATOR,
IOS,
ANDROID_VR_NO_AUTH,
)
internal val CLIENT_ORDER_TO_USE_YOUTUBE_MUSIC: Array<ClientType> = arrayOf(
ANDROID_VR,
ANDROID_MUSIC,
ANDROID_VR_NO_AUTH,
)
}
}

View File

@ -4,7 +4,7 @@ package app.revanced.extension.shared.patches.client
* Used to fetch video information.
*/
@Suppress("unused")
object WebClient {
object YouTubeWebClient {
/**
* This user agent does not require a PoToken in [ClientType.MWEB]
* https://github.com/yt-dlp/yt-dlp/blob/0b6b7742c2e7f2a1fcb0b54ef3dd484bab404b3f/yt_dlp/extractor/youtube.py#L259

View File

@ -7,6 +7,7 @@ import android.text.style.CharacterStyle;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.LineHeightSpan;
import android.text.style.TypefaceSpan;
import androidx.annotation.NonNull;
@ -93,6 +94,8 @@ public final class InclusiveSpanPatch {
return SpanType.TYPEFACE;
} else if (span instanceof ImageSpan) {
return SpanType.IMAGE;
} else if (span instanceof LineHeightSpan) {
return SpanType.LINE_HEIGHT;
} else if (span instanceof CharacterStyle) { // Replaced by patch.
return SpanType.CUSTOM_CHARACTER_STYLE;
} else {

View File

@ -3,12 +3,13 @@ package app.revanced.extension.shared.patches.spans;
import androidx.annotation.NonNull;
public enum SpanType {
CLICKABLE("ClickableSpan"),
FOREGROUND_COLOR("ForegroundColorSpan"),
ABSOLUTE_SIZE("AbsoluteSizeSpan"),
TYPEFACE("TypefaceSpan"),
IMAGE("ImageSpan"),
CLICKABLE("ClickableSpan"),
CUSTOM_CHARACTER_STYLE("CustomCharacterStyle"),
FOREGROUND_COLOR("ForegroundColorSpan"),
IMAGE("ImageSpan"),
LINE_HEIGHT("LineHeightSpan"),
TYPEFACE("TypefaceSpan"),
UNKNOWN("Unknown");
@NonNull

View File

@ -0,0 +1,109 @@
package app.revanced.extension.shared.patches.spoof;
import android.net.Uri;
import app.revanced.extension.shared.patches.PatchStatus;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.PackageUtils;
@SuppressWarnings("unused")
public class BlockRequestPatch {
/**
* Used in YouTube.
*/
public static final boolean SPOOF_STREAMING_DATA =
BaseSettings.SPOOF_STREAMING_DATA.get() && PatchStatus.SpoofStreamingData();
/**
* Used in YouTube Music.
*/
public static final boolean SPOOF_CLIENT =
BaseSettings.SPOOF_CLIENT.get() && PatchStatus.SpoofClient();
/**
* In order to load the action bar normally,
* Some versions must block the initplayback request.
*/
private static final boolean IS_7_17_OR_GREATER =
PackageUtils.getAppVersionName().compareTo("7.17.00") >= 0;
private static final boolean IS_YOUTUBE_BUTTON =
BaseSettings.SPOOF_CLIENT_TYPE.get().isYouTubeButton();
private static final boolean BLOCK_REQUEST;
static {
if (SPOOF_STREAMING_DATA) {
BLOCK_REQUEST = true;
} else {
if (!SPOOF_CLIENT) {
BLOCK_REQUEST = false;
} else {
if (!IS_7_17_OR_GREATER && !IS_YOUTUBE_BUTTON) {
// If the current version is lower than 7.16 and the client action button type is not YouTubeButton,
// the initplayback request must be blocked.
BLOCK_REQUEST = true;
} else {
// If the current version is higher than 7.17,
// the initplayback request must always be blocked.
BLOCK_REQUEST = IS_7_17_OR_GREATER;
}
}
}
}
/**
* Any unreachable ip address. Used to intentionally fail requests.
*/
private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0";
private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING);
/**
* Injection point.
* Blocks /get_watch requests by returning an unreachable URI.
*
* @param playerRequestUri The URI of the player request.
* @return An unreachable URI if the request is a /get_watch request, otherwise the original URI.
*/
public static Uri blockGetWatchRequest(Uri playerRequestUri) {
if (BLOCK_REQUEST) {
try {
String path = playerRequestUri.getPath();
if (path != null && path.contains("get_watch")) {
Logger.printDebug(() -> "Blocking 'get_watch' by returning unreachable uri");
return UNREACHABLE_HOST_URI;
}
} catch (Exception ex) {
Logger.printException(() -> "blockGetWatchRequest failure", ex);
}
}
return playerRequestUri;
}
/**
* Injection point.
* <p>
* Blocks /initplayback requests.
*/
public static Uri blockInitPlaybackRequest(Uri initPlaybackRequestUri) {
if (BLOCK_REQUEST) {
try {
String path = initPlaybackRequestUri.getPath();
if (path != null && path.contains("initplayback")) {
Logger.printDebug(() -> "Blocking 'initplayback' by clearing query");
return initPlaybackRequestUri.buildUpon().clearQuery().build();
}
} catch (Exception ex) {
Logger.printException(() -> "blockInitPlaybackRequest failure", ex);
}
}
return initPlaybackRequestUri;
}
}

View File

@ -0,0 +1,146 @@
package app.revanced.extension.shared.patches.spoof;
import app.revanced.extension.shared.patches.client.MusicAppClient.ClientType;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class SpoofClientPatch extends BlockRequestPatch {
private static final ClientType CLIENT_TYPE = Settings.SPOOF_CLIENT_TYPE.get();
/**
* Injection point.
*/
public static int getClientId(int original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.id;
}
return original;
}
/**
* Injection point.
*/
public static String getClientVersion(String original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.clientVersion;
}
return original;
}
/**
* Injection point.
*/
public static String getDeviceBrand(String original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.deviceBrand;
}
return original;
}
/**
* Injection point.
*/
public static String getDeviceMake(String original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.deviceMake;
}
return original;
}
/**
* Injection point.
*/
public static String getDeviceModel(String original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.deviceModel;
}
return original;
}
/**
* Injection point.
*/
public static String getOsName(String original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.osName;
}
return original;
}
/**
* Injection point.
*/
public static String getOsVersion(String original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.osVersion;
}
return original;
}
/**
* Injection point.
*/
public static String getUserAgent(String original) {
if (SPOOF_CLIENT) {
return CLIENT_TYPE.userAgent;
}
return original;
}
/**
* Injection point.
*/
public static boolean isClientSpoofingEnabled() {
return SPOOF_CLIENT;
}
/**
* Injection point.
* <p>
* When spoofing the client to iOS, the playback speed menu is missing from the player response.
* This fix is required because playback speed is not available in YouTube Music Podcasts.
* <p>
* Return true to force create the playback speed menu.
*/
public static boolean forceCreatePlaybackSpeedMenu(boolean original) {
if (SPOOF_CLIENT) {
return true;
}
return original;
}
/**
* Injection point.
* <p>
* When spoofing the client to Android, the playback speed menu is missing from the player response.
* This fix is required because playback speed is not available in YouTube Music Podcasts.
* <p>
* Return false to force create the playback speed menu.
*/
public static boolean forceCreatePlaybackSpeedMenuInverse(boolean original) {
if (SPOOF_CLIENT) {
return false;
}
return original;
}
/**
* Injection point.
* <p>
* Return false to force disable playback feature flag.
*/
public static boolean forceDisablePlaybackFeatureFlag(boolean original) {
if (SPOOF_CLIENT) {
return false;
}
return original;
}
}

View File

@ -1,11 +1,8 @@
package app.revanced.extension.shared.patches.spoof;
import static app.revanced.extension.shared.patches.PatchStatus.SpoofStreamingDataMusic;
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
@ -13,7 +10,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import app.revanced.extension.shared.patches.client.AppClient.ClientType;
import app.revanced.extension.shared.patches.client.YouTubeAppClient.ClientType;
import app.revanced.extension.shared.patches.spoof.requests.StreamingDataRequest;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.Setting;
@ -21,10 +18,7 @@ import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.Utils;
@SuppressWarnings("unused")
public class SpoofStreamingDataPatch {
private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_STREAMING_DATA.get();
private static final boolean SPOOF_STREAMING_DATA_YOUTUBE = SPOOF_STREAMING_DATA && !SpoofStreamingDataMusic();
private static final boolean SPOOF_STREAMING_DATA_MUSIC = SPOOF_STREAMING_DATA && SpoofStreamingDataMusic();
public class SpoofStreamingDataPatch extends BlockRequestPatch {
private static final String PO_TOKEN =
BaseSettings.SPOOF_STREAMING_DATA_PO_TOKEN.get();
private static final String VISITOR_DATA =
@ -50,58 +44,6 @@ public class SpoofStreamingDataPatch {
}
});
/**
* Injection point.
* Blocks /get_watch requests by returning an unreachable URI.
*
* @param playerRequestUri The URI of the player request.
* @return An unreachable URI if the request is a /get_watch request, otherwise the original URI.
*/
public static Uri blockGetWatchRequest(Uri playerRequestUri) {
if (SPOOF_STREAMING_DATA) {
// An exception may be thrown when the /get_watch request is blocked when connected to Wi-Fi in YouTube Music.
if (SPOOF_STREAMING_DATA_YOUTUBE || Utils.getNetworkType() == Utils.NetworkType.MOBILE) {
try {
String path = playerRequestUri.getPath();
if (path != null && path.contains("get_watch")) {
Logger.printDebug(() -> "Blocking 'get_watch' by returning unreachable uri");
return UNREACHABLE_HOST_URI;
}
} catch (Exception ex) {
Logger.printException(() -> "blockGetWatchRequest failure", ex);
}
}
}
return playerRequestUri;
}
/**
* Injection point.
* <p>
* Blocks /initplayback requests.
*/
public static String blockInitPlaybackRequest(String originalUrlString) {
if (SPOOF_STREAMING_DATA) {
try {
var originalUri = Uri.parse(originalUrlString);
String path = originalUri.getPath();
if (path != null && path.contains("initplayback")) {
Logger.printDebug(() -> "Blocking 'initplayback' by clearing query");
return originalUri.buildUpon().clearQuery().build().toString();
}
} catch (Exception ex) {
Logger.printException(() -> "blockInitPlaybackRequest failure", ex);
}
}
return originalUrlString;
}
/**
* Injection point.
*/
@ -120,100 +62,20 @@ public class SpoofStreamingDataPatch {
return false;
}
private static volatile String auth = "";
private static volatile Map<String, String> requestHeader;
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String[] REQUEST_HEADER_KEYS = {
AUTHORIZATION_HEADER,
"X-GOOG-API-FORMAT-VERSION",
"X-Goog-Visitor-Id"
};
/**
* If the /get_watch request is not blocked,
* fetchRequest will not be invoked at the point where the video starts.
* <p>
* An additional method is used to invoke fetchRequest in YouTube Music:
* 1. Save the requestHeader in a field.
* 2. Invoke fetchRequest with the videoId used in PlaybackStartDescriptor.
* <p>
*
* @param requestHeaders Save the request Headers used for login to a field.
* Only used in YouTube Music where login is required.
*/
private static void setRequestHeaders(Map<String, String> requestHeaders) {
if (SPOOF_STREAMING_DATA_MUSIC) {
try {
// Save requestHeaders whenever an account is switched.
String authorization = requestHeaders.get(AUTHORIZATION_HEADER);
if (authorization == null || auth.equals(authorization)) {
return;
}
for (String key : REQUEST_HEADER_KEYS) {
if (requestHeaders.get(key) == null) {
return;
}
}
auth = authorization;
requestHeader = requestHeaders;
} catch (Exception ex) {
Logger.printException(() -> "setRequestHeaders failure", ex);
}
}
}
/**
* Injection point.
*/
public static void fetchStreams(@NonNull String videoId) {
if (SPOOF_STREAMING_DATA_MUSIC) {
try {
if (requestHeader != null) {
StreamingDataRequest.fetchRequest(videoId, requestHeader, VISITOR_DATA, PO_TOKEN);
} else {
Logger.printDebug(() -> "Ignoring request with no header.");
}
} catch (Exception ex) {
Logger.printException(() -> "fetchStreams failure", ex);
}
}
}
/**
* Injection point.
*/
public static void fetchStreams(String url, Map<String, String> requestHeaders) {
setRequestHeaders(requestHeaders);
if (SPOOF_STREAMING_DATA) {
try {
Uri uri = Uri.parse(url);
String path = uri.getPath();
if (path == null || !path.contains("player")) {
return;
}
// 'get_drm_license' has no video id and appears to happen when waiting for a paid video to start.
// 'heartbeat' has no video id and appears to be only after playback has started.
// 'refresh' has no video id and appears to happen when waiting for a livestream to start.
// 'ad_break' has no video id.
if (path.contains("get_drm_license") || path.contains("heartbeat") || path.contains("refresh") || path.contains("ad_break")) {
Logger.printDebug(() -> "Ignoring path: " + path);
return;
}
String id = uri.getQueryParameter("id");
if (id == null) {
Logger.printException(() -> "Ignoring request with no id: " + url);
return;
}
StreamingDataRequest.fetchRequest(id, requestHeaders, VISITOR_DATA, PO_TOKEN);
} catch (Exception ex) {
Logger.printException(() -> "fetchStreams failure", ex);
String id = Utils.getVideoIdFromRequest(url);
if (id == null) {
Logger.printException(() -> "Ignoring request with no id: " + url);
return;
} else if (id.isEmpty()) {
return;
}
StreamingDataRequest.fetchRequest(id, requestHeaders, VISITOR_DATA, PO_TOKEN);
}
}
@ -262,7 +124,7 @@ public class SpoofStreamingDataPatch {
* Called after {@link #getStreamingData(String)}.
*/
public static void setApproxDurationMs(String videoId, long approxDurationMs) {
if (SPOOF_STREAMING_DATA_YOUTUBE && approxDurationMs != Long.MAX_VALUE) {
if (SPOOF_STREAMING_DATA && approxDurationMs != Long.MAX_VALUE) {
approxDurationMsMap.put(videoId, approxDurationMs);
Logger.printDebug(() -> "New approxDurationMs loaded, video id: " + videoId + ", video length: " + approxDurationMs);
}
@ -284,7 +146,7 @@ public class SpoofStreamingDataPatch {
* Called after {@link #getStreamingData(String)}.
*/
public static long getApproxDurationMs(String videoId) {
if (SPOOF_STREAMING_DATA_YOUTUBE && videoId != null) {
if (SPOOF_STREAMING_DATA && videoId != null) {
final Long approxDurationMs = approxDurationMsMap.get(videoId);
if (approxDurationMs != null) {
Logger.printDebug(() -> "Replacing video length: " + approxDurationMs + " for videoId: " + videoId);
@ -338,7 +200,7 @@ public class SpoofStreamingDataPatch {
@Override
public boolean isAvailable() {
return BaseSettings.SPOOF_STREAMING_DATA.get() &&
BaseSettings.SPOOF_STREAMING_DATA_TYPE.get() == ClientType.ANDROID_VR_NO_AUTH;
BaseSettings.SPOOF_STREAMING_DATA_CLIENT.get() == ClientType.ANDROID_VR_NO_AUTH;
}
}
}

View File

@ -1,7 +1,7 @@
package app.revanced.extension.shared.patches.spoof.requests
import app.revanced.extension.shared.patches.client.AppClient
import app.revanced.extension.shared.patches.client.WebClient
import app.revanced.extension.shared.patches.client.YouTubeAppClient
import app.revanced.extension.shared.patches.client.YouTubeWebClient
import app.revanced.extension.shared.requests.Requester
import app.revanced.extension.shared.requests.Route
import app.revanced.extension.shared.requests.Route.CompiledRoute
@ -44,12 +44,25 @@ object PlayerRoutes {
"&alt=proto"
).compile()
@JvmField
val GET_VIDEO_ACTION_BUTTON: CompiledRoute = Route(
Route.Method.POST,
"next" +
"?prettyPrint=false" +
"&fields=contents.singleColumnWatchNextResults." +
"results.results.contents.slimVideoMetadataSectionRenderer." +
"contents.elementRenderer.newElement.type.componentType." +
"model.videoActionBarModel.buttons.buttonViewModel"
).compile()
@JvmField
val GET_VIDEO_DETAILS: CompiledRoute = Route(
Route.Method.POST,
"player" +
"?prettyPrint=false" +
"&fields=videoDetails.channelId"
"&fields=videoDetails.channelId," +
"videoDetails.isLiveContent," +
"videoDetails.isUpcoming"
).compile()
private const val YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/"
@ -69,7 +82,7 @@ object PlayerRoutes {
@JvmStatic
fun createApplicationRequestBody(
clientType: AppClient.ClientType,
clientType: YouTubeAppClient.ClientType,
videoId: String,
playlistId: String? = null,
botGuardPoToken: String = "",
@ -130,7 +143,7 @@ object PlayerRoutes {
@JvmStatic
fun createWebInnertubeBody(
clientType: WebClient.ClientType,
clientType: YouTubeWebClient.ClientType,
videoId: String
): ByteArray {
val innerTubeBody = JSONObject()
@ -161,7 +174,7 @@ object PlayerRoutes {
@JvmStatic
fun getPlayerResponseConnectionFromRoute(
route: CompiledRoute,
clientType: AppClient.ClientType
clientType: YouTubeAppClient.ClientType
): HttpURLConnection {
return getPlayerResponseConnectionFromRoute(
route,
@ -174,7 +187,7 @@ object PlayerRoutes {
@JvmStatic
fun getPlayerResponseConnectionFromRoute(
route: CompiledRoute,
clientType: WebClient.ClientType
clientType: YouTubeWebClient.ClientType
): HttpURLConnection {
return getPlayerResponseConnectionFromRoute(
route,

View File

@ -1,8 +1,7 @@
package app.revanced.extension.shared.patches.spoof.requests
import androidx.annotation.GuardedBy
import app.revanced.extension.shared.patches.client.AppClient
import app.revanced.extension.shared.patches.client.AppClient.availableClientTypes
import app.revanced.extension.shared.patches.client.YouTubeAppClient
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.GET_STREAMING_DATA
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.createApplicationRequestBody
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes.getPlayerResponseConnectionFromRoute
@ -93,16 +92,16 @@ class StreamingDataRequest private constructor(
"X-GOOG-API-FORMAT-VERSION",
VISITOR_ID_HEADER
)
private val SPOOF_STREAMING_DATA_TYPE: AppClient.ClientType =
BaseSettings.SPOOF_STREAMING_DATA_TYPE.get()
private val SPOOF_STREAMING_DATA_CLIENT: YouTubeAppClient.ClientType =
BaseSettings.SPOOF_STREAMING_DATA_CLIENT.get()
private val CLIENT_ORDER_TO_USE: Array<AppClient.ClientType> =
availableClientTypes(SPOOF_STREAMING_DATA_TYPE)
private val CLIENT_ORDER_TO_USE: Array<YouTubeAppClient.ClientType> =
YouTubeAppClient.availableClientTypes(SPOOF_STREAMING_DATA_CLIENT)
private val DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH: Boolean =
SPOOF_STREAMING_DATA_TYPE == AppClient.ClientType.ANDROID_VR_NO_AUTH
SPOOF_STREAMING_DATA_CLIENT == YouTubeAppClient.ClientType.ANDROID_VR_NO_AUTH
private var lastSpoofedClientType: AppClient.ClientType? = null
private var lastSpoofedClientType: YouTubeAppClient.ClientType? = null
/**
@ -111,20 +110,13 @@ class StreamingDataRequest private constructor(
private const val HTTP_TIMEOUT_MILLISECONDS = 10 * 1000
/**
* Any arbitrarily large value, but must be at least twice [.HTTP_TIMEOUT_MILLISECONDS]
* Any arbitrarily large value, but must be at least twice [HTTP_TIMEOUT_MILLISECONDS]
*/
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000
@GuardedBy("itself")
val cache: MutableMap<String, StreamingDataRequest> = Collections.synchronizedMap(
object : LinkedHashMap<String, StreamingDataRequest>(100) {
/**
* Cache limit must be greater than the maximum number of videos open at once,
* which theoretically is more than 4 (3 Shorts + one regular minimized video).
* But instead use a much larger value, to handle if a video viewed a while ago
* is somehow still referenced. Each stream is a small array of Strings
* so memory usage is not a concern.
*/
private val CACHE_LIMIT = 50
override fun removeEldestEntry(eldest: Map.Entry<String, StreamingDataRequest>): Boolean {
@ -163,7 +155,7 @@ class StreamingDataRequest private constructor(
}
private fun send(
clientType: AppClient.ClientType,
clientType: YouTubeAppClient.ClientType,
videoId: String,
playerHeaders: Map<String, String>,
visitorId: String,
@ -279,7 +271,7 @@ class StreamingDataRequest private constructor(
} else {
BufferedInputStream(connection.inputStream).use { inputStream ->
ByteArrayOutputStream().use { stream ->
val buffer = ByteArray(4096)
val buffer = ByteArray(2048)
var bytesRead: Int
while ((inputStream.read(buffer)
.also { bytesRead = it }) >= 0

View File

@ -50,14 +50,18 @@ public class ChannelRequest {
});
public static void fetchRequestIfNeeded(@NonNull String handle, @NonNull String apiKey, Boolean userNameFirst) {
if (!cache.containsKey(handle)) {
cache.put(handle, new ChannelRequest(handle, apiKey, userNameFirst));
synchronized (cache) {
if (cache.get(handle) == null) {
cache.put(handle, new ChannelRequest(handle, apiKey, userNameFirst));
}
}
}
@Nullable
public static ChannelRequest getRequestForHandle(@NonNull String handle) {
return cache.get(handle);
synchronized (cache) {
return cache.get(handle);
}
}
private static void handleConnectionError(String toastMessage, @Nullable Exception ex) {
@ -108,7 +112,8 @@ public class ChannelRequest {
final String userName = channelJsonObject
.getJSONArray("items")
.getJSONObject(0)
.getJSONObject("snippet")
.getJSONObject("brandingSettings")
.getJSONObject("channel")
.getString("title");
return authorBadgeBuilder(handle, userName, userNameFirst);
} catch (JSONException e) {

View File

@ -11,7 +11,7 @@ import app.revanced.extension.shared.requests.Route;
public class ChannelRoutes {
public static final String YOUTUBEI_V3_GAPIS_URL = "https://www.googleapis.com/youtube/v3/";
public static final Route GET_CHANNEL_DETAILS = new Route(GET, "channels?part=snippet&forHandle={handle}&key={api_key}");
public static final Route GET_CHANNEL_DETAILS = new Route(GET, "channels?part=brandingSettings&maxResults=1&prettyPrint=false&forHandle={handle}&key={api_key}");
public ChannelRoutes() {
}

View File

@ -2,10 +2,11 @@ package app.revanced.extension.shared.settings;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.patches.PatchStatus.HideFullscreenAdsDefaultBoolean;
import app.revanced.extension.shared.patches.ReturnYouTubeUsernamePatch.DisplayFormat;
import app.revanced.extension.shared.patches.client.AppClient.ClientType;
import app.revanced.extension.shared.patches.WatchHistoryPatch.WatchHistoryType;
import app.revanced.extension.shared.patches.client.MusicAppClient;
import app.revanced.extension.shared.patches.client.YouTubeAppClient;
import app.revanced.extension.shared.patches.spoof.SpoofStreamingDataPatch.AudioStreamLanguageOverrideAvailability;
/**
@ -26,30 +27,43 @@ public class BaseSettings {
public static final EnumSetting<AppLanguage> REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true);
/**
* These settings are used by YouTube and YouTube Music.
* These settings are used by YouTube Music.
* Some patches are in a shared path, so they are declared here.
*/
public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", HideFullscreenAdsDefaultBoolean(), true);
public static final BooleanSetting HIDE_PROMOTION_ALERT_BANNER = new BooleanSetting("revanced_hide_promotion_alert_banner", TRUE);
public static final BooleanSetting DISABLE_AUTO_CAPTIONS = new BooleanSetting("revanced_disable_auto_captions", FALSE, true);
public static final BooleanSetting DISABLE_QUIC_PROTOCOL = new BooleanSetting("revanced_disable_quic_protocol", FALSE, true);
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
public static final BooleanSetting RETURN_YOUTUBE_USERNAME_ENABLED = new BooleanSetting("revanced_return_youtube_username_enabled", FALSE, true);
public static final EnumSetting<DisplayFormat> RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT = new EnumSetting<>("revanced_return_youtube_username_display_format", DisplayFormat.USERNAME_ONLY, true);
public static final StringSetting RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY = new StringSetting("revanced_return_youtube_username_youtube_data_api_v3_developer_key", "", true, false);
public static final BooleanSetting SPOOF_CLIENT = new BooleanSetting("revanced_spoof_client", TRUE, true);
public static final EnumSetting<MusicAppClient.ClientType> SPOOF_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_client_type", MusicAppClient.ClientType.IOS_MUSIC_6_21, true);
/**
* These settings are used by YouTube.
* Some patches are in a shared path, so they are declared here.
*/
public static final BooleanSetting SPOOF_STREAMING_DATA = new BooleanSetting("revanced_spoof_streaming_data", TRUE, true, "revanced_spoof_streaming_data_user_dialog_message");
public static final EnumSetting<AppLanguage> SPOOF_STREAMING_DATA_LANGUAGE = new EnumSetting<>("revanced_spoof_streaming_data_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
public static final BooleanSetting SPOOF_STREAMING_DATA_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_streaming_data_ios_force_avc", FALSE, true,
"revanced_spoof_streaming_data_ios_force_avc_user_dialog_message");
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE);
// Client type must be last spoof setting due to cyclic references.
public static final EnumSetting<ClientType> SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", ClientType.ANDROID_VR, true);
public static final EnumSetting<YouTubeAppClient.ClientType> SPOOF_STREAMING_DATA_CLIENT = new EnumSetting<>("revanced_spoof_streaming_data_client", YouTubeAppClient.ClientType.IOS_UNPLUGGED, true);
public static final StringSetting SPOOF_STREAMING_DATA_PO_TOKEN = new StringSetting("revanced_spoof_streaming_data_po_token", "", true);
public static final StringSetting SPOOF_STREAMING_DATA_VISITOR_DATA = new StringSetting("revanced_spoof_streaming_data_visitor_data", "", true);
/**
* These settings are used by YouTube and YouTube Music.
*/
public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", TRUE, true);
public static final BooleanSetting HIDE_PROMOTION_ALERT_BANNER = new BooleanSetting("revanced_hide_promotion_alert_banner", TRUE);
public static final BooleanSetting DISABLE_AUTO_CAPTIONS = new BooleanSetting("revanced_disable_auto_captions", FALSE, true);
public static final BooleanSetting DISABLE_QUIC_PROTOCOL = new BooleanSetting("revanced_disable_quic_protocol", FALSE, true);
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
public static final EnumSetting<WatchHistoryType> WATCH_HISTORY_TYPE = new EnumSetting<>("revanced_watch_history_type", WatchHistoryType.REPLACE);
public static final BooleanSetting RETURN_YOUTUBE_USERNAME_ENABLED = new BooleanSetting("revanced_return_youtube_username_enabled", FALSE, true);
public static final EnumSetting<DisplayFormat> RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT = new EnumSetting<>("revanced_return_youtube_username_display_format", DisplayFormat.USERNAME_ONLY, true);
public static final StringSetting RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY = new StringSetting("revanced_return_youtube_username_youtube_data_api_v3_developer_key", "", true, false);
/**
* @noinspection DeprecatedIsStillUsed
*/

View File

@ -0,0 +1,35 @@
package app.revanced.extension.shared.settings.preference;
import static android.text.Html.FROM_HTML_MODE_COMPACT;
import android.content.Context;
import android.preference.SwitchPreference;
import android.text.Html;
import android.util.AttributeSet;
/**
* Allows using basic html for the summary text.
*/
@SuppressWarnings({"unused", "deprecation"})
public class HtmlSwitchPreference extends SwitchPreference {
{
setSummaryOn(Html.fromHtml(getSummaryOn().toString(), FROM_HTML_MODE_COMPACT));
setSummaryOff(Html.fromHtml(getSummaryOff().toString(), FROM_HTML_MODE_COMPACT));
}
public HtmlSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public HtmlSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public HtmlSwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public HtmlSwitchPreference(Context context) {
super(context);
}
}

View File

@ -11,6 +11,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@ -432,6 +433,34 @@ public class Utils {
setEditTextDialogTheme(builder, false);
}
/**
* No video id in these parameters.
*/
private static final String[] PATH_NO_VIDEO_ID = {
"ad_break", // This request fetches a list of times when ads can be displayed.
"get_drm_license", // Waiting for a paid video to start.
"heartbeat", // This request determines whether to pause playback when the user is AFK.
"refresh", // Waiting for a livestream to start.
};
@Nullable
public static String getVideoIdFromRequest(String url) {
try {
Uri uri = Uri.parse(url);
String path = uri.getPath();
if (path != null && path.contains("player")) {
if (!containsAny(path, PATH_NO_VIDEO_ID)) {
return uri.getQueryParameter("id");
} else {
Logger.printDebug(() -> "Ignoring path: " + path);
}
}
} catch (Exception ex) {
Logger.printException(() -> "getVideoIdFromRequest failure", ex);
}
return "";
}
/**
* If {@link Fragment} uses [Android library] rather than [AndroidX library],
* the Dialog theme corresponding to [Android library] should be used.

View File

@ -4,13 +4,25 @@ import static app.revanced.extension.shared.utils.Utils.hideViewBy0dpUnderCondit
import android.view.View;
import java.util.List;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class AdsPatch {
private static final boolean hideGeneralAdsEnabled = Settings.HIDE_GENERAL_ADS.get();
private static final boolean hideGetPremiumAdsEnabled = Settings.HIDE_GET_PREMIUM.get();
private static final boolean hideVideoAdsEnabled = Settings.HIDE_VIDEO_ADS.get();
private static final boolean HIDE_END_SCREEN_STORE_BANNER =
Settings.HIDE_END_SCREEN_STORE_BANNER.get();
private static final boolean HIDE_GENERAL_ADS =
Settings.HIDE_GENERAL_ADS.get();
private static final boolean HIDE_GET_PREMIUM =
Settings.HIDE_GET_PREMIUM.get();
private static final boolean HIDE_VIDEO_ADS =
Settings.HIDE_VIDEO_ADS.get();
// https://encrypted-tbn0.gstatic.com/shopping?q=tbn
private static final String STORE_BANNER_DOMAIN =
"gstatic.com/shopping";
/**
* Injection point.
@ -19,18 +31,37 @@ public class AdsPatch {
* @param view The view, which shows ads.
*/
public static void hideAdAttributionView(View view) {
hideViewBy0dpUnderCondition(hideGeneralAdsEnabled, view);
hideViewBy0dpUnderCondition(HIDE_GENERAL_ADS, view);
}
/**
* Injection point.
*
* @param elementsList List of components of the end screen container.
* @param protobufList Component (ProtobufList).
*/
public static void hideEndScreenStoreBanner(List<Object> elementsList, Object protobufList) {
if (HIDE_END_SCREEN_STORE_BANNER &&
protobufList.toString().contains(STORE_BANNER_DOMAIN)) {
Logger.printDebug(() -> "Hiding store banner");
return;
}
elementsList.add(protobufList);
}
/**
* Injection point.
*/
public static boolean hideGetPremium() {
return hideGetPremiumAdsEnabled;
return HIDE_GET_PREMIUM;
}
/**
* Injection point.
*/
public static boolean hideVideoAds() {
return !hideVideoAdsEnabled;
return !HIDE_VIDEO_ADS;
}
/**
@ -40,7 +71,7 @@ public class AdsPatch {
* It is presumed to have been deprecated, and if it is confirmed that it is no longer used, remove it.
*/
public static boolean hideVideoAds(boolean original) {
return !hideVideoAdsEnabled && original;
return !HIDE_VIDEO_ADS && original;
}
}

View File

@ -6,6 +6,7 @@ import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroupList;
import app.revanced.extension.shared.patches.components.Filter;
import app.revanced.extension.shared.patches.components.StringFilterGroup;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
@ -18,6 +19,8 @@ public final class ActionButtonsFilter extends Filter {
private final StringFilterGroup likeSubscribeGlow;
private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
private static final boolean HIDE_ACTION_BUTTON_INDEX = Settings.HIDE_ACTION_BUTTON_INDEX.get();
public ActionButtonsFilter() {
actionBarRule = new StringFilterGroup(
null,
@ -95,6 +98,9 @@ public final class ActionButtonsFilter extends Filter {
@Override
public boolean isFiltered(String path, @Nullable String identifier, String allValue, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (HIDE_ACTION_BUTTON_INDEX) {
return false;
}
if (!path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX)) {
return false;
}

View File

@ -35,7 +35,7 @@ public final class AdsFilter extends Filter {
"alert_banner_promo.eml"
);
// Keywords checked in 2024:
// Keywords checked in 2025:
final StringFilterGroup generalAdsIdentifier = new StringFilterGroup(
Settings.HIDE_GENERAL_ADS,
// "brand_video_shelf.eml"
@ -43,35 +43,36 @@ public final class AdsFilter extends Filter {
"brand_video",
// "carousel_footered_layout.eml"
"carousel_footered_layout",
// "composite_concurrent_carousel_layout"
"composite_concurrent_carousel_layout",
"carousel_",
// "landscape_image_wide_button_layout.eml"
"landscape_image_wide_button_layout",
// "square_image_layout.eml"
"square_image_layout",
// "inline_injection_entrypoint_layout.eml"
"inline_injection_entrypoint_layout",
// "statement_banner.eml"
"statement_banner",
// "video_display_button_group_layout"
// "video_display_carousel_button_group_layout"
// "video_display_full_buttoned_layout"
// "video_display_full_layout"
// "video_display_full_layout.eml"
"video_display_full_layout",
"video_display_",
// "text_image_button_group_layout.eml"
// "video_display_button_group_layout.eml"
"_button_group_layout",
// "landscape_image_wide_button_layout.eml"
// "text_image_no_button_layout.eml"
"_button_layout",
// "banner_text_icon_buttoned_layout.eml"
// "video_display_compact_buttoned_layout.eml"
// "video_display_full_buttoned_layout.eml"
"_buttoned_layout",
// "compact_landscape_image_layout.eml"
// "full_width_portrait_image_layout.eml"
// "full_width_square_image_layout.eml"
// "square_image_layout.eml"
"_image_layout"
);

View File

@ -24,6 +24,7 @@ public final class CarouselShelfFilter extends Filter {
private static final String BROWSE_ID_NOTIFICATION = "FEactivity";
private static final String BROWSE_ID_NOTIFICATION_INBOX = "FEnotifications_inbox";
private static final String BROWSE_ID_PLAYLIST = "VLPL";
private static final String BROWSE_ID_PODCASTS = "FEpodcasts_destination";
private static final String BROWSE_ID_PREMIUM = "SPunlimited";
private static final String BROWSE_ID_SUBSCRIPTION = "FEsubscriptions";
@ -41,6 +42,7 @@ public final class CarouselShelfFilter extends Filter {
BROWSE_ID_MOVIE,
BROWSE_ID_NEWS,
BROWSE_ID_NOTIFICATION_INBOX,
BROWSE_ID_PODCASTS,
BROWSE_ID_PREMIUM
);

View File

@ -62,7 +62,9 @@ public final class FeedComponentsFilter extends Filter {
"images_post_root",
"images_post_slim",
"poll_post_root",
"text_post_root"
"post_shelf_slim",
"text_post_root",
"videos_post_root"
);
final StringFilterGroup expandableShelf = new StringFilterGroup(
@ -163,7 +165,7 @@ public final class FeedComponentsFilter extends Filter {
final StringFilterGroup playables = new StringFilterGroup(
Settings.HIDE_PLAYABLES,
"horizontal_gaming_shelf.eml",
"horizontal_gaming_shelf",
"mini_game_card.eml"
);
@ -172,6 +174,11 @@ public final class FeedComponentsFilter extends Filter {
"subscriptions_channel_bar"
);
final StringFilterGroup subscriptionsCategoryBar = new StringFilterGroup(
Settings.HIDE_CATEGORY_BAR_IN_FEED,
"subscriptions_chip_bar"
);
final StringFilterGroup ticketShelf = new StringFilterGroup(
Settings.HIDE_TICKET_SHELF,
"ticket_horizontal_shelf",
@ -192,6 +199,7 @@ public final class FeedComponentsFilter extends Filter {
notifyMe,
playables,
subscriptionsChannelBar,
subscriptionsCategoryBar,
ticketShelf
);

View File

@ -9,6 +9,8 @@ import java.util.regex.Pattern;
import app.revanced.extension.shared.patches.components.Filter;
import app.revanced.extension.shared.patches.components.StringFilterGroup;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.shared.utils.ResourceUtils;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.NavigationBar;
import app.revanced.extension.youtube.shared.RootView;
@ -70,10 +72,26 @@ public final class FeedVideoViewsFilter extends Filter {
return false;
}
private final String ARROW = " -> ";
private final String VIEWS = "views";
private final String[] parts = Settings.HIDE_VIDEO_VIEW_COUNTS_MULTIPLIER.get().split("\\n");
private Pattern[] viewCountPatterns = null;
private static final String ARROW = " -> ";
private static final String VIEWS = "views";
private static final StringSetting HIDE_VIDEO_VIEW_COUNTS_MULTIPLIER =
Settings.HIDE_VIDEO_VIEW_COUNTS_MULTIPLIER;
private static final String HIDE_VIDEO_VIEW_COUNTS_MULTIPLIER_DEFAULT_VALUE =
"revanced_hide_video_view_counts_multiplier_default_value";
private static String[] parts;
private static Pattern[] viewCountPatterns = null;
static {
final String multiplierString = HIDE_VIDEO_VIEW_COUNTS_MULTIPLIER.get();
if (multiplierString.equals(HIDE_VIDEO_VIEW_COUNTS_MULTIPLIER_DEFAULT_VALUE) &&
ResourceUtils.getContext() != null) {
String defaultValue = ResourceUtils.getString(HIDE_VIDEO_VIEW_COUNTS_MULTIPLIER_DEFAULT_VALUE);
if (!multiplierString.equals(defaultValue)) {
HIDE_VIDEO_VIEW_COUNTS_MULTIPLIER.save(defaultValue);
}
}
parts = HIDE_VIDEO_VIEW_COUNTS_MULTIPLIER.get().split("\\n");
}
/**
* Hide videos based on views count

View File

@ -8,11 +8,13 @@ import app.revanced.extension.shared.patches.components.StringFilterGroupList;
import app.revanced.extension.shared.utils.StringTrieSearch;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
import app.revanced.extension.youtube.shared.RootView;
@SuppressWarnings("unused")
public final class PlayerComponentsFilter extends Filter {
private final StringFilterGroupList channelBarGroupList = new StringFilterGroupList();
private final StringFilterGroup channelBar;
private final StringFilterGroup singleItemInformationPanel;
private final StringTrieSearch suggestedActionsException = new StringTrieSearch();
private final StringFilterGroup suggestedActions;
@ -53,7 +55,11 @@ public final class PlayerComponentsFilter extends Filter {
final StringFilterGroup infoPanel = new StringFilterGroup(
Settings.HIDE_INFO_PANEL,
"compact_banner",
"publisher_transparency_panel",
"publisher_transparency_panel"
);
singleItemInformationPanel = new StringFilterGroup(
Settings.HIDE_INFO_PANEL,
"single_item_information_panel"
);
@ -87,6 +93,7 @@ public final class PlayerComponentsFilter extends Filter {
infoPanel,
medicalPanel,
seekMessage,
singleItemInformationPanel,
suggestedActions,
timedReactions
);
@ -111,7 +118,16 @@ public final class PlayerComponentsFilter extends Filter {
@Override
public boolean isFiltered(String path, @Nullable String identifier, String allValue, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == suggestedActions) {
// This identifier is used not only in players but also in search results:
// https://github.com/ReVanced/revanced-patches/issues/3245
// Until 2024, medical information panels such as Covid 19 also used this identifier and were shown in the search results.
// From 2025, the medical information panel is no longer shown in the search results.
// Therefore, this identifier does not filter when the search bar is activated.
if (matchedGroup == singleItemInformationPanel) {
if (!RootView.isPlayerActive() && RootView.isSearchBarActive()) {
return false;
}
} else if (matchedGroup == suggestedActions) {
// suggested actions button on shorts and the suggested actions button on video players use the same path builder.
// Check PlayerType to make each setting work independently.
if (suggestedActionsException.matches(path) || PlayerType.getCurrent().isNoneOrHidden()) {

View File

@ -186,8 +186,15 @@ public class FeedPatch {
String menuTitleString = menuTitleCharSequence.toString();
for (String filter : blockList) {
if (menuTitleString.equals(filter) && !filter.isEmpty())
return null;
if (!filter.isEmpty()) {
if (Settings.HIDE_FEED_FLYOUT_MENU_FILTER_TYPE.get()) {
if (menuTitleString.contains(filter))
return null;
} else {
if (menuTitleString.equals(filter))
return null;
}
}
}
}

View File

@ -1,32 +1,18 @@
package app.revanced.extension.youtube.patches.feed;
import androidx.annotation.Nullable;
import java.util.concurrent.atomic.AtomicBoolean;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.BottomSheetState;
import app.revanced.extension.youtube.shared.EngagementPanel;
import app.revanced.extension.youtube.shared.RootView;
@SuppressWarnings("unused")
public final class RelatedVideoPatch {
private static final boolean HIDE_RELATED_VIDEOS = Settings.HIDE_RELATED_VIDEOS.get();
private static final int OFFSET = Settings.RELATED_VIDEOS_OFFSET.get();
// video title,channel bar, video action bar, comment
private static final int MAX_ITEM_COUNT = 4 + OFFSET;
private static final AtomicBoolean engagementPanelOpen = new AtomicBoolean(false);
public static void showEngagementPanel(@Nullable Object object) {
engagementPanelOpen.set(object != null);
}
public static void hideEngagementPanel() {
engagementPanelOpen.compareAndSet(true, false);
}
public static int overrideItemCounts(int itemCounts) {
if (!HIDE_RELATED_VIDEOS) {
return itemCounts;
@ -40,7 +26,7 @@ public final class RelatedVideoPatch {
if (BottomSheetState.getCurrent().isOpen()) {
return itemCounts;
}
if (engagementPanelOpen.get()) {
if (EngagementPanel.isOpen()) {
return itemCounts;
}
return MAX_ITEM_COUNT;

View File

@ -1,141 +0,0 @@
package app.revanced.extension.youtube.patches.general;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import android.content.Intent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class ChangeStartPagePatch {
public enum StartPage {
/**
* Unmodified type, and same as un-patched.
*/
ORIGINAL("", null),
/**
* Browse id.
*/
BROWSE("FEguide_builder", TRUE),
EXPLORE("FEexplore", TRUE),
HISTORY("FEhistory", TRUE),
LIBRARY("FElibrary", TRUE),
MOVIE("FEstorefront", TRUE),
NOTIFICATIONS("FEactivity", TRUE),
SUBSCRIPTIONS("FEsubscriptions", TRUE),
TRENDING("FEtrending", TRUE),
YOUR_CLIPS("FEclips", TRUE),
/**
* Channel id, this can be used as a browseId.
*/
COURSES("UCtFRv9O2AHqOZjjynzrv-xg", TRUE),
FASHION("UCrpQ4p1Ql_hG8rKXIKM1MOQ", TRUE),
GAMING("UCOpNcN46UbXVtpKMrmU4Abg", TRUE),
LIVE("UC4R8DWoMoI7CAwX8_LjQHig", TRUE),
MUSIC("UC-9-kyTW8ZkZNDHQJ6FgpwQ", TRUE),
NEWS("UCYfdidRxbB8Qhf0Nx7ioOYw", TRUE),
PODCASTS("UCNVkxRPqsBNejO6B9thG9Xw", TRUE),
SHOPPING("UCkYQyvc_i9hXEo4xic9Hh2g", TRUE),
SPORTS("UCEgdi0XIXXZ-qJOFPf4JSKw", TRUE),
/**
* Playlist id, this can be used as a browseId.
*/
LIKED_VIDEO("VLLL", TRUE),
WATCH_LATER("VLWL", TRUE),
/**
* Intent action.
*/
SEARCH("com.google.android.youtube.action.open.search", FALSE),
SHORTS("com.google.android.youtube.action.open.shorts", FALSE);
@Nullable
final Boolean isBrowseId;
@NonNull
final String id;
StartPage(@NonNull String id, @Nullable Boolean isBrowseId) {
this.id = id;
this.isBrowseId = isBrowseId;
}
private boolean isBrowseId() {
return BooleanUtils.isTrue(isBrowseId);
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean isIntentAction() {
return BooleanUtils.isFalse(isBrowseId);
}
}
/**
* Intent action when YouTube is cold started from the launcher.
* <p>
* If you don't check this, the hooking will also apply in the following cases:
* Case 1. The user clicked Shorts button on the YouTube shortcut.
* Case 2. The user clicked Shorts button on the YouTube widget.
* In this case, instead of opening Shorts, the start page specified by the user is opened.
*/
private static final String ACTION_MAIN = "android.intent.action.MAIN";
private static final StartPage START_PAGE = Settings.CHANGE_START_PAGE.get();
private static final boolean ALWAYS_CHANGE_START_PAGE = Settings.CHANGE_START_PAGE_TYPE.get();
/**
* There is an issue where the back button on the toolbar doesn't work properly.
* As a workaround for this issue, instead of overriding the browserId multiple times, just override it once.
*/
private static boolean appLaunched = false;
public static String overrideBrowseId(@NonNull String original) {
if (!START_PAGE.isBrowseId()) {
return original;
}
if (!ALWAYS_CHANGE_START_PAGE && appLaunched) {
Logger.printDebug(() -> "Ignore override browseId as the app already launched");
return original;
}
appLaunched = true;
final String browseId = START_PAGE.id;
Logger.printDebug(() -> "Changing browseId to " + browseId);
return browseId;
}
public static void overrideIntentAction(@NonNull Intent intent) {
if (!START_PAGE.isIntentAction()) {
return;
}
if (!StringUtils.equals(intent.getAction(), ACTION_MAIN)) {
Logger.printDebug(() -> "Ignore override intent action" +
" as the current activity is not the entry point of the application");
return;
}
final String intentAction = START_PAGE.id;
Logger.printDebug(() -> "Changing intent action to " + intentAction);
intent.setAction(intentAction);
}
public static final class ChangeStartPageTypeAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
return Settings.CHANGE_START_PAGE.get() != StartPage.ORIGINAL;
}
}
}

View File

@ -0,0 +1,150 @@
package app.revanced.extension.youtube.patches.general
import android.content.Intent
import android.net.Uri
import app.revanced.extension.shared.settings.Setting.Availability
import app.revanced.extension.shared.utils.Logger
import app.revanced.extension.youtube.settings.Settings
import org.apache.commons.lang3.StringUtils
import kotlin.Boolean
@Suppress("unused")
object ChangeStartPagePatch {
/**
* Intent action when YouTube is cold started from the launcher.
*
* If you don't check this, the hooking will also apply in the following cases:
* Case 1. The user clicked Shorts button on the YouTube shortcut.
* Case 2. The user clicked Shorts button on the YouTube widget.
* In this case, instead of opening Shorts, the start page specified by the user is opened.
*/
private const val ACTION_MAIN = "android.intent.action.MAIN"
private const val URL_ACTIVITY_CLASS_DESCRIPTOR =
"com.google.android.apps.youtube.app.application.Shell_UrlActivity"
private var START_PAGE = Settings.CHANGE_START_PAGE.get()
private val ALWAYS_CHANGE_START_PAGE = Settings.CHANGE_START_PAGE_TYPE.get()
/**
* There is an issue where the back button on the toolbar doesn't work properly.
* As a workaround for this issue, instead of overriding the browserId multiple times, just override it once.
*/
private var appLaunched = false
@JvmStatic
fun overrideBrowseId(original: String): String {
val browseId = START_PAGE.browseId
if (browseId == null) {
return original
}
if (!ALWAYS_CHANGE_START_PAGE && appLaunched) {
Logger.printDebug { "Ignore override browseId as the app already launched" }
return original
}
appLaunched = true
Logger.printDebug{ "Changing browseId to $browseId" }
return browseId
}
@JvmStatic
fun overrideIntent(intent: Intent) {
val action = START_PAGE.action
val url = START_PAGE.url
if (action == null && url == null) {
return
}
if (!StringUtils.equals(intent.action, ACTION_MAIN)) {
Logger.printDebug {
"Ignore override intent action" +
" as the current activity is not the entry point of the application"
}
return
}
if (!ALWAYS_CHANGE_START_PAGE && appLaunched) {
Logger.printDebug { "Ignore override intent as the app already launched" }
return
}
appLaunched = true
if (action != null) {
Logger.printDebug { "Changing intent action to $action" }
intent.setAction(action)
} else if (url != null) {
Logger.printDebug { "Changing url to $url" }
intent.setAction("android.intent.action.VIEW")
intent.setData(Uri.parse(url))
intent.putExtra("alias", URL_ACTIVITY_CLASS_DESCRIPTOR)
} else {
START_PAGE = Settings.CHANGE_START_PAGE.defaultValue
Settings.CHANGE_START_PAGE.resetToDefault()
Logger.printException { "Unknown start page: $START_PAGE" } // Should never happen
}
}
enum class StartPage(
val action: String? = null,
val browseId: String? = null,
val url: String? = null,
) {
/**
* Unmodified type, and same as un-patched.
*/
ORIGINAL,
/**
* Browse id.
*/
ALL_SUBSCRIPTIONS(browseId = "FEchannels"),
BROWSE(browseId = "FEguide_builder"),
EXPLORE(browseId = "FEexplore"),
HISTORY(browseId = "FEhistory"),
LIBRARY(browseId = "FElibrary"),
MOVIE(browseId = "FEstorefront"),
NOTIFICATIONS(browseId = "FEactivity"),
PLAYLISTS(browseId = "FEplaylist_aggregation"),
SUBSCRIPTIONS(browseId = "FEsubscriptions"),
TRENDING(browseId = "FEtrending"),
YOUR_CLIPS(browseId = "FEclips"),
/**
* Channel id, this can be used as a browseId.
*/
COURSES(browseId = "UCtFRv9O2AHqOZjjynzrv-xg"),
FASHION(browseId = "UCrpQ4p1Ql_hG8rKXIKM1MOQ"),
GAMING(browseId = "UCOpNcN46UbXVtpKMrmU4Abg"),
LIVE(browseId = "UC4R8DWoMoI7CAwX8_LjQHig"),
MUSIC(browseId = "UC-9-kyTW8ZkZNDHQJ6FgpwQ"),
NEWS(browseId = "UCYfdidRxbB8Qhf0Nx7ioOYw"),
SHOPPING(browseId = "UCkYQyvc_i9hXEo4xic9Hh2g"),
SPORTS(browseId = "UCEgdi0XIXXZ-qJOFPf4JSKw"),
VIRTUAL_REALITY(browseId = "UCzuqhhs6NWbgTzMuM09WKDQ"),
/**
* Playlist id, this can be used as a browseId.
*/
LIKED_VIDEO(browseId = "VLLL"),
WATCH_LATER(browseId = "VLWL"),
/**
* Intent action.
*/
SEARCH(action = "com.google.android.youtube.action.open.search"),
SHORTS(action = "com.google.android.youtube.action.open.shorts"),
/**
* URL.
*
* URL opens after the home feed is opened.
* Use this only if browseId cannot be used.
*/
PODCASTS(url = "www.youtube.com/podcasts");
}
class ChangeStartPageTypeAvailability : Availability {
override fun isAvailable(): Boolean {
return Settings.CHANGE_START_PAGE.get() != StartPage.ORIGINAL
}
}
}

View File

@ -171,15 +171,25 @@ public class GeneralPatch {
private static void hideAccountMenu(ViewGroup viewGroup, String menuTitleString) {
for (String filter : accountMenuBlockList) {
if (!filter.isEmpty() && menuTitleString.equals(filter)) {
if (viewGroup.getLayoutParams() instanceof MarginLayoutParams)
hideViewGroupByMarginLayoutParams(viewGroup);
else
viewGroup.setLayoutParams(new LayoutParams(0, 0));
if (!filter.isEmpty()) {
if (Settings.HIDE_ACCOUNT_MENU_FILTER_TYPE.get()) {
if (menuTitleString.contains(filter))
hideViewGroup(viewGroup);
} else {
if (menuTitleString.equals(filter))
hideViewGroup(viewGroup);
}
}
}
}
private static void hideViewGroup(ViewGroup viewGroup) {
if (viewGroup.getLayoutParams() instanceof MarginLayoutParams)
hideViewGroupByMarginLayoutParams(viewGroup);
else
viewGroup.setLayoutParams(new LayoutParams(0, 0));
}
public static int hideHandle(int originalValue) {
return Settings.HIDE_HANDLE.get() ? 8 : originalValue;
}

View File

@ -1,8 +1,14 @@
package app.revanced.extension.youtube.patches.general;
import androidx.annotation.Nullable;
import static app.revanced.extension.shared.utils.StringRef.str;
import java.util.concurrent.atomic.AtomicBoolean;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import com.facebook.litho.ComponentHost;
import java.util.Map;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.youtube.patches.general.requests.VideoDetailsRequest;
@ -14,68 +20,105 @@ public final class OpenChannelOfLiveAvatarPatch {
private static final boolean CHANGE_LIVE_RING_CLICK_ACTION =
Settings.CHANGE_LIVE_RING_CLICK_ACTION.get();
private static final AtomicBoolean engagementPanelOpen = new AtomicBoolean(false);
private static volatile boolean liveChannelAvatarClicked = false;
/**
* If you change the language in the app settings, a string from another language may be used.
* In this case, restarting the app will solve it.
*/
private static final String liveRingDescription = str("revanced_live_ring_description");
private static volatile String videoId = "";
public static void showEngagementPanel(@Nullable Object object) {
engagementPanelOpen.set(object != null);
/**
* This key's value is the LithoView that opened the video (Live ring or Thumbnails).
*/
private static final String ELEMENTS_SENDER_VIEW =
"com.google.android.libraries.youtube.rendering.elements.sender_view";
/**
* If the video is open by clicking live ring, this key does not exists.
*/
private static final String VIDEO_THUMBNAIL_VIEW_KEY =
"VideoPresenterConstants.VIDEO_THUMBNAIL_VIEW_KEY";
/**
* Injection point.
*
* @param playbackStartDescriptorMap map containing information about PlaybackStartDescriptor
* @param newlyLoadedVideoId id of the current video
*/
public static void fetchChannelId(@NonNull Map<Object, Object> playbackStartDescriptorMap, String newlyLoadedVideoId) {
try {
if (!CHANGE_LIVE_RING_CLICK_ACTION) {
return;
}
// Video id is empty
if (newlyLoadedVideoId.isEmpty()) {
return;
}
// Video was opened by clicking the thumbnail
if (playbackStartDescriptorMap.containsKey(VIDEO_THUMBNAIL_VIEW_KEY)) {
return;
}
// If the video was opened in the watch history, there is no VIDEO_THUMBNAIL_VIEW_KEY
// In this case, check the view that opened the video (Live ring is litho)
if (!(playbackStartDescriptorMap.get(ELEMENTS_SENDER_VIEW) instanceof ComponentHost componentHost)) {
return;
}
// Check content description (accessibility labels) of the live ring.
final String contentDescription = componentHost.getContentDescription().toString();
final boolean match = liveRingDescription.equals(contentDescription);
Logger.printDebug(() -> "resource description: '" + liveRingDescription + "', litho description: '" + contentDescription + "', match: " + match);
if (!match) {
// Sometimes it may not match:
// 1. In some languages, accessibility label is not provided.
// 2. Language has changed in the app settings, and the app has not restarted.
// In this case, fallback with the legacy method.
// Child count of other litho Views such as Thumbnail and Watch history: 2
// Child count of live ring: 1
if (componentHost.getChildCount() != 1) {
return;
}
// Play all button in playlist cannot be filtered with the above conditions
// Check the ViewGroup tree
if (!(componentHost.getChildAt(0) instanceof ComponentHost liveRingViewGroup)) {
return;
}
if (!(liveRingViewGroup.getChildAt(0) instanceof ImageView)) {
return;
}
}
// Fetch channel id
videoId = newlyLoadedVideoId;
VideoDetailsRequest.fetchRequestIfNeeded(newlyLoadedVideoId);
} catch (Exception ex) {
Logger.printException(() -> "fetchVideoInformation failure", ex);
}
}
public static void hideEngagementPanel() {
engagementPanelOpen.compareAndSet(true, false);
}
public static void liveChannelAvatarClicked() {
liveChannelAvatarClicked = true;
}
public static boolean openChannelOfLiveAvatar() {
public static boolean openChannel() {
try {
if (!CHANGE_LIVE_RING_CLICK_ACTION) {
return false;
}
if (!liveChannelAvatarClicked) {
return false;
}
if (engagementPanelOpen.get()) {
// If it is not fetch, the video id is empty
if (videoId.isEmpty()) {
return false;
}
VideoDetailsRequest request = VideoDetailsRequest.getRequestForVideoId(videoId);
if (request != null) {
String channelId = request.getInfo();
// Open the channel
if (channelId != null) {
videoId = "";
liveChannelAvatarClicked = false;
VideoUtils.openChannel(channelId);
return true;
}
}
} catch (Exception ex) {
Logger.printException(() -> "openChannelOfLiveAvatar failure", ex);
Logger.printException(() -> "openChannel failure", ex);
}
return false;
}
public static void openChannelOfLiveAvatar(String newlyLoadedVideoId) {
try {
if (!CHANGE_LIVE_RING_CLICK_ACTION) {
return;
}
if (!liveChannelAvatarClicked) {
return;
}
if (engagementPanelOpen.get()) {
return;
}
if (newlyLoadedVideoId.isEmpty()) {
return;
}
videoId = newlyLoadedVideoId;
VideoDetailsRequest.fetchRequestIfNeeded(newlyLoadedVideoId);
} catch (Exception ex) {
Logger.printException(() -> "openChannelOfLiveAvatar failure", ex);
}
}
}

View File

@ -3,51 +3,53 @@ package app.revanced.extension.youtube.patches.general;
import androidx.preference.PreferenceScreen;
import app.revanced.extension.shared.patches.BaseSettingsMenuPatch;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class SettingsMenuPatch extends BaseSettingsMenuPatch {
public static void hideSettingsMenu(PreferenceScreen mPreferenceScreen) {
if (mPreferenceScreen == null) return;
for (SettingsMenuComponent component : SettingsMenuComponent.values())
if (component.enabled)
removePreference(mPreferenceScreen, component.key);
if (mPreferenceScreen != null) {
for (SettingsMenuComponent component : SettingsMenuComponent.values())
if (component.setting.get())
removePreference(mPreferenceScreen, component.key);
}
}
private enum SettingsMenuComponent {
YOUTUBE_TV("yt_unplugged_pref_key", Settings.HIDE_SETTINGS_MENU_YOUTUBE_TV.get()),
PARENT_TOOLS("parent_tools_key", Settings.HIDE_SETTINGS_MENU_PARENT_TOOLS.get()),
PRE_PURCHASE("yt_unlimited_pre_purchase_key", Settings.HIDE_SETTINGS_MENU_PRE_PURCHASE.get()),
GENERAL("general_key", Settings.HIDE_SETTINGS_MENU_GENERAL.get()),
ACCOUNT("account_switcher_key", Settings.HIDE_SETTINGS_MENU_ACCOUNT.get()),
DATA_SAVING("data_saving_settings_key", Settings.HIDE_SETTINGS_MENU_DATA_SAVING.get()),
AUTOPLAY("auto_play_key", Settings.HIDE_SETTINGS_MENU_AUTOPLAY_PLAYBACK.get()),
PLAYBACK("playback_key", Settings.HIDE_SETTINGS_MENU_AUTOPLAY_PLAYBACK.get()),
VIDEO_QUALITY_PREFERENCES("video_quality_settings_key", Settings.HIDE_SETTINGS_MENU_VIDEO_QUALITY_PREFERENCES.get()),
POST_PURCHASE("yt_unlimited_post_purchase_key", Settings.HIDE_SETTINGS_MENU_POST_PURCHASE.get()),
OFFLINE("offline_key", Settings.HIDE_SETTINGS_MENU_OFFLINE.get()),
WATCH_ON_TV("pair_with_tv_key", Settings.HIDE_SETTINGS_MENU_WATCH_ON_TV.get()),
MANAGE_ALL_HISTORY("history_key", Settings.HIDE_SETTINGS_MENU_MANAGE_ALL_HISTORY.get()),
YOUR_DATA_IN_YOUTUBE("your_data_key", Settings.HIDE_SETTINGS_MENU_YOUR_DATA_IN_YOUTUBE.get()),
PRIVACY("privacy_key", Settings.HIDE_SETTINGS_MENU_PRIVACY.get()),
TRY_EXPERIMENTAL_NEW_FEATURES("premium_early_access_browse_page_key", Settings.HIDE_SETTINGS_MENU_TRY_EXPERIMENTAL_NEW_FEATURES.get()),
PURCHASES_AND_MEMBERSHIPS("subscription_product_setting_key", Settings.HIDE_SETTINGS_MENU_PURCHASES_AND_MEMBERSHIPS.get()),
BILLING_AND_PAYMENTS("billing_and_payment_key", Settings.HIDE_SETTINGS_MENU_BILLING_AND_PAYMENTS.get()),
NOTIFICATIONS("notification_key", Settings.HIDE_SETTINGS_MENU_NOTIFICATIONS.get()),
THIRD_PARTY("third_party_key", Settings.HIDE_SETTINGS_MENU_THIRD_PARTY.get()),
CONNECTED_APPS("connected_accounts_browse_page_key", Settings.HIDE_SETTINGS_MENU_CONNECTED_APPS.get()),
LIVE_CHAT("live_chat_key", Settings.HIDE_SETTINGS_MENU_LIVE_CHAT.get()),
CAPTIONS("captions_key", Settings.HIDE_SETTINGS_MENU_CAPTIONS.get()),
ACCESSIBILITY("accessibility_settings_key", Settings.HIDE_SETTINGS_MENU_ACCESSIBILITY.get()),
ABOUT("about_key", Settings.HIDE_SETTINGS_MENU_ABOUT.get());
YOUTUBE_TV("yt_unplugged_pref_key", Settings.HIDE_SETTINGS_MENU_YOUTUBE_TV),
PARENT_TOOLS("parent_tools_key", Settings.HIDE_SETTINGS_MENU_PARENT_TOOLS),
PRE_PURCHASE("yt_unlimited_pre_purchase_key", Settings.HIDE_SETTINGS_MENU_PRE_PURCHASE),
GENERAL("general_key", Settings.HIDE_SETTINGS_MENU_GENERAL),
ACCOUNT("account_switcher_key", Settings.HIDE_SETTINGS_MENU_ACCOUNT),
DATA_SAVING("data_saving_settings_key", Settings.HIDE_SETTINGS_MENU_DATA_SAVING),
AUTOPLAY("auto_play_key", Settings.HIDE_SETTINGS_MENU_AUTOPLAY_PLAYBACK),
PLAYBACK("playback_key", Settings.HIDE_SETTINGS_MENU_AUTOPLAY_PLAYBACK),
VIDEO_QUALITY_PREFERENCES("video_quality_settings_key", Settings.HIDE_SETTINGS_MENU_VIDEO_QUALITY_PREFERENCES),
POST_PURCHASE("yt_unlimited_post_purchase_key", Settings.HIDE_SETTINGS_MENU_POST_PURCHASE),
OFFLINE("offline_key", Settings.HIDE_SETTINGS_MENU_OFFLINE),
WATCH_ON_TV("pair_with_tv_key", Settings.HIDE_SETTINGS_MENU_WATCH_ON_TV),
MANAGE_ALL_HISTORY("history_key", Settings.HIDE_SETTINGS_MENU_MANAGE_ALL_HISTORY),
YOUR_DATA_IN_YOUTUBE("your_data_key", Settings.HIDE_SETTINGS_MENU_YOUR_DATA_IN_YOUTUBE),
PRIVACY("privacy_key", Settings.HIDE_SETTINGS_MENU_PRIVACY),
TRY_EXPERIMENTAL_NEW_FEATURES("premium_early_access_browse_page_key", Settings.HIDE_SETTINGS_MENU_TRY_EXPERIMENTAL_NEW_FEATURES),
PURCHASES_AND_MEMBERSHIPS("subscription_product_setting_key", Settings.HIDE_SETTINGS_MENU_PURCHASES_AND_MEMBERSHIPS),
BILLING_AND_PAYMENTS("billing_and_payment_key", Settings.HIDE_SETTINGS_MENU_BILLING_AND_PAYMENTS),
NOTIFICATIONS("notification_key", Settings.HIDE_SETTINGS_MENU_NOTIFICATIONS),
THIRD_PARTY("third_party_key", Settings.HIDE_SETTINGS_MENU_THIRD_PARTY),
CONNECTED_APPS("connected_accounts_browse_page_key", Settings.HIDE_SETTINGS_MENU_CONNECTED_APPS),
LIVE_CHAT("live_chat_key", Settings.HIDE_SETTINGS_MENU_LIVE_CHAT),
CAPTIONS("captions_key", Settings.HIDE_SETTINGS_MENU_CAPTIONS),
ACCESSIBILITY("accessibility_settings_key", Settings.HIDE_SETTINGS_MENU_ACCESSIBILITY),
ABOUT("about_key", Settings.HIDE_SETTINGS_MENU_ABOUT);
private final String key;
private final boolean enabled;
private final BooleanSetting setting;
SettingsMenuComponent(String key, boolean enabled) {
SettingsMenuComponent(String key, BooleanSetting setting) {
this.key = key;
this.enabled = enabled;
this.setting = setting;
}
}
}

View File

@ -2,7 +2,7 @@ package app.revanced.extension.youtube.patches.general.requests
import android.annotation.SuppressLint
import androidx.annotation.GuardedBy
import app.revanced.extension.shared.patches.client.WebClient
import app.revanced.extension.shared.patches.client.YouTubeWebClient
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
import app.revanced.extension.shared.requests.Requester
import app.revanced.extension.shared.utils.Logger
@ -81,7 +81,7 @@ class VideoDetailsRequest private constructor(
private fun sendRequest(videoId: String): JSONObject? {
val startTime = System.currentTimeMillis()
val clientType = WebClient.ClientType.MWEB
val clientType = YouTubeWebClient.ClientType.MWEB
val clientTypeName = clientType.name
Logger.printDebug { "Fetching video details request for: $videoId, using client: $clientTypeName" }
@ -119,9 +119,22 @@ class VideoDetailsRequest private constructor(
private fun parseResponse(videoDetailsJson: JSONObject): String? {
try {
return videoDetailsJson
.getJSONObject("videoDetails")
.getString("channelId")
val videoDetailsJson = videoDetailsJson.getJSONObject("videoDetails")
// Live streams always open when live ring is clicked.
// Make sure this video is live streams.
val isLiveContent = videoDetailsJson.has("isLiveContent") &&
videoDetailsJson.getBoolean("isLiveContent")
// Even if 'isLiveContent' is true, it may be 'UPCOMING' video.
// Check if the value of 'isUpcoming' is true.
val isUpcoming = videoDetailsJson.has("isUpcoming") &&
videoDetailsJson.getBoolean("isUpcoming")
// Return the channel id only if the video is live streams and not 'UPCOMING' video.
if (isLiveContent && !isUpcoming) {
return videoDetailsJson.getString("channelId")
}
} catch (e: JSONException) {
Logger.printException(
{ "Fetch failed while processing response data for response: $videoDetailsJson" },

View File

@ -1,10 +1,14 @@
package app.revanced.extension.youtube.patches.misc;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
@SuppressWarnings("unused")
public class BackgroundPlaybackPatch {
private static final BooleanSetting DISABLE_SHORTS_BACKGROUND_PLAYBACK =
Settings.DISABLE_SHORTS_BACKGROUND_PLAYBACK;
/**
* Injection point.
@ -18,7 +22,7 @@ public class BackgroundPlaybackPatch {
* Injection point.
*/
public static boolean isBackgroundShortsPlaybackAllowed(boolean original) {
return !Settings.DISABLE_SHORTS_BACKGROUND_PLAYBACK.get();
return !DISABLE_SHORTS_BACKGROUND_PLAYBACK.get();
}
}

View File

@ -2,53 +2,144 @@ package app.revanced.extension.youtube.patches.player;
import androidx.annotation.Nullable;
import org.apache.commons.lang3.ArrayUtils;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static app.revanced.extension.youtube.patches.player.ActionButtonsPatch.ActionButton.*;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.Utils;
import app.revanced.extension.youtube.patches.player.requests.ActionButtonRequest;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.VideoInformation;
@SuppressWarnings("unused")
@SuppressWarnings({"unused", "deprecation"})
public class ActionButtonsPatch {
public enum ActionButton {
INDEX_7(Settings.HIDE_ACTION_BUTTON_INDEX_7, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_7, 7),
INDEX_6(Settings.HIDE_ACTION_BUTTON_INDEX_6, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_6, 6),
INDEX_5(Settings.HIDE_ACTION_BUTTON_INDEX_5, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_5, 5),
INDEX_4(Settings.HIDE_ACTION_BUTTON_INDEX_4, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_4, 4),
INDEX_3(Settings.HIDE_ACTION_BUTTON_INDEX_3, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_3, 3),
INDEX_2(Settings.HIDE_ACTION_BUTTON_INDEX_2, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_2, 2),
INDEX_1(Settings.HIDE_ACTION_BUTTON_INDEX_1, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_1, 1),
INDEX_0(Settings.HIDE_ACTION_BUTTON_INDEX_0, Settings.HIDE_ACTION_BUTTON_INDEX_LIVE_0, 0);
UNKNOWN(
null,
null
),
CLIP(
"clipButtonViewModel",
Settings.HIDE_CLIP_BUTTON
),
DOWNLOAD(
"downloadButtonViewModel",
Settings.HIDE_DOWNLOAD_BUTTON
),
LIKE_DISLIKE(
"segmentedLikeDislikeButtonViewModel",
Settings.HIDE_LIKE_DISLIKE_BUTTON
),
LIVE_CHAT(
"yt_outline_message_bubble",
null
),
PLAYLIST(
"addToPlaylistButtonViewModel",
Settings.HIDE_PLAYLIST_BUTTON
),
REMIX(
"yt_outline_youtube_shorts_plus",
Settings.HIDE_REMIX_BUTTON
),
REPORT(
"yt_outline_flag",
Settings.HIDE_REPORT_BUTTON
),
REWARDS(
"yt_outline_account_link",
Settings.HIDE_REWARDS_BUTTON
),
SHARE(
"yt_outline_share",
Settings.HIDE_SHARE_BUTTON
),
SHOP(
"yt_outline_bag",
Settings.HIDE_SHOP_BUTTON
),
THANKS(
"yt_outline_dollar_sign_heart",
Settings.HIDE_THANKS_BUTTON
);
private final BooleanSetting generalSetting;
private final BooleanSetting liveSetting;
private final int index;
@Nullable
public final String identifier;
@Nullable
public final BooleanSetting setting;
ActionButton(final BooleanSetting generalSetting, final BooleanSetting liveSetting, final int index) {
this.generalSetting = generalSetting;
this.liveSetting = liveSetting;
this.index = index;
ActionButton(@Nullable String identifier, @Nullable BooleanSetting setting) {
this.identifier = identifier;
this.setting = setting;
}
}
private static final String TARGET_COMPONENT_TYPE = "LazilyConvertedElement";
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
private static final boolean HIDE_ACTION_BUTTON_INDEX = Settings.HIDE_ACTION_BUTTON_INDEX.get();
private static final int REMIX_INDEX = Settings.REMIX_BUTTON_INDEX.get() - 1;
/**
* Injection point.
*/
public static void fetchStreams(String url, Map<String, String> requestHeaders) {
if (HIDE_ACTION_BUTTON_INDEX) {
String id = Utils.getVideoIdFromRequest(url);
if (id == null) {
Logger.printException(() -> "Ignoring request with no id: " + url);
return;
} else if (id.isEmpty()) {
return;
}
ActionButtonRequest.fetchRequestIfNeeded(id, requestHeaders);
}
}
/**
* Injection point.
*
* @param list Type list of litho components
* @param identifier Identifier of litho components
*/
public static List<Object> hideActionButtonByIndex(@Nullable List<Object> list, @Nullable String identifier) {
try {
if (identifier != null &&
if (HIDE_ACTION_BUTTON_INDEX &&
identifier != null &&
identifier.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) &&
list != null &&
!list.isEmpty() &&
list.get(0).toString().equals(TARGET_COMPONENT_TYPE)
) {
final int size = list.size();
final boolean isLive = VideoInformation.getLiveStreamState();
for (ActionButton button : ActionButton.values()) {
if (size > button.index && (isLive ? button.liveSetting.get() : button.generalSetting.get())) {
list.remove(button.index);
final int listSize = list.size();
final String videoId = VideoInformation.getVideoId();
ActionButtonRequest request = ActionButtonRequest.getRequestForVideoId(videoId);
if (request != null) {
ActionButton[] actionButtons = request.getArray();
final int actionButtonsLength = actionButtons.length;
// The response is always included with the [LIKE_DISLIKE] button and the [SHARE] button.
// The minimum size of the action button array is 3.
if (actionButtonsLength > 2) {
// For some reason, the response does not contain the [REMIX] button.
// Add the [REMIX] button manually.
if (listSize - actionButtonsLength == 1) {
actionButtons = ArrayUtils.add(actionButtons, REMIX_INDEX, REMIX);
}
ActionButton[] finalActionButtons = actionButtons;
Logger.printDebug(() -> "videoId: " + videoId + ", buttons: " + Arrays.toString(finalActionButtons));
for (int i = actionButtons.length - 1; i > -1; i--) {
ActionButton actionButton = actionButtons[i];
if (actionButton.setting != null && actionButton.setting.get()) {
list.remove(i);
}
}
}
}
}

View File

@ -1,15 +1,17 @@
package app.revanced.extension.youtube.patches.general;
package app.revanced.extension.youtube.patches.player;
import static app.revanced.extension.shared.utils.StringRef.str;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.DISABLED;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_1;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_2;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_3;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.ORIGINAL;
import static app.revanced.extension.youtube.patches.player.MiniplayerPatch.MiniplayerType.DEFAULT;
import static app.revanced.extension.youtube.patches.player.MiniplayerPatch.MiniplayerType.DISABLED;
import static app.revanced.extension.youtube.patches.player.MiniplayerPatch.MiniplayerType.MODERN_1;
import static app.revanced.extension.youtube.patches.player.MiniplayerPatch.MiniplayerType.MODERN_2;
import static app.revanced.extension.youtube.patches.player.MiniplayerPatch.MiniplayerType.MODERN_3;
import static app.revanced.extension.youtube.patches.player.MiniplayerPatch.MiniplayerType.MODERN_4;
import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_20_OR_GREATER;
import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_21_OR_GREATER;
import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_26_OR_GREATER;
import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_29_OR_GREATER;
import static app.revanced.extension.youtube.utils.ExtendedUtils.IS_19_34_OR_GREATER;
import static app.revanced.extension.youtube.utils.ExtendedUtils.validateValue;
import android.content.Context;
@ -27,7 +29,6 @@ import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.ResourceUtils;
import app.revanced.extension.shared.utils.Utils;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.utils.ExtendedUtils;
@SuppressWarnings({"unused", "SpellCheckingInspection"})
public final class MiniplayerPatch {
@ -44,7 +45,7 @@ public final class MiniplayerPatch {
/**
* Unmodified type, and same as un-patched.
*/
ORIGINAL(null, null),
DEFAULT(null, null),
/**
* Exactly the same as MINIMAL and only here for migration of user settings.
* Eventually this should be deleted.
@ -57,10 +58,13 @@ public final class MiniplayerPatch {
MODERN_2(null, 2),
MODERN_3(null, 3),
/**
* Half broken miniplayer, that might be work in progress or left over abandoned code.
* Can force this type by editing the import/export settings.
* Works and is functional with 20.03+
*/
MODERN_4(null, 4);
MODERN_4(null, 4),
/**
* Half broken miniplayer, and in 20.02 and earlier is declared as type 4.
*/
MODERN_5(null, 5);
/**
* Legacy tablet hook value.
@ -143,6 +147,9 @@ public final class MiniplayerPatch {
private static final int MODERN_OVERLAY_SUBTITLE_TEXT
= ResourceUtils.getIdIdentifier("modern_miniplayer_subtitle_text");
private static final boolean DISABLE_RESUMING_MINIPLAYER =
Settings.DISABLE_RESUMING_MINIPLAYER.get();
private static final MiniplayerType CURRENT_TYPE = Settings.MINIPLAYER_TYPE.get();
/**
@ -157,20 +164,21 @@ public final class MiniplayerPatch {
private static final boolean DRAG_AND_DROP_ENABLED =
CURRENT_TYPE.isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get();
private static final boolean HIDE_EXPAND_CLOSE_ENABLED =
Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.get()
&& Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.isAvailable();
private static final boolean HIDE_OVERLAY_BUTTONS_ENABLED =
Settings.MINIPLAYER_HIDE_OVERLAY_BUTTONS.get()
&& Settings.MINIPLAYER_HIDE_OVERLAY_BUTTONS.isAvailable();
private static final boolean HIDE_SUBTEXT_ENABLED =
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3) && Settings.MINIPLAYER_HIDE_SUBTEXT.get();
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3 || CURRENT_TYPE == MODERN_4)
&& Settings.MINIPLAYER_HIDE_SUBTEXT.get();
// 19.25 is last version that has forward/back buttons for phones,
// but buttons still show for tablets/foldable devices and they don't work well so always hide.
private static final boolean HIDE_REWIND_FORWARD_ENABLED = CURRENT_TYPE == MODERN_1
&& (ExtendedUtils.IS_19_34_OR_GREATER || Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get());
&& (IS_19_34_OR_GREATER || Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get());
private static final boolean MINIPLAYER_ROUNDED_CORNERS_ENABLED =
Settings.MINIPLAYER_ROUNDED_CORNERS.get();
CURRENT_TYPE.isModern() && Settings.MINIPLAYER_ROUNDED_CORNERS.get();
private static final boolean MINIPLAYER_HORIZONTAL_DRAG_ENABLED =
DRAG_AND_DROP_ENABLED && Settings.MINIPLAYER_HORIZONTAL_DRAG.get();
@ -202,17 +210,25 @@ public final class MiniplayerPatch {
}
}
public static final class MiniplayerHideExpandCloseAvailability implements Setting.Availability {
public static final class MiniplayerHideOverlayButtonsAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
MiniplayerType type = Settings.MINIPLAYER_TYPE.get();
return (!IS_19_20_OR_GREATER && (type == MODERN_1 || type == MODERN_3))
return type == MODERN_4
|| (!IS_19_20_OR_GREATER && (type == MODERN_1 || type == MODERN_3))
|| (!IS_19_26_OR_GREATER && type == MODERN_1
&& !Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get() && !Settings.MINIPLAYER_DRAG_AND_DROP.get())
|| (IS_19_29_OR_GREATER && type == MODERN_3);
}
}
/**
* Injection point.
*/
public static boolean disableResumingStartupMiniPlayer(boolean original) {
return !DISABLE_RESUMING_MINIPLAYER && original;
}
/**
* Injection point.
* <p>
@ -220,7 +236,7 @@ public final class MiniplayerPatch {
* effectively disabling the miniplayer.
*/
public static boolean getMiniplayerOnCloseHandler(boolean original) {
return CURRENT_TYPE == ORIGINAL
return CURRENT_TYPE == DEFAULT
? original
: CURRENT_TYPE == DISABLED;
}
@ -239,7 +255,7 @@ public final class MiniplayerPatch {
* Injection point.
*/
public static boolean getModernMiniplayerOverride(boolean original) {
return CURRENT_TYPE == ORIGINAL
return CURRENT_TYPE == DEFAULT
? original
: CURRENT_TYPE.isModern();
}
@ -257,9 +273,13 @@ public final class MiniplayerPatch {
/**
* Injection point.
*/
public static void adjustMiniplayerOpacity(ImageView view) {
public static void adjustMiniplayerOpacity(View view) {
if (CURRENT_TYPE == MODERN_1) {
view.setImageAlpha(OPACITY_LEVEL);
if (view instanceof ImageView imageView) {
imageView.setImageAlpha(OPACITY_LEVEL);
} else {
Logger.printException(() -> "Unknown miniplayer overlay view. viewType: " + view.getClass().getName());
}
}
}
@ -267,7 +287,7 @@ public final class MiniplayerPatch {
* Injection point.
*/
public static boolean getModernFeatureFlagsActiveOverride(boolean original) {
if (CURRENT_TYPE == ORIGINAL) {
if (CURRENT_TYPE == DEFAULT) {
return original;
}
@ -277,8 +297,8 @@ public final class MiniplayerPatch {
/**
* Injection point.
*/
public static boolean enableMiniplayerDoubleTapAction(boolean original) {
if (CURRENT_TYPE == ORIGINAL) {
public static boolean getMiniplayerDoubleTapAction(boolean original) {
if (CURRENT_TYPE == DEFAULT) {
return original;
}
@ -288,8 +308,8 @@ public final class MiniplayerPatch {
/**
* Injection point.
*/
public static boolean enableMiniplayerDragAndDrop(boolean original) {
if (CURRENT_TYPE == ORIGINAL) {
public static boolean getMiniplayerDragAndDrop(boolean original) {
if (CURRENT_TYPE == DEFAULT) {
return original;
}
@ -300,9 +320,33 @@ public final class MiniplayerPatch {
/**
* Injection point.
*/
public static boolean setRoundedCorners(boolean original) {
if (CURRENT_TYPE.isModern()) {
return MINIPLAYER_ROUNDED_CORNERS_ENABLED;
public static boolean getRoundedCorners(boolean original) {
if (CURRENT_TYPE == DEFAULT) {
return original;
}
return MINIPLAYER_ROUNDED_CORNERS_ENABLED;
}
/**
* Injection point.
*/
public static boolean getHorizontalDrag(boolean original) {
if (CURRENT_TYPE == DEFAULT) {
return original;
}
return MINIPLAYER_HORIZONTAL_DRAG_ENABLED;
}
/**
* Injection point.
*/
public static boolean getMaximizeAnimation(boolean original) {
// This must be forced on if horizontal drag is enabled,
// otherwise the UI has visual glitches when maximizing the miniplayer.
if (MINIPLAYER_HORIZONTAL_DRAG_ENABLED) {
return true;
}
return original;
@ -311,7 +355,7 @@ public final class MiniplayerPatch {
/**
* Injection point.
*/
public static int setMiniplayerDefaultSize(int original) {
public static int getMiniplayerDefaultSize(int original) {
if (CURRENT_TYPE.isModern()) {
if (MINIPLAYER_SIZE == 0) {
setMiniPlayerSize();
@ -324,29 +368,26 @@ public final class MiniplayerPatch {
return original;
}
/**
* Injection point.
*/
public static void hideMiniplayerExpandClose(View view) {
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_OVERLAY_BUTTONS_ENABLED, view);
}
/**
* Injection point.
*/
public static boolean setHorizontalDrag(boolean original) {
if (CURRENT_TYPE.isModern()) {
return MINIPLAYER_HORIZONTAL_DRAG_ENABLED;
public static void hideMiniplayerActionButton(View view) {
if (CURRENT_TYPE == MODERN_4) {
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_OVERLAY_BUTTONS_ENABLED, view);
}
return original;
}
/**
* Injection point.
*/
public static void hideMiniplayerExpandClose(ImageView view) {
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_EXPAND_CLOSE_ENABLED, view);
}
/**
* Injection point.
*/
public static void hideMiniplayerRewindForward(ImageView view) {
public static void hideMiniplayerRewindForward(View view) {
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_REWIND_FORWARD_ENABLED, view);
}

View File

@ -19,16 +19,19 @@ import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.IntegerSetting;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.ResourceUtils;
import app.revanced.extension.shared.utils.Utils;
import app.revanced.extension.youtube.patches.utils.InitializationPatch;
import app.revanced.extension.youtube.patches.utils.PatchStatus;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.EngagementPanel;
import app.revanced.extension.youtube.shared.PlayerType;
import app.revanced.extension.youtube.shared.RootView;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
@ -116,34 +119,15 @@ public class PlayerPatch {
* view id R.id.content
*/
private static final int contentId = ResourceUtils.getIdIdentifier("content");
private static final boolean expandDescriptionEnabled = Settings.EXPAND_VIDEO_DESCRIPTION.get();
private static final String descriptionString = Settings.EXPAND_VIDEO_DESCRIPTION_STRINGS.get();
private static boolean isDescriptionPanel = false;
public static void setContentDescription(String contentDescription) {
if (!expandDescriptionEnabled) {
return;
}
if (contentDescription == null || contentDescription.isEmpty()) {
isDescriptionPanel = false;
return;
}
if (descriptionString.isEmpty()) {
isDescriptionPanel = false;
return;
}
isDescriptionPanel = descriptionString.equals(contentDescription);
}
private static final boolean EXPAND_VIDEO_DESCRIPTION = Settings.EXPAND_VIDEO_DESCRIPTION.get();
/**
* The last time the clickDescriptionView method was called.
*/
private static long lastTimeDescriptionViewInvoked;
public static void onVideoDescriptionCreate(RecyclerView recyclerView) {
if (!expandDescriptionEnabled)
if (!EXPAND_VIDEO_DESCRIPTION)
return;
recyclerView.getViewTreeObserver().addOnDrawListener(() -> {
@ -159,9 +143,8 @@ public class PlayerPatch {
if (contentView.getId() != contentId) {
return;
}
// This method is invoked whenever the Engagement panel is opened. (Description, Chapters, Comments, etc.)
// Check the title of the Engagement panel to prevent unnecessary clicking.
if (!isDescriptionPanel) {
// Check description panel opened.
if (!EngagementPanel.isDescription()) {
return;
}
// The first view group contains information such as the video's title, like count, and number of views.
@ -443,20 +426,22 @@ public class PlayerPatch {
imageView.setImageAlpha(PLAYER_OVERLAY_OPACITY_LEVEL);
}
private static boolean isAutoPopupPanel;
@NonNull
private static final AtomicBoolean newVideoStarted = new AtomicBoolean(false);
public static boolean disableAutoPlayerPopupPanels(boolean isLiveChatOrPlaylistPanel) {
if (!Settings.DISABLE_AUTO_PLAYER_POPUP_PANELS.get()) {
return false;
public static boolean disableAutoPlayerPopupPanels(boolean isLiveChatOrPlaylistPanel, String panelId) {
if (Settings.DISABLE_AUTO_PLAYER_POPUP_PANELS.get()) {
return isLiveChatOrPlaylistPanel || (panelId.equals("PAproduct_list") && newVideoStarted.get());
}
if (isLiveChatOrPlaylistPanel) {
return true;
}
return isAutoPopupPanel && ShortsPlayerState.getCurrent().isClosed();
return false;
}
public static void setInitVideoPanel(boolean initVideoPanel) {
isAutoPopupPanel = initVideoPanel;
public static void disableAutoPlayerPopupPanels(@NonNull String newlyLoadedChannelId, @NonNull String newlyLoadedChannelName,
@NonNull String newlyLoadedVideoId, @NonNull String newlyLoadedVideoTitle,
final long newlyLoadedVideoLength, boolean newlyLoadedLiveStreamValue) {
if (Settings.DISABLE_AUTO_PLAYER_POPUP_PANELS.get() && newVideoStarted.compareAndSet(false, true)) {
Utils.runOnMainThreadDelayed(() -> newVideoStarted.compareAndSet(true, false), 1500L);
}
}
@NonNull
@ -480,7 +465,7 @@ public class PlayerPatch {
return;
}
VideoUtils.pauseMedia();
VideoUtils.openVideo(videoId);
VideoUtils.openVideo(newlyLoadedVideoId);
}
public static boolean disableSpeedOverlay() {

View File

@ -20,38 +20,53 @@ public class SeekbarColorPatch {
private static final boolean CUSTOM_SEEKBAR_COLOR_ENABLED =
Settings.ENABLE_CUSTOM_SEEKBAR_COLOR.get();
private static final boolean HIDE_SEEKBAR_THUMBNAIL_ENABLED =
Settings.HIDE_SEEKBAR_THUMBNAIL.get();
/**
* Default color of the litho seekbar.
* Differs slightly from the default custom seekbar color setting.
*/
private static final int ORIGINAL_SEEKBAR_COLOR = 0xFFFF0000;
/**
* Default accent color of the litho seekbar.
*/
private static final int ORIGINAL_SEEKBAR_COLOR_ACCENT = 0xFFFF2791;
/**
* Feed default colors of the gradient seekbar.
*/
private static final int[] FEED_ORIGINAL_SEEKBAR_GRADIENT_COLORS = {0xFFFF0033, 0xFFFF2791};
private static final int[] FEED_ORIGINAL_SEEKBAR_GRADIENT_COLORS = {0xFFFF0033, ORIGINAL_SEEKBAR_COLOR_ACCENT};
/**
* Feed default positions of the gradient seekbar.
*/
private static final float[] FEED_ORIGINAL_SEEKBAR_GRADIENT_POSITIONS = {0.8f, 1.0f};
/**
* Empty seekbar gradient, if hide seekbar in feed is enabled.
*/
private static final int[] HIDDEN_SEEKBAR_GRADIENT_COLORS = { 0x0, 0x0 };
/**
* Default YouTube seekbar color brightness.
*/
private static final float ORIGINAL_SEEKBAR_COLOR_BRIGHTNESS;
/**
* Empty seekbar gradient, if hide seekbar in feed is enabled.
* If {@link Settings#ENABLE_CUSTOM_SEEKBAR_COLOR} is enabled,
* this is the color value of {@link Settings#CUSTOM_SEEKBAR_COLOR_PRIMARY}.
* Otherwise this is {@link #ORIGINAL_SEEKBAR_COLOR}.
*/
private static final int[] HIDDEN_SEEKBAR_GRADIENT_COLORS = {0x00000000, 0x00000000};
private static int customSeekbarColor = ORIGINAL_SEEKBAR_COLOR;
/**
* If {@link Settings#ENABLE_CUSTOM_SEEKBAR_COLOR} is enabled,
* this is the color value of {@link Settings#CUSTOM_SEEKBAR_COLOR_VALUE}.
* Otherwise this is {@link #ORIGINAL_SEEKBAR_COLOR}.
* this is the color value of {@link Settings#CUSTOM_SEEKBAR_COLOR_ACCENT}.
* Otherwise this is {@link #ORIGINAL_SEEKBAR_COLOR_ACCENT}.
*/
private static int seekbarColor = ORIGINAL_SEEKBAR_COLOR;
private static int customSeekbarColorAccent = ORIGINAL_SEEKBAR_COLOR_ACCENT;
/**
* Custom seekbar hue, saturation, and brightness values.
@ -61,7 +76,7 @@ public class SeekbarColorPatch {
/**
* Custom seekbar color, used for linear gradient replacements.
*/
private static final int[] customSeekbarColorInt = new int[2];
private static final int[] customSeekbarColorGradient = new int[2];
static {
float[] hsv = new float[3];
@ -71,33 +86,28 @@ public class SeekbarColorPatch {
if (CUSTOM_SEEKBAR_COLOR_ENABLED) {
loadCustomSeekbarColor();
}
Arrays.fill(customSeekbarColorInt, seekbarColor);
}
private static void loadCustomSeekbarColor() {
try {
seekbarColor = Color.parseColor(Settings.CUSTOM_SEEKBAR_COLOR_VALUE.get());
Color.colorToHSV(seekbarColor, customSeekbarColorHSV);
customSeekbarColor = Color.parseColor(Settings.CUSTOM_SEEKBAR_COLOR_PRIMARY.get());
Color.colorToHSV(customSeekbarColor, customSeekbarColorHSV);
customSeekbarColorAccent = Color.parseColor(Settings.CUSTOM_SEEKBAR_COLOR_ACCENT.get());
customSeekbarColorGradient[0] = customSeekbarColor;
customSeekbarColorGradient[1] = customSeekbarColorAccent;
} catch (Exception ex) {
Utils.showToastShort(str("revanced_custom_seekbar_color_value_invalid_invalid_toast"));
Utils.showToastShort(str("revanced_custom_seekbar_color_invalid_toast"));
Utils.showToastShort(str("revanced_extended_reset_to_default_toast"));
Settings.CUSTOM_SEEKBAR_COLOR_VALUE.resetToDefault();
Settings.CUSTOM_SEEKBAR_COLOR_PRIMARY.resetToDefault();
Settings.CUSTOM_SEEKBAR_COLOR_ACCENT.resetToDefault();
loadCustomSeekbarColor();
}
}
public static int getSeekbarColor() {
return seekbarColor;
}
/**
* Injection point
*/
public static boolean playerSeekbarGradientEnabled(boolean original) {
if (CUSTOM_SEEKBAR_COLOR_ENABLED) return false;
return original;
return customSeekbarColor;
}
/**
@ -141,7 +151,7 @@ public class SeekbarColorPatch {
// Even if the seekbar color xml value is changed to a completely different color (such as green),
// a color filter still cannot be selectively applied when the drawable has more than 1 color.
try {
String seekbarStyle = get9BitStyleIdentifier(seekbarColor);
String seekbarStyle = get9BitStyleIdentifier(customSeekbarColor);
Logger.printDebug(() -> "Using splash seekbar style: " + seekbarStyle);
final int styleIdentifierDefault = ResourceUtils.getStyleIdentifier(seekbarStyle);
@ -158,6 +168,20 @@ public class SeekbarColorPatch {
}
}
/**
* Injection point
*/
public static boolean playerSeekbarGradientEnabled(boolean original) {
return CUSTOM_SEEKBAR_COLOR_ENABLED || original;
}
/**
* Injection point.
*/
public static boolean showWatchHistoryProgressDrawable(boolean original) {
return !HIDE_SEEKBAR_THUMBNAIL_ENABLED && original;
}
/**
* Injection point.
* <p>
@ -168,31 +192,21 @@ public class SeekbarColorPatch {
*/
public static int getLithoColor(int colorValue) {
if (colorValue == ORIGINAL_SEEKBAR_COLOR) {
if (Settings.HIDE_SEEKBAR_THUMBNAIL.get()) {
return 0x00000000;
if (HIDE_SEEKBAR_THUMBNAIL_ENABLED) {
return 0x0;
}
return getSeekbarColorValue(ORIGINAL_SEEKBAR_COLOR);
return customSeekbarColor;
}
return colorValue;
}
/**
* Injection point.
*/
public static int[] getLinearGradient(int[] original) {
if (Settings.HIDE_SEEKBAR_THUMBNAIL.get()) {
return HIDDEN_SEEKBAR_GRADIENT_COLORS;
}
return CUSTOM_SEEKBAR_COLOR_ENABLED
? customSeekbarColorInt
: original;
return colorValue;
}
private static String colorArrayToHex(int[] colors) {
final int length = colors.length;
StringBuilder builder = new StringBuilder(length * 10);
StringBuilder builder = new StringBuilder(length * 12);
builder.append("[");
int i = 0;
for (int color : colors) {
builder.append(String.format("#%X", color));
@ -200,6 +214,7 @@ public class SeekbarColorPatch {
builder.append(", ");
}
}
builder.append("]");
return builder.toString();
}
@ -207,23 +222,31 @@ public class SeekbarColorPatch {
/**
* Injection point.
*/
public static void setLinearGradient(int[] colors, float[] positions) {
final boolean hideSeekbar = Settings.HIDE_SEEKBAR_THUMBNAIL.get();
public static int[] getPlayerLinearGradient(int[] original) {
return CUSTOM_SEEKBAR_COLOR_ENABLED
? customSeekbarColorGradient
: original;
}
if (CUSTOM_SEEKBAR_COLOR_ENABLED || hideSeekbar) {
/**
* Injection point.
*/
public static int[] getLithoLinearGradient(int[] colors, float[] positions) {
if (CUSTOM_SEEKBAR_COLOR_ENABLED || HIDE_SEEKBAR_THUMBNAIL_ENABLED) {
// Most litho usage of linear gradients is hooked here,
// so must only change if the values are those for the seekbar.
if ((Arrays.equals(FEED_ORIGINAL_SEEKBAR_GRADIENT_COLORS, colors)
&& Arrays.equals(FEED_ORIGINAL_SEEKBAR_GRADIENT_POSITIONS, positions))) {
Arrays.fill(colors, hideSeekbar
? 0x00000000
: seekbarColor);
return;
return HIDE_SEEKBAR_THUMBNAIL_ENABLED
? HIDDEN_SEEKBAR_GRADIENT_COLORS
: customSeekbarColorGradient;
}
Logger.printDebug(() -> "Ignoring gradient colors: " + colorArrayToHex(colors)
+ " positions: " + Arrays.toString(positions));
}
return colors;
}
/**
@ -247,11 +270,20 @@ public class SeekbarColorPatch {
* Overrides color used for the video player seekbar.
*/
public static int getVideoPlayerSeekbarColor(int originalColor) {
if (!CUSTOM_SEEKBAR_COLOR_ENABLED) {
return originalColor;
}
return CUSTOM_SEEKBAR_COLOR_ENABLED
? getSeekbarColorValue(originalColor)
: originalColor;
}
return getSeekbarColorValue(originalColor);
/**
* Injection point.
* <p>
* Overrides color used for the video player seekbar.
*/
public static int getVideoPlayerSeekbarColorAccent(int originalColor) {
return CUSTOM_SEEKBAR_COLOR_ENABLED
? customSeekbarColorAccent
: originalColor;
}
/**
@ -260,10 +292,6 @@ public class SeekbarColorPatch {
*/
private static int getSeekbarColorValue(int originalColor) {
try {
if (!CUSTOM_SEEKBAR_COLOR_ENABLED || originalColor == seekbarColor) {
return originalColor; // nothing to do
}
final int alphaDifference = Color.alpha(originalColor) - Color.alpha(ORIGINAL_SEEKBAR_COLOR);
// The seekbar uses the same color but different brightness for different situations.
@ -276,7 +304,7 @@ public class SeekbarColorPatch {
hsv[1] = customSeekbarColorHSV[1];
hsv[2] = clamp(customSeekbarColorHSV[2] + brightnessDifference, 0, 1);
final int replacementAlpha = clamp(Color.alpha(seekbarColor) + alphaDifference, 0, 255);
final int replacementAlpha = clamp(Color.alpha(customSeekbarColor) + alphaDifference, 0, 255);
final int replacementColor = Color.HSVToColor(replacementAlpha, hsv);
Logger.printDebug(() -> String.format("Original color: #%08X replacement color: #%08X",
originalColor, replacementColor));

View File

@ -0,0 +1,226 @@
package app.revanced.extension.youtube.patches.player.requests
import androidx.annotation.GuardedBy
import app.revanced.extension.shared.patches.client.YouTubeAppClient
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
import app.revanced.extension.shared.requests.Requester
import app.revanced.extension.shared.utils.Logger
import app.revanced.extension.shared.utils.Utils
import app.revanced.extension.youtube.patches.player.ActionButtonsPatch.ActionButton
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.net.SocketTimeoutException
import java.util.Collections
import java.util.Objects
import java.util.concurrent.ExecutionException
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
class ActionButtonRequest private constructor(
private val videoId: String,
private val playerHeaders: Map<String, String>,
) {
private val future: Future<Array<ActionButton>> = Utils.submitOnBackgroundThread {
fetch(videoId, playerHeaders)
}
val array: Array<ActionButton>
get() {
try {
return future[MAX_MILLISECONDS_TO_WAIT_FOR_FETCH.toLong(), TimeUnit.MILLISECONDS]
} catch (ex: TimeoutException) {
Logger.printInfo(
{ "getArray timed out" },
ex
)
} catch (ex: InterruptedException) {
Logger.printException(
{ "getArray interrupted" },
ex
)
Thread.currentThread().interrupt() // Restore interrupt status flag.
} catch (ex: ExecutionException) {
Logger.printException(
{ "getArray failure" },
ex
)
}
return emptyArray()
}
companion object {
/**
* TCP connection and HTTP read timeout.
*/
private const val HTTP_TIMEOUT_MILLISECONDS = 10 * 1000
/**
* Any arbitrarily large value, but must be at least twice [HTTP_TIMEOUT_MILLISECONDS]
*/
private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000
@GuardedBy("itself")
val cache: MutableMap<String, ActionButtonRequest> = Collections.synchronizedMap(
object : LinkedHashMap<String, ActionButtonRequest>(100) {
private val CACHE_LIMIT = 50
override fun removeEldestEntry(eldest: Map.Entry<String, ActionButtonRequest>): Boolean {
return size > CACHE_LIMIT // Evict the oldest entry if over the cache limit.
}
})
@JvmStatic
fun fetchRequestIfNeeded(videoId: String, playerHeaders: Map<String, String>) {
Objects.requireNonNull(videoId)
synchronized(cache) {
if (!cache.containsKey(videoId)) {
cache[videoId] = ActionButtonRequest(videoId, playerHeaders)
}
}
}
@JvmStatic
fun getRequestForVideoId(videoId: String): ActionButtonRequest? {
synchronized(cache) {
return cache[videoId]
}
}
private fun handleConnectionError(toastMessage: String, ex: Exception?) {
Logger.printInfo({ toastMessage }, ex)
}
private val REQUEST_HEADER_KEYS = arrayOf(
"Authorization", // Available only to logged-in users.
"X-GOOG-API-FORMAT-VERSION",
"X-Goog-Visitor-Id"
)
private fun sendRequest(videoId: String, playerHeaders: Map<String, String>): JSONObject? {
Objects.requireNonNull(videoId)
val startTime = System.currentTimeMillis()
// '/next' request does not require PoToken.
val clientType = YouTubeAppClient.ClientType.ANDROID
val clientTypeName = clientType.name
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
try {
val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(
PlayerRoutes.GET_VIDEO_ACTION_BUTTON,
clientType
)
connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS
connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS
// Since [THANKS] button and [CLIP] button are shown only with the logged in,
// Set the [Authorization] field to property to get the correct action buttons.
for (key in REQUEST_HEADER_KEYS) {
var value = playerHeaders[key]
if (value != null) {
connection.setRequestProperty(key, value)
}
}
val requestBody =
PlayerRoutes.createApplicationRequestBody(
clientType = clientType,
videoId = videoId
)
connection.setFixedLengthStreamingMode(requestBody.size)
connection.outputStream.write(requestBody)
val responseCode = connection.responseCode
if (responseCode == 200) return Requester.parseJSONObject(connection)
handleConnectionError(
(clientTypeName + " not available with response code: "
+ responseCode + " message: " + connection.responseMessage),
null
)
} catch (ex: SocketTimeoutException) {
handleConnectionError("Connection timeout", ex)
} catch (ex: IOException) {
handleConnectionError("Network error", ex)
} catch (ex: Exception) {
Logger.printException({ "sendApplicationRequest failed" }, ex)
} finally {
Logger.printDebug { "video: " + videoId + " took: " + (System.currentTimeMillis() - startTime) + "ms" }
}
return null
}
private fun parseResponse(json: JSONObject): Array<ActionButton> {
try {
val secondaryContentsJsonObject =
json.getJSONObject("contents")
.getJSONObject("singleColumnWatchNextResults")
.getJSONObject("results")
.getJSONObject("results")
.getJSONArray("contents")
.get(0)
if (secondaryContentsJsonObject is JSONObject) {
val tertiaryContentsJsonArray =
secondaryContentsJsonObject
.getJSONObject("slimVideoMetadataSectionRenderer")
.getJSONArray("contents")
val elementRendererJsonObject =
tertiaryContentsJsonArray
.get(tertiaryContentsJsonArray.length() - 1)
if (elementRendererJsonObject is JSONObject) {
val buttons =
elementRendererJsonObject
.getJSONObject("elementRenderer")
.getJSONObject("newElement")
.getJSONObject("type")
.getJSONObject("componentType")
.getJSONObject("model")
.getJSONObject("videoActionBarModel")
.getJSONArray("buttons")
val length = buttons.length()
val buttonsArr = Array<ActionButton>(length) { ActionButton.UNKNOWN }
for (i in 0 until length) {
val jsonObjectString = buttons.get(i).toString()
for (b in ActionButton.entries) {
if (b.identifier != null && jsonObjectString.contains(b.identifier)) {
buttonsArr[i] = b
}
}
}
// Still, the response includes the [LIVE_CHAT] button.
// In the Android YouTube client, this button moved to the comments.
return buttonsArr.filter { it.setting != null }.toTypedArray()
}
}
} catch (e: JSONException) {
val jsonForMessage = json.toString().substring(3000)
Logger.printException(
{ "Fetch failed while processing response data for response: $jsonForMessage" },
e
)
}
return emptyArray()
}
private fun fetch(videoId: String, playerHeaders: Map<String, String>): Array<ActionButton> {
val json = sendRequest(videoId, playerHeaders)
if (json != null) {
return parseResponse(json)
}
return emptyArray()
}
}
}

View File

@ -8,12 +8,11 @@ public class PlaybackSpeedWhilePlayingPatch {
private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f;
public static boolean playbackSpeedChanged(float playbackSpeed) {
PlayerType playerType = PlayerType.getCurrent();
if (playbackSpeed == DEFAULT_YOUTUBE_PLAYBACK_SPEED &&
PlayerType.getCurrent().isMaximizedOrFullscreen()) {
playerType.isMaximizedOrFullscreenOrPiP()) {
Logger.printDebug(() -> "Even though playback has already started and the user has not changed the playback speed, " +
"the app attempts to change the playback speed to 1.0x." +
"\nIgnore changing playback speed, as it is invalid request.");
Logger.printDebug(() -> "Ignore changing playback speed, as it is invalid request: " + playerType.name());
return true;
}

View File

@ -2,8 +2,8 @@ package app.revanced.extension.youtube.patches.video.requests
import android.annotation.SuppressLint
import androidx.annotation.GuardedBy
import app.revanced.extension.shared.patches.client.AppClient
import app.revanced.extension.shared.patches.client.WebClient
import app.revanced.extension.shared.patches.client.YouTubeAppClient
import app.revanced.extension.shared.patches.client.YouTubeWebClient
import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes
import app.revanced.extension.shared.requests.Requester
import app.revanced.extension.shared.utils.Logger
@ -119,7 +119,7 @@ class MusicRequest private constructor(
Objects.requireNonNull(videoId)
val startTime = System.currentTimeMillis()
val clientType = AppClient.ClientType.ANDROID_VR
val clientType = YouTubeAppClient.ClientType.ANDROID_VR
val clientTypeName = clientType.name
Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" }
@ -163,7 +163,7 @@ class MusicRequest private constructor(
Objects.requireNonNull(videoId)
val startTime = System.currentTimeMillis()
val clientType = WebClient.ClientType.MWEB
val clientType = YouTubeWebClient.ClientType.MWEB
val clientTypeName = clientType.name
Logger.printDebug { "Fetching microformat request for: $videoId, using client: $clientTypeName" }

View File

@ -6,11 +6,11 @@ import static app.revanced.extension.shared.settings.Setting.migrateFromOldPrefe
import static app.revanced.extension.shared.settings.Setting.parent;
import static app.revanced.extension.shared.settings.Setting.parentsAny;
import static app.revanced.extension.shared.utils.StringRef.str;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_1;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_2;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_3;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_4;
import static app.revanced.extension.youtube.patches.player.MiniplayerPatch.MiniplayerType;
import static app.revanced.extension.youtube.patches.player.MiniplayerPatch.MiniplayerType.MODERN_1;
import static app.revanced.extension.youtube.patches.player.MiniplayerPatch.MiniplayerType.MODERN_2;
import static app.revanced.extension.youtube.patches.player.MiniplayerPatch.MiniplayerType.MODERN_3;
import static app.revanced.extension.youtube.patches.player.MiniplayerPatch.MiniplayerType.MODERN_4;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE;
@ -35,10 +35,9 @@ import app.revanced.extension.youtube.patches.alternativethumbnails.AlternativeT
import app.revanced.extension.youtube.patches.general.ChangeStartPagePatch;
import app.revanced.extension.youtube.patches.general.ChangeStartPagePatch.StartPage;
import app.revanced.extension.youtube.patches.general.LayoutSwitchPatch.FormFactor;
import app.revanced.extension.youtube.patches.general.MiniplayerPatch;
import app.revanced.extension.youtube.patches.general.YouTubeMusicActionsPatch;
import app.revanced.extension.youtube.patches.misc.WatchHistoryPatch.WatchHistoryType;
import app.revanced.extension.youtube.patches.player.ExitFullscreenPatch.FullscreenMode;
import app.revanced.extension.youtube.patches.player.MiniplayerPatch;
import app.revanced.extension.youtube.patches.shorts.AnimationFeedbackPatch.AnimationType;
import app.revanced.extension.youtube.patches.shorts.ShortsRepeatStatePatch.ShortsLoopBehavior;
import app.revanced.extension.youtube.patches.utils.PatchStatus;
@ -48,6 +47,7 @@ import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
@SuppressWarnings("unused")
public class Settings extends BaseSettings {
// PreferenceScreen: Ads
public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true);
public static final BooleanSetting HIDE_GENERAL_ADS = new BooleanSetting("revanced_hide_general_ads", TRUE);
public static final BooleanSetting HIDE_GET_PREMIUM = new BooleanSetting("revanced_hide_get_premium", TRUE, true);
public static final BooleanSetting HIDE_MERCHANDISE_SHELF = new BooleanSetting("revanced_hide_merchandise_shelf", TRUE);
@ -86,7 +86,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", FALSE);
public static final BooleanSetting HIDE_MOVIE_SHELF = new BooleanSetting("revanced_hide_movie_shelf", FALSE);
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", FALSE);
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", FALSE);
public static final BooleanSetting HIDE_FEED_SEARCH_BAR = new BooleanSetting("revanced_hide_feed_search_bar", FALSE);
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
public static final BooleanSetting HIDE_SUBSCRIPTIONS_CAROUSEL = new BooleanSetting("revanced_hide_subscriptions_carousel", FALSE, true);
@ -113,6 +113,7 @@ public class Settings extends BaseSettings {
// PreferenceScreen: Feed - Flyout menu
public static final BooleanSetting HIDE_FEED_FLYOUT_MENU = new BooleanSetting("revanced_hide_feed_flyout_menu", FALSE);
public static final BooleanSetting HIDE_FEED_FLYOUT_MENU_FILTER_TYPE = new BooleanSetting("revanced_hide_feed_flyout_menu_filter_type", FALSE, true, parent(HIDE_FEED_FLYOUT_MENU));
public static final StringSetting HIDE_FEED_FLYOUT_MENU_FILTER_STRINGS = new StringSetting("revanced_hide_feed_flyout_menu_filter_strings", "", true, parent(HIDE_FEED_FLYOUT_MENU));
// PreferenceScreen: Feed - Video filter
@ -160,6 +161,7 @@ public class Settings extends BaseSettings {
// PreferenceScreen: General - Account menu
public static final BooleanSetting HIDE_ACCOUNT_MENU = new BooleanSetting("revanced_hide_account_menu", FALSE);
public static final BooleanSetting HIDE_ACCOUNT_MENU_FILTER_TYPE = new BooleanSetting("revanced_hide_account_menu_filter_type", FALSE, true, parent(HIDE_ACCOUNT_MENU));
public static final StringSetting HIDE_ACCOUNT_MENU_FILTER_STRINGS = new StringSetting("revanced_hide_account_menu_filter_strings", "", true, parent(HIDE_ACCOUNT_MENU));
public static final BooleanSetting HIDE_HANDLE = new BooleanSetting("revanced_hide_handle", TRUE, true);
@ -167,19 +169,6 @@ public class Settings extends BaseSettings {
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
// PreferenceScreen: General - Miniplayer
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.ORIGINAL, true);
private static final Setting.Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4);
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, MINIPLAYER_ANY_MODERN);
public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_drag_and_drop", TRUE, true, MINIPLAYER_ANY_MODERN);
public static final BooleanSetting MINIPLAYER_HORIZONTAL_DRAG = new BooleanSetting("revanced_miniplayer_horizontal_drag", FALSE, true, new MiniplayerPatch.MiniplayerHorizontalDragAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_EXPAND_CLOSE = new BooleanSetting("revanced_miniplayer_hide_expand_close", FALSE, true, new MiniplayerPatch.MiniplayerHideExpandCloseAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1, MODERN_3));
public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", TRUE, true, MINIPLAYER_TYPE.availability(MODERN_1));
public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, MINIPLAYER_ANY_MODERN);
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, MINIPLAYER_ANY_MODERN);
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, MINIPLAYER_TYPE.availability(MODERN_1));
// PreferenceScreen: General - Navigation Bar
public static final BooleanSetting ENABLE_NARROW_NAVIGATION_BUTTONS = new BooleanSetting("revanced_enable_narrow_navigation_buttons", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_CREATE_BUTTON = new BooleanSetting("revanced_hide_navigation_create_button", TRUE, true);
@ -190,7 +179,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_NAVIGATION_SUBSCRIPTIONS_BUTTON = new BooleanSetting("revanced_hide_navigation_subscriptions_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_LABEL = new BooleanSetting("revanced_hide_navigation_label", FALSE, true);
public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true, "revanced_switch_create_with_notifications_button_user_dialog_message");
public static final BooleanSetting ENABLE_TRANSLUCENT_NAVIGATION_BAR = new BooleanSetting("revanced_enable_translucent_navigation_bar", FALSE, true);
public static final BooleanSetting ENABLE_TRANSLUCENT_NAVIGATION_BAR = new BooleanSetting("revanced_enable_translucent_navigation_bar", FALSE, true, "revanced_enable_translucent_navigation_bar_user_dialog_message");
public static final BooleanSetting HIDE_NAVIGATION_BAR = new BooleanSetting("revanced_hide_navigation_bar", FALSE, true);
// PreferenceScreen: General - Override buttons
@ -204,31 +193,31 @@ public class Settings extends BaseSettings {
, new YouTubeMusicActionsPatch.HookYouTubeMusicPackageNameAvailability());
// PreferenceScreen: General - Settings menu
public static final BooleanSetting HIDE_SETTINGS_MENU_PARENT_TOOLS = new BooleanSetting("revanced_hide_settings_menu_parent_tools", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_GENERAL = new BooleanSetting("revanced_hide_settings_menu_general", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_ACCOUNT = new BooleanSetting("revanced_hide_settings_menu_account", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_DATA_SAVING = new BooleanSetting("revanced_hide_settings_menu_data_saving", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_AUTOPLAY_PLAYBACK = new BooleanSetting("revanced_hide_settings_menu_autoplay_playback", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_VIDEO_QUALITY_PREFERENCES = new BooleanSetting("revanced_hide_settings_menu_video_quality", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_OFFLINE = new BooleanSetting("revanced_hide_settings_menu_offline", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_WATCH_ON_TV = new BooleanSetting("revanced_hide_settings_menu_pair_with_tv", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_MANAGE_ALL_HISTORY = new BooleanSetting("revanced_hide_settings_menu_history", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_YOUR_DATA_IN_YOUTUBE = new BooleanSetting("revanced_hide_settings_menu_your_data", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_PRIVACY = new BooleanSetting("revanced_hide_settings_menu_privacy", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_TRY_EXPERIMENTAL_NEW_FEATURES = new BooleanSetting("revanced_hide_settings_menu_premium_early_access", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_PURCHASES_AND_MEMBERSHIPS = new BooleanSetting("revanced_hide_settings_menu_subscription_product", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_BILLING_AND_PAYMENTS = new BooleanSetting("revanced_hide_settings_menu_billing_and_payment", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_NOTIFICATIONS = new BooleanSetting("revanced_hide_settings_menu_notification", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_CONNECTED_APPS = new BooleanSetting("revanced_hide_settings_menu_connected_accounts", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_LIVE_CHAT = new BooleanSetting("revanced_hide_settings_menu_live_chat", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_CAPTIONS = new BooleanSetting("revanced_hide_settings_menu_captions", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_ACCESSIBILITY = new BooleanSetting("revanced_hide_settings_menu_accessibility", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_ABOUT = new BooleanSetting("revanced_hide_settings_menu_about", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_PARENT_TOOLS = new BooleanSetting("revanced_hide_settings_menu_parent_tools", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_GENERAL = new BooleanSetting("revanced_hide_settings_menu_general", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_ACCOUNT = new BooleanSetting("revanced_hide_settings_menu_account", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_DATA_SAVING = new BooleanSetting("revanced_hide_settings_menu_data_saving", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_AUTOPLAY_PLAYBACK = new BooleanSetting("revanced_hide_settings_menu_autoplay_playback", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_VIDEO_QUALITY_PREFERENCES = new BooleanSetting("revanced_hide_settings_menu_video_quality", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_OFFLINE = new BooleanSetting("revanced_hide_settings_menu_offline", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_WATCH_ON_TV = new BooleanSetting("revanced_hide_settings_menu_pair_with_tv", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_MANAGE_ALL_HISTORY = new BooleanSetting("revanced_hide_settings_menu_history", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_YOUR_DATA_IN_YOUTUBE = new BooleanSetting("revanced_hide_settings_menu_your_data", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_PRIVACY = new BooleanSetting("revanced_hide_settings_menu_privacy", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_TRY_EXPERIMENTAL_NEW_FEATURES = new BooleanSetting("revanced_hide_settings_menu_premium_early_access", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_PURCHASES_AND_MEMBERSHIPS = new BooleanSetting("revanced_hide_settings_menu_subscription_product", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_BILLING_AND_PAYMENTS = new BooleanSetting("revanced_hide_settings_menu_billing_and_payment", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_NOTIFICATIONS = new BooleanSetting("revanced_hide_settings_menu_notification", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_CONNECTED_APPS = new BooleanSetting("revanced_hide_settings_menu_connected_accounts", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_LIVE_CHAT = new BooleanSetting("revanced_hide_settings_menu_live_chat", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_CAPTIONS = new BooleanSetting("revanced_hide_settings_menu_captions", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_ACCESSIBILITY = new BooleanSetting("revanced_hide_settings_menu_accessibility", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_ABOUT = new BooleanSetting("revanced_hide_settings_menu_about", FALSE);
// dummy data
public static final BooleanSetting HIDE_SETTINGS_MENU_YOUTUBE_TV = new BooleanSetting("revanced_hide_settings_menu_youtube_tv", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_PRE_PURCHASE = new BooleanSetting("revanced_hide_settings_menu_pre_purchase", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_POST_PURCHASE = new BooleanSetting("revanced_hide_settings_menu_post_purchase", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_THIRD_PARTY = new BooleanSetting("revanced_hide_settings_menu_third_party", FALSE, true);
public static final BooleanSetting HIDE_SETTINGS_MENU_YOUTUBE_TV = new BooleanSetting("revanced_hide_settings_menu_youtube_tv", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_PRE_PURCHASE = new BooleanSetting("revanced_hide_settings_menu_pre_purchase", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_POST_PURCHASE = new BooleanSetting("revanced_hide_settings_menu_post_purchase", FALSE);
public static final BooleanSetting HIDE_SETTINGS_MENU_THIRD_PARTY = new BooleanSetting("revanced_hide_settings_menu_third_party", FALSE);
// PreferenceScreen: General - Snack bar
public static final BooleanSetting HIDE_SNACK_BAR = new BooleanSetting("revanced_hide_snack_bar", FALSE, true);
@ -289,22 +278,8 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_SHOP_BUTTON = new BooleanSetting("revanced_hide_shop_button", FALSE);
public static final BooleanSetting HIDE_THANKS_BUTTON = new BooleanSetting("revanced_hide_thanks_button", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_0 = new BooleanSetting("revanced_hide_action_button_index_0", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_1 = new BooleanSetting("revanced_hide_action_button_index_1", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_2 = new BooleanSetting("revanced_hide_action_button_index_2", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_3 = new BooleanSetting("revanced_hide_action_button_index_3", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_4 = new BooleanSetting("revanced_hide_action_button_index_4", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_5 = new BooleanSetting("revanced_hide_action_button_index_5", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_6 = new BooleanSetting("revanced_hide_action_button_index_6", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_7 = new BooleanSetting("revanced_hide_action_button_index_7", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_0 = new BooleanSetting("revanced_hide_action_button_index_live_0", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_1 = new BooleanSetting("revanced_hide_action_button_index_live_1", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_2 = new BooleanSetting("revanced_hide_action_button_index_live_2", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_3 = new BooleanSetting("revanced_hide_action_button_index_live_3", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_4 = new BooleanSetting("revanced_hide_action_button_index_live_4", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_5 = new BooleanSetting("revanced_hide_action_button_index_live_5", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_6 = new BooleanSetting("revanced_hide_action_button_index_live_6", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX_LIVE_7 = new BooleanSetting("revanced_hide_action_button_index_live_7", FALSE);
public static final BooleanSetting HIDE_ACTION_BUTTON_INDEX = new BooleanSetting("revanced_hide_action_button_index", FALSE, true);
public static final IntegerSetting REMIX_BUTTON_INDEX = new IntegerSetting("revanced_remix_button_index", 3, true, parent(HIDE_ACTION_BUTTON_INDEX));
// PreferenceScreen: Player - Ambient mode
public static final BooleanSetting BYPASS_AMBIENT_MODE_RESTRICTIONS = new BooleanSetting("revanced_bypass_ambient_mode_restrictions", FALSE);
@ -357,7 +332,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_PLAYER_FLYOUT_MENU_YT_MUSIC = new BooleanSetting("revanced_hide_player_flyout_menu_listen_with_youtube_music", TRUE);
// PreferenceScreen: Player - Fullscreen
public static final BooleanSetting DISABLE_ENGAGEMENT_PANEL = new BooleanSetting("revanced_disable_engagement_panel", FALSE, true);
public static final BooleanSetting DISABLE_ENGAGEMENT_PANEL = new BooleanSetting("revanced_disable_engagement_panel", FALSE);
public static final BooleanSetting ENTER_FULLSCREEN = new BooleanSetting("revanced_enter_fullscreen", FALSE);
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
public static final BooleanSetting SHOW_VIDEO_TITLE_SECTION = new BooleanSetting("revanced_show_video_title_section", TRUE, true, parent(DISABLE_ENGAGEMENT_PANEL));
@ -389,6 +364,20 @@ public class Settings extends BaseSettings {
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_SEEK_UNDO = new BooleanSetting("revanced_disable_haptic_feedback_seek_undo", FALSE);
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_ZOOM = new BooleanSetting("revanced_disable_haptic_feedback_zoom", FALSE);
// PreferenceScreen: Player - Miniplayer
public static final BooleanSetting DISABLE_RESUMING_MINIPLAYER = new BooleanSetting("revanced_disable_resuming_miniplayer", FALSE, true);
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.DEFAULT, true);
private static final Setting.Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4);
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, MINIPLAYER_ANY_MODERN);
public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_drag_and_drop", TRUE, true, MINIPLAYER_ANY_MODERN);
public static final BooleanSetting MINIPLAYER_HORIZONTAL_DRAG = new BooleanSetting("revanced_miniplayer_horizontal_drag", FALSE, true, new MiniplayerPatch.MiniplayerHorizontalDragAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_OVERLAY_BUTTONS = new BooleanSetting("revanced_miniplayer_hide_overlay_buttons", FALSE, true, new MiniplayerPatch.MiniplayerHideOverlayButtonsAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1, MODERN_3, MODERN_4));
public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", TRUE, true, MINIPLAYER_TYPE.availability(MODERN_1));
public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, MINIPLAYER_ANY_MODERN);
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, MINIPLAYER_ANY_MODERN);
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, MINIPLAYER_TYPE.availability(MODERN_1));
// PreferenceScreen: Player - Player buttons
public static final BooleanSetting HIDE_PLAYER_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_player_autoplay_button", TRUE, true);
public static final BooleanSetting HIDE_PLAYER_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_player_captions_button", FALSE, true);
@ -416,11 +405,12 @@ public class Settings extends BaseSettings {
public static final BooleanSetting REPLACE_TIME_STAMP_ACTION = new BooleanSetting("revanced_replace_time_stamp_action", TRUE, true, parent(APPEND_TIME_STAMP_INFORMATION));
public static final BooleanSetting DISABLE_SEEKBAR_CHAPTERS = new BooleanSetting("revanced_disable_seekbar_chapters", FALSE, true);
public static final BooleanSetting ENABLE_CUSTOM_SEEKBAR_COLOR = new BooleanSetting("revanced_enable_custom_seekbar_color", FALSE, true);
public static final StringSetting CUSTOM_SEEKBAR_COLOR_VALUE = new StringSetting("revanced_custom_seekbar_color_value", "#FF0033", true, parent(ENABLE_CUSTOM_SEEKBAR_COLOR));
public static final StringSetting CUSTOM_SEEKBAR_COLOR_PRIMARY = new StringSetting("revanced_custom_seekbar_color_primary", "#FF0033", true, parent(ENABLE_CUSTOM_SEEKBAR_COLOR));
public static final StringSetting CUSTOM_SEEKBAR_COLOR_ACCENT = new StringSetting("revanced_custom_seekbar_color_accent", "#FF2791", true, parent(ENABLE_CUSTOM_SEEKBAR_COLOR));
public static final BooleanSetting ENABLE_SEEKBAR_TAPPING = new BooleanSetting("revanced_enable_seekbar_tapping", TRUE);
public static final BooleanSetting HIDE_SEEKBAR_CHAPTER_LABEL = new BooleanSetting("revanced_hide_seekbar_chapter_label", FALSE, true);
public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", FALSE, true);
public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE);
public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE, true);
public static final BooleanSetting HIDE_TIME_STAMP = new BooleanSetting("revanced_hide_time_stamp", FALSE, true);
public static final BooleanSetting RESTORE_OLD_SEEKBAR_THUMBNAILS = new BooleanSetting("revanced_restore_old_seekbar_thumbnails",
PatchStatus.OldSeekbarThumbnailsDefaultBoolean(), true);
@ -439,7 +429,6 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_TRANSCRIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", FALSE);
public static final BooleanSetting DISABLE_VIDEO_DESCRIPTION_INTERACTION = new BooleanSetting("revanced_disable_video_description_interaction", FALSE, true);
public static final BooleanSetting EXPAND_VIDEO_DESCRIPTION = new BooleanSetting("revanced_expand_video_description", FALSE, true);
public static final StringSetting EXPAND_VIDEO_DESCRIPTION_STRINGS = new StringSetting("revanced_expand_video_description_strings", str("revanced_expand_video_description_strings_default_value"), true, parent(EXPAND_VIDEO_DESCRIPTION));
// PreferenceScreen: Shorts
@ -540,7 +529,7 @@ public class Settings extends BaseSettings {
@Deprecated // Patch is obsolete and no longer works with 19.09+
public static final BooleanSetting DISABLE_HDR_AUTO_BRIGHTNESS = new BooleanSetting("revanced_disable_hdr_auto_brightness", TRUE, true, parent(ENABLE_SWIPE_BRIGHTNESS));
public static final BooleanSetting DISABLE_SWIPE_TO_SWITCH_VIDEO = new BooleanSetting("revanced_disable_swipe_to_switch_video", TRUE, true);
public static final BooleanSetting DISABLE_SWIPE_TO_ENTER_FULLSCREEN_MODE_BELOW_THE_PLAYER = new BooleanSetting("revanced_disable_swipe_to_enter_fullscreen_mode_below_the_player", TRUE, true);
public static final BooleanSetting DISABLE_SWIPE_TO_ENTER_FULLSCREEN_MODE_BELOW_THE_PLAYER = new BooleanSetting("revanced_disable_swipe_to_enter_fullscreen_mode_below_the_player", FALSE, true);
public static final BooleanSetting DISABLE_SWIPE_TO_ENTER_FULLSCREEN_MODE_IN_THE_PLAYER = new BooleanSetting("revanced_disable_swipe_to_enter_fullscreen_mode_in_the_player", FALSE, true);
public static final BooleanSetting DISABLE_SWIPE_TO_EXIT_FULLSCREEN_MODE = new BooleanSetting("revanced_disable_swipe_to_exit_fullscreen_mode", FALSE, true);
public static final BooleanSetting SWIPE_BRIGHTNESS_AUTO = new BooleanSetting("revanced_swipe_brightness_auto", TRUE, false, false);
@ -587,7 +576,6 @@ public class Settings extends BaseSettings {
public static final LongSetting DOUBLE_BACK_TO_CLOSE_TIMEOUT = new LongSetting("revanced_double_back_to_close_timeout", 2000L);
// PreferenceScreen: Miscellaneous - Watch history
public static final EnumSetting<WatchHistoryType> WATCH_HISTORY_TYPE = new EnumSetting<>("revanced_watch_history_type", WatchHistoryType.REPLACE);
// PreferenceScreen: Miscellaneous - Spoof streaming data
@ -598,7 +586,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting RYD_DISLIKE_PERCENTAGE = new BooleanSetting("ryd_dislike_percentage", FALSE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_COMPACT_LAYOUT = new BooleanSetting("ryd_compact_layout", FALSE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_ESTIMATED_LIKE = new BooleanSetting("ryd_estimated_like", FALSE, true, parent(RYD_ENABLED));
public static final BooleanSetting RYD_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("ryd_toast_on_connection_error", FALSE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("ryd_toast_on_connection_error", TRUE, parent(RYD_ENABLED));
// PreferenceScreen: SponsorBlock
@ -613,7 +601,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting SB_COMPACT_SKIP_BUTTON = new BooleanSetting("sb_compact_skip_button", FALSE, parent(SB_ENABLED));
public static final BooleanSetting SB_AUTO_HIDE_SKIP_BUTTON = new BooleanSetting("sb_auto_hide_skip_button", TRUE, parent(SB_ENABLED));
public static final BooleanSetting SB_TOAST_ON_SKIP = new BooleanSetting("sb_toast_on_skip", TRUE, parent(SB_ENABLED));
public static final BooleanSetting SB_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("sb_toast_on_connection_error", FALSE, parent(SB_ENABLED));
public static final BooleanSetting SB_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("sb_toast_on_connection_error", TRUE, parent(SB_ENABLED));
public static final BooleanSetting SB_TRACK_SKIP_COUNT = new BooleanSetting("sb_track_skip_count", TRUE, parent(SB_ENABLED));
public static final FloatSetting SB_SEGMENT_MIN_DURATION = new FloatSetting("sb_min_segment_duration", 0F, parent(SB_ENABLED));
public static final BooleanSetting SB_VIDEO_LENGTH_WITHOUT_SEGMENTS = new BooleanSetting("sb_video_length_without_segments", FALSE, parent(SB_ENABLED));

View File

@ -2,8 +2,6 @@ package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.utils.StringRef.str;
import static app.revanced.extension.shared.utils.Utils.isSDKAbove;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_1;
import static app.revanced.extension.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_3;
import static app.revanced.extension.youtube.utils.ExtendedUtils.isSpoofingToLessThan;
import android.preference.Preference;
@ -11,7 +9,6 @@ import android.preference.SwitchPreference;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.youtube.patches.general.LayoutSwitchPatch;
import app.revanced.extension.youtube.patches.general.MiniplayerPatch;
import app.revanced.extension.youtube.patches.utils.PatchStatus;
import app.revanced.extension.youtube.patches.utils.ReturnYouTubeDislikePatch;
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
@ -48,7 +45,6 @@ public class ReVancedSettingsPreference extends ReVancedPreferenceFragment {
AmbientModePreferenceLinks();
ExternalDownloaderPreferenceLinks();
FullScreenPanelPreferenceLinks();
MiniPlayerPreferenceLinks();
NavigationPreferenceLinks();
RYDPreferenceLinks();
SeekBarPreferenceLinks();
@ -140,22 +136,6 @@ public class ReVancedSettingsPreference extends ReVancedPreferenceFragment {
);
}
/**
* Enable/Disable Preference related to Miniplayer settings
*/
private static void MiniPlayerPreferenceLinks() {
final MiniplayerPatch.MiniplayerType CURRENT_TYPE = Settings.MINIPLAYER_TYPE.get();
final boolean available =
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3) &&
!Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get() &&
!Settings.MINIPLAYER_DRAG_AND_DROP.get();
enableDisablePreferences(
!available,
Settings.MINIPLAYER_HIDE_EXPAND_CLOSE
);
}
/**
* Enable/Disable Preference related to Navigation settings
*/

View File

@ -62,7 +62,7 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
}
private void updateUI() {
final String clientName = Settings.SPOOF_STREAMING_DATA_TYPE.get().name().toLowerCase();
final String clientName = Settings.SPOOF_STREAMING_DATA_CLIENT.get().name().toLowerCase();
final String summaryTextKey = "revanced_spoof_streaming_data_side_effects_" + clientName;
setSummary(str(summaryTextKey));

View File

@ -8,9 +8,9 @@ import android.preference.Preference;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import app.revanced.extension.shared.patches.WatchHistoryPatch.WatchHistoryType;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.utils.Utils;
import app.revanced.extension.youtube.patches.misc.WatchHistoryPatch.WatchHistoryType;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings({"deprecation", "unused"})

View File

@ -0,0 +1,46 @@
package app.revanced.extension.youtube.shared;
import androidx.annotation.Nullable;
import java.util.concurrent.atomic.AtomicReference;
import app.revanced.extension.shared.utils.Logger;
@SuppressWarnings("unused")
public final class EngagementPanel {
private static final AtomicReference<String> engagementPanelId = new AtomicReference<>("");
/**
* Injection point.
*/
public static void setId(@Nullable String panelId) {
if (panelId != null) {
Logger.printDebug(() -> "engagementPanel open\npanelId: " + panelId);
engagementPanelId.set(panelId);
}
}
/**
* Injection point.
*/
public static void hide() {
String panelId = getId();
if (!panelId.isEmpty()) {
Logger.printDebug(() -> "engagementPanel closed\npanelId: " + panelId);
engagementPanelId.set("");
}
}
public static boolean isOpen() {
return !getId().isEmpty();
}
public static boolean isDescription() {
return getId().equals("video-description-ep-identifier");
}
public static String getId() {
return engagementPanelId.get();
}
}

View File

@ -137,6 +137,14 @@ enum class PlayerType {
return this == WATCH_WHILE_MAXIMIZED || this == WATCH_WHILE_FULLSCREEN
}
/**
* Check if the current player type is
* [WATCH_WHILE_MAXIMIZED], [WATCH_WHILE_FULLSCREEN], [WATCH_WHILE_PICTURE_IN_PICTURE]
*/
fun isMaximizedOrFullscreenOrPiP(): Boolean {
return isMaximizedOrFullscreen() || this == WATCH_WHILE_PICTURE_IN_PICTURE
}
/**
* Check if the current player type is
* [WATCH_WHILE_FULLSCREEN], [WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN].

View File

@ -12,6 +12,7 @@ import app.revanced.extension.shared.utils.PackageUtils;
import app.revanced.extension.youtube.settings.Settings;
public class ExtendedUtils extends PackageUtils {
@SuppressWarnings("unused")
public static final boolean IS_19_17_OR_GREATER = getAppVersionName().compareTo("19.17.00") >= 0;
public static final boolean IS_19_20_OR_GREATER = getAppVersionName().compareTo("19.20.00") >= 0;
public static final boolean IS_19_21_OR_GREATER = getAppVersionName().compareTo("19.21.00") >= 0;

View File

@ -0,0 +1,19 @@
package com.facebook.litho;
import android.content.Context;
import android.view.ViewGroup;
/**
* "CompileOnly" class
* <p>
* This class will not be included and "replaced" by the real package's class.
*/
public class ComponentHost extends ViewGroup {
public ComponentHost(Context context) {
super(context);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
}
}

View File

@ -0,0 +1,21 @@
extension {
name = "extensions/all/connectivity/wifi/spoof/spoof-wifi.rve"
}
android {
namespace = "app.revanced.extension"
compileSdk = 34
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
dependencies {
compileOnly(libs.annotation)
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

View File

@ -0,0 +1,431 @@
package app.revanced.extension.all.connectivity.wifi.spoof;
import android.app.PendingIntent;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.os.Build;
import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@RequiresApi(23)
@SuppressWarnings({"deprecation", "unused"})
public class SpoofWifiPatch {
// Used to check what the (real or fake) active network is (take a look at `hasTransport`).
private static ConnectivityManager CONNECTIVITY_MANAGER;
// If Wifi is not enabled, these are types that would pretend to be Wifi for android.net.Network (lower index = higher priority).
// This does not apply to android.net.NetworkInfo, because we can pretend that Wifi is always active there.
//
// VPN should be a fallback, because Reverse Tethering uses VPN.
private static final int[] FAKE_FALLBACK_NETWORKS = { NetworkCapabilities.TRANSPORT_ETHERNET, NetworkCapabilities.TRANSPORT_VPN };
// In order to initialize our own ConnectivityManager, if it isn't initialized yet.
public static Object getSystemService(Context context, String name) {
Object result = context.getSystemService(name);
if (CONNECTIVITY_MANAGER == null) {
if (context.getSystemService(Context.CONNECTIVITY_SERVICE) instanceof ConnectivityManager connectivityManager) {
CONNECTIVITY_MANAGER = connectivityManager;
}
}
return result;
}
// In order to initialize our own ConnectivityManager, if it isn't initialized yet.
public static Object getSystemService(Context context, Class<?> serviceClass) {
Object result = context.getSystemService(serviceClass);
if (CONNECTIVITY_MANAGER == null) {
if (context.getSystemService(Context.CONNECTIVITY_SERVICE) instanceof ConnectivityManager connectivityManager) {
CONNECTIVITY_MANAGER = connectivityManager;
}
}
return result;
}
// Simply always return Wifi as active network.
public static NetworkInfo getActiveNetworkInfo(ConnectivityManager connectivityManager) {
for (NetworkInfo networkInfo : connectivityManager.getAllNetworkInfo()) {
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
return networkInfo;
}
}
return connectivityManager.getActiveNetworkInfo();
}
// Pretend Wifi is always connected.
public static boolean isConnected(NetworkInfo networkInfo) {
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
return true;
}
return networkInfo.isConnected();
}
// Pretend Wifi is always connected.
public static boolean isConnectedOrConnecting(NetworkInfo networkInfo) {
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
return true;
}
return networkInfo.isConnectedOrConnecting();
}
// Pretend Wifi is always available.
public static boolean isAvailable(NetworkInfo networkInfo) {
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
return true;
}
return networkInfo.isAvailable();
}
// Pretend Wifi is always connected.
public static NetworkInfo.State getState(NetworkInfo networkInfo) {
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
return NetworkInfo.State.CONNECTED;
}
return networkInfo.getState();
}
// Pretend Wifi is always connected.
public static NetworkInfo.DetailedState getDetailedState(NetworkInfo networkInfo) {
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
return NetworkInfo.DetailedState.CONNECTED;
}
return networkInfo.getDetailedState();
}
// Pretend Wifi is enabled, so connection isn't metered.
public static boolean isActiveNetworkMetered(ConnectivityManager connectivityManager) {
return false;
}
// Returns the Wifi network, if Wifi is enabled.
// Otherwise if one of our fallbacks has a connection, return them.
// And as a last resort, return the default active network.
public static Network getActiveNetwork(ConnectivityManager connectivityManager) {
Network[] prioritizedNetworks = new Network[FAKE_FALLBACK_NETWORKS.length];
for (Network network : connectivityManager.getAllNetworks()) {
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
if (networkCapabilities == null) {
continue;
}
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return network;
}
if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
for (int i = 0; i < FAKE_FALLBACK_NETWORKS.length; i++) {
int transportType = FAKE_FALLBACK_NETWORKS[i];
if (networkCapabilities.hasTransport(transportType)) {
prioritizedNetworks[i] = network;
break;
}
}
}
}
for (Network network : prioritizedNetworks) {
if (network != null) {
return network;
}
}
return connectivityManager.getActiveNetwork();
}
// If the given network is a real or fake Wifi connection, return a Wifi network.
// Otherwise fallback to default implementation.
public static NetworkInfo getNetworkInfo(ConnectivityManager connectivityManager, Network network) {
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
if (networkCapabilities != null && hasTransport(networkCapabilities, NetworkCapabilities.TRANSPORT_WIFI)) {
for (NetworkInfo networkInfo : connectivityManager.getAllNetworkInfo()) {
if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
return networkInfo;
}
}
}
return connectivityManager.getNetworkInfo(network);
}
// If we are checking if the NetworkCapabilities use Wifi, return yes if
// - it is a real Wifi connection,
// - or the NetworkCapabilities are from a network pretending being a Wifi network.
// Otherwise fallback to default implementation.
public static boolean hasTransport(NetworkCapabilities networkCapabilities, int transportType) {
if (transportType == NetworkCapabilities.TRANSPORT_WIFI) {
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
return true;
}
if (CONNECTIVITY_MANAGER != null) {
Network activeNetwork = getActiveNetwork(CONNECTIVITY_MANAGER);
NetworkCapabilities activeNetworkCapabilities = CONNECTIVITY_MANAGER.getNetworkCapabilities(activeNetwork);
if (activeNetworkCapabilities != null) {
for (int fallbackTransportType : FAKE_FALLBACK_NETWORKS) {
if (activeNetworkCapabilities.hasTransport(fallbackTransportType) && networkCapabilities.hasTransport(fallbackTransportType)) {
return true;
}
}
}
}
}
return networkCapabilities.hasTransport(transportType);
}
// If the given network is a real or fake Wifi connection, pretend it has a connection (and some other things).
public static boolean hasCapability(NetworkCapabilities networkCapabilities, int capability) {
if (hasTransport(networkCapabilities, NetworkCapabilities.TRANSPORT_WIFI) && (
capability == NetworkCapabilities.NET_CAPABILITY_INTERNET
|| capability == NetworkCapabilities.NET_CAPABILITY_FOREGROUND
|| capability == NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
|| capability == NetworkCapabilities.NET_CAPABILITY_NOT_METERED
|| capability == NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED
|| capability == NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
|| capability == NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
|| capability == NetworkCapabilities.NET_CAPABILITY_NOT_VPN
|| capability == NetworkCapabilities.NET_CAPABILITY_TRUSTED
|| capability == NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
return true;
}
return networkCapabilities.hasCapability(capability);
}
// If it waits for Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
@RequiresApi(31)
public static void registerBestMatchingNetworkCallback(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback, Handler handler) {
Utils.networkCallback(
connectivityManager,
Utils.Option.of(request),
Utils.Option.of(networkCallback),
Utils.Option.empty(),
Utils.Option.of(handler),
() -> connectivityManager.registerBestMatchingNetworkCallback(request, networkCallback, handler)
);
}
// If it waits for Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
@RequiresApi(24)
public static void registerDefaultNetworkCallback(ConnectivityManager connectivityManager, ConnectivityManager.NetworkCallback networkCallback) {
Utils.networkCallback(
connectivityManager,
Utils.Option.empty(),
Utils.Option.of(networkCallback),
Utils.Option.empty(),
Utils.Option.empty(),
() -> connectivityManager.registerDefaultNetworkCallback(networkCallback)
);
}
// If it waits for Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
@RequiresApi(26)
public static void registerDefaultNetworkCallback(ConnectivityManager connectivityManager, ConnectivityManager.NetworkCallback networkCallback, Handler handler) {
Utils.networkCallback(
connectivityManager,
Utils.Option.empty(),
Utils.Option.of(networkCallback),
Utils.Option.empty(),
Utils.Option.of(handler),
() -> connectivityManager.registerDefaultNetworkCallback(networkCallback, handler)
);
}
// If it waits for Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
public static void registerNetworkCallback(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback) {
Utils.networkCallback(
connectivityManager,
Utils.Option.of(request),
Utils.Option.of(networkCallback),
Utils.Option.empty(),
Utils.Option.empty(),
() -> connectivityManager.registerNetworkCallback(request, networkCallback)
);
}
// If it waits for Wifi connectivity, pretend it is fulfilled immediately.
public static void registerNetworkCallback(ConnectivityManager connectivityManager, NetworkRequest request, PendingIntent operation) {
Utils.networkCallback(
connectivityManager,
Utils.Option.of(request),
Utils.Option.empty(),
Utils.Option.of(operation),
Utils.Option.empty(),
() -> connectivityManager.registerNetworkCallback(request, operation)
);
}
// If it waits for Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
@RequiresApi(26)
public static void registerNetworkCallback(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback, Handler handler) {
Utils.networkCallback(
connectivityManager,
Utils.Option.of(request),
Utils.Option.of(networkCallback),
Utils.Option.empty(),
Utils.Option.of(handler),
() -> connectivityManager.registerNetworkCallback(request, networkCallback, handler)
);
}
// If it requests Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
public static void requestNetwork(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback) {
Utils.networkCallback(
connectivityManager,
Utils.Option.of(request),
Utils.Option.of(networkCallback),
Utils.Option.empty(),
Utils.Option.empty(),
() -> connectivityManager.requestNetwork(request, networkCallback)
);
}
// If it requests Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
@RequiresApi(26)
public static void requestNetwork(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback, int timeoutMs) {
Utils.networkCallback(
connectivityManager,
Utils.Option.of(request),
Utils.Option.of(networkCallback),
Utils.Option.empty(),
Utils.Option.empty(),
() -> connectivityManager.requestNetwork(request, networkCallback, timeoutMs)
);
}
// If it requests Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
@RequiresApi(26)
public static void requestNetwork(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback, Handler handler) {
Utils.networkCallback(
connectivityManager,
Utils.Option.of(request),
Utils.Option.of(networkCallback),
Utils.Option.empty(),
Utils.Option.of(handler),
() -> connectivityManager.requestNetwork(request, networkCallback, handler)
);
}
// If it requests Wifi connectivity, pretend it is fulfilled immediately.
public static void requestNetwork(ConnectivityManager connectivityManager, NetworkRequest request, PendingIntent operation) {
Utils.networkCallback(
connectivityManager,
Utils.Option.of(request),
Utils.Option.empty(),
Utils.Option.of(operation),
Utils.Option.empty(),
() -> connectivityManager.requestNetwork(request, operation)
);
}
// If it requests Wifi connectivity, pretend it is fulfilled immediately if we have an active network.
@RequiresApi(26)
public static void requestNetwork(ConnectivityManager connectivityManager, NetworkRequest request, ConnectivityManager.NetworkCallback networkCallback, Handler handler, int timeoutMs) {
Utils.networkCallback(
connectivityManager,
Utils.Option.of(request),
Utils.Option.of(networkCallback),
Utils.Option.empty(),
Utils.Option.of(handler),
() -> connectivityManager.requestNetwork(request, networkCallback, handler, timeoutMs)
);
}
public static void unregisterNetworkCallback(ConnectivityManager connectivityManager, ConnectivityManager.NetworkCallback networkCallback) {
try {
connectivityManager.unregisterNetworkCallback(networkCallback);
} catch (IllegalArgumentException ignore) {
// ignore: NetworkCallback was not registered
}
}
public static void unregisterNetworkCallback(ConnectivityManager connectivityManager, PendingIntent operation) {
try {
connectivityManager.unregisterNetworkCallback(operation);
} catch (IllegalArgumentException ignore) {
// ignore: PendingIntent was not registered
}
}
private static class Utils {
private static class Option<T> {
private final T value;
private final boolean isPresent;
private Option(T value, boolean isPresent) {
this.value = value;
this.isPresent = isPresent;
}
private static <T> Option<T> of(T value) {
return new Option<>(value, true);
}
private static <T> Option<T> empty() {
return new Option<>(null, false);
}
}
/**
* @return whether the device's API level is higher than a specific SDK version.
*/
private static boolean isSDKAbove(int sdk) {
return Build.VERSION.SDK_INT >= sdk;
}
private static void networkCallback(
ConnectivityManager connectivityManager,
Option<NetworkRequest> request,
Option<ConnectivityManager.NetworkCallback> networkCallback,
Option<PendingIntent> operation,
Option<Handler> handler,
Runnable fallback
) {
if(!request.isPresent || requestsWifiNetwork(request.value)) {
Runnable runnable = null;
if (networkCallback.isPresent && networkCallback.value != null) {
Network network = activeWifiNetwork(connectivityManager);
if (network != null) {
runnable = () -> networkCallback.value.onAvailable(network);
}
} else if (operation.isPresent && operation.value != null) {
runnable = () -> {
try {
operation.value.send();
} catch (PendingIntent.CanceledException ignore) {}
};
}
if (runnable != null) {
if (handler.isPresent) {
if (handler.value != null) {
handler.value.post(runnable);
return;
}
} else {
runnable.run();
return;
}
}
}
fallback.run();
}
// Returns an active (maybe fake) Wifi network if there is one, otherwise null.
private static Network activeWifiNetwork(ConnectivityManager connectivityManager) {
Network network = getActiveNetwork(connectivityManager);
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
if (networkCapabilities != null && hasTransport(networkCapabilities, NetworkCapabilities.TRANSPORT_WIFI)) {
return network;
}
return null;
}
// Whether a Wifi network with connection is requested.
private static boolean requestsWifiNetwork(@Nullable NetworkRequest request) {
if (request != null && isSDKAbove(28)) {
return request.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
&& (request.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|| request.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED));
}
return false;
}
}
}

View File

@ -4,5 +4,5 @@ org.gradle.parallel = true
android.useAndroidX = true
kotlin.code.style = official
kotlin.jvm.target.validation.mode = IGNORE
version = 5.3.1
version = 5.4.1

View File

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=7a00d51fb93147819aab76024feece20b6b84e420694101f276be952e08bef03
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@ -58,7 +58,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -99,7 +99,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -138,7 +138,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -171,7 +171,7 @@
"Settings for YouTube",
"ResourcePatch",
"BytecodePatch",
"BytecodePatch"
"ResourcePatch"
],
"compatiblePackages": {
"com.google.android.youtube": [
@ -192,7 +192,10 @@
"use": false,
"dependencies": [],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": [
{
@ -247,7 +250,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -289,7 +292,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -445,7 +448,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": [
@ -491,7 +494,10 @@
"use": false,
"dependencies": [],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": [
{
@ -558,7 +564,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": [
@ -670,7 +676,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": [
@ -702,7 +708,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": [
@ -744,6 +750,7 @@
"BytecodePatch",
"BytecodePatch",
"BytecodePatch",
"BytecodePatch",
"ResourcePatch",
"ResourcePatch"
],
@ -795,7 +802,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -836,7 +843,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -877,7 +884,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -940,7 +947,27 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
},
{
"name": "Disable resuming Miniplayer on startup",
"description": "Adds an option to disable the Miniplayer \u0027Continue watching\u0027 from resuming on app startup.",
"use": true,
"dependencies": [
"Settings for YouTube"
],
"compatiblePackages": {
"com.google.android.youtube": [
"18.29.38",
"18.33.40",
"18.38.44",
"18.48.39",
"19.05.36",
"19.16.39",
"19.44.39"
]
},
"options": []
@ -974,7 +1001,10 @@
"Settings for Reddit"
],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": []
},
@ -1015,7 +1045,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -1056,7 +1086,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -1117,7 +1147,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -1144,7 +1174,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -1212,7 +1242,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": [
@ -1336,7 +1366,10 @@
"Settings for Reddit"
],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": []
},
@ -1360,6 +1393,26 @@
},
"options": []
},
{
"name": "Hide accessibility controls dialog",
"description": "Removes, at compile time, accessibility controls dialog \u0027Turn on accessibility controls for the video player?\u0027.",
"use": true,
"dependencies": [
"Settings for YouTube"
],
"compatiblePackages": {
"com.google.android.youtube": [
"18.29.38",
"18.33.40",
"18.38.44",
"18.48.39",
"19.05.36",
"19.16.39",
"19.44.39"
]
},
"options": []
},
{
"name": "Hide account components",
"description": "Adds options to hide components related to the account menu.",
@ -1376,7 +1429,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -1390,6 +1443,7 @@
"BytecodePatch",
"ResourcePatch",
"BytecodePatch",
"BytecodePatch",
"ResourcePatch"
],
"compatiblePackages": {
@ -1400,7 +1454,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -1412,6 +1466,7 @@
"dependencies": [
"Settings for YouTube",
"BytecodePatch",
"BytecodePatch",
"BytecodePatch"
],
"compatiblePackages": {
@ -1425,17 +1480,7 @@
"19.44.39"
]
},
"options": [
{
"key": "hideActionButtonByIndex",
"title": "Hide action buttons by index",
"description": "Add an option to hide action buttons by index.\n\nThis setting is still experimental, so use it only for debugging purposes.",
"required": true,
"type": "kotlin.Boolean",
"default": false,
"values": null
}
]
"options": []
},
{
"name": "Hide ads",
@ -1446,6 +1491,8 @@
"BytecodePatch",
"BytecodePatch",
"Navigation bar components",
"BytecodePatch",
"ResourcePatch",
"ResourcePatch"
],
"compatiblePackages": {
@ -1456,7 +1503,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -1466,12 +1513,13 @@
"description": "Adds options to hide ads.",
"use": true,
"dependencies": [
"Settings for Reddit",
"ResourcePatch",
"BytecodePatch"
"Settings for Reddit"
],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": []
},
@ -1590,7 +1638,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -1628,7 +1676,10 @@
"Settings for Reddit"
],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": []
},
@ -1648,7 +1699,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -1714,7 +1765,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -1727,7 +1778,10 @@
"Settings for Reddit"
],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": []
},
@ -1891,7 +1945,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -1929,7 +1983,10 @@
"BytecodePatch"
],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": []
},
@ -1942,7 +1999,10 @@
"BytecodePatch"
],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": []
},
@ -2057,7 +2117,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -2076,7 +2136,8 @@
"BytecodePatch",
"BytecodePatch",
"BytecodePatch",
"ResourcePatch"
"ResourcePatch",
"BytecodePatch"
],
"compatiblePackages": {
"com.google.android.youtube": [
@ -2097,7 +2158,10 @@
"use": true,
"dependencies": [],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": []
},
@ -2116,7 +2180,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -2127,7 +2191,8 @@
"use": true,
"dependencies": [
"BytecodePatch",
"Settings for YouTube"
"Settings for YouTube",
"ResourcePatch"
],
"compatiblePackages": {
"com.google.android.youtube": [
@ -2150,7 +2215,10 @@
"Settings for Reddit"
],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": []
},
@ -2170,7 +2238,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -2211,7 +2279,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -2232,7 +2300,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -2278,7 +2346,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -2320,7 +2388,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -2333,7 +2401,10 @@
"Settings for Reddit"
],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": []
},
@ -2393,17 +2464,23 @@
"BytecodePatch"
],
"compatiblePackages": {
"com.reddit.frontpage": null
"com.reddit.frontpage": [
"2024.17.0",
"2025.05.1"
]
},
"options": [
{
"key": "settingsLabel",
"key": "rvxSettingsLabel",
"title": "RVX settings menu name",
"description": "The name of the RVX settings menu.",
"required": true,
"type": "kotlin.String",
"default": "ReVanced Extended",
"values": null
"default": "RVX",
"values": {
"ReVanced Extended": "ReVanced Extended",
"RVX": "RVX"
}
}
]
},
@ -2413,7 +2490,7 @@
"use": true,
"dependencies": [
"BytecodePatch",
"BytecodePatch",
"ResourcePatch",
"ResourcePatch",
"BytecodePatch",
"BytecodePatch"
@ -2463,13 +2540,16 @@
}
},
{
"key": "settingsLabel",
"key": "rvxSettingsLabel",
"title": "RVX settings label",
"description": "The name of the RVX settings menu.",
"required": true,
"type": "kotlin.String",
"default": "ReVanced Extended",
"values": null
"default": "RVX",
"values": {
"ReVanced Extended": "ReVanced Extended",
"RVX": "RVX"
}
}
]
},
@ -2488,18 +2568,21 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": [
{
"key": "settingsLabel",
"key": "rvxSettingsLabel",
"title": "RVX settings label",
"description": "The name of the RVX settings menu.",
"required": true,
"type": "kotlin.String",
"default": "ReVanced Extended",
"values": null
"default": "RVX",
"values": {
"ReVanced Extended": "ReVanced Extended",
"RVX": "RVX"
}
}
]
},
@ -2566,6 +2649,15 @@
"default": "8.0dip",
"values": null
},
{
"key": "applyCornerRadiusToPlaylistBottomBar",
"title": "Apply corner radius to playlist bottom bar",
"description": "Whether to apply the same corner radius to the bottom bar of the playlist as the snack bar.",
"required": true,
"type": "kotlin.Boolean",
"default": false,
"values": null
},
{
"key": "darkThemeBackgroundColor",
"title": "Dark theme background color",
@ -2635,7 +2727,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -2672,21 +2764,27 @@
}
]
},
{
"name": "Spoof Wi-Fi connection",
"description": "Spoofs an existing Wi-Fi connection.",
"use": false,
"dependencies": [
"BytecodePatch"
],
"compatiblePackages": null,
"options": []
},
{
"name": "Spoof app version",
"description": "Adds options to spoof the YouTube Music client version. This can remove the radio mode restriction in Canadian regions or disable real-time lyrics.",
"description": "Adds options to spoof the YouTube Music client version. This can be used to restore old UI elements and features.",
"use": true,
"dependencies": [
"BytecodePatch",
"Restore old style library shelf",
"Settings for YouTube Music",
"ResourcePatch"
],
"compatiblePackages": {
"com.google.android.apps.youtube.music": [
"6.20.51",
"6.29.59",
"6.42.55",
"6.51.53",
"7.16.53"
]
@ -2719,29 +2817,12 @@
{
"name": "Spoof client",
"description": "Adds options to spoof the client to allow playback.",
"use": false,
"dependencies": [
"Settings for YouTube Music",
"ResourcePatch"
],
"compatiblePackages": {
"com.google.android.apps.youtube.music": [
"6.20.51",
"6.29.59",
"6.42.55",
"6.51.53",
"7.16.53"
]
},
"options": []
},
{
"name": "Spoof streaming data",
"description": "Adds options to spoof the streaming data to allow playback.",
"use": true,
"dependencies": [
"BytecodePatch",
"Settings for YouTube Music",
"ResourcePatch",
"BytecodePatch",
"ResourcePatch",
"BytecodePatch"
],
"compatiblePackages": {
@ -2752,7 +2833,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -2762,8 +2843,10 @@
"description": "Adds options to spoof the streaming data to allow playback.",
"use": true,
"dependencies": [
"Settings for YouTube",
"BytecodePatch",
"Settings for YouTube"
"BytecodePatch",
"BytecodePatch"
],
"compatiblePackages": {
"com.google.android.youtube": [
@ -2949,7 +3032,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": [
@ -2999,7 +3082,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": []
@ -3094,7 +3177,7 @@
"6.51.53",
"7.16.53",
"7.25.53",
"8.02.53"
"8.05.51"
]
},
"options": [
@ -3115,6 +3198,27 @@
}
]
},
{
"name": "Watch history",
"description": "Adds an option to change the domain of the watch history or check its status.",
"use": true,
"dependencies": [
"Settings for YouTube Music",
"BytecodePatch"
],
"compatiblePackages": {
"com.google.android.apps.youtube.music": [
"6.20.51",
"6.29.59",
"6.42.55",
"6.51.53",
"7.16.53",
"7.25.53",
"8.05.51"
]
},
"options": []
},
{
"name": "Watch history",
"description": "Adds an option to change the domain of the watch history or check its status.",

View File

@ -2,6 +2,27 @@ public final class app/revanced/generator/MainKt {
public static synthetic fun main ([Ljava/lang/String;)V
}
public final class app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatchKt {
public static final fun getSpoofWifiPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public abstract interface class app/revanced/patches/all/misc/transformation/IMethodCall {
public abstract fun getDefinedClassName ()Ljava/lang/String;
public abstract fun getMethodName ()Ljava/lang/String;
public abstract fun getMethodParams ()[Ljava/lang/String;
public abstract fun getReturnType ()Ljava/lang/String;
public abstract fun replaceInvokeVirtualWithExtension (Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lcom/android/tools/smali/dexlib2/iface/instruction/formats/Instruction35c;I)V
}
public final class app/revanced/patches/all/misc/transformation/IMethodCall$DefaultImpls {
public static fun replaceInvokeVirtualWithExtension (Lapp/revanced/patches/all/misc/transformation/IMethodCall;Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lcom/android/tools/smali/dexlib2/iface/instruction/formats/Instruction35c;I)V
}
public final class app/revanced/patches/all/misc/transformation/TransformInstructionsPatchKt {
public static final fun transformInstructionsPatch (Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Z)Lapp/revanced/patcher/patch/BytecodePatch;
public static synthetic fun transformInstructionsPatch$default (Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ZILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/all/misc/versioncode/ChangeVersionCodePatchKt {
public static final fun getChangeVersionCodePatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
@ -137,6 +158,10 @@ public final class app/revanced/patches/music/misc/tracking/SanitizeUrlQueryPatc
public static final fun getSanitizeUrlQueryPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/misc/watchhistory/WatchHistoryPatchKt {
public static final fun getWatchHistoryPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/navigation/components/NavigationBarComponentsPatchKt {
public static final fun getNavigationBarComponentsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@ -161,10 +186,6 @@ public final class app/revanced/patches/music/utils/fix/androidauto/AndroidAutoC
public static final fun getAndroidAutoCertificatePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/utils/fix/client/FingerprintsKt {
public static final fun indexOfBuildInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
}
public final class app/revanced/patches/music/utils/fix/client/SpoofClientPatchKt {
public static final fun getSpoofClientPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@ -173,11 +194,6 @@ public final class app/revanced/patches/music/utils/fix/fileprovider/FileProvide
public static final fun fileProviderPatch (Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/utils/fix/streamingdata/SpoofStreamingDataPatchKt {
public static final field EXTENSION_CLASS_DESCRIPTOR Ljava/lang/String;
public static final fun getSpoofStreamingDataPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/utils/flyoutmenu/FlyoutMenuHookPatchKt {
public static final fun getFlyoutMenuHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@ -190,6 +206,10 @@ public final class app/revanced/patches/music/utils/mainactivity/MainActivityRes
public static final fun getMainActivityResolvePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/utils/navigation/NavigationBarHookPatchKt {
public static final fun getNavigationBarHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/utils/playertype/PlayerTypeHookPatchKt {
public static final fun getPlayerTypeHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@ -199,6 +219,7 @@ public final class app/revanced/patches/music/utils/playservice/VersionCheckPatc
public static final fun is_6_27_or_greater ()Z
public static final fun is_6_36_or_greater ()Z
public static final fun is_6_42_or_greater ()Z
public static final fun is_6_43_or_greater ()Z
public static final fun is_7_03_or_greater ()Z
public static final fun is_7_06_or_greater ()Z
public static final fun is_7_13_or_greater ()Z
@ -208,7 +229,10 @@ public final class app/revanced/patches/music/utils/playservice/VersionCheckPatc
public static final fun is_7_23_or_greater ()Z
public static final fun is_7_25_or_greater ()Z
public static final fun is_7_27_or_greater ()Z
public static final fun is_7_28_or_greater ()Z
public static final fun is_7_29_or_greater ()Z
public static final fun is_7_33_or_greater ()Z
public static final fun is_8_03_or_greater ()Z
}
public final class app/revanced/patches/music/utils/resourceid/SharedResourceIdPatchKt {
@ -227,6 +251,7 @@ public final class app/revanced/patches/music/utils/resourceid/SharedResourceIdP
public static final fun getFloatingLayout ()J
public static final fun getHistoryMenuItem ()J
public static final fun getInlineTimeBarAdBreakMarkerColor ()J
public static final fun getInlineTimeBarProgressColor ()J
public static final fun getInterstitialsContainer ()J
public static final fun getLikeDislikeContainer ()J
public static final fun getMainActivityLaunchAnimation ()J
@ -235,6 +260,7 @@ public final class app/revanced/patches/music/utils/resourceid/SharedResourceIdP
public static final fun getMiniPlayerMdxPlaying ()J
public static final fun getMiniPlayerPlayPauseReplayButton ()J
public static final fun getMiniPlayerViewPager ()J
public static final fun getModernDialogBackground ()J
public static final fun getMusicNotifierShelf ()J
public static final fun getMusicTasteBuilderShelf ()J
public static final fun getNamesInactiveAccountThumbnailSize ()J
@ -396,10 +422,14 @@ public final class app/revanced/patches/reddit/utils/settings/SettingsPatchKt {
public static final fun is_2024_26_or_greater ()Z
public static final fun is_2024_41_or_greater ()Z
public static final fun is_2025_01_or_greater ()Z
public static final fun is_2025_05_or_greater ()Z
public static final fun is_2025_06_or_greater ()Z
}
public final class app/revanced/patches/shared/FingerprintsKt {
public static final field SPANNABLE_STRING_REFERENCE Ljava/lang/String;
public static final fun indexOfBrandInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
public static final fun indexOfManufacturerInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
public static final fun indexOfModelInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
public static final fun indexOfReleaseInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
public static final fun indexOfSpannableStringInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
@ -529,14 +559,9 @@ public final class app/revanced/patches/shared/spoof/appversion/BaseSpoofAppVers
public static final fun baseSpoofAppVersionPatch (Ljava/lang/String;)Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatchKt {
public final class app/revanced/patches/shared/spoof/blockrequest/BlockRequestPatchKt {
public static final field EXTENSION_CLASS_DESCRIPTOR Ljava/lang/String;
public static final fun baseSpoofStreamingDataPatch (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
public static synthetic fun baseSpoofStreamingDataPatch$default (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/shared/spoof/streamingdata/FingerprintsKt {
public static final field STREAMING_DATA_INTERFACE Ljava/lang/String;
public static final fun getBlockRequestPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/shared/spoof/useragent/BaseSpoofUserAgentPatchKt {
@ -555,21 +580,8 @@ public final class app/revanced/patches/shared/tracking/BaseSanitizeUrlQueryPatc
public static final fun getBaseSanitizeUrlQueryPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public abstract interface class app/revanced/patches/shared/transformation/IMethodCall {
public abstract fun getDefinedClassName ()Ljava/lang/String;
public abstract fun getMethodName ()Ljava/lang/String;
public abstract fun getMethodParams ()[Ljava/lang/String;
public abstract fun getReturnType ()Ljava/lang/String;
public abstract fun replaceInvokeVirtualWithExtension (Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lcom/android/tools/smali/dexlib2/iface/instruction/formats/Instruction35c;I)V
}
public final class app/revanced/patches/shared/transformation/IMethodCall$DefaultImpls {
public static fun replaceInvokeVirtualWithExtension (Lapp/revanced/patches/shared/transformation/IMethodCall;Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lcom/android/tools/smali/dexlib2/iface/instruction/formats/Instruction35c;I)V
}
public final class app/revanced/patches/shared/transformation/TransformInstructionsPatchKt {
public static final fun transformInstructionsPatch (Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
public static synthetic fun transformInstructionsPatch$default (Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
public final class app/revanced/patches/shared/trackingurlhook/TrackingUrlHookPatchKt {
public static final fun getTrackingUrlHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/shared/translations/BaseTranslationsPatchKt {
@ -633,10 +645,6 @@ public final class app/revanced/patches/youtube/general/loadingscreen/GradientLo
public static final fun getGradientLoadingScreenPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/general/miniplayer/MiniplayerPatchKt {
public static final fun getMiniplayerPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/general/music/YouTubeMusicActionsPatchKt {
public static final fun getYoutubeMusicActionsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@ -718,6 +726,10 @@ public final class app/revanced/patches/youtube/layout/visual/VisualPreferencesI
public static final fun getVisualPreferencesIconsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/youtube/misc/accessibility/AccessibilityPatchKt {
public static final fun getAccessibilityPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatchKt {
public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@ -794,6 +806,14 @@ public final class app/revanced/patches/youtube/player/hapticfeedback/HapticFeed
public static final fun getHapticFeedbackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/player/miniplayer/general/MiniplayerPatchKt {
public static final fun getMiniplayerPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/player/miniplayer/startup/ResumingMiniplayerOnStartupPatchKt {
public static final fun getResumingMiniplayerOnStartupPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsPatchKt {
public static final fun getOverlayButtonsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
@ -852,7 +872,7 @@ public final class app/revanced/patches/youtube/utils/fix/bottomui/CfBottomUIPat
}
public final class app/revanced/patches/youtube/utils/fix/cairo/CairoFragmentPatchKt {
public static final fun getCairoFragmentPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun getCairoFragmentPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/youtube/utils/fix/cairo/CairoSettingsPatchKt {
@ -879,6 +899,10 @@ public final class app/revanced/patches/youtube/utils/fix/splash/DarkModeSplashS
public static final fun getDarkModeSplashScreenPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/youtube/utils/fix/streamingdata/FingerprintsKt {
public static final field STREAMING_DATA_INTERFACE Ljava/lang/String;
}
public final class app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatchKt {
public static final fun getSpoofStreamingDataPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@ -976,6 +1000,8 @@ public final class app/revanced/patches/youtube/utils/playservice/VersionCheckPa
public static final fun is_19_46_or_greater ()Z
public static final fun is_19_49_or_greater ()Z
public static final fun is_20_02_or_greater ()Z
public static final fun is_20_03_or_greater ()Z
public static final fun is_20_05_or_greater ()Z
}
public final class app/revanced/patches/youtube/utils/recyclerview/RecyclerViewTreeObserverPatchKt {
@ -983,6 +1009,10 @@ public final class app/revanced/patches/youtube/utils/recyclerview/RecyclerViewT
public static final fun recyclerViewTreeObserverHook (Ljava/lang/String;)V
}
public final class app/revanced/patches/youtube/utils/request/BuildRequestPatchKt {
public static final fun getBuildRequestPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatchKt {
public static final fun getAccountSwitcherAccessibility ()J
public static final fun getActionBarRingo ()J
@ -1007,7 +1037,6 @@ public final class app/revanced/patches/youtube/utils/resourceid/SharedResourceI
public static final fun getChannelListSubMenu ()J
public static final fun getCompactLink ()J
public static final fun getCompactListItem ()J
public static final fun getComponentLongClickListener ()J
public static final fun getContentPill ()J
public static final fun getControlsLayoutStub ()J
public static final fun getDarkBackground ()J
@ -1018,7 +1047,6 @@ public final class app/revanced/patches/youtube/utils/resourceid/SharedResourceI
public static final fun getDrawerResults ()J
public static final fun getEasySeekEduContainer ()J
public static final fun getEditSettingsAction ()J
public static final fun getElementsImage ()J
public static final fun getEmojiPickerIcon ()J
public static final fun getEndScreenElementLayoutCircle ()J
public static final fun getEndScreenElementLayoutIcon ()J
@ -1029,11 +1057,13 @@ public final class app/revanced/patches/youtube/utils/resourceid/SharedResourceI
public static final fun getFilterBarHeight ()J
public static final fun getFloatyBarTopMargin ()J
public static final fun getFullScreenButton ()J
public static final fun getFullScreenEngagementAdContainer ()J
public static final fun getFullScreenEngagementOverlay ()J
public static final fun getFullScreenEngagementPanel ()J
public static final fun getHorizontalCardList ()J
public static final fun getImageOnlyTab ()J
public static final fun getInlineTimeBarColorizedBarPlayedColorDark ()J
public static final fun getInlineTimeBarLiveSeekAbleRange ()J
public static final fun getInlineTimeBarPlayedNotHighlightedColor ()J
public static final fun getInsetElementsWrapper ()J
public static final fun getInsetOverlayViewLayout ()J
@ -1044,10 +1074,10 @@ public final class app/revanced/patches/youtube/utils/resourceid/SharedResourceI
public static final fun getModernMiniPlayerClose ()J
public static final fun getModernMiniPlayerExpand ()J
public static final fun getModernMiniPlayerForwardButton ()J
public static final fun getModernMiniPlayerOverlayActionButton ()J
public static final fun getModernMiniPlayerRewindButton ()J
public static final fun getMusicAppDeeplinkButtonView ()J
public static final fun getNotificationBigPictureIconWidth ()J
public static final fun getOfflineActionsVideoDeletedUndoSnackbarText ()J
public static final fun getPlayerCollapseButton ()J
public static final fun getPlayerControlNextButtonTouchArea ()J
public static final fun getPlayerControlPreviousButtonTouchArea ()J
@ -1097,6 +1127,8 @@ public final class app/revanced/patches/youtube/utils/resourceid/SharedResourceI
public static final fun getYtOutlineVideoCamera ()J
public static final fun getYtOutlineXWhite ()J
public static final fun getYtPremiumWordMarkHeader ()J
public static final fun getYtStaticBrandRed ()J
public static final fun getYtTextSecondary ()J
public static final fun getYtWordMarkHeader ()J
public static final fun getYtYoutubeMagenta ()J
}
@ -1132,10 +1164,6 @@ public final class app/revanced/patches/youtube/utils/toolbar/ToolBarHookPatchKt
public static final fun getToolBarHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/utils/trackingurlhook/TrackingUrlHookPatchKt {
public static final fun getTrackingUrlHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/video/information/FingerprintsKt {
public static final fun indexOfPlayerResponseModelInterfaceInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
}
@ -1260,8 +1288,7 @@ public final class app/revanced/util/ResourceUtilsKt {
public static final fun copyAdaptiveIcon (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;)V
public static synthetic fun copyAdaptiveIcon$default (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)V
public static final fun copyFile (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;)Z
public static final fun copyResources (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;[Lapp/revanced/util/ResourceGroup;Z)V
public static synthetic fun copyResources$default (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;[Lapp/revanced/util/ResourceGroup;ZILjava/lang/Object;)V
public static final fun copyResources (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;[Lapp/revanced/util/ResourceGroup;)V
public static final fun copyXmlNode (Lapp/revanced/patcher/patch/ResourcePatchContext;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lkotlin/Unit;
public static final fun copyXmlNode (Ljava/lang/String;Lapp/revanced/patcher/util/Document;Lapp/revanced/patcher/util/Document;)Ljava/lang/AutoCloseable;
public static final fun doRecursively (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V

View File

@ -0,0 +1,225 @@
package app.revanced.patches.all.misc.connectivity.wifi.spoof
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.transformation.IMethodCall
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
internal const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
"Lapp/revanced/extension/all/connectivity/wifi/spoof/SpoofWifiPatch"
internal const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
@Suppress("unused")
val spoofWifiPatch = bytecodePatch(
name = "Spoof Wi-Fi connection",
description = "Spoofs an existing Wi-Fi connection.",
use = false,
) {
extendWith("extensions/all/connectivity/wifi/spoof/spoof-wifi.rve")
dependsOn(
transformInstructionsPatch(
filterMap = { classDef, _, instruction, instructionIndex ->
filterMapInstruction35c<MethodCall>(
EXTENSION_CLASS_DESCRIPTOR_PREFIX,
classDef,
instruction,
instructionIndex,
)
},
transform = { method, entry ->
val (methodType, instruction, instructionIndex) = entry
methodType.replaceInvokeVirtualWithExtension(
EXTENSION_CLASS_DESCRIPTOR,
method,
instruction,
instructionIndex,
)
},
),
)
}
// Information about method calls we want to replace
@Suppress("unused")
private enum class MethodCall(
override val definedClassName: String,
override val methodName: String,
override val methodParams: Array<String>,
override val returnType: String,
) : IMethodCall {
GetSystemService1(
"Landroid/content/Context;",
"getSystemService",
arrayOf("Ljava/lang/String;"),
"Ljava/lang/Object;",
),
GetSystemService2(
"Landroid/content/Context;",
"getSystemService",
arrayOf("Ljava/lang/Class;"),
"Ljava/lang/Object;",
),
GetActiveNetworkInfo(
"Landroid/net/ConnectivityManager;",
"getActiveNetworkInfo",
arrayOf(),
"Landroid/net/NetworkInfo;",
),
IsConnected(
"Landroid/net/NetworkInfo;",
"isConnected",
arrayOf(),
"Z",
),
IsConnectedOrConnecting(
"Landroid/net/NetworkInfo;",
"isConnectedOrConnecting",
arrayOf(),
"Z",
),
IsAvailable(
"Landroid/net/NetworkInfo;",
"isAvailable",
arrayOf(),
"Z",
),
GetState(
"Landroid/net/NetworkInfo;",
"getState",
arrayOf(),
"Landroid/net/NetworkInfo\$State;",
),
GetDetailedState(
"Landroid/net/NetworkInfo;",
"getDetailedState",
arrayOf(),
"Landroid/net/NetworkInfo\$DetailedState;",
),
IsActiveNetworkMetered(
"Landroid/net/ConnectivityManager;",
"isActiveNetworkMetered",
arrayOf(),
"Z",
),
GetActiveNetwork(
"Landroid/net/ConnectivityManager;",
"getActiveNetwork",
arrayOf(),
"Landroid/net/Network;",
),
GetNetworkInfo(
"Landroid/net/ConnectivityManager;",
"getNetworkInfo",
arrayOf("Landroid/net/Network;"),
"Landroid/net/NetworkInfo;",
),
HasTransport(
"Landroid/net/NetworkCapabilities;",
"hasTransport",
arrayOf("I"),
"Z",
),
HasCapability(
"Landroid/net/NetworkCapabilities;",
"hasCapability",
arrayOf("I"),
"Z",
),
RegisterBestMatchingNetworkCallback(
"Landroid/net/ConnectivityManager;",
"registerBestMatchingNetworkCallback",
arrayOf(
"Landroid/net/NetworkRequest;",
"Landroid/net/ConnectivityManager\$NetworkCallback;",
"Landroid/os/Handler;",
),
"V",
),
RegisterDefaultNetworkCallback1(
"Landroid/net/ConnectivityManager;",
"registerDefaultNetworkCallback",
arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"),
"V",
),
RegisterDefaultNetworkCallback2(
"Landroid/net/ConnectivityManager;",
"registerDefaultNetworkCallback",
arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;", "Landroid/os/Handler;"),
"V",
),
RegisterNetworkCallback1(
"Landroid/net/ConnectivityManager;",
"registerNetworkCallback",
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"),
"V",
),
RegisterNetworkCallback2(
"Landroid/net/ConnectivityManager;",
"registerNetworkCallback",
arrayOf("Landroid/net/NetworkRequest;", "Landroid/app/PendingIntent;"),
"V",
),
RegisterNetworkCallback3(
"Landroid/net/ConnectivityManager;",
"registerNetworkCallback",
arrayOf(
"Landroid/net/NetworkRequest;",
"Landroid/net/ConnectivityManager\$NetworkCallback;",
"Landroid/os/Handler;",
),
"V",
),
RequestNetwork1(
"Landroid/net/ConnectivityManager;",
"requestNetwork",
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"),
"V",
),
RequestNetwork2(
"Landroid/net/ConnectivityManager;",
"requestNetwork",
arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "I"),
"V",
),
RequestNetwork3(
"Landroid/net/ConnectivityManager;",
"requestNetwork",
arrayOf(
"Landroid/net/NetworkRequest;",
"Landroid/net/ConnectivityManager\$NetworkCallback;",
"Landroid/os/Handler;",
),
"V",
),
RequestNetwork4(
"Landroid/net/ConnectivityManager;",
"requestNetwork",
arrayOf("Landroid/net/NetworkRequest;", "Landroid/app/PendingIntent;"),
"V",
),
RequestNetwork5(
"Landroid/net/ConnectivityManager;",
"requestNetwork",
arrayOf(
"Landroid/net/NetworkRequest;",
"Landroid/net/ConnectivityManager\$NetworkCallback;",
"Landroid/os/Handler;",
"I",
),
"V",
),
UnregisterNetworkCallback1(
"Landroid/net/ConnectivityManager;",
"unregisterNetworkCallback",
arrayOf("Landroid/net/ConnectivityManager\$NetworkCallback;"),
"V",
),
UnregisterNetworkCallback2(
"Landroid/net/ConnectivityManager;",
"unregisterNetworkCallback",
arrayOf("Landroid/app/PendingIntent;"),
"V",
),
}

View File

@ -1,4 +1,4 @@
package app.revanced.patches.shared.transformation
package app.revanced.patches.all.misc.transformation
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod

View File

@ -1,4 +1,4 @@
package app.revanced.patches.shared.transformation
package app.revanced.patches.all.misc.transformation
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.bytecodePatch
@ -8,10 +8,15 @@ import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
private const val EXTENSION_NAME_SPACE_PATH =
"Lapp/revanced/extension/"
fun <T> transformInstructionsPatch(
filterMap: (ClassDef, Method, Instruction, Int) -> T?,
transform: (MutableMethod, T) -> Unit,
executeBlock: BytecodePatchContext.() -> Unit = {},
// If the instructions of Extension are replaced, the patch may not work as intended.
skipExtension: Boolean = true,
) = bytecodePatch(
description = "transformInstructionsPatch"
) {
@ -26,6 +31,9 @@ fun <T> transformInstructionsPatch(
// Find all methods to patch
buildMap {
classes.forEach { classDef ->
if (skipExtension && classDef.type.startsWith(EXTENSION_NAME_SPACE_PATH)) {
return@forEach
}
val methods = buildList {
classDef.methods.forEach { method ->
// Since the Sequence executes lazily,

View File

@ -6,12 +6,15 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.music.utils.ACTION_BAR_POSITION_FEATURE_FLAG
import app.revanced.patches.music.utils.actionBarPositionFeatureFlagFingerprint
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.ACTIONBAR_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.extension.Constants.COMPONENTS_PATH
import app.revanced.patches.music.utils.patch.PatchList.HIDE_ACTION_BAR_COMPONENTS
import app.revanced.patches.music.utils.playservice.is_7_17_or_greater
import app.revanced.patches.music.utils.playservice.is_7_25_or_greater
import app.revanced.patches.music.utils.playservice.is_7_33_or_greater
import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.resourceid.likeDislikeContainer
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
@ -23,6 +26,9 @@ import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.music.video.information.videoInformationPatch
import app.revanced.patches.shared.litho.addLithoFilter
import app.revanced.patches.shared.litho.lithoFilterPatch
import app.revanced.patches.shared.textcomponent.hookSpannableString
import app.revanced.patches.shared.textcomponent.textComponentPatch
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
@ -50,6 +56,7 @@ val actionBarComponentsPatch = bytecodePatch(
settingsPatch,
lithoFilterPatch,
sharedResourceIdPatch,
textComponentPatch,
videoInformationPatch,
versionCheckPatch,
)
@ -57,6 +64,36 @@ val actionBarComponentsPatch = bytecodePatch(
execute {
if (is_7_17_or_greater) {
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
hookSpannableString(ACTIONBAR_CLASS_DESCRIPTOR, "onLithoTextLoaded")
commandResolverFingerprint.methodOrThrow().addInstruction(
0,
"invoke-static {p2}, $ACTIONBAR_CLASS_DESCRIPTOR->inAppDownloadButtonOnClick(Ljava/util/Map;)Z"
)
offlineVideoEndpointFingerprint.methodOrThrow().addInstructionsWithLabels(
0, """
invoke-static {p2}, $ACTIONBAR_CLASS_DESCRIPTOR->inAppDownloadButtonOnClick(Ljava/util/Map;)Z
move-result v0
if-eqz v0, :ignore
return-void
:ignore
nop
"""
)
if (is_7_25_or_greater) {
actionBarPositionFeatureFlagFingerprint.injectLiteralInstructionBooleanCall(
ACTION_BAR_POSITION_FEATURE_FLAG,
"$ACTIONBAR_CLASS_DESCRIPTOR->changeActionBarPosition(Z)Z"
)
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_change_action_bar_position",
"false"
)
}
}
if (!is_7_25_or_greater) {
@ -181,31 +218,38 @@ val actionBarComponentsPatch = bytecodePatch(
)
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_hide_action_button_share",
"revanced_hide_action_button_radio",
"false"
)
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_hide_action_button_radio",
"revanced_hide_action_button_share",
"false"
)
if (is_7_33_or_greater) {
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_hide_action_button_song_video",
"false"
)
}
if (!is_7_25_or_greater) {
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_hide_action_button_label",
"false"
)
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_external_downloader_action",
"false"
)
addPreferenceWithIntent(
CategoryType.ACTION_BAR,
"revanced_external_downloader_package_name",
"revanced_external_downloader_action"
)
}
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_external_downloader_action",
"false"
)
addPreferenceWithIntent(
CategoryType.ACTION_BAR,
"revanced_external_downloader_package_name",
"revanced_external_downloader_action"
)
updatePatchStatus(HIDE_ACTION_BAR_COMPONENTS)

View File

@ -28,3 +28,19 @@ internal val likeDislikeContainerFingerprint = legacyFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
literals = listOf(likeDislikeContainer)
)
internal val commandResolverFingerprint = legacyFingerprint(
name = "commandResolverFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC or AccessFlags.FINAL,
returnType = "Z",
parameters = listOf("L", "L", "Ljava/util/Map;"),
strings = listOf("CommandResolver threw exception during resolution")
)
internal val offlineVideoEndpointFingerprint = legacyFingerprint(
name = "offlineVideoEndpointFingerprint",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "V",
parameters = listOf("L", "Ljava/util/Map;"),
strings = listOf("Object is not an offlineable video: %s")
)

View File

@ -2,12 +2,16 @@ package app.revanced.patches.music.ads.general
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.navigation.components.navigationBarComponentsPatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.ADS_PATH
import app.revanced.patches.music.utils.extension.Constants.COMPONENTS_PATH
import app.revanced.patches.music.utils.navigation.navigationBarHookPatch
import app.revanced.patches.music.utils.patch.PatchList.HIDE_ADS
import app.revanced.patches.music.utils.playservice.is_7_28_or_greater
import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.resourceid.buttonContainer
import app.revanced.patches.music.utils.resourceid.floatingLayout
import app.revanced.patches.music.utils.resourceid.interstitialsContainer
@ -29,6 +33,7 @@ import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
@ -55,7 +60,9 @@ val adsPatch = bytecodePatch(
baseAdsPatch("$ADS_PATH/MusicAdsPatch;", "hideMusicAds"),
lithoFilterPatch,
navigationBarComponentsPatch, // for 'Hide upgrade button' setting
navigationBarHookPatch,
sharedResourceIdPatch,
versionCheckPatch,
)
execute {
@ -76,16 +83,35 @@ val adsPatch = bytecodePatch(
// region patch for hide premium promotion popup
// get premium bottom sheet
floatingLayoutFingerprint.methodOrThrow().apply {
val targetIndex = indexOfFirstLiteralInstructionOrThrow(floatingLayout) + 2
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstruction(
targetIndex + 1,
"invoke-static {v$targetRegister}, $PREMIUM_PROMOTION_POP_UP_CLASS_DESCRIPTOR->hidePremiumPromotion(Landroid/view/View;)V"
"invoke-static {v$targetRegister}, $PREMIUM_PROMOTION_POP_UP_CLASS_DESCRIPTOR->hidePremiumPromotionBottomSheet(Landroid/view/View;)V"
)
}
// get premium dialog in player
if (is_7_28_or_greater) {
getPremiumDialogFingerprint
.methodOrThrow(getPremiumDialogParentFingerprint)
.apply {
val setContentViewIndex = indexOfSetContentViewInstruction(this)
val dialogInstruction = getInstruction<FiveRegisterInstruction>(setContentViewIndex)
val dialogRegister = dialogInstruction.registerC
val viewRegister = dialogInstruction.registerD
replaceInstruction(
setContentViewIndex,
"invoke-static {v$dialogRegister, v$viewRegister}, " +
" $PREMIUM_PROMOTION_POP_UP_CLASS_DESCRIPTOR->hidePremiumPromotionDialog(Landroid/app/Dialog;Landroid/view/View;)V"
)
}
}
// endregion
// region patch for hide premium renewal banner
@ -148,10 +174,12 @@ val adsPatch = bytecodePatch(
addLithoFilter(ADS_FILTER_CLASS_DESCRIPTOR)
// endregion
addSwitchPreference(
CategoryType.ADS,
"revanced_hide_fullscreen_ads",
"false"
"true"
)
addSwitchPreference(
CategoryType.ADS,

View File

@ -3,13 +3,18 @@ package app.revanced.patches.music.ads.general
import app.revanced.patches.music.utils.resourceid.buttonContainer
import app.revanced.patches.music.utils.resourceid.floatingLayout
import app.revanced.patches.music.utils.resourceid.interstitialsContainer
import app.revanced.patches.music.utils.resourceid.modernDialogBackground
import app.revanced.patches.music.utils.resourceid.musicNotifierShelf
import app.revanced.patches.music.utils.resourceid.privacyTosFooter
import app.revanced.patches.music.utils.resourceid.slidingDialogAnimation
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val accountMenuFooterFingerprint = legacyFingerprint(
name = "accountMenuFooterFingerprint",
@ -34,6 +39,29 @@ internal val floatingLayoutFingerprint = legacyFingerprint(
literals = listOf(floatingLayout)
)
internal val getPremiumDialogParentFingerprint = legacyFingerprint(
name = "getPremiumDialogParentFingerprint",
returnType = "Landroid/graphics/drawable/Drawable;",
accessFlags = AccessFlags.PROTECTED.value,
parameters = listOf("Landroid/content/Context;"),
literals = listOf(modernDialogBackground)
)
internal val getPremiumDialogFingerprint = legacyFingerprint(
name = "getPremiumDialogFingerprint",
returnType = "Landroid/app/Dialog;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/os/Bundle;"),
customFingerprint = { method, _ ->
indexOfSetContentViewInstruction(method) >= 0
}
)
internal fun indexOfSetContentViewInstruction(method: Method) =
method.indexOfFirstInstruction {
getReference<MethodReference>()?.toString() == "Landroid/app/Dialog;->setContentView(Landroid/view/View;)V"
}
internal val getPremiumTextViewFingerprint = legacyFingerprint(
name = "getPremiumTextViewFingerprint",
returnType = "V",

View File

@ -335,6 +335,12 @@ val flyoutMenuComponentsPatch = bytecodePatch(
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_not_interested",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_pin_to_speed_dial",

View File

@ -3,11 +3,11 @@ package app.revanced.patches.music.general.spoofappversion
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.general.oldstylelibraryshelf.oldStyleLibraryShelfPatch
import app.revanced.patches.music.utils.compatibility.Constants.YOUTUBE_MUSIC_PACKAGE_NAME
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.extension.Constants.PATCH_STATUS_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.patch.PatchList.SPOOF_APP_VERSION
import app.revanced.patches.music.utils.playservice.is_6_43_or_greater
import app.revanced.patches.music.utils.playservice.is_7_17_or_greater
import app.revanced.patches.music.utils.playservice.is_7_25_or_greater
import app.revanced.patches.music.utils.playservice.versionCheckPatch
@ -32,7 +32,7 @@ private val spoofAppVersionBytecodePatch = bytecodePatch(
)
execute {
if (is_7_25_or_greater) {
if (!is_6_43_or_greater || is_7_25_or_greater) {
return@execute
}
if (is_7_17_or_greater) {
@ -61,9 +61,6 @@ val spoofAppVersionPatch = resourcePatch(
) {
compatibleWith(
YOUTUBE_MUSIC_PACKAGE_NAME(
"6.20.51",
"6.29.59",
"6.42.55",
"6.51.53",
"7.16.53",
),
@ -71,14 +68,13 @@ val spoofAppVersionPatch = resourcePatch(
dependsOn(
spoofAppVersionBytecodePatch,
oldStyleLibraryShelfPatch,
settingsPatch,
versionCheckPatch,
)
execute {
if (is_7_25_or_greater) {
printWarn("\"${SPOOF_APP_VERSION.title}\" is not supported in this version. Use YouTube Music 7.24.51 or earlier.")
if (!is_6_43_or_greater || is_7_25_or_greater) {
printWarn("\"${SPOOF_APP_VERSION.title}\" is not supported in this version. Use YouTube Music 6.43.53 ~ 7.24.51.")
return@execute
}
if (is_7_17_or_greater) {

View File

@ -1,10 +1,11 @@
package app.revanced.patches.music.general.startpage
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.extension.Constants.GENERAL_PATH
import app.revanced.patches.music.utils.patch.PatchList.CHANGE_START_PAGE
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
@ -18,6 +19,9 @@ import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
private const val EXTENSION_CLASS_DESCRIPTOR =
"$GENERAL_PATH/ChangeStartPagePatch;"
@Suppress("unused")
val changeStartPagePatch = bytecodePatch(
CHANGE_START_PAGE.title,
@ -29,6 +33,11 @@ val changeStartPagePatch = bytecodePatch(
execute {
coldStartIntentFingerprint.methodOrThrow().addInstruction(
0,
"invoke-static {p1}, $EXTENSION_CLASS_DESCRIPTOR->overrideIntent(Landroid/content/Intent;)V"
)
coldStartUpFingerprint.methodOrThrow().apply {
val defaultBrowseIdIndex = indexOfFirstStringInstructionOrThrow(DEFAULT_BROWSE_ID)
val browseIdIndex = indexOfFirstInstructionReversedOrThrow(defaultBrowseIdIndex) {
@ -39,7 +48,7 @@ val changeStartPagePatch = bytecodePatch(
addInstructions(
browseIdIndex + 1, """
invoke-static {v$browseIdRegister}, $GENERAL_CLASS_DESCRIPTOR->changeStartPage(Ljava/lang/String;)Ljava/lang/String;
invoke-static {v$browseIdRegister}, $EXTENSION_CLASS_DESCRIPTOR->overrideBrowseId(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$browseIdRegister
"""
)

View File

@ -7,6 +7,17 @@ import com.android.tools.smali.dexlib2.Opcode
const val DEFAULT_BROWSE_ID = "FEmusic_home"
internal val coldStartIntentFingerprint = legacyFingerprint(
name = "coldStartIntentFingerprint",
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/content/Intent;"),
strings = listOf(
"android.intent.action.SEARCH",
"com.google.android.youtube.music.action.shortcut_type",
)
)
internal val coldStartUpFingerprint = legacyFingerprint(
name = "coldStartUpFingerprint",
returnType = "Ljava/lang/String;",
@ -16,6 +27,9 @@ internal val coldStartUpFingerprint = legacyFingerprint(
Opcode.CONST_STRING,
Opcode.RETURN_OBJECT
),
strings = listOf("FEmusic_library_sideloaded_tracks", DEFAULT_BROWSE_ID)
strings = listOf(
"FEmusic_library_sideloaded_tracks",
DEFAULT_BROWSE_ID
)
)

View File

@ -11,6 +11,7 @@ import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.settings.ResourceUtils.setIconType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.FilesCompat
import app.revanced.util.ResourceGroup
import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.util.copyAdaptiveIcon
@ -20,7 +21,6 @@ import app.revanced.util.underBarOrThrow
import app.revanced.util.valueOrThrow
import org.w3c.dom.Element
import java.io.File
import java.nio.file.Files
private const val ADAPTIVE_ICON_BACKGROUND_FILE_NAME =
"adaptiveproduct_youtube_music_background_color_108"
@ -155,9 +155,9 @@ val customBrandingIconPatch = resourcePatch(
val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName)
group.resources.forEach { iconFileName ->
Files.write(
toDirectory.resolve(iconFileName).toPath(),
fromDirectory.resolve(iconFileName).readBytes()
FilesCompat.copy(
fromDirectory.resolve(iconFileName),
toDirectory.resolve(iconFileName)
)
}
}
@ -215,7 +215,6 @@ val customBrandingIconPatch = resourcePatch(
copyResources(
"$youtubeMusicIconResourcePath/splash",
it,
createDirectoryIfNotExist = true
)
}
}

View File

@ -112,10 +112,11 @@ val darkThemePatch = resourcePatch(
.valueOrThrow()
document("res/values/colors.xml").use { document ->
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
val resourcesNode = document.documentElement
val childNodes = resourcesNode.childNodes
for (i in 0 until resourcesNode.childNodes.length) {
val node = resourcesNode.childNodes.item(i) as? Element ?: continue
for (i in 0 until childNodes.length) {
val node = childNodes.item(i) as? Element ?: continue
val colorName = node.getAttribute("name")
if (DARK_COLOR.contains(colorName)) {

View File

@ -0,0 +1,33 @@
package app.revanced.patches.music.misc.watchhistory
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.shared.trackingurlhook.hookWatchHistory
import app.revanced.patches.shared.trackingurlhook.trackingUrlHookPatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.WATCH_HISTORY
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.settingsPatch
@Suppress("unused")
val watchHistoryPatch = bytecodePatch(
WATCH_HISTORY.title,
WATCH_HISTORY.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
settingsPatch,
trackingUrlHookPatch,
)
execute {
hookWatchHistory()
addPreferenceWithIntent(
CategoryType.MISC,
"revanced_watch_history_type"
)
}
}

View File

@ -4,6 +4,7 @@ import app.revanced.patches.music.utils.extension.Constants.PLAYER_CLASS_DESCRIP
import app.revanced.patches.music.utils.playservice.is_7_18_or_greater
import app.revanced.patches.music.utils.resourceid.colorGrey
import app.revanced.patches.music.utils.resourceid.darkBackground
import app.revanced.patches.music.utils.resourceid.inlineTimeBarProgressColor
import app.revanced.patches.music.utils.resourceid.miniPlayerDefaultText
import app.revanced.patches.music.utils.resourceid.miniPlayerMdxPlaying
import app.revanced.patches.music.utils.resourceid.miniPlayerPlayPauseReplayButton
@ -227,13 +228,17 @@ internal val nextButtonVisibilityFingerprint = legacyFingerprint(
)
)
internal const val OLD_ENGAGEMENT_PANEL_FEATURE_FLAG = 45427672L
internal val oldEngagementPanelFingerprint = legacyFingerprint(
name = "oldEngagementPanelFingerprint",
returnType = "Z",
parameters = emptyList(),
literals = listOf(45427672L),
literals = listOf(OLD_ENGAGEMENT_PANEL_FEATURE_FLAG),
)
internal const val OLD_PLAYER_BACKGROUND_FEATURE_FLAG = 45415319L
/**
* Deprecated in YouTube Music v6.34.51+
*/
@ -241,9 +246,11 @@ internal val oldPlayerBackgroundFingerprint = legacyFingerprint(
name = "oldPlayerBackgroundFingerprint",
returnType = "Z",
parameters = emptyList(),
literals = listOf(45415319L),
literals = listOf(OLD_PLAYER_BACKGROUND_FEATURE_FLAG),
)
internal const val OLD_PLAYER_LAYOUT_FEATURE_FLAG = 45399578L
/**
* Deprecated in YouTube Music v6.31.55+
*/
@ -251,7 +258,7 @@ internal val oldPlayerLayoutFingerprint = legacyFingerprint(
name = "oldPlayerLayoutFingerprint",
returnType = "Z",
parameters = emptyList(),
literals = listOf(45399578L),
literals = listOf(OLD_PLAYER_LAYOUT_FEATURE_FLAG),
)
internal val playerPatchConstructorFingerprint = legacyFingerprint(
@ -306,23 +313,32 @@ internal val repeatTrackFingerprint = legacyFingerprint(
strings = listOf("w_st")
)
internal const val SHUFFLE_BUTTON_ID = 45468L
internal val shuffleOnClickFingerprint = legacyFingerprint(
name = "shuffleOnClickFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/view/View;"),
literals = listOf(45468L),
literals = listOf(SHUFFLE_BUTTON_ID),
customFingerprint = { method, _ ->
method.name == "onClick" &&
indexOfAccessibilityInstruction(method) >= 0
method.name == "onClick"
}
)
internal fun indexOfAccessibilityInstruction(method: Method) =
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "announceForAccessibility"
internal val shuffleEnumFingerprint = legacyFingerprint(
name = "shuffleEnumFingerprint",
returnType = "V",
parameters = emptyList(),
strings = listOf(
"SHUFFLE_OFF",
"SHUFFLE_ALL",
"SHUFFLE_DISABLED",
),
customFingerprint = { method, _ ->
method.name == "<clinit>"
}
)
internal val swipeToCloseFingerprint = legacyFingerprint(
name = "swipeToCloseFingerprint",
@ -344,6 +360,33 @@ internal val switchToggleColorFingerprint = legacyFingerprint(
)
)
internal val thickSeekBarColorFingerprint = legacyFingerprint(
name = "thickSeekBarColorFingerprint",
returnType = "V",
parameters = listOf("L"),
literals = listOf(inlineTimeBarProgressColor),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/MusicPlaybackControls;")
}
)
internal val thickSeekBarFeatureFlagFingerprint = legacyFingerprint(
name = "thickSeekBarFeatureFlagFingerprint",
returnType = "Z",
parameters = emptyList(),
literals = listOf(45659062L),
)
internal val thickSeekBarInflateFingerprint = legacyFingerprint(
name = "thickSeekBarInflateFingerprint",
returnType = "V",
parameters = emptyList(),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/MusicPlaybackControls;") &&
method.name == "onFinishInflate"
}
)
internal val zenModeFingerprint = legacyFingerprint(
name = "zenModeFingerprint",
returnType = "V",

View File

@ -24,6 +24,8 @@ import app.revanced.patches.music.utils.playservice.is_6_27_or_greater
import app.revanced.patches.music.utils.playservice.is_6_42_or_greater
import app.revanced.patches.music.utils.playservice.is_7_18_or_greater
import app.revanced.patches.music.utils.playservice.is_7_25_or_greater
import app.revanced.patches.music.utils.playservice.is_7_29_or_greater
import app.revanced.patches.music.utils.playservice.is_8_03_or_greater
import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.resourceid.colorGrey
import app.revanced.patches.music.utils.resourceid.darkBackground
@ -36,6 +38,7 @@ import app.revanced.patches.music.utils.resourceid.topEnd
import app.revanced.patches.music.utils.resourceid.topStart
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.music.utils.videotype.videoTypeHookPatch
@ -47,16 +50,19 @@ import app.revanced.util.addStaticFieldToExtension
import app.revanced.util.adoptChild
import app.revanced.util.cloneMutable
import app.revanced.util.doRecursively
import app.revanced.util.findInstructionIndicesReversed
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
import app.revanced.util.fingerprint.injectLiteralInstructionViewCall
import app.revanced.util.fingerprint.matchOrNull
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodCall
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.fingerprint.mutableClassOrThrow
import app.revanced.util.fingerprint.resolvable
import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
@ -65,13 +71,16 @@ import app.revanced.util.insertNode
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
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
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
import org.w3c.dom.Element
private const val IMAGE_VIEW_TAG_NAME =
@ -194,21 +203,21 @@ val playerComponentsPatch = bytecodePatch(
execute {
// region patch for enable next previous button
// region patch for add next previous button
val nextButtonFieldName = "nextButton"
val previousButtonFieldName = "previousButton"
val nextButtonViewMethodName = "setNextButtonView"
val previousButtonViewMethodName = "setPreviousButtonView"
val nextButtonClassFieldName = "nextButtonClass"
val previousButtonClassFieldName = "previousButtonClass"
val nextButtonButtonMethodName = "setNextButton"
val previousButtonMethodName = "setPreviousButton"
val nextButtonOnClickListenerMethodName = "setNextButtonOnClickListener"
val previousButtonOnClickListenerMethodName = "setPreviousButtonOnClickListener"
val nextOnClickListenerMethodName = "setNextButtonOnClickListener"
val previousOnClickListenerMethodName = "setPreviousButtonOnClickListener"
val nextButtonClickedMethodName = "nextButtonClicked"
val previousButtonClickedMethodName = "previousButtonClicked"
val nextButtonIntentString = "YTM Next"
val previousButtonIntentString = "YTM Previous"
fun MutableMethod.setStaticFieldValue(
fieldName: String,
fun MutableMethod.setButtonView(
methodName: String,
viewId: Long
) {
val miniPlayerPlayPauseReplayButtonIndex =
@ -228,7 +237,7 @@ val playerComponentsPatch = bytecodePatch(
const v$constRegister, $viewId
invoke-virtual {v$findViewByIdRegister, v$constRegister}, $definingClass->findViewById(I)Landroid/view/View;
move-result-object v$constRegister
sput-object v$constRegister, $PLAYER_CLASS_DESCRIPTOR->$fieldName:Landroid/view/View;
invoke-static {v$constRegister}, $PLAYER_CLASS_DESCRIPTOR->$methodName(Landroid/view/View;)V
"""
)
}
@ -252,7 +261,7 @@ val playerComponentsPatch = bytecodePatch(
)
}
fun MutableMethod.setOnClickListener(
fun MutableMethod.setIntentOnClickListener(
intentString: String,
methodName: String,
fieldName: String
@ -298,6 +307,32 @@ val playerComponentsPatch = bytecodePatch(
}
}
fun MutableMethod.setOnclickListener(
methodName: String,
viewId: Long
) {
val miniPlayerPlayPauseReplayButtonIndex =
indexOfFirstLiteralInstructionOrThrow(miniPlayerPlayPauseReplayButton)
val miniPlayerPlayPauseReplayButtonRegister =
getInstruction<OneRegisterInstruction>(miniPlayerPlayPauseReplayButtonIndex).registerA
val findViewByIdIndex =
indexOfFirstInstructionOrThrow(
miniPlayerPlayPauseReplayButtonIndex,
Opcode.INVOKE_VIRTUAL
)
val parentViewRegister =
getInstruction<FiveRegisterInstruction>(findViewByIdIndex).registerC
addInstructions(
miniPlayerPlayPauseReplayButtonIndex, """
const v$miniPlayerPlayPauseReplayButtonRegister, $viewId
invoke-virtual {v$parentViewRegister, v$miniPlayerPlayPauseReplayButtonRegister}, Landroid/view/View;->findViewById(I)Landroid/view/View;
move-result-object v$miniPlayerPlayPauseReplayButtonRegister
invoke-static {v$miniPlayerPlayPauseReplayButtonRegister}, $PLAYER_CLASS_DESCRIPTOR->$methodName(Landroid/view/View;)V
"""
)
}
val miniPlayerConstructorMutableMethod =
miniPlayerConstructorFingerprint.methodOrThrow()
@ -316,33 +351,33 @@ val playerComponentsPatch = bytecodePatch(
addInstructions(
targetIndex + 1, """
invoke-static {v$targetRegister}, $PLAYER_CLASS_DESCRIPTOR->enableMiniPlayerNextButton(Z)Z
invoke-static {v$targetRegister}, $PLAYER_CLASS_DESCRIPTOR->addMiniPlayerNextButton(Z)Z
move-result v$targetRegister
"""
)
}
}
} else {
miniPlayerConstructorMutableMethod.setInstanceFieldValue(
nextButtonButtonMethodName,
miniPlayerConstructorMutableMethod.setOnclickListener(
nextOnClickListenerMethodName,
topStart
)
mppWatchWhileLayoutMutableMethod.setStaticFieldValue(nextButtonFieldName, topStart)
pendingIntentReceiverMutableMethod.setOnClickListener(
mppWatchWhileLayoutMutableMethod.setButtonView(nextButtonViewMethodName, topStart)
pendingIntentReceiverMutableMethod.setIntentOnClickListener(
nextButtonIntentString,
nextButtonOnClickListenerMethodName,
nextButtonClickedMethodName,
nextButtonClassFieldName
)
}
miniPlayerConstructorMutableMethod.setInstanceFieldValue(
previousButtonMethodName,
miniPlayerConstructorMutableMethod.setOnclickListener(
previousOnClickListenerMethodName,
topEnd
)
mppWatchWhileLayoutMutableMethod.setStaticFieldValue(previousButtonFieldName, topEnd)
pendingIntentReceiverMutableMethod.setOnClickListener(
mppWatchWhileLayoutMutableMethod.setButtonView(previousButtonViewMethodName, topEnd)
pendingIntentReceiverMutableMethod.setIntentOnClickListener(
previousButtonIntentString,
previousButtonOnClickListenerMethodName,
previousButtonClickedMethodName,
previousButtonClassFieldName
)
@ -350,18 +385,18 @@ val playerComponentsPatch = bytecodePatch(
addSwitchPreference(
CategoryType.PLAYER,
"revanced_enable_mini_player_next_button",
"revanced_add_miniplayer_next_button",
"true"
)
addSwitchPreference(
CategoryType.PLAYER,
"revanced_enable_mini_player_previous_button",
"revanced_add_miniplayer_previous_button",
"true"
)
// endregion
// region patch for enable color match player and enable black player background
// region patch for color match player, change player background and enable zen mode (6.35+)
val (
colorMathPlayerMethodParameter,
@ -376,17 +411,19 @@ val playerComponentsPatch = bytecodePatch(
// black player background
val invokeDirectIndex = indexOfFirstInstructionOrThrow(Opcode.INVOKE_DIRECT)
val targetMethod = getWalkerMethod(invokeDirectIndex)
val insertIndex = targetMethod.indexOfFirstInstructionOrThrow(Opcode.IF_NE)
targetMethod.addInstructions(
insertIndex, """
invoke-static {p1}, $PLAYER_CLASS_DESCRIPTOR->enableBlackPlayerBackground(I)I
move-result p1
invoke-static {p2}, $PLAYER_CLASS_DESCRIPTOR->enableBlackPlayerBackground(I)I
move-result p2
"""
)
getWalkerMethod(invokeDirectIndex).apply {
val index = indexOfFirstInstructionOrThrow(Opcode.FILLED_NEW_ARRAY)
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
addInstructions(
index + 2, """
invoke-static {v$register}, $PLAYER_CLASS_DESCRIPTOR->changePlayerBackgroundColor([I)[I
move-result-object v$register
"""
)
}
Triple(
parameters,
getInstruction<ReferenceInstruction>(invokeVirtualIndex).reference,
@ -408,7 +445,6 @@ val playerComponentsPatch = bytecodePatch(
}.forEach { method ->
method.apply {
val freeRegister = implementation!!.registerCount - parameters.size - 3
val invokeDirectIndex =
indexOfFirstInstructionReversedOrThrow(Opcode.INVOKE_DIRECT)
val invokeDirectReference =
@ -416,7 +452,7 @@ val playerComponentsPatch = bytecodePatch(
addInstructionsWithLabels(
invokeDirectIndex + 1, """
invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->enableColorMatchPlayer()Z
invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->changeMiniPlayerColor()Z
move-result v$freeRegister
if-eqz v$freeRegister, :off
invoke-virtual {p1}, $colorMathPlayerInvokeVirtualReference
@ -434,14 +470,70 @@ val playerComponentsPatch = bytecodePatch(
addSwitchPreference(
CategoryType.PLAYER,
"revanced_enable_color_match_player",
"revanced_change_miniplayer_color",
"true"
)
addSwitchPreference(
CategoryType.PLAYER,
"revanced_enable_black_player_background",
"revanced_change_player_background_color",
"false"
)
addPreferenceWithIntent(
CategoryType.PLAYER,
"revanced_custom_player_background_color_primary",
"revanced_change_player_background_color"
)
addPreferenceWithIntent(
CategoryType.PLAYER,
"revanced_custom_player_background_color_secondary",
"revanced_change_player_background_color"
)
// endregion
// region patch for enable thick seek bar
var thickSeekBar = false
fun MutableMethod.thickSeekBarHook(index: Int, methodName: String = "enableThickSeekBar") {
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
addInstructions(
index + 2, """
invoke-static {v$register}, $PLAYER_CLASS_DESCRIPTOR->$methodName(Z)Z
move-result v$register
"""
)
}
if (is_7_25_or_greater) {
val thickSeekBarMethodCall = thickSeekBarFeatureFlagFingerprint.methodCall()
val filter: Instruction.() -> Boolean = {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.toString() == thickSeekBarMethodCall
}
thickSeekBarInflateFingerprint.methodOrThrow().apply {
val indexes = findInstructionIndicesReversed(filter)
thickSeekBarHook(indexes.first(), "changeSeekBarPosition")
thickSeekBarHook(indexes.last())
}
if (is_7_29_or_greater) {
thickSeekBarColorFingerprint.methodOrThrow().apply {
findInstructionIndicesReversed(filter).forEach { thickSeekBarHook(it) }
}
}
addSwitchPreference(
CategoryType.PLAYER,
"revanced_change_seekbar_position",
"false"
)
thickSeekBar = true
}
// endregion
@ -483,7 +575,7 @@ val playerComponentsPatch = bytecodePatch(
addSwitchPreference(
CategoryType.PLAYER,
"revanced_disable_mini_player_gesture",
"revanced_disable_miniplayer_gesture",
"false"
)
addSwitchPreference(
@ -494,7 +586,7 @@ val playerComponentsPatch = bytecodePatch(
// endregion
// region patch for enable force minimized player
// region patch for forced minimized player
minimizedPlayerFingerprint.matchOrThrow().let {
it.method.apply {
@ -503,7 +595,7 @@ val playerComponentsPatch = bytecodePatch(
addInstructions(
insertIndex, """
invoke-static {v$insertRegister}, $PLAYER_CLASS_DESCRIPTOR->enableForceMinimizedPlayer(Z)Z
invoke-static {v$insertRegister}, $PLAYER_CLASS_DESCRIPTOR->enableForcedMiniPlayer(Z)Z
move-result v$insertRegister
"""
)
@ -512,13 +604,13 @@ val playerComponentsPatch = bytecodePatch(
addSwitchPreference(
CategoryType.PLAYER,
"revanced_enable_force_minimized_player",
"revanced_enable_forced_miniplayer",
"true"
)
// endregion
// region patch for enable swipe to dismiss mini player
// region patch for enable swipe to dismiss miniplayer
if (!is_6_42_or_greater) {
swipeToCloseFingerprint.methodOrThrow().apply {
@ -672,13 +764,21 @@ val playerComponentsPatch = bytecodePatch(
addSwitchPreference(
CategoryType.PLAYER,
"revanced_enable_swipe_to_dismiss_mini_player",
"revanced_enable_swipe_to_dismiss_miniplayer",
"true"
)
// endregion
// region patch for enable zen mode
if (thickSeekBar) {
addSwitchPreference(
CategoryType.PLAYER,
"revanced_enable_thick_seekbar",
"true"
)
}
// region patch for enable zen mode (~ 6.34)
// this method is used for old player background (deprecated since YT Music v6.34.51)
zenModeFingerprint.matchOrNull(miniPlayerConstructorFingerprint)?.let {
@ -698,20 +798,6 @@ val playerComponentsPatch = bytecodePatch(
}
} // no exception
switchToggleColorFingerprint.methodOrThrow(miniPlayerConstructorFingerprint).apply {
val invokeDirectIndex = indexOfFirstInstructionOrThrow(Opcode.INVOKE_DIRECT)
val walkerMethod = getWalkerMethod(invokeDirectIndex)
walkerMethod.addInstructions(
0, """
invoke-static {p1}, $PLAYER_CLASS_DESCRIPTOR->enableZenMode(I)I
move-result p1
invoke-static {p2}, $PLAYER_CLASS_DESCRIPTOR->enableZenMode(I)I
move-result p2
"""
)
}
addSwitchPreference(
CategoryType.PLAYER,
"revanced_enable_zen_mode",
@ -790,7 +876,7 @@ val playerComponentsPatch = bytecodePatch(
// endregion
// region patch for hide song video switch toggle
// region patch for hide song video toggle
audioVideoSwitchToggleFingerprint.methodOrThrow().apply {
implementation!!.instructions
@ -809,14 +895,14 @@ val playerComponentsPatch = bytecodePatch(
replaceInstruction(
index,
"invoke-static {v${instruction.registerC}, v${instruction.registerD}}," +
"$PLAYER_CLASS_DESCRIPTOR->hideAudioVideoSwitchToggle(Landroid/view/View;I)V"
"$PLAYER_CLASS_DESCRIPTOR->hideSongVideoToggle(Landroid/view/View;I)V"
)
}
}
addSwitchPreference(
CategoryType.PLAYER,
"revanced_hide_audio_video_switch_toggle",
"revanced_hide_song_video_toggle",
"false"
)
@ -850,17 +936,28 @@ val playerComponentsPatch = bytecodePatch(
// region patch for remember shuffle state
shuffleOnClickFingerprint.methodOrThrow().apply {
val accessibilityIndex = indexOfAccessibilityInstruction(this)
// region set shuffle enum
val enumClass = shuffleEnumFingerprint.methodOrThrow().definingClass
val enumIndex = indexOfFirstInstructionReversedOrThrow(accessibilityIndex) {
opcode == Opcode.INVOKE_DIRECT &&
getReference<MethodReference>()?.returnType == "Ljava/lang/String;"
val startIndex = indexOfFirstLiteralInstructionOrThrow(SHUFFLE_BUTTON_ID)
val (enumIndex, enumRegister) = if (is_8_03_or_greater) {
val index = indexOfFirstInstructionReversedOrThrow(startIndex) {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.returnType == enumClass
}
val register = getInstruction<OneRegisterInstruction>(index + 1).registerA
Pair(index + 2, register)
} else {
val index = indexOfFirstInstructionReversedOrThrow(startIndex) {
opcode == Opcode.INVOKE_DIRECT &&
getReference<MethodReference>()?.returnType == "Ljava/lang/String;"
}
val register = getInstruction<FiveRegisterInstruction>(index).registerD
Pair(index, register)
}
val enumRegister = getInstruction<FiveRegisterInstruction>(enumIndex).registerD
val enumClass =
(getInstruction<ReferenceInstruction>(enumIndex).reference as MethodReference).parameterTypes.first()
addInstruction(
enumIndex,
@ -872,7 +969,7 @@ val playerComponentsPatch = bytecodePatch(
// region set static field
val shuffleClassIndex =
indexOfFirstInstructionReversedOrThrow(accessibilityIndex, Opcode.CHECK_CAST)
indexOfFirstInstructionReversedOrThrow(enumIndex, Opcode.CHECK_CAST)
val shuffleClass =
getInstruction<ReferenceInstruction>(shuffleClassIndex).reference.toString()
val shuffleMutableClass = classBy { classDef ->
@ -901,17 +998,55 @@ val playerComponentsPatch = bytecodePatch(
// region make all methods accessible
fun Method.indexOfEnumOrdinalInstruction() =
indexOfFirstInstruction {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_VIRTUAL &&
reference?.name == "ordinal" &&
reference.definingClass == enumClass
}
val isShuffleMethod: Method.() -> Boolean = {
returnType == "V" &&
indexOfEnumOrdinalInstruction() >= 0 &&
indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "post"
} >= 0
}
val shuffleMethod = shuffleMutableClass.methods.find { method ->
method.parameterTypes.firstOrNull() == enumClass &&
method.parameterTypes.size == 1 &&
method.returnType == "V"
method.isShuffleMethod()
} ?: throw PatchException("shuffle method not found")
val shuffleMethodRegisterCount = shuffleMethod.implementation!!.registerCount
shuffleMutableClass.methods.add(
shuffleMethod.cloneMutable(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
name = "shuffleTracks"
)
name = "shuffleTracks",
registerCount = if (is_8_03_or_greater) {
shuffleMethodRegisterCount + 1
} else {
shuffleMethodRegisterCount
},
parameters = listOf(
ImmutableMethodParameter(
enumClass,
annotations,
"enumClass"
)
)
).apply {
if (is_8_03_or_greater) {
val index = indexOfEnumOrdinalInstruction()
val register = getInstruction<FiveRegisterInstruction>(index).registerC
addInstruction(
index,
"move-object/from16 v$register, p1"
)
}
}
)
// endregion
@ -944,7 +1079,7 @@ val playerComponentsPatch = bytecodePatch(
if (is_6_27_or_greater && !is_7_18_or_greater) {
oldEngagementPanelFingerprint.injectLiteralInstructionBooleanCall(
45427672L,
OLD_ENGAGEMENT_PANEL_FEATURE_FLAG,
"$PLAYER_CLASS_DESCRIPTOR->restoreOldCommentsPopUpPanels(Z)Z"
)
restoreOldCommentsPopupPanel = true
@ -1028,7 +1163,7 @@ val playerComponentsPatch = bytecodePatch(
if (oldPlayerBackgroundFingerprint.resolvable()) {
oldPlayerBackgroundFingerprint.injectLiteralInstructionBooleanCall(
45415319L,
OLD_PLAYER_BACKGROUND_FEATURE_FLAG,
"$PLAYER_CLASS_DESCRIPTOR->restoreOldPlayerBackground(Z)Z"
)
addSwitchPreference(
@ -1044,7 +1179,7 @@ val playerComponentsPatch = bytecodePatch(
if (oldPlayerLayoutFingerprint.resolvable()) {
oldPlayerLayoutFingerprint.injectLiteralInstructionBooleanCall(
45399578L,
OLD_PLAYER_LAYOUT_FEATURE_FLAG,
"$PLAYER_CLASS_DESCRIPTOR->restoreOldPlayerLayout(Z)Z"
)
addSwitchPreference(
@ -1060,29 +1195,3 @@ val playerComponentsPatch = bytecodePatch(
}
}
private fun MutableMethod.setInstanceFieldValue(
methodName: String,
viewId: Long
) {
val miniPlayerPlayPauseReplayButtonIndex =
indexOfFirstLiteralInstructionOrThrow(miniPlayerPlayPauseReplayButton)
val miniPlayerPlayPauseReplayButtonRegister =
getInstruction<OneRegisterInstruction>(miniPlayerPlayPauseReplayButtonIndex).registerA
val findViewByIdIndex =
indexOfFirstInstructionOrThrow(
miniPlayerPlayPauseReplayButtonIndex,
Opcode.INVOKE_VIRTUAL
)
val parentViewRegister =
getInstruction<FiveRegisterInstruction>(findViewByIdIndex).registerC
addInstructions(
miniPlayerPlayPauseReplayButtonIndex, """
const v$miniPlayerPlayPauseReplayButtonRegister, $viewId
invoke-virtual {v$parentViewRegister, v$miniPlayerPlayPauseReplayButtonRegister}, Landroid/view/View;->findViewById(I)Landroid/view/View;
move-result-object v$miniPlayerPlayPauseReplayButtonRegister
invoke-static {v$miniPlayerPlayPauseReplayButtonRegister}, $PLAYER_CLASS_DESCRIPTOR->$methodName(Landroid/view/View;)V
"""
)
}

View File

@ -1,10 +1,20 @@
package app.revanced.patches.music.utils
import app.revanced.patches.music.utils.resourceid.varispeedUnavailableTitle
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal const val ACTION_BAR_POSITION_FEATURE_FLAG = 45658717L
internal val actionBarPositionFeatureFlagFingerprint = legacyFingerprint(
name = "actionBarPositionFeatureFlagFingerprint",
returnType = "Z",
parameters = emptyList(),
literals = listOf(ACTION_BAR_POSITION_FEATURE_FLAG)
)
internal val pendingIntentReceiverFingerprint = legacyFingerprint(
name = "pendingIntentReceiverFingerprint",
returnType = "V",
@ -22,6 +32,14 @@ internal val playbackSpeedBottomSheetFingerprint = legacyFingerprint(
strings = listOf("PLAYBACK_RATE_MENU_BOTTOM_SHEET_FRAGMENT")
)
internal val playbackRateBottomSheetClassFingerprint = legacyFingerprint(
name = "playbackRateBottomSheetClassFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
literals = listOf(varispeedUnavailableTitle)
)
internal val playbackSpeedFingerprint = legacyFingerprint(
name = "playbackSpeedFingerprint",
returnType = "V",

View File

@ -15,7 +15,7 @@ internal object Constants {
"6.51.53", // This is the latest version of YouTube Music 6.xx.xx
"7.16.53", // This is the latest version that supports the 'Spoof app version' patch.
"7.25.53", // This is the last supported version for 2024.
"8.02.53", // This is the latest version supported by the RVX patch.
"8.05.51", // This is the latest version supported by the RVX patch.
)
)
}

View File

@ -2,13 +2,9 @@ package app.revanced.patches.music.utils.fix.client
import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val createPlayerRequestBodyFingerprint = legacyFingerprint(
name = "createPlayerRequestBodyFingerprint",
@ -22,26 +18,6 @@ internal val createPlayerRequestBodyFingerprint = legacyFingerprint(
strings = listOf("ms"),
)
internal val createPlayerRequestBodyWithVersionReleaseFingerprint = legacyFingerprint(
name = "createPlayerRequestBodyWithVersionReleaseFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L"),
strings = listOf("Google Inc."),
customFingerprint = { method, _ ->
indexOfBuildInstruction(method) >= 0
},
)
fun indexOfBuildInstruction(method: Method) =
method.indexOfFirstInstruction {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_VIRTUAL &&
reference?.name == "build" &&
reference.parameterTypes.isEmpty() &&
reference.returnType.startsWith("L")
}
internal val setPlayerRequestClientTypeFingerprint = legacyFingerprint(
name = "setPlayerRequestClientTypeFingerprint",
opcodes = listOf(
@ -63,4 +39,20 @@ internal val userAgentHeaderBuilderFingerprint = legacyFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("Landroid/content/Context;"),
strings = listOf("(Linux; U; Android "),
)
/**
* If this flag is activated, a playback issue occurs.
* (Regardless of the 'Spoof client')
*
* Added in YouTube Music 7.33+
*/
internal const val PLAYBACK_FEATURE_FLAG = 45665455L
internal val playbackFeatureFlagFingerprint = legacyFingerprint(
name = "playbackFeatureFlagFingerprint",
returnType = "Z",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
literals = listOf(PLAYBACK_FEATURE_FLAG),
)

View File

@ -3,24 +3,36 @@ package app.revanced.patches.music.utils.fix.client
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
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.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.music.utils.compatibility.Constants
import app.revanced.patches.music.utils.extension.Constants.MISC_PATH
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.VIDEO_PATH
import app.revanced.patches.music.utils.patch.PatchList.SPOOF_CLIENT
import app.revanced.patches.music.utils.playbackRateBottomSheetClassFingerprint
import app.revanced.patches.music.utils.playbackSpeedBottomSheetFingerprint
import app.revanced.patches.music.utils.playservice.is_7_25_or_greater
import app.revanced.patches.music.utils.playservice.is_7_33_or_greater
import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.resourceid.varispeedUnavailableTitle
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.spoof.blockrequest.blockRequestPatch
import app.revanced.patches.shared.createPlayerRequestBodyWithModelFingerprint
import app.revanced.patches.shared.customspeed.customPlaybackSpeedPatch
import app.revanced.patches.shared.extension.Constants.PATCHES_PATH
import app.revanced.patches.shared.extension.Constants.SPOOF_PATH
import app.revanced.patches.shared.indexOfBrandInstruction
import app.revanced.patches.shared.indexOfManufacturerInstruction
import app.revanced.patches.shared.indexOfModelInstruction
import app.revanced.util.Utils.printWarn
import app.revanced.patches.shared.indexOfReleaseInstruction
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.fingerprint.mutableClassOrThrow
@ -28,113 +40,129 @@ 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
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
private const val EXTENSION_CLASS_DESCRIPTOR =
"$MISC_PATH/SpoofClientPatch;"
"$SPOOF_PATH/SpoofClientPatch;"
private const val CLIENT_INFO_CLASS_DESCRIPTOR =
"Lcom/google/protos/youtube/api/innertube/InnertubeContext\$ClientInfo;"
@Suppress("unused")
val spoofClientPatch = bytecodePatch(
SPOOF_CLIENT.title,
SPOOF_CLIENT.summary,
false,
SPOOF_CLIENT.summary
) {
compatibleWith(
Constants.YOUTUBE_MUSIC_PACKAGE_NAME(
"6.20.51",
"6.29.59",
"6.42.55",
"6.51.53",
"7.16.53",
),
)
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
settingsPatch,
sharedResourceIdPatch,
blockRequestPatch,
versionCheckPatch,
customPlaybackSpeedPatch(
"$VIDEO_PATH/CustomPlaybackSpeedPatch;",
5.0f
),
)
execute {
if (is_7_25_or_greater) {
printWarn("\"${SPOOF_CLIENT.title}\" is not supported in this version. Use YouTube Music 7.24.51 or earlier.")
return@execute
}
lateinit var clientInfoReference: Reference
lateinit var clientIdReference: Reference
lateinit var clientVersionReference: Reference
lateinit var deviceBrandReference: Reference
lateinit var deviceMakeReference: Reference
lateinit var deviceModelReference: Reference
lateinit var osNameReference: Reference
lateinit var osVersionReference: Reference
fun MutableMethod.getFieldReference(index: Int) =
getInstruction<ReferenceInstruction>(index).reference
// region Get field references to be used below.
val (clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField) =
setPlayerRequestClientTypeFingerprint.matchOrThrow().let { result ->
with(result.method) {
// Field in the player request object that holds the client info object.
val clientInfoField = instructions.find { instruction ->
// requestMessage.clientInfo = clientInfoBuilder.build();
instruction.opcode == Opcode.IPUT_OBJECT &&
instruction.getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
}?.getReference<FieldReference>()
?: throw PatchException("Could not find clientInfoField")
// Client info object's client type field.
val clientInfoClientTypeField =
getInstruction(result.patternMatch!!.endIndex)
.getReference<FieldReference>()
?: throw PatchException("Could not find clientInfoClientTypeField")
val clientInfoVersionIndex = result.stringMatches!!.first().index
val clientInfoVersionRegister =
getInstruction<OneRegisterInstruction>(clientInfoVersionIndex).registerA
val clientInfoClientVersionFieldIndex =
indexOfFirstInstructionOrThrow(clientInfoVersionIndex) {
opcode == Opcode.IPUT_OBJECT &&
(this as TwoRegisterInstruction).registerA == clientInfoVersionRegister
}
// Client info object's client version field.
val clientInfoClientVersionField =
getInstruction(clientInfoClientVersionFieldIndex)
.getReference<FieldReference>()
?: throw PatchException("Could not find clientInfoClientVersionField")
Triple(clientInfoField, clientInfoClientTypeField, clientInfoClientVersionField)
}
}
val clientInfoClientModelField =
with(createPlayerRequestBodyWithModelFingerprint.methodOrThrow()) {
// The next IPUT_OBJECT instruction after getting the client model is setting the client model field.
val clientInfoClientModelIndex =
indexOfFirstInstructionOrThrow(indexOfModelInstruction(this)) {
val reference = getReference<FieldReference>()
opcode == Opcode.IPUT_OBJECT &&
reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR &&
reference.type == "Ljava/lang/String;"
}
getInstruction<ReferenceInstruction>(clientInfoClientModelIndex).reference
}
val clientInfoOsVersionField =
with(createPlayerRequestBodyWithVersionReleaseFingerprint.methodOrThrow()) {
val buildIndex = indexOfBuildInstruction(this)
val clientInfoOsVersionIndex = indexOfFirstInstructionOrThrow(buildIndex - 5) {
val reference = getReference<FieldReference>()
setPlayerRequestClientTypeFingerprint.matchOrThrow().let {
it.method.apply {
val clientInfoIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.IPUT_OBJECT &&
reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR &&
reference.type == "Ljava/lang/String;"
getReference<FieldReference>()?.type == CLIENT_INFO_CLASS_DESCRIPTOR
}
getInstruction<ReferenceInstruction>(clientInfoOsVersionIndex).reference
val clientIdIndex = it.patternMatch!!.endIndex
val dummyClientVersionIndex = it.stringMatches!!.first().index
val dummyClientVersionRegister =
getInstruction<OneRegisterInstruction>(dummyClientVersionIndex).registerA
val clientVersionIndex =
indexOfFirstInstructionOrThrow(dummyClientVersionIndex) {
opcode == Opcode.IPUT_OBJECT &&
(this as TwoRegisterInstruction).registerA == dummyClientVersionRegister
}
clientInfoReference =
getFieldReference(clientInfoIndex)
clientIdReference =
getFieldReference(clientIdIndex)
clientVersionReference =
getFieldReference(clientVersionIndex)
}
}
fun MutableMethod.getClientInfoIndex(
startIndex: Int,
reversed: Boolean = false
): Int {
val filter: Instruction.() -> Boolean = {
val reference = getReference<FieldReference>()
opcode == Opcode.IPUT_OBJECT &&
reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR &&
reference.type == "Ljava/lang/String;"
}
return if (reversed) {
indexOfFirstInstructionReversedOrThrow(startIndex, filter)
} else {
indexOfFirstInstructionOrThrow(startIndex, filter)
}
}
createPlayerRequestBodyWithModelFingerprint.methodOrThrow().apply {
val buildManufacturerIndex =
indexOfManufacturerInstruction(this)
val deviceBrandIndex =
getClientInfoIndex(indexOfBrandInstruction(this))
val deviceMakeIndex =
getClientInfoIndex(buildManufacturerIndex)
val deviceModelIndex =
getClientInfoIndex(indexOfModelInstruction(this))
val chipSetIndex =
getClientInfoIndex(buildManufacturerIndex, true)
val osNameIndex =
getClientInfoIndex(chipSetIndex - 1, true)
val osVersionIndex =
getClientInfoIndex(indexOfReleaseInstruction(this))
deviceBrandReference =
getFieldReference(deviceBrandIndex)
deviceMakeReference =
getFieldReference(deviceMakeIndex)
deviceModelReference =
getFieldReference(deviceModelIndex)
osNameReference =
getFieldReference(osNameIndex)
osVersionReference =
getFieldReference(osVersionIndex)
}
// endregion
@ -181,31 +209,49 @@ val spoofClientPatch = bytecodePatch(
move-result v0
if-eqz v0, :disabled
iget-object v0, p0, $clientInfoField
iget-object v0, p0, $clientInfoReference
# Set client type to the spoofed value.
iget v1, v0, $clientInfoClientTypeField
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientTypeId(I)I
# Set client id.
iget v1, v0, $clientIdReference
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientId(I)I
move-result v1
iput v1, v0, $clientInfoClientTypeField
# Set client model to the spoofed value.
iget-object v1, v0, $clientInfoClientModelField
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientModel(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $clientInfoClientModelField
iput v1, v0, $clientIdReference
# Set client version to the spoofed value.
iget-object v1, v0, $clientInfoClientVersionField
# Set client version.
iget-object v1, v0, $clientVersionReference
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getClientVersion(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $clientInfoClientVersionField
iput-object v1, v0, $clientVersionReference
# Set client os version to the spoofed value.
iget-object v1, v0, $clientInfoOsVersionField
# Set device brand.
iget-object v1, v0, $deviceBrandReference
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getDeviceBrand(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $deviceBrandReference
# Set device make.
iget-object v1, v0, $deviceMakeReference
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getDeviceMake(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $deviceMakeReference
# Set device model.
iget-object v1, v0, $deviceModelReference
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getDeviceModel(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $deviceModelReference
# Set os name.
iget-object v1, v0, $osNameReference
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getOsName(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $osNameReference
# Set os version.
iget-object v1, v0, $osVersionReference
invoke-static { v1 }, $EXTENSION_CLASS_DESCRIPTOR->getOsVersion(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
iput-object v1, v0, $clientInfoOsVersionField
iput-object v1, v0, $osVersionReference
:disabled
return-void
@ -236,6 +282,7 @@ val spoofClientPatch = bytecodePatch(
// region fix for playback speed menu is not available in Podcasts
// for iOS Music
playbackSpeedBottomSheetFingerprint.mutableClassOrThrow().let {
val onItemClickMethod =
it.methods.find { method -> method.name == "onItemClick" }
@ -271,12 +318,48 @@ val spoofClientPatch = bytecodePatch(
}
}
// for Android Music
playbackRateBottomSheetClassFingerprint.methodOrThrow().apply {
val literalIndex =
indexOfFirstLiteralInstructionOrThrow(varispeedUnavailableTitle)
val insertIndex =
indexOfFirstInstructionReversedOrThrow(literalIndex, Opcode.IF_EQZ)
val insertRegister =
getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstructions(
insertIndex,
"""
invoke-static { v$insertRegister }, $EXTENSION_CLASS_DESCRIPTOR->forceCreatePlaybackSpeedMenuInverse(Z)Z
move-result v$insertRegister
""",
)
}
// endregion
// region fix for feature flags
if (is_7_33_or_greater) {
playbackFeatureFlagFingerprint.injectLiteralInstructionBooleanCall(
PLAYBACK_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->forceDisablePlaybackFeatureFlag(Z)Z"
)
}
// endregion
findMethodOrThrow("$PATCHES_PATH/PatchStatus;") {
name == "SpoofClient"
}.replaceInstruction(
0,
"const/4 v0, 0x1"
)
addSwitchPreference(
CategoryType.MISC,
"revanced_spoof_client",
"false"
"true"
)
addPreferenceWithIntent(
CategoryType.MISC,

Some files were not shown because too many files have changed in this diff Show More