mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-04-29 22:24:31 +02:00
Merge branch 'dev' into revanced-extended
This commit is contained in:
commit
1926becdfc
115
README.md
115
README.md
@ -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": []
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,6 @@ public class PatchStatus {
|
||||
}
|
||||
|
||||
public static String SpoofAppVersionDefaultString() {
|
||||
return "6.11.52";
|
||||
return "6.42.55";
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
@ -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
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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() {
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"
|
||||
);
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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" },
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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));
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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" }
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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));
|
||||
|
@ -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"})
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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].
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
21
extensions/spoof-wifi/build.gradle.kts
Normal file
21
extensions/spoof-wifi/build.gradle.kts
Normal 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)
|
||||
}
|
4
extensions/spoof-wifi/src/main/AndroidManifest.xml
Normal file
4
extensions/spoof-wifi/src/main/AndroidManifest.xml
Normal 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>
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
312
patches.json
312
patches.json
@ -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.",
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
),
|
||||
}
|
@ -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
|
@ -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,
|
@ -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)
|
||||
|
||||
|
@ -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")
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
"""
|
||||
)
|
||||
|
@ -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
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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)) {
|
||||
|
@ -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"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -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",
|
||||
|
@ -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
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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.
|
||||
)
|
||||
)
|
||||
}
|
@ -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),
|
||||
)
|
@ -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
Loading…
x
Reference in New Issue
Block a user