diff --git a/README.md b/README.md index 00d0e4ad8..e15af6bce 100644 --- a/README.md +++ b/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 | ### [📦 `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 | @@ -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": [] } diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/actionbar/ActionBarPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/actionbar/ActionBarPatch.java index d973918ee..8f20d153b 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/actionbar/ActionBarPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/actionbar/ActionBarPatch.java @@ -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 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) { diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumPromotionPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumPromotionPatch.java index 7a863606f..e8dd2be41 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumPromotionPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumPromotionPatch.java @@ -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); + } } } \ No newline at end of file diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumRenewalPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumRenewalPatch.java index f5efd9c56..b514a85cf 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumRenewalPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumRenewalPatch.java @@ -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); + } + } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/ActionButtonsFilter.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/ActionButtonsFilter.java index f0d636279..454abb0d7 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/ActionButtonsFilter.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/ActionButtonsFilter.java @@ -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( diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/PlayerFlyoutMenuFilter.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/PlayerFlyoutMenuFilter.java index 5f6701466..d1350ef41 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/PlayerFlyoutMenuFilter.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/PlayerFlyoutMenuFilter.java @@ -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" ) ); } diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/flyout/FlyoutPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/flyout/FlyoutPatch.java index d3b86723d..71ae52a34 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/flyout/FlyoutPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/flyout/FlyoutPatch.java @@ -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 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 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; } } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/ChangeStartPagePatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/ChangeStartPagePatch.java new file mode 100644 index 000000000..d837c8827 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/ChangeStartPagePatch.java @@ -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); + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/GeneralPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/GeneralPatch.java index 3e0dd7afc..dea03dfc7 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/GeneralPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/GeneralPatch.java @@ -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; diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/SettingsMenuPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/SettingsMenuPatch.java index 27359dcc9..92fb9030a 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/SettingsMenuPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/SettingsMenuPatch.java @@ -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; } } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/SpoofClientPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/SpoofClientPatch.java deleted file mode 100644 index e3e651a36..000000000 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/SpoofClientPatch.java +++ /dev/null @@ -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. - *

- * 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. - *

- * Return true to force create the playback speed menu. - */ - public static boolean forceCreatePlaybackSpeedMenu(boolean original) { - if (SPOOF_CLIENT) { - return true; - } - return original; - } -} \ No newline at end of file diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/client/AppClient.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/client/AppClient.java deleted file mode 100644 index 487ef08c4..000000000 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/client/AppClient.java +++ /dev/null @@ -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 - * client type - */ - 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; - } - } -} diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/player/PlayerPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/player/PlayerPatch.java index 71eed73e2..ed34d5cb1 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/player/PlayerPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/player/PlayerPatch.java @@ -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 previousButtonViewRef = new WeakReference<>(null); + private static WeakReference 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(); diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/DrawableColorPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/DrawableColorPatch.java index 18da455ad..b7b0f9175 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/DrawableColorPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/DrawableColorPatch.java @@ -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); + } + }); } } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/PatchStatus.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/PatchStatus.java index 1976df37c..3280fb59a 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/PatchStatus.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/PatchStatus.java @@ -12,6 +12,6 @@ public class PatchStatus { } public static String SpoofAppVersionDefaultString() { - return "6.11.52"; + return "6.42.55"; } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/ReturnYouTubeDislikePatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/ReturnYouTubeDislikePatch.java index 5e74f70a5..50c43f79c 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/ReturnYouTubeDislikePatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/ReturnYouTubeDislikePatch.java @@ -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. */ diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/returnyoutubedislike/ReturnYouTubeDislike.java b/extensions/shared/src/main/java/app/revanced/extension/music/returnyoutubedislike/ReturnYouTubeDislike.java index 69389d1a7..faffaad72 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/returnyoutubedislike/ReturnYouTubeDislike.java @@ -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); diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/Settings.java index 43da6f9c4..8343ac79e 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/settings/Settings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/Settings.java @@ -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 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 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 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, }; /** diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java index 67c442d04..d06d664cd 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java @@ -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); } diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ResettableListPreference.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ResettableListPreference.java index f0c68b46f..c70b2c084 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ResettableListPreference.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ResettableListPreference.java @@ -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) diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/shared/NavigationBar.java b/extensions/shared/src/main/java/app/revanced/extension/music/shared/NavigationBar.java new file mode 100644 index 000000000..0dec169ef --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/music/shared/NavigationBar.java @@ -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; + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/utils/ExtendedUtils.java b/extensions/shared/src/main/java/app/revanced/extension/music/utils/ExtendedUtils.java index 5de71744e..ed5bebf89 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/utils/ExtendedUtils.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/utils/ExtendedUtils.java @@ -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; diff --git a/extensions/shared/src/main/java/app/revanced/extension/reddit/settings/ActivityHook.java b/extensions/shared/src/main/java/app/revanced/extension/reddit/settings/ActivityHook.java index ffe8bfd7d..09f0eabaf 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/reddit/settings/ActivityHook.java +++ b/extensions/shared/src/main/java/app/revanced/extension/reddit/settings/ActivityHook.java @@ -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(); diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/FullscreenAdsPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/FullscreenAdsPatch.java index 341f8748e..e60a4ddf0 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/FullscreenAdsPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/FullscreenAdsPatch.java @@ -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) + *

+ * 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; } diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/PatchStatus.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/PatchStatus.java index 20bf9d5e0..8282eadf5 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/PatchStatus.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/PatchStatus.java @@ -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; } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/ReturnYouTubeUsernamePatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/ReturnYouTubeUsernamePatch.java index 45a79db40..a0b009040 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/ReturnYouTubeUsernamePatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/ReturnYouTubeUsernamePatch.java @@ -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); } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/misc/WatchHistoryPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/WatchHistoryPatch.java similarity index 83% rename from extensions/shared/src/main/java/app/revanced/extension/youtube/patches/misc/WatchHistoryPatch.java rename to extensions/shared/src/main/java/app/revanced/extension/shared/patches/WatchHistoryPatch.java index 01a002be4..46e3d1cf9 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/misc/WatchHistoryPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/WatchHistoryPatch.java @@ -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) { diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/MusicAppClient.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/MusicAppClient.java new file mode 100644 index 000000000..21b580c8d --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/MusicAppClient.java @@ -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 + * client type + */ + 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; + } + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/AppClient.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeAppClient.kt similarity index 84% rename from extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/AppClient.kt rename to extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeAppClient.kt index 9ebc13426..c50bca220 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/AppClient.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeAppClient.kt @@ -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 { - 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 = 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 = 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 = arrayOf( ANDROID_VR, - ANDROID_MUSIC, + ANDROID_VR_NO_AUTH, ) } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/WebClient.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeWebClient.kt similarity index 97% rename from extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/WebClient.kt rename to extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeWebClient.kt index f39a56c60..331d1d9a4 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/WebClient.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeWebClient.kt @@ -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 diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spans/InclusiveSpanPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spans/InclusiveSpanPatch.java index ceacf6f67..a3a30b96c 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spans/InclusiveSpanPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spans/InclusiveSpanPatch.java @@ -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 { diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spans/SpanType.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spans/SpanType.java index 0ba705410..593671b24 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spans/SpanType.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spans/SpanType.java @@ -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 diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/BlockRequestPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/BlockRequestPatch.java new file mode 100644 index 000000000..7e90cfce9 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/BlockRequestPatch.java @@ -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. + *

+ * 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; + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofClientPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofClientPatch.java new file mode 100644 index 000000000..f26063a4f --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofClientPatch.java @@ -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. + *

+ * 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. + *

+ * Return true to force create the playback speed menu. + */ + public static boolean forceCreatePlaybackSpeedMenu(boolean original) { + if (SPOOF_CLIENT) { + return true; + } + return original; + } + + /** + * Injection point. + *

+ * 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. + *

+ * Return false to force create the playback speed menu. + */ + public static boolean forceCreatePlaybackSpeedMenuInverse(boolean original) { + if (SPOOF_CLIENT) { + return false; + } + return original; + } + + /** + * Injection point. + *

+ * Return false to force disable playback feature flag. + */ + public static boolean forceDisablePlaybackFeatureFlag(boolean original) { + if (SPOOF_CLIENT) { + return false; + } + return original; + } +} \ No newline at end of file diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java index 124790b88..1580244a8 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/SpoofStreamingDataPatch.java @@ -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. - *

- * 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 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. - *

- * 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. - *

- * - * @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 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 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; } } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.kt index 5bd228d84..39ece7fd9 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.kt @@ -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, diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt index bd82b3fee..ab6dba404 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/StreamingDataRequest.kt @@ -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 = - availableClientTypes(SPOOF_STREAMING_DATA_TYPE) + private val CLIENT_ORDER_TO_USE: Array = + 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 = Collections.synchronizedMap( object : LinkedHashMap(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): Boolean { @@ -163,7 +155,7 @@ class StreamingDataRequest private constructor( } private fun send( - clientType: AppClient.ClientType, + clientType: YouTubeAppClient.ClientType, videoId: String, playerHeaders: Map, 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 diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/returnyoutubeusername/requests/ChannelRequest.java b/extensions/shared/src/main/java/app/revanced/extension/shared/returnyoutubeusername/requests/ChannelRequest.java index 84e02755b..492a7598e 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/returnyoutubeusername/requests/ChannelRequest.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/returnyoutubeusername/requests/ChannelRequest.java @@ -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) { diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/returnyoutubeusername/requests/ChannelRoutes.java b/extensions/shared/src/main/java/app/revanced/extension/shared/returnyoutubeusername/requests/ChannelRoutes.java index 14da59603..414dc7fbd 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/returnyoutubeusername/requests/ChannelRoutes.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/returnyoutubeusername/requests/ChannelRoutes.java @@ -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() { } diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java index 63c3c5b05..c212cef74 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java @@ -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 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 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 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 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 SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", ClientType.ANDROID_VR, true); + public static final EnumSetting 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 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 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 */ diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/HtmlSwitchPreference.java b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/HtmlSwitchPreference.java new file mode 100644 index 000000000..382e898f7 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/HtmlSwitchPreference.java @@ -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); + } +} \ No newline at end of file diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/utils/Utils.java b/extensions/shared/src/main/java/app/revanced/extension/shared/utils/Utils.java index 5ef12c4a8..33154e8c8 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/utils/Utils.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/utils/Utils.java @@ -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. diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/ads/AdsPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/ads/AdsPatch.java index 9eb1aa7b1..f3ba3f78c 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/ads/AdsPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/ads/AdsPatch.java @@ -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 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; } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/ActionButtonsFilter.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/ActionButtonsFilter.java index 69386f21f..680846c13 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/ActionButtonsFilter.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/ActionButtonsFilter.java @@ -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; } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/AdsFilter.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/AdsFilter.java index bb98225f6..84d2ceaa5 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/AdsFilter.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/AdsFilter.java @@ -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" ); diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/CarouselShelfFilter.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/CarouselShelfFilter.java index ebb05bd72..939760ae0 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/CarouselShelfFilter.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/CarouselShelfFilter.java @@ -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 ); diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/FeedComponentsFilter.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/FeedComponentsFilter.java index 707b5511d..6363af990 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/FeedComponentsFilter.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/FeedComponentsFilter.java @@ -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 ); diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/FeedVideoViewsFilter.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/FeedVideoViewsFilter.java index 9b0779ecc..271f8a611 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/FeedVideoViewsFilter.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/FeedVideoViewsFilter.java @@ -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 diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/PlayerComponentsFilter.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/PlayerComponentsFilter.java index 627469cd9..a47e59476 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/PlayerComponentsFilter.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/components/PlayerComponentsFilter.java @@ -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()) { diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/feed/FeedPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/feed/FeedPatch.java index 9ab4dd81d..70fa37edc 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/feed/FeedPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/feed/FeedPatch.java @@ -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; + } + } } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/feed/RelatedVideoPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/feed/RelatedVideoPatch.java index ccc20a631..cc1c9d59a 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/feed/RelatedVideoPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/feed/RelatedVideoPatch.java @@ -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; diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/ChangeStartPagePatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/ChangeStartPagePatch.java deleted file mode 100644 index 7ef55d606..000000000 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/ChangeStartPagePatch.java +++ /dev/null @@ -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. - *

- * 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; - } - } -} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/ChangeStartPagePatch.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/ChangeStartPagePatch.kt new file mode 100644 index 000000000..5625e07d3 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/ChangeStartPagePatch.kt @@ -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 + } + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/GeneralPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/GeneralPatch.java index db497199e..531af491c 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/GeneralPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/GeneralPatch.java @@ -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; } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/OpenChannelOfLiveAvatarPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/OpenChannelOfLiveAvatarPatch.java index bce03fedf..489ac1cae 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/OpenChannelOfLiveAvatarPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/OpenChannelOfLiveAvatarPatch.java @@ -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 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); - } - } - } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/SettingsMenuPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/SettingsMenuPatch.java index 84d4e6e50..8525cff82 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/SettingsMenuPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/SettingsMenuPatch.java @@ -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; } } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/requests/VideoDetailsRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/requests/VideoDetailsRequest.kt index e7eddf002..1db71775a 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/requests/VideoDetailsRequest.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/requests/VideoDetailsRequest.kt @@ -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" }, diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/misc/BackgroundPlaybackPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/misc/BackgroundPlaybackPatch.java index 39dcff1a7..e6172078d 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/misc/BackgroundPlaybackPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/misc/BackgroundPlaybackPatch.java @@ -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(); } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/ActionButtonsPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/ActionButtonsPatch.java index 9cd6f6a0c..f3207ae8f 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/ActionButtonsPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/ActionButtonsPatch.java @@ -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 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 hideActionButtonByIndex(@Nullable List 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); + } + } } } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/MiniplayerPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/MiniplayerPatch.java similarity index 74% rename from extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/MiniplayerPatch.java rename to extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/MiniplayerPatch.java index 615d27e10..687c0263f 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/MiniplayerPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/MiniplayerPatch.java @@ -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. *

@@ -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); } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java index e7e187d1a..f87708b9a 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/PlayerPatch.java @@ -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() { diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/SeekbarColorPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/SeekbarColorPatch.java index 256d19902..8b3b99528 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/SeekbarColorPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/SeekbarColorPatch.java @@ -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. *

@@ -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. + *

+ * 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)); diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/requests/ActionButtonRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/requests/ActionButtonRequest.kt new file mode 100644 index 000000000..a10bd26d4 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/player/requests/ActionButtonRequest.kt @@ -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, +) { + private val future: Future> = Utils.submitOnBackgroundThread { + fetch(videoId, playerHeaders) + } + + val array: Array + 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 = Collections.synchronizedMap( + object : LinkedHashMap(100) { + private val CACHE_LIMIT = 50 + + override fun removeEldestEntry(eldest: Map.Entry): Boolean { + return size > CACHE_LIMIT // Evict the oldest entry if over the cache limit. + } + }) + + @JvmStatic + fun fetchRequestIfNeeded(videoId: String, playerHeaders: Map) { + 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): 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 { + 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(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): Array { + val json = sendRequest(videoId, playerHeaders) + if (json != null) { + return parseResponse(json) + } + + return emptyArray() + } + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlaybackSpeedWhilePlayingPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlaybackSpeedWhilePlayingPatch.java index 257c92644..67e65bd5c 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlaybackSpeedWhilePlayingPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlaybackSpeedWhilePlayingPatch.java @@ -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; } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/MusicRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/MusicRequest.kt index 5ceb2615f..d48ca8b51 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/MusicRequest.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/requests/MusicRequest.kt @@ -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" } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java index ff4bcb090..afd77628f 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -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 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 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 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 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)); diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedSettingsPreference.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedSettingsPreference.java index a3987e7b9..43ee95719 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedSettingsPreference.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedSettingsPreference.java @@ -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 */ diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java index 99958d018..b116a3d5c 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java @@ -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)); diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/WatchHistoryStatusPreference.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/WatchHistoryStatusPreference.java index 104785fc1..c7f1e812a 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/WatchHistoryStatusPreference.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/WatchHistoryStatusPreference.java @@ -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"}) diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/EngagementPanel.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/EngagementPanel.java new file mode 100644 index 000000000..11c4b3c8b --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/EngagementPanel.java @@ -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 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(); + } + +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt index 201d32085..8b5a7bd0e 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/PlayerType.kt @@ -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]. diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/ExtendedUtils.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/ExtendedUtils.java index 3af96e1c8..fae38500b 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/ExtendedUtils.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/ExtendedUtils.java @@ -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; diff --git a/extensions/shared/stub/src/main/java/com/facebook/litho/ComponentHost.java b/extensions/shared/stub/src/main/java/com/facebook/litho/ComponentHost.java new file mode 100644 index 000000000..49e69e237 --- /dev/null +++ b/extensions/shared/stub/src/main/java/com/facebook/litho/ComponentHost.java @@ -0,0 +1,19 @@ +package com.facebook.litho; + +import android.content.Context; +import android.view.ViewGroup; + +/** + * "CompileOnly" class + *

+ * 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) { + } +} diff --git a/extensions/spoof-wifi/build.gradle.kts b/extensions/spoof-wifi/build.gradle.kts new file mode 100644 index 000000000..4f8e643cd --- /dev/null +++ b/extensions/spoof-wifi/build.gradle.kts @@ -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) +} diff --git a/extensions/spoof-wifi/src/main/AndroidManifest.xml b/extensions/spoof-wifi/src/main/AndroidManifest.xml new file mode 100644 index 000000000..7425a54c5 --- /dev/null +++ b/extensions/spoof-wifi/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + diff --git a/extensions/spoof-wifi/src/main/java/app/revanced/extension/all/connectivity/wifi/spoof/SpoofWifiPatch.java b/extensions/spoof-wifi/src/main/java/app/revanced/extension/all/connectivity/wifi/spoof/SpoofWifiPatch.java new file mode 100644 index 000000000..f266841d8 --- /dev/null +++ b/extensions/spoof-wifi/src/main/java/app/revanced/extension/all/connectivity/wifi/spoof/SpoofWifiPatch.java @@ -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 { + private final T value; + private final boolean isPresent; + + private Option(T value, boolean isPresent) { + this.value = value; + this.isPresent = isPresent; + } + + private static Option of(T value) { + return new Option<>(value, true); + } + + private static Option 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 request, + Option networkCallback, + Option operation, + Option 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; + } + } +} diff --git a/gradle.properties b/gradle.properties index c22c51760..b7236d61b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e1b837a19..d71047787 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/patches.json b/patches.json index 36c4d06e0..808b2b456 100644 --- a/patches.json +++ b/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.", diff --git a/patches/api/patches.api b/patches/api/patches.api index cec332225..371bc0a1a 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -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 diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.kt new file mode 100644 index 000000000..3758030cd --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWifiPatch.kt @@ -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( + 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, + 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", + ), +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/transformation/MethodCall.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/transformation/MethodCall.kt similarity index 98% rename from patches/src/main/kotlin/app/revanced/patches/shared/transformation/MethodCall.kt rename to patches/src/main/kotlin/app/revanced/patches/all/misc/transformation/MethodCall.kt index 37213570e..a033333fb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/transformation/MethodCall.kt +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/transformation/MethodCall.kt @@ -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 diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/transformation/TransformInstructionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/transformation/TransformInstructionsPatch.kt similarity index 84% rename from patches/src/main/kotlin/app/revanced/patches/shared/transformation/TransformInstructionsPatch.kt rename to patches/src/main/kotlin/app/revanced/patches/all/misc/transformation/TransformInstructionsPatch.kt index c01aa1456..7d5bcb0ad 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/transformation/TransformInstructionsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/transformation/TransformInstructionsPatch.kt @@ -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 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 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, diff --git a/patches/src/main/kotlin/app/revanced/patches/music/actionbar/components/ActionBarComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/actionbar/components/ActionBarComponentsPatch.kt index c596d8590..506dac546 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/actionbar/components/ActionBarComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/actionbar/components/ActionBarComponentsPatch.kt @@ -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) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/actionbar/components/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/actionbar/components/Fingerprints.kt index e7e9eb934..0584f618f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/actionbar/components/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/actionbar/components/Fingerprints.kt @@ -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") +) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/ads/general/AdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/ads/general/AdsPatch.kt index 15fc2d864..9ae14aea4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/ads/general/AdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/ads/general/AdsPatch.kt @@ -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(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(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, diff --git a/patches/src/main/kotlin/app/revanced/patches/music/ads/general/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/ads/general/Fingerprints.kt index 1c06ae47d..a70e8e57a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/ads/general/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/ads/general/Fingerprints.kt @@ -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()?.toString() == "Landroid/app/Dialog;->setContentView(Landroid/view/View;)V" + } + internal val getPremiumTextViewFingerprint = legacyFingerprint( name = "getPremiumTextViewFingerprint", returnType = "V", diff --git a/patches/src/main/kotlin/app/revanced/patches/music/flyoutmenu/components/FlyoutMenuComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/flyoutmenu/components/FlyoutMenuComponentsPatch.kt index 17b3cae4c..8848c8d88 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/flyoutmenu/components/FlyoutMenuComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/flyoutmenu/components/FlyoutMenuComponentsPatch.kt @@ -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", diff --git a/patches/src/main/kotlin/app/revanced/patches/music/general/spoofappversion/SpoofAppVersionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/general/spoofappversion/SpoofAppVersionPatch.kt index 1cfc4789a..f910e84d1 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/general/spoofappversion/SpoofAppVersionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/general/spoofappversion/SpoofAppVersionPatch.kt @@ -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) { diff --git a/patches/src/main/kotlin/app/revanced/patches/music/general/startpage/ChangeStartPagePatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/general/startpage/ChangeStartPagePatch.kt index d9d81d9e0..92aa964c8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/general/startpage/ChangeStartPagePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/general/startpage/ChangeStartPagePatch.kt @@ -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 """ ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/general/startpage/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/general/startpage/Fingerprints.kt index 9da6c2b7e..136443f6b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/general/startpage/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/general/startpage/Fingerprints.kt @@ -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 + ) ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch.kt index 90780c724..9fc7bf556 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch.kt @@ -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 ) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/theme/DarkThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/theme/DarkThemePatch.kt index 45f85b967..3379b60cb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/theme/DarkThemePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/theme/DarkThemePatch.kt @@ -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)) { diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/watchhistory/WatchHistoryPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/watchhistory/WatchHistoryPatch.kt new file mode 100644 index 000000000..fdc321bca --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/watchhistory/WatchHistoryPatch.kt @@ -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" + ) + } + +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/music/player/components/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/player/components/Fingerprints.kt index b1c99a5f6..0182bc141 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/player/components/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/player/components/Fingerprints.kt @@ -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()?.name == "announceForAccessibility" +internal val shuffleEnumFingerprint = legacyFingerprint( + name = "shuffleEnumFingerprint", + returnType = "V", + parameters = emptyList(), + strings = listOf( + "SHUFFLE_OFF", + "SHUFFLE_ALL", + "SHUFFLE_DISABLED", + ), + customFingerprint = { method, _ -> + method.name == "" } +) 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", diff --git a/patches/src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt index 6f49240a4..c39664845 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt @@ -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(miniPlayerPlayPauseReplayButtonIndex).registerA + val findViewByIdIndex = + indexOfFirstInstructionOrThrow( + miniPlayerPlayPauseReplayButtonIndex, + Opcode.INVOKE_VIRTUAL + ) + val parentViewRegister = + getInstruction(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(index + 1).registerA + + addInstructions( + index + 2, """ + invoke-static {v$register}, $PLAYER_CLASS_DESCRIPTOR->changePlayerBackgroundColor([I)[I + move-result-object v$register + """ + ) + } + Triple( parameters, getInstruction(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(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()?.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()?.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()?.returnType == enumClass + } + val register = getInstruction(index + 1).registerA + + Pair(index + 2, register) + } else { + val index = indexOfFirstInstructionReversedOrThrow(startIndex) { + opcode == Opcode.INVOKE_DIRECT && + getReference()?.returnType == "Ljava/lang/String;" + } + val register = getInstruction(index).registerD + + Pair(index, register) } - val enumRegister = getInstruction(enumIndex).registerD - val enumClass = - (getInstruction(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(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() + opcode == Opcode.INVOKE_VIRTUAL && + reference?.name == "ordinal" && + reference.definingClass == enumClass + } + + val isShuffleMethod: Method.() -> Boolean = { + returnType == "V" && + indexOfEnumOrdinalInstruction() >= 0 && + indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.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(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(miniPlayerPlayPauseReplayButtonIndex).registerA - val findViewByIdIndex = - indexOfFirstInstructionOrThrow( - miniPlayerPlayPauseReplayButtonIndex, - Opcode.INVOKE_VIRTUAL - ) - val parentViewRegister = - getInstruction(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 - """ - ) -} diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/Fingerprints.kt index 2bbf58903..3b66b1618 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/Fingerprints.kt @@ -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", diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/compatibility/Constants.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/compatibility/Constants.kt index f7d70c001..435f19c2e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/compatibility/Constants.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/compatibility/Constants.kt @@ -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. ) ) } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/Fingerprints.kt index fcbbdf9ac..f69913007 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/Fingerprints.kt @@ -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() - 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), ) \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/SpoofClientPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/SpoofClientPatch.kt index af3dc5463..386955825 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/SpoofClientPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/client/SpoofClientPatch.kt @@ -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(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()?.type == CLIENT_INFO_CLASS_DESCRIPTOR - }?.getReference() - ?: throw PatchException("Could not find clientInfoField") - - // Client info object's client type field. - val clientInfoClientTypeField = - getInstruction(result.patternMatch!!.endIndex) - .getReference() - ?: throw PatchException("Could not find clientInfoClientTypeField") - - val clientInfoVersionIndex = result.stringMatches!!.first().index - val clientInfoVersionRegister = - getInstruction(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() - ?: 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() - opcode == Opcode.IPUT_OBJECT && - reference?.definingClass == CLIENT_INFO_CLASS_DESCRIPTOR && - reference.type == "Ljava/lang/String;" - } - getInstruction(clientInfoClientModelIndex).reference - } - - val clientInfoOsVersionField = - with(createPlayerRequestBodyWithVersionReleaseFingerprint.methodOrThrow()) { - val buildIndex = indexOfBuildInstruction(this) - val clientInfoOsVersionIndex = indexOfFirstInstructionOrThrow(buildIndex - 5) { - val reference = getReference() + 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()?.type == CLIENT_INFO_CLASS_DESCRIPTOR } - getInstruction(clientInfoOsVersionIndex).reference + val clientIdIndex = it.patternMatch!!.endIndex + val dummyClientVersionIndex = it.stringMatches!!.first().index + val dummyClientVersionRegister = + getInstruction(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() + 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(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, diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/streamingdata/SpoofStreamingDataPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/streamingdata/SpoofStreamingDataPatch.kt deleted file mode 100644 index a30c435c2..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/fix/streamingdata/SpoofStreamingDataPatch.kt +++ /dev/null @@ -1,67 +0,0 @@ -package app.revanced.patches.music.utils.fix.streamingdata - -import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction -import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE -import app.revanced.patches.music.utils.compatibility.Constants.YOUTUBE_MUSIC_PACKAGE_NAME -import app.revanced.patches.music.utils.patch.PatchList.SPOOF_STREAMING_DATA -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.video.playerresponse.hookPlayerResponse -import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch -import app.revanced.patches.shared.extension.Constants.PATCHES_PATH -import app.revanced.patches.shared.extension.Constants.SPOOF_PATH -import app.revanced.patches.shared.spoof.streamingdata.baseSpoofStreamingDataPatch -import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch -import app.revanced.util.findMethodOrThrow - -const val EXTENSION_CLASS_DESCRIPTOR = - "$SPOOF_PATH/SpoofStreamingDataPatch;" - -@Suppress("unused") -val spoofStreamingDataPatch = baseSpoofStreamingDataPatch( - { - compatibleWith(COMPATIBLE_PACKAGE) - - dependsOn( - baseSpoofUserAgentPatch(YOUTUBE_MUSIC_PACKAGE_NAME), - settingsPatch, - playerResponseMethodHookPatch, - ) - }, - { - findMethodOrThrow("$PATCHES_PATH/PatchStatus;") { - name == "SpoofStreamingDataMusic" - }.replaceInstruction( - 0, - "const/4 v0, 0x1" - ) - - hookPlayerResponse( - "$EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;)V", - true - ) - - addSwitchPreference( - CategoryType.MISC, - "revanced_spoof_streaming_data", - "true" - ) - addPreferenceWithIntent( - CategoryType.MISC, - "revanced_spoof_streaming_data_type", - "revanced_spoof_streaming_data" - ) - addSwitchPreference( - CategoryType.MISC, - "revanced_spoof_streaming_data_stats_for_nerds", - "true", - "revanced_spoof_streaming_data" - ) - - updatePatchStatus(SPOOF_STREAMING_DATA) - - } -) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/flyoutmenu/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/flyoutmenu/Fingerprints.kt deleted file mode 100644 index 17366b163..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/flyoutmenu/Fingerprints.kt +++ /dev/null @@ -1,14 +0,0 @@ -package app.revanced.patches.music.utils.flyoutmenu - -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 - -internal val playbackRateBottomSheetClassFingerprint = legacyFingerprint( - name = "playbackRateBottomSheetClassFingerprint", - returnType = "V", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = emptyList(), - literals = listOf(varispeedUnavailableTitle) -) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/flyoutmenu/FlyoutMenuHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/flyoutmenu/FlyoutMenuHookPatch.kt index c2d7b0c8f..4a175811c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/flyoutmenu/FlyoutMenuHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/flyoutmenu/FlyoutMenuHookPatch.kt @@ -2,6 +2,8 @@ package app.revanced.patches.music.utils.flyoutmenu import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.music.utils.extension.Constants.EXTENSION_PATH +import app.revanced.patches.music.utils.extension.sharedExtensionPatch +import app.revanced.patches.music.utils.playbackRateBottomSheetClassFingerprint import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch import app.revanced.util.addStaticFieldToExtension import app.revanced.util.fingerprint.methodOrThrow @@ -12,7 +14,10 @@ private const val EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR = val flyoutMenuHookPatch = bytecodePatch( description = "flyoutMenuHookPatch", ) { - dependsOn(sharedResourceIdPatch) + dependsOn( + sharedExtensionPatch, + sharedResourceIdPatch, + ) execute { diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/navigation/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/navigation/Fingerprints.kt new file mode 100644 index 000000000..e400776a5 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/navigation/Fingerprints.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.music.utils.navigation + +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 tabLayoutViewSetSelectedFingerprint = legacyFingerprint( + name = "tabLayoutViewSetSelectedFingerprint", + returnType = "V", + accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, + parameters = listOf("I"), + customFingerprint = { method, classDef -> + classDef.type == "Lcom/google/android/material/tabs/TabLayout;" && + indexOfChildAtInstruction(method) >= 0 && + indexOfSetViewActivatedInstruction(method) >= 0 + } +) + +internal fun indexOfChildAtInstruction(method: Method) = method.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && getReference()?.name == "getChildAt" +} + +private fun indexOfSetViewActivatedInstruction(method: Method) = method.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && getReference()?.name == "setActivated" +} diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/navigation/NavigationBarHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/navigation/NavigationBarHookPatch.kt new file mode 100644 index 000000000..4f7453613 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/navigation/NavigationBarHookPatch.kt @@ -0,0 +1,48 @@ +package app.revanced.patches.music.utils.navigation + +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.music.utils.extension.Constants.SHARED_PATH +import app.revanced.patches.music.utils.extension.sharedExtensionPatch +import app.revanced.util.fingerprint.methodOrThrow +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +internal const val EXTENSION_CLASS_DESCRIPTOR = + "$SHARED_PATH/NavigationBar;" + +val navigationBarHookPatch = bytecodePatch( + description = "navigationBarHookPatch", +) { + dependsOn(sharedExtensionPatch) + + execute { + tabLayoutViewSetSelectedFingerprint.methodOrThrow().apply { + val childAtIndex = indexOfChildAtInstruction(this) + val tabIndexRegister = + getInstruction(childAtIndex).registerD + + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + val reference = (instruction as? ReferenceInstruction)?.reference + reference is MethodReference && + reference.name == "setActivated" + } + .map { (index, _) -> index } + .reversed() + .forEach { index -> + val isSelectedRegister = + getInstruction(childAtIndex).registerD + + addInstruction( + index, + "invoke-static {v$tabIndexRegister, v$isSelectedRegister}, " + + "$EXTENSION_CLASS_DESCRIPTOR->navigationTabSelected(IZ)V" + ) + } + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/patch/PatchList.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/patch/PatchList.kt index bb89a82b8..7f38fea41 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/patch/PatchList.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/patch/PatchList.kt @@ -147,16 +147,12 @@ internal enum class PatchList( ), SPOOF_APP_VERSION( "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." + "Adds options to spoof the YouTube Music client version. This can be used to restore old UI elements and features." ), SPOOF_CLIENT( "Spoof client", "Adds options to spoof the client to allow playback." ), - SPOOF_STREAMING_DATA( - "Spoof streaming data", - "Adds options to spoof the streaming data to allow playback." - ), TRANSLATIONS_FOR_YOUTUBE_MUSIC( "Translations for YouTube Music", "Add translations or remove string resources." @@ -168,5 +164,9 @@ internal enum class PatchList( VISUAL_PREFERENCES_ICONS_FOR_YOUTUBE_MUSIC( "Visual preferences icons for YouTube Music", "Adds icons to specific preferences in the settings." + ), + WATCH_HISTORY( + "Watch history", + "Adds an option to change the domain of the watch history or check its status." ) } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/playservice/VersionCheckPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/playservice/VersionCheckPatch.kt index 9e4c07482..cdadd5a4c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/playservice/VersionCheckPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/playservice/VersionCheckPatch.kt @@ -11,6 +11,8 @@ var is_6_36_or_greater = false private set var is_6_42_or_greater = false private set +var is_6_43_or_greater = false + private set var is_7_03_or_greater = false private set var is_7_06_or_greater = false @@ -29,8 +31,14 @@ var is_7_25_or_greater = false private set var is_7_27_or_greater = false private set +var is_7_28_or_greater = false + private set var is_7_29_or_greater = false private set +var is_7_33_or_greater = false + private set +var is_8_03_or_greater = false + private set val versionCheckPatch = resourcePatch( description = "versionCheckPatch", @@ -49,6 +57,7 @@ val versionCheckPatch = resourcePatch( is_6_27_or_greater = 234412000 <= playStoreServicesVersion is_6_36_or_greater = 240399000 <= playStoreServicesVersion is_6_42_or_greater = 240999000 <= playStoreServicesVersion + is_6_43_or_greater = 241099000 <= playStoreServicesVersion is_7_03_or_greater = 242199000 <= playStoreServicesVersion is_7_06_or_greater = 242499000 <= playStoreServicesVersion is_7_13_or_greater = 243199000 <= playStoreServicesVersion @@ -58,6 +67,9 @@ val versionCheckPatch = resourcePatch( is_7_23_or_greater = 244199000 <= playStoreServicesVersion is_7_25_or_greater = 244399000 <= playStoreServicesVersion is_7_27_or_greater = 244515000 <= playStoreServicesVersion + is_7_28_or_greater = 244699000 <= playStoreServicesVersion is_7_29_or_greater = 244799000 <= playStoreServicesVersion + is_7_33_or_greater = 245199000 <= playStoreServicesVersion + is_8_03_or_greater = 250399000 <= playStoreServicesVersion } } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt index 3363e56b0..f8ffbd8be 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt @@ -43,6 +43,8 @@ var historyMenuItem = -1L private set var inlineTimeBarAdBreakMarkerColor = -1L private set +var inlineTimeBarProgressColor = -1L + private set var interstitialsContainer = -1L private set var isTablet = -1L @@ -61,6 +63,8 @@ var miniPlayerPlayPauseReplayButton = -1L private set var miniPlayerViewPager = -1L private set +var modernDialogBackground = -1L + private set var musicNotifierShelf = -1L private set var musicTasteBuilderShelf = -1L @@ -172,6 +176,10 @@ internal val sharedResourceIdPatch = resourcePatch( COLOR, "inline_time_bar_ad_break_marker_color" ] + inlineTimeBarProgressColor = resourceMappings[ + COLOR, + "inline_time_bar_progress_color" + ] interstitialsContainer = resourceMappings[ ID, "interstitials_container" @@ -208,6 +216,10 @@ internal val sharedResourceIdPatch = resourcePatch( ID, "mini_player_view_pager" ] + modernDialogBackground = resourceMappings[ + DRAWABLE, + "modern_dialog_background" + ] musicNotifierShelf = resourceMappings[ LAYOUT, "music_notifier_shelf" diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/ReturnYouTubeDislikePatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/ReturnYouTubeDislikePatch.kt index eb4a694e2..caa969d01 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/ReturnYouTubeDislikePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/ReturnYouTubeDislikePatch.kt @@ -4,6 +4,8 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch +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.UTILS_PATH import app.revanced.patches.music.utils.patch.PatchList.RETURN_YOUTUBE_DISLIKE @@ -25,6 +27,7 @@ import app.revanced.patches.shared.removeLikeFingerprint import app.revanced.patches.shared.textcomponent.hookSpannableString import app.revanced.patches.shared.textcomponent.textComponentPatch import app.revanced.util.adoptChild +import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow import com.android.tools.smali.dexlib2.Opcode @@ -78,6 +81,11 @@ private val returnYouTubeDislikeBytecodePatch = bytecodePatch( """ ) } + } else { + actionBarPositionFeatureFlagFingerprint.injectLiteralInstructionBooleanCall( + ACTION_BAR_POSITION_FEATURE_FLAG, + "$EXTENSION_CLASS_DESCRIPTOR->actionBarFeatureFlagLoaded(Z)Z" + ) } if (is_7_17_or_greater) { @@ -137,7 +145,7 @@ val returnYouTubeDislikePatch = resourcePatch( addSwitchPreference( CategoryType.RETURN_YOUTUBE_DISLIKE, "revanced_ryd_toast_on_connection_error", - "false", + "true", "revanced_ryd_enabled" ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt index ce7fab878..5f017a0d7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt @@ -123,12 +123,11 @@ private val settingsBytecodePatch = bytecodePatch( EXTENSION_UTILS_CLASS_DESCRIPTOR, "setActivity" ) - } } -private const val DEFAULT_LABEL = "ReVanced Extended" -private lateinit var customName: String +private const val DEFAULT_LABEL = "RVX" +private lateinit var settingsLabel: String val settingsPatch = resourcePatch( SETTINGS_FOR_YOUTUBE_MUSIC.title, @@ -140,9 +139,13 @@ val settingsPatch = resourcePatch( settingsBytecodePatch, ) - val settingsLabel = stringOption( - key = "settingsLabel", + val rvxSettingsLabel = stringOption( + key = "rvxSettingsLabel", default = DEFAULT_LABEL, + values = mapOf( + "ReVanced Extended" to "ReVanced Extended", + "RVX" to DEFAULT_LABEL, + ), title = "RVX settings label", description = "The name of the RVX settings menu.", required = true, @@ -152,7 +155,7 @@ val settingsPatch = resourcePatch( /** * check patch options */ - customName = settingsLabel + settingsLabel = rvxSettingsLabel .valueOrThrow() /** @@ -220,13 +223,13 @@ val settingsPatch = resourcePatch( * change RVX settings menu name * since it must be invoked after the Translations patch, it must be the last in the order. */ - if (customName != DEFAULT_LABEL) { + if (settingsLabel != DEFAULT_LABEL) { removeStringsElements( arrayOf("revanced_extended_settings_title") ) document("res/values/strings.xml").use { document -> mapOf( - "revanced_extended_settings_title" to customName + "revanced_extended_settings_title" to settingsLabel ).forEach { (k, v) -> val stringElement = document.createElement("string") diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/SponsorBlockPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/SponsorBlockPatch.kt index 269378cfc..3daa36de6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/SponsorBlockPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/SponsorBlockPatch.kt @@ -315,7 +315,7 @@ val sponsorBlockPatch = resourcePatch( addSwitchPreference( SPONSOR_BLOCK_CATEGORY, "sb_toast_on_connection_error", - "false", + "true", "sb_enabled" ) addPreferenceWithIntent( diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/ad/AdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/ad/AdsPatch.kt index df6539d9a..8c4c9cdbf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/reddit/ad/AdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/reddit/ad/AdsPatch.kt @@ -5,80 +5,34 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patcher.patch.resourcePatch -import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.reddit.utils.extension.Constants.PATCHES_PATH import app.revanced.patches.reddit.utils.patch.PatchList.HIDE_ADS import app.revanced.patches.reddit.utils.settings.settingsPatch import app.revanced.patches.reddit.utils.settings.updatePatchStatus -import app.revanced.util.fingerprint.matchOrThrow +import app.revanced.util.findMutableMethodOf import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.getReference -import app.revanced.util.getWalkerMethod import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstStringInstruction +import app.revanced.util.or +import com.android.tools.smali.dexlib2.AccessFlags +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.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference -private const val RESOURCE_FILE_PATH = "res/layout/merge_listheader_link_detail.xml" - -private val bannerAdsPatch = resourcePatch( - description = "bannerAdsPatch", -) { - execute { - document(RESOURCE_FILE_PATH).use { document -> - document.getElementsByTagName("merge").item(0).childNodes.apply { - val attributes = arrayOf("height", "width") - - for (i in 1 until length) { - val view = item(i) - if ( - view.hasAttributes() && - view.attributes.getNamedItem("android:id").nodeValue.endsWith("ad_view_stub") - ) { - attributes.forEach { attribute -> - view.attributes.getNamedItem("android:layout_$attribute").nodeValue = - "0.0dip" - } - - break - } - } - } - } - } -} - -private const val EXTENSION_METHOD_DESCRIPTOR = - "$PATCHES_PATH/GeneralAdsPatch;->hideCommentAds()Z" - -private val commentAdsPatch = bytecodePatch( - description = "commentAdsPatch", -) { - execute { - commentAdsFingerprint.matchOrThrow().let { - val walkerMethod = it.getWalkerMethod(it.patternMatch!!.startIndex) - walkerMethod.apply { - addInstructionsWithLabels( - 0, """ - invoke-static {}, $EXTENSION_METHOD_DESCRIPTOR - move-result v0 - if-eqz v0, :show - new-instance v0, Ljava/lang/Object; - invoke-direct {v0}, Ljava/lang/Object;->()V - return-object v0 - """, ExternalLabel("show", getInstruction(0)) - ) - } - } - } -} - private const val EXTENSION_CLASS_DESCRIPTOR = "$PATCHES_PATH/GeneralAdsPatch;" +private val isCommentAdsMethod: Method.() -> Boolean = { + parameterTypes.size == 1 && + parameterTypes.first().startsWith("Lcom/reddit/ads/conversation/") && + accessFlags == AccessFlags.PUBLIC or AccessFlags.FINAL && + returnType == "V" && + indexOfFirstStringInstruction("ad") >= 0 +} + @Suppress("unused") val adsPatch = bytecodePatch( HIDE_ADS.title, @@ -86,11 +40,7 @@ val adsPatch = bytecodePatch( ) { compatibleWith(COMPATIBLE_PACKAGE) - dependsOn( - settingsPatch, - bannerAdsPatch, - commentAdsPatch, - ) + dependsOn(settingsPatch) execute { // region Filter promoted ads (does not work in popular or latest feed) @@ -127,6 +77,27 @@ val adsPatch = bytecodePatch( ) } + // region Filter comment ads + classes.forEach { classDef -> + classDef.methods.forEach { method -> + if (method.isCommentAdsMethod()) { + proxy(classDef) + .mutableClass + .findMutableMethodOf(method) + .addInstructionsWithLabels( + 0, """ + invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->hideCommentAds()Z + move-result v0 + if-eqz v0, :show + return-void + :show + nop + """ + ) + } + } + } + updatePatchStatus( "enableGeneralAds", HIDE_ADS diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/ad/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/ad/Fingerprints.kt index ad7bd5971..fb5f5d21b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/reddit/ad/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/reddit/ad/Fingerprints.kt @@ -9,22 +9,6 @@ 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 commentAdsFingerprint = legacyFingerprint( - name = "commentAdsFingerprint", - returnType = "L", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = listOf("L"), - opcodes = listOf( - Opcode.INVOKE_STATIC, - Opcode.MOVE_RESULT_OBJECT, - Opcode.RETURN_OBJECT - ), - customFingerprint = { method, _ -> - method.definingClass.endsWith("/PostDetailPresenter\$loadAd\$1;") && - method.name == "invokeSuspend" - }, -) - internal val adPostFingerprint = legacyFingerprint( name = "adPostFingerprint", returnType = "V", diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch.kt index 25a3db115..7d51cb4a0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch.kt @@ -9,6 +9,8 @@ import app.revanced.patches.reddit.utils.extension.Constants.PATCHES_PATH import app.revanced.patches.reddit.utils.patch.PatchList.REMOVE_SUBREDDIT_DIALOG import app.revanced.patches.reddit.utils.settings.is_2024_41_or_greater import app.revanced.patches.reddit.utils.settings.is_2025_01_or_greater +import app.revanced.patches.reddit.utils.settings.is_2025_05_or_greater +import app.revanced.patches.reddit.utils.settings.is_2025_06_or_greater import app.revanced.patches.reddit.utils.settings.settingsPatch import app.revanced.patches.reddit.utils.settings.updatePatchStatus import app.revanced.util.fingerprint.methodOrThrow @@ -53,15 +55,17 @@ val subRedditDialogPatch = bytecodePatch( } // Not used in latest Reddit client. - frequentUpdatesSheetScreenFingerprint.methodOrThrow().apply { - val index = indexOfFirstInstructionReversedOrThrow(Opcode.RETURN_OBJECT) - val register = - getInstruction(index).registerA + if (!is_2025_05_or_greater) { + frequentUpdatesSheetScreenFingerprint.methodOrThrow().apply { + val index = indexOfFirstInstructionReversedOrThrow(Opcode.RETURN_OBJECT) + val register = + getInstruction(index).registerA - addInstruction( - index, - "invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->dismissDialog(Landroid/view/View;)V" - ) + addInstruction( + index, + "invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->dismissDialog(Landroid/view/View;)V" + ) + } } if (is_2025_01_or_greater) { @@ -80,19 +84,21 @@ val subRedditDialogPatch = bytecodePatch( } // Not used in latest Reddit client. - redditAlertDialogsFingerprint.methodOrThrow().apply { - val backgroundTintIndex = indexOfSetBackgroundTintListInstruction(this) - val insertIndex = - indexOfFirstInstructionOrThrow(backgroundTintIndex) { - opcode == Opcode.INVOKE_VIRTUAL && - getReference()?.name == "setTextAppearance" - } - val insertRegister = getInstruction(insertIndex).registerC + if (!is_2025_06_or_greater) { + redditAlertDialogsFingerprint.methodOrThrow().apply { + val backgroundTintIndex = indexOfSetBackgroundTintListInstruction(this) + val insertIndex = + indexOfFirstInstructionOrThrow(backgroundTintIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setTextAppearance" + } + val insertRegister = getInstruction(insertIndex).registerC - addInstruction( - insertIndex, - "invoke-static {v$insertRegister}, $EXTENSION_CLASS_DESCRIPTOR->confirmDialog(Landroid/widget/TextView;)V" - ) + addInstruction( + insertIndex, + "invoke-static {v$insertRegister}, $EXTENSION_CLASS_DESCRIPTOR->confirmDialog(Landroid/widget/TextView;)V" + ) + } } updatePatchStatus( diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/utils/compatibility/Constants.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/utils/compatibility/Constants.kt index 7ef039926..9589f9dd7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/reddit/utils/compatibility/Constants.kt +++ b/patches/src/main/kotlin/app/revanced/patches/reddit/utils/compatibility/Constants.kt @@ -8,6 +8,9 @@ internal object Constants { val COMPATIBLE_PACKAGE: Pair?> = Pair( REDDIT_PACKAGE_NAME, - null + setOf( + "2024.17.0", // This is the last version that can be patched without anti-split. + "2025.05.1", // This is the latest version supported by the RVX patch. + ) ) } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/utils/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/utils/settings/SettingsPatch.kt index ae28725f9..e126683fd 100644 --- a/patches/src/main/kotlin/app/revanced/patches/reddit/utils/settings/SettingsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/reddit/utils/settings/SettingsPatch.kt @@ -39,6 +39,10 @@ var is_2024_41_or_greater = false private set var is_2025_01_or_greater = false private set +var is_2025_05_or_greater = false + private set +var is_2025_06_or_greater = false + private set private val settingsBytecodePatch = bytecodePatch( description = "settingsBytecodePatch" @@ -62,6 +66,8 @@ private val settingsBytecodePatch = bytecodePatch( is_2024_26_or_greater = 2024260 <= versionNumber is_2024_41_or_greater = 2024410 <= versionNumber is_2025_01_or_greater = 2025010 <= versionNumber + is_2025_05_or_greater = 2025050 <= versionNumber + is_2025_06_or_greater = 2025060 <= versionNumber } /** @@ -137,7 +143,7 @@ internal fun updatePatchStatus( updatePatchStatus(patch) } -private const val DEFAULT_LABEL = "ReVanced Extended" +private const val DEFAULT_LABEL = "RVX" val settingsPatch = resourcePatch( SETTINGS_FOR_REDDIT.title, @@ -150,9 +156,13 @@ val settingsPatch = resourcePatch( settingsBytecodePatch ) - val settingsLabelOption = stringOption( - key = "settingsLabel", + val rvxSettingsLabel = stringOption( + key = "rvxSettingsLabel", default = DEFAULT_LABEL, + values = mapOf( + "ReVanced Extended" to "ReVanced Extended", + "RVX" to DEFAULT_LABEL, + ), title = "RVX settings menu name", description = "The name of the RVX settings menu.", required = true @@ -162,7 +172,7 @@ val settingsPatch = resourcePatch( /** * Replace settings icon and label */ - val settingsLabel = settingsLabelOption + val settingsLabel = rvxSettingsLabel .valueOrThrow() arrayOf( diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/Fingerprints.kt index 699e4fb0e..f9e6b4460 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/Fingerprints.kt @@ -10,6 +10,7 @@ import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.Method 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.StringReference internal val createPlayerRequestBodyWithModelFingerprint = legacyFingerprint( name = "createPlayerRequestBodyWithModelFingerprint", @@ -18,11 +19,19 @@ internal val createPlayerRequestBodyWithModelFingerprint = legacyFingerprint( parameters = emptyList(), opcodes = listOf(Opcode.OR_INT_LIT16), customFingerprint = { method, _ -> - indexOfModelInstruction(method) >= 0 && + indexOfBrandInstruction(method) >= 0 && + indexOfManufacturerInstruction(method) >= 0 && + indexOfModelInstruction(method) >= 0 && indexOfReleaseInstruction(method) >= 0 } ) +fun indexOfBrandInstruction(method: Method) = + method.indexOfFieldReference("Landroid/os/Build;->BRAND:Ljava/lang/String;") + +fun indexOfManufacturerInstruction(method: Method) = + method.indexOfFieldReference("Landroid/os/Build;->MANUFACTURER:Ljava/lang/String;") + fun indexOfModelInstruction(method: Method) = method.indexOfFieldReference("Landroid/os/Build;->MODEL:Ljava/lang/String;") @@ -67,9 +76,14 @@ internal val sharedSettingFingerprint = legacyFingerprint( internal val spannableStringBuilderFingerprint = legacyFingerprint( name = "spannableStringBuilderFingerprint", returnType = "Ljava/lang/CharSequence;", - strings = listOf("Failed to set PB Style Run Extension in TextComponentSpec. Extension id: %s"), customFingerprint = { method, _ -> - indexOfSpannableStringInstruction(method) >= 0 + method.indexOfFirstInstruction { + opcode == Opcode.CONST_STRING && + getReference() + ?.string.toString() + .startsWith("Failed to set PB Style Run Extension in TextComponentSpec.") + } >= 0 && + indexOfSpannableStringInstruction(method) >= 0 } ) @@ -81,27 +95,6 @@ fun indexOfSpannableStringInstruction(method: Method) = method.indexOfFirstInstr getReference()?.toString() == SPANNABLE_STRING_REFERENCE } -internal val startVideoInformerFingerprint = legacyFingerprint( - name = "startVideoInformerFingerprint", - returnType = "V", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - opcodes = listOf( - Opcode.INVOKE_INTERFACE, - Opcode.RETURN_VOID - ), - strings = listOf("pc"), - customFingerprint = { method, _ -> - method.implementation - ?.instructions - ?.withIndex() - ?.filter { (_, instruction) -> - instruction.opcode == Opcode.CONST_STRING - } - ?.map { (index, _) -> index } - ?.size == 1 - } -) - internal val videoLengthFingerprint = legacyFingerprint( name = "videoLengthFingerprint", returnType = "V", diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/ads/BaseAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/ads/BaseAdsPatch.kt index c2d7d85f0..55f2354d3 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/ads/BaseAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/ads/BaseAdsPatch.kt @@ -16,6 +16,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.reference.FieldReference @@ -91,17 +92,32 @@ internal fun MutableMethod.hookNonLithoFullscreenAds(literal: Long) { internal fun Match.hookLithoFullscreenAds() { method.apply { + // It is ideal to check the dialog type and protobuffer before closing the dialog. + // There is no register that can be used freely, so it is divided into two hooking. + val showDialogIndex = indexOfFirstInstructionOrThrow { + getReference()?.name == "show" + } + val dialogRegister = getInstruction(showDialogIndex).registerC + + addInstruction( + showDialogIndex + 1, + "invoke-static {v$dialogRegister}, $EXTENSION_CLASS_DESCRIPTOR->dismissDialog(Ljava/lang/Object;)V" + ) + + // Dialog type should be checked first. val dialogCodeIndex = patternMatch!!.endIndex val dialogCodeField = getInstruction(dialogCodeIndex).reference as FieldReference - if (dialogCodeField.type != "I") + if (dialogCodeField.type != "I") { throw PatchException("Invalid dialogCodeField: $dialogCodeField") + } var prependInstructions = """ move-object/from16 v0, p1 move-object/from16 v1, p2 """ + // Used only in very old versions. if (parameterTypes.firstOrNull() != "[B") { val toByteArrayReference = getInstruction( indexOfFirstInstructionOrThrow { @@ -116,15 +132,12 @@ internal fun Match.hookLithoFullscreenAds() { } // Disable fullscreen ads - addInstructionsWithLabels( + addInstructions( 0, prependInstructions + """ check-cast v1, ${dialogCodeField.definingClass} iget v1, v1, $dialogCodeField - invoke-static {v0, v1}, $EXTENSION_CLASS_DESCRIPTOR->disableFullscreenAds([BI)Z - move-result v1 - if-eqz v1, :show - return-void - """, ExternalLabel("show", getInstruction(0)) + invoke-static {v0, v1}, $EXTENSION_CLASS_DESCRIPTOR->checkDialog([BI)V + """ ) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/captions/BaseAutoCaptionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/captions/BaseAutoCaptionsPatch.kt index 26c19b4cf..ae3d48eed 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/captions/BaseAutoCaptionsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/captions/BaseAutoCaptionsPatch.kt @@ -6,7 +6,6 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.extension.Constants.PATCHES_PATH -import app.revanced.patches.shared.startVideoInformerFingerprint import app.revanced.util.fingerprint.methodOrThrow private const val EXTENSION_CLASS_DESCRIPTOR = diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/captions/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/captions/Fingerprints.kt index 0f2bb490e..34efb7b19 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/captions/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/captions/Fingerprints.kt @@ -3,6 +3,28 @@ package app.revanced.patches.shared.captions 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 val startVideoInformerFingerprint = legacyFingerprint( + name = "startVideoInformerFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + opcodes = listOf( + Opcode.INVOKE_INTERFACE, + Opcode.RETURN_VOID + ), + strings = listOf("pc"), + customFingerprint = { method, _ -> + method.implementation + ?.instructions + ?.withIndex() + ?.filter { (_, instruction) -> + instruction.opcode == Opcode.CONST_STRING + } + ?.map { (index, _) -> index } + ?.size == 1 + } +) internal val storyboardRendererDecoderRecommendedLevelFingerprint = legacyFingerprint( name = "storyboardRendererDecoderRecommendedLevelFingerprint", diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/customspeed/CustomPlaybackSpeedPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/customspeed/CustomPlaybackSpeedPatch.kt index c23316358..42cb1f3ca 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/customspeed/CustomPlaybackSpeedPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/customspeed/CustomPlaybackSpeedPatch.kt @@ -14,6 +14,8 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference +private var patchIncluded = false + fun customPlaybackSpeedPatch( descriptor: String, maxSpeed: Float @@ -21,6 +23,10 @@ fun customPlaybackSpeedPatch( description = "customPlaybackSpeedPatch" ) { execute { + if (patchIncluded) { + return@execute + } + arrayGeneratorFingerprint.matchOrThrow().let { it.method.apply { val targetIndex = it.patternMatch!!.startIndex @@ -85,6 +91,8 @@ fun customPlaybackSpeedPatch( } } + patchIncluded = true + } } diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/gms/GmsCoreSupportPatch.kt index 92d885399..0a5d12a9a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/gms/GmsCoreSupportPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/gms/GmsCoreSupportPatch.kt @@ -187,8 +187,8 @@ fun gmsCoreSupportPatch( in AUTHORITIES, -> referencedString.replace("com.google", gmsCoreVendorGroupId!!) - // No vendor prefix for whatever reason... - "subscribedfeeds" -> "$gmsCoreVendorGroupId.subscribedfeeds" + // TODO: Add this permission when bumping GmsCore + // "android.media.MediaRouteProviderService" -> "$gmsCoreVendorGroupId.android.media.MediaRouteProviderService" else -> null } @@ -205,15 +205,6 @@ fun gmsCoreSupportPatch( ) } } - - // gms also has a 'subscribedfeeds' authority, check for that one too - val subFeedsUriPrefix = "content://subscribedfeeds" - if (str.startsWith(subFeedsUriPrefix)) { - return str.replace( - subFeedsUriPrefix, - "content://$gmsCoreVendorGroupId.subscribedfeeds" - ) - } } return null @@ -349,6 +340,13 @@ private object Constants { "com.google.android.c2dm.permission.RECEIVE", "com.google.android.c2dm.permission.SEND", "com.google.android.providers.gsf.permission.READ_GSERVICES", + + // ads + "com.google.android.gms.permission.AD_ID", + "com.google.android.gms.permission.AD_ID_NOTIFICATION", + + // TODO: Add this permission when bumping GmsCore + // "com.google.android.gms.permission.ACTIVITY_RECOGNITION", ) val ACTIONS = setOf( @@ -381,6 +379,9 @@ private object Constants { "com.google.android.gms.cast.firstparty.START", "com.google.android.gms.cast.service.BIND_CAST_DEVICE_CONTROLLER_SERVICE", + // TODO: Add this permission when bumping GmsCore + // "android.media.MediaRouteProviderService", + // fonts "com.google.android.gms.fonts", @@ -388,6 +389,7 @@ private object Constants { "com.google.android.gms.phenotype.service.START", // misc + "com.google.android.gms.ads.identifier.service.START", "com.google.android.gms.clearcut.service.START", "com.google.android.gms.common.telemetry.service.START", "com.google.android.gms.gmscompliance.service.START", @@ -516,7 +518,11 @@ fun gmsCoreSupportResourcePatch( "$fromPackageName.permission.C2D_MESSAGE" to "$packageName.permission.C2D_MESSAGE", "$fromPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" to "$packageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION", "com.google.android.c2dm" to "$gmsCoreVendorGroupId.android.c2dm", + "com.google.android.gms.permission.AD_ID" to "$gmsCoreVendorGroupId.android.gms.permission.AD_ID", "com.google.android.libraries.photos.api.mars" to "$gmsCoreVendorGroupId.android.apps.photos.api.mars", + "com.google.android.providers.gsf.permission.READ_GSERVICES" to "$gmsCoreVendorGroupId.android.providers.gsf.permission.READ_GSERVICES", + // TODO: Add this permission when bumping GmsCore + // "com.google.android.gms.permission.ACTIVITY_RECOGNITION" to "$gmsCoreVendorGroupId.android.gms.permission.ACTIVITY_RECOGNITION", ) // 'QUERY_ALL_PACKAGES' permission is required, diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/litho/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/litho/Fingerprints.kt index 1adf981c2..3008532e8 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/litho/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/litho/Fingerprints.kt @@ -1,16 +1,21 @@ package app.revanced.patches.shared.litho 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.reference.StringReference + +internal const val BUFFER_UPD_FEATURE_FLAG = 45419603L internal val bufferUpbFeatureFlagFingerprint = legacyFingerprint( name = "bufferUpbFeatureFlagFingerprint", returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, parameters = listOf("L"), - literals = listOf(45419603L), + literals = listOf(BUFFER_UPD_FEATURE_FLAG), ) internal val byteBufferFingerprint = legacyFingerprint( @@ -51,9 +56,16 @@ internal val emptyComponentsFingerprint = legacyFingerprint( Opcode.INVOKE_INTERFACE, Opcode.INVOKE_STATIC_RANGE, Opcode.MOVE_RESULT_OBJECT, - Opcode.IGET_OBJECT + Opcode.IGET_OBJECT, ), - strings = listOf("Error while converting %s"), + customFingerprint = { method, _ -> + method.indexOfFirstInstruction { + opcode == Opcode.CONST_STRING && + getReference() + ?.string.toString() + .startsWith("Error while converting") + } >= 0 + } ) /** @@ -65,10 +77,12 @@ internal val pathBuilderFingerprint = legacyFingerprint( strings = listOf("Number of bits must be positive"), ) +internal const val PATH_UPD_FEATURE_FLAG = 45631264L + internal val pathUpbFeatureFlagFingerprint = legacyFingerprint( name = "pathUpbFeatureFlagFingerprint", returnType = "Z", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = emptyList(), - literals = listOf(45631264L), + literals = listOf(PATH_UPD_FEATURE_FLAG), ) \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt index d517732b1..54767b4da 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/litho/LithoFilterPatch.kt @@ -165,13 +165,11 @@ val lithoFilterPatch = bytecodePatch( // Turn off native code that handles litho component names. If this feature is on then nearly // all litho components have a null name and identifier/path filtering is completely broken. - if (bufferUpbFeatureFlagFingerprint.second.methodOrNull != null && - pathUpbFeatureFlagFingerprint.second.methodOrNull != null - ) { - mapOf( - bufferUpbFeatureFlagFingerprint to 45419603L, - pathUpbFeatureFlagFingerprint to 45631264L, - ).forEach { (fingerprint, literalValue) -> + mapOf( + bufferUpbFeatureFlagFingerprint to BUFFER_UPD_FEATURE_FLAG, + pathUpbFeatureFlagFingerprint to PATH_UPD_FEATURE_FLAG, + ).forEach { (fingerprint, literalValue) -> + if (fingerprint.second.methodOrNull != null) { fingerprint.injectLiteralInstructionBooleanCall( literalValue, "0x0" diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/materialyou/BaseMaterialYouPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/materialyou/BaseMaterialYouPatch.kt index ca07af5af..755f66559 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/materialyou/BaseMaterialYouPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/materialyou/BaseMaterialYouPatch.kt @@ -1,8 +1,8 @@ package app.revanced.patches.shared.materialyou import app.revanced.patcher.patch.ResourcePatchContext +import app.revanced.util.FilesCompat import org.w3c.dom.Element -import java.nio.file.Files private fun ResourcePatchContext.patchXmlFile( fromDir: String, @@ -17,7 +17,7 @@ private fun ResourcePatchContext.patchXmlFile( val fromDirectory = resourceDirectory.resolve(fromDir) val toDirectory = resourceDirectory.resolve(toDir) - if (!toDirectory.isDirectory) Files.createDirectories(toDirectory.toPath()) + if (!toDirectory.isDirectory) toDirectory.mkdirs() val fromXmlFile = fromDirectory.resolve(xmlFileName) val toXmlFile = toDirectory.resolve(xmlFileName) @@ -27,9 +27,9 @@ private fun ResourcePatchContext.patchXmlFile( } if (!toXmlFile.exists()) { - Files.copy( - fromXmlFile.toPath(), - toXmlFile.toPath() + FilesCompat.copy( + fromXmlFile, + toXmlFile ) } @@ -37,8 +37,9 @@ private fun ResourcePatchContext.patchXmlFile( val parentList = document.getElementsByTagName(parentNode).item(0) as Element if (targetNode != null) { - for (i in 0 until parentList.childNodes.length) { - val node = parentList.childNodes.item(i) as? Element ?: continue + val childNodes = parentList.childNodes + for (i in 0 until childNodes.length) { + val node = childNodes.item(i) as? Element ?: continue if (node.nodeName == targetNode && node.hasAttribute(attribute)) { node.getAttributeNode(attribute).textContent = newValue diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/blockrequest/BlockRequestPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/blockrequest/BlockRequestPatch.kt new file mode 100644 index 000000000..dbb60c9af --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/blockrequest/BlockRequestPatch.kt @@ -0,0 +1,54 @@ +package app.revanced.patches.shared.spoof.blockrequest + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.shared.extension.Constants.SPOOF_PATH +import app.revanced.util.fingerprint.methodOrThrow +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction + +const val EXTENSION_CLASS_DESCRIPTOR = + "$SPOOF_PATH/BlockRequestPatch;" + +val blockRequestPatch = bytecodePatch( + description = "blockRequestPatch" +) { + execute { + // region Block /initplayback requests to fall back to /get_watch requests. + + buildInitPlaybackRequestFingerprint.methodOrThrow().apply { + val uriIndex = indexOfUriToStringInstruction(this) + val uriRegister = + getInstruction(uriIndex).registerC + + addInstructions( + uriIndex, + """ + invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Landroid/net/Uri;)Landroid/net/Uri; + move-result-object v$uriRegister + """, + ) + } + + // endregion + + // region Block /get_watch requests to fall back to /player requests. + + buildPlayerRequestURIFingerprint.methodOrThrow().apply { + val invokeToStringIndex = indexOfUriToStringInstruction(this) + val uriRegister = + getInstruction(invokeToStringIndex).registerC + + addInstructions( + invokeToStringIndex, + """ + invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri; + move-result-object v$uriRegister + """, + ) + } + + // endregion + } +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/blockrequest/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/blockrequest/Fingerprints.kt new file mode 100644 index 000000000..2fc03222d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/blockrequest/Fingerprints.kt @@ -0,0 +1,43 @@ +package app.revanced.patches.shared.spoof.blockrequest + +import app.revanced.util.fingerprint.legacyFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +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 buildInitPlaybackRequestFingerprint = legacyFingerprint( + name = "buildInitPlaybackRequestFingerprint", + returnType = "Lorg/chromium/net/UrlRequest\$Builder;", + opcodes = listOf( + Opcode.MOVE_RESULT_OBJECT, + Opcode.IGET_OBJECT, // Moves the request URI string to a register to build the request with. + ), + strings = listOf( + "Content-Type", + "Range", + ), + customFingerprint = { method, _ -> + indexOfUriToStringInstruction(method) >= 0 + }, +) + +internal val buildPlayerRequestURIFingerprint = legacyFingerprint( + name = "buildPlayerRequestURIFingerprint", + returnType = "Ljava/lang/String;", + strings = listOf( + "key", + "asig", + ), + customFingerprint = { method, _ -> + indexOfUriToStringInstruction(method) >= 0 + }, +) + +internal fun indexOfUriToStringInstruction(method: Method) = + method.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference().toString() == "Landroid/net/Uri;->toString()Ljava/lang/String;" + } + diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatch.kt deleted file mode 100644 index 451a17c12..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/BaseSpoofStreamingDataPatch.kt +++ /dev/null @@ -1,370 +0,0 @@ -package app.revanced.patches.shared.spoof.streamingdata - -import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions -import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.extensions.InstructionExtensions.instructions -import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction -import app.revanced.patcher.patch.BytecodePatchBuilder -import app.revanced.patcher.patch.BytecodePatchContext -import app.revanced.patcher.patch.PatchException -import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable -import app.revanced.patches.shared.extension.Constants.SPOOF_PATH -import app.revanced.patches.shared.formatStreamModelConstructorFingerprint -import app.revanced.util.findInstructionIndicesReversedOrThrow -import app.revanced.util.fingerprint.definingClassOrThrow -import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall -import app.revanced.util.fingerprint.matchOrThrow -import app.revanced.util.fingerprint.methodOrThrow -import app.revanced.util.getReference -import app.revanced.util.indexOfFirstInstructionOrThrow -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.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 -import com.android.tools.smali.dexlib2.iface.reference.FieldReference -import com.android.tools.smali.dexlib2.immutable.ImmutableMethod -import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter - -const val EXTENSION_CLASS_DESCRIPTOR = - "$SPOOF_PATH/SpoofStreamingDataPatch;" - -fun baseSpoofStreamingDataPatch( - block: BytecodePatchBuilder.() -> Unit = {}, - executeBlock: BytecodePatchContext.() -> Unit = {}, -) = bytecodePatch( - name = "Spoof streaming data", - description = "Adds options to spoof the streaming data to allow playback." -) { - block() - - execute { - // region Block /initplayback requests to fall back to /get_watch requests. - - buildInitPlaybackRequestFingerprint.matchOrThrow().let { - it.method.apply { - val moveUriStringIndex = it.patternMatch!!.startIndex - val targetRegister = - getInstruction(moveUriStringIndex).registerA - - addInstructions( - moveUriStringIndex + 1, - """ - invoke-static { v$targetRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockInitPlaybackRequest(Ljava/lang/String;)Ljava/lang/String; - move-result-object v$targetRegister - """, - ) - } - } - - // endregion - - // region Block /get_watch requests to fall back to /player requests. - - buildPlayerRequestURIFingerprint.methodOrThrow().apply { - val invokeToStringIndex = indexOfToStringInstruction(this) - val uriRegister = - getInstruction(invokeToStringIndex).registerC - - addInstructions( - invokeToStringIndex, - """ - invoke-static { v$uriRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetWatchRequest(Landroid/net/Uri;)Landroid/net/Uri; - move-result-object v$uriRegister - """, - ) - } - - // endregion - - // region Get replacement streams at player requests. - - buildRequestFingerprint.methodOrThrow().apply { - val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this) - val urlRegister = - getInstruction(newRequestBuilderIndex).registerD - - val entrySetIndex = indexOfEntrySetInstruction(this) - val mapRegister = if (entrySetIndex < 0) - urlRegister + 1 - else - getInstruction(entrySetIndex).registerC - - var smaliInstructions = - "invoke-static { v$urlRegister, v$mapRegister }, " + - "$EXTENSION_CLASS_DESCRIPTOR->" + - "fetchStreams(Ljava/lang/String;Ljava/util/Map;)V" - - if (entrySetIndex < 0) smaliInstructions = """ - move-object/from16 v$mapRegister, p1 - - """ + smaliInstructions - - // Copy request headers for streaming data fetch. - addInstructions(newRequestBuilderIndex + 2, smaliInstructions) - } - - // endregion - - // region Replace the streaming data. - - val approxDurationMsReference = formatStreamModelConstructorFingerprint.matchOrThrow().let { - with(it.method) { - getInstruction(it.patternMatch!!.startIndex).reference - } - } - - val streamingDataFormatsReference = with( - videoStreamingDataConstructorFingerprint.methodOrThrow( - videoStreamingDataToStringFingerprint - ) - ) { - val getFormatsFieldIndex = indexOfGetFormatsFieldInstruction(this) - val longMaxValueIndex = indexOfLongMaxValueInstruction(this, getFormatsFieldIndex) - val longMaxValueRegister = - getInstruction(longMaxValueIndex).registerA - val videoIdIndex = - indexOfFirstInstructionOrThrow(longMaxValueIndex) { - val reference = getReference() - opcode == Opcode.IGET_OBJECT && - reference?.type == "Ljava/lang/String;" && - reference.definingClass == definingClass - } - - val definingClassRegister = - getInstruction(videoIdIndex).registerB - val videoIdReference = - getInstruction(videoIdIndex).reference - - addInstructions( - longMaxValueIndex + 1, """ - # Get video id. - iget-object v$longMaxValueRegister, v$definingClassRegister, $videoIdReference - - # Override approxDurationMs. - invoke-static { v$longMaxValueRegister }, $EXTENSION_CLASS_DESCRIPTOR->getApproxDurationMs(Ljava/lang/String;)J - move-result-wide v$longMaxValueRegister - """ - ) - removeInstruction(longMaxValueIndex) - - getInstruction(getFormatsFieldIndex).reference - } - - createStreamingDataFingerprint.matchOrThrow(createStreamingDataParentFingerprint) - .let { result -> - result.method.apply { - val setStreamDataMethodName = "patch_setStreamingData" - val calcApproxDurationMsMethodName = "patch_calcApproxDurationMs" - val resultClassDef = result.classDef - val resultMethodType = resultClassDef.type - val setStreamingDataIndex = result.patternMatch!!.startIndex - val setStreamingDataField = - getInstruction(setStreamingDataIndex).getReference() - .toString() - - val playerProtoClass = - getInstruction(setStreamingDataIndex + 1).getReference()!!.definingClass - val protobufClass = - protobufClassParseByteBufferFingerprint.definingClassOrThrow() - - val getStreamingDataField = instructions.find { instruction -> - instruction.opcode == Opcode.IGET_OBJECT && - instruction.getReference()?.definingClass == playerProtoClass - }?.getReference() - ?: throw PatchException("Could not find getStreamingDataField") - - val videoDetailsIndex = result.patternMatch!!.endIndex - val videoDetailsRegister = - getInstruction(videoDetailsIndex).registerA - val videoDetailsClass = - getInstruction(videoDetailsIndex).getReference()!!.type - - addInstruction( - videoDetailsIndex + 1, - "invoke-direct { p0, v$videoDetailsRegister }, " + - "$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V", - ) - - result.classDef.methods.add( - ImmutableMethod( - resultMethodType, - setStreamDataMethodName, - listOf( - ImmutableMethodParameter( - videoDetailsClass, - annotations, - "videoDetails" - ) - ), - "V", - AccessFlags.PRIVATE.value or AccessFlags.FINAL.value, - annotations, - null, - MutableMethodImplementation(9), - ).toMutable().apply { - addInstructionsWithLabels( - 0, - """ - invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled()Z - move-result v0 - if-eqz v0, :disabled - - # Get video id. - iget-object v2, p1, $videoDetailsClass->c:Ljava/lang/String; - if-eqz v2, :disabled - - # Get streaming data. - invoke-static { v2 }, $EXTENSION_CLASS_DESCRIPTOR->getStreamingData(Ljava/lang/String;)Ljava/nio/ByteBuffer; - move-result-object v3 - - if-eqz v3, :disabled - - # Parse streaming data. - sget-object v4, $playerProtoClass->a:$playerProtoClass - invoke-static { v4, v3 }, $protobufClass->parseFrom(${protobufClass}Ljava/nio/ByteBuffer;)$protobufClass - move-result-object v5 - check-cast v5, $playerProtoClass - - iget-object v6, v5, $getStreamingDataField - if-eqz v6, :disabled - - # Caculate approxDurationMs. - invoke-direct { p0, v2 }, $resultMethodType->$calcApproxDurationMsMethodName(Ljava/lang/String;)V - - # Set spoofed streaming data. - iput-object v6, p0, $setStreamingDataField - - :disabled - return-void - """, - ) - }, - ) - - resultClassDef.methods.add( - ImmutableMethod( - resultMethodType, - calcApproxDurationMsMethodName, - listOf( - ImmutableMethodParameter( - "Ljava/lang/String;", - annotations, - "videoId" - ) - ), - "V", - AccessFlags.PRIVATE.value or AccessFlags.FINAL.value, - annotations, - null, - MutableMethodImplementation(12), - ).toMutable().apply { - addInstructionsWithLabels( - 0, - """ - # Get video format list. - iget-object v0, p0, $setStreamingDataField - iget-object v0, v0, $streamingDataFormatsReference - invoke-interface {v0}, Ljava/util/List;->iterator()Ljava/util/Iterator; - move-result-object v0 - - # Initialize approxDurationMs field. - const-wide v1, 0x7fffffffffffffffL - - :loop - # Loop over all video formats to get the approxDurationMs - invoke-interface {v0}, Ljava/util/Iterator;->hasNext()Z - move-result v3 - const-wide/16 v4, 0x0 - - if-eqz v3, :exit - invoke-interface {v0}, Ljava/util/Iterator;->next()Ljava/lang/Object; - move-result-object v3 - check-cast v3, ${(approxDurationMsReference as FieldReference).definingClass} - - # Get approxDurationMs from format - iget-wide v6, v3, $approxDurationMsReference - - # Compare with zero to make sure approxDurationMs is not negative - cmp-long v8, v6, v4 - if-lez v8, :loop - - # Only use the min value of approxDurationMs - invoke-static {v1, v2, v6, v7}, Ljava/lang/Math;->min(JJ)J - move-result-wide v1 - goto :loop - - :exit - # Save approxDurationMs to integrations - invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->setApproxDurationMs(Ljava/lang/String;J)V - - return-void - """, - ) - }, - ) - } - } - - // endregion - - // region Remove /videoplayback request body to fix playback. - // This is needed when using iOS client as streaming data source. - - buildMediaDataSourceFingerprint.methodOrThrow().apply { - val targetIndex = instructions.lastIndex - - addInstructions( - targetIndex, - """ - # Field a: Stream uri. - # Field c: Http method. - # Field d: Post data. - move-object/from16 v0, p0 - iget-object v1, v0, $definingClass->a:Landroid/net/Uri; - iget v2, v0, $definingClass->c:I - iget-object v3, v0, $definingClass->d:[B - invoke-static { v1, v2, v3 }, $EXTENSION_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B - move-result-object v1 - iput-object v1, v0, $definingClass->d:[B - """, - ) - } - - // endregion - - // region Append spoof info. - - nerdsStatsVideoFormatBuilderFingerprint.methodOrThrow().apply { - findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index -> - val register = getInstruction(index).registerA - - addInstructions( - index, """ - invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String; - move-result-object v$register - """ - ) - } - } - - // endregion - - // region Fix iOS livestream current time. - - hlsCurrentTimeFingerprint.injectLiteralInstructionBooleanCall( - HLS_CURRENT_TIME_FEATURE_FLAG, - "$EXTENSION_CLASS_DESCRIPTOR->fixHLSCurrentTime(Z)Z" - ) - - // endregion - - executeBlock() - - } -} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/useragent/BaseSpoofUserAgentPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/useragent/BaseSpoofUserAgentPatch.kt index 2b1999dfe..92114cc6b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/useragent/BaseSpoofUserAgentPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/spoof/useragent/BaseSpoofUserAgentPatch.kt @@ -3,9 +3,9 @@ package app.revanced.patches.shared.spoof.useragent import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction -import app.revanced.patches.shared.transformation.IMethodCall -import app.revanced.patches.shared.transformation.filterMapInstruction35c -import app.revanced.patches.shared.transformation.transformInstructionsPatch +import app.revanced.patches.all.misc.transformation.IMethodCall +import app.revanced.patches.all.misc.transformation.filterMapInstruction35c +import app.revanced.patches.all.misc.transformation.transformInstructionsPatch import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstruction diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/trackingurlhook/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/trackingurlhook/Fingerprints.kt new file mode 100644 index 000000000..eefb24db2 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/trackingurlhook/Fingerprints.kt @@ -0,0 +1,35 @@ +package app.revanced.patches.shared.trackingurlhook + +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 val trackingUrlModelFingerprint = legacyFingerprint( + name = "trackingUrlModelFingerprint", + returnType = "Landroid/net/Uri;", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = emptyList(), + opcodes = listOf( + Opcode.IGET_OBJECT, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + ), +) + +/** + * On YouTube, this class is 'Lcom/google/android/libraries/youtube/innertube/model/player/TrackingUrlModel;' + * On YouTube Music, class names are obfuscated. + */ +internal val trackingUrlModelToStringFingerprint = legacyFingerprint( + name = "trackingUrlModelToStringFingerprint", + returnType = "Ljava/lang/String;", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = emptyList(), + strings = listOf( + "@", + "baseUrl->", + "params->", + "headers->", + ) +) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/trackingurlhook/TrackingUrlHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/trackingurlhook/TrackingUrlHookPatch.kt similarity index 69% rename from patches/src/main/kotlin/app/revanced/patches/youtube/utils/trackingurlhook/TrackingUrlHookPatch.kt rename to patches/src/main/kotlin/app/revanced/patches/shared/trackingurlhook/TrackingUrlHookPatch.kt index cd9761c7b..df2fe376d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/trackingurlhook/TrackingUrlHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/trackingurlhook/TrackingUrlHookPatch.kt @@ -1,9 +1,10 @@ -package app.revanced.patches.youtube.utils.trackingurlhook +package app.revanced.patches.shared.trackingurlhook import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.shared.extension.Constants.PATCHES_PATH import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow @@ -11,13 +12,21 @@ import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.MethodReference +private const val EXTENSION_WATCH_HISTORY_CLASS_DESCRIPTOR = + "$PATCHES_PATH/WatchHistoryPatch;" + private lateinit var trackingUrlMethod: MutableMethod +/** + * This patch is currently used only for the 'Watch history' patch. + * In some versions, it can be used to forcibly generate 'Watch history'. + */ val trackingUrlHookPatch = bytecodePatch( description = "trackingUrlHookPatch" ) { execute { - trackingUrlMethod = trackingUrlModelFingerprint.methodOrThrow() + trackingUrlMethod = + trackingUrlModelFingerprint.methodOrThrow(trackingUrlModelToStringFingerprint) } } @@ -44,3 +53,6 @@ internal fun hookTrackingUrl( smaliInstruction ) } + +internal fun hookWatchHistory() = + hookTrackingUrl("$EXTENSION_WATCH_HISTORY_CLASS_DESCRIPTOR->replaceTrackingUrl(Landroid/net/Uri;)Landroid/net/Uri;") diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/translations/BaseTranslationsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/translations/BaseTranslationsPatch.kt index f7f3b8b97..82ca978e6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/translations/BaseTranslationsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/translations/BaseTranslationsPatch.kt @@ -2,13 +2,12 @@ package app.revanced.patches.shared.translations import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.ResourcePatchContext +import app.revanced.util.FilesCompat import app.revanced.util.doRecursively import app.revanced.util.inputStreamFromBundledResource import org.w3c.dom.Element import org.w3c.dom.Node import java.io.File -import java.nio.file.Files -import java.nio.file.StandardCopyOption import javax.xml.parsers.DocumentBuilderFactory import javax.xml.transform.OutputKeys import javax.xml.transform.TransformerFactory @@ -119,40 +118,46 @@ fun ResourcePatchContext.baseTranslationsPatch( if (!isYouTube) return filteredAppLanguages = filteredAppLanguages.map { language -> - language.subSequence(0,2).toString().uppercase() + val hyphenIndex = language.indexOf("-") - 1 + if (hyphenIndex > 2) { + language.subSequence(0, hyphenIndex).toString().uppercase() + } else { + language.uppercase() + } }.toHashSet().toTypedArray() // Remove unselected app languages from RVX Settings - setOf( - "revanced_language_entries", - "revanced_language_entry_values", - ).forEach { attributeName -> - document("res/values/arrays.xml").use { document -> - with(document) { - val nodesToRemove = mutableListOf() + document("res/values/arrays.xml").use { document -> + val targetAttributeNames = setOf( + "revanced_language_entries", + "revanced_language_entry_values", + ) + val nodesToRemove = mutableListOf() - val resourcesNode = getElementsByTagName("resources").item(0) as Element - for (i in 0 until resourcesNode.childNodes.length) { - val node = resourcesNode.childNodes.item(i) as? Element ?: continue + val resourcesNode = document.documentElement + val childNodes = resourcesNode.childNodes + for (i in 0 until childNodes.length) { + val node = childNodes.item(i) as? Element ?: continue - if (node.getAttribute("name") == attributeName) { - for (j in 0 until node.childNodes.length) { - val item = node.childNodes.item(j) as? Element ?: continue - val text = item.textContent - val length = text.length - if (!text.endsWith("DEFAULT") && text.subSequence(length - 2, length) !in filteredAppLanguages) { - nodesToRemove.add(item) - } - } + if (node.getAttribute("name") in targetAttributeNames) { + val itemNodes = node.childNodes + for (j in 0 until itemNodes.length) { + val item = itemNodes.item(j) as? Element ?: continue + val text = item.textContent + val length = text.length + if (!text.endsWith("DEFAULT") && + length >= 2 && + text.subSequence(length - 2, length) !in filteredAppLanguages) { + nodesToRemove.add(item) } } - - // Remove the collected nodes (avoids NullPointerException) - for (n in nodesToRemove) { - n.parentNode?.removeChild(n) - } } } + + // Remove the collected nodes (avoids NullPointerException) + for (n in nodesToRemove) { + n.parentNode?.removeChild(n) + } } } @@ -174,12 +179,11 @@ private fun ResourcePatchContext.copyStringsXml( )?.let { inputStream -> val directory = "values-$language-v21" val valuesV21Directory = resourceDirectory.resolve(directory) - if (!valuesV21Directory.isDirectory) Files.createDirectories(valuesV21Directory.toPath()) + if (!valuesV21Directory.isDirectory) valuesV21Directory.mkdirs() - Files.copy( + FilesCompat.copy( inputStream, - resourceDirectory.resolve("$directory/strings.xml").toPath(), - StandardCopyOption.REPLACE_EXISTING + resourceDirectory.resolve("$directory/strings.xml") ) } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ads/general/AdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ads/general/AdsPatch.kt index 5419d11e8..6d119a03d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/ads/general/AdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ads/general/AdsPatch.kt @@ -8,7 +8,6 @@ import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.ads.baseAdsPatch import app.revanced.patches.shared.ads.hookLithoFullscreenAds import app.revanced.patches.shared.ads.hookNonLithoFullscreenAds -import app.revanced.patches.shared.extension.Constants.PATCHES_PATH import app.revanced.patches.shared.litho.addLithoFilter import app.revanced.patches.shared.litho.lithoFilterPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE @@ -22,12 +21,12 @@ import app.revanced.patches.youtube.utils.resourceid.interstitialsContainer import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.settingsPatch -import app.revanced.util.findMethodOrThrow import app.revanced.util.findMutableMethodOf import app.revanced.util.fingerprint.matchOrThrow import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.injectHideViewCall import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c @@ -132,12 +131,23 @@ val adsPatch = bytecodePatch( // endregion - findMethodOrThrow("$PATCHES_PATH/PatchStatus;") { - name == "HideFullscreenAdsDefaultBoolean" - }.replaceInstruction( - 0, - "const/4 v0, 0x1" - ) + // region patch for hide end screen store banner + + fullScreenEngagementAdContainerFingerprint.methodOrThrow().apply { + val addListIndex = indexOfAddListInstruction(this) + val addListInstruction = + getInstruction(addListIndex) + val listRegister = addListInstruction.registerC + val objectRegister = addListInstruction.registerD + + replaceInstruction( + addListIndex, + "invoke-static { v$listRegister, v$objectRegister }, " + + "$ADS_CLASS_DESCRIPTOR->hideEndScreenStoreBanner(Ljava/util/List;Ljava/lang/Object;)V" + ) + } + + // endregion // region add settings diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ads/general/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ads/general/Fingerprints.kt index c51509b41..315b1d9ac 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/ads/general/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ads/general/Fingerprints.kt @@ -1,11 +1,16 @@ package app.revanced.patches.youtube.ads.general +import app.revanced.patches.youtube.utils.resourceid.fullScreenEngagementAdContainer import app.revanced.patches.youtube.utils.resourceid.interstitialsContainer import app.revanced.patches.youtube.utils.resourceid.slidingDialogAnimation import app.revanced.util.fingerprint.legacyFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionReversed 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 compactYpcOfferModuleViewFingerprint = legacyFingerprint( name = "compactYpcOfferModuleViewFingerprint", @@ -24,6 +29,23 @@ internal val compactYpcOfferModuleViewFingerprint = legacyFingerprint( } ) +internal val fullScreenEngagementAdContainerFingerprint = legacyFingerprint( + name = "fullScreenEngagementAdContainerFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = emptyList(), + literals = listOf(fullScreenEngagementAdContainer), + customFingerprint = { method, _ -> + indexOfAddListInstruction(method) >= 0 + } +) + +internal fun indexOfAddListInstruction(method: Method) = + method.indexOfFirstInstructionReversed { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "add" + } + internal val interstitialsContainerFingerprint = legacyFingerprint( name = "interstitialsContainerFingerprint", returnType = "V", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/feed/components/FeedComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/feed/components/FeedComponentsPatch.kt index d4054ad43..ff76bc9f7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/feed/components/FeedComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/feed/components/FeedComponentsPatch.kt @@ -14,7 +14,6 @@ import app.revanced.patches.shared.mainactivity.onCreateMethod import app.revanced.patches.youtube.utils.bottomsheet.bottomSheetHookPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.engagement.engagementPanelHookPatch -import app.revanced.patches.youtube.utils.engagement.hookEngagementPanelState import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH import app.revanced.patches.youtube.utils.extension.Constants.FEED_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.extension.Constants.FEED_PATH @@ -190,8 +189,6 @@ val feedComponentsPatch = bytecodePatch( } } - hookEngagementPanelState(RELATED_VIDEO_CLASS_DESCRIPTOR) - // endregion // region patch for hide subscriptions channel section for tablet diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/livering/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/livering/Fingerprints.kt index 873ef9df8..b41ad0aac 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/livering/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/livering/Fingerprints.kt @@ -1,6 +1,5 @@ package app.revanced.patches.youtube.general.livering -import app.revanced.patches.youtube.utils.resourceid.elementsImage import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstruction @@ -10,14 +9,6 @@ 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 elementsImageFingerprint = legacyFingerprint( - name = "elementsImageFingerprint", - returnType = "Landroid/view/View;", - accessFlags = AccessFlags.PRIVATE or AccessFlags.STATIC, - parameters = listOf("Landroid/view/View;"), - literals = listOf(elementsImage), -) - internal val clientSettingEndpointFingerprint = legacyFingerprint( name = "clientSettingEndpointFingerprint", returnType = "V", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/livering/OpenChannelOfLiveAvatarPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/livering/OpenChannelOfLiveAvatarPatch.kt index 7f7798982..cb105e27d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/livering/OpenChannelOfLiveAvatarPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/livering/OpenChannelOfLiveAvatarPatch.kt @@ -1,25 +1,58 @@ package app.revanced.patches.youtube.general.livering -import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.patch.resourcePatch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE -import app.revanced.patches.youtube.utils.engagement.engagementPanelHookPatch -import app.revanced.patches.youtube.utils.engagement.hookEngagementPanelState import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH import app.revanced.patches.youtube.utils.patch.PatchList.CHANGE_LIVE_RING_CLICK_ACTION -import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch +import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater +import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.settingsPatch +import app.revanced.patches.youtube.video.playbackstart.PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR import app.revanced.patches.youtube.video.playbackstart.playbackStartDescriptorPatch import app.revanced.patches.youtube.video.playbackstart.playbackStartVideoIdReference +import app.revanced.patches.youtube.video.playbackstart.shortsPlaybackStartIntentFingerprint +import app.revanced.patches.youtube.video.playbackstart.shortsPlaybackStartIntentLegacyFingerprint +import app.revanced.util.copyXmlNode import app.revanced.util.fingerprint.methodOrThrow +import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionReversedOrThrow 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 +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.iface.reference.TypeReference + +private val openChannelOfLiveAvatarResourcePatch = resourcePatch( + description = "openChannelOfLiveAvatarResourcePatch" +) { + execute { + arrayOf( + "", "af", "am", "ar", "as", "az", "b+sr+Latn", "be", "bg", "bn", "bs", "ca", + "cs", "da", "de", "el", "en-rGB", "en-rIN", "es", "es-rUS", "et", "eu", "fa", + "fi", "fr", "fr-rCA", "gl", "gu", "hi", "hr", "hu", "hy", "in", "is", "it", + "iw", "ja", "ka", "kk", "km", "kn", "ko", "ky", "lo", "lt", "lv", "mk", "ml", + "mn", "mr", "ms", "my", "nb", "ne", "nl", "or", "pa", "pl", "pt", "pt-rBR", + "pt-rPT", "ro", "ru", "si", "sk", "sl", "sq", "sr", "sv", "sw", "ta", "te", + "th", "tl", "tr", "uk", "ur", "uz", "vi", "zh-rCN", "zh-rHK", "zh-rTW", "zu" + ).forEach { locale -> + val directory = if (locale.isEmpty()) + "values" + else + "values-$locale" + + copyXmlNode("youtube/livering/host", "$directory/strings.xml", "resources") + } + } +} private const val EXTENSION_CLASS_DESCRIPTOR = "$GENERAL_PATH/OpenChannelOfLiveAvatarPatch;" @@ -33,20 +66,13 @@ val openChannelOfLiveAvatarPatch = bytecodePatch( dependsOn( settingsPatch, - sharedResourceIdPatch, + openChannelOfLiveAvatarResourcePatch, playbackStartDescriptorPatch, - engagementPanelHookPatch, + versionCheckPatch, ) execute { - elementsImageFingerprint.methodOrThrow().addInstruction( - 0, - "invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->liveChannelAvatarClicked()V" - ) - - hookEngagementPanelState(EXTENSION_CLASS_DESCRIPTOR) - clientSettingEndpointFingerprint.methodOrThrow().apply { val eqzIndex = indexOfFirstInstructionReversedOrThrow(Opcode.IF_EQZ) var freeIndex = indexOfFirstInstructionReversedOrThrow(eqzIndex, Opcode.NEW_INSTANCE) @@ -54,7 +80,7 @@ val openChannelOfLiveAvatarPatch = bytecodePatch( addInstructionsWithLabels( eqzIndex, """ - invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->openChannelOfLiveAvatar()Z + invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->openChannel()Z move-result v$freeRegister if-eqz v$freeRegister, :ignore return-void @@ -66,6 +92,14 @@ val openChannelOfLiveAvatarPatch = bytecodePatch( val playbackStartIndex = indexOfPlaybackStartDescriptorInstruction(this) + 1 val playbackStartRegister = getInstruction(playbackStartIndex).registerA + val mapIndex = indexOfFirstInstructionOrThrow(playbackStartIndex) { + val reference = getReference() + opcode == Opcode.INVOKE_STATIC && + reference?.returnType == "Ljava/lang/Object;" && + reference.parameterTypes.firstOrNull() == "Ljava/util/Map;" + } + val mapRegister = getInstruction(mapIndex).registerC + freeIndex = indexOfFirstInstructionOrThrow(playbackStartIndex, Opcode.CONST_STRING) freeRegister = getInstruction(freeIndex).registerA @@ -73,11 +107,82 @@ val openChannelOfLiveAvatarPatch = bytecodePatch( playbackStartIndex + 1, """ invoke-virtual { v$playbackStartRegister }, $playbackStartVideoIdReference move-result-object v$freeRegister - invoke-static { v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->openChannelOfLiveAvatar(Ljava/lang/String;)V + invoke-static { v$mapRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->fetchChannelId(Ljava/util/Map;Ljava/lang/String;)V """ ) } + fun MutableMethod.openChannel() = + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + val reference = (instruction as? ReferenceInstruction)?.reference + instruction.opcode == Opcode.NEW_INSTANCE && + reference is TypeReference && + reference.type == "Landroid/os/Bundle;" + } + .map { (index, _) -> index } + .reversed() + .forEach { index -> + val register = getInstruction(index).registerA + + addInstructionsWithLabels( + index, """ + invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->openChannel()Z + move-result v$register + if-eqz v$register, :ignore + return-void + :ignore + nop + """ + ) + } + + + fun fetchChannelIdInstructions( + playbackStartRegister: Int, + mapRegister: Int, + videoIdRegister: Int, + ) = + """ + invoke-virtual { v$playbackStartRegister }, $playbackStartVideoIdReference + move-result-object v$videoIdRegister + invoke-static { v$mapRegister, v$videoIdRegister }, $EXTENSION_CLASS_DESCRIPTOR->fetchChannelId(Ljava/util/Map;Ljava/lang/String;)V + """ + + if (is_19_25_or_greater) { + shortsPlaybackStartIntentFingerprint.methodOrThrow().apply { + openChannel() + + addInstructionsWithLabels( + 0, """ + move-object/from16 v0, p1 + move-object/from16 v1, p2 + ${fetchChannelIdInstructions(0, 1, 2)} + """ + ) + } + } else { + shortsPlaybackStartIntentLegacyFingerprint.methodOrThrow().apply { + openChannel() + + val playbackStartIndex = indexOfFirstInstructionOrThrow { + getReference()?.returnType == PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR + } + val mapIndex = indexOfFirstInstructionReversedOrThrow(playbackStartIndex, Opcode.IPUT) + val mapRegister = getInstruction(mapIndex).registerA + val playbackStartRegister = getInstruction(playbackStartIndex + 1).registerA + val videoIdRegister = getInstruction(playbackStartIndex).registerC + + addInstructionsWithLabels( + playbackStartIndex + 2, """ + move-object/from16 v$mapRegister, p2 + ${fetchChannelIdInstructions(playbackStartRegister, mapRegister, videoIdRegister)} + """ + ) + } + } + // region add settings addPreference( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/snackbar/SnackBarComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/snackbar/SnackBarComponentsPatch.kt index 5175a04ec..83821d0e9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/snackbar/SnackBarComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/snackbar/SnackBarComponentsPatch.kt @@ -5,6 +5,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction +import app.revanced.patcher.patch.booleanOption import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.stringOption @@ -28,6 +29,7 @@ import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.getNode import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.indexOfFirstInstructionReversedOrThrow import app.revanced.util.valueOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction @@ -77,22 +79,31 @@ private val snackBarComponentsBytecodePatch = bytecodePatch( bottomUiContainerThemeFingerprint.matchOrThrow().let { it.method.apply { - val startIndex = it.patternMatch!!.startIndex - val appThemeIndex = startIndex + 1 - val darkThemeIndex = startIndex + 2 - val insertIndex = startIndex + 3 + val darkThemeIndex = it.patternMatch!!.startIndex + 2 + val darkThemeReference = getInstruction(darkThemeIndex).reference.toString() - val appThemeRegister = - getInstruction(appThemeIndex).registerA - val darkThemeRegister = - getInstruction(darkThemeIndex).registerA + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + instruction.opcode == Opcode.SGET_OBJECT && + (instruction as? ReferenceInstruction)?.reference?.toString() == darkThemeReference + } + .map { (index, _) -> index } + .reversed() + .forEach { index -> + val appThemeIndex = indexOfFirstInstructionReversedOrThrow(index, Opcode.MOVE_RESULT_OBJECT) + val appThemeRegister = + getInstruction(appThemeIndex).registerA + val darkThemeRegister = + getInstruction(index).registerA - addInstructions( - insertIndex, """ - invoke-static {v$appThemeRegister, v$darkThemeRegister}, $EXTENSION_CLASS_DESCRIPTOR->invertSnackBarTheme(Ljava/lang/Enum;Ljava/lang/Enum;)Ljava/lang/Enum; - move-result-object v$appThemeRegister - """ - ) + addInstructions( + index + 1, """ + invoke-static {v$appThemeRegister, v$darkThemeRegister}, $EXTENSION_CLASS_DESCRIPTOR->invertSnackBarTheme(Ljava/lang/Enum;Ljava/lang/Enum;)Ljava/lang/Enum; + move-result-object v$appThemeRegister + """ + ) + } } } @@ -212,6 +223,14 @@ val snackBarComponentsPatch = resourcePatch( required = true, ) + val applyCornerRadiusToPlaylistBottomBarOption by booleanOption( + key = "applyCornerRadiusToPlaylistBottomBar", + default = false, + 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 + ) + val darkThemeBackgroundColor = stringOption( key = "darkThemeBackgroundColor", default = ytBackgroundColorDark, @@ -248,6 +267,8 @@ val snackBarComponentsPatch = resourcePatch( // Check patch options first. val cornerRadius = cornerRadiusOption .valueOrThrow() + val applyCornerRadiusToPlaylistBottomBar = + applyCornerRadiusToPlaylistBottomBarOption == true val darkThemeColor = darkThemeBackgroundColor .valueOrThrow() val lightThemeColor = lightThemeBackgroundColor @@ -326,6 +347,29 @@ val snackBarComponentsPatch = resourcePatch( } } + document("res/values/dimens.xml").use { document -> + val resourcesNode = document.documentElement + val childNodes = resourcesNode.childNodes + + for (i in 0 until childNodes.length) { + val node = childNodes.item(i) as? Element ?: continue + val dimenName = node.getAttribute("name") + + if (dimenName.equals("snackbar_corner_radius")) { + node.textContent = cornerRadius + break + } + } + } + + if (applyCornerRadiusToPlaylistBottomBar) { + document("res/drawable/playlist_entry_point_corner_drawable.xml").use { document -> + document.getNode("corners").apply { + attributes.getNamedItem("android:radius").nodeValue = cornerRadius + } + } + } + // region add settings addPreference( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/startpage/ChangeStartPagePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/startpage/ChangeStartPagePatch.kt index d83f96fa2..b4cc929cf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/startpage/ChangeStartPagePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/startpage/ChangeStartPagePatch.kt @@ -48,9 +48,9 @@ val changeStartPagePatch = bytecodePatch( // There is no browseId assigned to Shorts and Search. // Just hook the Intent action. - intentActionFingerprint.methodOrThrow().addInstruction( + intentFingerprint.methodOrThrow().addInstruction( 0, - "invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->overrideIntentAction(Landroid/content/Intent;)V" + "invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->overrideIntent(Landroid/content/Intent;)V" ) // region add settings diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/startpage/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/startpage/Fingerprints.kt index e67f99af3..c62420265 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/startpage/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/startpage/Fingerprints.kt @@ -15,8 +15,8 @@ internal val browseIdFingerprint = legacyFingerprint( strings = listOf("FEwhat_to_watch"), ) -internal val intentActionFingerprint = legacyFingerprint( - name = "intentActionFingerprint", +internal val intentFingerprint = legacyFingerprint( + name = "intentFingerprint", parameters = listOf("Landroid/content/Intent;"), strings = listOf("has_handled_intent"), ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/actionbuttons/ShortsActionButtonsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/actionbuttons/ShortsActionButtonsPatch.kt index a955491e1..af1a960b9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/actionbuttons/ShortsActionButtonsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/actionbuttons/ShortsActionButtonsPatch.kt @@ -11,7 +11,7 @@ import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.util.ResourceGroup import app.revanced.util.Utils.printInfo import app.revanced.util.copyResources -import app.revanced.util.inputStreamFromBundledResourceOrThrow +import app.revanced.util.inputStreamFromBundledResource import app.revanced.util.lowerCaseOrThrow import java.nio.file.Files import java.nio.file.StandardCopyOption @@ -78,18 +78,25 @@ val shortsActionButtonsPatch = resourcePatch( fromResourceArray.forEach { fromFileName -> drawableDirectories.forEach { drawableDirectory -> val fromFile = "$drawableDirectory/$fromFileName.webp" - val fromPath = res.resolve(fromFile).toPath() + val fromFileResolved = res.resolve(fromFile) val toFile = "$drawableDirectory/$toFileName.webp" - val toPath = res.resolve(toFile).toPath() + val toFileResolved = res.resolve(toFile) val inputStreamForLegacy = - inputStreamFromBundledResourceOrThrow(sourceResourceDirectory, fromFile) - val inputStreamForNew = - inputStreamFromBundledResourceOrThrow(sourceResourceDirectory, fromFile) + inputStreamFromBundledResource(sourceResourceDirectory, fromFile) - Files.copy(inputStreamForLegacy, fromPath, StandardCopyOption.REPLACE_EXISTING) + // Some directory is missing in the bundles. + if (inputStreamForLegacy != null && fromFileResolved.exists()) { + Files.copy(inputStreamForLegacy, fromFileResolved.toPath(), StandardCopyOption.REPLACE_EXISTING) + } if (is_19_36_or_greater) { - Files.copy(inputStreamForNew, toPath, StandardCopyOption.REPLACE_EXISTING) + val inputStreamForNew = + inputStreamFromBundledResource(sourceResourceDirectory, fromFile) + + // Some directory is missing in the bundles. + if (inputStreamForNew != null && toFileResolved.exists()) { + Files.copy(inputStreamForNew, toFileResolved.toPath(), StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt index 8a75f139d..28b2f4446 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt @@ -77,10 +77,11 @@ val themePatch = resourcePatch( arrayOf("values", "values-v31").forEach { path -> document("res/$path/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 node.textContent = when (node.getAttribute("name")) { "yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/accessibility/AccessibilityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/accessibility/AccessibilityPatch.kt new file mode 100644 index 000000000..cf0098362 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/accessibility/AccessibilityPatch.kt @@ -0,0 +1,48 @@ +package app.revanced.patches.youtube.misc.accessibility + +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_ACCESSIBILITY_CONTROLS_DIALOG +import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference +import app.revanced.patches.youtube.utils.settings.settingsPatch +import app.revanced.util.findMethodOrThrow +import app.revanced.util.fingerprint.mutableClassOrThrow +import app.revanced.util.indexOfFirstInstruction +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.or +import app.revanced.util.returnEarly +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction + +@Suppress("unused") +val accessibilityPatch = bytecodePatch( + HIDE_ACCESSIBILITY_CONTROLS_DIALOG.title, + HIDE_ACCESSIBILITY_CONTROLS_DIALOG.summary, +) { + compatibleWith(COMPATIBLE_PACKAGE) + + dependsOn(settingsPatch) + + execute { + + playerAccessibilitySettingsEduControllerParentFingerprint + .mutableClassOrThrow() + .methods + .first { method -> method.name == "" } + .apply { + val lifecycleObserverIndex = indexOfFirstInstructionReversedOrThrow(Opcode.NEW_INSTANCE) + val lifecycleObserverClass = getInstruction(lifecycleObserverIndex).reference.toString() + + findMethodOrThrow(lifecycleObserverClass) { + accessFlags == AccessFlags.PUBLIC or AccessFlags.FINAL && + parameterTypes.size == 1 && + indexOfFirstInstruction(Opcode.INVOKE_DIRECT) >= 0 + }.returnEarly() + } + + addPreference(HIDE_ACCESSIBILITY_CONTROLS_DIALOG) + + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/accessibility/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/accessibility/Fingerprints.kt new file mode 100644 index 000000000..ef931211d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/accessibility/Fingerprints.kt @@ -0,0 +1,49 @@ +package app.revanced.patches.youtube.misc.accessibility + +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 +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference + +/** + * The name of the class to find is 'Lcom/google/android/apps/youtube/app/player/overlay/accessibility/PlayerAccessibilitySettingsEduController$LifecycleObserver;'. + * It is one of the types of fields in the class. + * This class name has been obfuscated since YouTube 18.25.40. + * This class is always the same structure despite not a synthetic class. + * (Same field and method, checked in YouTube 17.34.36 ~ 20.02.41). + */ +internal val playerAccessibilitySettingsEduControllerParentFingerprint = legacyFingerprint( + name = "playerAccessibilitySettingsEduControllerParentFingerprint", + returnType = "V", + accessFlags = AccessFlags.STATIC or AccessFlags.CONSTRUCTOR, + parameters = emptyList(), + opcodes = listOf( + Opcode.SGET_OBJECT, + Opcode.CONST_WIDE_16, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_WIDE, + Opcode.SPUT_WIDE, + Opcode.RETURN_VOID, + ), + customFingerprint = custom@{ method, classDef -> + // The number of fields is always 12. + if (classDef.fields.count() != 12) { + return@custom false + } + // The number of methods is always 2 or 3. + if (classDef.methods.count() > 3) { + return@custom false + } + val implementation = method.implementation + ?: return@custom false + val instructions = implementation.instructions + val instructionCount = instructions.count() + if (instructionCount != 6) { + return@custom false + } + + ((instructions.elementAt(0) as? ReferenceInstruction)?.reference as? FieldReference)?.toString() == "Ljava/util/concurrent/TimeUnit;->DAYS:Ljava/util/concurrent/TimeUnit;" + } +) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt index cf3729409..c6380be34 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch.kt @@ -1,5 +1,6 @@ package app.revanced.patches.youtube.misc.backgroundplayback +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.instructions import app.revanced.patcher.patch.bytecodePatch @@ -7,10 +8,14 @@ import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PAC import app.revanced.patches.youtube.utils.extension.Constants.MISC_PATH import app.revanced.patches.youtube.utils.patch.PatchList.REMOVE_BACKGROUND_PLAYBACK_RESTRICTIONS import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch +import app.revanced.patches.youtube.utils.playservice.is_19_34_or_greater +import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.util.addInstructionsAtControlFlowLabel import app.revanced.util.findInstructionIndicesReversedOrThrow +import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall +import app.revanced.util.fingerprint.matchOrThrow import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.fingerprint.originalMethodOrThrow import app.revanced.util.getReference @@ -32,6 +37,7 @@ val backgroundPlaybackPatch = bytecodePatch( dependsOn( playerTypeHookPatch, settingsPatch, + versionCheckPatch, ) execute { @@ -39,7 +45,7 @@ val backgroundPlaybackPatch = bytecodePatch( arrayOf( backgroundPlaybackManagerFingerprint to "isBackgroundPlaybackAllowed", backgroundPlaybackManagerShortsFingerprint to "isBackgroundShortsPlaybackAllowed", - ).forEach { (fingerprint, integrationsMethod) -> + ).forEach { (fingerprint, extensionsMethod) -> fingerprint.methodOrThrow().apply { findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index -> val register = getInstruction(index).registerA @@ -47,7 +53,7 @@ val backgroundPlaybackPatch = bytecodePatch( addInstructionsAtControlFlowLabel( index, """ - invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->$integrationsMethod(Z)Z + invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->$extensionsMethod(Z)Z move-result v$register """, ) @@ -68,7 +74,35 @@ val backgroundPlaybackPatch = bytecodePatch( } // Force allowing background play for Shorts. - shortsBackgroundPlaybackFeatureFlagFingerprint.methodOrThrow().returnEarly(true) + shortsBackgroundPlaybackFeatureFlagFingerprint.injectLiteralInstructionBooleanCall( + SHORTS_BACKGROUND_PLAYBACK_FEATURE_FLAG, + "$EXTENSION_CLASS_DESCRIPTOR->isBackgroundShortsPlaybackAllowed(Z)Z" + ) + + // Fix PiP mode issue. + if (is_19_34_or_greater) { + arrayOf( + backgroundPlaybackManagerCairoFragmentPrimaryFingerprint, + backgroundPlaybackManagerCairoFragmentSecondaryFingerprint + ).forEach { fingerprint -> + fingerprint.matchOrThrow(backgroundPlaybackManagerCairoFragmentParentFingerprint).let { + it.method.apply { + val insertIndex = it.patternMatch!!.startIndex + 4 + val insertRegister = getInstruction(insertIndex).registerA + + addInstruction( + insertIndex, + "const/4 v$insertRegister, 0x0" + ) + } + } + } + + pipInputConsumerFeatureFlagFingerprint.injectLiteralInstructionBooleanCall( + PIP_INPUT_CONSUMER_FEATURE_FLAG, + "0x0" + ) + } // Force allowing background play for videos labeled for kids. kidsBackgroundPlaybackPolicyControllerFingerprint.methodOrThrow( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/Fingerprints.kt index 7fd5299c8..fca3d72da 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/backgroundplayback/Fingerprints.kt @@ -1,6 +1,7 @@ package app.revanced.patches.youtube.misc.backgroundplayback import app.revanced.patches.youtube.utils.PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR +import app.revanced.patches.youtube.utils.fix.cairo.cairoFragmentConfigFingerprint import app.revanced.patches.youtube.utils.resourceid.backgroundCategory import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.getReference @@ -64,10 +65,83 @@ internal val backgroundPlaybackManagerShortsFingerprint = legacyFingerprint( literals = listOf(151635310L), ) +internal val backgroundPlaybackManagerCairoFragmentParentFingerprint = legacyFingerprint( + name = "backgroundPlaybackManagerCairoFragmentParentFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + parameters = emptyList(), + strings = listOf("yt_android_settings"), + customFingerprint = { method, _ -> + method.definingClass != "Lcom/google/android/apps/youtube/app/settings/AboutPrefsFragment;" + } +) + +/** + * Matches using the class found in [backgroundPlaybackManagerCairoFragmentParentFingerprint]. + * + * In this method, the value of the cairoFragmentConfig - [cairoFragmentConfigFingerprint] - must be disabled. + * If not, sometimes the pause / play button may not work when entering the PIP mode. + * See [ReVanced_Extended#2764](https://github.com/inotia00/ReVanced_Extended/issues/2764). + */ +internal val backgroundPlaybackManagerCairoFragmentPrimaryFingerprint = legacyFingerprint( + name = "backgroundPlaybackManagerCairoFragmentPrimaryFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + parameters = emptyList(), + opcodes = listOf( + Opcode.INVOKE_SUPER, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, // Method of [cairoFragmentConfigFingerprint] + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.IGET_OBJECT, + Opcode.CONST_4, + Opcode.IPUT_OBJECT, + ), +) + +/** + * Matches using the class found in [backgroundPlaybackManagerCairoFragmentParentFingerprint]. + * + * In this method, the value of the cairoFragmentConfig - [cairoFragmentConfigFingerprint] - must be disabled. + * If not, sometimes the pause / play button may not work when entering the PIP mode. + * See [ReVanced_Extended#2764](https://github.com/inotia00/ReVanced_Extended/issues/2764). + */ +internal val backgroundPlaybackManagerCairoFragmentSecondaryFingerprint = legacyFingerprint( + name = "backgroundPlaybackManagerCairoFragmentSecondaryFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + parameters = emptyList(), + opcodes = listOf( + Opcode.INVOKE_SUPER, + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, // Method of [cairoFragmentConfigFingerprint] + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.IGET_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.IGET_OBJECT, + Opcode.NEW_INSTANCE, + ), +) + +internal const val PIP_INPUT_CONSUMER_FEATURE_FLAG = 45638483L + +/** + * Fix 'E/InputDispatcher: Window handle pip_input_consumer has no registered input channel' + * Related with [ReVanced_Extended#2764](https://github.com/inotia00/ReVanced_Extended/issues/2764). + */ +internal val pipInputConsumerFeatureFlagFingerprint = legacyFingerprint( + name = "pipInputConsumerFeatureFlagFingerprint", + literals = listOf(PIP_INPUT_CONSUMER_FEATURE_FLAG), +) + +internal const val SHORTS_BACKGROUND_PLAYBACK_FEATURE_FLAG = 45415425L + internal val shortsBackgroundPlaybackFeatureFlagFingerprint = legacyFingerprint( name = "shortsBackgroundPlaybackFeatureFlagFingerprint", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, returnType = "Z", parameters = emptyList(), - literals = listOf(45415425L), + literals = listOf(SHORTS_BACKGROUND_PLAYBACK_FEATURE_FLAG), ) \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/openlinks/externally/OpenLinksExternallyPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/openlinks/externally/OpenLinksExternallyPatch.kt index 4a7d1cd9a..d20e8e1a6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/openlinks/externally/OpenLinksExternallyPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/openlinks/externally/OpenLinksExternallyPatch.kt @@ -2,7 +2,7 @@ package app.revanced.patches.youtube.misc.openlinks.externally import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patches.shared.transformation.transformInstructionsPatch +import app.revanced.patches.all.misc.transformation.transformInstructionsPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.extension.Constants.MISC_PATH import app.revanced.patches.youtube.utils.patch.PatchList.OPEN_LINKS_EXTERNALLY diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/watchhistory/WatchHistoryPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/watchhistory/WatchHistoryPatch.kt index 5916130c9..fb201c2ac 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/watchhistory/WatchHistoryPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/watchhistory/WatchHistoryPatch.kt @@ -1,13 +1,12 @@ package app.revanced.patches.youtube.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.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE -import app.revanced.patches.youtube.utils.extension.Constants.MISC_PATH import app.revanced.patches.youtube.utils.patch.PatchList.WATCH_HISTORY import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.settingsPatch -import app.revanced.patches.youtube.utils.trackingurlhook.hookTrackingUrl -import app.revanced.patches.youtube.utils.trackingurlhook.trackingUrlHookPatch @Suppress("unused") val watchHistoryPatch = bytecodePatch( @@ -23,7 +22,7 @@ val watchHistoryPatch = bytecodePatch( execute { - hookTrackingUrl("$MISC_PATH/WatchHistoryPatch;->replaceTrackingUrl(Landroid/net/Uri;)Landroid/net/Uri;") + hookWatchHistory() // region add settings diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/action/ActionButtonsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/action/ActionButtonsPatch.kt index d1d695fdd..ce57443ec 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/action/ActionButtonsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/action/ActionButtonsPatch.kt @@ -1,7 +1,6 @@ package app.revanced.patches.youtube.player.action import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.patch.booleanOption import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.shared.litho.addLithoFilter import app.revanced.patches.shared.litho.emptyComponentsFingerprint @@ -10,10 +9,11 @@ import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PAC import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_ACTION_BUTTONS +import app.revanced.patches.youtube.utils.request.buildRequestPatch +import app.revanced.patches.youtube.utils.request.hookBuildRequest import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.patches.youtube.video.information.videoInformationPatch -import app.revanced.util.Utils.trimIndentMultiline import app.revanced.util.addInstructionsAtControlFlowLabel import app.revanced.util.findMethodOrThrow import app.revanced.util.fingerprint.methodOrThrow @@ -44,72 +44,61 @@ val actionButtonsPatch = bytecodePatch( settingsPatch, lithoFilterPatch, videoInformationPatch, - ) - - val hideActionButtonByIndex by booleanOption( - key = "hideActionButtonByIndex", - default = false, - title = "Hide action buttons by index", - description = """ - Add an option to hide action buttons by index. - - This setting is still experimental, so use it only for debugging purposes. - """.trimIndentMultiline(), - required = true + buildRequestPatch, ) execute { addLithoFilter(FILTER_CLASS_DESCRIPTOR) - var settingArray = arrayOf( - "PREFERENCE_SCREEN: PLAYER", - "SETTINGS: HIDE_ACTION_BUTTONS" - ) + // region patch for hide action buttons by index - if (hideActionButtonByIndex == true) { - componentListFingerprint.methodOrThrow(emptyComponentsFingerprint).apply { - val conversionContextToStringMethod = - findMethodOrThrow(parameters[1].type) { - name == "toString" - } - val identifierReference = with (conversionContextToStringMethod) { - val identifierStringIndex = - indexOfFirstStringInstructionOrThrow(", identifierProperty=") - val identifierStringAppendIndex = - indexOfFirstInstructionOrThrow(identifierStringIndex, Opcode.INVOKE_VIRTUAL) - val identifierStringAppendIndexRegister = getInstruction(identifierStringAppendIndex).registerD - val identifierAppendIndex = - indexOfFirstInstructionOrThrow(identifierStringAppendIndex + 1, Opcode.INVOKE_VIRTUAL) - val identifierRegister = getInstruction(identifierAppendIndex).registerD - val identifierIndex = indexOfFirstInstructionReversedOrThrow(identifierAppendIndex) { - opcode == Opcode.IGET_OBJECT && - getReference()?.type == "Ljava/lang/String;" && - (this as? TwoRegisterInstruction)?.registerA == identifierRegister - } - getInstruction(identifierIndex).reference + componentListFingerprint.methodOrThrow(emptyComponentsFingerprint).apply { + val conversionContextToStringMethod = + findMethodOrThrow(parameters[1].type) { + name == "toString" } - - val listIndex = implementation!!.instructions.lastIndex - val listRegister = getInstruction(listIndex).registerA - val identifierRegister = listRegister + 1 - - addInstructionsAtControlFlowLabel( - listIndex, """ - move-object/from16 v$identifierRegister, p2 - iget-object v$identifierRegister, v$identifierRegister, $identifierReference - invoke-static {v$listRegister, v$identifierRegister}, $ACTION_BUTTONS_CLASS_DESCRIPTOR->hideActionButtonByIndex(Ljava/util/List;Ljava/lang/String;)Ljava/util/List; - move-result-object v$listRegister - """ - ) - - settingArray += "SETTINGS: HIDE_BUTTONS_BY_INDEX" + val identifierReference = with (conversionContextToStringMethod) { + val identifierStringIndex = + indexOfFirstStringInstructionOrThrow(", identifierProperty=") + val identifierStringAppendIndex = + indexOfFirstInstructionOrThrow(identifierStringIndex, Opcode.INVOKE_VIRTUAL) + val identifierStringAppendIndexRegister = getInstruction(identifierStringAppendIndex).registerD + val identifierAppendIndex = + indexOfFirstInstructionOrThrow(identifierStringAppendIndex + 1, Opcode.INVOKE_VIRTUAL) + val identifierRegister = getInstruction(identifierAppendIndex).registerD + val identifierIndex = indexOfFirstInstructionReversedOrThrow(identifierAppendIndex) { + opcode == Opcode.IGET_OBJECT && + getReference()?.type == "Ljava/lang/String;" && + (this as? TwoRegisterInstruction)?.registerA == identifierRegister + } + getInstruction(identifierIndex).reference } + + val listIndex = implementation!!.instructions.lastIndex + val listRegister = getInstruction(listIndex).registerA + val identifierRegister = listRegister + 1 + + addInstructionsAtControlFlowLabel( + listIndex, """ + move-object/from16 v$identifierRegister, p2 + iget-object v$identifierRegister, v$identifierRegister, $identifierReference + invoke-static {v$listRegister, v$identifierRegister}, $ACTION_BUTTONS_CLASS_DESCRIPTOR->hideActionButtonByIndex(Ljava/util/List;Ljava/lang/String;)Ljava/util/List; + move-result-object v$listRegister + """ + ) } + hookBuildRequest("$ACTION_BUTTONS_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V") + + // endregion + // region add settings addPreference( - settingArray, + arrayOf( + "PREFERENCE_SCREEN: PLAYER", + "SETTINGS: HIDE_ACTION_BUTTONS" + ), HIDE_ACTION_BUTTONS ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/components/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/components/Fingerprints.kt index 33098e261..b489f3dd6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/components/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/components/Fingerprints.kt @@ -2,14 +2,12 @@ package app.revanced.patches.youtube.player.components -import app.revanced.patches.youtube.utils.resourceid.componentLongClickListener import app.revanced.patches.youtube.utils.resourceid.darkBackground import app.revanced.patches.youtube.utils.resourceid.donationCompanion import app.revanced.patches.youtube.utils.resourceid.easySeekEduContainer import app.revanced.patches.youtube.utils.resourceid.endScreenElementLayoutCircle import app.revanced.patches.youtube.utils.resourceid.endScreenElementLayoutIcon import app.revanced.patches.youtube.utils.resourceid.endScreenElementLayoutVideo -import app.revanced.patches.youtube.utils.resourceid.offlineActionsVideoDeletedUndoSnackbarText import app.revanced.patches.youtube.utils.resourceid.scrubbing import app.revanced.patches.youtube.utils.resourceid.seekEasyHorizontalTouchOffsetToStartScrubbing import app.revanced.patches.youtube.utils.resourceid.suggestedAction @@ -45,6 +43,8 @@ internal val nextGenWatchLayoutFingerprint = legacyFingerprint( } ) +internal const val RESTORE_SLIDE_TO_SEEK_FEATURE_FLAG = 45411329L + /** * This value restores the 'Slide to seek' behavior. * Deprecated in YouTube v19.18.41+. @@ -54,7 +54,7 @@ internal val restoreSlideToSeekBehaviorFingerprint = legacyFingerprint( returnType = "Z", parameters = emptyList(), opcodes = listOf(Opcode.MOVE_RESULT), - literals = listOf(45411329L), + literals = listOf(RESTORE_SLIDE_TO_SEEK_FEATURE_FLAG), ) internal val slideToSeekMotionEventFingerprint = legacyFingerprint( @@ -72,6 +72,8 @@ internal val slideToSeekMotionEventFingerprint = legacyFingerprint( ) ) +internal const val SPEED_OVERLAY_FEATURE_FLAG = 45411330L + /** * This value disables 'Playing at 2x speed' while holding down. * Deprecated in YouTube v19.18.41+. @@ -81,7 +83,7 @@ internal val speedOverlayFingerprint = legacyFingerprint( returnType = "Z", parameters = emptyList(), opcodes = listOf(Opcode.MOVE_RESULT), - literals = listOf(45411330L), + literals = listOf(SPEED_OVERLAY_FEATURE_FLAG), ) /** @@ -152,6 +154,13 @@ internal val filmStripOverlayPreviewFingerprint = legacyFingerprint( ) ) +internal const val FILM_STRIP_OVERLAY_V2_FEATURE_FLAG = 45420198L + +internal val filmStripOverlayConfigV2Fingerprint = legacyFingerprint( + name = "filmStripOverlayConfigV2Fingerprint", + literals = listOf(FILM_STRIP_OVERLAY_V2_FEATURE_FLAG), +) + internal val infoCardsIncognitoFingerprint = legacyFingerprint( name = "infoCardsIncognitoFingerprint", returnType = "Ljava/lang/Boolean;", @@ -198,30 +207,6 @@ internal val layoutVideoFingerprint = legacyFingerprint( literals = listOf(endScreenElementLayoutVideo), ) -internal val lithoComponentOnClickListenerFingerprint = legacyFingerprint( - name = "lithoComponentOnClickListenerFingerprint", - returnType = "V", - accessFlags = AccessFlags.PRIVATE or AccessFlags.STATIC, - parameters = listOf("L"), - literals = listOf(componentLongClickListener), -) - -internal val engagementPanelPlaylistSyntheticFingerprint = legacyFingerprint( - name = "engagementPanelPlaylistSyntheticFingerprint", - strings = listOf("engagement-panel-playlist"), - customFingerprint = { _, classDef -> - classDef.interfaces.contains("Landroid/view/View${'$'}OnClickListener;") - } -) - -internal val offlineActionsOnClickListenerFingerprint = legacyFingerprint( - name = "offlineActionsOnClickListenerFingerprint", - returnType = "V", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = listOf("Ljava/lang/String;"), - literals = listOf(offlineActionsVideoDeletedUndoSnackbarText), -) - internal val quickSeekOverlayFingerprint = legacyFingerprint( name = "quickSeekOverlayFingerprint", returnType = "V", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/components/PlayerComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/components/PlayerComponentsPatch.kt index 029818d01..326b1e5c6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/components/PlayerComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/components/PlayerComponentsPatch.kt @@ -13,10 +13,13 @@ import app.revanced.patches.shared.litho.addLithoFilter import app.revanced.patches.shared.litho.lithoFilterPatch import app.revanced.patches.shared.spans.addSpanFilter import app.revanced.patches.shared.spans.inclusiveSpanPatch -import app.revanced.patches.shared.startVideoInformerFingerprint import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.controlsoverlay.controlsOverlayConfigPatch -import app.revanced.patches.youtube.utils.engagementPanelBuilderFingerprint +import app.revanced.patches.youtube.utils.engagement.engagementPanelBuilderMethod +import app.revanced.patches.youtube.utils.engagement.engagementPanelFreeRegister +import app.revanced.patches.youtube.utils.engagement.engagementPanelHookPatch +import app.revanced.patches.youtube.utils.engagement.engagementPanelIdIndex +import app.revanced.patches.youtube.utils.engagement.engagementPanelIdRegister import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.extension.Constants.SPANS_PATH @@ -24,6 +27,8 @@ import app.revanced.patches.youtube.utils.fix.suggestedvideoendscreen.suggestedV import app.revanced.patches.youtube.utils.patch.PatchList.PLAYER_COMPONENTS import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch import app.revanced.patches.youtube.utils.playservice.is_20_02_or_greater +import app.revanced.patches.youtube.utils.playservice.is_20_03_or_greater +import app.revanced.patches.youtube.utils.playservice.is_20_05_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.resourceid.darkBackground import app.revanced.patches.youtube.utils.resourceid.fadeDurationFast @@ -37,7 +42,6 @@ import app.revanced.patches.youtube.utils.youtubeControlsOverlayFingerprint import app.revanced.patches.youtube.video.information.hookVideoInformation import app.revanced.patches.youtube.video.information.videoInformationPatch import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT -import app.revanced.util.Utils.printWarn import app.revanced.util.findMethodOrThrow import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall import app.revanced.util.fingerprint.injectLiteralInstructionViewCall @@ -47,7 +51,6 @@ 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 @@ -91,8 +94,8 @@ private val speedOverlayPatch = bytecodePatch( // region patch for Disable speed overlay (Enable slide to seek) mapOf( - restoreSlideToSeekBehaviorFingerprint to 45411329L, - speedOverlayFingerprint to 45411330L + restoreSlideToSeekBehaviorFingerprint to RESTORE_SLIDE_TO_SEEK_FEATURE_FLAG, + speedOverlayFingerprint to SPEED_OVERLAY_FEATURE_FLAG ).forEach { (fingerprint, literal) -> fingerprint.injectLiteralInstructionBooleanCall( literal, @@ -237,18 +240,21 @@ private val speedOverlayPatch = bytecodePatch( ) } - speedOverlayTextValueFingerprint.matchOrThrow().let { - it.method.apply { - val targetIndex = it.patternMatch!!.startIndex - val targetRegister = - getInstruction(targetIndex).registerA + // Removed in YouTube 20.03+ + if (!is_20_03_or_greater) { + speedOverlayTextValueFingerprint.matchOrThrow().let { + it.method.apply { + val targetIndex = it.patternMatch!!.startIndex + val targetRegister = + getInstruction(targetIndex).registerA - addInstructions( - targetIndex + 1, """ - invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->speedOverlayValue()D - move-result-wide v$targetRegister - """ - ) + addInstructions( + targetIndex + 1, """ + invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->speedOverlayValue()D + move-result-wide v$targetRegister + """ + ) + } } } @@ -281,6 +287,7 @@ val playerComponentsPatch = bytecodePatch( suggestedVideoEndScreenPatch, videoInformationPatch, versionCheckPatch, + engagementPanelHookPatch, ) execute { @@ -368,54 +375,19 @@ val playerComponentsPatch = bytecodePatch( // region patch for disable auto player popup panels - fun MutableMethod.hookInitVideoPanel(initVideoPanel: Int) = - addInstructions( - 0, """ - const/4 v0, $initVideoPanel - invoke-static {v0}, $PLAYER_CLASS_DESCRIPTOR->setInitVideoPanel(Z)V - """ - ) - - arrayOf( - lithoComponentOnClickListenerFingerprint, - offlineActionsOnClickListenerFingerprint, - ).forEach { fingerprint -> - fingerprint.methodOrThrow().apply { - val syntheticIndex = - indexOfFirstInstruction(Opcode.NEW_INSTANCE) - if (syntheticIndex >= 0) { - val syntheticReference = - getInstruction(syntheticIndex).reference.toString() - - findMethodOrThrow(syntheticReference) { - name == "onClick" - }.hookInitVideoPanel(0) - } else { - printWarn("target Opcode not found in ${fingerprint.first}") - } - } - } - - findMethodOrThrow( - engagementPanelPlaylistSyntheticFingerprint.methodOrThrow().definingClass - ) { - name == "onClick" - }.hookInitVideoPanel(0) - - startVideoInformerFingerprint.methodOrThrow().hookInitVideoPanel(1) - - engagementPanelBuilderFingerprint.methodOrThrow().apply { - addInstructionsWithLabels( - 0, """ - move/from16 v0, p4 - invoke-static {v0}, $PLAYER_CLASS_DESCRIPTOR->disableAutoPlayerPopupPanels(Z)Z - move-result v0 - if-eqz v0, :shown - const/4 v0, 0x0 - return-object v0 - """, ExternalLabel("shown", getInstruction(0)) - ) - } + engagementPanelBuilderMethod.addInstructionsWithLabels( + engagementPanelIdIndex, """ + move/from16 v$engagementPanelFreeRegister, p4 + invoke-static {v$engagementPanelFreeRegister, v$engagementPanelIdRegister}, $PLAYER_CLASS_DESCRIPTOR->disableAutoPlayerPopupPanels(ZLjava/lang/String;)Z + move-result v$engagementPanelFreeRegister + if-eqz v$engagementPanelFreeRegister, :shown + const/4 v$engagementPanelFreeRegister, 0x0 + return-object v$engagementPanelFreeRegister + :shown + nop + """ + ) + hookVideoInformation("$PLAYER_CLASS_DESCRIPTOR->disableAutoPlayerPopupPanels(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZ)V") // endregion @@ -509,32 +481,42 @@ val playerComponentsPatch = bytecodePatch( fingerprint.methodOrThrow(filmStripOverlayParentFingerprint).hookFilmstripOverlay() } - youtubeControlsOverlayFingerprint.methodOrThrow().apply { - val constIndex = indexOfFirstLiteralInstructionOrThrow(fadeDurationFast) - val constRegister = getInstruction(constIndex).registerA - val insertIndex = - indexOfFirstInstructionReversedOrThrow(constIndex, Opcode.INVOKE_VIRTUAL) + 1 - val jumpIndex = implementation!!.instructions.let { instruction -> - insertIndex + instruction.subList(insertIndex, instruction.size - 1) - .indexOfFirst { instructions -> - instructions.opcode == Opcode.GOTO || instructions.opcode == Opcode.GOTO_16 - } + // Removed in YouTube 20.05+ + if (!is_20_05_or_greater) { + youtubeControlsOverlayFingerprint.methodOrThrow().apply { + val constIndex = indexOfFirstLiteralInstructionOrThrow(fadeDurationFast) + val constRegister = getInstruction(constIndex).registerA + val insertIndex = + indexOfFirstInstructionReversedOrThrow(constIndex, Opcode.INVOKE_VIRTUAL) + 1 + val jumpIndex = implementation!!.instructions.let { instruction -> + insertIndex + instruction.subList(insertIndex, instruction.size - 1) + .indexOfFirst { instructions -> + instructions.opcode == Opcode.GOTO || instructions.opcode == Opcode.GOTO_16 + } + } + + val replaceInstruction = getInstruction(insertIndex) + val replaceReference = + getInstruction(insertIndex).reference + + addInstructionsWithLabels( + insertIndex + 1, getAllLiteralComponent(insertIndex, jumpIndex - 1) + """ + const v$constRegister, $fadeDurationFast + invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->hideFilmstripOverlay()Z + move-result v${replaceInstruction.registerA} + if-nez v${replaceInstruction.registerA}, :hidden + iget-object v${replaceInstruction.registerA}, v${replaceInstruction.registerB}, $replaceReference + """, ExternalLabel("hidden", getInstruction(jumpIndex)) + ) + removeInstruction(insertIndex) } - - val replaceInstruction = getInstruction(insertIndex) - val replaceReference = - getInstruction(insertIndex).reference - - addInstructionsWithLabels( - insertIndex + 1, getAllLiteralComponent(insertIndex, jumpIndex - 1) + """ - const v$constRegister, $fadeDurationFast - invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->hideFilmstripOverlay()Z - move-result v${replaceInstruction.registerA} - if-nez v${replaceInstruction.registerA}, :hidden - iget-object v${replaceInstruction.registerA}, v${replaceInstruction.registerB}, $replaceReference - """, ExternalLabel("hidden", getInstruction(jumpIndex)) + } else { + // This is a new film strip overlay added to YouTube 20.05+ + // Disabling this flag is not related to the operation of the patch. + filmStripOverlayConfigV2Fingerprint.injectLiteralInstructionBooleanCall( + FILM_STRIP_OVERLAY_V2_FEATURE_FLAG, + "0x0" ) - removeInstruction(insertIndex) } // endregion @@ -571,6 +553,7 @@ val playerComponentsPatch = bytecodePatch( ) } + // Removed in YouTube 20.02+ if (!is_20_02_or_greater) { youtubeControlsOverlayFingerprint.methodOrThrow().apply { val insertIndex = diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/DescriptionComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/DescriptionComponentsPatch.kt index 7cfda01cf..12be08c50 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/DescriptionComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/DescriptionComponentsPatch.kt @@ -9,6 +9,7 @@ import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.litho.addLithoFilter import app.revanced.patches.shared.litho.lithoFilterPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.engagement.engagementPanelHookPatch import app.revanced.patches.youtube.utils.extension.Constants.COMPONENTS_PATH import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.patch.PatchList.DESCRIPTION_COMPONENTS @@ -46,6 +47,7 @@ val descriptionComponentsPatch = bytecodePatch( lithoFilterPatch, playerTypeHookPatch, recyclerViewTreeObserverPatch, + engagementPanelHookPatch, sharedResourceIdPatch, versionCheckPatch, ) @@ -104,19 +106,6 @@ val descriptionComponentsPatch = bytecodePatch( ) } - engagementPanelTitleFingerprint.methodOrThrow(engagementPanelTitleParentFingerprint) - .apply { - val contentDescriptionIndex = indexOfContentDescriptionInstruction(this) - val contentDescriptionRegister = - getInstruction(contentDescriptionIndex).registerD - - addInstruction( - contentDescriptionIndex, - "invoke-static {v$contentDescriptionRegister}," + - "$PLAYER_CLASS_DESCRIPTOR->setContentDescription(Ljava/lang/String;)V" - ) - } - recyclerViewTreeObserverHook("$PLAYER_CLASS_DESCRIPTOR->onVideoDescriptionCreate(Landroid/support/v7/widget/RecyclerView;)V") settingArray += "SETTINGS: DESCRIPTION_INTERACTION" diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/Fingerprints.kt index 135a3a021..0fccda3a4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/descriptions/Fingerprints.kt @@ -3,30 +3,10 @@ package app.revanced.patches.youtube.player.descriptions import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstruction -import app.revanced.util.indexOfFirstInstructionReversed 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 engagementPanelTitleFingerprint = legacyFingerprint( - name = "engagementPanelTitleFingerprint", - strings = listOf(". "), - customFingerprint = { method, _ -> - indexOfContentDescriptionInstruction(method) >= 0 - } -) - -internal val engagementPanelTitleParentFingerprint = legacyFingerprint( - name = "engagementPanelTitleParentFingerprint", - strings = listOf("[EngagementPanelTitleHeader] Cannot remove action buttons from header as the child count is out of sync. Buttons to remove exceed current header child count.") -) - -internal fun indexOfContentDescriptionInstruction(method: Method) = - method.indexOfFirstInstructionReversed { - opcode == Opcode.INVOKE_VIRTUAL && - getReference()?.name == "setContentDescription" - } - /** * This fingerprint is compatible with YouTube v18.35.xx~ * Nonetheless, the patch works in YouTube v19.02.xx~ diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/Fingerprints.kt similarity index 88% rename from patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/Fingerprints.kt rename to patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/Fingerprints.kt index 304c4d0f9..df9835b17 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/Fingerprints.kt @@ -1,12 +1,13 @@ @file:Suppress("SpellCheckingInspection") -package app.revanced.patches.youtube.general.miniplayer +package app.revanced.patches.youtube.player.miniplayer.general import app.revanced.patches.youtube.utils.resourceid.floatyBarTopMargin import app.revanced.patches.youtube.utils.resourceid.miniplayerMaxSize import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerClose import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerExpand import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerForwardButton +import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerOverlayActionButton import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerRewindButton import app.revanced.patches.youtube.utils.resourceid.scrimOverlay import app.revanced.patches.youtube.utils.resourceid.ytOutlinePictureInPictureWhite @@ -15,6 +16,8 @@ import app.revanced.util.or import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode +// region legacy miniplayer + internal val miniplayerDimensionsCalculatorParentFingerprint = legacyFingerprint( name = "miniplayerDimensionsCalculatorParentFingerprint", returnType = "V", @@ -23,6 +26,68 @@ internal val miniplayerDimensionsCalculatorParentFingerprint = legacyFingerprint literals = listOf(floatyBarTopMargin), ) +/** + * Matches using the class found in [miniplayerDimensionsCalculatorParentFingerprint]. + */ +internal val miniplayerOverrideNoContextFingerprint = legacyFingerprint( + name = "miniplayerOverrideNoContextFingerprint", + accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, + returnType = "Z", + opcodes = listOf(Opcode.IGET_BOOLEAN), // anchor to insert the instruction +) + +internal val miniplayerOverrideFingerprint = legacyFingerprint( + name = "miniplayerOverrideFingerprint", + returnType = "L", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + strings = listOf("appName") +) + +internal val miniplayerResponseModelSizeCheckFingerprint = legacyFingerprint( + name = "miniplayerResponseModelSizeCheckFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "L", + parameters = listOf("Ljava/lang/Object;", "Ljava/lang/Object;"), + opcodes = listOf( + Opcode.RETURN_OBJECT, + Opcode.CHECK_CAST, + Opcode.CHECK_CAST, + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT, + Opcode.IF_NEZ, + ) +) + +// endregion + +// region modern miniplayer + +internal const val MINIPLAYER_MODERN_FEATURE_KEY = 45622882L +// In later targets this feature flag does nothing and is dead code. +internal const val MINIPLAYER_MODERN_FEATURE_LEGACY_KEY = 45630429L +internal const val MINIPLAYER_DOUBLE_TAP_FEATURE_KEY = 45628823L +internal const val MINIPLAYER_DRAG_DROP_FEATURE_KEY = 45628752L +internal const val MINIPLAYER_HORIZONTAL_DRAG_FEATURE_KEY = 45658112L +internal const val MINIPLAYER_ROUNDED_CORNERS_FEATURE_KEY = 45652224L +internal const val MINIPLAYER_INITIAL_SIZE_FEATURE_KEY = 45640023L +internal const val MINIPLAYER_DISABLED_FEATURE_KEY = 45657015L +internal const val MINIPLAYER_ANIMATED_EXPAND_FEATURE_KEY = 45644360L + +internal val miniplayerModernConstructorFingerprint = legacyFingerprint( + name = "miniplayerModernConstructorFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + parameters = listOf("L"), + literals = listOf(45623000L), +) + +internal val miniplayerModernViewParentFingerprint = legacyFingerprint( + name = "miniplayerModernViewParentFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "Ljava/lang/String;", + parameters = listOf(), + strings = listOf("player_overlay_modern_mini_player_controls") +) + /** * Matches using the class found in [miniplayerModernViewParentFingerprint]. */ @@ -38,43 +103,18 @@ internal val miniplayerModernAddViewListenerFingerprint = legacyFingerprint( */ internal val miniplayerModernCloseButtonFingerprint = legacyFingerprint( name = "miniplayerModernCloseButtonFingerprint", - returnType = "Landroid/widget/ImageView;", + returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = emptyList(), literals = listOf(modernMiniPlayerClose), ) -internal const val MINIPLAYER_MODERN_FEATURE_KEY = 45622882L - -// In later targets this feature flag does nothing and is dead code. -internal const val MINIPLAYER_MODERN_FEATURE_LEGACY_KEY = 45630429L -internal const val MINIPLAYER_DOUBLE_TAP_FEATURE_KEY = 45628823L -internal const val MINIPLAYER_DRAG_DROP_FEATURE_KEY = 45628752L -internal const val MINIPLAYER_HORIZONTAL_DRAG_FEATURE_KEY = 45658112L -internal const val MINIPLAYER_ROUNDED_CORNERS_FEATURE_KEY = 45652224L -internal const val MINIPLAYER_INITIAL_SIZE_FEATURE_KEY = 45640023L -internal const val MINIPLAYER_DISABLED_FEATURE_KEY = 45657015L - -internal val miniplayerModernConstructorFingerprint = legacyFingerprint( - name = "miniplayerModernConstructorFingerprint", - accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, - parameters = listOf("L"), - literals = listOf(45623000L), -) - -internal val miniplayerOnCloseHandlerFingerprint = legacyFingerprint( - name = "miniplayerOnCloseHandlerFingerprint", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - returnType = "Z", - literals = listOf(MINIPLAYER_DISABLED_FEATURE_KEY), -) - /** * Matches using the class found in [miniplayerModernViewParentFingerprint]. */ internal val miniplayerModernExpandButtonFingerprint = legacyFingerprint( name = "miniplayerModernExpandButtonFingerprint", - returnType = "Landroid/widget/ImageView;", + returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = emptyList(), literals = listOf(modernMiniPlayerExpand), @@ -96,7 +136,7 @@ internal val miniplayerModernExpandCloseDrawablesFingerprint = legacyFingerprint */ internal val miniplayerModernForwardButtonFingerprint = legacyFingerprint( name = "miniplayerModernForwardButtonFingerprint", - returnType = "Landroid/widget/ImageView;", + returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = emptyList(), literals = listOf(modernMiniPlayerForwardButton), @@ -107,7 +147,6 @@ internal val miniplayerModernForwardButtonFingerprint = legacyFingerprint( */ internal val miniplayerModernOverlayViewFingerprint = legacyFingerprint( name = "miniplayerModernOverlayViewFingerprint", - returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = emptyList(), literals = listOf(scrimOverlay), @@ -118,18 +157,21 @@ internal val miniplayerModernOverlayViewFingerprint = legacyFingerprint( */ internal val miniplayerModernRewindButtonFingerprint = legacyFingerprint( name = "miniplayerModernRewindButtonFingerprint", - returnType = "Landroid/widget/ImageView;", + returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = emptyList(), literals = listOf(modernMiniPlayerRewindButton), ) -internal val miniplayerModernViewParentFingerprint = legacyFingerprint( - name = "miniplayerModernViewParentFingerprint", +/** + * Matches using the class found in [miniplayerModernViewParentFingerprint]. + */ +internal val miniplayerModernActionButtonFingerprint = legacyFingerprint( + name = "miniplayerModernActionButtonFingerprint", + returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - returnType = "Ljava/lang/String;", - parameters = listOf(), - strings = listOf("player_overlay_modern_mini_player_controls") + parameters = emptyList(), + literals = listOf(modernMiniPlayerOverlayActionButton), ) internal val miniplayerMinimumSizeFingerprint = legacyFingerprint( @@ -138,33 +180,11 @@ internal val miniplayerMinimumSizeFingerprint = legacyFingerprint( literals = listOf(192L, 128L, miniplayerMaxSize), ) -internal val miniplayerOverrideFingerprint = legacyFingerprint( - name = "miniplayerOverrideFingerprint", - returnType = "L", +internal val miniplayerOnCloseHandlerFingerprint = legacyFingerprint( + name = "miniplayerOnCloseHandlerFingerprint", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - strings = listOf("appName") -) - -internal val miniplayerOverrideNoContextFingerprint = legacyFingerprint( - name = "miniplayerOverrideNoContextFingerprint", - accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, returnType = "Z", - opcodes = listOf(Opcode.IGET_BOOLEAN), // anchor to insert the instruction -) - -internal val miniplayerResponseModelSizeCheckFingerprint = legacyFingerprint( - name = "miniplayerResponseModelSizeCheckFingerprint", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - returnType = "L", - parameters = listOf("Ljava/lang/Object;", "Ljava/lang/Object;"), - opcodes = listOf( - Opcode.RETURN_OBJECT, - Opcode.CHECK_CAST, - Opcode.CHECK_CAST, - Opcode.INVOKE_STATIC, - Opcode.MOVE_RESULT, - Opcode.IF_NEZ, - ) + literals = listOf(MINIPLAYER_DISABLED_FEATURE_KEY), ) internal const val YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME = diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/MiniplayerPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/MiniplayerPatch.kt similarity index 77% rename from patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/MiniplayerPatch.kt rename to patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/MiniplayerPatch.kt index 0c9882607..bdd835f99 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/miniplayer/MiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/MiniplayerPatch.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.youtube.general.miniplayer +package app.revanced.patches.youtube.player.miniplayer.general import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions @@ -8,19 +8,22 @@ 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.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE -import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH +import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH import app.revanced.patches.youtube.utils.patch.PatchList.MINIPLAYER import app.revanced.patches.youtube.utils.playservice.is_19_15_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_17_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_23_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_26_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_29_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_36_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_43_or_greater +import app.revanced.patches.youtube.utils.playservice.is_20_03_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerClose import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerExpand import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerForwardButton +import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerOverlayActionButton import app.revanced.patches.youtube.utils.resourceid.modernMiniPlayerRewindButton import app.revanced.patches.youtube.utils.resourceid.scrimOverlay import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch @@ -36,6 +39,7 @@ import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.fingerprint.mutableClassOrThrow import app.revanced.util.getReference import app.revanced.util.getWalkerMethod +import app.revanced.util.indexOfFirstInstruction import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstLiteralInstructionOrThrow import com.android.tools.smali.dexlib2.AccessFlags @@ -51,7 +55,7 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter private const val EXTENSION_CLASS_DESCRIPTOR = - "$GENERAL_PATH/MiniplayerPatch;" + "$PLAYER_PATH/MiniplayerPatch;" // YT uses "Miniplayer" without a space between 'mini' and 'player: https://support.google.com/youtube/answer/9162927. @Suppress("unused", "SpellCheckingInspection") @@ -70,7 +74,8 @@ val miniplayerPatch = bytecodePatch( execute { var settingArray = arrayOf( - "PREFERENCE_SCREEN: GENERAL" + "PREFERENCE_SCREEN: PLAYER", + "SETTINGS: MINIPLAYER_COMPONENTS" ) fun Method.findReturnIndicesReversed() = @@ -116,25 +121,6 @@ val miniplayerPatch = bytecodePatch( ) } - fun MutableMethod.hookInflatedView( - literalValue: Long, - hookedClassType: String, - extensionMethodName: String, - ) { - val imageViewIndex = indexOfFirstInstructionOrThrow( - indexOfFirstLiteralInstructionOrThrow(literalValue) - ) { - opcode == Opcode.CHECK_CAST && - getReference()?.type == hookedClassType - } - - val register = getInstruction(imageViewIndex).registerA - addInstruction( - imageViewIndex + 1, - "invoke-static { v$register }, $extensionMethodName" - ) - } - // Modern mini player is only present and functional in 19.15+. // Resource is not present in older versions. Using it to determine, if patching an old version. val isPatchingOldVersion = !is_19_15_or_greater @@ -212,7 +198,7 @@ val miniplayerPatch = bytecodePatch( if (is_19_23_or_greater) { miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( MINIPLAYER_DRAG_DROP_FEATURE_KEY, - "$EXTENSION_CLASS_DESCRIPTOR->enableMiniplayerDragAndDrop(Z)Z" + "$EXTENSION_CLASS_DESCRIPTOR->getMiniplayerDragAndDrop(Z)Z" ) settingArray += "SETTINGS: MINIPLAYER_DRAG_AND_DROP" } @@ -230,7 +216,7 @@ val miniplayerPatch = bytecodePatch( miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( MINIPLAYER_DOUBLE_TAP_FEATURE_KEY, - "$EXTENSION_CLASS_DESCRIPTOR->enableMiniplayerDoubleTapAction(Z)Z" + "$EXTENSION_CLASS_DESCRIPTOR->getMiniplayerDoubleTapAction(Z)Z" ) if (!is_19_29_or_greater) { @@ -244,13 +230,11 @@ val miniplayerPatch = bytecodePatch( MINIPLAYER_INITIAL_SIZE_FEATURE_KEY, ) val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.LONG_TO_INT) - val register = getInstruction(targetIndex).registerA addInstructions( - targetIndex + 1, - """ - invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->setMiniplayerDefaultSize(I)I + targetIndex + 1, """ + invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getMiniplayerDefaultSize(I)I move-result v$register """, ) @@ -270,15 +254,17 @@ val miniplayerPatch = bytecodePatch( replaceInstruction(index, "const/16 v$register, 170") } + settingArray += "SETTINGS: MINIPLAYER_OVERLAY_BUTTONS_19_26" settingArray += "SETTINGS: MINIPLAYER_WIDTH_DIP" } else { + settingArray += "SETTINGS: MINIPLAYER_OVERLAY_BUTTONS_19_25" settingArray += "SETTINGS: MINIPLAYER_REWIND_FORWARD" } if (is_19_36_or_greater) { miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( MINIPLAYER_ROUNDED_CORNERS_FEATURE_KEY, - "$EXTENSION_CLASS_DESCRIPTOR->setRoundedCorners(Z)Z" + "$EXTENSION_CLASS_DESCRIPTOR->getRoundedCorners(Z)Z" ) settingArray += "SETTINGS: MINIPLAYER_ROUNDED_CORNERS" @@ -292,13 +278,23 @@ val miniplayerPatch = bytecodePatch( miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( MINIPLAYER_HORIZONTAL_DRAG_FEATURE_KEY, - "$EXTENSION_CLASS_DESCRIPTOR->setHorizontalDrag(Z)Z" + "$EXTENSION_CLASS_DESCRIPTOR->getHorizontalDrag(Z)Z" + ) + + miniplayerModernConstructorFingerprint.injectLiteralInstructionBooleanCall( + MINIPLAYER_ANIMATED_EXPAND_FEATURE_KEY, + "$EXTENSION_CLASS_DESCRIPTOR->getMaximizeAnimation(Z)Z" ) settingArray += "SETTINGS: MINIPLAYER_HORIZONTAL_DRAG" - settingArray += "SETTINGS: MINIPLAYER_TYPE_19_43" + } + + settingArray += if (is_20_03_or_greater) { + "SETTINGS: MINIPLAYER_TYPE_20_03" + } else if (is_19_43_or_greater) { + "SETTINGS: MINIPLAYER_TYPE_19_43" } else { - settingArray += "SETTINGS: MINIPLAYER_TYPE_19_16" + "SETTINGS: MINIPLAYER_TYPE_19_16" } // endregion @@ -339,6 +335,11 @@ val miniplayerPatch = bytecodePatch( modernMiniPlayerClose, "hideMiniplayerExpandClose" ), + Triple( + miniplayerModernActionButtonFingerprint, + modernMiniPlayerOverlayActionButton, + "hideMiniplayerActionButton" + ), Triple( miniplayerModernRewindButtonFingerprint, modernMiniPlayerRewindButton, @@ -355,11 +356,24 @@ val miniplayerPatch = bytecodePatch( "adjustMiniplayerOpacity" ) ).forEach { (fingerprint, literalValue, methodName) -> - fingerprint.methodOrThrow(miniplayerModernViewParentFingerprint).hookInflatedView( - literalValue, - "Landroid/widget/ImageView;", - "$EXTENSION_CLASS_DESCRIPTOR->$methodName(Landroid/widget/ImageView;)V" - ) + fingerprint.methodOrThrow(miniplayerModernViewParentFingerprint).apply { + val literalIndex = indexOfFirstLiteralInstructionOrThrow(literalValue) + val checkCastIndex = indexOfFirstInstruction(literalIndex) { + opcode == Opcode.CHECK_CAST && + getReference()?.type == "Landroid/widget/ImageView;" + } + val viewIndex = if (checkCastIndex >= 0) { + checkCastIndex + } else { + indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT_OBJECT) + } + val viewRegister = getInstruction(viewIndex).registerA + + addInstruction( + viewIndex + 1, + "invoke-static { v$viewRegister }, $EXTENSION_CLASS_DESCRIPTOR->$methodName(Landroid/view/View;)V" + ) + } } miniplayerModernAddViewListenerFingerprint.methodOrThrow( @@ -374,39 +388,42 @@ val miniplayerPatch = bytecodePatch( // Modern 2 uses the same overlay controls as the regular video player, // and the overlay views are added at runtime. // Add a hook to the overlay class, and pass the added views to extension. + // Problem is fixed in 19.21+ // // NOTE: Modern 2 uses the same video UI as the regular player except resized to smaller. // This patch code could be used to hide other player overlays that do not use Litho. - youTubePlayerOverlaysLayoutFingerprint.matchOrThrow().let { - it.method.apply { - it.classDef.methods.add( - ImmutableMethod( - YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME, - "addView", - listOf( - ImmutableMethodParameter("Landroid/view/View;", annotations, null), - ImmutableMethodParameter("I", annotations, null), - ImmutableMethodParameter( - "Landroid/view/ViewGroup\$LayoutParams;", - annotations, - null + if (!is_19_17_or_greater) { + youTubePlayerOverlaysLayoutFingerprint.matchOrThrow().let { + it.method.apply { + it.classDef.methods.add( + ImmutableMethod( + YOUTUBE_PLAYER_OVERLAYS_LAYOUT_CLASS_NAME, + "addView", + listOf( + ImmutableMethodParameter("Landroid/view/View;", annotations, null), + ImmutableMethodParameter("I", annotations, null), + ImmutableMethodParameter( + "Landroid/view/ViewGroup\$LayoutParams;", + annotations, + null + ), ), - ), - "V", - AccessFlags.PUBLIC.value, - annotations, - null, - MutableMethodImplementation(4), - ).toMutable().apply { - addInstructions( - """ - invoke-super { p0, p1, p2, p3 }, Landroid/view/ViewGroup;->addView(Landroid/view/View;ILandroid/view/ViewGroup${'$'}LayoutParams;)V - invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->playerOverlayGroupCreated(Landroid/view/View;)V - return-void - """, - ) - } - ) + "V", + AccessFlags.PUBLIC.value, + annotations, + null, + MutableMethodImplementation(4), + ).toMutable().apply { + addInstructions( + """ + invoke-super { p0, p1, p2, p3 }, Landroid/view/ViewGroup;->addView(Landroid/view/View;ILandroid/view/ViewGroup${'$'}LayoutParams;)V + invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->playerOverlayGroupCreated(Landroid/view/View;)V + return-void + """, + ) + } + ) + } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/startup/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/startup/Fingerprints.kt new file mode 100644 index 000000000..6a70fc522 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/startup/Fingerprints.kt @@ -0,0 +1,26 @@ +@file:Suppress("SpellCheckingInspection") + +package app.revanced.patches.youtube.player.miniplayer.startup + +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 + +/** + * Tested on YouTube 18.25.40 ~ 20.05.44 + * + * This fingerprint is not compatible with YouTube 18.19.36 or earlier + */ +internal val showMiniplayerCommandFingerprint = legacyFingerprint( + name = "showMiniplayerCommandFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("L", "Ljava/util/Map;"), + // Opcode pattern looks very weak, but it's not really. + opcodes = listOf( + Opcode.IF_NEZ, + Opcode.IF_EQZ, + ), + literals = listOf(121253L, 164817L), +) \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/startup/ResumingMiniplayerOnStartupPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/startup/ResumingMiniplayerOnStartupPatch.kt new file mode 100644 index 000000000..0add88cf9 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/startup/ResumingMiniplayerOnStartupPatch.kt @@ -0,0 +1,55 @@ +package app.revanced.patches.youtube.player.miniplayer.startup + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH +import app.revanced.patches.youtube.utils.patch.PatchList.DISABLE_RESUMING_MINIPLAYER_ON_STARTUP +import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference +import app.revanced.patches.youtube.utils.settings.settingsPatch +import app.revanced.util.fingerprint.matchOrThrow +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction + +private const val EXTENSION_CLASS_DESCRIPTOR = + "$PLAYER_PATH/MiniplayerPatch;" + +// YT uses "Miniplayer" without a space between 'mini' and 'player: https://support.google.com/youtube/answer/9162927. +@Suppress("unused", "SpellCheckingInspection") +val resumingMiniplayerOnStartupPatch = bytecodePatch( + DISABLE_RESUMING_MINIPLAYER_ON_STARTUP.title, + DISABLE_RESUMING_MINIPLAYER_ON_STARTUP.summary, +) { + compatibleWith(COMPATIBLE_PACKAGE) + + dependsOn(settingsPatch) + + execute { + + showMiniplayerCommandFingerprint.matchOrThrow().let { + it.method.apply { + val insertIndex = it.patternMatch!!.endIndex + val insertRegister = + getInstruction(insertIndex).registerA + + addInstructions( + insertIndex, """ + invoke-static {v$insertRegister}, $EXTENSION_CLASS_DESCRIPTOR->disableResumingStartupMiniPlayer(Z)Z + move-result v$insertRegister + """ + ) + } + } + + + addPreference( + arrayOf( + "PREFERENCE_SCREEN: PLAYER", + "SETTINGS: MINIPLAYER_COMPONENTS", + "SETTINGS: DISABLE_RESUMING_MINIPLAYER" + ), + DISABLE_RESUMING_MINIPLAYER_ON_STARTUP + ) + + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/Fingerprints.kt index d6ce2d8e3..22584d1ab 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/Fingerprints.kt @@ -1,12 +1,32 @@ package app.revanced.patches.youtube.player.seekbar +import app.revanced.patches.youtube.utils.resourceid.inlineTimeBarLiveSeekAbleRange import app.revanced.patches.youtube.utils.resourceid.reelTimeBarPlayedColor +import app.revanced.patches.youtube.utils.resourceid.ytStaticBrandRed +import app.revanced.patches.youtube.utils.resourceid.ytTextSecondary import app.revanced.patches.youtube.utils.resourceid.ytYoutubeMagenta import app.revanced.util.containsLiteralInstruction 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 +import kotlin.collections.listOf + +internal val shortsSeekbarColorFingerprint = legacyFingerprint( + name = "shortsSeekbarColorFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + literals = listOf(reelTimeBarPlayedColor), +) + +internal val controlsOverlayStyleFingerprint = legacyFingerprint( + name = "controlsOverlayStyleFingerprint", + opcodes = listOf(Opcode.CONST_HIGH16), + strings = listOf("YOUTUBE", "PREROLL", "POSTROLL"), + customFingerprint = { method, _ -> + method.definingClass.endsWith("/ControlsOverlayStyle;") + } +) internal const val PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG = 45617850L @@ -17,6 +37,28 @@ internal val playerSeekbarGradientConfigFingerprint = legacyFingerprint( literals = listOf(PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG), ) +internal val playerSeekbarHandleColorPrimaryFingerprint = legacyFingerprint( + name = "playerSeekbarHandleColorPrimaryFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + parameters = listOf("Landroid/content/Context;"), + literals = listOf(ytTextSecondary, ytStaticBrandRed), +) + +internal val playerSeekbarHandleColorSecondaryFingerprint = legacyFingerprint( + name = "playerSeekbarHandleColorSecondaryFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + parameters = listOf("Landroid/content/Context;"), + literals = listOf(inlineTimeBarLiveSeekAbleRange, ytStaticBrandRed), +) + +internal val watchHistoryMenuUseProgressDrawableFingerprint = legacyFingerprint( + name = "watchHistoryMenuUseProgressDrawableFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + parameters = listOf("L"), + literals = listOf(-1712394514), +) + internal val lithoLinearGradientFingerprint = legacyFingerprint( name = "lithoLinearGradientFingerprint", accessFlags = AccessFlags.STATIC.value, @@ -25,11 +67,13 @@ internal val lithoLinearGradientFingerprint = legacyFingerprint( ) /** - * YouTube 19.25 - 19.47 + * YouTube 19.49+ */ -internal val playerLinearGradientLegacyFingerprint = legacyFingerprint( - name = "playerLinearGradientLegacyFingerprint", - returnType = "V", +internal val playerLinearGradientFingerprint = legacyFingerprint( + name = "playerLinearGradientFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, + parameters = listOf("I", "I", "I", "I", "Landroid/content/Context;", "I"), + returnType = "Landroid/graphics/LinearGradient;", opcodes = listOf( Opcode.FILLED_NEW_ARRAY, Opcode.MOVE_RESULT_OBJECT @@ -38,13 +82,11 @@ internal val playerLinearGradientLegacyFingerprint = legacyFingerprint( ) /** - * YouTube 19.49+ + * YouTube 19.25 - 19.47 */ -internal val playerLinearGradientFingerprint = legacyFingerprint( - name = "playerLinearGradientFingerprint", - returnType = "Landroid/graphics/LinearGradient;", - accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, - parameters = listOf("I", "I", "I", "I", "Landroid/content/Context;", "I"), +internal val playerLinearGradientLegacyFingerprint = legacyFingerprint( + name = "playerLinearGradientLegacyFingerprint", + returnType = "V", opcodes = listOf( Opcode.FILLED_NEW_ARRAY, Opcode.MOVE_RESULT_OBJECT @@ -67,15 +109,6 @@ internal val launchScreenLayoutTypeFingerprint = legacyFingerprint( } ) -internal val controlsOverlayStyleFingerprint = legacyFingerprint( - name = "controlsOverlayStyleFingerprint", - opcodes = listOf(Opcode.CONST_HIGH16), - strings = listOf("YOUTUBE", "PREROLL", "POSTROLL"), - customFingerprint = { method, _ -> - method.definingClass.endsWith("/ControlsOverlayStyle;") - } -) - internal val seekbarTappingFingerprint = legacyFingerprint( name = "seekbarTappingFingerprint", returnType = "Z", @@ -108,13 +141,6 @@ internal val seekbarThumbnailsQualityFingerprint = legacyFingerprint( literals = listOf(45399684L), ) -internal val shortsSeekbarColorFingerprint = legacyFingerprint( - name = "shortsSeekbarColorFingerprint", - returnType = "V", - accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, - literals = listOf(reelTimeBarPlayedColor), -) - internal val thumbnailPreviewConfigFingerprint = legacyFingerprint( name = "thumbnailPreviewConfigFingerprint", returnType = "Z", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch.kt index fe14c9d11..23bdab4dd 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch.kt @@ -24,6 +24,7 @@ import app.revanced.patches.youtube.utils.playerButtonsResourcesFingerprint import app.revanced.patches.youtube.utils.playerButtonsVisibilityFingerprint import app.revanced.patches.youtube.utils.playerSeekbarColorFingerprint import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_34_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_46_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_49_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch @@ -31,6 +32,7 @@ import app.revanced.patches.youtube.utils.resourceid.inlineTimeBarColorizedBarPl import app.revanced.patches.youtube.utils.resourceid.inlineTimeBarPlayedNotHighlightedColor import app.revanced.patches.youtube.utils.resourceid.reelTimeBarPlayedColor import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch +import app.revanced.patches.youtube.utils.resourceid.ytStaticBrandRed import app.revanced.patches.youtube.utils.seekbarFingerprint import app.revanced.patches.youtube.utils.seekbarOnDrawFingerprint import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference @@ -215,14 +217,18 @@ val seekbarComponentsPatch = bytecodePatch( // region patch for seekbar color - fun MutableMethod.addColorChangeInstructions(literal: Long) { - val insertIndex = indexOfFirstLiteralInstructionOrThrow(literal) + 2 - val insertRegister = getInstruction(insertIndex).registerA + fun MutableMethod.addColorChangeInstructions( + literal: Long, + methodName: String = "getVideoPlayerSeekbarColor" + ) { + val index = indexOfFirstLiteralInstructionOrThrow(literal) + 2 + val insertIndex = indexOfFirstInstructionOrThrow(index, Opcode.MOVE_RESULT) + val register = getInstruction(insertIndex).registerA addInstructions( insertIndex + 1, """ - invoke-static {v$insertRegister}, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->getVideoPlayerSeekbarColor(I)I - move-result v$insertRegister + invoke-static {v$register}, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->$methodName(I)I + move-result v$register """ ) } @@ -254,34 +260,70 @@ val seekbarComponentsPatch = bytecodePatch( addDrawableColorHook("$EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->getLithoColor(I)I") if (is_19_25_or_greater) { - lithoLinearGradientFingerprint.methodOrThrow().addInstruction( - 0, - "invoke-static/range { p4 .. p5 }, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->setLinearGradient([I[F)V" + playerSeekbarGradientConfigFingerprint.injectLiteralInstructionBooleanCall( + PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG, + "$EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->playerSeekbarGradientEnabled(Z)Z" ) - if (!is_19_49_or_greater) { - playerLinearGradientLegacyFingerprint.matchOrThrow().let { - it.method.apply { - val index = it.patternMatch!!.endIndex - val register = getInstruction(index).registerA - - addInstructions( - index + 1, - """ - invoke-static { v$register }, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->getLinearGradient([I)[I - move-result-object v$register - """ - ) + arrayOf( + playerSeekbarHandleColorPrimaryFingerprint, + playerSeekbarHandleColorSecondaryFingerprint + ).forEach { + it.methodOrThrow().addColorChangeInstructions(ytStaticBrandRed, "getVideoPlayerSeekbarColorAccent") + } + // If hiding feed seekbar thumbnails, then turn off the cairo gradient + // of the watch history menu items as they use the same gradient as the + // player and there is no easy way to distinguish which to use a transparent color. + if (is_19_34_or_greater) { + watchHistoryMenuUseProgressDrawableFingerprint.methodOrThrow().apply { + val progressIndex = indexOfFirstInstructionOrThrow { + val reference = getReference() + reference?.definingClass == "Landroid/widget/ProgressBar;" && + reference.name == "setMax" } + val index = indexOfFirstInstructionOrThrow(progressIndex, Opcode.MOVE_RESULT) + val register = getInstruction(index).registerA + addInstructions( + index + 1, + """ + invoke-static { v$register }, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->showWatchHistoryProgressDrawable(Z)Z + move-result v$register + """ + ) } - } else { - // TODO: add 19.49 support - playerSeekbarGradientConfigFingerprint.injectLiteralInstructionBooleanCall( - PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG, - "$EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->playerSeekbarGradientEnabled(Z)Z" - ) } + lithoLinearGradientFingerprint.methodOrThrow().addInstructions( + 0, + """ + invoke-static/range { p4 .. p5 }, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->getLithoLinearGradient([I[F)[I + move-result-object p4 + """ + ) + + val playerFingerprint = + if (is_19_49_or_greater) { + playerLinearGradientFingerprint + } else { + playerLinearGradientLegacyFingerprint + } + + playerFingerprint.matchOrThrow().let { + it.method.apply { + val index = it.patternMatch!!.endIndex + val register = getInstruction(index).registerA + + addInstructions( + index + 1, + """ + invoke-static { v$register }, $EXTENSION_SEEKBAR_COLOR_CLASS_DESCRIPTOR->getPlayerLinearGradient([I)[I + move-result-object v$register + """ + ) + } + } + + settingArray += "SETTINGS: CUSTOM_SEEKBAR_COLOR_ACCENT" if (!restoreOldSplashAnimationIncluded) { // Don't use the lotte splash screen layout if using custom seekbar. @@ -327,7 +369,6 @@ val seekbarComponentsPatch = bytecodePatch( "setSplashAnimationDrawableTheme(Landroid/graphics/drawable/AnimatedVectorDrawable;)V" ) } - } val context = getContext() diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/Fingerprints.kt index 573c63f4a..00f4cc521 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/Fingerprints.kt @@ -71,6 +71,19 @@ internal val reelEnumStaticFingerprint = legacyFingerprint( returnType = "L" ) +internal const val SHORTS_HUD_FEATURE_FLAG = 45644023L + +/** + * Fix [HUD is hidden](https://github.com/ReVanced/revanced-patches/issues/4426) + */ +internal val shortsHUDFeatureFingerprint = legacyFingerprint( + name = "shortsHUDFeatureFingerprint", + returnType = "Z", + parameters = emptyList(), + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + literals = listOf(SHORTS_HUD_FEATURE_FLAG), +) + internal val reelFeedbackFingerprint = legacyFingerprint( name = "reelFeedbackFingerprint", returnType = "V", @@ -187,41 +200,3 @@ internal val shortsFullscreenFeatureFingerprint = legacyFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, literals = listOf(FULLSCREEN_FEATURE_FLAG), ) - -// Pre 19.25 -internal val shortsPlaybackIntentLegacyFingerprint = legacyFingerprint( - name = "shortsPlaybackIntentLegacyFingerprint", - returnType = "V", - parameters = listOf( - "L", - "Ljava/util/Map;", - "J", - "Ljava/lang/String;", - "Z", - "Ljava/util/Map;" - ), - strings = listOf( - // None of these strings are unique. - "com.google.android.apps.youtube.app.endpoint.flags", - "ReelWatchFragmentArgs", - "reels_fragment_descriptor" - ) -) - -internal val shortsPlaybackIntentFingerprint = legacyFingerprint( - name = "shortsPlaybackIntentFingerprint", - accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL, - returnType = "V", - parameters = listOf( - "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;", - "Ljava/util/Map;", - "J", - "Ljava/lang/String;" - ), - strings = listOf( - // None of these strings are unique. - "com.google.android.apps.youtube.app.endpoint.flags", - "ReelWatchFragmentArgs", - "reels_fragment_descriptor" - ) -) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt index 8d7b386d4..e327d3e75 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt @@ -65,6 +65,8 @@ import app.revanced.patches.youtube.video.information.videoInformationPatch import app.revanced.patches.youtube.video.playbackstart.PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR import app.revanced.patches.youtube.video.playbackstart.playbackStartDescriptorPatch import app.revanced.patches.youtube.video.playbackstart.playbackStartVideoIdReference +import app.revanced.patches.youtube.video.playbackstart.shortsPlaybackStartIntentFingerprint +import app.revanced.patches.youtube.video.playbackstart.shortsPlaybackStartIntentLegacyFingerprint import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId import app.revanced.patches.youtube.video.videoid.videoIdPatch import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT @@ -391,7 +393,10 @@ private val shortsRepeatPatch = bytecodePatch( description = "shortsRepeatPatch" ) { execute { - dependsOn(mainActivityResolvePatch) + dependsOn( + mainActivityResolvePatch, + versionCheckPatch, + ) injectOnCreateMethodCall( EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR, @@ -460,6 +465,13 @@ private val shortsRepeatPatch = bytecodePatch( } } } + + if (is_19_34_or_greater) { + shortsHUDFeatureFingerprint.injectLiteralInstructionBooleanCall( + SHORTS_HUD_FEATURE_FLAG, + "0x0" + ) + } } } @@ -894,7 +906,7 @@ val shortsComponentPatch = bytecodePatch( """ if (is_19_25_or_greater) { - shortsPlaybackIntentFingerprint.methodOrThrow().addInstructionsWithLabels( + shortsPlaybackStartIntentFingerprint.methodOrThrow().addInstructionsWithLabels( 0, """ move-object/from16 v0, p1 @@ -902,7 +914,7 @@ val shortsComponentPatch = bytecodePatch( """ ) } else { - shortsPlaybackIntentLegacyFingerprint.methodOrThrow().apply { + shortsPlaybackStartIntentLegacyFingerprint.methodOrThrow().apply { val index = indexOfFirstInstructionOrThrow { getReference()?.returnType == PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/Fingerprints.kt index 89aa98555..3a579a948 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/Fingerprints.kt @@ -48,17 +48,6 @@ fun indexOfSpannedCharSequenceInstruction(method: Method) = reference.returnType == "Ljava/lang/CharSequence;" } -internal val engagementPanelBuilderFingerprint = legacyFingerprint( - name = "engagementPanelBuilderFingerprint", - returnType = "L", - accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, - parameters = listOf("L", "L", "Z", "Z"), - strings = listOf( - "EngagementPanelController: cannot show EngagementPanel before EngagementPanelController.init() has been called.", - "[EngagementPanel] Cannot show EngagementPanel before EngagementPanelController.init() has been called." - ) -) - internal val layoutConstructorFingerprint = legacyFingerprint( name = "layoutConstructorFingerprint", returnType = "V", @@ -95,7 +84,6 @@ internal val playerButtonsResourcesFingerprint = legacyFingerprint( internal val playerButtonsVisibilityFingerprint = legacyFingerprint( name = "playerButtonsVisibilityFingerprint", returnType = "V", - accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, opcodes = listOf( Opcode.IGET_OBJECT, Opcode.IGET_OBJECT, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/engagement/EngagementPanelHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/engagement/EngagementPanelHookPatch.kt index e8607bd69..6af2931bc 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/engagement/EngagementPanelHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/engagement/EngagementPanelHookPatch.kt @@ -1,23 +1,30 @@ package app.revanced.patches.youtube.utils.engagement 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.patcher.util.proxy.mutableTypes.MutableMethod -import app.revanced.patches.youtube.utils.engagementPanelBuilderFingerprint +import app.revanced.patches.youtube.utils.extension.Constants.SHARED_PATH import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch -import app.revanced.util.fingerprint.matchOrThrow +import app.revanced.util.findMethodOrThrow import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.getReference -import app.revanced.util.indexOfFirstInstructionReversed +import app.revanced.util.indexOfFirstInstruction +import app.revanced.util.indexOfFirstInstructionOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.Method -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction -import com.android.tools.smali.dexlib2.iface.reference.MethodReference -import com.android.tools.smali.dexlib2.util.MethodUtil +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 -private lateinit var hideEngagementPanelMethod: MutableMethod -private var showEngagementPanelMethods = mutableListOf() +private const val EXTENSION_CLASS_DESCRIPTOR = + "$SHARED_PATH/EngagementPanel;" + +internal lateinit var engagementPanelBuilderMethod: MutableMethod +internal var engagementPanelFreeRegister = 0 +internal var engagementPanelIdIndex = 0 +internal var engagementPanelIdRegister = 0 val engagementPanelHookPatch = bytecodePatch( description = "engagementPanelHookPatch" @@ -25,43 +32,66 @@ val engagementPanelHookPatch = bytecodePatch( dependsOn(sharedResourceIdPatch) execute { - engagementPanelBuilderFingerprint.matchOrThrow().let { - it.classDef.methods.filter { method -> - method.indexOfEngagementPanelBuilderInstruction(it.method) >= 0 - }.forEach { method -> - showEngagementPanelMethods.add(method) + fun Method.setFreeIndex(startIndex: Int) { + val startRegister = engagementPanelIdRegister + var index = startIndex + var register = startRegister + + while (register == startRegister) { + index = indexOfFirstInstruction(index + 1, Opcode.IGET_OBJECT) + register = getInstruction(index).registerA } + + engagementPanelFreeRegister = register } - hideEngagementPanelMethod = - engagementPanelUpdateFingerprint.methodOrThrow(engagementPanelBuilderFingerprint) - } -} + val engagementPanelInfoClass = engagementPanelLayoutFingerprint + .methodOrThrow() + .parameters[0] + .toString() -private fun Method.indexOfEngagementPanelBuilderInstruction(targetMethod: MutableMethod) = - indexOfFirstInstructionReversed { - opcode == Opcode.INVOKE_DIRECT && - MethodUtil.methodSignaturesMatch( - targetMethod, - getReference()!! + val (engagementPanelIdReference, engagementPanelObjectReference) = + with(findMethodOrThrow(engagementPanelInfoClass)) { + val engagementPanelIdIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.IPUT_OBJECT && + getReference()?.type == "Ljava/lang/String;" + } + val engagementPanelObjectIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.IPUT_OBJECT && + getReference()?.type != "Ljava/lang/String;" + } + Pair( + getInstruction(engagementPanelIdIndex).reference.toString(), + getInstruction(engagementPanelObjectIndex).reference.toString(), ) - } + } -internal fun hookEngagementPanelState(classDescriptor: String) { - showEngagementPanelMethods.forEach { method -> - method.apply { - val index = indexOfEngagementPanelBuilderInstruction(this) - val register = getInstruction(index + 1).registerA + engagementPanelBuilderFingerprint.methodOrThrow().apply { + val insertIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.IGET_OBJECT && + getReference()?.toString() == engagementPanelObjectReference + } + val insertInstruction = getInstruction(insertIndex) + val classRegister = insertInstruction.registerB + engagementPanelIdRegister = insertInstruction.registerA - addInstruction( - index + 2, - "invoke-static {v$register}, $classDescriptor->showEngagementPanel(Ljava/lang/Object;)V" + setFreeIndex(insertIndex) + + addInstructions( + insertIndex, """ + iget-object v$engagementPanelIdRegister, v$classRegister, $engagementPanelIdReference + invoke-static {v$engagementPanelIdRegister}, $EXTENSION_CLASS_DESCRIPTOR->setId(Ljava/lang/String;)V + """ ) + engagementPanelIdIndex = insertIndex + 1 + engagementPanelBuilderMethod = this } - } - hideEngagementPanelMethod.addInstruction( - 0, - "invoke-static {}, $classDescriptor->hideEngagementPanel()V" - ) -} + engagementPanelUpdateFingerprint + .methodOrThrow(engagementPanelBuilderFingerprint) + .addInstruction( + 0, + "invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->hide()V" + ) + } +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/engagement/Fingerprints .kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/engagement/Fingerprints .kt deleted file mode 100644 index 855f89fd8..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/engagement/Fingerprints .kt +++ /dev/null @@ -1,22 +0,0 @@ -package app.revanced.patches.youtube.utils.engagement - -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.reference.MethodReference - -internal val engagementPanelUpdateFingerprint = legacyFingerprint( - name = "engagementPanelUpdateFingerprint", - returnType = "V", - accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, - parameters = listOf("L", "Z"), - customFingerprint = { method, _ -> - method.indexOfFirstInstruction { - opcode == Opcode.INVOKE_VIRTUAL && - getReference().toString() == "Ljava/util/ArrayDeque;->pop()Ljava/lang/Object;" - } >= 0 - } -) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/engagement/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/engagement/Fingerprints.kt new file mode 100644 index 000000000..2e6866839 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/engagement/Fingerprints.kt @@ -0,0 +1,46 @@ +package app.revanced.patches.youtube.utils.engagement + +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.reference.MethodReference + +internal val engagementPanelBuilderFingerprint = legacyFingerprint( + name = "engagementPanelBuilderFingerprint", + returnType = "L", + accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, + parameters = listOf("L", "L", "Z", "Z"), + strings = listOf( + "EngagementPanelController: cannot show EngagementPanel before EngagementPanelController.init() has been called.", + "[EngagementPanel] Cannot show EngagementPanel before EngagementPanelController.init() has been called." + ) +) + +internal val engagementPanelLayoutFingerprint = legacyFingerprint( + name = "engagementPanelLayoutFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("L", "L", "I"), + customFingerprint = { method, _ -> + method.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference().toString() == "Landroid/widget/FrameLayout;->indexOfChild(Landroid/view/View;)I" + } >= 0 + } +) + +internal val engagementPanelUpdateFingerprint = legacyFingerprint( + name = "engagementPanelUpdateFingerprint", + returnType = "V", + accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, + parameters = listOf("L", "Z"), + customFingerprint = { method, _ -> + method.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference().toString() == "Ljava/util/ArrayDeque;->pop()Ljava/lang/Object;" + } >= 0 + } +) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/cairo/CairoFragmentPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/cairo/CairoFragmentPatch.kt index 7dcb6f3bf..ae2d3c8cf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/cairo/CairoFragmentPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/cairo/CairoFragmentPatch.kt @@ -5,26 +5,32 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch import app.revanced.patches.youtube.utils.playservice.is_19_34_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch -import app.revanced.patches.youtube.utils.resourceid.settingsFragmentCairo import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch +import app.revanced.util.Utils.printWarn import app.revanced.util.fingerprint.methodCall import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.getReference -import app.revanced.util.getWalkerMethod import app.revanced.util.indexOfFirstInstructionOrThrow -import app.revanced.util.indexOfFirstLiteralInstructionOrThrow import app.revanced.util.insertNode +import app.revanced.util.returnEarly import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.MethodReference import org.w3c.dom.Element -private val cairoFragmentResourcePatch = resourcePatch( - description = "cairoFragmentResourcePatch" +private var cairoFragmentDisabled = false + +private val cairoFragmentBytecodePatch = bytecodePatch( + description = "cairoFragmentBytecodePatch" ) { - dependsOn(versionCheckPatch) + dependsOn( + sharedExtensionPatch, + sharedResourceIdPatch, + versionCheckPatch + ) execute { /** @@ -34,6 +40,71 @@ private val cairoFragmentResourcePatch = resourcePatch( return@execute } + // Instead of disabling all Cairo fragment configs, + // Just disable 'Load Cairo fragment xml' and 'Set style to Cairo preference'. + fun MutableMethod.disableCairoFragmentConfig() { + val cairoFragmentConfigMethodCall = cairoFragmentConfigFingerprint + .methodCall() + val insertIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.toString() == cairoFragmentConfigMethodCall + } + 2 + val insertRegister = getInstruction(insertIndex - 1).registerA + + addInstruction(insertIndex, "const/4 v$insertRegister, 0x0") + } + + try { + arrayOf( + // Load cairo fragment xml. + settingsFragmentSyntheticFingerprint + .methodOrThrow(), + // Set style to cairo preference. + settingsFragmentStylePrimaryFingerprint + .methodOrThrow(), + settingsFragmentStyleSecondaryFingerprint + .methodOrThrow(settingsFragmentStylePrimaryFingerprint), + ).forEach { method -> + method.disableCairoFragmentConfig() + } + cairoFragmentDisabled = true + } catch (_: Exception) { + cairoFragmentConfigFingerprint + .methodOrThrow() + .returnEarly() + + printWarn("Failed to restore 'Playback' settings. 'Autoplay next video' setting may not appear in the YouTube settings.") + } + } +} + +/** + * What [cairoFragmentPatch] does: + * 1. Disable Cairo fragment settings. + * 2. Fix - When spoofing the app version to 19.20 or earlier, the app crashes or the Notifications tab is inaccessible. + * 3. Fix - Preference 'Playback' is hidden. + * 4. Some settings that were in Preference 'General' are moved to Preference 'Playback'. + */ +val cairoFragmentPatch = resourcePatch( + description = "cairoFragmentPatch" +) { + dependsOn( + cairoFragmentBytecodePatch, + versionCheckPatch, + ) + + execute { + /** + * Cairo fragment have been widely rolled out in YouTube 19.34+. + */ + if (!is_19_34_or_greater) { + return@execute + } + + if (!cairoFragmentDisabled) { + return@execute + } + /** * The Preference key for 'Playback' is '@string/playback_key'. * Copy the node to add the Preference 'Playback' to the legacy settings fragment. @@ -53,74 +124,5 @@ private val cairoFragmentResourcePatch = resourcePatch( } } } - - } -} - -/** - * What [cairoFragmentPatch] does: - * 1. Disable Cairo fragment settings. - * 2. Fix - When spoofing the app version to 19.20 or earlier, the app crashes or the Notifications tab is inaccessible. - * 3. Fix - Preference 'Playback' is hidden. - * 4. Some settings that were in Preference 'General' are moved to Preference 'Playback'. - */ -val cairoFragmentPatch = bytecodePatch( - description = "cairoFragmentPatch" -) { - dependsOn( - cairoFragmentResourcePatch, - sharedResourceIdPatch, - versionCheckPatch - ) - - execute { - /** - * Cairo fragment have been widely rolled out in YouTube 19.34+. - */ - if (!is_19_34_or_greater) { - return@execute - } - - // Instead of disabling all Cairo fragment configs, - // Just disable 'Load Cairo fragment xml' and 'Set style to Cairo preference'. - val cairoFragmentConfigMethodCall = cairoFragmentConfigFingerprint - .methodCall() - - fun MutableMethod.disableCairoFragmentConfig() { - val insertIndex = indexOfFirstInstructionOrThrow { - opcode == Opcode.INVOKE_VIRTUAL && - getReference()?.toString() == cairoFragmentConfigMethodCall - } + 2 - val insertRegister = getInstruction(insertIndex - 1).registerA - - addInstruction(insertIndex, "const/4 v$insertRegister, 0x0") - } - - settingsFragmentSyntheticFingerprint.methodOrThrow().apply { - val literalIndex = indexOfFirstLiteralInstructionOrThrow(settingsFragmentCairo) - val fragmentStylePrimaryIndex = indexOfFirstInstructionOrThrow(literalIndex) { - val reference = getReference() - opcode == Opcode.INVOKE_VIRTUAL_RANGE && - reference?.returnType == "V" && - reference.parameterTypes.firstOrNull() == "Ljava/lang/String;" - } - val fragmentStyleSecondaryIndex = indexOfFirstInstructionOrThrow(literalIndex) { - val reference = getReference() - opcode == Opcode.INVOKE_VIRTUAL && - reference?.returnType == "V" && - reference.parameterTypes == listOf("Ljava/util/List;", "Landroidx/preference/Preference;") - } - - arrayOf( - // Load cairo fragment xml. - this, - // Set style to cairo preference. - getWalkerMethod(fragmentStylePrimaryIndex), - getWalkerMethod(fragmentStyleSecondaryIndex) - ).forEach { method -> - method.disableCairoFragmentConfig() - } - } - } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/cairo/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/cairo/Fingerprints.kt index 1e592cbc8..d96edd681 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/cairo/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/cairo/Fingerprints.kt @@ -29,3 +29,26 @@ internal val settingsFragmentSyntheticFingerprint = legacyFingerprint( opcodes = listOf(Opcode.INVOKE_VIRTUAL_RANGE), literals = listOf(settingsFragment, settingsFragmentCairo), ) + +internal val settingsFragmentStylePrimaryFingerprint = legacyFingerprint( + name = "settingsFragmentStylePrimaryFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf( + "Ljava/lang/String;", + "Ljava/util/List;", + "Landroidx/preference/Preference;", + "Lj${'$'}/util/Optional;", + "Lj${'$'}/util/Optional;", + ), +) + +internal val settingsFragmentStyleSecondaryFingerprint = legacyFingerprint( + name = "settingsFragmentStyleSecondaryFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf( + "Ljava/util/List;", + "Landroidx/preference/Preference;", + ), +) \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/Fingerprints.kt similarity index 65% rename from patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt rename to patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/Fingerprints.kt index 79e334099..c5ff68e76 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/spoof/streamingdata/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/Fingerprints.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.shared.spoof.streamingdata +package app.revanced.patches.youtube.utils.fix.streamingdata import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.getReference @@ -15,37 +15,6 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference const val STREAMING_DATA_INTERFACE = "Lcom/google/protos/youtube/api/innertube/StreamingDataOuterClass${'$'}StreamingData;" -internal val buildInitPlaybackRequestFingerprint = legacyFingerprint( - name = "buildInitPlaybackRequestFingerprint", - returnType = "Lorg/chromium/net/UrlRequest\$Builder;", - opcodes = listOf( - Opcode.MOVE_RESULT_OBJECT, - Opcode.IGET_OBJECT, // Moves the request URI string to a register to build the request with. - ), - strings = listOf( - "Content-Type", - "Range", - ), -) - -internal val buildPlayerRequestURIFingerprint = legacyFingerprint( - name = "buildPlayerRequestURIFingerprint", - returnType = "Ljava/lang/String;", - strings = listOf( - "key", - "asig", - ), - customFingerprint = { method, _ -> - indexOfToStringInstruction(method) >= 0 - }, -) - -internal fun indexOfToStringInstruction(method: Method) = - method.indexOfFirstInstruction { - opcode == Opcode.INVOKE_VIRTUAL && - getReference().toString() == "Landroid/net/Uri;->toString()Ljava/lang/String;" - } - internal val buildMediaDataSourceFingerprint = legacyFingerprint( name = "buildMediaDataSourceFingerprint", accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, @@ -64,38 +33,6 @@ internal val buildMediaDataSourceFingerprint = legacyFingerprint( ) ) -internal val buildRequestFingerprint = legacyFingerprint( - name = "buildRequestFingerprint", - customFingerprint = { method, _ -> - method.implementation != null && - indexOfRequestFinishedListenerInstruction(method) >= 0 && - !method.definingClass.startsWith("Lorg/") && - indexOfNewUrlRequestBuilderInstruction(method) >= 0 && - // Earlier targets - (indexOfEntrySetInstruction(method) >= 0 || - // Later targets - method.parameters[1].type == "Ljava/util/Map;") - } -) - -internal fun indexOfRequestFinishedListenerInstruction(method: Method) = - method.indexOfFirstInstruction { - opcode == Opcode.INVOKE_VIRTUAL && - getReference()?.name == "setRequestFinishedListener" - } - -internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) = - method.indexOfFirstInstruction { - opcode == Opcode.INVOKE_VIRTUAL && - getReference().toString() == "Lorg/chromium/net/CronetEngine;->newUrlRequestBuilder(Ljava/lang/String;Lorg/chromium/net/UrlRequest${'$'}Callback;Ljava/util/concurrent/Executor;)Lorg/chromium/net/UrlRequest${'$'}Builder;" - } - -internal fun indexOfEntrySetInstruction(method: Method) = - method.indexOfFirstInstruction { - opcode == Opcode.INVOKE_INTERFACE && - getReference().toString() == "Ljava/util/Map;->entrySet()Ljava/util/Set;" - } - internal val createStreamingDataFingerprint = legacyFingerprint( name = "createStreamingDataFingerprint", accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt index a2ba66890..8954d9933 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch.kt @@ -1,29 +1,334 @@ package app.revanced.patches.youtube.utils.fix.streamingdata -import app.revanced.patches.shared.spoof.streamingdata.baseSpoofStreamingDataPatch +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.extensions.InstructionExtensions.instructions +import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction +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.Companion.toMutable +import app.revanced.patches.shared.extension.Constants.PATCHES_PATH +import app.revanced.patches.shared.extension.Constants.SPOOF_PATH +import app.revanced.patches.shared.formatStreamModelConstructorFingerprint +import app.revanced.patches.shared.spoof.blockrequest.blockRequestPatch import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.compatibility.Constants.YOUTUBE_PACKAGE_NAME import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_STREAMING_DATA +import app.revanced.patches.youtube.utils.request.buildRequestPatch +import app.revanced.patches.youtube.utils.request.hookBuildRequest import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.settingsPatch +import app.revanced.util.findInstructionIndicesReversedOrThrow +import app.revanced.util.findMethodOrThrow +import app.revanced.util.fingerprint.definingClassOrThrow +import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall +import app.revanced.util.fingerprint.matchOrThrow +import app.revanced.util.fingerprint.methodOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +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.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.immutable.ImmutableMethod +import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter -val spoofStreamingDataPatch = baseSpoofStreamingDataPatch( - { - compatibleWith(COMPATIBLE_PACKAGE) +private const val EXTENSION_CLASS_DESCRIPTOR = + "$SPOOF_PATH/SpoofStreamingDataPatch;" - dependsOn( - baseSpoofUserAgentPatch(YOUTUBE_PACKAGE_NAME), - settingsPatch +val spoofStreamingDataPatch = bytecodePatch( + SPOOF_STREAMING_DATA.title, + SPOOF_STREAMING_DATA.summary +) { + compatibleWith(COMPATIBLE_PACKAGE) + + dependsOn( + settingsPatch, + baseSpoofUserAgentPatch(YOUTUBE_PACKAGE_NAME), + blockRequestPatch, + buildRequestPatch, + ) + + execute { + + // region Get replacement streams at player requests. + + hookBuildRequest("$EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V") + + // endregion + + // region Replace the streaming data. + + val approxDurationMsReference = formatStreamModelConstructorFingerprint.matchOrThrow().let { + with(it.method) { + getInstruction(it.patternMatch!!.startIndex).reference + } + } + + val streamingDataFormatsReference = with( + videoStreamingDataConstructorFingerprint.methodOrThrow( + videoStreamingDataToStringFingerprint + ) + ) { + val getFormatsFieldIndex = indexOfGetFormatsFieldInstruction(this) + val longMaxValueIndex = indexOfLongMaxValueInstruction(this, getFormatsFieldIndex) + val longMaxValueRegister = + getInstruction(longMaxValueIndex).registerA + val videoIdIndex = + indexOfFirstInstructionOrThrow(longMaxValueIndex) { + val reference = getReference() + opcode == Opcode.IGET_OBJECT && + reference?.type == "Ljava/lang/String;" && + reference.definingClass == definingClass + } + + val definingClassRegister = + getInstruction(videoIdIndex).registerB + val videoIdReference = + getInstruction(videoIdIndex).reference + + addInstructions( + longMaxValueIndex + 1, """ + # Get video id. + iget-object v$longMaxValueRegister, v$definingClassRegister, $videoIdReference + + # Override approxDurationMs. + invoke-static { v$longMaxValueRegister }, $EXTENSION_CLASS_DESCRIPTOR->getApproxDurationMs(Ljava/lang/String;)J + move-result-wide v$longMaxValueRegister + """ + ) + removeInstruction(longMaxValueIndex) + + getInstruction(getFormatsFieldIndex).reference + } + + createStreamingDataFingerprint.matchOrThrow(createStreamingDataParentFingerprint) + .let { result -> + result.method.apply { + val setStreamDataMethodName = "patch_setStreamingData" + val calcApproxDurationMsMethodName = "patch_calcApproxDurationMs" + val resultClassDef = result.classDef + val resultMethodType = resultClassDef.type + val setStreamingDataIndex = result.patternMatch!!.startIndex + val setStreamingDataField = + getInstruction(setStreamingDataIndex).getReference() + .toString() + + val playerProtoClass = + getInstruction(setStreamingDataIndex + 1).getReference()!!.definingClass + val protobufClass = + protobufClassParseByteBufferFingerprint.definingClassOrThrow() + + val getStreamingDataField = instructions.find { instruction -> + instruction.opcode == Opcode.IGET_OBJECT && + instruction.getReference()?.definingClass == playerProtoClass + }?.getReference() + ?: throw PatchException("Could not find getStreamingDataField") + + val videoDetailsIndex = result.patternMatch!!.endIndex + val videoDetailsRegister = + getInstruction(videoDetailsIndex).registerA + val videoDetailsClass = + getInstruction(videoDetailsIndex).getReference()!!.type + + addInstruction( + videoDetailsIndex + 1, + "invoke-direct { p0, v$videoDetailsRegister }, " + + "$resultMethodType->$setStreamDataMethodName($videoDetailsClass)V", + ) + + result.classDef.methods.add( + ImmutableMethod( + resultMethodType, + setStreamDataMethodName, + listOf( + ImmutableMethodParameter( + videoDetailsClass, + annotations, + "videoDetails" + ) + ), + "V", + AccessFlags.PRIVATE.value or AccessFlags.FINAL.value, + annotations, + null, + MutableMethodImplementation(9), + ).toMutable().apply { + addInstructionsWithLabels( + 0, + """ + invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->isSpoofingEnabled()Z + move-result v0 + if-eqz v0, :disabled + + # Get video id. + iget-object v2, p1, $videoDetailsClass->c:Ljava/lang/String; + if-eqz v2, :disabled + + # Get streaming data. + invoke-static { v2 }, $EXTENSION_CLASS_DESCRIPTOR->getStreamingData(Ljava/lang/String;)Ljava/nio/ByteBuffer; + move-result-object v3 + + if-eqz v3, :disabled + + # Parse streaming data. + sget-object v4, $playerProtoClass->a:$playerProtoClass + invoke-static { v4, v3 }, $protobufClass->parseFrom(${protobufClass}Ljava/nio/ByteBuffer;)$protobufClass + move-result-object v5 + check-cast v5, $playerProtoClass + + iget-object v6, v5, $getStreamingDataField + if-eqz v6, :disabled + + # Caculate approxDurationMs. + invoke-direct { p0, v2 }, $resultMethodType->$calcApproxDurationMsMethodName(Ljava/lang/String;)V + + # Set spoofed streaming data. + iput-object v6, p0, $setStreamingDataField + + :disabled + return-void + """, + ) + }, + ) + + resultClassDef.methods.add( + ImmutableMethod( + resultMethodType, + calcApproxDurationMsMethodName, + listOf( + ImmutableMethodParameter( + "Ljava/lang/String;", + annotations, + "videoId" + ) + ), + "V", + AccessFlags.PRIVATE.value or AccessFlags.FINAL.value, + annotations, + null, + MutableMethodImplementation(12), + ).toMutable().apply { + addInstructionsWithLabels( + 0, + """ + # Get video format list. + iget-object v0, p0, $setStreamingDataField + iget-object v0, v0, $streamingDataFormatsReference + invoke-interface {v0}, Ljava/util/List;->iterator()Ljava/util/Iterator; + move-result-object v0 + + # Initialize approxDurationMs field. + const-wide v1, 0x7fffffffffffffffL + + :loop + # Loop over all video formats to get the approxDurationMs + invoke-interface {v0}, Ljava/util/Iterator;->hasNext()Z + move-result v3 + const-wide/16 v4, 0x0 + + if-eqz v3, :exit + invoke-interface {v0}, Ljava/util/Iterator;->next()Ljava/lang/Object; + move-result-object v3 + check-cast v3, ${(approxDurationMsReference as FieldReference).definingClass} + + # Get approxDurationMs from format + iget-wide v6, v3, $approxDurationMsReference + + # Compare with zero to make sure approxDurationMs is not negative + cmp-long v8, v6, v4 + if-lez v8, :loop + + # Only use the min value of approxDurationMs + invoke-static {v1, v2, v6, v7}, Ljava/lang/Math;->min(JJ)J + move-result-wide v1 + goto :loop + + :exit + # Save approxDurationMs to integrations + invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->setApproxDurationMs(Ljava/lang/String;J)V + + return-void + """, + ) + }, + ) + } + } + + // endregion + + // region Remove /videoplayback request body to fix playback. + // This is needed when using iOS client as streaming data source. + + buildMediaDataSourceFingerprint.methodOrThrow().apply { + val targetIndex = instructions.lastIndex + + addInstructions( + targetIndex, + """ + # Field a: Stream uri. + # Field c: Http method. + # Field d: Post data. + move-object/from16 v0, p0 + iget-object v1, v0, $definingClass->a:Landroid/net/Uri; + iget v2, v0, $definingClass->c:I + iget-object v3, v0, $definingClass->d:[B + invoke-static { v1, v2, v3 }, $EXTENSION_CLASS_DESCRIPTOR->removeVideoPlaybackPostBody(Landroid/net/Uri;I[B)[B + move-result-object v1 + iput-object v1, v0, $definingClass->d:[B + """, + ) + } + + // endregion + + // region Append spoof info. + + nerdsStatsVideoFormatBuilderFingerprint.methodOrThrow().apply { + findInstructionIndicesReversedOrThrow(Opcode.RETURN_OBJECT).forEach { index -> + val register = getInstruction(index).registerA + + addInstructions( + index, """ + invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->appendSpoofedClient(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$register + """ + ) + } + } + + // endregion + + // region Fix iOS livestream current time. + + hlsCurrentTimeFingerprint.injectLiteralInstructionBooleanCall( + HLS_CURRENT_TIME_FEATURE_FLAG, + "$EXTENSION_CLASS_DESCRIPTOR->fixHLSCurrentTime(Z)Z" ) - }, - { + + // endregion + + findMethodOrThrow("$PATCHES_PATH/PatchStatus;") { + name == "SpoofStreamingData" + }.replaceInstruction( + 0, + "const/4 v0, 0x1" + ) + addPreference( arrayOf( "SETTINGS: SPOOF_STREAMING_DATA" ), SPOOF_STREAMING_DATA ) - } -) +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/patch/PatchList.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/patch/PatchList.kt index 41a56de93..d56f8fb1d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/patch/PatchList.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/patch/PatchList.kt @@ -81,6 +81,10 @@ internal enum class PatchList( "Disable haptic feedback", "Adds options to disable haptic feedback when swiping in the video player." ), + DISABLE_RESUMING_MINIPLAYER_ON_STARTUP( + "Disable resuming Miniplayer on startup", + "Adds an option to disable the Miniplayer 'Continue watching' from resuming on app startup." + ), DISABLE_RESUMING_SHORTS_ON_STARTUP( "Disable resuming Shorts on startup", "Adds an option to disable the Shorts player from resuming on app startup when Shorts were last being watched." @@ -113,6 +117,10 @@ internal enum class PatchList( "GmsCore support", "Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services." ), + HIDE_ACCESSIBILITY_CONTROLS_DIALOG( + "Hide accessibility controls dialog", + "Removes, at compile time, accessibility controls dialog 'Turn on accessibility controls for the video player?'." + ), HIDE_SHORTS_DIMMING( "Hide Shorts dimming", "Removes, at compile time, the dimming effect at the top and bottom of Shorts videos." diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt index d05423dea..0015e98d5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt @@ -55,6 +55,10 @@ var is_19_49_or_greater = false private set var is_20_02_or_greater = false private set +var is_20_03_or_greater = false + private set +var is_20_05_or_greater = false + private set val versionCheckPatch = resourcePatch( description = "versionCheckPatch", @@ -95,5 +99,7 @@ val versionCheckPatch = resourcePatch( is_19_46_or_greater = 244705000 <= playStoreServicesVersion is_19_49_or_greater = 245005000 <= playStoreServicesVersion is_20_02_or_greater = 250299000 <= playStoreServicesVersion + is_20_03_or_greater = 250405000 <= playStoreServicesVersion + is_20_05_or_greater = 250605000 <= playStoreServicesVersion } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/request/BuildRequestPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/request/BuildRequestPatch.kt new file mode 100644 index 000000000..6c1c600b9 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/request/BuildRequestPatch.kt @@ -0,0 +1,56 @@ +package app.revanced.patches.youtube.utils.request + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch +import app.revanced.util.fingerprint.methodOrThrow +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction + +private lateinit var buildRequestMethod: MutableMethod +private var urlRegister = 0 +private var mapRegister = 0 +private var offSet = 0 + +val buildRequestPatch = bytecodePatch( + description = "buildRequestPatch", +) { + dependsOn(sharedExtensionPatch) + + execute { + buildRequestFingerprint.methodOrThrow().apply { + buildRequestMethod = this + + val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this) + urlRegister = + getInstruction(newRequestBuilderIndex).registerD + + val entrySetIndex = indexOfEntrySetInstruction(this) + val isLegacyTarget = entrySetIndex < 0 + mapRegister = if (isLegacyTarget) + urlRegister + 1 + else + getInstruction(entrySetIndex).registerC + + if (isLegacyTarget) { + addInstructions( + newRequestBuilderIndex + 2, + "move-object/from16 v$mapRegister, p1" + ) + offSet++ + } + } + } +} + +internal fun hookBuildRequest(descriptor: String) { + buildRequestMethod.apply { + val insertIndex = indexOfNewUrlRequestBuilderInstruction(this) + 2 + offSet + + addInstructions( + insertIndex, + "invoke-static { v$urlRegister, v$mapRegister }, $descriptor" + ) + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/request/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/request/Fingerprints.kt new file mode 100644 index 000000000..5906be353 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/request/Fingerprints.kt @@ -0,0 +1,40 @@ +package app.revanced.patches.youtube.utils.request + +import app.revanced.util.fingerprint.legacyFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +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 buildRequestFingerprint = legacyFingerprint( + name = "buildRequestFingerprint", + customFingerprint = { method, _ -> + method.implementation != null && + indexOfRequestFinishedListenerInstruction(method) >= 0 && + !method.definingClass.startsWith("Lorg/") && + indexOfNewUrlRequestBuilderInstruction(method) >= 0 && + // Earlier targets + (indexOfEntrySetInstruction(method) >= 0 || + // Later targets + method.parameters[1].type == "Ljava/util/Map;") + } +) + +internal fun indexOfRequestFinishedListenerInstruction(method: Method) = + method.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setRequestFinishedListener" + } + +internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) = + method.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference().toString() == "Lorg/chromium/net/CronetEngine;->newUrlRequestBuilder(Ljava/lang/String;Lorg/chromium/net/UrlRequest${'$'}Callback;Ljava/util/concurrent/Executor;)Lorg/chromium/net/UrlRequest${'$'}Builder;" + } + +internal fun indexOfEntrySetInstruction(method: Method) = + method.indexOfFirstInstruction { + opcode == Opcode.INVOKE_INTERFACE && + getReference().toString() == "Ljava/util/Map;->entrySet()Ljava/util/Set;" + } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt index bf423d64a..cfb48d045 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch.kt @@ -61,8 +61,6 @@ var compactLink = -1L private set var compactListItem = -1L private set -var componentLongClickListener = -1L - private set var contentPill = -1L private set var controlsLayoutStub = -1L @@ -83,8 +81,6 @@ var easySeekEduContainer = -1L private set var editSettingsAction = -1L private set -var elementsImage = -1L - private set var endScreenElementLayoutCircle = -1L private set var endScreenElementLayoutIcon = -1L @@ -105,6 +101,8 @@ var floatyBarTopMargin = -1L private set var fullScreenButton = -1L private set +var fullScreenEngagementAdContainer = -1L + private set var fullScreenEngagementOverlay = -1L private set var fullScreenEngagementPanel = -1L @@ -115,6 +113,8 @@ var imageOnlyTab = -1L private set var inlineTimeBarColorizedBarPlayedColorDark = -1L private set +var inlineTimeBarLiveSeekAbleRange = -1L + private set var inlineTimeBarPlayedNotHighlightedColor = -1L private set var insetOverlayViewLayout = -1L @@ -135,14 +135,14 @@ var modernMiniPlayerExpand = -1L private set var modernMiniPlayerForwardButton = -1L private set +var modernMiniPlayerOverlayActionButton = -1L + private set var modernMiniPlayerRewindButton = -1L private set var musicAppDeeplinkButtonView = -1L private set var notificationBigPictureIconWidth = -1L private set -var offlineActionsVideoDeletedUndoSnackbarText = -1L - private set var playerCollapseButton = -1L private set var playerControlPreviousButtonTouchArea = -1L @@ -241,6 +241,10 @@ var ytOutlineXWhite = -1L private set var ytPremiumWordMarkHeader = -1L private set +var ytTextSecondary = -1L + private set +var ytStaticBrandRed = -1L + private set var ytWordMarkHeader = -1L private set var ytYoutubeMagenta = -1L @@ -344,10 +348,6 @@ internal val sharedResourceIdPatch = resourcePatch( LAYOUT, "compact_list_item" ] - componentLongClickListener = resourceMappings[ - ID, - "component_long_click_listener" - ] contentPill = resourceMappings[ LAYOUT, "content_pill" @@ -388,10 +388,6 @@ internal val sharedResourceIdPatch = resourcePatch( STRING, "edit_settings_action" ] - elementsImage = resourceMappings[ - ID, - "elements_image" - ] endScreenElementLayoutCircle = resourceMappings[ LAYOUT, "endscreen_element_layout_circle" @@ -432,6 +428,10 @@ internal val sharedResourceIdPatch = resourcePatch( ID, "fullscreen_button" ] + fullScreenEngagementAdContainer = resourceMappings[ + ID, + "fullscreen_engagement_ad_container" + ] fullScreenEngagementOverlay = resourceMappings[ LAYOUT, "fullscreen_engagement_overlay" @@ -452,6 +452,10 @@ internal val sharedResourceIdPatch = resourcePatch( COLOR, "inline_time_bar_colorized_bar_played_color_dark" ] + inlineTimeBarLiveSeekAbleRange = resourceMappings[ + COLOR, + "inline_time_bar_live_seekable_range" + ] inlineTimeBarPlayedNotHighlightedColor = resourceMappings[ COLOR, "inline_time_bar_played_not_highlighted_color" @@ -492,6 +496,10 @@ internal val sharedResourceIdPatch = resourcePatch( ID, "modern_miniplayer_forward_button" ] + modernMiniPlayerOverlayActionButton = resourceMappings[ + ID, + "modern_miniplayer_overlay_action_button" + ] modernMiniPlayerRewindButton = resourceMappings[ ID, "modern_miniplayer_rewind_button" @@ -504,10 +512,6 @@ internal val sharedResourceIdPatch = resourcePatch( DIMEN, "notification_big_picture_icon_width" ] - offlineActionsVideoDeletedUndoSnackbarText = resourceMappings[ - STRING, - "offline_actions_video_deleted_undo_snackbar_text" - ] playerCollapseButton = resourceMappings[ ID, "player_collapse_button" @@ -704,6 +708,14 @@ internal val sharedResourceIdPatch = resourcePatch( ATTR, "ytPremiumWordmarkHeader" ] + ytTextSecondary = resourceMappings[ + ATTR, + "ytTextSecondary", + ] + ytStaticBrandRed = resourceMappings[ + ATTR, + "ytStaticBrandRed", + ] ytWordMarkHeader = resourceMappings[ ATTR, "ytWordmarkHeader" diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt index 5a0854b44..032dbc5b6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt @@ -79,7 +79,7 @@ private val settingsBytecodePatch = bytecodePatch( } private const val DEFAULT_ELEMENT = "@string/about_key" -private const val DEFAULT_LABEL = "ReVanced Extended" +private const val DEFAULT_LABEL = "RVX" private val SETTINGS_ELEMENTS_MAP = mapOf( "Parent settings" to "@string/parent_tools_key", @@ -106,7 +106,7 @@ private val SETTINGS_ELEMENTS_MAP = mapOf( "About" to DEFAULT_ELEMENT ) -private lateinit var customName: String +private lateinit var settingsLabel: String val settingsPatch = resourcePatch( SETTINGS_FOR_YOUTUBE.title, @@ -131,9 +131,13 @@ val settingsPatch = resourcePatch( required = true, ) - val settingsLabel = stringOption( - key = "settingsLabel", + val rvxSettingsLabel = stringOption( + key = "rvxSettingsLabel", default = DEFAULT_LABEL, + values = mapOf( + "ReVanced Extended" to "ReVanced Extended", + "RVX" to DEFAULT_LABEL, + ), title = "RVX settings label", description = "The name of the RVX settings menu.", required = true, @@ -143,7 +147,7 @@ val settingsPatch = resourcePatch( /** * check patch options */ - customName = settingsLabel + settingsLabel = rvxSettingsLabel .valueOrThrow() val insertKey = insertPosition @@ -219,22 +223,21 @@ val settingsPatch = resourcePatch( /** * remove ReVanced Extended Settings divider */ - arrayOf("Theme.YouTube.Settings", "Theme.YouTube.Settings.Dark").forEach { themeName -> - document("res/values/styles.xml").use { document -> - with(document) { - val resourcesNode = getElementsByTagName("resources").item(0) as Element + document("res/values/styles.xml").use { document -> + val themeNames = arrayOf("Theme.YouTube.Settings", "Theme.YouTube.Settings.Dark") + with(document) { + val resourcesNode = documentElement + val childNodes = resourcesNode.childNodes - val newElement: Element = createElement("item") - newElement.setAttribute("name", "android:listDivider") + for (i in 0 until childNodes.length) { + val node = childNodes.item(i) as? Element ?: continue - for (i in 0 until resourcesNode.childNodes.length) { - val node = resourcesNode.childNodes.item(i) as? Element ?: continue - - if (node.getAttribute("name") == themeName) { - newElement.appendChild(createTextNode("@null")) - - node.appendChild(newElement) + if (node.getAttribute("name") in themeNames) { + val newElement = createElement("item").apply { + setAttribute("name", "android:listDivider") + appendChild(createTextNode("@null")) } + node.appendChild(newElement) } } } @@ -258,13 +261,13 @@ val settingsPatch = resourcePatch( * change RVX settings menu name * since it must be invoked after the Translations patch, it must be the last in the order. */ - if (customName != DEFAULT_LABEL) { + if (settingsLabel != DEFAULT_LABEL) { removeStringsElements( arrayOf("revanced_extended_settings_title") ) document("res/values/strings.xml").use { document -> mapOf( - "revanced_extended_settings_title" to customName + "revanced_extended_settings_title" to settingsLabel ).forEach { (k, v) -> val stringElement = document.createElement("string") diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/trackingurlhook/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/trackingurlhook/Fingerprints.kt deleted file mode 100644 index 1da109cbe..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/trackingurlhook/Fingerprints.kt +++ /dev/null @@ -1,21 +0,0 @@ -package app.revanced.patches.youtube.utils.trackingurlhook - -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 val trackingUrlModelFingerprint = legacyFingerprint( - name = "trackingUrlModelFingerprint", - returnType = "Landroid/net/Uri;", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = emptyList(), - opcodes = listOf( - Opcode.IGET_OBJECT, - Opcode.INVOKE_STATIC, - Opcode.MOVE_RESULT_OBJECT, - ), - customFingerprint = { method, _ -> - method.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/player/TrackingUrlModel;" - } -) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playbackstart/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playbackstart/Fingerprints.kt index c196c1f44..db140c52a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playbackstart/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playbackstart/Fingerprints.kt @@ -1,6 +1,8 @@ package app.revanced.patches.youtube.video.playbackstart import app.revanced.util.fingerprint.legacyFingerprint +import app.revanced.util.or +import com.android.tools.smali.dexlib2.AccessFlags const val PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR = "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;" @@ -16,4 +18,42 @@ internal val playbackStartFeatureFlagFingerprint = legacyFingerprint( literals = listOf(45380134L) ) +internal val shortsPlaybackStartIntentFingerprint = legacyFingerprint( + name = "shortsPlaybackStartIntentFingerprint", + accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL, + returnType = "V", + parameters = listOf( + "Lcom/google/android/libraries/youtube/player/model/PlaybackStartDescriptor;", + "Ljava/util/Map;", + "J", + "Ljava/lang/String;" + ), + strings = listOf( + // None of these strings are unique. + "com.google.android.apps.youtube.app.endpoint.flags", + "ReelWatchFragmentArgs", + "reels_fragment_descriptor" + ) +) + +// Pre 19.25 +internal val shortsPlaybackStartIntentLegacyFingerprint = legacyFingerprint( + name = "shortsPlaybackStartIntentLegacyFingerprint", + returnType = "V", + parameters = listOf( + "L", + "Ljava/util/Map;", + "J", + "Ljava/lang/String;", + "Z", + "Ljava/util/Map;" + ), + strings = listOf( + // None of these strings are unique. + "com.google.android.apps.youtube.app.endpoint.flags", + "ReelWatchFragmentArgs", + "reels_fragment_descriptor" + ) +) + diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playbackstart/PlaybackStartDescriptorPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playbackstart/PlaybackStartDescriptorPatch.kt index c2dbc260a..1b4e38b32 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playbackstart/PlaybackStartDescriptorPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playbackstart/PlaybackStartDescriptorPatch.kt @@ -2,7 +2,7 @@ package app.revanced.patches.youtube.video.playbackstart import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch +import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow @@ -15,7 +15,7 @@ internal lateinit var playbackStartVideoIdReference: Reference val playbackStartDescriptorPatch = bytecodePatch( description = "playbackStartDescriptorPatch" ) { - dependsOn(sharedResourceIdPatch) + dependsOn(sharedExtensionPatch) execute { // Find the obfuscated method name for PlaybackStartDescriptor.videoId() diff --git a/patches/src/main/kotlin/app/revanced/util/FilesCompat.kt b/patches/src/main/kotlin/app/revanced/util/FilesCompat.kt new file mode 100644 index 000000000..6bd4a93ed --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/util/FilesCompat.kt @@ -0,0 +1,53 @@ +package app.revanced.util + +import java.io.File +import java.io.InputStream +import java.nio.file.Files +import java.nio.file.StandardCopyOption + +/** + * Provides java.nio.file.Files compatible functions. + * + * This is needed for the ReVanced Manager running on Android 5.0-7.1 + * because Android 7.1 and below does not support the Java NIO2 Files API. + */ +internal object FilesCompat { + private val useCompat = try { + // Check for the existence of java.nio.file.Files class + Class.forName("java.nio.file.Files") + false + } catch (_ : ClassNotFoundException) { + // Under Android 8.0 + true + } + + /** + * Copy a file to a target file. + * + * If the `target` file already exists, replace an existing file. + */ + fun copy(source: File, target: File) { + if (useCompat) { + source.copyTo(target, overwrite = true) + } else { + Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING) + } + } + + /** + * Copies all bytes from an input stream to a file. + * + * If the `target` file already exists, replace an existing file. + */ + fun copy(source: InputStream, target: File) { + if (useCompat) { + source.use { inputStream -> + target.outputStream().use { outputStream -> + inputStream.copyTo(outputStream) + } + } + } else { + Files.copy(source, target.toPath(), StandardCopyOption.REPLACE_EXISTING) + } + } +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/util/ResourceUtils.kt b/patches/src/main/kotlin/app/revanced/util/ResourceUtils.kt index 9f172b065..04302c9b1 100644 --- a/patches/src/main/kotlin/app/revanced/util/ResourceUtils.kt +++ b/patches/src/main/kotlin/app/revanced/util/ResourceUtils.kt @@ -11,8 +11,6 @@ import org.w3c.dom.Node import org.w3c.dom.NodeList import java.io.File import java.io.InputStream -import java.nio.file.Files -import java.nio.file.StandardCopyOption private val classLoader = object {}.javaClass.classLoader @@ -55,7 +53,8 @@ fun Node.cloneNodes(parent: Node) { */ fun Node.doRecursively(action: (Node) -> Unit) { action(this) - for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action) + val childNodes = this.childNodes + for (i in 0 until childNodes.length) childNodes.item(i).doRecursively(action) } fun List.getResourceGroup(fileNames: Array) = map { directory -> @@ -114,14 +113,9 @@ fun ResourcePatchContext.copyAdaptiveIcon( if (oldIconResourceFile != newIconResourceFile) { mipmapDirectories.forEach { val mipmapDirectory = get("res").resolve(it) - Files.copy( - mipmapDirectory - .resolve("$oldIconResourceFile.png") - .toPath(), - mipmapDirectory - .resolve("$newIconResourceFile.png") - .toPath(), - StandardCopyOption.REPLACE_EXISTING + FilesCompat.copy( + mipmapDirectory.resolve("$oldIconResourceFile.png"), + mipmapDirectory.resolve("$newIconResourceFile.png") ) } } @@ -131,14 +125,9 @@ fun ResourcePatchContext.copyAdaptiveIcon( adaptiveIconMonoChromeFileName != getAdaptiveIconMonoChromeResourceFile() ) { val drawableDirectory = get("res").resolve("drawable") - Files.copy( - drawableDirectory - .resolve("$adaptiveIconMonoChromeFileName.xml") - .toPath(), - drawableDirectory - .resolve("${getAdaptiveIconMonoChromeResourceFile()}.xml") - .toPath(), - StandardCopyOption.REPLACE_EXISTING + FilesCompat.copy( + drawableDirectory.resolve("$adaptiveIconMonoChromeFileName.xml"), + drawableDirectory.resolve("${getAdaptiveIconMonoChromeResourceFile()}.xml") ) } } @@ -164,10 +153,11 @@ fun ResourcePatchContext.addEntryValues( ) { document(path).use { document -> with(document) { - val resourcesNode = getElementsByTagName("resources").item(0) as Element + val resourcesNode = documentElement + val childNodes = resourcesNode.childNodes val newElement: Element = createElement("item") - 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 if (node.getAttribute("name") == attributeName) { newElement.appendChild(createTextNode(attributeValue)) @@ -177,6 +167,7 @@ fun ResourcePatchContext.addEntryValues( } else { node.insertBefore(newElement, node.firstChild) } + break } } } @@ -198,9 +189,9 @@ fun ResourcePatchContext.copyFile( 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) ) } } @@ -297,26 +288,22 @@ fun Node.insertNode(tagName: String, targetNode: Node, block: Element.() -> Unit fun ResourcePatchContext.copyResources( sourceResourceDirectory: String, vararg resources: ResourceGroup, - createDirectoryIfNotExist: Boolean = false, ) { val resourceDirectory = get("res") for (resourceGroup in resources) { resourceGroup.resources.forEach { resource -> val resourceDirectoryName = resourceGroup.resourceDirectoryName - if (createDirectoryIfNotExist) { - val targetDirectory = resourceDirectory.resolve(resourceDirectoryName) - if (!targetDirectory.isDirectory) Files.createDirectories(targetDirectory.toPath()) - } + val targetDirectory = resourceDirectory.resolve(resourceDirectoryName) + if (!targetDirectory.isDirectory) targetDirectory.mkdirs() val resourceFile = "$resourceDirectoryName/$resource" inputStreamFromBundledResource( sourceResourceDirectory, resourceFile )?.let { inputStream -> - Files.copy( + FilesCompat.copy( inputStream, - resourceDirectory.resolve(resourceFile).toPath(), - StandardCopyOption.REPLACE_EXISTING, + resourceDirectory.resolve(resourceFile), ) } } @@ -355,11 +342,14 @@ fun ResourcePatchContext.copyXmlNode( resourceDirectory, targetResource )?.let { inputStream -> - // Copy nodes from the resources node to the real resource node - elementTag.copyXmlNode( - document(inputStream), - document("res/$targetResource"), - ).close() + val outputPath = "res/$targetResource" + if (get(outputPath).exists()) { + // Copy nodes from the resources node to the real resource node + elementTag.copyXmlNode( + document(inputStream), + document(outputPath), + ).close() + } } /** diff --git a/patches/src/main/resources/music/settings/host/values/arrays.xml b/patches/src/main/resources/music/settings/host/values/arrays.xml index c1adaccde..e56a20190 100644 --- a/patches/src/main/resources/music/settings/host/values/arrays.xml +++ b/patches/src/main/resources/music/settings/host/values/arrays.xml @@ -1,18 +1,30 @@ - @string/revanced_change_start_page_entry_chart + @string/revanced_change_start_page_entry_default + @string/revanced_change_start_page_entry_charts + @string/revanced_change_start_page_entry_episodes_for_later @string/revanced_change_start_page_entry_explore - @string/revanced_change_start_page_entry_home + @string/revanced_change_start_page_entry_history @string/revanced_change_start_page_entry_library - @string/revanced_change_start_page_entry_subscription + @string/revanced_change_start_page_entry_liked_music + @string/revanced_change_start_page_entry_podcasts + @string/revanced_change_start_page_entry_samples + @string/revanced_change_start_page_entry_search + @string/revanced_change_start_page_entry_subscriptions - FEmusic_charts - FEmusic_explore - FEmusic_home - FEmusic_library_landing - FEmusic_library_corpus_artists + ORIGINAL + CHARTS + EPISODES_FOR_LATER + EXPLORE + HISTORY + LIBRARY + LIKED_MUSIC + PODCASTS + SAMPLES + SEARCH + SUBSCRIPTIONS @string/revanced_disable_music_video_in_album_redirect_type_entry_redirect @@ -58,29 +70,31 @@ HANDLE_USERNAME - @string/revanced_spoof_app_version_target_entry_6_11_52 - @string/revanced_spoof_app_version_target_entry_4_27_53 + @string/revanced_spoof_app_version_target_entry_6_42_55 - 6.11.52 - 4.27.53 + 6.42.55 - @string/revanced_spoof_client_type_entry_ios_music_6_21 - @string/revanced_spoof_client_type_entry_android_music_5_29 @string/revanced_spoof_client_type_entry_android_music_4_27 + @string/revanced_spoof_client_type_entry_android_music_5_29 + @string/revanced_spoof_client_type_entry_ios_music_6_21 + @string/revanced_spoof_client_type_entry_ios_music_7_04 - IOS_MUSIC_6_21 - ANDROID_MUSIC_5_29 ANDROID_MUSIC_4_27 + ANDROID_MUSIC_5_29 + IOS_MUSIC_6_21 + IOS_MUSIC_7_04 - - @string/revanced_spoof_streaming_data_type_entry_android_vr - @string/revanced_spoof_streaming_data_type_entry_android_music + + @string/revanced_watch_history_type_entry_1 + @string/revanced_watch_history_type_entry_2 + @string/revanced_watch_history_type_entry_3 - - ANDROID_VR - ANDROID_MUSIC + + ORIGINAL + REPLACE + BLOCK diff --git a/patches/src/main/resources/music/settings/host/values/strings.xml b/patches/src/main/resources/music/settings/host/values/strings.xml index 4b7216254..cf9ffdaab 100644 --- a/patches/src/main/resources/music/settings/host/values/strings.xml +++ b/patches/src/main/resources/music/settings/host/values/strings.xml @@ -1,7 +1,7 @@ - ReVanced Extended + RVX Reset to default values. @@ -26,6 +26,8 @@ Action bar + Change action bar position + Moves the action bar below the play button. Hide Like and Dislike buttons Hides the Like and Dislike buttons. It does not work in the old player layout. Hide Comments button @@ -34,10 +36,13 @@ Hides the Save button. Hide Download button Hides the Download button. - Hide Share button - Hides the Share button. Hide Radio button Hides the Radio button. + Hide Share button + Hides the Share button. + Hide Song / Video button + "Hides the Song / Video button. +(This button is only available to some users)" Hide action button labels Hides the labels of the action buttons. Override Download action button @@ -58,10 +63,8 @@ Please download %2$s from the website." Ads Hide fullscreen ads - "Hides fullscreen ads. - -Limitations: -• Sometimes you may see a blank black screen instead of the home feed." + Hides fullscreen ads. + Fullscreen ads are closed. Hide general ads Hides general ads. Hide media ads @@ -70,6 +73,7 @@ Limitations: Hides the paid promotion label. Hide premium promotion popups Hides the premium promotion popups. + Premium promotion popups are closed. Hide premium renewal banner Hides the premium renewal banner. Hide promotion alert banner @@ -104,6 +108,7 @@ Limitations: Hide Go to episode menu Hide Go to podcast menu Hide Help & feedback menu + Hide Not interested menu Hide Pin to Speed dial menu Hide Play next menu Hide Quality menu @@ -138,11 +143,17 @@ Limitations: Change start page Select which page the app opens in. - Charts + Default + Charts + Episodes for Later Explore - Home + History Library - Subscriptions + Liked Music + Podcasts + Samples + Search + Subscriptions Disable dislike redirection Disables redirection to the next track when clicking the Dislike button. Disable forced auto captions @@ -191,8 +202,7 @@ This does not bypass the age restriction. It just accepts it automatically." Spoof app version target Select the spoof app version target. - 4.27.53 - Disable Radio mode in Canadian regions - 6.11.52 - Disable real-time lyrics + 6.42.55 - Disable real-time lyrics 7.16.53 - Restore old action bar @@ -223,22 +233,35 @@ This does not bypass the age restriction. It just accepts it automatically." Player - Add miniplayer next button - Adds a next track button to the miniplayer. - Add miniplayer previous button - Adds a previous track button to the miniplayer. - Change miniplayer color - Changes the miniplayer color to match the fullscreen player. - Change player background color - Changes the player background color to black. - Disable miniplayer gesture - Disables swipe to change tracks in the miniplayer. + Add miniplayer next button + Adds a next track button to the miniplayer. + Add miniplayer previous button + Adds a previous track button to the miniplayer. + Change miniplayer color + Changes the miniplayer color to match the fullscreen player. + Change player background color + Changes the player background color to custom color. + Player background primary color + "Type the hex code of the player background primary color. + +Use dark colors if possible, as the app does not support light themes." + Player background secondary color + "Type the hex code of the player background secondary color. + +Use dark colors if possible, as the app does not support light themes." + Invalid player background color. + Change seekbar position + Moves the seekbar below the play button. + Disable miniplayer gesture + Disables swipe to change tracks in the miniplayer. Disable player gesture Disables swipe to change tracks in the player. - Enable forced miniplayer - Enables forced miniplayer when switching to a new track. - Enable swipe to dismiss miniplayer - Enables swipe down to dismiss miniplayer. + Enable forced miniplayer + Enables forced miniplayer when switching to a new track. + Enable swipe to dismiss miniplayer + Enables swipe down to dismiss miniplayer. + Enable thick seekbar + Enables the thick seekbar. Enable Zen mode Enables a light grey color for the player background to reduce eye strain. Enable Zen mode in podcasts @@ -251,8 +274,8 @@ This does not bypass the age restriction. It just accepts it automatically."Hides the emoji and timestamp buttons when typing comments. Hide fullscreen Share button Hides the Share button in the fullscreen player. - Hide Song / Video toggle - Hides the Song / Video toggle in the player. + Hide Song / Video toggle + Hides the Song / Video toggle in the player. Remember repeat state Remembers the state of the repeat toggle. Remember shuffle state @@ -464,27 +487,20 @@ Info: Sanitize sharing links Sanitizes sharing links by removing tracking query parameters. Spoof client - "Spoof the client to prevent playback issues. - -• When used with 'Spoofing streaming data', playback issues may occur." + Spoof the client to prevent playback issues. Default client - "Defines a default client for spoofing. - -• When using the Android client, it is recommended to also use 'Spoof app version'." + Defines a default client for spoofing. Android Music 4.27.53 Android Music 5.29.53 iOS Music 6.21 - - Spoof streaming data - "Spoof the streaming data to prevent playback issues. - -• When used with 'Spoof client', playback issues may occur." - Default client - Defines a default client that fetches streaming data. - Show in Stats for nerds - Shows the client used to fetch streaming data in Stats for nerds. - Android VR - Android Music + iOS Music 7.04 + Watch history type + "• Original: Follows the watch history settings of Google account, but watch history may not work due to DNS or VPN. +• Replace domain: Follows the watch history settings of Google account. +• Block watch history: Watch history is blocked." + Original + Replace domain + Block watch history Open default app settings To open YouTube Music links in RVX Music, enable Open supported links and enable all the Supported web addresses. diff --git a/patches/src/main/resources/music/translations/bg-rBG/strings.xml b/patches/src/main/resources/music/translations/bg-rBG/strings.xml index f341cc8f3..a22a0ec04 100644 --- a/patches/src/main/resources/music/translations/bg-rBG/strings.xml +++ b/patches/src/main/resources/music/translations/bg-rBG/strings.xml @@ -1,7 +1,6 @@ - ReVanced Extended Рестартирайте, за да заредите оформлението нормално Опреснете и рестартирайте @@ -27,10 +26,10 @@ Скрива бутона \"Запазване\". Скриване на бутона за изтегляне Скрива бутона „Изтегляне“. - Скриване на бутона за споделяне - Скрива бутона „Споделяне“. Скрийте бутона \"Радио\" Скрива бутона \"Радио\". + Скриване на бутона за споделяне + Скрива бутона „Споделяне“. Скриване на етикетите на бутоните за действие Скрива. етикетите на бутоните за действие. Замяна на бутона за изтегляне @@ -48,7 +47,6 @@ Реклами Скриване на рекламите в режим на цял екран - "Скриване на рекламите в режим на цял екран." Скриване на общите реклами Скриване на общите реклами. Скриване на музикални реклами @@ -115,11 +113,8 @@ Главни Промяна на началната страница Изберете на коя страница да се отвори приложението. - Хит-парад Преглед - Начало Библиотека - Абонаменти Désactiver la redirection du bouton \"Je n\'aime pas\" Деактивира пренасочването към следващата песен, когато щракнете върху бутона „Не харесвам“. Изкл. принудителни автоматични субтититри @@ -167,8 +162,6 @@ • Ако деактивирате тази опция, след като я активирате, старият интерфейс може да остане, докато данните на приложението не бъдат изчистени." Подлъгване за версията на приложението Задайте желаната фалшива версия на приложението. - 4.27.53 - Деактивира радио режима в регионите на Канада - 6.11.52 -Изключва речта в реално време Лента за навигация Скриване на бутон за Начало @@ -181,10 +174,6 @@ Скриване на навигационен панел Плеър - A játékos színmegfelelésének engedélyezése - Цветът на плейъра на цял екран съответства с цвета на минимизирания. - Kapcsolja be az állandó összeomlott lejátszót - Tartsa a lejátszót mindig minimálisra, még akkor is, ha egy másik számot játszik le. Включване на zen режим Добавя сив оттенък към видеоплейъра, за да намали напрежението на очите. diff --git a/patches/src/main/resources/music/translations/bn/strings.xml b/patches/src/main/resources/music/translations/bn/strings.xml index b1eebe132..abc813210 100644 --- a/patches/src/main/resources/music/translations/bn/strings.xml +++ b/patches/src/main/resources/music/translations/bn/strings.xml @@ -45,10 +45,6 @@ নেভিগেশন বারের লেবেল লুকান নেভিগেশন বার থেকে লেবেল হাইড করুন। - প্লেয়ারের রং মিলানো সক্রিয় করুন - পূর্ণস্ক্রীন প্লেয়ারের রং মিনিমাইজ করা প্লেয়ারের রং এর সাথে মিলবে। - জোরপূর্বক মিনিমাইজড প্লেয়ার সক্রিয় করুন - প্লেয়ারকে স্থায়ীভাবে মিনিমাইজ করে রাখুন এমনকি যদি অন্য ট্র্যাক চালানো হয়। জেন মোড সক্রিয় করুন চোখের চাপ কমাতে ভিডিও প্লেয়ারে ধূসর আভা যোগ করুন। diff --git a/patches/src/main/resources/music/translations/cs-rCZ/strings.xml b/patches/src/main/resources/music/translations/cs-rCZ/strings.xml index 1a93c5201..b5bb3c2db 100644 --- a/patches/src/main/resources/music/translations/cs-rCZ/strings.xml +++ b/patches/src/main/resources/music/translations/cs-rCZ/strings.xml @@ -1,7 +1,6 @@ - ReVanced Extended Obnovit na výchozí hodnoty. @@ -42,10 +41,6 @@ Skrýt popisky navigačního panelu Skrýt popisky v navigačním panelu. - Povolit barevně shodný přehrávač - Odpovídá barvě mini přehrávače a režimu celé obrazovky. - Povolit vynucenou minimalizaci přehrávače - Zachovat přehrávač trvale minimalizovaný, i když je přehrávána jiná skladba. Povolit zen mod Přidá šedý odstín do přehrávače videa ke snížení namáhání očí. diff --git a/patches/src/main/resources/music/translations/el-rGR/strings.xml b/patches/src/main/resources/music/translations/el-rGR/strings.xml index 82d2aab64..c755a5efa 100644 --- a/patches/src/main/resources/music/translations/el-rGR/strings.xml +++ b/patches/src/main/resources/music/translations/el-rGR/strings.xml @@ -1,7 +1,7 @@ - ReVanced Extended + RVX Επαναφέρθηκε στην προεπιλεγμένη τιμή. Επανεκκίνηση ώστε να φορτωθεί σωστά η διάταξη @@ -20,6 +20,8 @@ Απόκρυψη των στοιχείων απορρήτου / όρων και προϋποθέσεων. Γραμμή ενεργειών + Αλλαγή θέσης γραμμής ενεργειών + Μετακίνηση της γραμμής ενεργειών κάτω από το κουμπί αναπαραγωγής. Απόκρυψη κουμπιών «Μου αρέσει» και «Δεν μου αρέσει» Απόκρυψη των κουμπιών «Μου αρέσει» και «Δεν μου αρέσει». Δεν λειτουργεί στην παλιά εμφάνιση της οθόνης αναπαραγωγής. Απόκρυψη κουμπιού σχολίων @@ -28,10 +30,13 @@ Απόκρυψη του κουμπιού αποθήκευσης σε λίστα αναπαραγωγής. Απόκρυψη κουμπιού «Λήψη» Απόκρυψη του κουμπιού λήψης. - Απόκρυψη κουμπιού «Κοινοποίηση» - Απόκρυψη του κουμπιού κοινοποίησης. Απόκρυψη κουμπιού «Ραδιόφωνο» Απόκρυψη του κουμπιού έναρξης ραδιοφώνου. + Απόκρυψη κουμπιού «Κοινοποίηση» + Απόκρυψη του κουμπιού κοινοποίησης. + Απόκρυψη κουμπιού «Ήχος / Βίντεο» + "Απόκρυψη του κουμπιού «Ήχος / Βίντεο». +(Αυτό το κουμπί είναι διαθέσιμο μόνο σε ορισμένους χρήστες)" Απόκρυψη ονομασιών κουμπιών ενέργειας Απόκρυψη των ονομασιών των κουμπιών ενεργειών. Μετατροπή κουμπιού ενέργειας «Λήψη» @@ -49,10 +54,8 @@ Διαφημίσεις Απόκρυψη διαφημίσεων πλήρους οθόνης - "Απόκρυψη των ενδιάμεσων διαφημίσεων πλήρους οθόνης. - -Περιορισμοί: -• Ενδέχεται μερικές φορές να φαίνεται μια κενή μαύρη οθόνη αντί για την αρχική ροή." + Απόκρυψη των διαφημίσεων πλήρους οθόνης. + Οι διαφημίσεις πλήρους οθόνης έκλεισαν. Απόκρυψη γενικών διαφημίσεων Απόκρυψη των γενικών διαφημίσεων. Απόκρυψη διαφημίσεων μουσικής @@ -61,6 +64,7 @@ Απόκρυψη της ετικέτας προώθησης επί πληρωμή. Απόκρυψη παραθύρων προώθησης Premium Απόκρυψη των αναδυόμενων παραθύρων προώθησης Premium. + Τα αναδυόμενα παράθυρα προώθησης Premium έκλεισαν. Απόκρυψη διαφημιστικού ανανέωσης Premium Απόκρυψη του διαφημιστικού ανανέωσης YT Premium. Απόκρυψη ετικετών προειδοποίησης προώθησης @@ -92,6 +96,7 @@ Απόκρυψη μενού «Μετάβαση στο επεισόδιο» Απόκρυψη μενού «Μετάβαση στο podcast» Απόκρυψη μενού «Βοήθεια & σχόλια» + Απόκρυψη μενού «Δεν ενδιαφέρομαι» Απόκρυψη μενού «Καρφίτσωμα στην ταχεία κλήση» Απόκρυψη μενού «Αναπαραγωγή μετά» Απόκρυψη μενού «Ποιότητα» @@ -123,11 +128,17 @@ Γενικά Αλλαγή αρχικής σελίδας Ορισμός της αρχικής σελίδας ανοίγματος της εφαρμογής. - Διαγράμματα + Προεπιλογή + Διαγράμματα + Επεισόδια για αργότερα Εξερεύνηση - Αρχική + Ιστορικό Βιβλιοθήκη - Εγγραφές + Μουσική που μου αρέσει + Podcasts + Δείγματα + Αναζήτηση + Εγγραφές Απενεργοποίηση ανακατεύθυνσης dislike Απενεργοποίηση της ανακατεύθυνσης στο επόμενο κομμάτι όταν πατάτε το κουμπί «Δεν μου αρέσει». Απενεργοποίηση αυτόματων υπότιτλων @@ -176,8 +187,7 @@ • Αν αργότερα γίνει απενεργοποίηση, η παλιά εμφάνιση μπορεί να παραμείνει μέχρι να διαγραφούν τα δεδομένα της εφαρμογής." Έκδοση της εφαρμογής που θα χρησιμοποιηθεί Επιλέξτε την έκδοση εφαρμογής που θα χρησιμοποιηθεί. - 4.27.53 - Απενεργοποίηση λειτουργίας ραδιοφώνου σε περιοχές του Καναδά - 6.11.52 - Απενεργοποίηση στίχων σε πραγματικό χρόνο + 6.42.55 - Απενεργοποίηση στίχων σε πραγματικό χρόνο 7.16.53 - Επαναφορά παλιάς γραμμής ενεργειών Γραμμή πλοήγησης @@ -202,22 +212,35 @@ Απόκρυψη ονομασιών των κουμπιών στη γραμμή πλοήγησης. Οθόνη αναπαραγωγής - Κουμπί επόμενου βίντεο στον miniplayer - Ενεργοποίηση του κουμπιού επόμενου βίντεο στην ελαχιστοποιημένη οθόνη αναπαραγωγής. - Κουμπί προηγούμενου βίντεο στον miniplayer - Ενεργοποίηση του κουμπιού προηγούμενου βίντεο στην ελαχιστοποιημένη οθόνη αναπαραγωγής. - Αλλαγή χρώματος ελαχιστοποιημένης οθόνης αναπαραγωγής - Αλλαγή του χρώματος της ελαχιστοποιημένης οθόνης αναπαραγωγής με αυτό της οθόνης αναπαραγωγής πλήρους οθόνης. - Αλλαγή χρώματος φόντου οθόνης αναπαραγωγής - Αλλαγή χρώματος της οθόνης αναπαραγωγής σε μαύρο. - Απενεργοποίηση χειρονομίας ελαχιστοποιημένης οθόνης αναπαραγωγής - Απενεργοποίηση της χειρονομίας σάρωσης για αλλαγή κομματιού στην ελαχιστοποιημένη οθόνη αναπαραγωγής. + Προσθήκη κουμπιού επόμενου κομματιού στην ελαχιστοποιημένη οθόνη αναπαραγωγής + Προσθήκη ενός κουμπιού επόμενου κομματιού στην ελαχιστοποιημένη οθόνη αναπαραγωγής. + Προσθήκη κουμπιού προηγούμενου κομματιού στην ελαχιστοποιημένη οθόνη αναπαραγωγής + Προσθήκη ενός κουμπιού προηγούμενου κομματιού στην ελαχιστοποιημένη οθόνη αναπαραγωγής. + Αλλαγή χρώματος ελαχιστοποιημένης οθόνης αναπαραγωγής + Αλλαγή του χρώματος της ελαχιστοποιημένης οθόνης αναπαραγωγής σε αυτό της οθόνης αναπαραγωγής πλήρους οθόνης. + Αλλαγή χρώματος φόντου οθόνης αναπαραγωγής + Αλλαγή χρώματος του φόντου της οθόνης αναπαραγωγής σε προσαρμοσμένο χρώμα. + Βασικό χρώμα φόντου οθόνης αναπαραγωγής + "Πληκτρολογήστε τον κωδικό hex του βασικού χρώματος φόντου της οθόνης αναπαραγωγής. + +Χρησιμοποιείστε σκούρα χρώματα αν είναι δυνατόν, καθώς η εφαρμογή δεν υποστηρίζει φωτεινά θέματα." + Δευτερεύον χρώμα φόντου οθόνης αναπαραγωγής + "Πληκτρολογήστε τον κωδικό hex του δευτερεύοντος χρώματος φόντου της οθόνης αναπαραγωγής. + +Χρησιμοποιείστε σκούρα χρώματα αν είναι δυνατόν, καθώς η εφαρμογή δεν υποστηρίζει φωτεινά θέματα." + Μη έγκυρο χρώμα φόντου οθόνης αναπαραγωγής. + Αλλαγή θέσης γραμμής προόδου + Μετακίνηση της γραμμής προόδου κάτω από το κουμπί αναπαραγωγής. + Απενεργοποίηση χειρονομίας ελαχιστοποιημένης οθόνης αναπαραγωγής + Απενεργοποίηση της χειρονομίας σάρωσης για αλλαγή κομματιού στην ελαχιστοποιημένη οθόνη αναπαραγωγής. Απενεργοποίηση χειρονομίας οθόνης αναπαραγωγής Απενεργοποίηση της χειρονομίας σάρωσης για αλλαγή κομματιού στην οθόνη αναπαραγωγής. - Εξαναγκαστική ελαχιστοποίηση οθόνης αναπαραγωγής - Να διατηρείται μόνιμα ελαχιστοποιημένο το πρόγραμμα αναπαραγωγής ακόμη και όταν αναπαράγεται άλλο κομμάτι. - Χειρονομία απόρριψης ελαχιστοποιημένης οθόνης αναπαραγωγής - Ενεργοποίηση χειρονομίας σάρωσης προς τα κάτω για απόρριψη της ελαχιστοποιημένης οθόνης αναπαραγωγής. + Εξαναγκαστική ελαχιστοποίηση οθόνης αναπαραγωγής + Μόνιμη διατήρηση της ελαχιστοποιημένης οθόνης αναπαραγωγής ακόμη και όταν γίνεται μετάβαση σε άλλο κομμάτι. + Χειρονομία απόρριψης ελαχιστοποιημένης οθόνης αναπαραγωγής + Ενεργοποίηση χειρονομίας σάρωσης προς τα κάτω για απόρριψη της ελαχιστοποιημένης οθόνης αναπαραγωγής. + Γραμμή προόδου παχιού στυλ + Ενεργοποίηση της γραμμής προόδου παχιού στυλ. Ενεργοποίηση λειτουργίας zen Ενεργοποίηση μιας γκρι απόχρωσης στο παρασκήνιο της οθόνης αναπαραγωγής για να μειωθεί η καταπόνηση των ματιών. Λειτουργία zen σε ηχητικές εκπομπές @@ -230,8 +253,8 @@ Απόκρυψη των κουμπιών χρονοσήμανσης και επιλογής emoji κατά την πληκτρολόγηση σχολίου. Απόκρυψη κουμπιού κοινοποίησης στη λειτουργία πλήρους οθόνης Απόκρυψη του κουμπιού κοινοποίησης στην οθόνη αναπαραγωγής πλήρους οθόνης. - Απόκρυψη εναλλαγής ήχου / βίντεο - Απόκρυψη της εναλλαγής ήχου / βίντεο στην οθόνη αναπαραγωγής. + Απόκρυψη εναλλαγής Ήχου / Βίντεο + Απόκρυψη της εναλλαγής Ήχου / Βίντεο στην οθόνη αναπαραγωγής. Απομνημόνευση κατάστασης επανάληψης Απομνημόνευση της κατάστασης του κουμπιού επανάληψης. Απομνημόνευση κατάστασης ανακατέματος @@ -414,26 +437,20 @@ Καθαρισμός συνδέσμων κοινοποίησης Αφαίρεση των παραμέτρων παρακολούθησης από τις διευθύνσεις URL κατά την κοινοποίηση συνδέσμων. Παραποίηση προγράμματος πελάτη - "Παραποίηση του προγράμματος πελάτη για την αποφυγή προβλημάτων αναπαραγωγής. - -※ Αν ενεργοποιηθεί παράλληλα με τη λειτουργία «Παραποίηση δεδομένων ροής», ενδέχεται να εμφανιστούν προβλήματα αναπαραγωγής." + Παραποίηση του προγράμματος πελάτη για την αποφυγή προβλημάτων αναπαραγωγής. Προεπιλεγμένο πρόγραμμα πελάτη - "Καθορισμός ενός προεπιλεγμένου πρόγραμμα πελάτη για παραποίηση. - -※ Όταν ορίζεται πρόγραμμα πελάτη Android, συνιστάται να το χρησιμοποιήσετε μαζί με την λειτουργία «Παραποίηση έκδοσης εφαρμογής»." + Καθορισμός ενός προεπιλεγμένου πρόγραμμα πελάτη για παραποίηση. Android Music 4.27.53 Android Music 5.29.53 iOS Music 6.21 - Παραποίηση δεδομένων ροής - "Παραποίηση των δεδομένων ροής για την αποφυγή προβλημάτων αναπαραγωγής. - -※ Αν ενεργοποιηθεί παράλληλα με τη λειτουργία «Παραποίηση προγράμματος πελάτη», ενδέχεται να εμφανιστούν προβλήματα αναπαραγωγής." - Προεπιλεγμένο πρόγραμμα πελάτη - Καθορισμός ενός προεπιλεγμένου προγράμματος-πελάτη για την λήψη δεδομένων ροής. - Εμφάνιση στο «Στατιστικά για σπασίκλες» - Εμφάνιση του προγράμματος πελάτη που χρησιμοποιείται για τη λήψη δεδομένων ροής στο μενού «Στατιστικά για σπασίκλες». - Android VR - Android Music + iOS Music 7.04 + Τύπος ιστορικού παρακολούθησης + "• Αρχικός: Ακολουθεί τις ρυθμίσεις ιστορικού παρακολούθησης του λογαριασμού Google σας, αλλά το ιστορικό παρακολούθησης μπορεί να μη λειτουργεί λόγω χρήσης VPN ή εναλλακτικού DNS. +• Αντικατάσταση του domain: Ακολουθεί τις ρυθμίσεις ιστορικού παρακολούθησης του λογαριασμού Google σας. +• Αποκλεισμός ιστορικού παρακολούθησης: Το ιστορικό παρακολούθησης είναι αποκλεισμένο." + Αρχικός + Αντικατάσταση του domain + Αποκλεισμός ιστορικού παρακολούθησης Άνοιγμα ρυθμίσεων προεπιλεγμένων εφαρμογών Για να ανοίγουν οι συνδέσμοι YouTube Music στο RVX Music, ενεργοποιήστε το «Άνοιγμα υποστηριζόμενων συνδέσμων» και τις υποστηριζόμενες διευθύνσεις ιστού. Άνοιγμα ρυθμίσεων του MicroG GmsCore diff --git a/patches/src/main/resources/music/translations/es-rES/strings.xml b/patches/src/main/resources/music/translations/es-rES/strings.xml index 39bc4d864..4a12bb617 100644 --- a/patches/src/main/resources/music/translations/es-rES/strings.xml +++ b/patches/src/main/resources/music/translations/es-rES/strings.xml @@ -1,7 +1,6 @@ - ReVanced Extended Restablecer a valores por defecto. Reiniciar para cargar el diseño normalmente @@ -28,10 +27,10 @@ Oculta botón Guardar. Ocultar botón Descargar Oculta el botón Descargar. - Ocultar botón de compartir - Oculta el botón Compartir. Ocultar botón de emisoras de radio Oculta el botón Radio. + Ocultar botón de compartir + Oculta el botón Compartir. Ocultar etiquetas de botón de acción Oculta las etiquetas de los botones de acción. Reemplazar botón de acción de Descarga @@ -49,7 +48,6 @@ Descarga %2$s desde el sitio web." Anuncios Ocultar anuncios en pantalla completa - "Oculta anuncios en pantalla completa." Ocultar anuncios generales Oculta anuncios generales. Ocultar anuncios de música @@ -120,11 +118,17 @@ Problemas conocidos: General Cambiar página de inicio Seleccione en qué página se abre la aplicación. - Ranking + Predeterminado + Ranking + Episodios para luego Explorar - Inicio + Historial Biblioteca - Suscripciones + Música \"Me gusta\" + Pódcasts + Muestras + Buscar + Suscripciones Desactivar redirección de No me Gusta Deshabilita la redirección a la siguiente pista al hacer clic en el botón No me Gusta. Desactivar subtítulos automáticos @@ -173,11 +177,11 @@ Esto no evita la restricción de edad. Solo la acepta automáticamente." Objetivo de la versión de la app a modificar Seleccione el objetivo de la versión de la app a modificar. - 4.27.53 - Desactivar el modo radio en las regiones canadienses - 6.11.52 - Desactivar letras en tiempo real + 6.42.55 - Desactivar letras en tiempo real 7.16.53 - Restaurar la antigua barra de acción Barra de navegación + Activar color personalizado de la barra de navegación Ocultar botón de Inicio Oculta el botón de Inicio. Ocultar botón de Samples @@ -194,22 +198,8 @@ Esto no evita la restricción de edad. Solo la acepta automáticamente."Oculta las etiquetas en la barra de navegación. Reproductor - Añadir botón siguiente al minireproductor - Añadir botón siguiente pista al minireproductor. - Añadir botón anterior al minireproductor - Añadir botón pista anterior al minireproductor. - Activar coincidencia de color de reproductores - Hace coincidir el color del reproductor a pantalla completa con el de minimizado. - Activar fondo de reproductor negro - Cambia el color de fondo del reproductor a negro. - Desactivar gesto de minireproductor - Desactivar el gesto de deslizar para cambiar de pista en el minireproductor. Desactivar gesto del reproductor Desactivar el gesto de deslizar para cambiar de pista en el reproductor. - Activar reproductor minimizado forzado - Mantiene el reproductor permanentemente minimizado incluso si se reproduce otra pista. - Activar deslizar para descartar el minireproductor - Permite deslizar hacia abajo para descartar el minireproductor. Activar modo zen Añade un tinte gris al reproductor de vídeo para reducir la fatiga visual. Activar el modo Zen en podcasts @@ -222,8 +212,6 @@ Esto no evita la restricción de edad. Solo la acepta automáticamente."Oculta los botones marca de tiempo y emoji al escribir comentarios. Ocultar el botón Compartir en pantalla completa Oculta el botón Compartir en el reproductor de pantalla completa. - Ocultar Interruptor de Audio / Video - Oculta el interruptor de Audio / Video en el reproductor. Recordar estado de repetición Recuerda el estado de la repetición. Recordar estado aleatorio @@ -404,29 +392,10 @@ Se usa una instancia redireccionada, pero la API puede no estar disponible en al Desinfectar enlaces compartidos Elimina los parámetros de consulta de seguimiento de las URL al compartir enlaces. Falsificar cliente - "\"falsifica al cliente para evitar problemas de reproducción. - -Limitaciones: -• Código de audio OPUS puede no ser compatible. -• La miniatura de la barra de Seekbar puede no estar presente. -• El historial de la vista no funciona con una cuenta de marca." Cliente por defecto - "Define un cliente predeterminado a falsear. - -※ Al usar el cliente Android, se recomienda usarlo con 'Versión de aplicación Spoof'." Android Music 4.27.53 Android Music 5.29.53 iOS Music 6.21 - Falsificar datos de transmisión - "Falsificar los datos de streaming para evitar problemas de reproducción. - -※ Cuando se utiliza con 'Cliente de spoof', problemas de reproducción pueden producirse." - Cliente por defecto - Define un cliente por defecto que obtiene datos de streaming. - Mostrar en estadísticas para nerds - Muestra el cliente utilizado para obtener datos de streaming en Estadísticas para nerds. - Android VR - Android Music Abrir ajustes predeterminados de la app Para abrir los enlaces de YouTube Music en RVX Music, activa \'Abrir enlaces soportados\' y activa las direcciones web soportadas. Abrir GmsCore diff --git a/patches/src/main/resources/music/translations/fr-rFR/strings.xml b/patches/src/main/resources/music/translations/fr-rFR/strings.xml index 5ed471cd8..1ee9b147d 100644 --- a/patches/src/main/resources/music/translations/fr-rFR/strings.xml +++ b/patches/src/main/resources/music/translations/fr-rFR/strings.xml @@ -1,7 +1,6 @@ - ReVanced Extended Réinitialiser les valeurs par défaut. Redémarrer pour charger l\'interface correctement @@ -28,10 +27,10 @@ Masque le bouton \"Enregistrer\". Masquer le bouton \"Télécharger\" Masque le bouton \"Télécharger\". - Masquer le bouton \"Partager\" - Masque le bouton \"Partager\". Masquer le bouton \"Radio\" Masque le bouton \"Démarrer la radio\". + Masquer le bouton \"Partager\" + Masque le bouton \"Partager\". Masquer les noms de la barre d’action Masque les noms de la barre d’action. Remplacer l\'action du bouton \"Télécharger\" @@ -49,7 +48,6 @@ Veuillez télécharger %2$s à partir du site web." Publicités Masquer les publicités en plein écran - "Masque les publicités en plein écran." Masquer les publicités générales Masque les publicités générales. Masquer les publicités musicales @@ -118,11 +116,8 @@ Limitations : Interface Modifier la page de démarrage Sélectionnez la page de démarrage de l\'appli. - Charts Explorer - Accueil Bibliothèque - Abonnements Désactiver la redirection du bouton \"Je n\'aime pas\" Désactive le passage à la piste suivante lorsque vous cliquez sur le bouton \"Je n\'aime pas\". Désactiver les sous-titres forcés @@ -171,8 +166,6 @@ Cela ne contourne pas la restriction d'âge, mais le confirme automatiquement."< • Si désactivée ultérieurement, l'ancienne interface peut subsister jusqu'à la suppression des données de l'application." Choisir la version à falsifier Sélectionner la version de l\'application à falsifier. - 4.27.53 - Désactive le mode radio dans les régions canadiennes - 6.11.52 - Désactive les paroles en temps réel 7.16.53 - Restaurer l\'ancienne barre d\'action Barre de navigation @@ -192,22 +185,8 @@ Cela ne contourne pas la restriction d'âge, mais le confirme automatiquement."< Masque le nom sous les boutons de la barre de navigation. Lecteur - Ajouter le bouton \"Suivant\" sur le minilecteur - Ajoute le bouton \"Suivant\" sur le minilecteur. - Ajouter le bouton \"Précédent\" sur le minilecteur - Ajoute le bouton \"Précédent\" sur le minilecteur. - Activer l\'harmonisation des couleurs du lecteur - Harmonise les couleurs du minilecteur à celle du lecteur en plein écran. - Activer l\'interface du lecteur en noir - Change la couleur de l\'interface du lecteur en noir. - Désactiver les gestes du minilecteur - Désactive les gestes pour changer de musique dans le minilecteur. Désactiver les gestes du lecteur Désactive les gestes pour changer de musique dans le lecteur. - Activer la minimisation forcée du lecteur - Maintient le lecteur minimisé même si une autre piste est lue. - Activer le geste pour fermer le minilecteur - Active le geste vers le bas pour fermer le minilecteur. Activer le mode zen Change la couleur du lecteur par un voile gris pour réduire la fatigue oculaire. Activer le mode \"Zen\" sur les Podcasts @@ -220,8 +199,6 @@ Cela ne contourne pas la restriction d'âge, mais le confirme automatiquement."< Masque les boutons \"émoji\" et \"horodatage\" lors de la rédaction d\'un commentaire. Masquer le bouton \"Partager\" en plein écran Masque le bouton \"Partager\" sur le lecteur en plein écran. - Masquer le sélecteur Audio/Vidéo - Masque le sélecteur Audio/Vidéo en haut du lecteur. Enregistrer l\'état du mode répétition Enregistre l\'état du mode répétition. Enregistrer l\'état du mode aléatoire @@ -395,7 +372,6 @@ Info : Nettoyer les liens partagés Supprime les paramètres de suivi (tracking) des URL lors du partage de liens. Client par défaut - Client par défaut Ouvrir les paramètres par défaut de l\'application Pour ouvrir les liens YouTube Music dans RVX Music, activez \'Ouvrir les liens compatibles\' et activez les adresses web prises en charge. Ouvrir GmsCore diff --git a/patches/src/main/resources/music/translations/hu-rHU/strings.xml b/patches/src/main/resources/music/translations/hu-rHU/strings.xml index 47f00b78d..0a8eca43e 100644 --- a/patches/src/main/resources/music/translations/hu-rHU/strings.xml +++ b/patches/src/main/resources/music/translations/hu-rHU/strings.xml @@ -1,7 +1,7 @@ - ReVanced Extended + RVX Visszaállítás az alapértelmezett értékekre. Indítsd újra az elrendezés normál betöltéséhez @@ -28,10 +28,10 @@ Elrejti a mentés gombot. Letöltés gomb elrejtése Elrejti a letöltés gombot. - Megosztás gomb elrejtése - Elrejti a Megosztás gombot. Rádió gomb elrejtése Elrejti a rádió gombot. + Megosztás gomb elrejtése + Elrejti a Megosztás gombot. Navigációs gombok címkéinek elrejtése Elrejti a címkéket az műveleti gombokon. Letöltés gomb felülírása @@ -49,7 +49,6 @@ Töltsd le a(z) %2$s weboldalról." Hirdetések Teljes képernyős hirdetések elrejtése - "Teljes képernyős hirdetések elrejtése." Általános hirdetések elrejtése Elrejti az általános hirdetéseket. Zenei hirdetések elrejtése @@ -120,11 +119,17 @@ Korlátozások: Általános Kezdőlap megváltoztatása Kiválaszthatod, hogy milyen oldalon nyíljon meg az alkalmazás. - Diagramok + Alapértelmezett + Diagramok + Epizódok későbbre Felfedezés - Kezdőlap + Előzmények Könyvtár - Feliratkozások + Lájkolt zenék + Podcastek + Minták + Kereső + Feliratkozások Nem tetszik átirányítás letiltása Letiltja az átirányítást a következő számra, amikor rányomsz a nem tetszik gombra. Kényszerített automatikus feliratok letiltása @@ -173,8 +178,7 @@ Ez nem kerüli meg a korhatárkorlátozást. Csak automatikusan elfogadja azt."< • Ha később kikapcsolod, a régi felhasználói felület megmaradhat, amíg az alkalmazás adatait nem törlöd." Cél alkalmazásverzió Válaszd ki, hogy melyik alkalmazásverziót akarod használni. - 4.27.53 - Letiltja a rádió módot Kanada területén - 6.11.52 - Letiltja a valós idejű dalszövegeket + 6.42.55 - Letiltja a valós idejű dalszövegeket 7.16.53 - Régi menüsor visszaállítása Navigációs sor @@ -199,22 +203,8 @@ Ez nem kerüli meg a korhatárkorlátozást. Csak automatikusan elfogadja azt."< Elrejti a szöveget a navigációs gombok alatt. Lejátszó - Minilejátszó következő gomb engedélyezése - Engedélyezi a következő szám gombot a minilejátszónál. - Minilejátszó előző gomb engedélyezése - Engedélyezi a előző szám gombot a minilejátszónál. - Minialejátszó színének módosítása - A minialejátszó színét a teljes képernyős lejátszó színéhez igazítja. - A lejátszó háttérszínének módosítása - Megváltoztatja a lejátszó háttér színét feketére. - Minilejátszó gesztus letiltása - Letiltja a zeneszámok váltását a minilejátszóban. Lejtászó gesztus letiltása Letiltja a zeneszámok váltását a lejátszóban. - Minilejátszó kényszerítése - A lejátszó akkor is minimalizálva marad, amikor új zeneszámra vált. - Minilejátszó elhagyása egy húzással - Lehetővé teszi a minialejátszó elhagyását lefelé húzással. Zen mód bekapcsolása Világosszürke színt használ a lejátszó hátteréhez, hogy csökkentse a szem megerőltetését. Zen mód engedélyezése podcastekben @@ -227,8 +217,6 @@ Ez nem kerüli meg a korhatárkorlátozást. Csak automatikusan elfogadja azt."< Elrejti az emoji és az időbélyeg gombokat a hozzászólás beírásakor. Teljes képernyős megosztás gomb elrejtése Elrejti a megosztás gombot a teljes képernyős lejátszóban. - Hang/Videó gomb elrejtése - Elrejti a Hang / Videó gombot a lejátszóban. Isméltés állapotának megjegyzése Emlékezik az ismétlés állapotára. Keverés állapotának megjegyzése @@ -413,26 +401,20 @@ Információk: Megosztási linkek tisztítása A megosztási linkeket a nyomkövető lekérdezési paraméterek eltávolításával tisztítja. Kliens hamisítása - "Az kliens meghamisítása a lejátszási problémák megelőzése érdekében. - -• Az 'Adatfolyam meghamisítása' funkcióval együtt használva lejátszási problémák léphetnek fel." + Kliens meghamisítása a lejátszási hibák elkerüléséhez. Alapértelmezett kliens - "Meghatározza az alapértelmezett klienst a hamisításhoz. - -• Az Android kliens használata esetén ajánlott a 'Alkalmazás verziójának meghamisítása' használni." + Meghatároz egy alapértelmezett klients a hamisításhoz. Android Music 4.27.53 Android Music 5.29.53 iOS Music 6.21 - Adatfolyam meghamisítása - "A lejátszási problémák megelőzése érdekében hamisítja a streaming-adatokat. - -• A 'Kliens hamisítása' használata esetén lejátszási problémák léphetnek fel." - Alapértelmezett kliens - Meghatároz egy alapértelmezett klienst, amely streaming adatokat hív le. - Megjelenik a statisztikák kockáknakban - Megmutatja a streaming adatok lekérdezésére használt klienst a Statisztikák kockáknakban. - Android VR - Android Music + iOS Music 7.04 + Megtekintési előzmények típusa + "• Eredeti: A Google-fiók megtekintési előzményeinek beállításait követi, de előfordulhat, hogy a DNS vagy a VPN miatt nem működik. +• Domain cseréje: Követi a Google-fiók megtekintési előzmények beállításait. +• Megtekintési előzmények blokkolása: A megtekintési előzmények blokkolva vannak." + Eredeti + Domain cseréje + Megtekintési előzmények tiltása Alapértelmezett program beállítások megnyitása A YouTube Music linkek megnyitásához az RVX Musicban engedélyezze a Támogatott linkek megnyitása és az összes Támogatott webcím engedélyezése opciót. GmsCore megnyitása diff --git a/patches/src/main/resources/music/translations/id-rID/strings.xml b/patches/src/main/resources/music/translations/id-rID/strings.xml index 905c3a3e7..5958ebe52 100644 --- a/patches/src/main/resources/music/translations/id-rID/strings.xml +++ b/patches/src/main/resources/music/translations/id-rID/strings.xml @@ -1,7 +1,6 @@ - ReVanced Extended Mulai ulang untuk memuat layout secara normal Refresh dan mulai ulang @@ -27,10 +26,10 @@ Menyembunyikan tombol Simpan. Sembunyikan tombol Unduh Menyembunyikan tombol Unduh. - Sembunyikan tombol Bagikan - Menyembunyikan tombol Bagikan. Sembunyikan tombol Radio Menyembunyikan tombol Radio. + Sembunyikan tombol Bagikan + Menyembunyikan tombol Bagikan. Sembunyikan tombol bilah tindakan Menyembunyikan bilah dari tombol tindakan. Ganti tombol tindakan Unduh @@ -48,7 +47,6 @@ Download %2$s dari website." Iklan Sembunyikan iklan fullscreen - "Menyembunyikan iklan fullscreen." Sembunyikan Iklan Umum Menyembunyikan Iklan Umum. Sembunyikan iklan musik @@ -115,11 +113,8 @@ Masalah yang diketahui: Umum Ganti Halaman Awal Select which page the app opens in. - Charts Jelajahi - Beranda Koleksi - Berlangganan Disable dislike redirection Disables redirection to the next track when clicking the Dislike button. Nonaktifkan teks otomatis paksa @@ -168,8 +163,7 @@ This does not bypass the age restriction. It just accepts it automatically." Target pemalsuan versi aplikasi Pilih target pemalsuan versi aplikasi. - 4.27.53 - Nonaktifkan mode radio di wilayah Kanada - 6.11.52 - Matikan Lirik real-time + Bilah Navigasi Hide Home button @@ -188,22 +182,8 @@ This does not bypass the age restriction. It just accepts it automatically."Menyembunyikan label di bilah navigasi. Player - Add miniplayer next button - Adds a next track button to the miniplayer. - Add miniplayer previous button - Adds a previous track button to the miniplayer. - Aktifkan pencocokan warna pemutar - Mencocokkan warna pemutar layar penuh dengan yang diperkecil. - Enable black player background - Changes the player background color to black. - Nonaktifkan gerakan miniplayer - Nonaktifkan gesekan untuk mengubah trek di miniplayer. Menonaktifkan gerakan pemutar Nonaktifkan usap untuk mengubah trek di pemutar. - Aktifkan pemutar yang diminimalkan paksa - Mempertahankan pemutar agar tetap diminimalkan secara permanen meskipun trek lain diputar. - Enable swipe to dismiss miniplayer - Enables swipe down to dismiss miniplayer. Aktifkan mode zen Menambahkan rona abu-abu ke pemutar video untuk mengurangi ketegangan mata. Enable Zen mode in podcasts @@ -216,8 +196,6 @@ This does not bypass the age restriction. It just accepts it automatically."Hides the timestamp and emoji buttons when typing comments. Hide fullscreen Share button Hides the Share button in the fullscreen player. - Hide Audio / Video toggle - Hides the Audio / Video toggle in the player. Ingat keadaan pengulangan Mengingat keadaan pengulangan. Ingat keadaan pengacakan diff --git a/patches/src/main/resources/music/translations/in/strings.xml b/patches/src/main/resources/music/translations/in/strings.xml index 905c3a3e7..5958ebe52 100644 --- a/patches/src/main/resources/music/translations/in/strings.xml +++ b/patches/src/main/resources/music/translations/in/strings.xml @@ -1,7 +1,6 @@ - ReVanced Extended Mulai ulang untuk memuat layout secara normal Refresh dan mulai ulang @@ -27,10 +26,10 @@ Menyembunyikan tombol Simpan. Sembunyikan tombol Unduh Menyembunyikan tombol Unduh. - Sembunyikan tombol Bagikan - Menyembunyikan tombol Bagikan. Sembunyikan tombol Radio Menyembunyikan tombol Radio. + Sembunyikan tombol Bagikan + Menyembunyikan tombol Bagikan. Sembunyikan tombol bilah tindakan Menyembunyikan bilah dari tombol tindakan. Ganti tombol tindakan Unduh @@ -48,7 +47,6 @@ Download %2$s dari website." Iklan Sembunyikan iklan fullscreen - "Menyembunyikan iklan fullscreen." Sembunyikan Iklan Umum Menyembunyikan Iklan Umum. Sembunyikan iklan musik @@ -115,11 +113,8 @@ Masalah yang diketahui: Umum Ganti Halaman Awal Select which page the app opens in. - Charts Jelajahi - Beranda Koleksi - Berlangganan Disable dislike redirection Disables redirection to the next track when clicking the Dislike button. Nonaktifkan teks otomatis paksa @@ -168,8 +163,7 @@ This does not bypass the age restriction. It just accepts it automatically." Target pemalsuan versi aplikasi Pilih target pemalsuan versi aplikasi. - 4.27.53 - Nonaktifkan mode radio di wilayah Kanada - 6.11.52 - Matikan Lirik real-time + Bilah Navigasi Hide Home button @@ -188,22 +182,8 @@ This does not bypass the age restriction. It just accepts it automatically."Menyembunyikan label di bilah navigasi. Player - Add miniplayer next button - Adds a next track button to the miniplayer. - Add miniplayer previous button - Adds a previous track button to the miniplayer. - Aktifkan pencocokan warna pemutar - Mencocokkan warna pemutar layar penuh dengan yang diperkecil. - Enable black player background - Changes the player background color to black. - Nonaktifkan gerakan miniplayer - Nonaktifkan gesekan untuk mengubah trek di miniplayer. Menonaktifkan gerakan pemutar Nonaktifkan usap untuk mengubah trek di pemutar. - Aktifkan pemutar yang diminimalkan paksa - Mempertahankan pemutar agar tetap diminimalkan secara permanen meskipun trek lain diputar. - Enable swipe to dismiss miniplayer - Enables swipe down to dismiss miniplayer. Aktifkan mode zen Menambahkan rona abu-abu ke pemutar video untuk mengurangi ketegangan mata. Enable Zen mode in podcasts @@ -216,8 +196,6 @@ This does not bypass the age restriction. It just accepts it automatically."Hides the timestamp and emoji buttons when typing comments. Hide fullscreen Share button Hides the Share button in the fullscreen player. - Hide Audio / Video toggle - Hides the Audio / Video toggle in the player. Ingat keadaan pengulangan Mengingat keadaan pengulangan. Ingat keadaan pengacakan diff --git a/patches/src/main/resources/music/translations/it-rIT/strings.xml b/patches/src/main/resources/music/translations/it-rIT/strings.xml index 25486a5c0..30f83dae9 100644 --- a/patches/src/main/resources/music/translations/it-rIT/strings.xml +++ b/patches/src/main/resources/music/translations/it-rIT/strings.xml @@ -1,7 +1,6 @@ - ReVanced Extended Resetta ai valori iniziali. Riavvia per caricare il layout normalmente @@ -44,10 +43,6 @@ Nascondi etichetta di navigazione - Abilita l\'abbinamento di colore dei Riproduttori - Allinea il colore del lettore a schermo intero con quello in secondo piano. - Abilita il riproduttore in secondo piano forzato - Mantieni il riproduttore in secondo piano anche se un\'altra traccia viene riprodotta. Abilita la modalità zen Aggiunge una sfumatura grigia al riproduttore video per ridurre l\'affaticamento degli occhi. Ricorda lo stato di ripetizione diff --git a/patches/src/main/resources/music/translations/ja-rJP/strings.xml b/patches/src/main/resources/music/translations/ja-rJP/strings.xml index a132811e2..ef7d43124 100644 --- a/patches/src/main/resources/music/translations/ja-rJP/strings.xml +++ b/patches/src/main/resources/music/translations/ja-rJP/strings.xml @@ -1,7 +1,6 @@ - ReVanced Extended デフォルト値にリセット。 再起動してレイアウトを正常に読み込みます @@ -28,10 +27,10 @@ プレイリストに追加ボタンを非表示にします。 ダウンロードボタンを非表示 ダウンロードボタンを非表示にします。 - 共有ボタンを非表示 - 共有ボタンを非表示にします。 ラジオボタンを非表示 ラジオボタンを非表示にします。 + 共有ボタンを非表示 + 共有ボタンを非表示にします。 アクションボタンのラベルを非表示 アクションボタンのラベルを非表示にします。 ダウンロードボタンを置き換える @@ -49,7 +48,6 @@ 広告 全画面広告を非表示 - "全画面広告を非表示にします。" 一般広告を非表示 一般広告を非表示にします。 音楽の広告を非表示 @@ -120,11 +118,8 @@ 全般 スタートページを変更 アプリのスタートページを変更します。 - チャート 探索 - ホーム ライブラリ - 定期購入 低評価リダイレクトを無効化 低評価ボタンを押したとき、次の曲へのリダイレクトするのを無効にする。 字幕の強制を無効化 @@ -173,8 +168,6 @@ • 後からこの機能を無効にしても、データを消去するまで古い UI のままになる場合があります。" 偽装するバージョン 偽装するバージョンを選択してください。 - 4.27.53 - カナダの地域でラジオモードを無効化 - 6.11.52 - リアルタイムの歌詞を無効化 7.16.53 - 古いアクションバーを復元 ナビゲーションバー @@ -199,22 +192,8 @@ ナビゲーションバーのラベルを非表示にします。 プレーヤー - 「次の曲に進むボタン」を表示 - ミニプレーヤーの「次の曲に進むボタン」を表示します。 - 「前の曲に戻るボタン」を表示 - ミニプレーヤーで「前の曲に戻るボタン」を表示します。 - カラーマッチプレーヤーを有効化 - ミニプレーヤーと全画面プレーヤーの色を統一します。 - 黒のプレイヤー背景を有効化 - プレイヤーの背景の色を黒に固定します。 - ミニプレーヤージェスチャーを無効にする - ミニプレーヤーでスワイプによる曲の変更を無効にします プレイヤージェスチャーを無効にする プレイヤーでスワイプによる曲の変更を無効にします。 - 最小化されたプレーヤーを有効にする - 他のトラックが再生されていても、プレーヤーを常に最小化したままにします。 - スワイプしてミニプレーヤーを閉じる - 下にスワイプしてミニプレーヤーを閉じられるようにします。 Zen モードを有効化 動画プレーヤーに灰色の色合いを追加し、目の疲れを軽減します。 ポッドキャストでZenモードを有効化 @@ -227,8 +206,6 @@ コメントを入力するときにタイムスタンプと絵文字ボタンを非表示にします。 全画面共有ボタンを非表示 全画面表示のプレイヤーの共有ボタンを非表示にします。 - 曲と動画の切り替えスイッチを非表示 - プレイヤーの曲と動画の切り替えスイッチを非表示にします。 リピートの状態を保存 リピートの状態を記憶します。 シャッフルの状態を保存 @@ -409,29 +386,10 @@ API キーの発行方法については、ここをタップしてください 共有リンクのクリーンアップ リンクを共有する際に、URL からトラッキングクエリパラメーターを削除します。 クライアントを偽装 - "再生の問題を防ぐためにクライアントを偽装します - -制限事項 -• OPUSオーディオコーデックはサポートされていない可能性があります。 -• シークバーのサムネイルが表示されない場合があります。 -• 再生履歴はブランドアカウントでは動作しません。" 既定のクライアント - "偽装のデフォルトクライアントを定義します。 - -• Androidクライアントを使用する場合は、「Spoof app version」も使用することを推奨します。" Android Music 4.27.53 Android Music 5.29.53 iOS Music 6.21 - ストリーミングデータを偽装 - "ストリーミングデータを偽装して、再生の問題を防ぎます。 - -• Spoof client と併用すると、再生に問題が発生する場合があります。" - 既定のクライアント - ストリーミングデータを取得するデフォルトのクライアントを定義します。 - マニア向けの統計を表示 - Stats for nerds でストリーミングデータの取得に使用されるクライアントを定義する。 - Android VR - Android Music 「デフォルトで開く」の設定 RVX Music でYouTube Music のURLを開くには、「対応リンクを開く」を有効にし、サポートされているURLを有効にします。 GmsCoreを開く diff --git a/patches/src/main/resources/music/translations/ko-rKR/strings.xml b/patches/src/main/resources/music/translations/ko-rKR/strings.xml index f0d1f6fc1..8dde7b7ce 100644 --- a/patches/src/main/resources/music/translations/ko-rKR/strings.xml +++ b/patches/src/main/resources/music/translations/ko-rKR/strings.xml @@ -1,7 +1,7 @@ - ReVanced Extended 설정 + RVX Music 설정 기본값으로 초기화합니다. 레이아웃을 정상적으로 불러오기 위해 다시 시작합니다. @@ -20,6 +20,8 @@ 서비스 약관 컨테이너를 숨깁니다. 액션바 + 액션바 위치 변경 + 액션바가 재생 버튼 아래로 이동합니다. 좋아요 & 싫어요 버튼 제거 좋아요 & 싫어요 버튼을 숨깁니다. \n이전 플레이어 레이아웃에서는 작동하지 않습니다. 댓글 버튼 제거 @@ -28,10 +30,13 @@ (재생목록에) 저장 버튼을 숨깁니다. 오프라인 저장 버튼 제거 오프라인 저장 버튼을 숨깁니다. - 공유 버튼 제거 - 공유 버튼을 숨깁니다. 뮤직 스테이션 버튼 제거 뮤직 스테이션 버튼을 숨깁니다. + 공유 버튼 제거 + 공유 버튼을 숨깁니다. + 노래 / 동영상 버튼 제거 + "노래 / 동영상 버튼을 숨깁니다. +(이 버튼은 일부 사용자만 사용할 수 있습니다.)" 액션 버튼 라벨 제거 액션 버튼에서 라벨을 숨깁니다. 오프라인 저장 버튼 재정의 @@ -49,22 +54,21 @@ 광고 전체 화면 광고 제거 - "전체 화면 광고를 숨깁니다. - -알려진 문제점: -• 가끔씩 홈 피드 대신 검정 공백 화면이 표시될 수 있습니다." + 전체 화면 광고를 숨깁니다. + 전체 화면 광고가 닫아집니다. 일반 레이아웃 광고 제거 일반 레이아웃 광고를 숨깁니다. 음악 광고 제거 음악을 재생하는 동안에 광고를 숨깁니다. 유료 광고 포함 라벨 제거 유료 광고 포함 라벨을 숨깁니다. - YT Premium 프로모션 팝업 제거 - YT Premium 프로모션 팝업을 숨깁니다. - YT Premium 갱신 배너 제거 - YT Premium 갱신 배너를 숨깁니다. - 프로모션 알림 배너 제거 - 프로모션 알림 배너를 숨깁니다. + Premium 프로모션 팝업 제거 + Premium 프로모션 팝업을 숨깁니다. + Premium 프로모션 팝업이 닫아집니다. + Premium 갱신 배너 제거 + Premium 갱신 배너를 숨깁니다. + Premium 프로모션 알림 배너 제거 + Premium 프로모션 알림 배너를 숨깁니다. 메뉴 구성요소 무음 건너뛰기 스위치 추가 @@ -92,6 +96,7 @@ 에피소드로 이동 메뉴 제거 팟캐스트로 이동 메뉴 제거 고객센터 메뉴 제거 + 관심 없음 메뉴 제거 빠른 선곡에 고정 제거 다음에 재생 메뉴 제거 품질 메뉴 제거 @@ -110,11 +115,11 @@ 빠른 선곡에서 고정 해제 제거 노래 크레딧 보기 메뉴 제거 이어서 시청 - \'YouTube로 시청\' 메뉴를 누르면 YouTube로 변경하여 동영상을 현재 재생 시간부터 이어서 시청합니다. - YouTube로 시청 + \'YouTube에서 감상하기\' 메뉴를 누르면 YouTube 앱으로 변경하여 동영상을 현재 재생 시간부터 이어서 감상합니다. + YouTube에서 감상하기 잘못된 동영상 URL입니다. 현재 재생목록 닫기 메뉴 변경 - \'현재 재생목록 닫기\' 메뉴를 \'YouTube로 시청\' 메뉴로 변경합니다. + \'현재 재생목록 닫기\' 메뉴를 \'YouTube에서 감상하기\' 메뉴로 변경합니다. 신고 메뉴 변경 \'신고\' 메뉴를 \'재생 속도\' 메뉴로 변경합니다. 댓글에서 신고 메뉴 유지 @@ -123,13 +128,19 @@ 일반 앱 시작 페이지 변경 앱 시작 페이지를 변경합니다. - 차트 + 홈 (기본값) + 차트 + 나중에 감상할 에피소드 둘러보기 - + 기록 보관함 - 구독 + 좋아요 표시한 음악 + 팟캐스트 + 샘플 + 검색 + 구독 싫어요 리다이렉션 비활성화 - \'싫어요 버튼을 누르면 다음 트랙으로 리다이렉션\'을 비활성화합니다. + 싫어요 버튼을 누르면 다음 트랙으로 리다이렉션할 수 없습니다. 자동 자막 비활성화 자막이 자동으로 활성화되지 않도록 설정합니다. 가로 모드 활성화 @@ -160,14 +171,14 @@ 피드에서 샘플 선반을 숨깁니다. 노래 검색 버튼 제거 툴바에서 노래 검색 버튼을 숨깁니다. - \'탭하여 업데이트\' 버튼 제거 - \'탭하여 업데이트\' 버튼을 숨깁니다. + 탭하여 업데이트 버튼 제거 + 탭하여 업데이트 버튼을 숨깁니다. 음성 검색 버튼 제거 툴바에서 음성 검색 버튼을 숨깁니다. 이전 보관함 선반으로 복원 이전 보관함 탭으로 복원합니다. (실험 기능) - 시청 경고 다이얼로그 제거 - "시청 경고 다이얼로그를 제거합니다. + 시청자 재량 다이얼로그 제거 + "시청자 재량 다이얼로그를 제거합니다. 이 설정은 다이얼로그를 자동으로 허용하기만 하며, 연령 제한(성인인증 절차)을 우회할 수 없습니다." 앱 버전 변경 @@ -177,15 +188,14 @@ • 나중에 이 기능을 비활성화하면 앱 데이터를 지우기 전까지 이전 레이아웃이 유지될 수 있습니다." 변경할 앱 버전 변경할 앱 버전을 선택하세요. - 4.27.53 - 캐나다 지역에서 뮤직 스테이션 모드를 비활성화합니다. - 6.11.52 - 실시간 가사를 비활성화합니다. + 6.42.55 - 실시간 가사를 비활성화합니다. 7.16.53 - 이전 액션바로 복원합니다. 하단바 사용자 정의 하단바 색상 활성화 사용자 정의 하단바 색상을 활성화합니다. 사용자 정의 하단바 색상 - 하단바 색상의 헥스 코드를 입력하세요. + 사용하고 싶은 하단바 색상의 헥스 코드를 입력하세요. 잘못된 하단바 색상 값입니다. 홈 버튼 제거 홈 버튼을 숨깁니다. @@ -203,22 +213,35 @@ 하단바 버튼에서 라벨을 숨깁니다. 플레이어 - 미니 플레이어에서 다음 버튼 활성화 - 미니 플레이어에서 다음 버튼을 활성화합니다. - 미니 플레이어에서 이전 버튼 활성화 - 미니 플레이어에서 이전 버튼을 활성화합니다. - 미니 플레이어 색상 변경 - 미니 플레이어 색상을 전체 화면 플레이어 색상으로 변경합니다. - 검정 플레이어 배경 활성화 - 플레이어 배경 색상을 검정으로 설정합니다. - 미니 플레이어 제스처 비활성화 - 미니 플레이어에서 \'스와이프 제스처로 트랙 변경\'을 비활성화합니다. + 미니 플레이어 다음 재생 버튼 추가 + 미니 플레이어에 다음 트랙 재생 버튼을 추가합니다. + 미니 플레이어 이전 재생 버튼 추가 + 미니 플레이어에 이전 트랙 재생 버튼을 추가합니다. + 미니 플레이어 색상 변경 + 미니 플레이어 색상을 전체 화면 플레이어와 일치하도록 변경합니다. + 플레이어 배경 색상 변경 + 풀레이어 배경 색상을 사용자 정의 색상으로 변경합니다. + 플레이어 배경 메인 색상 + "사용하고 싶은 플레이어 배경 메인 색상의 헥스 코드를 입력하세요. + +앱이 밝은 테마를 지원하지 않으므로 가능하면 어두운 색상을 사용하세요." + 플레이어 배경 보조 색상 + "사용하고 싶은 플레이어 배경 보조 색상의 헥스 코드를 입력하세요. + +앱이 밝은 테마를 지원하지 않으므로 가능하면 어두운 색상을 사용하세요." + 잘못된 플레이어 배경 색상입니다. + 재생바 위치 변경 + 재생바가 재생 버튼 아래로 이동합니다. + 미니 플레이어 제스처 비활성화 + 미니 플레이어에서 스와이프하여 트랙을 변경할 수 없습니다. 플레이어 제스처 비활성화 - 플레이어에서 \'스와이프 제스처로 트랙 변경\'을 비활성화합니다. - 미니 플레이어 강제 활성화 - 트랙이 변경되더라도 미니 플레이어로 강제 활성화합니다. - 스와이프하여 미니 플레이어 닫기 활성화 - 아래로 스와이프하여 미니 플레이어 닫기를 활성화합니다. + 플레이어에서 스와이프하여 트랙을 변경할 수 없습니다. + 강제로 미니 플레이어 활성화 + 새로운 트랙으로 변경되면 강제로 미니 플레이어를 활성화합니다. + 스와이프하여 미니 플레이어 닫기 활성화 + 미니 플레이어를 아래로 스와이프하여 닫을 수 있습니다. + 두꺼운 재생바 활성화 + 두꺼운 재생바를 활성화합니다. 집중 모드 활성화 동영상 플레이어의 색상을 회색조로 설정해 눈의 피로를 줄입니다. 팟캐스트에서 집중 모드 활성화 @@ -231,8 +254,8 @@ 댓글을 입력할 때, 타임스탬프 및 이모지 버튼을 숨깁니다. 전체 화면에서 공유 버튼 제거 전체 화면에서 공유 버튼을 숨깁니다. - \'노래↔동영상\' 토글 제거 - 플레이어에서 \'노래↔동영상\' 토글을 숨깁니다. + 노래 / 동영상 토글 제거 + 플레이어에서 노래 / 동영상 토글을 숨깁니다. 반복 상태 저장 반복 재생 토글 상태를 저장합니다. 셔플 상태 저장 @@ -372,7 +395,7 @@ API Key를 발급받는 방법을 보려면 여기를 누르세요." 기타 설정 가져오기 / 내보내기 - 설정을 가져오거나 내보낼 수 있습니다. + RVX Music 설정을 가져오거나 내보낼 수 있습니다. 파일로 설정 내보내기 파일에서 설정 가져오기 텍스트로 설정 가져오기 / 내보내기 @@ -388,7 +411,7 @@ API Key를 발급받는 방법을 보려면 여기를 누르세요." 이미지 표시 제한 국가 우회 이미지 도메인을 변경하여 일부 국가에서 차단된 재생목록 썸네일, 채널 프로필 사진, 커뮤니티 게시물 이미지 등을 수신할 수 있습니다. 공유 시트 변경 - YT Music 기본 공유 시트에서 Android 기본 공유 시트로 변경합니다.\n\n• 공유 버튼으로 바로 Android 기본 공유 메뉴를 실행할 수 있습니다. + YT Music 기본 공유 시트에서 Android 시스템 공유 시트로 변경합니다.\n\n• 공유 버튼으로 바로 Android 시스템 공유 메뉴를 실행할 수 있습니다. Cairo 스플래시 애니메이션 비활성화 앱을 시작할 때, Cairo 스플래시 애니메이션을 비활성화합니다. DRC 오디오 비활성화 @@ -417,30 +440,24 @@ API Key를 발급받는 방법을 보려면 여기를 누르세요." 추적 쿼리를 제거한 링크 공유 링크를 공유할 때, URL에서 추적 쿼리 매개변수를 제거합니다. 클라이언트 변경 - "클라이언트를 변경하여 재생 문제를 방지할 수 있습니다. - -• '스트리밍 데이터 변경'과 함께 사용할 경우에 재생 문제가 발생할 수 있습니다." + 클라이언트를 변경하여 재생 문제를 방지할 수 있습니다. 기본 클라이언트 - "변경할 기본 클라이언트를 정의합니다. - -• Android 클라이언트를 사용할 경우에 '앱 버전 변경'과 함께 사용하는 것을 권장합니다." + 클라이언트 변경에 대한 기본 클라이언트를 정의할 수 있습니다. Android Music 4.27.53 Android Music 5.29.53 iOS Music 6.21 - 스트리밍 데이터 변경 - "스트리밍 데이터를 변경하여 재생 문제를 방지합니다. - -• '클라이언트 변경'과 함께 사용할 경우에 재생 문제가 발생할 수 있습니다." - 기본 클라이언트 - 스트리밍 데이터를 가져오는 데 사용되는 기본 클라이언트를 정의할 수 있습니다. - 동영상 통계에서 표시 - \'스트리밍 데이터를 가져오는 데 사용되는 클라이언트\'가 동영상 통계에서 표시됩니다. - Android VR - Android Music + iOS Music 7.04 + 시청 기록 유형 + "• 기본값: Google 계정의 시청 기록 설정을 따르지만 DNS 또는 VPN으로 인하여 시청 기록이 작동되지 않을 수 있습니다. +• 도메인 변경: Google 계정의 시청 기록 설정을 따릅니다. +• 시청 기록 차단: 시청 기록이 차단됩니다." + 기본값 + 도메인 변경 + 검색 기록 차단 기본 앱 설정 열기 YT Music 링크를 RVX Music으로 열려면 \'지원되는 링크 열기\'를 활성화하고 지원되는 링크를 추가하세요. 링크 추가가 잠겨있다면 순정 YT Music 앱 정보 → \'기본적으로 열기\'에서 \'지원되는 링크 열기\'를 비활성화한 후에 추가할 수 있습니다. GmsCore 설정 열기 - 알림 수신을 위한 클라우드 메시징 설정을 할 수 있습니다. + RVX Music 알림 수신을 위한 클라우드 메시징 설정을 할 수 있습니다. GmsCore가 설치되어 있지 않습니다. 설치하세요. 필수 조치 "GmsCore에 백그라운드에서 실행할 수 있는 권한이 없습니다. diff --git a/patches/src/main/resources/music/translations/nl-rNL/strings.xml b/patches/src/main/resources/music/translations/nl-rNL/strings.xml index 0bc35e554..056c41f41 100644 --- a/patches/src/main/resources/music/translations/nl-rNL/strings.xml +++ b/patches/src/main/resources/music/translations/nl-rNL/strings.xml @@ -1,7 +1,6 @@ - ReVanced ExtExtended Start opnieuw op om de lay-out normaal te laden Vernieuwen en opnieuw opstarten @@ -27,10 +26,10 @@ Verbergt de knop Toevoegen aan afspeellijst. Downloadknop verbergen Verbergt de downloadknop. - Deelknop verbergen - Verbergt de deelknop. Keuzerondje verbergen Verbergt het startkeuzerondje. + Deelknop verbergen + Verbergt de deelknop. Actieknoplabels verbergen Verbergt labels in actieknoppen. Downloadactieknop negeren @@ -48,7 +47,6 @@ Advertenties Advertenties op volledig scherm verbergen - "Verbergt advertenties op volledig scherm." Algemene advertenties verbergen Verbergt algemene advertenties. Verberg muziek advertenties @@ -118,8 +116,6 @@ Bekende problemen: • Als later uitgeschakeld kan de oude UI blijven totdat de appgegevens gewist worden" Spoof app versie doel Selecteer het doel van de spoof app versie - 4.27.53 - Radio modus uitschakelen in Canadese regio\'s - 6.11.52 - Realtime songteksten uitschakelen Navigatiebalk Home-knop verbergen @@ -138,16 +134,6 @@ Bekende problemen: Verberg labels in de navigatie balk. Speler - Schakel de volgende knop voor de minispeler in - Schakelt de volgende knop in de minispeler in. - Knop Vorige minispeler inschakelen - Schakelt de vorige knop in de minispeler in. - Kleuren overeenkomst van de speler inschakelen - Komt overeen met de kleur van de mini speler en de volschermspeler. - Forceer geminimaliseerde speler - Houd de speler permanent geminimaliseerd, zelfs als er een ander nummer wordt afgespeeld. - Schakel vegen in om de minispeler te sluiten - Hiermee kun je naar beneden vegen om de minispeler te sluiten. Zen-modus inschakelen Een grijze tint toevoegen aan de videospeler om vermoeidheid van de ogen te verminderen. Schakel de zen-modus in podcasts in @@ -158,8 +144,6 @@ Bekende problemen: Verbergt tijdstempel- en emoji-knoppen tijdens het typen van opmerkingen. Knop voor delen op volledig scherm verbergen Verbergt de deelknop in de speler op volledig scherm. - Verberg audio-videoschakelaar - Verbergt de audio-videoschakelaar in de speler. Herinner me de herhalingsstatus Onthoudt de status van de herhaling. Onthoud shuffle status diff --git a/patches/src/main/resources/music/translations/pl-rPL/strings.xml b/patches/src/main/resources/music/translations/pl-rPL/strings.xml index f8c99af58..eac228b1d 100644 --- a/patches/src/main/resources/music/translations/pl-rPL/strings.xml +++ b/patches/src/main/resources/music/translations/pl-rPL/strings.xml @@ -1,7 +1,7 @@ - ReVanced Extended + RVX Przywrócono domyślne wartości. Uruchom ponownie, aby wczytać układ poprawnie @@ -20,6 +20,8 @@ Ukrywa kontener warunków usług z menu konta. Pasek akcji + Zmień pozycję paska akcji + Przenosi pasek akcji pod przycisk odtwarzania. Ukryj przyciski łapki w górę i dół Ukrywa przyciski łapki w górę i dół. Nie będzie działać na starym układzie odtwarzacza. Ukryj przycisk komentarzy @@ -28,10 +30,13 @@ Ukrywa przycisk dodawania do playlisty. Ukryj przycisk pobierania Ukrywa przycisk pobierania. - Ukryj przycisk udostępniania - Ukrywa przycisk udostępniania. Ukryj przycisk radia Ukrywa przycisk radia. + Ukryj przycisk udostępniania + Ukrywa przycisk udostępniania. + Ukryj przycisk utwór/teledysk + "Ukrywa przycisk utwór/teledysk. +(Ten przycisk jest jedynie dostępny u niektórych użytkowników)" Ukryj nazwy przycisków akcji Ukrywa nazwy przycisków akcji. Zastąp przycisk od pobierania @@ -49,10 +54,8 @@ Pobierz %2$s ze strony." Reklamy Ukryj reklamy pełnoekranowe - "Ukrywa reklamy pełnoekranowe. - -Ograniczenie: -• Czasem może pojawić się pusty, czarny ekran zamiast strony głównej" + Ukrywa pełnoekranowe reklamy. + Zamknięto pełnoekranowe reklamy Ukryj ogólne reklamy Ukrywa ogólne reklamy. Ukryj reklamy multimedialne @@ -61,6 +64,7 @@ Ograniczenie: Ukrywa etykiety oznaczające płatne promocje. Ukryj wyskakujące okienka promocyjne Premium Ukrywa wyskakujące okienka promocyjne Premium. + Zamknięto wyskakujące okienka promocyjne Premium. Ukryj baner odnawiania Premium Ukrywa baner odnawiania Premium. Ukryj banery z alertami promocyjnymi @@ -92,6 +96,7 @@ Ograniczenia: Ukryj menu od przechodzenia do odcinka Ukryj menu od przechodzenia do podcastu Ukryj menu do pomocy i opinii + Ukryj menu od niezainteresowania Ukryj przypinanie do menu szybkiego wybierania Ukryj menu od odtwarzania jako następny Ukryj menu od jakości @@ -123,11 +128,17 @@ Ograniczenia: Ogólne Zmień stronę startową Wybierz, na której stronie ma otwierać się aplikacja. - Listy przebojów + Strona główna + Listy przebojów + Odcinki na później Odkrywaj - Strona główna + Historia Biblioteka - Subskrypcje + Muzyka, która Ci się podoba + Podcasty + Sample + Wyszukiwanie + Subskrypcje Wyłącz pomijanie nielubianych piosenek Wyłącza przenoszenie do następnego utworu po kliknięciu łapki w dół. Wyłącz automatyczne napisy @@ -176,8 +187,7 @@ Nie pomija to ograniczeń wiekowych, lecz akceptuje je automatycznie." • Jeśli potem opcja zostanie wyłączona, stary interfejs użytkownika może pozostać do momentu wyczyszczenia danych aplikacji." Docelowa wersja aplikacji Wybierz wersję, którą chcesz oszukiwać. - 4.27.53 - Wyłącza tryb radia w rejonach kanadyjskich - 6.11.52 - Wyłącza teksty w czasie rzeczywistym + 6.42.55 - Wyłącza teksty w czasie rzeczywistym 7.16.53 - Przywraca stary pasek akcji Pasek nawigacji @@ -202,22 +212,35 @@ Nie pomija to ograniczeń wiekowych, lecz akceptuje je automatycznie." Ukrywa nazwy każdego przycisku w pasku nawigacji. Odtwarzacz - Dodaj przycisk do następnej piosenki w miniodtwarzaczu - Dodaje przycisk do następnej piosenki w miniodtwarzaczu. - Dodaj przycisk do poprzedniej piosenki w miniodtwarzaczu - Dodaje przycisk do poprzedniej piosenki w miniodtwarzaczu. - Zmień kolor miniodtwarzacza - Zmienia kolor miniodtwarzacza na kolor będący w odtwarzaczu pełnoekranowym. - Zmień kolor tła odtwarzacza - Zmienia tło odtwarzacza na czarne. - Wyłącz gest w miniodtwarzaczu - Wyłącza gest przesuwania, aby zmienić utwór w miniodtwarzaczu. + Dodaj przycisk do następnego utworu w miniodtwarzaczu + Dodaje przycisk do następnego utworu w miniodtwarzaczu. + Dodaj przycisk do poprzedniego utworu w miniodtwarzaczu + Dodaje przycisk do poprzedniego utworu w miniodtwarzaczu. + Zmień kolor miniodtwarzacza + Zmienia kolor miniodtwarzacza na pasujący do odtwarzacza pełnoekranowego. + Zmień kolor tła odtwarzacza + Zmienia kolor tła odtwarzacza na własny. + Kolor pierwszorzędny + "Wpisz kod hex pierwszorzędnego koloru tła odtwarzacza. + +Jeśli to możliwe, użyj ciemnych kolorów, gdyż aplikacja nie obsługuje jasnych motywów." + Kolor drugorzędny + "Wpisz kod hex drugorzędnego koloru tła odtwarzacza. + +Jeśli to możliwe, użyj ciemnych kolorów, gdyż aplikacja nie obsługuje jasnych motywów." + Nieprawidłowy kolor tła odtwarzacza. + Zmień pozycję paska postępu utworu + Przenosi pasek postępu utworu pod przycisk odtwarzania. + Wyłącz gest miniodtwarzacza + Wyłącza gest przesunięcia, aby zmienić utwór. Wyłącz gest w odtwarzaczu Wyłącza gest przesuwania, aby zmienić utwór w odtwarzaczu. - Włącz wymuszenie zminimalizowanego odtwarzacza - Zostawia odtwarzacz zminimalizowany, nawet jeśli zostanie odtworzony inny utwór. - Włącz przesuwanie do zamykania miniodtwarzacza - Włącza przesuwanie w dół do zamykania miniodtwarzacza. + Włącz wymuszenie miniodtwarzacza + Zostawia odtwarzacz zminimalizowany, nawet jeśli zostanie odtworzony inny utwór. + Włącz przesunięcie, by zamknąć miniodtwarzacz + Włącza przesunięcie, by zamknąć miniodtwarzacz. + Włącz pogrubiony pasek postępu utworu + Włącza pogrubiony pasek postępu utworu. Włącz tryb zen Włącza jasnoszary kolor tła odtwarzacza, aby zmniejszyć zmęczenie oczu. Włącz tryb zen w podcastach @@ -230,8 +253,8 @@ Nie pomija to ograniczeń wiekowych, lecz akceptuje je automatycznie." Ukrywa przyciski czasu i emotikon podczas pisania komentarzy. Ukryj przycisk udostępniania w trybie pełnoekranowym Ukrywa przycisk udostępniania w trybie pełnoekranowym. - Ukryj przełącznik utwór-teledysk - Ukrywa przełącznik utwór-teledysk w odtwarzaczu. + Ukryj przełącznik utwór/teledysk + Ukrywa przełącznik utwór/teledysk w odtwarzaczu. Zapamiętaj stan pętli Zapisuje stan pętli. Zapamiętaj stan odtwarzania losowego @@ -401,8 +424,8 @@ Opcja znajduje oficjalny utwór, jeśli wykryje odtwarzanie teledysku z albumu. Typ przekierowania Określa, w jaki sposób przekierowuje do oficjalnego utworu Przekieruj - Kliknij przełącznik utwór-teledysk - Kliknij i przytrzymaj przełącznik utwór-teledysk + Kliknij przełącznik utwór/teledysk + Kliknij i przytrzymaj przełącznik utwór/teledysk Włącz logowanie debugowania Wyświetla log od debugowania. Logi do debugowania buforu @@ -416,27 +439,20 @@ Informacje: Oczyść udostępniane linki Usuwa parametry śledzących zapytań z adresów URL podczas udostępniania linków. Oszukuj klienta - "Oszukuj klienta, by zapobiec problemom z odtwarzaniem. - -※ Używane równolegle z opcją 'Oszukuj strumień danych', może powodować problemy z odtwarzaniem -" + Oszukuj klienta, by zapobiec problemom z odtwarzaniem. Domyślny klient - "Definiuje domyślnego klienta do oszukiwania. - -※ Jeśli używasz klienta Androida, zaleca się korzystanie razem z opcją 'Oszukuj wersję aplikacji'." + Definiuje domyślny klient do oszukiwania. Android Music 4.27.53 Android Music 5.29.53 iOS Music 6.21 - Oszukuj strumień danych - "Oszukuj strumień danych, by zapobiec problemom z odtwarzaniem. - -※ Używane równolegle z opcją 'Oszukuj klienta', może powodować problemy z odtwarzaniem" - Domyślny klient - Denifiuje domyślnego klienta, z którego będzie przechwytywany strumień danych. - Informacja w statystykach dla nerdów - Pokazuje używanego klienta do przechwytywania strumienia danych w statystykach dla nerdów. - Android VR - Android Music + iOS Music 7.04 + Typ historii oglądania + "• Oryginalny: Stosuje się do ustawień historii oglądania konta Google, lecz historia oglądania może nie działać przy używaniu DNS lub VPN +• Zastąpienie domeny: Stosuje się do ustawień historii oglądania konta Google +• Zablokowanie historii oglądania: Historia oglądania jest zablokowana" + Oryginalny + Zastąpienie domeny + Zablokowanie historii oglądania Otwórz systemowe ustawienia aplikacji Aby otwierać linki YouTube Music w RVX Music, przejdź do opcji obsługiwanych linków w ustawieniach i włącz obsługiwane adresy internetowe dla RVX Music. Otwórz ustawienia GmsCore diff --git a/patches/src/main/resources/music/translations/pt-rBR/strings.xml b/patches/src/main/resources/music/translations/pt-rBR/strings.xml index d46b912e1..cfd01eb3b 100644 --- a/patches/src/main/resources/music/translations/pt-rBR/strings.xml +++ b/patches/src/main/resources/music/translations/pt-rBR/strings.xml @@ -1,7 +1,7 @@ - ReVanced Extended + RVX Redefinir para os valores padrão. Reinicie para carregar o layout normalmente @@ -28,10 +28,10 @@ Oculta o botão Salvar. Ocultar botão Download Oculta o botão de Download. - Ocultar botão Compartilhar - Oculta botão de Compartilhar. Ocultar botão Rádio Oculta o botão de Rádio. + Ocultar botão Compartilhar + Oculta botão de Compartilhar. Ocultar rótulo do botão de ação Oculta os rótulos dos botões de ação. Substituir ação do botão de Download @@ -49,10 +49,6 @@ Por favor, baixe %2$s do site." Anúncios Ocultar anúncios em tela cheia - "Oculta anúncios de tela cheia. - -Limitações: -• Às vezes você pode ver uma tela preta em branco ao invés da fonte inicial." Ocultar anúncios gerais Oculta anúncios gerais. Ocultar anúncios de mídia @@ -123,11 +119,17 @@ Limitações: Geral Alterar a página inicial Selecione em qual página o aplicativo será aberto. - Paradas + Padrão + Paradas + Episódios para Mais Tarde Explorar - Início + Histórico Biblioteca - Inscrições + Músicas curtidas + Podcasts + Descobertas + Buscar + Inscrições Desativar redirecionamento de dislike Desativa o redirecionamento para a próxima faixa ao clicar no botão de Dislike. Desativar legendas automáticas @@ -176,8 +178,7 @@ Isso não ignora a restrição de idade, apenas aceita isso automaticamente." Versão da falsificação do aplicativo Selecione a versão do app para falsificação. - 4.27.53 - Desativar modo de Rádio em regiões Canadenses - 6.11.52 - Desativar letras em tempo real + 6.42.55 - Desativar letras em tempo real 7.16.53 - Restaurar barra de ação antiga Barra de Navegação @@ -202,22 +203,8 @@ Isso não ignora a restrição de idade, apenas aceita isso automaticamente."Oculta o rótulo abaixo de cada botão de navegação. Reprodutor - Adicionar o botão próximo no mini reprodutor - Adiciona o botão próximo no mini reprodutor. - Adicionar o botão anterior no mini reprodutor - Adiciona o botão anterior no mini reprodutor. - Alterar cor do mini reprodutor - Altera a cor do mini reprodutor para a cor do reprodutor em tela cheia. - Alterar cor de fundo do reprodutor - Altera a cor de fundo do reprodutor para preto. - Desativar gesto do mini reprodutor - Desativa a função de deslizar para alterar faixa no mini reprodutor. Desativar gesto do reprodutor Desativa a função de deslizar para alterar faixa no reprodutor. - Ativar mini reprodutor forçado - Ativa o mini reprodutor forçado ao alternar para uma nova faixa. - Ativar deslizar para dispensar o mini reprodutor - Permite deslizar para baixo para dispensar o mini reprodutor. Ativar modo Calmo Ativa uma cor cinza claro para o fundo do reprodutor para reduzir o cansaço visual. Ativar o modo Calmo em podcasts @@ -230,8 +217,6 @@ Isso não ignora a restrição de idade, apenas aceita isso automaticamente."Oculta os botões de emoji e marcação de tempo ao escrever comentários. Ocultar botão Compartilhar em tela cheia Oculta o botão Compartilhar no reprodutor de tela cheia. - Ocultar alternador de Música / Vídeo - Oculta o alternador de Música / Vídeo no reprodutor. Lembrar estado de repetição Lembra o estado da alternância de repetição. Lembrar estado do modo aleatório @@ -416,26 +401,20 @@ Informações: Limpar links compartilhados Limpa links de compartilhamento removendo parâmetros de consulta de rastreamento. Falsificar cliente - "Falsificar o cliente para evitar problemas de reprodução. - -• Quando usado com 'Dados de streaming falsos', podem ocorrer problemas de reprodução." + Falsificar o cliente para evitar problemas de reprodução. Cliente padrão - "Define um cliente padrão para falsificar. - -• Ao usar o cliente Android, é recomendado usar também 'Falsificar versão do aplicativo'." + Define um cliente padrão para falsificar. Android Music 4.27.53 Android Music 5.29.53 iOS Music 6.21 - Dados de streaming falsos - "Falsifique os dados de streaming para evitar problemas de reprodução. - -• Quando usado com 'Falsificar cliente', podem ocorrer problemas de reprodução." - Cliente padrão - Define um cliente padrão que busca dados de streaming. - Exibir em Estatísticas para nerds - Exibir o cliente usado para buscar dados de streaming em estatísticas para nerds. - Android VR - Android Music + iOS Music 7.04 + Tipo de histórico de exibição + "• Original: segue as configurações do histórico de exibição da conta do Google, mas o histórico de exibição pode não funcionar devido a DNS ou VPN. +• Substituir domínio: segue as configurações do histórico de exibição da conta do Google. +• Bloquear histórico de exibição: o histórico de exibição é bloqueado." + Original + Substituir domínio + Bloquear histórico de exibição Abrir configurações padrão do aplicativo Para abrir links do YouTube Music no RVX Music, ative Abrir links suportados e ative todos os endereços da web suportados. Abrir configurações do GmsCore diff --git a/patches/src/main/resources/music/translations/ro-rRO/strings.xml b/patches/src/main/resources/music/translations/ro-rRO/strings.xml index ce5df7023..d72ba8bf0 100644 --- a/patches/src/main/resources/music/translations/ro-rRO/strings.xml +++ b/patches/src/main/resources/music/translations/ro-rRO/strings.xml @@ -45,17 +45,12 @@ Probleme cunoscute: Ascunde bara de categorii de muzică din partea de sus a paginii principale. Ascunde butonul de istoric Ascunde butonul de istoric în bara de instrumente. - 4.27.53 - Dezactivare mod radio în regiunile canadiene Ascunde bara de navigare Ascunde bara de navigare. Ascunde eticheta de navigare Ascunde etichetele în bara de navigare. - Activare culori potrivire player - Potrivește culoarea mini player-ului și a player-ului pe tot ecranul. - Activare player minimizat forțat - Menține player-ul minimizat permanent chiar dacă o altă piesă este redată. Activare mod zen Adaugă o nuanță gri la player-ul video pentru a reduce oboseala ochilor. Memorează starea repetării diff --git a/patches/src/main/resources/music/translations/ru-rRU/strings.xml b/patches/src/main/resources/music/translations/ru-rRU/strings.xml index 5ffe9ad7a..1082e7151 100644 --- a/patches/src/main/resources/music/translations/ru-rRU/strings.xml +++ b/patches/src/main/resources/music/translations/ru-rRU/strings.xml @@ -1,7 +1,7 @@ - Настройки ReVanced Extended + RVX Сброшены до значений по умолчанию. Перезапустите для правильной загрузки интерфейса @@ -20,6 +20,8 @@ Скрывает пункт \"Конфиденциальность • Условия\". Панель действий + Изменить положение панели действий + Перемещает панель действий под кнопку воспроизведения. Скрыть кнопки \"Нравится\" и \"Не нравится\" Скрывает кнопки \"Нравится\" и \"Не нравится\". Не работает в старом интерфейсе проигрывателя. Скрыть кнопку \"Комментарии\" @@ -28,10 +30,13 @@ Скрывает кнопку \"Добавить в плейлист\". Скрыть кнопку \"Скачать\" Скрывает кнопку \"Скачать\". - Скрыть кнопку \"Поделиться\" - Скрывает кнопку \"Поделиться\". Скрыть кнопку \"Включить радиостанцию\" Скрывает кнопку \"Включить радиостанцию\". + Скрыть кнопку \"Поделиться\" + Скрывает кнопку \"Поделиться\". + Скрыть кнопку Песня / Видео + "Скрывает кнопку Песня/Видео. +(Эта кнопка доступна только некоторым пользователям)" Скрыть подписи кнопок действий Скрывает подписи на кнопках действий. Подменить кнопку \"Скачать\" @@ -49,7 +54,8 @@ Реклама Полноэкранная реклама - "Скрывает полноэкранную рекламу." + Скрывает полноэкранную рекламу. + Полноэкранная реклама закрыта. Скрыть рекламу общего формата Скрывает рекламу общего формата. Скрыть музыкальную рекламу @@ -58,6 +64,7 @@ Скрывает метку \"Содержит прямую рекламу\". Скрыть всплывающую рекламу Premium Скрывает всплывающую рекламу Premium. + Всплывающие окна покупки премиума закрыты. Скрыть баннер продления Premium Скрывает баннер продления Premium. Скрыть баннер с уведомлением о промо акции @@ -89,6 +96,7 @@ Скрыть пункт \"Перейти к выпуску\" Скрыть пункт \"Перейти к подкасту\" Скрыть пункт \"Справка и отзывы\" + Скрыть меню \"Не интересует\" Скрыть \"Закрепить в быстром выборе\" Скрыть пункт \"Включить следующим\" Скрыть пункт \"Качество\" @@ -120,11 +128,17 @@ Основные Изменить начальную страницу Выберите, на какой странице открывается приложение. - Хит-парады + По умолчанию + Хит-парады + Послушать позже Навигатор - Главная + История Библиотека - Подписки + Понравившаяся музыка + Подкасты + Сэмплы + Поиск + Подписки Отключить переключение при \"Не нравится\" Отключает перенаправление на следующий трек при нажатии на кнопку \"Не нравится\". Отключить автоматические субтитры @@ -173,8 +187,7 @@ • Если отключить данную опцию после её активации, старый интерфейс может оставаться до тех пор, пока данные приложения не будут очищены." Целевая версия приложения при подмене Выберите целевую версию приложения для подмены. - 4.27.53 - Отключить режим радиостанции в канадских регионах - 6.11.52 - Отключить динамические текста + 6.42.55 - Отключает динамических текстов 7.16.53 - Восстановить старую панель действий Панель навигации @@ -199,22 +212,35 @@ Скрывает подписи под кнопками навигации. Плеер - Включить кнопку следующего в миниплеере - Включает кнопку следующего трека в миниплеере. - Включить кнопку предыдущего в миниплеере - Включает кнопку предыдущего трека в миниплеере. - Цветовое соответствие проигрывателей - Цвет мини-проигрывателя соответствует цвету полноэкранного проигрывателя. - Включить черный фон плеера - Меняет адаптивный цвет фона плеера на черный. - Отключить жест мини плеера - Отключает свайп для переключения треков в миниплеере. + Добавить кнопку следующее в миниплеер + Добавляет кнопку следующего трека в миниплеер. + Добавить кнопку предыдущее в миниплеер + Добавляет кнопку предыдущего трека в миниплеер. + Изменить цвет миниплеера + Меняет цвет миниплеера на цвет полноэкранного плеера. + Изменить цвет фона плеера + Меняет цвет фона плеера на пользовательский. + Основной цвет фона плеера + "Введите hex код основного цвета фона плеера. + +По возможности используйте темные цвета, поскольку программа не поддерживает светлые темы." + Дополнительный цвет фона плеера + "Введите hex код дополнительного цвета фона плеера. + +По возможности используйте темные цвета, поскольку программа не поддерживает светлые темы." + Недопустимый цвет фона плеера. + Изменить положение панели прогресса + Перемещает панель прогресса под кнопку воспроизведения. + Отключить жесты миниплеера + Отключает жесты переключения треков в миниплеере. Отключить жест плеера Отключает свайп для переключения треков в плеере. - Удерживать проигрыватель свёрнутым - Удерживает проигрыватель свёрнутым, даже если играет другой трек. - Включить жест скрытия миниплеера - Включает жест вниз для скрытия миниплеера. + Включить миниплеер на постоянной основе + Включает миниплеер на постоянной основе при переходе на новый трек. + Включить жест закрытия миниплеера + Включает жест вниз для закрытия миниплеера. + Включить толстую панель прогресса + Включает толстую панель прогресса. Режим \"Дзен\" Меняет оттенок фона проигрывателя видео на светло-серый, чтобы уменьшить нагрузку на глаза. Включить режим \"Дзен\" в подкастах @@ -227,8 +253,8 @@ Скрывает кнопки метки времени и эмодзи при вводе комментариев. Скрыть кнопку \"Поделиться\" в полноэкранном режиме Скрывает кнопку \"Поделиться\" в полноэкранном проигрывателе. - Скрыть переключатель \"аудио/видео\" - Скрывает переключатель \"аудио/видео\" в плеере. + Скрыть переключатель \"песня | видео\" + Скрывает переключатель \"песня | видео\" в плеере. Запоминать состояние повтора Запоминает состояние переключателя \"Повтор воспроизведения\". Запоминать состояние перемешивания @@ -413,29 +439,20 @@ Подчищать ссылки Убирает параметры отслеживания запросов из адресов при отправке ссылки. Подмена клиента - "Подмена клиента для предотвращения проблем с воспроизведением. - -Ограничения: -• Кодек OPUS может не работать. -• Превью во время перемотки может отсутствовать. -• История просмотра не работает при использовании аккаунта компании." + Подмена клиента, чтобы предотвратить проблемы воспроизведения. Клиент по умолчанию - "Определяет клиент по умолчанию для подмены. - -※ При использовании клиента Android рекомендуется использовать его вместе с \"Подмена версии приложения\"." + Определяет клиент по умолчанию для подмены. Android Music 4.27.53 Android Music 5.29.53 iOS Music 6.21 - Подделка потоковых данных - "Подделать потоковые данные, чтобы предотвратить проблемы с воспроизведением. - -※ При использовании вместе с \"Подменой клиента\" могут возникнуть проблемы с воспроизведением." - Клиент по умолчанию - Определяет клиент по умолчанию, получающий потоковые данные. - Показывать в \"Статистике для сисадминов\" - Показывает клиент, используемый для получения потоковых данных в Статистике для сисадминов. - Android VR - Android Music + iOS Music 7.04 + Тип истории просмотра + "• Оригинальный: Соответствует настройкам истории просмотров аккаунта Google, но история просмотров может не работать через DNS или VPN. +• Заменить домен: Настройка истории просмотров аккаунта Google. +• Блокировка истории просмотров: История просмотров заблокирована." + Оригинальный + Заменить домен + Блокировать историю просмотров Открыть настройки по умолчанию Чтобы открыть ссылку на YouTube Music в RVX Music, включите \"Открывать поддерживаемые ссылки\" и включите поддерживаемые веб-адреса. Открыть GmsCore diff --git a/patches/src/main/resources/music/translations/tr-rTR/strings.xml b/patches/src/main/resources/music/translations/tr-rTR/strings.xml index 3ee2cee43..bc80077ec 100644 --- a/patches/src/main/resources/music/translations/tr-rTR/strings.xml +++ b/patches/src/main/resources/music/translations/tr-rTR/strings.xml @@ -1,7 +1,7 @@ - ReVanced Extended + RVX Varsayılan değerlere sıfırlayın. Düzeni normal şekilde yüklemek için yeniden başlatın @@ -28,10 +28,10 @@ Kaydet butonunu gizler. İndir butonunu gizle İndir butonunu gizler. - Paylaş butonunu gizle - Paylaş butonunu gizler. Radyo düğmesini gizle Radyo düğmesini gizler. + Paylaş butonunu gizle + Paylaş butonunu gizler. Aksiyon butonu etiketlerini gizle Aksiyon butonlarındaki etiketleri gizle. İndirme eylem düğmesini kullan @@ -49,10 +49,6 @@ Lütfen web sitesinden %2$s dosyasını indirin." Reklamlar Tam ekran reklamlarını gizle - "Tam ekran reklamları gizle. - -Sınırlamalar: -• Bazen ana akış yerine boş siyah bir ekran görebilirsiniz." Genel reklamları gizle Genel reklamları gizler. Müzik reklamlarını gizle @@ -121,11 +117,17 @@ Bilinen sorunlar: Genel Başlangıç ​​sayfasını değiştir Uygulamanın başlangıç sayfasını değiştirin. - Listeler + Varsayılan + Trendler + Daha sonra izle Keşfet - Ana Sayfa + Geçmiş Kitaplık - Abonelikler + Beğenilen Müzikler + Podcast\'ler + Örnekler + Arama + Abonelikler Beğenmeme yönlendirmesini devre dışı bırak Beğenmedim düğmesine tıklandığında sonraki parçaya yönlendirmeyi devre dışı bırakır. Altyazıların kendiliğinden açılmasını kapat @@ -162,7 +164,7 @@ Bilinen sorunlar: Güncellemek için tıkla butonunu gizler. Sesli arama düğmesini gizle Arama çubuğundaki sesli arama düğmesini gizler. - Eski stil kitaplık rafını geri getir + Eski stil kütüphane rafını geri getir Kütüphane rafını eski stile döndürün. (Deneysel) İzleyicinin takdirine bağlı iletişim kutusunu kaldır @@ -174,11 +176,15 @@ Bilinen sorunlar: • Daha sonra kapatılırsa, uygulama verileri temizlenene kadar eski kullanıcı arayüzü kalabilir." Uygulama hedef sürümünü kandırma hedefi Sahte uygulama sürümü hedefini seçin. - 4.27.53 - Kanada bölgelerinde radyo modunu devre dışı bırakın - 6.11.52 - Gerçek zamanlı şarkı sözlerini devre dışı bırak + 6.42.55 - Gerçek zamanlı şarkı sözlerini devre dışı bırak 7.16.53 - Eski eylem çubuğunu geri yükle Gezinti çubuğu + Özel gezinme çubuğu rengini etkinleştir + Gezinme çubuğu rengini ayarlar. + Özel gezinme çubuğu renk değeri + Gezinme çubuğu renginin hex kodunu yaz. + Geçersiz gezinme çubuğu renk değeri. Ana sayfa düğmesini gizle Ev düğmesini gizler. Örnekler düğmesini gizle @@ -195,22 +201,8 @@ Bilinen sorunlar: Gezinme çubuğunun altındaki etiketleri gizleyin. Oynatıcı - Mini oynatıcıyıdaki sonraki düğmesini etkinleştir - Mini oynatıcıda sonraki düğmesini etkinleştirir. - Mini oynatıcıdaki önceki düğmesini etkinleştir - Mini oynatıcıda önceki düğmesini etkinleştirir. - Oynatıcı renk eşlemesini etkinleştir - Küçültülmüş oynatıcının rengi ile tam ekran oynatıcının rengini eşler. - Siyah oynatıcı arka planını etkinleştir - Oynatıcının arka plan rengini siyaha değiştirir. - Mini oynatıcı hareketini devre dışı bırak - Mini oynatıcıdaki parçaları değiştirmek için kaydırmayı devre dışı bırakın. Oynatıcı hareketini devre dışı bırak Oynatıcıdaki parçaları değiştirmek için kaydırmayı devre dışı bırakın. - Zorla küçültülmüş pencereyi aktifleştir - Başka bir kayıt oynatılıyor ise oynatıcıyı tamamen küçült. - Mini oynatıcıyı kapatmak için kaydırmayı etkinleştirin - Mini oynatıcıyı kapatmak için aşağı kaydırmayı etkinleştirir. Zen modunu aç/kapa Video oynatıcıya açık gri bir ton ekleyerek göz yorgunluğunu azaltır. Podcastlerde zen modunu aç/kapa @@ -223,8 +215,6 @@ Bilinen sorunlar: Yorum yazarken zaman damgası ve emoji düğmelerini gizle. Tam ekran\'daki paylaş butonunu gizle Tam ekran oynatıcısındaki paylaş butonunu gizler. - Ses video geçiş anahtarını gizle - Oynatıcıdaki ses video geçiş anahtarını gizler. Tekrarın durumunu hatırlar Tekrarın durumunu hatırlar. Karıştır durumunu hatırlar @@ -390,6 +380,19 @@ APl anahtarının nasıl verileceğini görmek için tıklayın." Uygulama içi paylaşım sayfasını sistem paylaşım sayfasıyla değiştirin. Kahire açılış animasyonunu devre dışı bırak Uygulama açılışındaki Kahire açılış animasyonunu devre dışı bırakır. + Sabit sesi devre dışı bırak + Sese uygulanan Dinamik Aralık Sıkıştırmayı devre dışı bırakır. + Albümdeki müzik videosunu devre dışı bırak + "Premium olmayan bir kullanıcı bir albümde yer alan bir şarkıyı çaldığında, bazen resmi şarkı yerine müzik videosu çalınır. + +Bir albümden müzik videosu çalındığı algılanırsa resmi şarkıyı bulur. + +• Piped Instance API tarafından desteklenmektedir." + Yönlendirme türü + Resmi şarkıya nasıl yönlendireleceğini belirtir. + Yönlendirme + Şarkı / Video geçişi için dokunun + Şarkı / Video geçişi iççin basılı tutun Hata ayıklama günlüğünü etkinleştir Hata ayıklama günlüğünü yazdırır. Hata ayıklama günlüklerine arabelleği kaydet @@ -403,12 +406,20 @@ Bilgi: Paylaşılan bağlantıları sterilize edin Bağlantıları paylaşırken, tracking query parametrelerini URL\'lerden kaldırır. Sahte istemci - "\"Oynatma sorunlarını önlemek için istemciyi taklit edin. - -Sınırlamalar: -• OPUS ses kodeği desteklenmiyor olabilir. -• Arama çubuğu küçük resmi mevcut olmayabilir. -• İzleme geçmişi marka hesabında çalışmaz." + Oynatma sorunlarını çözmek için istemciyi taklit eder. + Varsayılan istemci + Taklit etmek için varsayılan bir istemci tanımlar. + Android Music 4.27.53 + Android Music 5.29.53 + iOS Music 6.21 + iOS Music 7.04 + İzleme geçmişi türü + "- Orijinal: Google hesabının izleme geçmişi ayarlarını takip eder, ancak izleme geçmişi DNS veya VPN nedeniyle çalışmayabilir. +- Alan adını değiştir: Google hesabının izleme geçmişi ayarlarını takip eder. +- İzleme geçmişini engelle: İzleme geçmişi engellenir." + Orijinal + Alan adını değiştir + İzleme geçmişini engelle Varsayılan uygulama ayarlarını aç YouTube Müzik bağlantılarını RVX Music\'te açmak için \'Desteklenen bağlantıları aç\'ı etkinleştirin ve desteklenen web adreslerini etkinleştirin. GmsCore\'yi aç diff --git a/patches/src/main/resources/music/translations/uk-rUA/strings.xml b/patches/src/main/resources/music/translations/uk-rUA/strings.xml index 3cb5e92ef..75193593f 100644 --- a/patches/src/main/resources/music/translations/uk-rUA/strings.xml +++ b/patches/src/main/resources/music/translations/uk-rUA/strings.xml @@ -1,7 +1,7 @@ - ReVanced Extended + RVX Скинуто до значень за замовчуванням. Перезапустіть, щоб нормально завантажився макет @@ -20,6 +20,8 @@ Приховує контейнер \"Конфіденційність • Умови використання\". Панель дій + Змінити положення панелі дій + Переміщує панель дій під кнопку відтворення. Приховати \"Подобається\" і \"Не подобається\" Приховує кнопки \"Подобається\" та \"Не подобається\". Це не працює в старому інтерфейсі плеєра. Приховати \"Коментарі\" @@ -28,10 +30,13 @@ Приховує кнопку \"Зберегти\" (в список відтворення) на панелі дій плеєра. Приховати \"Завантажити\" Приховує кнопку \"Завантажити\" на панелі дій плеєра. - Приховати \"Поділитися\" - Приховує кнопку \"Поділитися\" на панелі дій плеєра. Приховати \"Радіо\" Приховує кнопку \"Радіо\" на панелі дій плеєра. + Приховати \"Поділитися\" + Приховує кнопку \"Поділитися\" на панелі дій плеєра. + Приховати кнопку Пісня / Відео + "Приховує кнопку Пісня / Відео. +(Ця кнопка доступна лише деяким користувачам)" Приховати підписи панелі дій Приховує підписи кнопок на панелі дій плеєра. Підмінити \"Завантажити\" @@ -49,10 +54,8 @@ Реклама Приховати повноекранну рекламу - "Приховує повноекранну рекламу. - -Обмеження: -• Іноді замість головної стрічки ви можете побачити порожній чорний екран." + Приховує повноекранну рекламу. + Повноекранну рекламу закрито. Приховати загальну рекламу Приховує загальну рекламу. Приховати медіарекламу @@ -61,6 +64,7 @@ Приховує мітку \"Містить пряму рекламу\". Приховати спливаючу рекламу Premium Приховує спливаючі вікна реклами підписки. + Спливаючі вікна покупки преміум закрито. Приховати банер поновлення Premium Приховує банер поновлення підписки Music Premium. Приховати рекламні сповіщення @@ -92,6 +96,7 @@ Приховати \"Перейти до випуску\" Приховати \"Перейти до подкасту\" Приховати \"Довідка й відгуки\" + Приховати меню \"Не цікавить\" Приховати \"Закріпити в розділі Швидкий набір\" Приховати \"Відтворити наступним\" Приховати \"Якість\" @@ -123,11 +128,17 @@ Загальні Змінити початкову сторінку Виберіть сторінку з якої буде стартувати додаток. - Хіт-паради + За замовчуванням + Хіт-паради + Послухати пізніше Навігація - Головна + Історія Бібліотека - Підписка + Музика, яка сподобалась + Подкасти + Семпли + Пошук + Підписки Вимкнути перенаправлення при \"Не подобається\" Вимикає перенаправлення на наступний трек при натисканні на кнопку \"Не подобається\". Вимкнути примусові авто субтитри @@ -176,8 +187,7 @@ • Якщо пізніше вимкнути, старий інтерфейс може залишитися, доки не буде очищено дані програми." Підробити цільову версію програми Виберіть зі списку цільову версію підробки програми. - 4.27.53 - Вимкнення режиму радіо в канадських регіонах - 6.11.52 - Вимкнення динамічних текстів (караоке) + 6.42.55 - Вимкнення динамічних текстів 7.16.53 - Відновлення старої панелі дій Панель навігації @@ -202,22 +212,35 @@ Приховує підпис під кожною навігаційною кнопкою. Плеєр - Додати кнопку наступне у мініплеєр - Додає кнопку наступного треку у мініплеєр. - Додати кнопку попереднє у мініплеєр - Додає кнопку попереднього треку у мініплеєр. - Змінити колір мініплеєра - Змінює колір мініплеєра на колір повноекранного плеєра. - Змінити колір фону плеєра - Змінює адаптивний колір фона плеєра на чорний. - Вимкнути жести мініплеєра - Вимикає жести перемикання треків у мініплеєрі. + Додати кнопку наступне у мініплеєр + Додає кнопку наступного треку у мініплеєр. + Додати кнопку попереднє у мініплеєр + Додає кнопку попереднього треку у мініплеєр. + Змінити колір мініплеєра + Змінює колір мініплеєра на колір повноекранного плеєра. + Змінити колір фону плеєра + Змінює колір фона плеєра на користувацький. + Основний колір фону плеєра + "Введіть hex код основного кольору фону плеєра. + +По можливості використовуйте темні кольори, оскільки програма не підтримує світлі теми." + Вторинний колір фону плеєра + "Введіть hex код вторинного кольору фону плеєра. + +По можливості використовуйте темні кольори, оскільки програма не підтримує світлі теми." + Недійсний колір фону плеєра. + Змінити положення панелі прогресу + Переміщує панель прогресу під кнопку відтворення. + Вимкнути жести мініплеєра + Вимикає жести перемикання треків у мініплеєрі. Вимкнути жести плеєра Вимикає жести перемикання треків у плеєрі. - Увімкнути мініплеєр на постійній основі - Вмикає мініплеєр на постійній основі під час переходу на новий трек. - Увімкнути жест закриття мініплеєру - Вмикає жест вниз для закриття мініплеєру. + Увімкнути мініплеєр на постійній основі + Вмикає мініплеєр на постійній основі під час переходу на новий трек. + Увімкнути жест закриття мініплеєру + Вмикає жест вниз для закриття мініплеєру. + Увімкнути товсту панель прогресу + Вмикає товсту панель прогресу. Увімкнути режим \"Дзен\" Вмикає світло-сірий колір фону плеєра, щоб зменшити навантаження на очі. Увімкнути режим \"Дзен\" у подкастах @@ -230,8 +253,8 @@ Приховує кнопки емодзі та мітки часу під час введення коментарів. Приховати \"Поділитися\" повноекранного режиму Приховує кнопку \"Поділитися\" в повноекранному плеєрі. - Приховати перемикач \"пісня | відео\" - Приховує перемикач \"пісня | відео\" у плеєрі. + Приховати перемикач \"пісня | відео\" + Приховує перемикач \"пісня | відео\" у плеєрі. Запам\'ятовувати стан повтору Запам\'ятовує стан кнопки \"Повтор відтворення\". Запам\'ятовувати стан перемішування @@ -416,26 +439,20 @@ Обробляти поширення посилань Видаляє параметри запиту відстеження з посилання перед тим, як поділитися ним. Підміна клієнта - "Підробити клієнт, щоб запобігти проблемам із відтворенням. - -※ Під час використання разом з \"Підробка потокових даних\" можуть виникнути проблеми з відтворенням." + Підміна клієнта щоб запобігти проблемам відтворення. Клієнт за замовчуванням - "Визначає клієнт за замовчуванням для підробки. - -※ Під час використання клієнта Android рекомендується використовувати його разом з \"Підробити версію програми\"." + Визначає клієнт за замовчуванням для підробки. Android Music 4.27.53 Android Music 5.29.53 iOS Music 6.21 - Підробка потокових даних - "Підробити потокові дані, щоб запобігти проблемам із відтворенням. - -※ Під час використання разом з \"Підміною клієнта\" можуть виникнути проблеми з відтворенням." - Клієнт за замовчуванням - Визначає клієнт за замовчуванням, який отримує потокові дані. - Показувати у \"Статистиці для сисадмінів\" - Показує клієнт, який використовується для отримання потокових даних у \"Статистиці для сисадмінів\". - Android VR - Android Music + iOS Music 7.04 + Тип історії перегляду + "• Оригінальний: Відповідає налаштуванням історії переглядів облікового запису Google, але історія переглядів може не працювати через DNS або VPN. +• Замінити домен: Дотримується налаштувань історії переглядів облікового запису Google. +• Блокувати історію переглядів: Історію переглядів заблоковано." + Оригінальний + Замінити домен + Блокувати історію переглядів Відкрити налаштування за замовчуванням Щоб відкривати посилання на YouTube Music у RVX Music, увімкніть \"Відкривати підтримувані посилання\" та активуйте підтримувані веб-адреси. Відкрити налаштування GmsCore diff --git a/patches/src/main/resources/music/translations/vi-rVN/strings.xml b/patches/src/main/resources/music/translations/vi-rVN/strings.xml index 52647048e..837373ba8 100644 --- a/patches/src/main/resources/music/translations/vi-rVN/strings.xml +++ b/patches/src/main/resources/music/translations/vi-rVN/strings.xml @@ -1,7 +1,7 @@ - ReVanced Extended + RVX Đặt lại về giá trị mặc định. Vui lòng khởi động lại ứng dụng để các tính năng hoạt động bình thường @@ -19,21 +19,26 @@ Ẩn mục Bảo mật và Điều khoản Ẩn các mục Chính sách quyền riêng tư và Điều khoản dịch vụ khỏi trình đơn Tài khoản. - Thanh thao tác + Thanh tương tác + Thay đổi vị trí thanh tương tác + Di chuyển thanh tương tác xuống bên dưới nút Phát. Ẩn các nút Thích/Không thích - Ẩn nút Thích và nút Không thích.\n\nLưu ý: Tuỳ chọn này không hoạt động trong bố cục trình phát kiểu cũ. + Ẩn nút Thích và nút Không thích. Tuỳ chọn này không hoạt động trong bố cục trình phát kiểu cũ. Ẩn nút Bình luận - Ẩn nút Bình luận trong thanh thao tác. + Ẩn nút Bình luận trong thanh tương tác. Ẩn nút Lưu - Ẩn nút Lưu trong thanh thao tác. + Ẩn nút Lưu trong thanh tương tác. Ẩn nút Tải xuống - Ẩn nút Tải xuống trong thanh thao tác. - Ẩn nút Chia sẻ - Ẩn nút Chia sẻ trong thanh thao tác. + Ẩn nút Tải xuống trong thanh tương tác. Ẩn nút Đài phát - Ẩn nút Đài phát trong thanh thao tác. + Ẩn nút Đài phát trong thanh tương tác. + Ẩn nút Chia sẻ + Ẩn nút Chia sẻ trong thanh tương tác. + Ẩn nút chuyển đổi giữa Bài hát và Video + "Ẩn nút chuyển đổi giữa Bài hát và Video trên thanh tương tác. +(Nút này chỉ có sẵn với một số người dùng)" Ẩn tên nút - Ẩn tên nút trong thanh thao tác. + Ẩn tên nút trong thanh tương tác. Ghi đè nút tải xuống "Khi thao tác với nút tải xuống sẽ mở trình tải xuống bên ngoài của bạn. @@ -49,18 +54,17 @@ Quảng cáo Ẩn quảng cáo toàn màn hình - "Ẩn quảng cáo toàn màn hình. - -Hạn chế: -• Đôi khi mở thẻ Trang chủ, bạn chỉ thấy một màn hình màu đen." + Ẩn quảng cáo toàn màn hình. + Quảng cáo toàn màn hình đã đóng. Ẩn quảng cáo chung Ẩn quảng cáo xuất hiện trước khi phát. Ẩn quảng cáo Ẩn quảng cáo xuất hiện trong khi phát nhạc. Ẩn nhãn quảng cáo được tài trợ Ẩn nhãn quảng cáo được tài trợ. - Ẩn quảng cáo bật lên - Ẩn quảng cáo mua Music Premium bật lên. + Ẩn cửa sổ quảng cáo + Ẩn cửa sổ quảng cáo Premium. + Cửa sổ quảng cáo Premium đã đóng. Ẩn quảng cáo biểu ngữ Ẩn quảng cáo biểu ngữ mua Music Premium. Ẩn biểu ngữ thông báo khuyến mãi @@ -70,7 +74,7 @@ Hạn chế: Cắt bỏ khoảng lặng "Thêm tính năng Cắt bỏ khoảng lặng vào mục tuỳ chọn tốc độ phát. - Cụ thể: + Chi tiết: • Tính năng này dành cho podcast. • Tính năng này vẫn đang được phát triển nên có thể chưa ổn định." Trình đơn tuỳ chọn thu gọn @@ -92,6 +96,7 @@ Hạn chế: Ẩn mục Chuyển đến tập podcast Ẩn mục Chuyển đến podcast Ẩn mục Trợ giúp & phản hồi + Ẩn mục Không quan tâm Ẩn mục Ghim vào kệ Phát nhanh Ẩn mục Phát video tiếp theo Ẩn mục Chất lượng @@ -123,11 +128,17 @@ Hạn chế: Tổng quan Thay đổi trang khởi động Chọn trang sẽ hiển thị khi bạn khởi động ứng dụng. - Bảng xếp hạng + Mặc định + Bảng xếp hạng + Tập podcast để thưởng thức sau Khám phá - Trang chủ + Video đã xem Thư viện - Kênh đăng ký + Nhạc đã thích + Podcast + Đoạn nhạc + Tìm kiếm + Kênh đăng ký Tắt chuyển hướng khi nhấn nút Không thích Không chuyển đến bài hát tiếp theo khi nhấn vào nút Không thích. Tắt tự động hiển thị phụ đề @@ -176,8 +187,7 @@ Hạn chế: • Nếu tắt tuỳ chọn này sau đó, giao diện cũ có thể vẫn tồn tại cho đến khi bạn xoá dữ liệu ứng dụng." Phiên bản giả mạo Chọn phiên bản YouTube Music mà bạn muốn giả mạo. - 4.27.53 - Tắt chế độ Đài phát ở một số vùng của Canada - 6.11.52 - Tắt lời bài hát theo thời gian thực + 6.42.55 - Tắt lời bài hát theo thời gian thực 7.16.53 - Khôi phục thanh thao tác kiểu cũ Thanh điều hướng @@ -202,22 +212,35 @@ Hạn chế: Ẩn tên bên dưới các nút trên thanh điều hướng. Trình phát - Thêm nút tiếp theo vào trình phát thu nhỏ - Thêm nút bài hát tiếp theo vào trình phát thu nhỏ. - Thêm nút trước đó vào trình phát thu nhỏ - Thêm nút bài hát trước đó vào trình phát thu nhỏ. - Thay đổi màu của trình phát thu nhỏ - Thay đổi màu của trình phát thu nhỏ khớp với màu của trình phát toàn màn hình. - Thay đổi màu nền của trình phát - Thay đổi màu nền trình phát thành màu đen. - Tắt cử chỉ trình phát thu nhỏ - Tắt vuốt để chuyển bài hát trong trình phát thu nhỏ. + Nút tiếp theo trong trình phát thu nhỏ + Thêm nút bài hát tiếp theo vào trình phát thu nhỏ. + Nút trước đó trong trình phát thu nhỏ + Thêm nút bài hát trước đó vào trình phát thu nhỏ. + Thay đổi màu của trình phát thu nhỏ + Thay đổi màu của trình phát thu nhỏ khớp với màu của trình phát toàn màn hình. + Thay đổi màu nền của trình phát + Thay đổi màu nền trình phát sang màu sắc mà bạn muốn. + Màu nền chính cho trình phát + "Nhập mã hex của màu nền chính mà bạn muốn thay đổi cho trình phát. + +Nếu có thể, hãy sử dụng màu tối vì ứng dụng không hỗ trợ giao diện sáng." + Màu nền phụ cho trình phát + "Nhập mã hex của màu nền phụ mà bạn muốn thay đổi cho trình phát. + +Nếu có thể, hãy sử dụng màu tối vì ứng dụng không hỗ trợ giao diện sáng." + Màu nền của trình phát mà bạn đã nhập không hợp lệ. + Thay đổi vị trí thanh tiến trình + Di chuyển thanh tiến trình xuống bên dưới nút Phát. + Tắt cử chỉ trình phát thu nhỏ + Tắt vuốt để chuyển bài hát trong trình phát thu nhỏ. Tắt cử chỉ trình phát Tắt vuốt để chuyển bài hát trong trình phát. - Luôn phát trong trình phát thu nhỏ - Luôn phát nhạc trong trình phát thu nhỏ bất cứ khi nào bạn nghe một bài hát nằm ngoài trình phát hoặc bắt đầu đài phát. - Vuốt để đóng trình phát thu nhỏ - Vuốt xuống để đóng trình phát thu nhỏ. + Luôn phát trong trình phát thu nhỏ + Luôn phát nhạc trong trình phát thu nhỏ bất cứ khi nào bạn nghe một bài hát mới. + Vuốt để đóng trình phát thu nhỏ + Vuốt xuống để đóng trình phát thu nhỏ. + Thanh tiến trình dày + Bật thanh tiến trình dày. Chế độ tập trung Thay đổi nền của trình phát thành màu xám nhạt để giúp mắt của bạn thư giãn hơn. Chế độ tập trung cho Podcast @@ -230,8 +253,8 @@ Hạn chế: Ẩn biểu tượng cảm xúc và nút dấu thời gian khi đang nhập bình luận. Ẩn nút Chia sẻ trong trình phát toàn màn hình Ẩn nút Chia sẻ trong trình phát toàn màn hình. - Ẩn nút chuyển đổi giữa Bài hát và Video - Ẩn nút chuyển đổi giữa Bài hát và Video trong trình phát. + Ẩn công tắc chuyển đổi giữa Bài hát và Video + Ẩn công tắc chuyển đổi giữa Bài hát và Video trong trình phát. Lưu trạng thái phát lặp lại Ghi nhớ trạng thái phát lặp lại một danh sách phát hoặc phát lặp lại một bài hát. Lưu trạng thái phát ngẫu nhiên @@ -410,32 +433,26 @@ Trong trường hợp đó, hệ thống sẽ tìm và chuyển hướng tới b Codec OPUS "Áp dụng codec OPUS nếu phản hồi của trình phát bao gồm nó. -Cụ thể: +Chi tiết: • Các phiên bản YouTube Music mới nhất sử dụng codec OPUS như mặc định. • Điều này chỉ áp dụng cho người dùng giả mạo với các phiên bản ứng dụng rất cũ." Liên kết sạch khi chia sẻ Loại bỏ các tham số truy vấn theo dõi khỏi URL khi chia sẻ liên kết. Giả mạo ứng dụng khách - "Giả mạo ứng dụng khách nhằm khắc phục sự cố phát. - -• Khi được sử dụng đồng thời với \"Giả mạo luồng dữ liệu trực tuyến\", có thể gây ra sự cố phát." + Giả mạo ứng dụng khách khách để ngăn chặn sự cố phát. Ứng dụng khách mặc định - "Xác định ứng dụng khách mặc định để giả mạo. - -• Khi sử dụng ứng dụng khách Android, tính năng này nên được sử dụng đồng thời với \"Giả mạo phiên bản ứng dụng\"." + Xác định ứng dụng khách mặc định để giả mạo. Android Music 4.27.53 Android Music 5.29.53 - Music iOS 6.21 - Giả mạo luồng dữ liệu trực tuyến - "Giả mạo luồng dữ liệu trực tuyến nhằm khắc phục sự cố phát. - -• Khi được sử dụng đồng thời với \"Giả mạo ứng dụng khách\", có thể gây ra sự cố phát." - Ứng dụng khách mặc định - Xác định một ứng dụng khách mặc định để nạp luồng dữ liệu trực tuyến. - Hiển thị trong Thống kê chi tiết - Hiển thị ứng dụng khách được sử dụng để nạp luồng dữ liệu trực tuyến trong Thống kê chi tiết. - Android VR - Android Music + iOS Music 6.21 + iOS Music 7.04 + Kiểu nhật ký + "• Gốc: Tuân theo cài đặt nhật ký xem của tài khoản Google, nhưng có thể không hoạt động do DNS hoặc VPN. +• Thay thế miền: Vẫn tuân theo cài đặt nhật ký xem của tài khoản Google. +• Chặn nhật ký: Nhật ký xem bị chặn hoàn toàn." + Gốc + Thay thế miền + Chặn nhật ký Mở theo mặc định Để mở liên kết YouTube Music trong RVX Music, hãy kích hoạt \"Mở các đường liên kết được hỗ trợ\" và thêm các đường liên kết được hỗ trợ. Cài đặt GmsCore diff --git a/patches/src/main/resources/music/translations/zh-rCN/strings.xml b/patches/src/main/resources/music/translations/zh-rCN/strings.xml index 9b30f988b..430556b98 100644 --- a/patches/src/main/resources/music/translations/zh-rCN/strings.xml +++ b/patches/src/main/resources/music/translations/zh-rCN/strings.xml @@ -1,7 +1,6 @@ - ReVanced Extended 重启应用以正常加载界面布局 刷新并重启 @@ -27,10 +26,10 @@ 隐藏添加到播放列表按钮 隐藏下载按钮 隐藏下载按钮 - 隐藏分享按钮 - 隐藏分享按钮 隐藏电台按钮 隐藏开启电台按钮 + 隐藏分享按钮 + 隐藏分享按钮 隐藏操作按钮标签 隐藏操作按钮中的标签 覆盖下载操作按钮 @@ -48,7 +47,6 @@ 广告 隐藏全屏广告 - "隐藏全屏广告" 隐藏一般广告 隐藏一般广告 音乐广告 @@ -115,11 +113,8 @@ 常规设置 更改起始页面 选择打开应用显示的页面 - 图表 探索 - 首页 媒体库 - 订阅 禁用不喜欢重定向 点击不喜欢按钮时禁用重定向到下一曲目 禁用自动字幕 @@ -168,8 +163,6 @@ ・如果关掉该项,可能仍然保留旧版界面,清除应用数据以解决该问题" 伪装应用版本 选择伪装的应用版本 - 4.27.53 - 在加拿大地区禁用收音机模式 - 6.11.52 - 禁用实时歌词 导航栏 隐藏首页按钮 @@ -188,18 +181,6 @@ 隐藏导航栏标签 播放器 - 启用迷你播放器的下一首按钮 - 启用迷你播放器的下一首按钮 - 启用迷你播放器的上一首按钮 - 启用迷你播放器的上一首按钮 - 匹配播放器颜色 - 使导航栏播放器与全屏播放器颜色一致. - 启用黑色播放器背景 - 将播放器背景颜色更改为黑色 - 强制最小化播放器 - 保持播放器最小化,即使播放另一首曲目 - 启用滑动以关闭迷你播放器 - 启用向下滑动以关闭迷你播放器 禅定模式 在视频播放器上添加灰色阴影以减少眼睛疲劳 在播客中启用 Zen 模式 @@ -211,8 +192,6 @@ 输入评论时隐藏时间戳和表情符号按钮 隐藏全屏分享按钮 隐藏全屏播放器中的分享按钮 - 隐藏音频/视频开关 - 隐藏播放器中的音频/视频开关 记住重复播放状态 记住重复播放状态 记住随机播放状态 diff --git a/patches/src/main/resources/music/translations/zh-rTW/strings.xml b/patches/src/main/resources/music/translations/zh-rTW/strings.xml index 3b261f8e4..bcd3a8651 100644 --- a/patches/src/main/resources/music/translations/zh-rTW/strings.xml +++ b/patches/src/main/resources/music/translations/zh-rTW/strings.xml @@ -1,7 +1,6 @@ - ReVanced 擴充功能 重設為預設值。 重新啟動以套用更改後的介面 @@ -28,10 +27,10 @@ 隱藏\"新增至播放列表\"按鈕 隱藏下載按鈕 隱藏下載按鈕 - 隱藏分享按鈕 - 隱藏分享按鈕 隱藏電台按鈕 隱藏開啟電台按鈕 + 隱藏分享按鈕 + 隱藏分享按鈕 隱藏操作按鈕中的標籤 隱藏操作按鈕中的標籤 覆蓋下載動作按鈕 @@ -49,7 +48,6 @@ 廣告 隱藏全螢幕廣告 - "隱藏全螢幕廣告" 隱藏一般廣告 隱藏一般廣告 隱藏音樂廣告 @@ -120,11 +118,8 @@ 一般設定 更改應用程式的起始頁面 更改應用程式開啟時的頁面 - 圖表 探索 - 首頁 媒體庫 - 訂閱 停用不喜歡重定向 點擊不喜歡按鈕時停用重定向到下一首歌曲 停用強制自動字幕 @@ -174,8 +169,6 @@ • 如果稍後停用,舊的 UI 可能會保留,直到應用程式資料被清除" 偽裝應用程式版本 選擇欲偽裝的應用程式版本 - 4.27.53 - 在加拿大地區停用電台模式 - 6.11.52 - 停用即時歌詞 7.16.53 - 還原舊的動作欄 導覽列 @@ -195,22 +188,8 @@ 隱藏位於導覽列的標籤 播放器 - 啟用迷你播放器的下一首按鈕 - 啟用迷你播放器中的下一首按鈕 - 啟用迷你播放器上一首按鈕 - 啟用迷你播放器中的上一首按鈕 - 啟用彩色播放列 - 讓播放列顏色和全螢幕播放器一致 - 啟用黑色播放器背景 - 將播放器背景顏色設成為黑色 - 停用迷你播放器手勢 - 停用迷你播放器中的滑動切換曲目功能。 停用播放器手勢 停用播放器中的滑動切換歌曲。 - 切換歌曲時保持迷你播放器 - 切換歌曲時保持迷你播放器狀態 - 啟用滑動來關閉迷你播放器 - 允許向下滑動以關閉迷你播放器 護眼模式 在影片播放器上增加灰色陰影以減少眼睛疲勞 在播客中啟用護眼模式 @@ -223,8 +202,6 @@ 輸入留言時隱藏時間戳記和表情符號按鈕 隱藏全螢幕播放器中的分享按鈕 在全螢幕播放器中隱藏分享按鈕 - 隱藏音訊影片切換開關 - 在播放器中隱藏音訊影片切換開關 記住重複播放狀態 記住重複播放的狀態 記住隨機播放狀態 @@ -393,21 +370,10 @@ 清理分享連結 分享連結時從 URL 中刪除追蹤參數。 偽裝客戶端 - "偽裝客戶端以避免播放問題 - -• 使用「偽裝串流資料」時可能會發生播放問題" 預設客戶端 Android Music 4.27.53 Android Music 5.29.53 iOS Music 6.21 - 偽裝串流數據 - "偽裝客戶端以避免播放問題 - -• 使用「偽裝串流資料」時可能會發生播放問題" - 預設客戶端 - 定義預設客戶端以抓取串流資料。 - Android VR - Android Music 開啟預設應用程式設定 要在RVX Music中打開YouTube Music連結,啟用「打開支援的連結」並啟用支援的網址。 開啟 GmsCore 設定 diff --git a/patches/src/main/resources/youtube/livering/host/values-af/strings.xml b/patches/src/main/resources/youtube/livering/host/values-af/strings.xml new file mode 100644 index 000000000..6ca517dfe --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-af/strings.xml @@ -0,0 +1,4 @@ + + + Tap to watch live + diff --git a/patches/src/main/resources/youtube/livering/host/values-am/strings.xml b/patches/src/main/resources/youtube/livering/host/values-am/strings.xml new file mode 100644 index 000000000..0172d10e0 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-am/strings.xml @@ -0,0 +1,4 @@ + + + በቀጥታ ስርጭት ለመመልከት መታ ያድርጉ + diff --git a/patches/src/main/resources/youtube/livering/host/values-ar/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ar/strings.xml new file mode 100644 index 000000000..baf61a49d --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ar/strings.xml @@ -0,0 +1,4 @@ + + + انقر لمشاهدة البث المباشر. + diff --git a/patches/src/main/resources/youtube/livering/host/values-as/strings.xml b/patches/src/main/resources/youtube/livering/host/values-as/strings.xml new file mode 100644 index 000000000..57b468160 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-as/strings.xml @@ -0,0 +1,4 @@ + + + লাইভ চাবলৈ টিপক + diff --git a/patches/src/main/resources/youtube/livering/host/values-az/strings.xml b/patches/src/main/resources/youtube/livering/host/values-az/strings.xml new file mode 100644 index 000000000..efae04d83 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-az/strings.xml @@ -0,0 +1,4 @@ + + + Canlı baxmaq üçün toxunun + diff --git a/patches/src/main/resources/youtube/livering/host/values-b+sr+Latn/strings.xml b/patches/src/main/resources/youtube/livering/host/values-b+sr+Latn/strings.xml new file mode 100644 index 000000000..b3fe94f5b --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-b+sr+Latn/strings.xml @@ -0,0 +1,4 @@ + + + Dodirnite da biste gledali uživo + diff --git a/patches/src/main/resources/youtube/livering/host/values-be/strings.xml b/patches/src/main/resources/youtube/livering/host/values-be/strings.xml new file mode 100644 index 000000000..d0ddf14e8 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-be/strings.xml @@ -0,0 +1,4 @@ + + + Націсніце, каб глядзець жывую трансляцыю + diff --git a/patches/src/main/resources/youtube/livering/host/values-bg/strings.xml b/patches/src/main/resources/youtube/livering/host/values-bg/strings.xml new file mode 100644 index 000000000..3bf1eacaa --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-bg/strings.xml @@ -0,0 +1,4 @@ + + + Докоснете, за да гледате на живо + diff --git a/patches/src/main/resources/youtube/livering/host/values-bn/strings.xml b/patches/src/main/resources/youtube/livering/host/values-bn/strings.xml new file mode 100644 index 000000000..9fd923abf --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-bn/strings.xml @@ -0,0 +1,4 @@ + + + লাইভ দেখতে ট্যাপ করুন + diff --git a/patches/src/main/resources/youtube/livering/host/values-bs/strings.xml b/patches/src/main/resources/youtube/livering/host/values-bs/strings.xml new file mode 100644 index 000000000..3ffcb7d32 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-bs/strings.xml @@ -0,0 +1,4 @@ + + + Dodirnite da gledate uživo + diff --git a/patches/src/main/resources/youtube/livering/host/values-ca/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ca/strings.xml new file mode 100644 index 000000000..bc23bf2c4 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ca/strings.xml @@ -0,0 +1,4 @@ + + + "Toca per veure l'emissió en directe" + diff --git a/patches/src/main/resources/youtube/livering/host/values-cs/strings.xml b/patches/src/main/resources/youtube/livering/host/values-cs/strings.xml new file mode 100644 index 000000000..d9a94b6b0 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-cs/strings.xml @@ -0,0 +1,4 @@ + + + Klepnutím spustíte živý přenos + diff --git a/patches/src/main/resources/youtube/livering/host/values-da/strings.xml b/patches/src/main/resources/youtube/livering/host/values-da/strings.xml new file mode 100644 index 000000000..4a0e0f4f3 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-da/strings.xml @@ -0,0 +1,4 @@ + + + Tryk for at se live + diff --git a/patches/src/main/resources/youtube/livering/host/values-de/strings.xml b/patches/src/main/resources/youtube/livering/host/values-de/strings.xml new file mode 100644 index 000000000..a4c33a28c --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-de/strings.xml @@ -0,0 +1,4 @@ + + + Zum live Ansehen tippen + diff --git a/patches/src/main/resources/youtube/livering/host/values-el/strings.xml b/patches/src/main/resources/youtube/livering/host/values-el/strings.xml new file mode 100644 index 000000000..e77e174ba --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-el/strings.xml @@ -0,0 +1,4 @@ + + + Πατήστε για να παρακολουθήσετε ζωντανά + diff --git a/patches/src/main/resources/youtube/livering/host/values-en-rGB/strings.xml b/patches/src/main/resources/youtube/livering/host/values-en-rGB/strings.xml new file mode 100644 index 000000000..6ca517dfe --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-en-rGB/strings.xml @@ -0,0 +1,4 @@ + + + Tap to watch live + diff --git a/patches/src/main/resources/youtube/livering/host/values-en-rIN/strings.xml b/patches/src/main/resources/youtube/livering/host/values-en-rIN/strings.xml new file mode 100644 index 000000000..6ca517dfe --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-en-rIN/strings.xml @@ -0,0 +1,4 @@ + + + Tap to watch live + diff --git a/patches/src/main/resources/youtube/livering/host/values-es-rUS/strings.xml b/patches/src/main/resources/youtube/livering/host/values-es-rUS/strings.xml new file mode 100644 index 000000000..d38bf91c4 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-es-rUS/strings.xml @@ -0,0 +1,4 @@ + + + Presiona para ver la transmisión en vivo + diff --git a/patches/src/main/resources/youtube/livering/host/values-es/strings.xml b/patches/src/main/resources/youtube/livering/host/values-es/strings.xml new file mode 100644 index 000000000..622953775 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-es/strings.xml @@ -0,0 +1,4 @@ + + + Toca para ver en directo + diff --git a/patches/src/main/resources/youtube/livering/host/values-et/strings.xml b/patches/src/main/resources/youtube/livering/host/values-et/strings.xml new file mode 100644 index 000000000..94062bf0c --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-et/strings.xml @@ -0,0 +1,4 @@ + + + Puudutage, et otse vaadata + diff --git a/patches/src/main/resources/youtube/livering/host/values-eu/strings.xml b/patches/src/main/resources/youtube/livering/host/values-eu/strings.xml new file mode 100644 index 000000000..761242b43 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-eu/strings.xml @@ -0,0 +1,4 @@ + + + Sakatu hau bideoa zuzenean ikusteko + diff --git a/patches/src/main/resources/youtube/livering/host/values-fa/strings.xml b/patches/src/main/resources/youtube/livering/host/values-fa/strings.xml new file mode 100644 index 000000000..03704a512 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-fa/strings.xml @@ -0,0 +1,4 @@ + + + برای تماشای زنده تک‌ضرب بزنید + diff --git a/patches/src/main/resources/youtube/livering/host/values-fi/strings.xml b/patches/src/main/resources/youtube/livering/host/values-fi/strings.xml new file mode 100644 index 000000000..44e145be3 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-fi/strings.xml @@ -0,0 +1,4 @@ + + + Napauta katsoaksesi livenä + diff --git a/patches/src/main/resources/youtube/livering/host/values-fr-rCA/strings.xml b/patches/src/main/resources/youtube/livering/host/values-fr-rCA/strings.xml new file mode 100644 index 000000000..6eeb17b9f --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-fr-rCA/strings.xml @@ -0,0 +1,4 @@ + + + Touchez pour regarder en direct + diff --git a/patches/src/main/resources/youtube/livering/host/values-fr/strings.xml b/patches/src/main/resources/youtube/livering/host/values-fr/strings.xml new file mode 100644 index 000000000..db46b5e9c --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-fr/strings.xml @@ -0,0 +1,4 @@ + + + Appuyer pour regarder en direct + diff --git a/patches/src/main/resources/youtube/livering/host/values-gl/strings.xml b/patches/src/main/resources/youtube/livering/host/values-gl/strings.xml new file mode 100644 index 000000000..f3b177da1 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-gl/strings.xml @@ -0,0 +1,4 @@ + + + Toca para velo en directo + diff --git a/patches/src/main/resources/youtube/livering/host/values-gu/strings.xml b/patches/src/main/resources/youtube/livering/host/values-gu/strings.xml new file mode 100644 index 000000000..eaef567ed --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-gu/strings.xml @@ -0,0 +1,4 @@ + + + લાઇવ જોવા માટે ટૅપ કરો + diff --git a/patches/src/main/resources/youtube/livering/host/values-hi/strings.xml b/patches/src/main/resources/youtube/livering/host/values-hi/strings.xml new file mode 100644 index 000000000..41a3c8fab --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-hi/strings.xml @@ -0,0 +1,4 @@ + + + लाइव वीडियो देखने के लिए टैप करें + diff --git a/patches/src/main/resources/youtube/livering/host/values-hr/strings.xml b/patches/src/main/resources/youtube/livering/host/values-hr/strings.xml new file mode 100644 index 000000000..1c49f26af --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-hr/strings.xml @@ -0,0 +1,4 @@ + + + Dodirnite za gledanje uživo + diff --git a/patches/src/main/resources/youtube/livering/host/values-hu/strings.xml b/patches/src/main/resources/youtube/livering/host/values-hu/strings.xml new file mode 100644 index 000000000..e47840e9a --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-hu/strings.xml @@ -0,0 +1,4 @@ + + + Koppints és nézd élőben + diff --git a/patches/src/main/resources/youtube/livering/host/values-hy/strings.xml b/patches/src/main/resources/youtube/livering/host/values-hy/strings.xml new file mode 100644 index 000000000..c033afec6 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-hy/strings.xml @@ -0,0 +1,4 @@ + + + "Հպեք՝ ուղիղ եթերը դիտելու համար" + diff --git a/patches/src/main/resources/youtube/livering/host/values-in/strings.xml b/patches/src/main/resources/youtube/livering/host/values-in/strings.xml new file mode 100644 index 000000000..96235b9ba --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-in/strings.xml @@ -0,0 +1,4 @@ + + + Ketuk untuk menonton live + diff --git a/patches/src/main/resources/youtube/livering/host/values-is/strings.xml b/patches/src/main/resources/youtube/livering/host/values-is/strings.xml new file mode 100644 index 000000000..9667f8e00 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-is/strings.xml @@ -0,0 +1,4 @@ + + + Ýttu til að horfa í beinni + diff --git a/patches/src/main/resources/youtube/livering/host/values-it/strings.xml b/patches/src/main/resources/youtube/livering/host/values-it/strings.xml new file mode 100644 index 000000000..b540cebb1 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-it/strings.xml @@ -0,0 +1,4 @@ + + + Tocca per guardare dal vivo + diff --git a/patches/src/main/resources/youtube/livering/host/values-iw/strings.xml b/patches/src/main/resources/youtube/livering/host/values-iw/strings.xml new file mode 100644 index 000000000..f7b480810 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-iw/strings.xml @@ -0,0 +1,4 @@ + + + אפשר ללחוץ כדי לצפות בשידור החי + diff --git a/patches/src/main/resources/youtube/livering/host/values-ja/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ja/strings.xml new file mode 100644 index 000000000..41a04038b --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ja/strings.xml @@ -0,0 +1,4 @@ + + + タップしてライブを視聴 + diff --git a/patches/src/main/resources/youtube/livering/host/values-ka/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ka/strings.xml new file mode 100644 index 000000000..4cde4a77d --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ka/strings.xml @@ -0,0 +1,4 @@ + + + შეეხეთ პირდაპირ ეთერში საყურებლად + diff --git a/patches/src/main/resources/youtube/livering/host/values-kk/strings.xml b/patches/src/main/resources/youtube/livering/host/values-kk/strings.xml new file mode 100644 index 000000000..7f3e87a29 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-kk/strings.xml @@ -0,0 +1,4 @@ + + + Тікелей эфирді көру үшін түртіңіз + diff --git a/patches/src/main/resources/youtube/livering/host/values-km/strings.xml b/patches/src/main/resources/youtube/livering/host/values-km/strings.xml new file mode 100644 index 000000000..d37d12dd1 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-km/strings.xml @@ -0,0 +1,4 @@ + + + ចុចដើម្បីមើលការផ្សាយផ្ទាល់ + diff --git a/patches/src/main/resources/youtube/livering/host/values-kn/strings.xml b/patches/src/main/resources/youtube/livering/host/values-kn/strings.xml new file mode 100644 index 000000000..fdd8b3381 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-kn/strings.xml @@ -0,0 +1,4 @@ + + + ಲೈವ್ ನೋಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ + diff --git a/patches/src/main/resources/youtube/livering/host/values-ko/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ko/strings.xml new file mode 100644 index 000000000..dd91e88cf --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ko/strings.xml @@ -0,0 +1,4 @@ + + + 탭하여 실시간으로 시청하기 + diff --git a/patches/src/main/resources/youtube/livering/host/values-ky/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ky/strings.xml new file mode 100644 index 000000000..7ffa9a175 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ky/strings.xml @@ -0,0 +1,4 @@ + + + Түз ободо көрүү үчүн таптап коюңуз + diff --git a/patches/src/main/resources/youtube/livering/host/values-lo/strings.xml b/patches/src/main/resources/youtube/livering/host/values-lo/strings.xml new file mode 100644 index 000000000..f95632e5f --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-lo/strings.xml @@ -0,0 +1,4 @@ + + + ແຕະເພື່ອເບິ່ງການຖ່າຍທອດສົດ + diff --git a/patches/src/main/resources/youtube/livering/host/values-lt/strings.xml b/patches/src/main/resources/youtube/livering/host/values-lt/strings.xml new file mode 100644 index 000000000..3d6729d19 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-lt/strings.xml @@ -0,0 +1,4 @@ + + + Palieskite ir žiūrėkite tiesiogiai + diff --git a/patches/src/main/resources/youtube/livering/host/values-lv/strings.xml b/patches/src/main/resources/youtube/livering/host/values-lv/strings.xml new file mode 100644 index 000000000..11be1855c --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-lv/strings.xml @@ -0,0 +1,4 @@ + + + Pieskarieties, lai skatītos tiešraidi + diff --git a/patches/src/main/resources/youtube/livering/host/values-mk/strings.xml b/patches/src/main/resources/youtube/livering/host/values-mk/strings.xml new file mode 100644 index 000000000..b35f7e184 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-mk/strings.xml @@ -0,0 +1,4 @@ + + + Допрете за да гледате во живо + diff --git a/patches/src/main/resources/youtube/livering/host/values-ml/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ml/strings.xml new file mode 100644 index 000000000..4a6d05b2f --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ml/strings.xml @@ -0,0 +1,4 @@ + + + തത്സമയം കാണാൻ ടാപ്പ് ചെയ്യുക + diff --git a/patches/src/main/resources/youtube/livering/host/values-mn/strings.xml b/patches/src/main/resources/youtube/livering/host/values-mn/strings.xml new file mode 100644 index 000000000..fb22e3400 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-mn/strings.xml @@ -0,0 +1,4 @@ + + + Шууд үзэхийн тулд товшино уу + diff --git a/patches/src/main/resources/youtube/livering/host/values-mr/strings.xml b/patches/src/main/resources/youtube/livering/host/values-mr/strings.xml new file mode 100644 index 000000000..0bf9702be --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-mr/strings.xml @@ -0,0 +1,4 @@ + + + लाइव्ह पाहण्यासाठी टॅप करा + diff --git a/patches/src/main/resources/youtube/livering/host/values-ms/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ms/strings.xml new file mode 100644 index 000000000..cc53b9071 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ms/strings.xml @@ -0,0 +1,4 @@ + + + Ketik untuk menonton secara langsung + diff --git a/patches/src/main/resources/youtube/livering/host/values-my/strings.xml b/patches/src/main/resources/youtube/livering/host/values-my/strings.xml new file mode 100644 index 000000000..6ccbaff97 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-my/strings.xml @@ -0,0 +1,4 @@ + + + တိုက်ရိုက်ကြည့်ရန် တို့ပါ + diff --git a/patches/src/main/resources/youtube/livering/host/values-nb/strings.xml b/patches/src/main/resources/youtube/livering/host/values-nb/strings.xml new file mode 100644 index 000000000..ad9307f5d --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-nb/strings.xml @@ -0,0 +1,4 @@ + + + Trykk for å se direktesendingen + diff --git a/patches/src/main/resources/youtube/livering/host/values-ne/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ne/strings.xml new file mode 100644 index 000000000..ece3a2945 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ne/strings.xml @@ -0,0 +1,4 @@ + + + लाइभ हेर्न ट्याप गर्नुहोस् + diff --git a/patches/src/main/resources/youtube/livering/host/values-nl/strings.xml b/patches/src/main/resources/youtube/livering/host/values-nl/strings.xml new file mode 100644 index 000000000..bb749e151 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-nl/strings.xml @@ -0,0 +1,4 @@ + + + Tik om live te kijken + diff --git a/patches/src/main/resources/youtube/livering/host/values-or/strings.xml b/patches/src/main/resources/youtube/livering/host/values-or/strings.xml new file mode 100644 index 000000000..75141ddbc --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-or/strings.xml @@ -0,0 +1,4 @@ + + + ଲାଇଭ ଦେଖିବାକୁ ଟାପ କରନ୍ତୁ + diff --git a/patches/src/main/resources/youtube/livering/host/values-pa/strings.xml b/patches/src/main/resources/youtube/livering/host/values-pa/strings.xml new file mode 100644 index 000000000..abbdef872 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-pa/strings.xml @@ -0,0 +1,4 @@ + + + ਲਾਈਵ ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ + diff --git a/patches/src/main/resources/youtube/livering/host/values-pl/strings.xml b/patches/src/main/resources/youtube/livering/host/values-pl/strings.xml new file mode 100644 index 000000000..5b4609267 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-pl/strings.xml @@ -0,0 +1,4 @@ + + + Dotknij, aby obejrzeć na żywo + diff --git a/patches/src/main/resources/youtube/livering/host/values-pt-rBR/strings.xml b/patches/src/main/resources/youtube/livering/host/values-pt-rBR/strings.xml new file mode 100644 index 000000000..3ce7fd7c3 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-pt-rBR/strings.xml @@ -0,0 +1,4 @@ + + + Toque para assistir a transmissão ao vivo + diff --git a/patches/src/main/resources/youtube/livering/host/values-pt-rPT/strings.xml b/patches/src/main/resources/youtube/livering/host/values-pt-rPT/strings.xml new file mode 100644 index 000000000..20a391e44 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-pt-rPT/strings.xml @@ -0,0 +1,4 @@ + + + Toque para ver em direto + diff --git a/patches/src/main/resources/youtube/livering/host/values-pt/strings.xml b/patches/src/main/resources/youtube/livering/host/values-pt/strings.xml new file mode 100644 index 000000000..3ce7fd7c3 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-pt/strings.xml @@ -0,0 +1,4 @@ + + + Toque para assistir a transmissão ao vivo + diff --git a/patches/src/main/resources/youtube/livering/host/values-ro/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ro/strings.xml new file mode 100644 index 000000000..514b6d5f6 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ro/strings.xml @@ -0,0 +1,4 @@ + + + Atinge pentru a viziona live + diff --git a/patches/src/main/resources/youtube/livering/host/values-ru/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ru/strings.xml new file mode 100644 index 000000000..83f23c3a2 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ru/strings.xml @@ -0,0 +1,4 @@ + + + Нажмите, чтобы посмотреть трансляцию + diff --git a/patches/src/main/resources/youtube/livering/host/values-si/strings.xml b/patches/src/main/resources/youtube/livering/host/values-si/strings.xml new file mode 100644 index 000000000..c4fa988d4 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-si/strings.xml @@ -0,0 +1,4 @@ + + + සජීවීව නැරඹීමට තට්ටු කරන්න + diff --git a/patches/src/main/resources/youtube/livering/host/values-sk/strings.xml b/patches/src/main/resources/youtube/livering/host/values-sk/strings.xml new file mode 100644 index 000000000..5b9234086 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-sk/strings.xml @@ -0,0 +1,4 @@ + + + Klepnutím spustíte prehrávanie priameho prenosu + diff --git a/patches/src/main/resources/youtube/livering/host/values-sl/strings.xml b/patches/src/main/resources/youtube/livering/host/values-sl/strings.xml new file mode 100644 index 000000000..f48d84429 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-sl/strings.xml @@ -0,0 +1,4 @@ + + + Dotaknite se za ogled v živo + diff --git a/patches/src/main/resources/youtube/livering/host/values-sq/strings.xml b/patches/src/main/resources/youtube/livering/host/values-sq/strings.xml new file mode 100644 index 000000000..15d088bff --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-sq/strings.xml @@ -0,0 +1,4 @@ + + + Trokit për të parë drejtpërdrejt + diff --git a/patches/src/main/resources/youtube/livering/host/values-sr/strings.xml b/patches/src/main/resources/youtube/livering/host/values-sr/strings.xml new file mode 100644 index 000000000..ebeac482a --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-sr/strings.xml @@ -0,0 +1,4 @@ + + + Додирните да бисте гледали уживо + diff --git a/patches/src/main/resources/youtube/livering/host/values-sv/strings.xml b/patches/src/main/resources/youtube/livering/host/values-sv/strings.xml new file mode 100644 index 000000000..8208efa79 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-sv/strings.xml @@ -0,0 +1,4 @@ + + + Tryck för att titta live + diff --git a/patches/src/main/resources/youtube/livering/host/values-sw/strings.xml b/patches/src/main/resources/youtube/livering/host/values-sw/strings.xml new file mode 100644 index 000000000..0058be5cf --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-sw/strings.xml @@ -0,0 +1,4 @@ + + + Gusa ili utazame moja kwa moja + diff --git a/patches/src/main/resources/youtube/livering/host/values-ta/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ta/strings.xml new file mode 100644 index 000000000..278fc3d63 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ta/strings.xml @@ -0,0 +1,4 @@ + + + நேரலையில் பார்க்க, தட்டவும் + diff --git a/patches/src/main/resources/youtube/livering/host/values-te/strings.xml b/patches/src/main/resources/youtube/livering/host/values-te/strings.xml new file mode 100644 index 000000000..6d2ad15c0 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-te/strings.xml @@ -0,0 +1,4 @@ + + + లైవ్‌ను చూడటానికి ట్యాప్ చేయండి + diff --git a/patches/src/main/resources/youtube/livering/host/values-th/strings.xml b/patches/src/main/resources/youtube/livering/host/values-th/strings.xml new file mode 100644 index 000000000..8e12bc663 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-th/strings.xml @@ -0,0 +1,4 @@ + + + แตะเพื่อดูถ่ายทอดสด + diff --git a/patches/src/main/resources/youtube/livering/host/values-tl/strings.xml b/patches/src/main/resources/youtube/livering/host/values-tl/strings.xml new file mode 100644 index 000000000..a41f3ea47 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-tl/strings.xml @@ -0,0 +1,4 @@ + + + I-tap para live na mapanood + diff --git a/patches/src/main/resources/youtube/livering/host/values-tr/strings.xml b/patches/src/main/resources/youtube/livering/host/values-tr/strings.xml new file mode 100644 index 000000000..59fdaf7ef --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-tr/strings.xml @@ -0,0 +1,4 @@ + + + Canlı izlemek için dokunun + diff --git a/patches/src/main/resources/youtube/livering/host/values-uk/strings.xml b/patches/src/main/resources/youtube/livering/host/values-uk/strings.xml new file mode 100644 index 000000000..40e3eb69d --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-uk/strings.xml @@ -0,0 +1,4 @@ + + + Натисніть, щоб дивитися прямий ефір + diff --git a/patches/src/main/resources/youtube/livering/host/values-ur/strings.xml b/patches/src/main/resources/youtube/livering/host/values-ur/strings.xml new file mode 100644 index 000000000..dc8cc994d --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-ur/strings.xml @@ -0,0 +1,4 @@ + + + لائیو دیکھنے کیلئے تھپتھپائیں + diff --git a/patches/src/main/resources/youtube/livering/host/values-uz/strings.xml b/patches/src/main/resources/youtube/livering/host/values-uz/strings.xml new file mode 100644 index 000000000..dfd0e4cd1 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-uz/strings.xml @@ -0,0 +1,4 @@ + + + Jonli tomosha uchun bosing + diff --git a/patches/src/main/resources/youtube/livering/host/values-vi/strings.xml b/patches/src/main/resources/youtube/livering/host/values-vi/strings.xml new file mode 100644 index 000000000..c7fabdf51 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-vi/strings.xml @@ -0,0 +1,4 @@ + + + Nhấn để xem sự kiện trực tiếp + diff --git a/patches/src/main/resources/youtube/livering/host/values-zh-rCN/strings.xml b/patches/src/main/resources/youtube/livering/host/values-zh-rCN/strings.xml new file mode 100644 index 000000000..85cb394fd --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-zh-rCN/strings.xml @@ -0,0 +1,4 @@ + + + 点按即可观看直播 + diff --git a/patches/src/main/resources/youtube/livering/host/values-zh-rHK/strings.xml b/patches/src/main/resources/youtube/livering/host/values-zh-rHK/strings.xml new file mode 100644 index 000000000..b5c4af39b --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-zh-rHK/strings.xml @@ -0,0 +1,4 @@ + + + 輕按即可收看直播 + diff --git a/patches/src/main/resources/youtube/livering/host/values-zh-rTW/strings.xml b/patches/src/main/resources/youtube/livering/host/values-zh-rTW/strings.xml new file mode 100644 index 000000000..2df919cb3 --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-zh-rTW/strings.xml @@ -0,0 +1,4 @@ + + + 輕觸即可觀看直播影片 + diff --git a/patches/src/main/resources/youtube/livering/host/values-zu/strings.xml b/patches/src/main/resources/youtube/livering/host/values-zu/strings.xml new file mode 100644 index 000000000..6b7206ace --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values-zu/strings.xml @@ -0,0 +1,4 @@ + + + Thepha ukuze ubuke bukhoma + diff --git a/patches/src/main/resources/youtube/livering/host/values/strings.xml b/patches/src/main/resources/youtube/livering/host/values/strings.xml new file mode 100644 index 000000000..6ca517dfe --- /dev/null +++ b/patches/src/main/resources/youtube/livering/host/values/strings.xml @@ -0,0 +1,4 @@ + + + Tap to watch live + diff --git a/patches/src/main/resources/youtube/settings/host/values/arrays.xml b/patches/src/main/resources/youtube/settings/host/values/arrays.xml index 7b65a3b73..c3dd9f126 100644 --- a/patches/src/main/resources/youtube/settings/host/values/arrays.xml +++ b/patches/src/main/resources/youtube/settings/host/values/arrays.xml @@ -38,6 +38,7 @@ @string/revanced_change_start_page_entry_default + @string/revanced_change_start_page_entry_all_subscriptions @string/revanced_change_start_page_entry_browse @string/revanced_change_start_page_entry_courses @string/revanced_change_start_page_entry_explore @@ -51,6 +52,7 @@ @string/revanced_change_start_page_entry_music @string/revanced_change_start_page_entry_news @string/revanced_change_start_page_entry_notifications + @string/revanced_change_start_page_entry_playlists @string/revanced_change_start_page_entry_podcasts @string/revanced_change_start_page_entry_search @string/revanced_change_start_page_entry_shopping @@ -58,11 +60,13 @@ @string/revanced_change_start_page_entry_sports @string/revanced_change_start_page_entry_subscriptions @string/revanced_change_start_page_entry_trending + @string/revanced_change_start_page_entry_virtual_reality @string/revanced_change_start_page_entry_watch_later @string/revanced_change_start_page_entry_your_clips ORIGINAL + ALL_SUBSCRIPTIONS BROWSE COURSES EXPLORE @@ -76,6 +80,7 @@ MUSIC NEWS NOTIFICATIONS + PLAYLISTS PODCASTS SEARCH SHOPPING @@ -83,6 +88,7 @@ SPORTS SUBSCRIPTIONS TRENDING + VIRTUAL_REALITY WATCH_LATER YOUR_CLIPS @@ -297,6 +303,26 @@ VI ZH + + @string/revanced_miniplayer_type_entry_0 + @string/revanced_miniplayer_type_entry_1 + @string/revanced_miniplayer_type_entry_2 + @string/revanced_miniplayer_type_entry_3 + @string/revanced_miniplayer_type_entry_4 + @string/revanced_miniplayer_type_entry_5 + @string/revanced_miniplayer_type_entry_6 + @string/revanced_miniplayer_type_entry_7 + + + DISABLED + DEFAULT + MINIMAL + TABLET + MODERN_1 + MODERN_2 + MODERN_3 + MODERN_4 + @string/revanced_miniplayer_type_entry_0 @string/revanced_miniplayer_type_entry_1 @@ -308,7 +334,7 @@ DISABLED - ORIGINAL + DEFAULT MINIMAL TABLET MODERN_1 @@ -324,7 +350,7 @@ @string/revanced_miniplayer_type_entry_6 - ORIGINAL + DEFAULT MINIMAL TABLET MODERN_1 @@ -337,7 +363,7 @@ @string/revanced_miniplayer_type_entry_3 - ORIGINAL + DEFAULT MINIMAL TABLET @@ -409,6 +435,22 @@ 4 5 + + 3 + 4 + 5 + 6 + 7 + 8 + + + 3 + 4 + 5 + 6 + 7 + 8 + @string/revanced_watch_history_type_entry_1 @string/revanced_watch_history_type_entry_2 diff --git a/patches/src/main/resources/youtube/settings/host/values/strings.xml b/patches/src/main/resources/youtube/settings/host/values/strings.xml index ae042b980..8b04f6dea 100644 --- a/patches/src/main/resources/youtube/settings/host/values/strings.xml +++ b/patches/src/main/resources/youtube/settings/host/values/strings.xml @@ -5,7 +5,7 @@ Your controls are modified because an accessibility service is on. - ReVanced Extended + RVX Search %s Reset to default values. @@ -80,9 +80,13 @@ Please download %2$s from the website." Ads + Hide end screen store banner + Store banner is hidden. + Store banner is shown. Hide fullscreen ads Fullscreen ads are hidden. Fullscreen ads are shown. + Fullscreen ads are closed. Hide general ads General ads are hidden. General ads are shown. @@ -284,6 +288,9 @@ Store" Enable feed flyout menu filter Feed flyout menu filter is enabled. Feed flyout menu filter is disabled. + Feed flyout menu filter type + Filter if contains.<br><br>To hide the <b>\'Play next in queue\'</b> menu, you can use <b>\'Play next\'</b> or <b>\'in queue\'</b> as keywords. + Filter if matches.<br><br>To hide the <b>\'Play next in queue\'</b> menu, you can only use <b>\'Play next in queue\'</b> as keywords. Feed flyout menu filter List of flyout menu names to filter, separated by new lines. @@ -376,6 +383,7 @@ If the layout of the player screen changes due to server-side changes, unintende Change start page Default + All subscriptions Browse channels Courses / Learning Explore @@ -389,6 +397,7 @@ If the layout of the player screen changes due to server-side changes, unintende Music News Notifications + Playlists Podcasts Search Shopping @@ -396,6 +405,7 @@ If the layout of the player screen changes due to server-side changes, unintende Sports Subscriptions Trending + Virtual Reality Watch later Your clips Change start page type @@ -436,7 +446,9 @@ This does not bypass the age restriction. It just accepts it automatically."Tablet Tablet (Min 600 dp) Change live ring click action - Channel opens when the live ring is clicked. + "Channel opens when the live ring is clicked. + +Limitation: When the Shorts live stream is opened in regular player due to the 'Open Shorts in regular player' setting, channel does not open." Live stream opens when the live ring is clicked. Spoof app version Version spoofed @@ -465,6 +477,9 @@ If later turned off, it is recommended to clear the app data to prevent UI bugs. Hide account menu "Hide elements of the account menu and You tab. Some components may not be hidden." + Account menu filter type + Filter if contains.<br><br>To hide the <b>\'Get YouTube Premium\'</b> menu, you can use <b>\'YouTube Premium\'</b> or <b>\'Premium\'</b> as keywords. + Filter if matches.<br><br>To hide the <b>\'Get YouTube Premium\'</b> menu, you can only use <b>\'Get YouTube Premium\'</b> as keywords. Account menu filter List of account menu names to filter, separated by new lines. Hide handle @@ -511,58 +526,6 @@ Some components may not be hidden." Prerequisite YouTube Music is required to override button action. Tap here to download YouTube Music. - - Miniplayer - Change the style of the in-app minimized player. - - Miniplayer type - Disabled - Original - Minimal - Tablet - Modern 1 - Modern 2 - Modern 3 - Enable rounded corners - Corners are rounded. - Corners are square. - Enable double-tap and pinch to resize - "Double-tap action and pinch to resize is enabled. - -• Double tap to increase miniplayer size. -• Double tap again to restore original size." - Double-tap action and pinch to resize is disabled. - Enable drag and drop - "Drag and drop is enabled. - -Miniplayer can be dragged to any corner of the screen." - Drag and drop is disabled. - Enable horizontal drag gesture. - "Horizontal drag gesture enabled. - -Miniplayer can be dragged off-screen to the left or right." - Horizontal drag gesture disabled. - Hide close button - Close button is hidden. - Close button is shown. - Hide expand and close buttons - "Buttons are hidden. - -Swipe to expand or close." - Expand and close buttons are shown. - Hide subtexts - Subtexts are hidden. - Subtexts are shown. - Hide skip forward and back buttons - Skip forward and back are hidden. - Skip forward and back are shown. - Initial size - Initial on screen size, in pixels. - Pixel size must be between %1$s and %2$s. - Overlay opacity - Opacity value between 0-100, where 0 is transparent. - Miniplayer overlay opacity must be between 0-100. - Navigation bar Hide or show navigation bar section components. @@ -605,6 +568,7 @@ If this setting do not take effect, try switching to Incognito mode." Enable translucent navigation bar Navigation bar is translucent. Navigation bar is opaque. + In certain YouTube versions, this setting can make the system navigation bar transparent or the layout can be broken in PIP mode. Hide navigation bar Navigation bar is hidden. Navigation bar is shown. @@ -868,42 +832,19 @@ Settings → Autoplay / Playback → Autoplay next video" Thanks button is hidden. Thanks button is shown. - - Hide by index + + Hide action button by index + "Action buttons are hidden by index. - Hide first button - First button is hidden. - First button is shown. - Hide second button - Second button is hidden. - Second button is shown. - Hide third button - Third button is hidden. - Third button is shown. - Hide fourth button - Fourth button is hidden. - Fourth button is shown. - Hide fifth button - Fifth button is hidden. - Fifth button is shown. - Hide sixth button - Sixth button is hidden. - Sixth button is shown. - Hide seventh button - Seventh button is hidden. - Seventh button is shown. - Hide eighth button - Eighth button is hidden. - Eighth button is shown. +Info: +• Wrong action buttons may be hidden, or action buttons may not be hidden. +• Hiding action buttons leaves no empty space." + "Action buttons are hidden by identifier filter. - - Hide by index in live stream - - About Hide action button by index - "Hide the action buttons by index before the action buttons are initialized. - -- Hiding the action buttons leaves no empty space. -- Index of the action buttons may not always be the same button." +Info: +• Right action buttons are hidden. +• Hiding action buttons leaves empty space." + Remix button index Ambient mode @@ -1142,6 +1083,62 @@ Limitation: Video title disappears when clicked." Haptic feedback is disabled. Haptic feedback is enabled. + + Miniplayer + Hide or change components related to Miniplayer. + + Disable resuming Miniplayer + <b>Continue watching</b> will not resume on app startup. + <b>Continue watching</b> will resume on app startup.<br><br>Info:<br>• <b>Continue watching</b> is the YouTube Premium feature.<br>• This setting does not force <b>Continue watching</b> to be enabled. + Miniplayer type + Disabled + Default + Minimal + Tablet + Modern 1 + Modern 2 + Modern 3 + Modern 4 + Enable rounded corners + Corners are rounded. + Corners are square. + Enable double-tap and pinch to resize + "Double-tap action and pinch to resize is enabled. + +• Double tap to increase Miniplayer size. +• Double tap again to restore original size." + Double-tap action and pinch to resize is disabled. + Enable drag and drop + "Drag and drop is enabled. + +Miniplayer can be dragged to any corner of the screen." + Drag and drop is disabled. + Enable horizontal drag gesture. + "Horizontal drag gesture enabled. + +Miniplayer can be dragged off-screen to the left or right." + Horizontal drag gesture disabled. + Hide overlay buttons + Overlay buttons are hidden. + Overlay buttons are shown. + Hide expand and close buttons + "Buttons are hidden. + +Swipe to expand or close." + Expand and close buttons are shown. + Hide skip forward and back buttons + Skip forward and back are hidden. + Skip forward and back are shown. + Hide subtexts + Subtexts are hidden. + Subtexts are shown. + Initial size + Initial on screen size, in pixels. + Pixel size must be between %1$s and %2$s. + Overlay opacity + Opacity value between 0-100, where 0 is transparent. + Miniplayer overlay opacity must be between 0-100. + Player buttons Hide or show buttons in the video player. @@ -1254,9 +1251,11 @@ Tap and hold to toggle the appended information type." Enable custom seekbar color Custom seekbar color is enabled. Custom seekbar color is disabled. - Custom seekbar color value - Type the hex code of the seekbar color. - Invalid seekbar color value. + Custom seekbar primary color + Type the hex code of the seekbar primary color. + Custom seekbar accent color + Type the hex code of the seekbar accent color. + Invalid seekbar color. Enable seekbar tapping Seekbar tapping is enabled. Seekbar tapping is disabled. @@ -1328,10 +1327,6 @@ This feature works best with a very fast internet connection." Expand video descriptions Video descriptions are expanded automatically. Video descriptions are not expanded automatically. - Title in video description panel - "Enter the title of the video description panel in your language. -The Expand video description option may not work if the entered string does not match the video description panel title." - Description diff --git a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml index ddf290997..d11be7b40 100644 --- a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -4,6 +4,7 @@ @@ -130,6 +132,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -339,30 +299,10 @@ - SETTINGS: HIDE_ACTION_BUTTONS --> - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -511,7 +502,12 @@ - + SETTINGS: SEEKBAR_COMPONENTS --> + + + + @@ -919,7 +914,6 @@ - @@ -932,11 +926,13 @@ + + @@ -972,6 +968,7 @@ + diff --git a/patches/src/main/resources/youtube/translations/ar/strings.xml b/patches/src/main/resources/youtube/translations/ar/strings.xml index c228283c4..c7c927ec5 100644 --- a/patches/src/main/resources/youtube/translations/ar/strings.xml +++ b/patches/src/main/resources/youtube/translations/ar/strings.xml @@ -4,7 +4,7 @@ تمكين عناصر التحكم في إمكانية الوصول لمشغل الفيديو؟ تم تعديل عناصر التحكم الخاصة بك لأن خدمة إمكانية الوصول قيد التشغيل. - ReVanced Extended + RVX بحث %s إعادة التعيين إلى القيم الافتراضية. تعديلات تجريبية @@ -74,9 +74,13 @@ Chinese الإعلانات + إخفاء لافتة شاشة المتجر النهائية + تم إخفاء لافتة المتجر. + يتم عرض لافتة المتجر. إخفاء إعلانات ملء الشاشة تم إخفاء إعلانات ملء الشاشة. يتم عرض إعلانات ملء الشاشة. + تم إغلاق الإعلانات على كامل الشاشة. إخفاء الإعلانات العامة تم إخفاء الإعلانات العامة. يتم عرض الإعلانات العامة. @@ -264,6 +268,9 @@ تمكين فلتر القائمة المنبثقة بالموجز تم تمكين فلتر القائمة المنبثقة بالموجز. تم تعطيل فلتر القائمة المنبثقة بالموجز. + نوع تصفية القائمة المنبثقة للموجز + قم بالتصفية إذا كان يحتوي على.<br><br>لإخفاء القائمة <b>\'تشغيل التالي في قائمة المحتوى التالي\'</b>، يمكنك استخدام <b>\'تشغيل التالي\'</b> أو <b>\'في قائمة المحتوى الرئيسي\'</b> ككلمات رئيسية. + قم بالتصفية إذا كانت هناك تطابقات.<br><br>لإخفاء القائمة <b>\'تشغيل التالي في قائمة المحتوى التالي\'</b>، يمكنك فقط استخدام <b>\'تشغيل التالي في قائمة المحتوى التالي\'</b> ككلمات رئيسية. تصفية القائمة المنبثقة بالموجز قائمة بأسماء القائمة المنبثقة المراد تصفيتها، مفصولة بسطور جديدة. @@ -348,6 +355,7 @@ عام تغيير صفحة البداية الافتراضي + كلّ الاشتراكات تصفح القنوات الدورات / التعلم اكتشف @@ -361,6 +369,7 @@ موسيقى أخبار الإشعارات + قوائم التشغيل البودكاست البحث التسوق @@ -368,6 +377,7 @@ رياضة الاشتراكات المحتوى الرائج + الواقع الافتراضي المشاهدة لاحقًا فيديوهاتك تغيير نوع صفحة البداية @@ -405,7 +415,9 @@ الجهاز اللوحي الجهاز اللوحي (الحد الأدنى 600 dp) تغيير إجراء النقر على البث المباشر - يتم فتح القناة عند النقر على الحلقة المباشرة. + "يتم فتح القناة عند النقر على الحلقة المباشرة. + +القيود: عند فتح البث المباشر لفيديوهات Shorts في المشغل العادي بسبب إعداد \"فتح فيديوهات Shorts في المشغل العادي\"، لا يتم فتح القناة." يتم فتح البث المباشر عند النقر على الحلقة المباشرة. إصدار تطبيق وهمي تم تغيير اصدار التطبيق @@ -432,6 +444,9 @@ إخفاء قائمة الحساب "إخفاء عناصر قائمة الحساب وعلامة التبويب أنت. قد لا يتم إخفاء بعض المكونات." + نوع تصفية قائمة الحساب + قم بالتصفية إذا كان يحتوي على.<br><br>لإخفاء قائمة <b>\'الحصول على YouTube Premium\'</b>، يمكنك استخدام <b>\'YouTube Premium\'</b> أو <b>\'Premium\'</b> ككلمات رئيسية. + قم بالتصفية إذا تطابقت النتائج.<br><br>لإخفاء قائمة <b>\'الحصول على YouTube Premium\'</b>، يمكنك فقط استخدام <b>\'الحصول على YouTube Premium\'</b> ككلمات رئيسية. تعديل فلتر قائمة الحساب قائمة بأسماء قائمة الحسابات المراد تصفيتها، مفصولة بسطور جديدة. إخفاء الاسم المعرِّف @@ -471,56 +486,6 @@ %s لم يتم تثبيته. الرجاء تثبيته. متطلب أساسي موسيقى YouTube مطلوبًا لتجاوز إجراء الزر. انقر هنا لتنزيل موسيقى YouTube. - - المشغل المصغر - تغيير نمط المشغل المصغر داخل التطبيق. - نوع المشغل المصغر - معطَّل - الأصلي - الحد الأدنى - الجهاز اللوحي - حديث 1 - حديث 2 - حديث 3 - تمكين الزوايا المستديرة - الزوايا مستديرة. - الزوايا مربعة. - تمكين النقر المزدوج والضغط لتغيير الحجم - "تم تمكين خاصية النقر المزدوج والضغط لتغيير الحجم. - -• انقر مرتين لزيادة حجم المشغل المصغر. -• انقر مرتين مرة أخرى لاستعادة الحجم الأصلي." - تم تعطيل خاصية النقر المزدوج والضغط لتغيير الحجم. - تمكين السحب والإفلات - "تم تمكين السحب والإفلات. - -يمكن سحب المشغل المصغر إلى أي زاوية من الشاشة." - تم تعطيل السحب والإفلات. - تمكين إيماءة السحب الأفقية - "تم تمكين إيماءة السحب الأفقية. - -يمكن سحب المشغل المصغر خارج الشاشة إلى اليمين أو اليسار." - تم تعطيل إيماءة السحب الأفقية. - إخفاء زر الإغلاق - تم إخفاء زر الإغلاق. - يتم عرض زر الإغلاق. - إخفاء أزرار التوسيع والإغلاق - "تم إخفاء الأزرار. - -مرر للتوسيع أو الإغلاق." - يتم عرض أزرار التوسيع والإغلاق. - إخفاء النصوص الفرعية - تم إخفاء النصوص الفرعية. - يتم عرض النصوص الفرعية. - إخفاء أزرار التخطي للأمام والخلف - تم إخفاء التخطي للأمام والخلف. - يتم عرض التخطي للأمام والخلف. - تهيئة الحجم - تهيئة الحجم على الشاشة، بالبكسل. - حجم البكسل يجب أن يكون بين %1$s و %2$s. - شفافية الواجهة - قيمة الشفافية بين 0-100، حيث يكون 0 شفاف. - يجب أن تكون نسبة شفافية واجهة المشغل المصغر بين 0-100. شريط التنقل إخفاء أو عرض مكونات قسم شريط التنقل. @@ -561,6 +526,7 @@ تمكين شريط التنقل الشفاف شريط التنقل شفاف. شريط التنقل غير شفاف. + في بعض إصدارات YouTube، قد يؤدي هذا الإعداد إلى جعل شريط التنقل في النظام شفافًا أو قد يتم كسر التخطيط في وضع صورة داخل صورة. إخفاء شريط التنقل تم إخفاء شريط التنقل. يتم عرض شريط التنقل. @@ -811,39 +777,19 @@ إخفاء زر شكرًا تم إخفاء زر شكرًا. يتم عرض زر شكرًا. - - إخفاء حسب الفهرس - إخفاء الزر الأول - تم إخفاء الزر الأول. - يتم عرض الزر الأول. - إخفاء الزر الثاني - تم إخفاء الزر الثاني. - يتم عرض الزر الثاني. - إخفاء الزر الثالث - تم إخفاء الزر الثالث. - يتم عرض الزر الثالث. - إخفاء الزر الرابع - تم إخفاء الزر الرابع. - يتم عرض الزر الرابع. - إخفاء الزر الخامس - تم إخفاء الزر الخامس. - يتم عرض الزر الخامس. - إخفاء الزر السادس - تم إخفاء الزر السادس. - يتم عرض الزر السادس. - إخفاء الزر السابع - تم إخفاء الزر السابع. - يتم عرض الزر السابع. - إخفاء الزر الثامن - تم إخفاء الزر الثامن. - يتم عرض الزر الثامن. - - إخفاء حسب الفهرس في البث المباشر - لمحة عن إخفاء زر الإجراء حسب الفهرس - "إخفاء أزرار الإجراءات بواسطة الفهرس قبل تهيئة أزرار الإجراءات. + + إخفاء زر الإجراء حسب الفهرس + "تم إخفاء أزرار الإجراءات بواسطة الفهرس. -- إخفاء أزرار الإجراءات لا يترك أي مساحة فارغة. -- قد لا يكون فهرس أزرار الإجراءات هو الزر نفسه دائمًا." +معلومات: +• قد تكون أزرار الإجراءات مخفية بشكل خاطئ، أو قد لا تكون أزرار الإجراءات مخفية. +• لا يترك إخفاء أزرار الإجراءات أي مساحة فارغة." + "تم إخفاء أزرار الإجراءات بواسطة مرشح المعرف. + +معلومات: +• يتم إخفاء أزرار الإجراءات اليمنى. +• يؤدي إخفاء أزرار الإجراءات إلى ترك مساحة فارغة." + فهرس زر ريمكس وضع الإضاءة السينمائية تعطيل وضع الإضاءة السينمائية أو تجاوز قيود وضع الإضاءة السينمائية. @@ -1066,6 +1012,60 @@ تعطيل الاهتزاز عند التكبير تم تعطيل الاهتزاز. تم تمكين الاهتزاز. + + المشغل المصغر + إخفاء أو تغيير المكونات المتعلقة بالمشغل المصغر. + تعطيل استئناف المشغل المصغر + لن يتم استئناف <b>متابعة المشاهدة</b> عند بدء تشغيل التطبيق. + سيتم استئناف ميزة <b>متابعة المشاهدة</b> عند بدء تشغيل التطبيق.<br><br>معلومات:<br>• <b>متابعة المشاهدة</b> هي ميزة YouTube Premium.<br>• لا يفرض هذا الإعداد تمكين ميزة <b>متابعة المشاهدة.</b> + نوع المشغل المصغر + معطَّل + الأصلي + الحد الأدنى + الجهاز اللوحي + حديث 1 + حديث 2 + حديث 3 + حديث 4 + تمكين الزوايا المستديرة + الزوايا مستديرة. + الزوايا مربعة. + تمكين النقر المزدوج والضغط لتغيير الحجم + "تم تمكين خاصية النقر المزدوج والضغط لتغيير الحجم. + +• انقر مرتين لزيادة حجم المشغل المصغر. +• انقر مرتين مرة أخرى لاستعادة الحجم الأصلي." + تم تعطيل خاصية النقر المزدوج والضغط لتغيير الحجم. + تمكين السحب والإفلات + "تم تمكين السحب والإفلات. + +يمكن سحب المشغل المصغر إلى أي زاوية من الشاشة." + تم تعطيل السحب والإفلات. + تمكين إيماءة السحب الأفقية + "تم تمكين إيماءة السحب الأفقية. + +يمكن سحب المشغل المصغر خارج الشاشة إلى اليمين أو اليسار." + تم تعطيل إيماءة السحب الأفقية. + إخفاء أزرار الواجهة + تم إخفاء أزرار الواجهة. + يتم عرض أزرار الواجهة. + إخفاء أزرار التوسيع والإغلاق + "تم إخفاء الأزرار. + +مرر للتوسيع أو الإغلاق." + يتم عرض أزرار التوسيع والإغلاق. + إخفاء أزرار التخطي للأمام والخلف + تم إخفاء التخطي للأمام والخلف. + يتم عرض التخطي للأمام والخلف. + إخفاء النصوص الفرعية + تم إخفاء النصوص الفرعية. + يتم عرض النصوص الفرعية. + تهيئة الحجم + تهيئة الحجم على الشاشة، بالبكسل. + حجم البكسل يجب أن يكون بين %1$s و %2$s. + شفافية الواجهة + قيمة الشفافية بين 0-100، حيث يكون 0 شفاف. + يجب أن تكون نسبة شفافية واجهة المشغل المصغر بين 0-100. أزرار المشغل إخفاء أو عرض الأزرار في مشغل الفيديو. @@ -1171,9 +1171,11 @@ تمكين لون شريط تقدم الفيديو المخصص تم تمكين لون شريط تقدم الفيديو المخصص. تم تعطيل لون شريط تقدم الفيديو المخصص. - قيمة لون شريط تقدم الفيديو المخصصة - اكتب رمز اللون للون شريط تقدم الفيديو. - قيمة لون شريط التقدم غير صالحة. + اللون الأساسي لشريط التقدم المخصص + اكتب رمز اللون لشريط التقدم الأساسي. + لون لشريط التقدم المميز المخصص + اكتب رمز اللون المميز لشريط التقدم الأساسي. + قيمة لون شريط التقدم غير صالحة. تمكين النقر على شريط التقدم تم تمكين النقر على شريط الوقت (شريط تقدم الفيديو). تم تعطيل النقر على شريط الوقت (شريط تقدم الفيديو). @@ -1242,10 +1244,6 @@ توسيع وصف الفيديو يتم توسيع أوصاف الفيديو تلقائيًا. لا يتم توسيع أوصاف الفيديو تلقائيًا. - العنوان في لوحة وصف الفيديو - "أدخل عنوان لوحة وصف الفيديو بلغتك. -قد لا يعمل خيار توسيع وصف الفيديو إذا كانت السلسلة المدخلة لا تتطابق مع عنوان لوحة وصف الفيديو." - الوصف Shorts تعطيل تشغيل فيديوهات Shorts في الخلفية diff --git a/patches/src/main/resources/youtube/translations/bg-rBG/strings.xml b/patches/src/main/resources/youtube/translations/bg-rBG/strings.xml index a7c4c0518..2a1cda728 100644 --- a/patches/src/main/resources/youtube/translations/bg-rBG/strings.xml +++ b/patches/src/main/resources/youtube/translations/bg-rBG/strings.xml @@ -4,7 +4,6 @@ Включване на контролите за достъпност на видеоплеaра? Вашите контроли са променени, защото е активирана услуга за достъпност. - Настройки ReVanced Търсене %s Възстановяване на стандартните стойности. Експериментални настройки @@ -393,7 +392,7 @@ Таблет Таблет (Мин 600 dip) Действие на кликване върху точка за предаване на живо - Каналът се отваря, когато се щракне върху пръстена на живо. + "Каналът се отваря, когато се щракне върху пръстена на живо." Потокът на живо се отваря, когато се щракне върху пръстена на живо. Променете версията на приложението Подправена версия @@ -456,55 +455,6 @@ %s не е инсталирано. Моля инсталирайте го. Изисквания Изисква се YouTube Music, за да замени действието на бутона. Докоснете тук, за да изтеглите YouTube Music. - - Минимизиран екран за възпроизвеждане - Променете стила на минимизирания екран за възпроизвеждане. - Минимизиран тип екран за гледане - Изключено - Оригинал - Телефон - Таблет - Модерен 1 - Модерен 2 - Модерен 3 - Активирайте заоблени ъгли - Ъглите са заоблени. - Ъглите са нормални. - Активирайте двойното докосване и щипване за преоразмеряване - "Действието с двойно докосване и щипване за преоразмеряване е активирано. -• Докоснете двукратно, за да увеличите размера на миниплейъра. -• Докоснете два пъти отново, за да възстановите оригиналния размер." - Двойно докосване и щипване за преоразмеряване е деактивирано. - Разрешете плъзгане и местене - "Плъзгане и местенето е активирано. - -Миниплейърът може да се плъзга до всеки ъгъл на екрана." - Плъзгането и преместването е деактивирано. - Активиране на хоризонтално плъзгане. - "Хоризонталното плъзгане е активиран. - -Миниплейърът може да се плъзга извън екрана наляво или надясно." - Хоризонтално плъзгане е деактивиран. - Бутони за разширяване и свиване на екрана - Бутоните са скрити.\n(плъзнете миниплейъра, за да разширите или затворите) - Бутони за разширяване и свиване на екрана са видими. - Бутони за разширяване и свиване на екрана - "Бутоните са скрити. - -Плъзнете, за да разширите или затворите." - Бутони за разширяване и свиване на екрана са видими. - Възпроизвеждане на екранни текстове, етикети - Подтекстовете са скрити. - Подтекстовете се показват. - Бутони за напред и назад - Бутони за напред и назад са скрити. - Бутони за напред и назад са показани. - Първоначален размер - Първоначален размер на екрана, в пиксели. - Размерът на пиксела трябва да бъде между %1$s и %2$s. - Прозрачност на менютата - Стойност на прозрачност между 0-100, където 0 е прозрачно. - Прозрачността на менюто на плейъра трябва да бъде между 0-100. Нулирайте стойностите по подразбирне. Лента за навигация Скриване или показване на елементи от лентата за навигация. @@ -780,14 +730,7 @@ Скриване на бурона за благодарност Бутона за благодарност е скрит. Бутона за благодарност се показва. - - Скриване на третия бутон - Бутона е скрит. - Бутона се показва. - Скриване на четвъртия бутон - Бутона е скрит. - Бутона се показва. - + Подсветка около видеото Изключете подсветка около видеото или прескочете ограничението в режим за пестене на батерията. @@ -1010,6 +953,47 @@ Изкл. вибрация при зум Вибрация за обратна връзка е изключена. Вибрация за обратна връзка е включена. + + Минимизиран екран за възпроизвеждане + Променете стила на минимизирания екран за възпроизвеждане. + Минимизиран тип екран за гледане + Изключено + Оригинал + Телефон + Таблет + Модерен 1 + Модерен 2 + Модерен 3 + Активирайте заоблени ъгли + Ъглите са заоблени. + Ъглите са нормални. + Активирайте двойното докосване и щипване за преоразмеряване + "Действието с двойно докосване и щипване за преоразмеряване е активирано. +• Докоснете двукратно, за да увеличите размера на миниплейъра. +• Докоснете два пъти отново, за да възстановите оригиналния размер." + Двойно докосване и щипване за преоразмеряване е деактивирано. + Разрешете плъзгане и местене + "Плъзгане и местенето е активирано. + +Миниплейърът може да се плъзга до всеки ъгъл на екрана." + Плъзгането и преместването е деактивирано. + Активиране на хоризонтално плъзгане. + "Хоризонталното плъзгане е активиран. + +Миниплейърът може да се плъзга извън екрана наляво или надясно." + Хоризонтално плъзгане е деактивиран. + Бутони за напред и назад + Бутони за напред и назад са скрити. + Бутони за напред и назад са показани. + Възпроизвеждане на екранни текстове, етикети + Подтекстовете са скрити. + Подтекстовете се показват. + Първоначален размер + Първоначален размер на екрана, в пиксели. + Размерът на пиксела трябва да бъде между %1$s и %2$s. + Прозрачност на менютата + Стойност на прозрачност между 0-100, където 0 е прозрачно. + Прозрачността на менюто на плейъра трябва да бъде между 0-100. Нулирайте стойностите по подразбирне. Бутони на екрана за възпроизвеждане Скриване или показване на бутони на екрана на видеоплейъра. @@ -1111,9 +1095,6 @@ Промяна на цвета на индикатора за време Стойността за избор на цвят на лентата за време е включена. Стойността за избор на цвят на лентата за време е изключена. - Стойност по избор за цвят на лентата за време - Въведете кода за цвят на лентата за време. - Невалидна стойност за цвят на лентата на прогреса. Активиране на докосването на лентата за време Докосването на лентата за време е включено. Докосването на лентата за време е изключено. @@ -1183,10 +1164,6 @@ Автоматично отваряне на видео описание Описанието на видеоклипа се отваря автоматично. Описанието на видеоклипа не се отваря автоматично. - Заглавие в панела с описание на видеоклипа - "Въведете заглавие в описанието на видеоклипа на вашия език. -Опцията „Отваряне на описанието автоматично“ може да не работи, ако стойността на заглавието не съвпада със заглавието в описанието." - Описание Shorts Възпроизвеждане на Shorts в фонов режим diff --git a/patches/src/main/resources/youtube/translations/de-rDE/strings.xml b/patches/src/main/resources/youtube/translations/de-rDE/strings.xml index d8fe125b8..c241a3ff0 100644 --- a/patches/src/main/resources/youtube/translations/de-rDE/strings.xml +++ b/patches/src/main/resources/youtube/translations/de-rDE/strings.xml @@ -4,7 +4,7 @@ Bedienungshilfen für den Video-Player aktivieren? Ihre Steuerungen wurden angepasst, da ein Barrierefreiheitsdienst aktiviert ist. - ReVanced Extended + RVX Suche %s Auf Standardwerte zurücksetzen. Experimentelle Flags @@ -19,6 +19,7 @@ "%1$s ist nicht installiert. Bitte lade %2$s von der Webseite herunter." %s ist nicht installiert. Bitte installieren. + RVX Sprache Werbung Vollbildwerbung verstecken @@ -384,38 +385,6 @@ Manche Komponenten könnten nicht versteckt werden." Warnung %s ist nicht installiert. Bitte installieren. Voraussetzung - - Miniplayer - Ändern Sie den Stil des in App minimierten Players. - Miniplayer Typ - Deaktiviert - Original - Telefon - Tablet - Modern 1 - Modern 2 - Modern 3 - Abgerundete Ecken aktivieren - Ecken sind abgerundet. - Ecken sind nicht abgerundet. - Aktiviere doppeltes Tippen und Pinchen um die Größe zu ändern - Drag and Drop aktivieren - Drag and Drop ist deaktiviert. - Ausklappen und Schließen der Tasten ausblenden - Tasten sind ausgeblendet.\n(wischen Sie den Miniplayer zum erweitern oder schließen) - Erweitern und Schließen Schaltflächen werden angezeigt. - Untertitel ausblenden - Untertitel sind versteckt. - Untertitel werden angezeigt. - Vorwärts- und Rückwärts-Buttons ausblenden - Vorwärts springen und zurück sind versteckt. - Vor- und zurückspringen wird angezeigt. - Initiale Größe - Initiale Bildschirmgröße (in Pixel). - Pixelgröße muss zwischen %1$s und %2$s liegen. - Deckkraft der Überlagerung - Deckkraft Wert zwischen 0-100, wobei 0 transparent ist. - Die Transparenz des Miniplayers muss zwischen 0-100 liegen. Zurückgesetzt auf Standardwerte. Navigationsleiste Komponenten der Navigationsleiste ausblenden oder anzeigen. @@ -667,8 +636,7 @@ Einstellungen → Autoplay → Nächstes Video automatisch abspielen" Verstecke \"Danke\" Schaltfläche \"Danke\" Schaltfläche wird versteckt. \"Danke\" Schaltfläche wird angezeigt. - - + Ambient-Modus Ambient-Modus deaktivieren oder Einschränkungen des Ambient-Modus umgehen. @@ -855,6 +823,35 @@ Einschränkung: Videotitel verschwindet beim Klicken auf den Bildschirm."Deaktiviere Haptisches Feedback beim Zoomen Haptisches Feedback ist deaktiviert. Haptisches Feedback ist aktiviert. + + Miniplayer + Ändern Sie den Stil des in App minimierten Players. + Miniplayer Typ + Deaktiviert + Original + Telefon + Tablet + Modern 1 + Modern 2 + Modern 3 + Abgerundete Ecken aktivieren + Ecken sind abgerundet. + Ecken sind nicht abgerundet. + Aktiviere doppeltes Tippen und Pinchen um die Größe zu ändern + Drag and Drop aktivieren + Drag and Drop ist deaktiviert. + Vorwärts- und Rückwärts-Buttons ausblenden + Vorwärts springen und zurück sind versteckt. + Vor- und zurückspringen wird angezeigt. + Untertitel ausblenden + Untertitel sind versteckt. + Untertitel werden angezeigt. + Initiale Größe + Initiale Bildschirmgröße (in Pixel). + Pixelgröße muss zwischen %1$s und %2$s liegen. + Deckkraft der Überlagerung + Deckkraft Wert zwischen 0-100, wobei 0 transparent ist. + Die Transparenz des Miniplayers muss zwischen 0-100 liegen. Zurückgesetzt auf Standardwerte. Player Buttons Verstecke oder zeige Buttons im Video-Player. @@ -934,8 +931,6 @@ Tippen und halten Sie, um den Einstellungsdialog für die Whitelist anzuzeigen.< Eigene Suchleistenfarbe aktivieren Die benutzerdefinierte Farbe der Suchleiste ist aktiviert Die benutzerdefinierte Farbe der Suchleiste ist deaktiviert - Benutzerdefinierter Farbwert für die Suchleiste - Hex-Code der Suchleisten-Farbe eingeben Aktiviere Suchleisten-Tippen (Video Fortschrittsbalken) Tippen der Suchleiste ist aktiviert Tippen der Suchleiste ist deaktiviert @@ -982,10 +977,6 @@ Tippen und halten Sie, um den Einstellungsdialog für die Whitelist anzuzeigen.< Videobeschreibungen erweitern Videobeschreibungen werden automatisch erweitert. Videobeschreibungen werden nicht automatisch erweitert. - Titel im Video-Beschreibungsfeld - "Geben Sie den Titel des Video-Beschreibungsfensters in Ihrer Sprache ein. -Die Option \"Videobeschreibung\" kann nicht funktionieren, wenn die eingegebene Zeichenkette nicht mit dem Titel des Videobeschreibungsfensters übereinstimmt." - Beschreibung Shorts Shorts-Player beim App-Start ausblenden diff --git a/patches/src/main/resources/youtube/translations/el-rGR/strings.xml b/patches/src/main/resources/youtube/translations/el-rGR/strings.xml index 9b59a3443..3539748ee 100644 --- a/patches/src/main/resources/youtube/translations/el-rGR/strings.xml +++ b/patches/src/main/resources/youtube/translations/el-rGR/strings.xml @@ -4,7 +4,7 @@ Ενεργοποίηση των στοιχείων ελέγχου προσβασιμότητας για το πρόγραμμα αναπαραγωγής βίντεο; Τα στοιχεία ελέγχου σας τροποποιούνται επειδή είναι ενεργή κάποια υπηρεσία προσβασιμότητας. - ReVanced Extended + RVX Αναζήτηση %s Έγινε επαναφορά στις προεπιλεγμένες τιμές. Πειραματικές λειτουργίες @@ -74,9 +74,13 @@ Κινέζικα Διαφημίσεις + Ετικέτα καταστήματος τελικής οθόνης + Κρυμμένη. + Εμφανίζεται. Διαφημίσεις πλήρους οθόνης Κρυμμένες. Εμφανίζονται. + Οι διαφημίσεις πλήρους οθόνης έκλεισαν. Γενικές διαφημίσεις Κρυμμένες. Εμφανίζονται. @@ -262,6 +266,9 @@ Playlists Φιλτράρισμα του αναδυόμενου μενού στη ροή Το φιλτράρισμα του αναδυόμενου μενού στη ροή είναι ενεργοποιημένο. Το φιλτράρισμα του αναδυόμενου μενού στη ροή είναι απενεργοποιημένο. + Τύπος φίλτρου αναδυόμενου μενού ροής + Φιλτράρισμα αν στο μενού περιέχονται οι εισαγόμενες λέξεις-κλειδιά. <br><br>Παράδειγμα: Για απόκρυψη του μενού <br>«Αναπαραγωγή επόμενου στην ουρά»<br>, μπορεί να εισαχθεί το <b>«Αναπαραγωγή επόμενου»</b> ή το <b>«στην ουρά»</b> ως λέξεις-κλειδιά. + Φιλτράρισμα αν το μενού ταιριάζει με τις εισαγόμενες λέξεις-κλειδιά.<br><br>Παράδειγμα: Για απόκρυψη του μενού <b>«Αναπαραγωγή επόμενου στην ουρά»</b>, πρέπει να εισαχθεί το <b>«Αναπαραγωγή επόμενου στην ουρά»</b> ως λέξεις-κλειδιά. Επεξεργασία φίλτρου αναδυόμενων μενού ροής Λίστα ονομάτων των επιλογών του αναδυόμενου μενού για φιλτράρισμα, διαχωρισμένα με νέες γραμμές. @@ -344,6 +351,7 @@ Playlists Γενικά Αλλαγή αρχικής σελίδας Προεπιλογή + Όλες οι εγγραφές Περιήγηση καναλιών Μαθήματα / Εκμάθηση Εξερεύνηση @@ -357,6 +365,7 @@ Playlists Μουσική Ειδήσεις Ειδοποιήσεις + Λίστες αναπαραγωγής Podcasts Αναζήτηση Shopping @@ -364,6 +373,7 @@ Playlists Αθλητικά Εγγραφές Τάσεις + Εικονική Πραγματικότητα Παρακολούθηση αργότερα Τα κλιπ σας Αλλαγή τύπου αρχικής σελίδας @@ -402,7 +412,9 @@ Playlists Τάμπλετ Τάμπλετ (Μέγιστο dp 600) Αλλαγή ενέργειας πατήματος δακτυλίου ζωντανής μετάδοσης - Το κανάλι ανοίγει όταν πατιέται ο δακτύλιος ζωντανής μετάδοσης. + "Το κανάλι ανοίγει όταν πατιέται ο δακτύλιος ζωντανής μετάδοσης. + +Περιορισμός: Όταν πρόκειται για ζωντανή μετάδοση Shorts και η λειτουργία «Άνοιγμα των Shorts στην κανονική οθόνη αναπαραγωγής» είναι ενεργοποιημένη, δεν ανοίγει το κανάλι." Η ζωντανή μετάδοση ανοίγει όταν πατιέται ο δακτύλιος ζωντανής μετάδοσης. Παραποίηση έκδοσης εφαρμογής Η έκδοση παραποιείται. @@ -429,6 +441,9 @@ Playlists Φιλτράρισμα μενού λογαριασμού "Απόκρυψη στοιχείων του μενού λογαριασμού και της καρτέλας «Εσείς». Κάποια στοιχεία ενδέχεται να μην κρύβονται." + Τύπος φίλτρου μενού λογαριασμού + Φιλτράρισμα αν στο μενού περιέχονται οι εισαγόμενες λέξεις-κλειδιά. <br><br>Παράδειγμα: Για απόκρυψη του μενού <br>«Αποκτήστε το YouTube Premium»<br>, μπορεί να εισαχθεί το <b>«YouTube Premium»</b> ή το <b>«Premium»</b> ως λέξεις-κλειδιά. + Φιλτράρισμα αν το μενού ταιριάζει με τις εισαγόμενες λέξεις-κλειδιά. <br><br>Παράδειγμα: Για απόκρυψη του μενού <br>«Αποκτήστε το YouTube Premium»<br>, πρέπει να εισαχθεί το <b>«Αποκτήστε το YouTube Premium»</b> ως λέξεις-κλειδιά. Επεξεργασία φίλτρου μενού λογαριασμού Λίστα ονομάτων των επιλογών του μενού λογαριασμού για φιλτράρισμα, διαχωρισμένα με νέες γραμμές. Ψευδώνυμο & διεύθυνση e-mail @@ -468,56 +483,6 @@ Playlists %s δεν έχει εγκατασταθεί. Παρακαλούμε εγκαταστήστε το. Προαπαιτούμενο Το YouTube Music είναι απαραίτητο για την μετατροπή ενέργειας του κουμπιού. Πατήστε για να κατεβάσετε το YouTube Music. - - Ελαχιστοποιημένη οθόνη αναπαραγωγής - Αλλαγή του στυλ της ελαχιστοποιημένης οθόνης αναπαραγωγής. - Τύπος ελαχιστοποιημένης οθόνης αναπαραγωγής - Απενεργοποιημένη - Αρχικός - Ελάχιστος - Τάμπλετ - Μοντέρνος 1 - Μοντέρνος 2 - Μοντέρνος 3 - Στρογγυλεμένες γωνίες - Οι γωνίες της ελαχιστοποιημένης οθόνης είναι στρογγυλεμένες. - Οι γωνίες της ελαχιστοποιημένης οθόνης είναι τετράγωνες. - Διπλό πάτημα & τσίμπημα για αλλαγή μεγέθους - "Η λειτουργία διπλού πατήματος και τσιμπήματος για αλλαγή μεγέθους είναι ενεργοποιημένη. - -• Πατήστε δύο φορές για να αυξήσετε το μέγεθος της ελαχιστοποιημένης οθόνης. -• Πατήστε ξανά δύο φορές για επαναφορά στο αρχικό της μέγεθος." - Η λειτουργία διπλού πατήματος και τσιμπήματος για αλλαγή μεγέθους είναι απενεργοποιημένη. - Μεταφορά και απόθεση - "Η λειτουργία μεταφοράς και απόθεσης είναι ενεργοποιημένη. - -Η ελαχιστοποιημένη οθόνη αναπαραγωγής μπορεί να μετακινηθεί σε οποιαδήποτε γωνία της οθόνης." - Η λειτουργία μεταφοράς και απόθεσης είναι απενεργοποιημένη. - Χειρονομία οριζόντιας σύρσης - "Η χειρονομία οριζόντιας σύρσης είναι ενεργοποιημένη. - -Η ελαχιστοποιημένη οθόνη μπορεί να συρθεί εκτός οθόνης προς τα αριστερά ή δεξιά." - Η χειρονομία οριζόντιας σύρσης είναι απενεργοποιημένη. - Κουμπί κλεισίματος - Κρυμμένο. - Εμφανίζεται. - Κουμπιά επέκτασης και κλεισίματος - "Κρυμμένα. - -Σύρετε την ελαχιστοποιημένη οθόνη αναπαραγωγής για επέκταση ή κλείσιμο του βίντεο." - Εμφανίζονται. - Κείμενα οθόνης αναπαραγωγής - Κρυμμένα. - Εμφανίζονται. - Κουμπιά παράλειψης και επιστροφής - Κρυμμένα. - Εμφανίζονται. - Αρχικό μέγεθος - Αρχικό μέγεθος οθόνης, σε pixels. - Τα pixel πρέπει να είναι μεταξύ %1$s και %2$s. - Αδιαφάνεια φόντου παρασκηνίου - Τιμή αδιαφάνειας μεταξύ 0-100, όπου το 0 είναι διαφανές. - Η αδιαφάνεια πρέπει να ναι μεταξύ 0-100. Γραμμή πλοήγησης Απόκρυψη ή εμφάνιση των στοιχείων της γραμμής πλοήγησης. @@ -558,6 +523,7 @@ Playlists Διαφανή γραμμή πλοήγησης Η γραμμή πλοήγησης είναι διαφανής. Η γραμμή πλοήγησης δεν είναι διαφανής. + Σε ορισμένες εκδόσεις YouTube, αυτή η ρύθμιση μπορεί να κάνει τη γραμμή πλοήγησης του συστήματος διάφανη ή να χαλάσει τη διάταξη σε λειτουργία PIP. Γραμμή πλοήγησης Κρυμμένη. Εμφανίζεται. @@ -625,9 +591,25 @@ Playlists Κρυμμένο. Εμφανίζεται. + Γραμμή ειδοποιήσεων αλληλεπίδρασης + Απόκρυψη ή αλλαγή στοιχείων που σχετίζονται με τη γραμμή ειδοποιήσεων αλληλεπίδρασης. Μηνύματα αλληλεπίδρασης Κρυμμένα. Εμφανίζονται. + Γραμμή ειδοποιήσεων αλληλεπίδρασης πλευράς του διακομιστή + Κρυμμένη. + Εμφανίζεται. + Αντιστροφή θέματος γραμμής ειδοποιήσεων αλληλεπίδρασης + Το θέμα της γραμμής ειδοποιήσεων αλληλεπίδρασης είναι ανεστραμμένο. + Το θέμα της γραμμής ειδοποιήσεων αλληλεπίδρασης δεν είναι ανεστραμμένο. + Αλλαγή φόντου γραμμής ειδοποιήσεων αλληλεπίδρασης + Το χρώμα φόντου της γραμμής ειδοποιήσεων αλληλεπίδρασης έχει αλλάξει. + Το χρώμα φόντου της γραμμής ειδοποιήσεων αλληλεπίδρασης δεν έχει αλλάξει. + "Κάποιες γραμμές ειδοποιήσεων αλληλεπίδρασης χρησιμοποιούν ένα θέμα που έχει οριστεί στην πλευρά του διακομιστή, και όχι το θέμα της εφαρμογής. + +Αλλαγή χρώματος του φόντου αυτών των γραμμών. + +Αν υπάρξουν αλλαγές στην πλευρά του διακομιστή, το χρώμα φόντου της γραμμής ενδέχεται να μην αλλάξει." Γραμμή εργαλείων Απόκρυψη ή αλλαγή στοιχείων που βρίσκονται στη γραμμή εργαλείων, όπως τα κουμπιά, την γραμμή αναζήτησης, ή την επικεφαλίδα. @@ -793,39 +775,19 @@ Playlists Κουμπί «Σας ευχαριστούμε» Κρυμμένο. Εμφανίζεται. - - Απόκρυψη ανά ευρετήριο - Πρώτο κουμπί - Κρυμμένο. - Εμφανίζεται. - Δεύτερο κουμπί - Κρυμμένο. - Εμφανίζεται. - Τρίτο κουμπί - Κρυμμένο. - Εμφανίζεται. - Τέταρτο κουμπί - Κρυμμένο. - Εμφανίζεται. - Πέμπτο κουμπί - Κρυμμένο. - Εμφανίζεται. - Έκτο κουμπί - Κρυμμένο. - Εμφανίζεται. - Έβδομο κουμπί - Κρυμμένο. - Εμφανίζεται. - Όγδοο κουμπί - Κρυμμένο. - Εμφανίζεται. - - Απόκρυψη ανά ευρετήριο σε ζωντανή μετάδοση - Σχετικά με την απόκρυψη κουμπιού ενέργειας ανά ευρετήριο - "Απόκρυψη των κουμπιών ενεργειών ανά ευρετήριο πριν την αρχικοποίηση των κουμπιών ενεργειών. + + Απόκρυψη κουμπιoύ ενεργειών ανά ευρετήριο + "Τα κουμπιά ενεργειών είναι κρυμμένα βάσει ευρετηρίου. -- Η απόκρυψη των κουμπιών ενεργειών δεν αφήνει κενό χώρο. -- Το ευρετήριο των κουμπιών ενεργειών μπορεί να μην είναι πάντα το ίδιο κουμπί." +Πληροφορίες: +• Μπορεί να γίνεται απόκρυψη των λάθος κουμπιών, ή να μη γίνεται καθόλου απόκρυψη. +• Η απόκρυψη ανά ευρετήριο δεν αφήνει κενό χώρο." + "Τα κουμπιά ενεργειών είναι κρυμμένα βάσει φίλτρου αναγνωριστικού. + +Πληροφορίες: +• Γίνεται πάντα απόκρυψη των σωστών κουμπιών ενεργειών. +• Η απόκρυψη βάσει φίλτρου αναγνωριστικού αφήνει κενό χώρο." + Ευρετήριο κουμπιού Remix Λειτουργία περιβάλλοντος Παράκαμψη περιορισμών λειτουργίας περιβάλλοντος ή απενεργοποίηση της. @@ -1052,6 +1014,60 @@ Playlists Απενεργοποίηση απόκρισης δόνησης κατά την χειρονομία ζουμ Η απόκριση δόνησης είναι απενεργοποιημένη. Η απόκριση δόνησης είναι ενεργοποιημένη. + + Ελαχιστοποιημένη οθόνη αναπαραγωγής + Αλλαγή του στυλ της ελαχιστοποιημένης οθόνης αναπαραγωγής. + Απενεργοποίηση συνέχισης αναπαραγωγής σε ελαχιστοποιημένη οθόνη + Η λειτουργία <b>«Συνέχιση παρακολούθησης»</b> είναι απενεργοποιημένη κατά την εκκίνηση της εφαρμογής. + Η λειτουργία <b>«Συνέχιση παρακολούθησης»</b> είναι ενεργοποιημένη κατά την εκκίνηση της εφαρμογής. <br><br>Πληροφορίες:<br>• <b>Η λειτουργία <b>«Συνέχιση παρακολούθησης»<b> πρόκειται για λειτουργία YouTube Premium.<br>• Η τρέχουσα ρύθμιση δεν εξαναγκάζει την ενεργοποίηση της λειτουργίας <b>«Συνέχιση παρακολούθησης»</b>. + Τύπος ελαχιστοποιημένης οθόνης αναπαραγωγής + Απενεργοποιημένη + Αρχικός + Ελάχιστος + Τάμπλετ + Μοντέρνος 1 + Μοντέρνος 2 + Μοντέρνος 3 + Μοντέρνος 4 + Στρογγυλεμένες γωνίες + Οι γωνίες της ελαχιστοποιημένης οθόνης είναι στρογγυλεμένες. + Οι γωνίες της ελαχιστοποιημένης οθόνης είναι τετράγωνες. + Διπλό πάτημα & τσίμπημα για αλλαγή μεγέθους + "Η λειτουργία διπλού πατήματος και τσιμπήματος για αλλαγή μεγέθους είναι ενεργοποιημένη. + +• Πατήστε δύο φορές για να αυξήσετε το μέγεθος της ελαχιστοποιημένης οθόνης. +• Πατήστε ξανά δύο φορές για επαναφορά στο αρχικό της μέγεθος." + Η λειτουργία διπλού πατήματος και τσιμπήματος για αλλαγή μεγέθους είναι απενεργοποιημένη. + Μεταφορά και απόθεση + "Η λειτουργία μεταφοράς και απόθεσης είναι ενεργοποιημένη. + +Η ελαχιστοποιημένη οθόνη αναπαραγωγής μπορεί να μετακινηθεί σε οποιαδήποτε γωνία της οθόνης." + Η λειτουργία μεταφοράς και απόθεσης είναι απενεργοποιημένη. + Χειρονομία οριζόντιας σύρσης + "Η χειρονομία οριζόντιας σύρσης είναι ενεργοποιημένη. + +Η ελαχιστοποιημένη οθόνη μπορεί να συρθεί εκτός οθόνης προς τα αριστερά ή δεξιά." + Η χειρονομία οριζόντιας σύρσης είναι απενεργοποιημένη. + Κουμπιά οθόνης + Κρυμμένα. + Εμφανίζονται. + Κουμπιά επέκτασης και κλεισίματος + "Κρυμμένα. + +Σύρετε την ελαχιστοποιημένη οθόνη αναπαραγωγής για επέκταση ή κλείσιμο του βίντεο." + Εμφανίζονται. + Κουμπιά παράλειψης και επιστροφής + Κρυμμένα. + Εμφανίζονται. + Κείμενα οθόνης αναπαραγωγής + Κρυμμένα. + Εμφανίζονται. + Αρχικό μέγεθος + Αρχικό μέγεθος οθόνης, σε pixels. + Τα pixel πρέπει να είναι μεταξύ %1$s και %2$s. + Αδιαφάνεια φόντου παρασκηνίου + Τιμή αδιαφάνειας μεταξύ 0-100, όπου το 0 είναι διαφανές. + Η αδιαφάνεια πρέπει να ναι μεταξύ 0-100. Κουμπιά οθόνης αναπαραγωγής Απόκρυψη ή εμφάνιση κουμπιών στην οθόνη αναπαραγωγής βίντεο. @@ -1165,9 +1181,11 @@ Playlists Προσαρμοσμένο χρώμα γραμμής προόδου Το προσαρμοσμένο χρώμα γραμμής προόδου είναι ενεργοποιημένο. Το προσαρμοσμένο χρώμα γραμμής προόδου είναι απενεργοποιημένο. - Τιμή χρώματος γραμμής προόδου - Εισάγετε τον κωδικό hex του χρώματος της γραμμής προόδου. - Μη έγκυρη τιμή χρώματος γραμμής προόδου. + Προσαρμοσμένο χρώμα γραμμής προόδου + Εισάγετε τον κωδικό hex του πρωτεύοντος χρώματος της γραμμής προόδου. + Προσαρμοσμένο χρώμα έμφασης γραμμής προόδου + Εισάγετε τον κωδικό hex του χρώματος έμφασης της γραμμής προόδου. + Μη έγκυρο χρώμα γραμμής προόδου. Πάτημα γραμμής προόδου Το πάτημα γραμμής προόδου είναι ενεργοποιημένο. Το πάτημα γραμμής προόδου είναι απενεργοποιημένο. @@ -1244,10 +1262,6 @@ Playlists Αυτόματο άνοιγμα περιγραφής βίντεο Η περιγραφή βίντεο ανοίγεται αυτόματα. Η περιγραφή βίντεο ανοίγεται χειροκίνητα. - Τίτλος του πίνακα περιγραφής βίντεο - "Εισάγετε τον τίτλο του πίνακα περιγραφής βίντεο στη γλώσσα σας. -Η λειτουργία «Αυτόματο άνοιγμα περιγραφής βίντεο» ενδέχεται να μη λειτουργήσει αν η εισαγόμενη συμβολοσειρά σας δεν ταιριάζει με τον τίτλο." - Περιγραφή Shorts Απενεργοποίηση αναπαραγωγής παρασκηνίου για τα Shorts @@ -1536,8 +1550,8 @@ Playlists Προεπιλεγμένη ποιότητα βίντεο με δεδομένα κινητής τηλεφωνίας Προεπιλεγμένη ποιότητα βίντεο με Wi-Fi Απενεργοποίηση βίντεο HDR - Τα βίντεο που υποστηρίζουν HDR δεν θα παίζουν σε HDR ποιότητα. - Τα βίντεο που υποστηρίζουν HDR θα παίζουν σε HDR ποιότητα. + Τα βίντεο που υποστηρίζουν HDR δεν αναπαράγονται σε HDR ποιότητα. + Τα βίντεο που υποστηρίζουν HDR αναπαράγονται σε HDR ποιότητα. Προσαρμοσμένη ταχύτητα αναπαραγωγής Η προσαρμοσμένη ταχύτητα αναπαραγωγής είναι ενεργοποιημένη. Η προσαρμοσμένη ταχύτητα αναπαραγωγής είναι απενεργοποιημένη. diff --git a/patches/src/main/resources/youtube/translations/es-rES/strings.xml b/patches/src/main/resources/youtube/translations/es-rES/strings.xml index 105917d9a..25c0708b4 100644 --- a/patches/src/main/resources/youtube/translations/es-rES/strings.xml +++ b/patches/src/main/resources/youtube/translations/es-rES/strings.xml @@ -4,10 +4,10 @@ ¿Activar controles de accesibilidad para el reproductor de vídeo? Tus controles se modifican porque un servicio de accesibilidad está activado. - ReVanced Extended + RVX Buscar en %s Restablecer valores predeterminados. - Funciones Experimentales + Funciones experimentales ¿Quieres continuar? Reiniciar para cargar el diseño normalmente Actualizar y reiniciar @@ -74,9 +74,13 @@ Por favor, descarga %2$s desde el sitio web." Chino Anuncios + Ocultar banner de tienda de pantalla final + El banner de la tienda está oculto. + El banner de la tienda está visible. Ocultar anuncios en pantalla completa Los anuncios en pantalla completa están ocultos. Los anuncios en pantalla completa están visibles. + Los anuncios en pantalla completa están cerrados. Ocultar anuncios generales Los anuncios generales están ocultos. Los anuncios generales están visibles. @@ -264,6 +268,9 @@ Tienda" Activar filtro del menú desplegable del feed El filtro del menú desplegable del feed está activado. El filtro del menú desplegable del feed está desactivado. + Tipo de filtro del menú desplegable del feed + Filtrar si contiene.<br><br>Para ocultar el menú <b>\'Reproducir el siguiente de la cola\'</b>, puedes utilizar <b>\'Reproducir el siguiente\'</b> o <b>\'de la cola\'</b> como palabras clave. + Filtrar si coincide.<br><br>Para ocultar el menú <b>\'Reproducir el siguiente de la cola\'</b>, solo puedes utilizar <b>\'Reproducir el siguiente de la cola\'</b> como palabras clave. Filtro del menú desplegable del feed Lista de nombres del menú desplegable a filtrar separados por una nueva línea. @@ -345,6 +352,7 @@ Si el diseño de la pantalla del reproductor cambia debido a cambios en el servi General Cambiar página de inicio Predeterminada + Todas las suscripciones Explorar canales Cursos / Aprendizaje Explorar @@ -358,6 +366,7 @@ Si el diseño de la pantalla del reproductor cambia debido a cambios en el servi Música Noticias Notificaciones + Listas de reproducción Podcasts Búsqueda Compras @@ -365,6 +374,7 @@ Si el diseño de la pantalla del reproductor cambia debido a cambios en el servi Deportes Suscripciones Tendencias + Realidad virtual Ver más tarde Tus clips Cambiar tipo de página de inicio @@ -403,7 +413,7 @@ Esto no evita la restricción de edad. Solo la acepta automáticamente."Tablet Tablet (min. 600 dpi) Cambiar acción de pulsación en anillo del directo - El canal se abre al pulsar en el anillo del directo. + "El canal se abre al pulsar en el anillo del directo." El directo se abre al pulsar en el anillo del directo. Falsificar versión de la app Versión falsificada @@ -430,6 +440,9 @@ Si se desactiva más tarde, se recomienda borrar los datos de la aplicación par Ocultar menú de cuenta "Oculta elementos del menú de la cuenta y de la pestaña Tú. Algunos componentes pueden no estar ocultos." + Tipo de filtro de menú de cuenta + Filtrar si contiene.<br><br>Para ocultar el menú <b>\'Obtener YouTube Premium\'</b>, puedes utilizar <b>\'YouTube Premium\'</b> o <b>\'Premium\'</b> como palabras clave. + Filtrar si coincide.<br><br>Para ocultar el menú <b>\'Obtener YouTube Premium\'</b>, solo puedes utilizar <b>\'Obtener YouTube Premium\'</b> como palabras claves. Filtro de menú de cuenta Lista de nombres del menú de la cuenta a filtrar separados por una nueva línea. Ocultar nombre de usuario @@ -469,56 +482,6 @@ Algunos componentes pueden no estar ocultos." %s no está instalado. Por favor, instálalo. Requisito previo Se requiere YouTube Music para reemplazar la acción del botón. Pulsa aquí para descargar YouTube Music. - - Minirreproductor - Cambia el estilo del reproductor minimizado de la aplicación. - Tipo de minirreproductor - Desactivada - Original - Teléfono - Tablet - Moderno 1 - Moderno 2 - Moderno 3 - Habilitar esquinas redondeadas - Las esquinas están redondeadas. - Las esquinas son cuadradas. - Habilitar doble toque y pellizco para redimensionar - "Acción de doble toque y pulsar para cambiar el tamaño está habilitado. - -• Doble toque para aumentar el tamaño de mini reproductor. -• Doble toque de nuevo para restaurar el tamaño original." - Acción de doble toque y pellizco para redimensionar está desactivado. - Activar arrastrar y soltar - "Arrastrar y soltar está habilitado. - -El mini reproductor puede ser arrastrado a cualquier esquina de la pantalla." - Arrastrar y soltar está desactivado. - Activar gesto de arrastre horizontal. - "Gesto de arrastre horizontal habilitado. - -El mini reproductor puede ser arrastrado de la pantalla a la izquierda o derecha." - Gesto de arrastre horizontal desactivado. - Ocultar botones de expandir y cerrar - Los botones están ocultos.\n(pasa el dedo por el minirreproductor para ampliarlo o cerrarlo) - Los botones de expandir y cerrar están visibles. - Ocultar botones de expandir y cerrar - "Los botones están ocultos. - -Desliza para expandir o cerrar." - Mostrar los botones de ampliación y cierre. - Ocultar subtextos - Los subtextos están ocultos. - Los subtextos están visibles. - Ocultar botones de avanzar y retroceder - Avanzar y retroceder están ocultos. - Avanzar y retroceder están visibles. - Tamaño inicial - Inicial en tamaño de pantalla, en píxeles. - El tamaño del píxel debe estar entre %1$s y %2$s. - Opacidad de superposición - Valor de opacidad entre 0-100, donde 0 es transparente. - La opacidad del minirreproductor debe estar entre 0-100. Restablezca a los valores predeterminados. Barra de navegación Ocultar o mostrar los componentes de la sección de la barra de navegación. @@ -559,6 +522,7 @@ Si este ajuste no surte efecto, prueba a cambiar al modo incógnito." Activar barra de navegación translúcida La barra de navegación es translúcida. La barra de navegación es opaca. + En ciertas versiones de YouTube, este ajuste puede hacer transparente la barra de navegación del sistema o el diseño puede romperse en modo PIP. Ocultar barra de navegación La barra de navegación está oculta. La barra de navegación está visible. @@ -577,9 +541,9 @@ Si este ajuste no surte efecto, prueba a cambiar al modo incógnito." Ocultar menú \"Ahorro de datos\" El menú \"Ahorro de datos\" está oculto. El menú \"Ahorro de datos\" está visible. - Ocultar menú de reproducción automática o de reproducción - El menú de reproducción automática o de reproducción está oculto. - El menú de reproducción automática o de reproducción está visible. + Ocultar menú \"Reproducción automática\" + El menú de reproducción automática está oculto. + El menú de reproducción automática está visible. Ocultar menú \"Preferencias de calidad de vídeo\" El menú \"Preferencias de calidad de vídeo\" está oculto. El menú \"Preferencias de calidad de vídeo\" está visible. @@ -626,9 +590,25 @@ Si este ajuste no surte efecto, prueba a cambiar al modo incógnito." El menú \"Información\" está oculto. El menú \"Información\" está visible. + Barra de notificaciones + Ocultar o cambiar los componentes relacionados con la barra de notificaciones. Ocultar barra de notificaciones La barra de notificaciones está oculta. La barra de notificaciones está visible. + Ocultar barra de notificaciones del lado del servidor + La barra de notificaciones del lado del servidor está oculta. + La barra de notificaciones del lado del servidor está visible. + Invertir tema de barra de notificaciones + El tema de la barra de notificaciones está invertido. + El tema de la barra de notificaciones no está invertido. + Cambiar fondo de barra de notificaciones del lado del servidor + El color de fondo de la barra de notificaciones del lado del servidor ha cambiado. + El color de fondo de la barra de notificaciones del lado del servidor no ha cambiado. + "Algunas barras de notificaciones utilizan un tema definido en el lado del servidor, no el tema de la aplicación. + +Cambie el color de fondo de estas barras de notificaciones. + +Si hay cambios en el lado del servidor, es posible que el color de fondo de la barra de notificaciones no cambie." Barra de herramientas Ocultar o cambiar los componentes situados en la barra de herramientas, como los botones, la barra de búsqueda o la cabecera. @@ -793,39 +773,19 @@ La reproducción automática se puede cambiar en la configuración de YouTube: Ocultar botón de gracias El botón de gracias está oculto. El botón de gracias está visible. - - Ocultar por índice - Ocultar primer botón - El primer botón está oculto. - El primer botón está visible. - Ocultar segundo botón - El segundo botón está oculto. - El segundo botón está visible. - Ocultar tercer botón - El tercer botón está oculto. - El tercer botón está visible. - Ocultar cuarto botón - El cuarto botón está oculto. - El cuarto botón está visible. - Ocultar quinto botón - El quinto botón está oculto. - El quinto botón está visible. - Ocultar sexto botón - El sexto botón está oculto. - El sexto botón está visible. - Ocultar séptimo botón - El séptimo botón está oculto. - El séptimo botón está visible. - Ocultar octavo botón - El octavo botón está oculto. - El octavo botón está visible. - - Ocultar por índice en directo - Acerca de Ocultar botón de acción por índice - "Oculta los botones de acción por índice antes de que se inicialicen los botones de acción. + + Ocultar botón de acción por índice + "Los botones de acción están ocultos por índice. -- Al ocultar los botones de acción no se deja ningún espacio vacío. -- El índice de los botones de acción puede no ser siempre el mismo botón." +Información: +• Los botones de acción incorrectos pueden estar ocultos, o los botones de acción pueden no estar ocultos. +• Al ocultar los botones de acción no se deja un espacio vacío." + "Los botones de acción están ocultos por el filtro de identificación. + +Información: +• Los botones de acción derecha están ocultos. +• Al ocultar los botones de acción se deja un espacio vacío." + Índice de botón Remix Modo ambiente Omite las restricciones del modo ambiente o desactiva el modo ambiente. @@ -1048,6 +1008,60 @@ Limitación: el título de vídeo desaparece cuando se pulsa." Desactivar vibración al hacer zoom La vibración está desactivada. La vibración está activada. + + Mini reproductor + Ocultar o cambiar componentes relacionados con el mini reproductor. + Desactivar reanudación del mini reproductor + <b>Seguir viendo</b> no se reanudará al iniciar la aplicación. + <b>Seguir viendo</b> no se reanudará al iniciar la aplicación.<br><br> Información:<br>• <b>Seguir viendo</b> es la función de YouTube Premium.<br>• Este ajuste no obliga a activar <b>Seguir viendo</b>. + Tipo de mini reproductor + Desactivada + Original + Teléfono + Tablet + Moderno 1 + Moderno 2 + Moderno 3 + Moderno 4 + Habilitar esquinas redondeadas + Las esquinas están redondeadas. + Las esquinas son cuadradas. + Habilitar doble toque y pellizco para redimensionar + "Acción de doble toque y pulsar para cambiar el tamaño está habilitado. + +• Doble toque para aumentar el tamaño de mini reproductor. +• Doble toque de nuevo para restaurar el tamaño original." + Acción de doble toque y pellizco para redimensionar está desactivado. + Activar arrastrar y soltar + "Arrastrar y soltar está habilitado. + +El mini reproductor puede ser arrastrado a cualquier esquina de la pantalla." + Arrastrar y soltar está desactivado. + Activar gesto de arrastre horizontal. + "Gesto de arrastre horizontal habilitado. + +El mini reproductor puede ser arrastrado de la pantalla a la izquierda o derecha." + Gesto de arrastre horizontal desactivado. + Ocultar botones superpuestos + Los botones superpuestos están ocultos. + Los botones superpuestos están visibles. + Ocultar botones de expandir y cerrar + "Los botones están ocultos. + +Desliza para expandir o cerrar." + Los botones de expandir y cerrar están visibles. + Ocultar botones de avanzar y retroceder + Avanzar y retroceder están ocultos. + Avanzar y retroceder están visibles. + Ocultar subtextos + Los subtextos están ocultos. + Los subtextos están visibles. + Tamaño inicial + Inicial en tamaño de pantalla, en píxeles. + El tamaño del píxel debe estar entre %1$s y %2$s. + Opacidad de superposición + Valor de opacidad entre 0-100, donde 0 es transparente. + La opacidad del minirreproductor debe estar entre 0-100. Restablezca a los valores predeterminados. Botones del reproductor Ocultar o mostrar botones en vídeos. @@ -1150,9 +1164,11 @@ Información: Activar color personalizado de barra de progreso El color personalizado de la barra de progreso está activado. El color personalizado de la barra de progreso está desactivado. - Valor de color personalizado de barra de progreso - Escribe el código hexadecimal del color de la barra de progreso. - Valor de color de la barra de búsqueda inválido. + Color primario personalizado de barra de progreso + Escribe el código hexadecimal del color primario de la barra de progreso. + Color de acento personalizado de barra de progreso + Escribe el código hexadecimal del color de acento de la barra de progreso. + Color de la barra de progreso no válido. Activar pulsación en barra de progreso La pulsación en la barra de progreso está activada. La pulsación en la barra de progreso está desactivada. @@ -1221,11 +1237,6 @@ Esta función funciona mejor con una conexión a Internet muy rápida." Expandir descripción de vídeo La descripción del vídeo se expande automáticamente. La descripción del vídeo se expande manualmente. - Título en panel de descripción del vídeo - "Ingresa un título en el panel de descripción del vídeo. -Estos caracteres varían dependiendo de tu idioma. -\"Expandir descripción de vídeo\" puede no funcionar si guardas una cadena incorrecta." - Descripción Shorts Desactivar la reproducción en segundo plano de Shorts diff --git a/patches/src/main/resources/youtube/translations/fr-rFR/strings.xml b/patches/src/main/resources/youtube/translations/fr-rFR/strings.xml index e367e9f56..46d5ffcdb 100644 --- a/patches/src/main/resources/youtube/translations/fr-rFR/strings.xml +++ b/patches/src/main/resources/youtube/translations/fr-rFR/strings.xml @@ -4,7 +4,7 @@ Activer les contrôles d\'accessibilité pour le lecteur vidéo ? Vos contrôles sont modifiés car un service d\'accessibilité est activé. - ReVanced Extended + RVX Rechercher sur %s Réinitialiser les valeurs par défaut. Options expérimentales @@ -74,9 +74,13 @@ Veuillez télécharger %2$s à partir du site web." Chinois Publicités + Masquer la bannière de boutique de l\'écran de fin + La bannière de boutique de l\'écran de fin est masquée. + La bannière de boutique de l\'écran de fin est affichée. Masquer les publicités en plein écran Les publicités en plein écran sont masquées. Les publicités en plein écran sont affichées. + Les publicités en plein écran sont fermées. Masquer les publicités générales Les publicités générales sont masquées. Les publicités générales sont affichées. @@ -264,6 +268,9 @@ Boutique" Activer le filtre du menu déroulant Le filtre du menu déroulant est activé. Le filtre du menu déroulant est désactivé. + Type de filtre du menu déroulant dans les flux + Filtrer si contient.<br><br>Pour masquer le menu <b>\'Lire ensuite dans la file d\'\attente\'</b> vous pouvez uniquement utiliser <b>\'dans la file d\'\attente\'</b> comme mots-clés. + Filtrer si correspond.<br><br>Pour masquer le menu <b>\'Lire ensuite dans la file d\'\attente\'</b> vous pouvez uniquement utiliser <b>\'dans la file d\'\attente\'</b> comme mots-clés. Filtre du menu déroulant Liste de menus déroulant à filtrer, séparés par un saut de ligne. @@ -348,6 +355,7 @@ Si la mise en page de l'écran du lecteur change en raison de modifications côt Interface Modifier la page de démarrage Par défaut + Tous les abonnements Parcourir les chaînes Savoir / Culture Explorer @@ -361,6 +369,7 @@ Si la mise en page de l'écran du lecteur change en raison de modifications côt Musique Actualités Notifications + Playlists Podcasts Rechercher sur YouTube Produits @@ -368,6 +377,7 @@ Si la mise en page de l'écran du lecteur change en raison de modifications côt Sports Abonnements Tendances + Réalité virtuelle Regarder plus tard Vos clips Type de modification de la page de démarrage @@ -406,7 +416,9 @@ Cela ne contourne pas la restriction d'âge, mais le confirme automatiquement."< Tablette Tablette (Min 600 dpi) Changer l\'action du cercle \'En direct\' - La chaîne s\'ouvre lorsque vous cliquez sur le cercle \'En direct\'. + "La chaîne s'ouvre lorsque vous cliquez sur le cercle 'En direct'. + +Limitation : Lorsque la diffusion en direct de Shorts est ouverte dans le lecteur normal en raison du paramètre \"Ouvrir Shorts dans le lecteur normal\", la chaîne ne s'ouvre pas." La diffusion en direct s\'ouvre lorsque vous cliquez sur le cercle \'En direct\'. Falsifier la version de l\'app Version falsifiée @@ -433,6 +445,9 @@ Si désactivé ultérieurement, il est recommandé d'effacer les données de l'a Masquer le menu du compte "Masque les éléments du menu du compte et de l'onglet 'Vous'. Certains composants peuvent ne pas être masqués." + Type de filtre de menu du compte + Filtrer si contient.<br><br>Pour masquer le menu <b>\'S\'\abonner à YouTube Premium\'</b> vous pouvez uniquement utiliser <b>\'YouTube Premium\'</b> comme mots-clés. + Filtrer si correspond.<br><br>Pour masquer le menu <b>\'S\'\abonner à YouTube Premium\'</b> vous pouvez uniquement utiliser <b>\'YouTube Premium\'</b> comme mots-clés. Filtre du menu du compte Liste de noms du menu de compte à filtrer, séparés par un saut de ligne. Masquer l\'identifiant @@ -472,56 +487,6 @@ Certains composants peuvent ne pas être masqués." %s n\'est pas installé. Veuillez l’installer. Prérequis YouTube Music est requis pour remplacer l\'action du bouton. Cliquez ici pour télécharger YouTube Music. - - Mini Lecteur - Change le style du lecteur minimisé de l\'application. - Style du minilecteur - Désactivé - Original - Minimal - Tablette - Moderne 1 - Moderne 2 - Moderne 3 - Activer les coins arrondis - Les coins sont arrondis. - Les coins sont carrés. - Activer le double-appui et pincer pour redimensionner - "L'action du double appui et le pincement pour redimensionner est activée. - -• Double appui pour augmenter la taille du minilecteur. -• Double appui à nouveau pour rétablir la taille d'origine." - L\'action du double appui et du pincement pour redimensionner est désactivée. - Activer de glisser-déposer - "Le gliser-déposer est activé. - -Le minilecteur peut être déplacé dans tous les coins de l'écran." - Le glisser-déposer est désactivé. - Activer le geste de glissement horizontal. - "Le geste de glissement horizontal est activé. - -Le minilecteur peut être glissé hors de l'écran vers la gauche ou la droite." - Le geste de glissement horizontal est désactivé. - Masquer le bouton \'fermer\' - Le bouton \'Fermer\' est masqué. - Le bouton \'Fermer\' est affiché. - Masquer les boutons agrandir et fermer - "Les boutons sont masqués. - -Glissez le minilecteur pour agrandir ou fermer." - Les boutons \'agrandir\' et \'fermer\' sont affichés. - Masquer les sous-textes - Les sous-textes sont masqués. - Les sous-textes sont affichés. - Masquer les boutons \'Avancer\' et \'Reculer\' - Les boutons \'Avancer\' et \'Reculer\' sont masqués. - Les boutons \'Avancer\' et \'Reculer\' sont affichés. - Taille initiale - Taille initiale de l\'écran, en pixels. - La taille du pixel doit être comprise entre %1$s et %2$s. - Opacité du mini lecteur - Valeur d\'opacité entre 0-100, 0 étant transparent. - L\'opacité du minilecteur doit être compris entre 0-100. Barre de navigation Masque ou affiche les éléments de la barre de navigation. @@ -562,6 +527,7 @@ Si ce paramètre ne fait pas effet, essayer de passer en mode Incognito."Activer la barre de navigation translucide La barre de navigation est translucide. La barre de navigation est opaque. + Dans certaines versions de YouTube, ce paramètre peut rendre la barre de navigation du système transparente ou la disposition peut être cassée en mode PIP. Masquer la barre de navigation La barre de navigation est masqué. La barre de navigation est affichée. @@ -629,9 +595,25 @@ Si ce paramètre ne fait pas effet, essayer de passer en mode Incognito."Le menu \'À propos\' est masqué. Le menu \'À propos\' est affiché. + Snack bar + Masquer ou modifier les éléments relatifs a la snack bar. Masquer les barres d\'actions Les barres d\'actions présentes en haut ou en bas de l\'écran permettant généralement de rafraîchir la page sont masquée. Les barres d\'actions présentes en haut ou en bas de l\'écran permettant généralement de rafraîchir la page sont affiché. + Cacher la snack bar côté serveur + La snack bar côté serveur est masquée. + La snack bar côté serveur est affichée. + Inverser le thème de la snack bar + Le thème de la snack bar est inversé. + Le thème de la snack bar n\'est pas inversé. + Modifier l\'arrière-plan de la snack bar côté serveur + La couleur d\'arrière-plan de la snack bar côté serveur a été changée. + La couleur d\'arrière-plan de la snack bar côté serveur n\'a pas été changée. + "Certaines snack bar utilisent un thème défini côté serveur, et non le thème de l'application. + +Modifiez la couleur d'arrière-plan de ces snack bar + +Si des modifications sont apportées côté serveur, il se peut que la couleur d'arrière-plan de la snack bar ne change pas." Barre d\'outils Masque ou change les éléments situés dans la barre d\'outils, tels que les boutons de la barre d\'outils, la barre de recherche, l\'en-tête. @@ -796,39 +778,19 @@ La lecture automatique peut être modifiée dans les paramètres de YouTube : Masquer le bouton \'Merci\' Le bouton \'Merci\' est masqué. Le bouton \'Merci\' est affiché. - - Masquer par Index - Masquer le premier bouton - Le premier bouton est masqué. - Le premier bouton est affiché. - Masquer le second bouton - Le second bouton est masqué. - Le second bouton est affiché. - Masquer le troisième bouton - Le troisième bouton est masqué. - Le troisième bouton est affiché. - Masquer le quatrième bouton - Le quatrième bouton est masqué. - Le quatrième bouton est affiché. - Masquer le cinquième bouton - Le cinquième bouton est masqué. - Le cinquième bouton est affiché. - Masquer le sixième bouton - Le sixième bouton est masqué. - Le sixième bouton est affiché. - Masquer le septième bouton - Le septième bouton est masqué. - Le septième bouton est affiché. - Masquer le huitième bouton - Le huitième bouton est masqué. - Le huitième bouton est affiché. - - Masquer par index pour diff. en direct - A propos du masquage du bouton d\'action par l\'index - "Masquer les boutons d'action par index avant que les boutons d'action ne soient initialisés. + + Masquer le bouton d\'action par index + "Les boutons d'action sont masqués par l'index. -- Masque les boutons d'action sans laisser d'espace vide. -- L'index des boutons d'action ne correspond pas toujours au même bouton." +Info : +• Des boutons d'action incorrects peuvent être masqués, ou des boutons d'action peuvent ne pas être masqués. +• Le fait de masquer les boutons d'action ne laisse pas d'espace vide." + "Les boutons d'action sont masqués par le filtre d'identification. + +Info: +• Les boutons d'action de droite sont masqués. +• Le fait de masquer les boutons d'action laisse un espace vide." + Index du bouton \'Remix\' Mode ambiant Désactive ou contourne les restrictions du Mode ambiant. @@ -1051,6 +1013,57 @@ Limitation : Le titre de la vidéo disparaît lorsque vous cliquez dessus."Désact. vibration lors du zoom La vibration est désactivée. La vibration est activée. + + Mini Lecteur + Change le style du lecteur minimisé de l\'application. + Style du minilecteur + Désactivé + Original + Minimal + Tablette + Moderne 1 + Moderne 2 + Moderne 3 + Moderne 4 + Activer les coins arrondis + Les coins sont arrondis. + Les coins sont carrés. + Activer le double-appui et pincer pour redimensionner + "L'action du double appui et le pincement pour redimensionner est activée. + +• Double appui pour augmenter la taille du minilecteur. +• Double appui à nouveau pour rétablir la taille d'origine." + L\'action du double appui et du pincement pour redimensionner est désactivée. + Activer de glisser-déposer + "Le gliser-déposer est activé. + +Le minilecteur peut être déplacé dans tous les coins de l'écran." + Le glisser-déposer est désactivé. + Activer le geste de glissement horizontal. + "Le geste de glissement horizontal est activé. + +Le minilecteur peut être glissé hors de l'écran vers la gauche ou la droite." + Le geste de glissement horizontal est désactivé. + Masq. fond des boutons + Le fond des boutons sont masqués. + Le fond des boutons sont affichés. + Masquer les boutons agrandir et fermer + "Les boutons sont masqués. + +Glissez le minilecteur pour agrandir ou fermer." + Les boutons \'agrandir\' et \'fermer\' sont affichés. + Masquer les boutons \'Avancer\' et \'Reculer\' + Les boutons \'Avancer\' et \'Reculer\' sont masqués. + Les boutons \'Avancer\' et \'Reculer\' sont affichés. + Masquer les sous-textes + Les sous-textes sont masqués. + Les sous-textes sont affichés. + Taille initiale + Taille initiale de l\'écran, en pixels. + La taille du pixel doit être comprise entre %1$s et %2$s. + Opacité du mini lecteur + Valeur d\'opacité entre 0-100, 0 étant transparent. + L\'opacité du minilecteur doit être compris entre 0-100. Boutons du lecteur Masque ou affiche les boutons sur les vidéos. @@ -1156,9 +1169,11 @@ Appuyez longuement pour modifier le type d'information ajouté." Activer une couleur perso. barre de progression La couleur personnalisée de la barre de progression est activée. La couleur personnalisée de la barre de progression est désactivée. - Couleur perso. barre de progression - Saisissez le code hexadécimal de la couleur de la barre de progression. - Valeur de la couleur de la barre de progression invalide. + Personnaliser la couleur primaire de la barre de progression + Saisissez le code hexadécimal de la couleur primaire de la barre de progression. + Personnaliser la couleur d\'accentuation de la barre de progression + Saisissez le code hexadécimal de la couleur d\'accentuation de la barre de progression. + Couleur de la barre de progression invalide. Activer l\'appui sur la barre de progression L\'appui sur la barre de progression est activé. L\'appui sur la barre de progression est désactivé. @@ -1227,10 +1242,6 @@ Cette fonction fonctionne mieux avec une connexion internet très rapide."Ouvrir la description automatiquement La description s\'ouvre automatiquement. La description s\'ouvre manuellement. - Titre dans la description de la vidéo - "Entrez un titre dans la description de la vidéo dans votre langue. -L'option 'Ouvrir la description automatiquement' risque de ne pas fonctionner si la valeur du titre ne correspond pas au titre dans la description." - Description Shorts Désactiver la lecture en arrière-plan des Shorts diff --git a/patches/src/main/resources/youtube/translations/hu-rHU/strings.xml b/patches/src/main/resources/youtube/translations/hu-rHU/strings.xml index 557782837..88b7c4b3b 100644 --- a/patches/src/main/resources/youtube/translations/hu-rHU/strings.xml +++ b/patches/src/main/resources/youtube/translations/hu-rHU/strings.xml @@ -4,7 +4,7 @@ Engedélyezi a videolejátszó akadálymentesítési vezérlőit? Az eszközök azért módosulnak, mert egy akadálymentesítési szolgáltatás be van kapcsolva. - ReVanced Extended + RVX %s keresése Visszaállítás az alapértelmezett értékekre. Kísérleti funkciók @@ -74,9 +74,13 @@ Töltsd le a(z) %2$s weboldalról." Kínai Hirdetések + Záróképernyő üzlet banner elrejtése + Az üzlet banner el van rejtve. + Az üzlet banner látható. Teljes képernyős hirdetések elrejtése A teljes képernyős hirdetések rejtve vannak. A teljes képernyős hirdetések láthatók. + A teljes képernyős hirdetések bezárva. Általános hirdetések elrejtése Az általános hirdetések el vannak rejtve. Az általános hirdetések láthatóak. @@ -102,8 +106,8 @@ Töltsd le a(z) %2$s weboldalról." A termékek megtekintése banner el van rejtve. A termékek megtekintése banner látható. Webes keresési találatok elrejtése - A webes keresési találatok rejtve vannak - A webes keresési találatok megjelennek + A webes keresési találatok el vannak rejtve. + A webes keresési találatok láthatóak. YouTube Prémium promóció elrejtése A YouTube Prémium promóció el van rejtve. A YouTube Prémium promóció látható. @@ -112,10 +116,10 @@ Töltsd le a(z) %2$s weboldalról." Kezdőlap fül Lejátszó lejátszási listák, ajánlások Keresési találatok - Előfizetések oldal + Előfizetések fül Te lap Eredeti indexképek - DeArrow & eredeti indexképek + DeArrow és eredeti indexképek DeArrow és pillanatképek Pillanatképek A DeArrow-ról @@ -152,9 +156,9 @@ Koppints ide, ha többet szeretnél megtudni a DeArrow-ról." Az album kártyák el vannak rejtve. Az album kártyák láthatóak. Feliratok gomb elrejtése - A Feliratok gomb el van rejtve. - A Feliratok gomb megjelenik. - Forduló polc elrejtése + A feliratok gomb el van rejtve. + A feliratok gomb látható. + Karusszel polcok elrejtése "A karusszel polcok rejtettek, továbbá ezek is: • Friss hírek • Folytassa a megtekintést @@ -182,7 +186,7 @@ Koppints ide, ha többet szeretnél megtudni a DeArrow-ról." A legutóbbi bejegyzések el vannak rejtve. A legutóbbi bejegyzések láthatóak. Rejtse el a \'Legújabb videók\' gombot - A \'Legújabb videók\' gomb el van rejtve. + A legújabb videók gomb el van rejtve. A \'Legújabb videók\' gomb megjelenik. Egyveleg lejátszási listák elrejtése Az egyveleg lejátszási listák el vannak rejtve. @@ -194,20 +198,20 @@ Koppints ide, ha többet szeretnél megtudni a DeArrow-ról." Az \'Értesítsen\' gomb el van rejtve. Az \'Értesítsen\' gomb megjelenik. Lejátszható elemek elrejtése - A játékszoba rejtett - A játékszoba megjelenik + A játékszoba el van rejtve. + A játékszoba látható. Keresősáv elrejtése - A keresősáv elrejtve. + A keresősáv el van rejtve. A keresősáv látható. - \'Továbbiak megjelenítése\' gomb elrejtése - A gomb el van rejtve - A gomb megjelenik - Feliratkozások rész elrejtése - A feliratkozások rész elrejtve. - A feliratkozások rész látható. + Továbbiak megjelenítése gomb elrejtése + A továbbiak megjelenítése gomb el van rejtve. + A továbbiak megjelenítése gomb látható. + Feliratkozások karusszel elrejtése + A feliratkozások karusszel el van rejtve. + A feliratkozások karusszel látható. Kérdőívek elrejtése - A kérdőívek elrejtve. - A kérdőívek megjelennek. + A kérdőívek el vannak rejtve. + A kérdőívek láthatóak. Jegy polcok elrejtése A jegy polcok elrejtve. A jegy polcok láthatóak. @@ -216,19 +220,19 @@ Koppints ide, ha többet szeretnél megtudni a DeArrow-ról." Rejtse el vagy jelenítse meg a kategória sávot a hírfolyamban, keresésben és a kapcsolódó videókban. Rejtse el a hírfolyamban Elrejtve a hírfolyamban. - Megjelenik a hírfolyamban. + Látható a hírfolyamban. Rejtse el a kapcsolódó videókban Elrejtve a kapcsolódó videókban. - Megjelenik a kapcsolódó videókban. + Látható a kapcsolódó videókban. Rejtse el a keresési eredményekben Elrejtve a keresési eredményekben. - Megjelenik a keresési eredményekben. + Látható a keresési eredményekben. Csatorna profil Komponensek elrejtése vagy megjelenítése a csatornaprofilban. - Csatornafül szűrő bekapcsolása - A csatornafül szűrő be van kapcsolva. - A csatornafül szűrő ki van kapcsolva. + Csatornafül szűrő engedélyezése + A csatornafül szűrő engedélyezett. + A csatornafül szűrő le van tiltva. Csatornafül szűrő Szűrendő csatornafül neveinek listája, új sorral elválasztva. "Shortok @@ -236,13 +240,13 @@ Lejátszási listák Áruház" Csatornatag polc elrejtése A csatornatagok polca rejtett - A csatornatagok polca megjelenik + A csatornatagok polca látható. Csatorna profil tetején lévő linkek elrejtése A csatorna profil tetején lévő linkek el vannak rejtve. A csatorna profil tetején lévő linkek láthatóak. \"Neked\" polc elrejtése a csatorna oldalon - A polc rejtett - A polc megjelenik + A neked polc el van rejtve. + A neked polc látható. Áruház böngészése gomb elrejtése Az áruház böngészése gomb el van rejtve. Az áruház böngészése gomb látható. @@ -251,7 +255,7 @@ Lejátszási listák Közösségi bejegyzések elrejtése vagy megjelenítése a hírfolyamban és a csatornán. Elrejtése a csatornában Elrejtve a csatornában. - Megjelenítve a csatornában. + Látható a csatornában. Elrejtés a kezdőlapon és a kapcsolódó videóknál Elrejtve a kezdőlapon és a kapcsolódó videóknál. Megjelenítve a kezdőlapon és a kapcsolódó videóknál. @@ -264,6 +268,9 @@ Lejátszási listák Hírfolyam lebegő menü szűrő engedélyezése A hírfolyam lebegő menü szűrő engedélyezve van. A hírfolyam lebegő menü szűrő ki van kapcsolva. + Hírfolyam előugró menü szűrés típusa + Szűrje, ha tartalmazza.<br><br>A <b>Következő a lejátszási sorban</b> menü elrejtéséhez használhatja a <b>Következő a</b> vagy <b>lejátszási sorban</b> kulcsszót. + Szűrje, ha egyezik.<br><br>A <b>\'Következő a lejátszási sorban\'</b> menü elrejtéséhez csak a <b>\'Következő a lejátszási sorban\'</b>lehetőséget használhatja kulcsszóként. Hírfolyam lebegő menü szűrő Szűrendő lebegő menü neveinek listája, új sorokkal elválasztva. @@ -308,13 +315,13 @@ Korlátozások: Alacsony nézettségű videók elrejtése "Az 1000-nél kevesebb megtekintést elért videók elrejtése a Kezdőlapon, amiket a leiratkozott csatornákról töltöttek fel. -Lehet, hogy ez a szűrő már nem működik, használd helyette a 'Megtekintésszám szűrő' szűrőt." +Lehet, hogy ez a szűrő már nem működik, használd helyette a szűrés nézettség alapján szűrőt." Ajánlott videók elrejtése "Elrejti a következő ajánlott videókat: • Olyan videók, amelyek alatt olyan kifejezések szerepelnek, mint „Mások is megnézték”." - Megtekintések szűrő + Szűrés nézettség alapján Videók elrejtése a kezdőlapon a nézettség alapján A videók a kezdőlapon szűrve vannak. A videók a kezdőlapon nincsenek szűrve. @@ -338,7 +345,7 @@ Korlátozások: • A Shortokat nem lehet elrejteni. • A 0 megtekintésű videók nincsenek kiszűrve." Kapcsolódó videók elrejtése - A kapcsolódó videók elrejtve. + A kapcsolódó videók el vannak rejtve. A kapcsolódó videók láthatóak. "Ez a beállítás korlátozza a lejátszó képernyőjére betölthető elrendezések maximális számát. @@ -348,10 +355,11 @@ Ha a lejátszó képernyőjének elrendezése a szerveroldali változtatások mi Általános Kezdőlap megváltoztatása Alapértelmezett + Minden feliratkozás Csatornák böngészése Kurzusok / Tanulás Felfedezés - Divat & szépség + Divat és szépség Játékok Előzmények Könyvtár @@ -361,13 +369,15 @@ Ha a lejátszó képernyőjének elrendezése a szerveroldali változtatások mi Zene Hírek Értesítések - Podcastok + Lejátszási listák + Podcastek Keresés Vásárlás Shorts Sport Feliratkozások Felkapott + Virtuális valóság Megnézem később Klipjeid Kiinduló lap megváltoztatása @@ -382,13 +392,13 @@ Korlátozás: Előfordulhat, hogy az eszköztár Vissza gombja nem működik."A kényszerített automatikus feliratok le vannak tiltva. A kényszerített automatikus feliratok engedélyezve vannak. Indító animáció letiltása - Az indító animáció letiltva. - Az indító animáció engedélyezve. + Az indító animáció le van tiltva. + Az indító animáció engedélyezett. Átlátszó állapotsor letiltása - Az állapotsor nem látszik át. - Az állapotsor átlátszatlan vagy áttetsző. + Az állapotsor nem átlátszó. + Az állapotsor nem átlátszó vagy átlátszó. Színátmenetes betöltési képernyő engedélyezése - A színátmenetes betöltési képernyő engedélyezve van. + A színátmenetes betöltési képernyő engedélyezett. A színátmenetes betöltési képernyő le van tiltva. Lebegő mikrofon gomb elrejtése A lebegő mikrofon gomb el van rejtve. @@ -406,7 +416,7 @@ Ez nem kerüli meg a korhatárt. Csak automatikusan fogadja el." Tablet Tablet (min 600 dpi) Élő közvetítés ikon kattintás műveletének módosítása - A csatorna nyílik meg az ikonra kattintva. + "A csatorna nyílik meg az ikonra kattintva." Az élő közvetítés nyílik meg az ikonra kattintva. Alkalmazásverzió hamisítása Verzió hamisítás @@ -416,7 +426,7 @@ Ez nem kerüli meg a korhatárt. Csak automatikusan fogadja el." Ez meg fogja változtatni az app működését és kinézetét és nem várt mellékhatások előfordulhatnak. Ha kikapcsolja, akkor ajánlott törölni az app adatait, hogy elkerülje a UI hibákat." - Hamisított alkalmazásverzió célja + Hamisított alkalmazásverzió szerkesztése Írja be a hamis alkalmazásverziót. Hamis alkalmazásverzió 17.41.37 - Régi lejátszási lista polc visszállítása @@ -433,6 +443,9 @@ Ha kikapcsolja, akkor ajánlott törölni az app adatait, hogy elkerülje a UI h Fiókmenü elrejtése "Fiókmenü és az Te lap elemeinek elrejtése. Előfordulhat, hogy egyes komponensek nincsenek elrejtve." + Fiók menü szűrés típusa + Szűrje, ha tartalmazza.<br><br>A <b>YouTube Prémium beszerzése</b> menü elrejtéséhez használhatja a <b>YouTube Prémium</b> vagy a <b>Prémium</b> kulcsszót. + Szűrje, ha egyezik.<br><br>A <br>YouTube Prémium beszerzése<br> menü elrejtéséhez csak a <b>YouTube Prémium beszerzése</b> lehetőséget használhatja kulcsszóként. Fiók menü szűrő A fiók menüben szűrendő menüpontok listája, új sorokkal elválasztva. Kezelő elrejtése @@ -442,8 +455,8 @@ Előfordulhat, hogy egyes komponensek nincsenek elrejtve." Egyéni szűrő Komponensek elrejtése egyéni szűrőkkel. Egyéni szűrő engedélyezése - Az egyéni szűrő engedélyezve. - Az egyéni szűrő letiltva. + Az egyéni szűrő engedélyezve van. + Az egyéni szűrő le van tiltva. Egyéni szűrő A szűrendő összetevők listája új sorokkal elválasztva. @@ -472,56 +485,6 @@ Előfordulhat, hogy egyes komponensek nincsenek elrejtve." %s nincs telepítve. Kérlek telepítsd. Előfeltétel A YouTube Music szükséges a gombművelet felülbírálásához. Koppints ide a YouTube Music letöltéséhez. - - Minilejátszó - Módosítsa az alkalmazáson belüli minilejátszó stílusát. - Minilejátszó típusa - Letiltva - Eredeti - Minimális - Tablet - Modern 1 - Modern 2 - Modern 3 - Lekerekített sarkok engedélyezése - A sarkok lekerekítettek. - A sarkok négyzet alakúak. - Dupla koppintás és összehúzás engedélyezése átméretezéshez - "A dupla koppintás és összehúzás átméretezéshez engedélyezett. - -• Koppintson duplán a minilejátszó méretének növeléséhez. -• Koppintson duplán az eredeti méret visszaállításához." - A dupla koppintás és összehúzás átméretezéshez le van tiltva. - Fogd és vidd engedélyezése - "A fogd és vidd engedélyezett - -A minilejátszó a képernyő bármely sarkába elhúzható." - A fogd és vidd le van tiltva. - Vízszintes húzás engedélyezése. - "A vízszintes húzás engedélyezett. - -A minilejátszó jobbra vagy balra húzható a képernyőn." - A vízszintes húzás le van tiltva. - Kibontás és bezárás gombok elrejtése - A bezárás gomb el van rejtve. - A kibontás és bezárás gombok láthatóak. - Kibontás és bezárás gombok elrejtése - "A gombok rejtettek. - -Csúsztasson a kibontáshoz vagy bezáráshoz." - A kibontás és bezárás gombok láthatóak. - Alszövegek elrejtése - Az alszövegek elrejtve. - Az alszövegek megjelennek. - Előre és vissza ugrás gombok elrejtése - Az előre és hátra ugrás elrejtve. - Az előre és hátra ugrás láthatóak. - Kezdeti méret - Kezdeti méret a képernyőn, képpontban. - A képpont méretének %1$s és %2$s között kell lennie. - Átfedés átlátszósága - Átlátszósági érték 0 és 100 között, ahol a 0 az átlátszó. - A minilejátszó átlátszóságának 0 és 100 között kell lennie. Visszaállítás alapértelmezettre. Navigációs sáv Navigációs sáv komponenseinek láthatósága. @@ -550,18 +513,19 @@ Csúsztasson a kibontáshoz vagy bezáráshoz." A navigációs sáv el van rejtve. A navigációs sáv látható. Létrehozás és értesítések gombok felcserélése - "A Létrehozás gomb megcserélése az Értesítések gombbal. + "A létrehozás gomb helyet cserél az értesítések gombbal. -Megjegyzés: Engedélyezés esetén a videó hirdetéseket is elrejti." +Megjegyzés: engedélyezés esetén a videó hirdetéseket is elrejti." A Létrehozás gomb nincs felcserélve az Értesítések gombbal. "Ennek kikapcsolása esetleg több reklámot tölt be a szerverről. Tovább, a reklámok nem lesznek tiltva a Shortokban. Ha ez a beállítás nem működik, váltson inkognító módra." - Áttetsző navigációs sáv engedélyezése - A navigációs sáv áttetsző. - A navigációs sáv nem áttetsző. + Átlátszó navigációs sáv engedélyezése + A navigációs sáv átlátszó. + A navigációs sáv nem átlátszó. + Egyes YouTube verziókban ez a beállítás átlátszóvá teheti a rendszer navigációs sávját, vagy kép a képben módban az elrendezés széteshet. Navigációs sáv elrejtése A navigációs sáv el van rejtve. A navigációs sáv látható. @@ -683,7 +647,7 @@ Ebben az esetben előfordulhat, hogy a következő elérési utat kell használn A bélyegképek a keresési kifejezések előzményeiben el vannak rejtve. A bélyegképek a keresési kifejezések előzményeiben láthatóak. Képkeresés gomb elrejtése - A képkeresés gomb elrejtve. + A képkeresés gomb el van rejtve. A képkeresés gomb látható. Hangkeresés gomb elrejtése A hangkeresés gomb el van rejtve. @@ -729,9 +693,9 @@ Megjegyzés: Közösségi finanszírozás elrejtése A közösségi finanszírozás el van rejtve. A közösségi finanszírozás látható. - Dupla koppintás átfedés szűrő elrejtése - A dupla koppintásos átfedés szűrő elrejtve. - A dupla koppintásos átfedés szűrő látható. + Dupla koppintás átfedés elrejtése + A dupla koppintásos átfedés elrejtve. + A dupla koppintásos átfedés látható. Záróképernyő kártyák elrejtése A záróképernyő kártyák el vannak rejtve. A záróképernyő kártyák láthatóak. @@ -810,39 +774,19 @@ Beállítások → Automatikus lejátszás → Következő videó automatikus le Köszönet gomb elrejtése A köszönet gomb el van rejtve. A köszönet gomb látható. - - Elrejtés index alapján - Első gomb elrejtése - Az első gomb el van rejtve. - Az első gomb látható. - Második gomb elrejtése - A második gomb el van rejtve. - A második gomb látható. - Harmadik gomb elrejtése - A harmadik gomb el van rejtve. - A harmadik gomb látható. - Negyedik gomb elrejtése - A negyedik gomb el van rejtve. - A negyedik gomb látható. - Ötödik gomb elrejtése - Az ötödik gomb el van rejtve. - Az ötödik gomb látható. - Hatodik gomb elrejtése - A hatodik gomb el van rejtve. - A hatodik gomb látható. - Hetedik gomb elrejtése - A hetedik gomb el van rejtve. - A hetedik gomb látható. - Nyolcadik gomb elrejtése - A nyolcadik gomb el van rejtve. - A nyolcadik gomb látható. - - Elrejtés az élő közvetítésben index alapján - Műveletgomb elrejtése index alapján - "A műveletgombok index szerint elrejtése, mielőtt inicializálná azokat. + + Műveletgomb elrejtése index alapján + "A műveletgombok index alapján vannak elrejtve. -- A műveletgombok elrejtése nem hagy üres helyet. -- Előfordulhat, hogy a műveletgombok indexe nem mindig ugyanaz a gomb." +Információ: +• Előfordulhat, hogy a műveletgombok nincsenek vagy rosszak vannak elrejtve. +• A műveletgombok elrejtése nem hagy üres helyet." + "A műveletgombokat az azonosító szűrő rejti el. + +Információ: +• A megfelelő műveletgombok vannak elrejtve. +• A műveletgombok elrejtése üres helyet hagy." + Gomb index megkeverése Mozifilmes világítás mód Tiltsa le a mozifilmes világítás módot, vagy kerülje meg a korlátozásait. @@ -946,7 +890,7 @@ Beállítások → Automatikus lejátszás → Következő videó automatikus le A \'Mozifilmes világítás\' menü látható. Segítség és visszajelzés menü elrejtése A segítség és visszajelzés menü el van rejtve. - A segítség és visszajelzés menü megjelenik. + A segítség és visszajelzés menü látható. Hallgatás a YouTube Music-al menü elrejtése A YouTube Music-kal való hallgatás menü el van rejtve. A YouTube Music-kal való hallgatás menü megjelenik. @@ -957,8 +901,8 @@ Beállítások → Automatikus lejátszás → Következő videó automatikus le A kép-a-képben menü el van rejtve. A kép-a-képben menü látható. Prémium vezérlők menü elrejtése - A Prémium vezérlők menü el van rejtve. - A Prémium vezérlők menü megjelenik. + A prémium vezérlők menü el van rejtve. + A prémium vezérlők menü látható. Elalvási időzítő elrejtése Az elalvási időzítő el van rejtve. Az elalvási időzítő látható. @@ -1002,9 +946,9 @@ Korlátozás: A videó címe eltűnik, ha rákattint." A további videók rész a gyors menüben és a kapcsolódó videó láthatóak. Gyors műveletek - Gyorsműveletek konténer elrejtése + Gyorsműveletek elrejtése A gyorsműveletek el vannak rejtve. - A gyorsműveletek megjelennek. + A gyorsműveletek láthatóak. Hozzászólás gomb elrejtése A hozzászólás gomb el van rejtve. A hozzászólás gomb látható. @@ -1065,6 +1009,60 @@ Korlátozás: A videó címe eltűnik, ha rákattint." Nagyítás haptikus visszajelzésének letiltása A haptikus visszajelzés le van tiltva. A haptikus visszajelzés engedélyezett. + + Minilejátszó + Módosítsa az alkalmazáson belüli minilejátszó stílusát. + Minilejátszó folytatásának letiltása + A <b>Folytassa a lejátszást</b> nem fog elindulni az alkalmazás indításakor. + A <b>Folytassa a lejátszást</b> el fog indulni az alkalmazás indításakor. <br><br>Infó:<br>• A <b>Folytassa a lejátszást</b> egy YouTube Prémium funkció.<br>• Ez a beállítás nem erőlteti a <b>Folytassa a lejátszást</b> engedélyezését. + Minilejátszó típusa + Letiltva + Eredeti + Minimális + Tablet + Modern 1 + Modern 2 + Modern 3 + Modern 4 + Lekerekített sarkok engedélyezése + A sarkok lekerekítettek. + A sarkok négyzet alakúak. + Dupla koppintás és összehúzás engedélyezése átméretezéshez + "A dupla koppintás és összehúzás átméretezéshez engedélyezett. + +• Koppintson duplán a minilejátszó méretének növeléséhez. +• Koppintson duplán az eredeti méret visszaállításához." + A dupla koppintás és összehúzás átméretezéshez le van tiltva. + Fogd és vidd engedélyezése + "A fogd és vidd engedélyezett. + +A minilejátszó a képernyő bármely sarkába elhúzható." + A fogd és vidd le van tiltva. + Vízszintes húzás engedélyezése. + "A vízszintes húzás engedélyezett. + +A minilejátszó jobbra vagy balra húzható a képernyőn." + A vízszintes húzás le van tiltva. + Átfedés gombok elrejtése + Az átfedés gombok el vannak rejtve. + Az átfedés gombok láthatóak. + Kibontás és bezárás gombok elrejtése + "A gombok rejtettek. + +Csúsztasson a kibontáshoz vagy bezáráshoz." + A kibontás és bezárás gombok láthatóak. + Előre és vissza ugrás gombok elrejtése + Az előre és hátra ugrás elrejtve. + Az előre és hátra ugrás láthatóak. + Alszövegek elrejtése + Az alszövegek elrejtve. + Az alszövegek megjelennek. + Kezdeti méret + Kezdeti méret a képernyőn, képpontban. + A képpont méretének %1$s és %2$s között kell lennie. + Átfedés átlátszósága + Átlátszósági érték 0 és 100 között, ahol a 0 az átlátszó. + A minilejátszó átlátszóságának 0 és 100 között kell lennie. Visszaállítás alapértelmezettre. Lejátszó gombok A videólejátszó gombok elrejtése vagy megjelenítése. @@ -1083,7 +1081,7 @@ Korlátozás: A videó címe eltűnik, ha rákattint." Teljes képernyős gomb elrejtése A teljes képernyős gomb el van rejtve. A teljes képernyős gomb látható. - Előző és következő gomb elrejtése + Előző és következő gombok elrejtése A gombok el vannak rejtve. A gombok láthatóak. YouTube Music gomb elrejtése @@ -1132,12 +1130,12 @@ Információ: Csak tagoknak szóló élő videók A lejátszási lista nem hozható létre csatornaazonosító eltérés miatt. Csatorna kivétellista - Ellenőrizd vagy távolítsd el az kivétellistához hozzáadott csatornákat. + Ellenőrizze vagy távolítsa el a kivétellistához hozzáadott csatornákat. \'%1$s\' csatorna hozzá lett adva a %2$s kivételekhez. Nem sikerült hozzáadni a(z) \'%1$s\' csatornát a(z) %2$s engedélyezőlistához. \'%1$s\' csatorna törölve a(z) %2$s engedélyezőlistáról. Nem sikerült eltávolítani a(z) \'%1$s\' csatornát a(z) %2$s engedélyezőlistáról. - Eltávolítod a(z) \'%1$s\' csatornát a(z) %2$s engedélyezőlistáról? + Eltávolítja a(z) \'%1$s\' csatornát a(z) %2$s engedélyezőlistáról? Nincsenek engedélyezett csatornák. Nem lett hozzáadva az engedélyezettekhez. Hozzáadva az engedélyezettekhez. @@ -1170,9 +1168,11 @@ Információ: Egyéni keresősáv szín engedélyezése Az egyéni keresősáv szín engedélyezett. Az egyéni keresősáv szín le van tiltva. - Egyéni keresősáv szín - Írja be a keresősáv színének hexadecimális kódját. - Érvénytelen keresősáv színértéke. + Keresősáv egyéni elsődleges színe + Írja be a keresősáv elsődleges színének hexadecimális kódját. + Keresősáv egyéni kiemelő színe + Írja be a keresősáv kiemelő színének hexadecimális kódját. + Érvénytelen keresősáv szín. Érintés engedélyezése a kereső sávon A keresősávon történő érintés engedélyezett. A keresősávon történő érintés le van tiltva. @@ -1241,10 +1241,6 @@ Ez a funkció nagyon gyors internetkapcsolat mellett működik a legjobban."Videoleírások kibővítése A videoleírások automatikusan kibővülnek. A videoleírások nem bővülnek ki automatikusan. - Cím a videoleírás panelen - "Írja be a videoleírás panel nevét az Ön nyelvén. -A videoleírások kibővítése nem működik, ha a beírt név nem egyezik meg a videoleírás panel nevével." - Leírás Shorts Shortok háttérben történő lejátszásának tiltása @@ -1353,8 +1349,8 @@ Információ: A vásárlás gomb el van rejtve. A vásárlás gomb látható. Szuper köszönet gomb elrejtése - A Szuper köszönet gomb el van rejtve. - A Szuper köszönet gomb látható. + A szuper köszönet gomb el van rejtve. + A szuper köszönet gomb látható. Megjelölt termékek elrejtése A címkézett termékek el vannak rejtve. A címkézett termékek láthatóak. @@ -1365,7 +1361,7 @@ Információ: Az ennek a zenének a használata gomb el van rejtve. Az ennek a zenének a használata gomb látható. - Akció gombok + Művelet gombok Tetszik gomb elrejtése A tetszik gomb el van rejtve. A tetszik gomb látható. @@ -1387,8 +1383,8 @@ Információ: Animáció / Visszajelzés Tetszik gomb szökőkút animáció elrejtése - A szökőkút animáció le van tiltva a Tetszik gombon. - A szökőkút animáció engedélyezve van a Tetszik gombon. + A szökőkút animáció le van tiltva a tetszik gombon. + A szökőkút animáció engedélyezett a tetszik gombon. Dupla koppintás animáció Eredeti Felfelé menő hüvelykujj @@ -1396,9 +1392,9 @@ Információ: Szív Szív (színárnyalatos) Rejtett - Lejátszás & Szünet gomb hátterének elrejtése - A lejátszás & szünet gombok háttere el van rejtve. - A gomb háttere látható. + Lejátszás és szünet gombok hátterének elrejtése + A lejátszás és szünet gombok háttere el van rejtve. + A lejátszás és szünet gombok háttere látható. Egyéni műveletek Egyéni műveletek engedélyezése a felugró menüben @@ -1437,7 +1433,7 @@ Nyomja meg és tartsa lenyomva a További gombot az egyéni műveletek párbesz Az egyéni műveletekről "Ez a funkció még kísérleti jellegű, így nincs garancia arra, hogy tökéletesen fog működni. -A legtöbb hiba nem javítható az ügyféloldali korlátozások miatt, ezért csak tesztelési célokra használd." +A legtöbb hiba nem javítható az ügyféloldali korlátozások miatt, ezért csak tesztelési célokra használja." Fejezetek engedélyezése "Az időbélyeg engedélyezve van. @@ -1461,8 +1457,8 @@ Korlátozások: Az eszköztár el van rejtve. Az eszköztár látható. Cserélje ki a csatorna kezelőt - A csatorna név használatban van. - A csatorna kezelő használatban van. + A csatorna név van használatban. + A csatorna kezelő van használatban. Régi lejátszó felület visszaállítása "A régi lejátszó felület van használva. Nincs margó a lejátszó tetején és alján." @@ -1745,7 +1741,7 @@ Kattintson az API-kulcs kiadás folyamatának megtekintéséhez." Az irányelvek szabályokat és ötleteket tartalmaznak a szakaszok beküldésével kapcsolatban. Kövesd az irányelveket Olvassa el a SponsorBlock irányelveket szakaszok beküldése előtt. - Már elolvastad + Már olvasott Mutasd Általános @@ -1753,13 +1749,13 @@ Kattintson az API-kulcs kiadás folyamatának megtekintéséhez." Üzenet látható, ha a SponsorBlock nem elérhető. Nem látható üzenet, ha a SponsorBlock nem elérhető. Átugrások számolásának engedélyezése - Értesíti a SponsorBlock ranglistáját, hogy mennyi időt takarítottál meg. Minden egyes szakasz kihagyásakor üzenetet küld a ranglistának. + Értesíti a SponsorBlock ranglistáját, hogy mennyi időt takarított meg. Minden egyes szakasz kihagyásakor üzenetet küld a ranglistának. A kihagyások számának követése nem engedélyezett. - Minimálus szakasz időtartam + Minimális szakasz időtartam A beállított értéknél (másodpercben) rövidebb szakaszokat nem hagyja ki vagy jeleníti meg. Érvénytelen időtartam. - Az privát felhasználói azonosítód - Ezt bizalmasan kell kezelni. Olyan mint egy jelszó és senkivel sem ajánlott megosztani. Ha valaki megszerzi, meg tud személyesíteni téged. + A privát felhasználói azonosítója + Ezt bizalmasan kell kezelni. Ez olyan, mint egy jelszó, és nem szabad megosztani senkivel. Ha ezt valaki megszerzi, akkor megszemélyesítheti Önt. A privát felhasználói azonosítónak legalább 30 karakter hosszúnak kell lennie. API URL módosítása Az a cím, amelyet a SponsorBlock a szervere eléréséhez használ. @@ -1773,7 +1769,7 @@ Kattintson az API-kulcs kiadás folyamatának megtekintéséhez." A beállítások sikeresen importálva. Nem sikerült importálni: %s. Nem sikerült exportálni: %s. - A beállításaid tartalmazzák a privát SponsorBlock felhasználói azonosítót.\n\nA felhasználói azonosító olyan, mint egy jelszó, és soha nem szabad megosztani.\n + A beállításai tartalmazzák a privát SponsorBlock felhasználói azonosítót.\n\nA felhasználói azonosító olyan, mint egy jelszó, és soha nem szabad megosztani.\n Ne jelenjen meg többet A SponsorBlock átmenetileg nem elérhető. A SponsorBlock átmenetileg nem elérhető (állapot %d). @@ -1802,7 +1798,7 @@ Kattintson az API-kulcs kiadás folyamatának megtekintéséhez." A szakasz kezdetének időpontja A szakasz végének időpontja Helyesek az időpontok? - A szegmens:\n\n%1$s-tól\n\n%2$s-ig\n\n(%3$s)\n\nKészen állsz a beküldésre? + A szakasz:\n\n%1$s-tól\n\n%2$s-ig\n\n(%3$s)\n\nKészen állsz a beküldésre? A kezdetnek a vége előtt kell lennie. Előbb jelöljön meg két pontot az idősávon. Szakasz előnézete a zökkenőmentes kihagyás érdekében. @@ -1814,27 +1810,27 @@ Kattintson az API-kulcs kiadás folyamatának megtekintéséhez." A statisztikák átmenetileg nem elérhetőek (API leállt). Betöltés... A SponsorBlock le van tiltva. - Az felhasználóneved: <b>%s</b> - Koppints ide a felhasználónév megváltoztatásához + A felhasználóneve: <b>%s</b> + Koppintson ide a felhasználónév megváltoztatásához A felhasználónév nem módosítható: Állapot: %1$d %2$s. A felhasználónév sikeresen módosítva. - Az hírneved: <b>%.2f</b> - <b>%s</b> szakaszt készítettél + A hírneve: <b>%.2f</b> + <b>%s</b> szakaszt készített Koppintson ide a szegmensek megtekintéséhez. SponsorBlock ranglista - <b>%s</b> szakasztól mentettél meg másokat - Koppints ide a globális statisztikák és a kiemelt közreműködők megtekintéséhez. + <b>%s</b> szakasztól mentett meg másokat + Koppintson a globális statisztikák és a kiemelt közreműködők megtekintéséhez. Ez <b>%s</b> az életükből.<br>Koppintson a ranglista megtekintéséhez. - <b>%s</b> szakaszt hagytál ki + Kihagyott <b>%s</b> szakaszt Ez <b>%s</b>. - Visszaállítod a kihagyott szakaszok számlálóját? + Visszaállítja a kihagyott szakaszok számlálóját? %1$s óra %2$s perc %1$s perc %2$s másodperc %s másodperc Névjegy sponsor.ajay.app - Az adatokat a SponsorBlock API biztosítja. Koppints további információért és a más platformokra való letöltések megtekintéséhez. + Az adatokat a SponsorBlock API biztosítja. Koppintson további infóért és a más platformokra való letöltések megtekintéséhez. Vegyes URL átirányítások kikerülése @@ -1857,19 +1853,19 @@ Kattintson az API-kulcs kiadás folyamatának megtekintéséhez." YouTube linkek RVX-el való megnyitásához, engedélyezze a támogatott hivatkozások megnyitását és engedélyezze az összes támogatott webcímet. GmsCore beállítások megnyitása Engedélyezze a felhő alapú üzenetküldést az értesítések fogadásához. - MicroG GmsCore nincs telepítve. Telepítsd. + A MicroG GmsCore nincs telepítve. Telepítse. Művelet szükséges - "A MicroG GmsCore nem rendelkezik engedéllyel, hogy a háttérben futhasson. + "A MicroG GmsCore nem rendelkezik engedéllyel, hogy a háttérben fusson. -Kövesse a \"Don't kill my app\" útmutatót a telefonjához és alkalmazza a leírtakat a MicroG telepítésre. +Kövesse a \"Don't kill my app\" útmutatót a telefonjához és alkalmazza a leírtakat a MicroG GmsCore telepítésre. Ez szükséges az alkalmazás működéséhez." Webhely megnyitása - "A GmsCore akkumulátor-optimalizálását ki kell kapcsolni a problémák megelőzése érdekében. + "A MicroG GmsCore akkumulátor optimalizálását ki kell kapcsolni a problémák megelőzése érdekében. Az akkumulátor-optimalizálás letiltása nem befolyásolja negatívan az akkumulátor használatát. -Kattints a folytatás gombra és kapcsold ki az akkumulátor-optimalizálásokat." +Kattintson a folytatás gombra és kapcsolja ki az akkumulátor optimalizálásokat." Folytatás Megosztási lap módosítása A rendszer megosztási lap van használatban. @@ -1935,9 +1931,9 @@ Az AVC maximális felbontása 1080p, az Opus hangkodek nem érhető el, és a vi VR alapértelmezett audio adatfolyam nyelve PoToken / VisitorData - PoToken használatához + PoToken használatban A BotGuard által kibocsátott PoToken egy megbízható böngészőben. - VisitorData használatához + VisitorData használatban A BotGuard által kibocsátott VisitorData egy megbízható böngészőben. A PoToken-ről / VisitorData-ról "Néhány kliensnek PoToken és VisitorData szükséges az adatok érvényes folyamatos adatátvitelére adott válaszhoz. @@ -1963,11 +1959,11 @@ Kattintson a további információkért." Patch információ Patch információ - Információk az alkalmazott javításokról. + Információ az alkalmazott patchekről. Használt eszköz - Egyéb + Egyebek Egyéni Alap Kék Afn diff --git a/patches/src/main/resources/youtube/translations/it-rIT/strings.xml b/patches/src/main/resources/youtube/translations/it-rIT/strings.xml index 22a27e293..9101f1947 100644 --- a/patches/src/main/resources/youtube/translations/it-rIT/strings.xml +++ b/patches/src/main/resources/youtube/translations/it-rIT/strings.xml @@ -4,7 +4,7 @@ Vuoi attivare i controlli di accessibilità del riproduttore? I tuoi controlli sono diversi poiché un servizio di accessibilità è attivato. - ReVanced Extended + RVX Cerca su %s Ripristinato ai valori predefiniti Opzioni sperimentali @@ -74,9 +74,13 @@ Si prega di scaricare %2$s dal sito web." Cinese Annunci + Nascondi il banner del negozio alla fine dei video + Il banner del negozio alla fine dei video è nascosto. + Il banner del negozio alla fine dei video è visibile. Nascondi gli annunci a schermo intero Gli annunci a schermo intero sono nascosti. Gli annunci a schermo intero sono visibili. + Annunci a schermo intero chiusi Nascondi gli annunci generali Gli annunci generali sono nascosti. Gli annunci generali sono visibili. @@ -247,8 +251,8 @@ Negozio" Il pulsante Esplora Negozio è nascosto. Il pulsante Esplora Negozio è visibile. - Post della community - Nascondi o mostra i post della community nelle schede e nel canale. + Post delle community + Nascondi o mostra i post delle community nelle schede e nel canale. Nascondi nel canale Sono nascosti nel canale. Sono visibili nel canale. @@ -264,6 +268,9 @@ Negozio" Attiva il filtro dei menù a comparsa Il filtro dei menù a comparsa è attivato. Il filtro dei menù a comparsa è disattivato. + Cambia il tipo di filtro dei menù a comparsa + Filtra se contiene.<br><br>Per nascondere il menù <b>Riproduci come il prossimo in coda</b>, puoi usare le parole chiave come <b>Riproduci come</b> o <b>in coda</b>. + Filtra se corrisponde.<br><br>Per nascondere il menù <b>Riproduci come il prossimo in coda</b>, puoi usare solo la parola chiave <b>Riproduci come il prossimo in coda</b>. Filtro dei menù a comparsa L\'elenco dei nomi dei menù a comparsa da filtrare, separati da nuove righe. @@ -346,6 +353,7 @@ Se l'interfaccia della schermata del riproduttore cambia a causa di modifiche la Generali Cambia la scheda iniziale Predefinita (Home) + Tutte le iscrizioni Esplora canali Apprendimento Esplora @@ -359,6 +367,7 @@ Se l'interfaccia della schermata del riproduttore cambia a causa di modifiche la Musica Notizie Notifiche + Playlist Podcast Cerca Shopping @@ -366,6 +375,7 @@ Se l'interfaccia della schermata del riproduttore cambia a causa di modifiche la Sport Iscrizioni Tendenze + Realtà virtuale Guarda più tardi I tuoi clip Cambia il comportamento della pagina iniziale @@ -404,7 +414,9 @@ Nota: questo non bypassa la restrizione di età, ma la accetta automaticamente." Tablet Tablet (Minimo 600 dp) Cambia cosa apre l\'anello live quando viene toccato - Canale + "Canale + +Nota: i canali non si aprono se gli Shorts live sono aperti nel riproduttore normale." Video live Attiva il camuffamento della versione dell\'app Il camuffamento della versione dell\'app è attivato. @@ -431,6 +443,9 @@ Se in seguito verrà disattivato, si consiglia di cancellare i dati dell'app per Nascondi i menù dell\'account "Nascondi i componenti dei menù dell'account e della scheda Tu. Nota: alcuni componenti potrebbero non essere nascosti." + Cambia il tipo di filtro dei menù dell\'account + Filtra se contiene.<br><br>Per nascondere il menù <b>Passa a YouTube Premium</b>, puoi usare le parole chiave come <b>YouTube Premium</b> o <b>Premium</b>. + Filtra se corrisponde.<br><br>Per nascondere il menù <b>Passa a YouTube Premium</b>, puoi usare solo la parola chiave <b>Passa a YouTube Premium</b>. Modifica il filtro dei menù dell\'account L\'elenco dei nomi dei menù degli account da filtrare, separati da nuove righe. Nascondi l\'handle @@ -470,57 +485,6 @@ Nota: alcuni componenti potrebbero non essere nascosti." %s non è installato. Per favore installalo. Prerequisito L\'app YouTube Music è necessario per sovrascrivere l\'azione del pulsante. Tocca qui per scaricare YouTube Music. - - Riproduttore minimizzato - Personalizza i componenti del riproduttore minimizzato. - Cambia lo stile del riproduttore minimizzato - Disattivato - Originale - Minimale - Tablet - Moderno 1 - Moderno 2 - Moderno 3 - Attiva gli angoli arrotondati - Gli angoli sono arrotondati. - Gli angoli sono quadrati. - Attiva il gesto Doppio Tocco e Pizzico per ridimensionare - "Il gesto Doppio Tocco e Pizzico per ridimensionare è attivato. - -Note: -• Tocca due volte per aumentare la dimesione del riproduttore minimizzato. -• Tocca di nuovo due volte per ripristinare la dimensione originale." - Il gesto Doppio Tocco e Pizzico per ridimensionare è disattivato. - Attiva il gesto Trascina e Rilascia - "Il gesto Trascina e Rilascia è attivato. - -Il riproduttore minimizzato può essere trascinato in qualsiasi angolo dello schermo." - Il gesto Trascina e Rilascia è disattivato. - Attiva il gesto Trascina Orizzontalmente - "Il gesto Trascina Orizzontalmente è attivato. - -Il riproduttore minimizzato può essere trascinato fuori dallo schermo a sinistra o a destra." - Il gesto Trascina Orizzontalmente è disattivato. - Nascondi il pulsante Chiudi - Il pulsante Chiudi è nascosto. - Il pulsante Chiudi è visibile. - Nascondi i pulsanti Espandi e Chiudi - "I pulsanti Espandi e Chiudi sono nascosti. - -Trascina il riproduttore minimizzato per espandere o chiudere." - I pulsanti Espandi e Chiudi sono visibili. - Nascondi i sottotesti - I sottotesti sono nascosti. - I sottotesti sono visibili. - Nascondi i pulsanti Salta Avanti e Salta Indietro - I pulsanti Salta Avanti e Salta Indietro sono nascosti. - I pulsanti Salta Avanti e Salta Indietro sono visibili. - Dimensione iniziale - La dimensione iniziale sullo schermo in pixel. - La dimensione in pixel deve essere tra %1$s e %2$s - Opacità della sovrapposizione - Il valore dell\'opacità è tra 0 e 100, dove 0 è trasparente. - L\'opacità della sovrapposizione del riproduttore minimizzato deve essere tra 0 e 100 Barra di navigazione Personalizza i componenti della barra di navigazione. @@ -561,6 +525,7 @@ Se questa impostazione non ha effetto, prova a passare alla navigazione in incog Attiva la barra di navigazione traslucida La barra di navigazione è traslucida. La barra di navigazione è opaca. + In alcune versioni di YouTube, questa impostazione può rendere trasparente la barra di navigazione del sistema, oppure l\'interfaccia potrebbe non funzionare correttamente in modalità PIP. Nascondi la barra di navigazione La barra di navigazione è nascosta. La barra di navigazione è visibile. @@ -759,12 +724,12 @@ Note: Nascondi le azioni consigliate Le azioni consigliate sono nascoste. Le azioni consigliate sono visibili. - Nascondi la schermata finale del video consigliato - "La schermata finale del video consigliato è nascosta quando la riproduzione automatica è disattivata. + Nascondi il video consigliato alla fine dei video + "Il video consigliato alla fine dei video è nascosto quando la riproduzione automatica è disattivata. La riproduzione automatica può essere modificata seguendo questo percorso: Impostazioni → Riproduzione automatica / Riproduzione → Riproduci automatic. video successivo" - La schermata finale del video consigliato è visibile. + Il video consigliato alla fine dei video è visibile. Salta il conto alla rovescia della riproduzione automatica Se la riproduzione automatica è attivata, il video successivo verrà riprodotto immediatamente. Se la riproduzione automatica è attivata, il video successivo verrà riprodotto al termine del conto alla rovescia. @@ -813,38 +778,19 @@ Impostazioni → Riproduzione automatica / Riproduzione → Riproduci automatic. Nascondi il pulsante Grazie Il pulsante Grazie è nascosto. Il pulsante Grazie è visibile. - - Nascondi in base all\'indice - Nascondi il primo pulsante - Il primo pulsante è nascosto. - Il primo pulsante è visibile. - Nascondi il secondo pulsante - Il secondo pulsante è nascosto. - Il secondo pulsante è visibile. - Nascondi il terzo pulsante - Il terzo pulsante è nascosto. - Il terzo pulsante è visibile. - Nascondi il quarto pulsante - Il quarto pulsante è nascosto. - Il quarto pulsante è visibile. - Nascondi il quinto pulsante - Il quinto pulsante è nascosto. - Il quinto pulsante è visibile. - Nascondi il sesto pulsante - Il sesto pulsante è nascosto. - Il sesto pulsante è visibile. - Nascondi il settimo pulsante - Il settimo pulsante è nascosto. - Il settimo pulsante è visibile. - Nascondi l\'ottavo pulsante - L\'ottavo pulsante è nascosto. - L\'ottavo pulsante è visibile. - - Nascondi in base all\'indice nei video live - Informazioni sul nascondere in base all\'indice - "Nascondi i pulsanti di azione in base al loro indice prima che vengano inizializzati. + + Nascondi i pulsanti di azione in base all\'indice + "I pulsanti di azione sono nascosti in base all'indice. -Nota: in questo modo non ci saranno spazi vuoti tra i pulsanti, ma l'indice potrebbe non corrispondere sempre allo stesso pulsante." +Note: +• I pulsanti sbagliati potrebbero essere nascosti o potrebbero non essere nascosti affatto. +• Nascondendoli in questo modo, non ci saranno spazi vuoti tra i pulsanti." + "I pulsanti di azione sono nascosti in base al filtro identificatore. + +Note: +• I pulsanti a destra sono nascosti. +• Nascondendoli in questo modo, ci saranno spazi vuoti tra i pulsanti." + Indice del pulsante Remix Modalità Ambient Disattiva la modalità Ambient o bypassa le sue restrizioni. @@ -1067,6 +1013,61 @@ Nota: il titolo scompare quando lo si tocca." Disattiva la vibrazione tattile dello zoom La vibrazione tattile dello zoom è disattivata. La vibrazione tattile dello zoom è attivata. + + Riproduttore minimizzato + Personalizza i componenti del riproduttore minimizzato. + Disattiva la ripresa del riproduttore + <b>Continua a guardare</b> non riprenderà all\'avvio dell\'app. + <b>Continua a guardare</b> riprenderà all\'avvio dell\'app.<br><br>Note:<br>• <b>Continua a guardare</b> è un impostazione Premium di YouTube del riproduttore minimizzato.<br>• Questa impostazione non attiva forzatamente <b>Continua a guardare</b>. + Cambia lo stile del riproduttore minimizzato + Disattivato + Predefinito + Minimale + Tablet + Moderno 1 + Moderno 2 + Moderno 3 + Moderno 4 + Attiva gli angoli arrotondati + Gli angoli sono arrotondati. + Gli angoli sono quadrati. + Attiva il gesto Doppio Tocco e Pizzico per ridimensionare + "Il gesto Doppio Tocco e Pizzico per ridimensionare è attivato. + +Note: +• Tocca due volte per aumentare la dimesione del riproduttore minimizzato. +• Tocca di nuovo due volte per ripristinare la dimensione originale." + Il gesto Doppio Tocco e Pizzico per ridimensionare è disattivato. + Attiva il gesto Trascina e Rilascia + "Il gesto Trascina e Rilascia è attivato. + +Il riproduttore minimizzato può essere trascinato in qualsiasi angolo dello schermo." + Il gesto Trascina e Rilascia è disattivato. + Attiva il gesto Trascina Orizzontalmente + "Il gesto Trascina Orizzontalmente è attivato. + +Il riproduttore minimizzato può essere trascinato fuori dallo schermo a sinistra o a destra." + Il gesto Trascina Orizzontalmente è disattivato. + Nascondi i pulsanti in sovrapposizione + I pulsanti in sovrapposizione sono nascosti. + I pulsanti in sovrapposizione sono visibili. + Nascondi i pulsanti Espandi e Chiudi + "I pulsanti Espandi e Chiudi sono nascosti. + +Trascina il riproduttore minimizzato per espanderlo o chiuderlo." + I pulsanti Espandi e Chiudi sono visibili. + Nascondi i pulsanti Salta Avanti e Salta Indietro + I pulsanti Salta Avanti e Salta Indietro sono nascosti. + I pulsanti Salta Avanti e Salta Indietro sono visibili. + Nascondi i sottotesti + I sottotesti sono nascosti. + I sottotesti sono visibili. + Dimensione iniziale + La dimensione iniziale sullo schermo in pixel. + La dimensione in pixel deve essere tra %1$s e %2$s + Opacità della sovrapposizione + Il valore dell\'opacità è tra 0 e 100, dove 0 è trasparente. + L\'opacità della sovrapposizione del riproduttore minimizzato deve essere tra 0 e 100 Pulsanti Personalizza i pulsanti del riproduttore. @@ -1172,9 +1173,11 @@ Tocca e tieni premuto l'informazione mostrata per cambiare il tipo." Attiva il colore personalizzato Il colore personalizzato è attivato. Il colore personalizzato è disattivato. - Valore del colore personalizzato - Digita il codice esadecimale del colore. - Valore del colore della barra di avanzamento non valido + Colore primario personalizzato + Digita il codice esadecimale del colore. + Colore accento personalizzato + Digita il codice esadecimale del colore. + Colore della barra di avanzamento non valido Attiva il tocco Il tocco è attivato. Il tocco è disattivato. @@ -1242,14 +1245,9 @@ Questa impostazione funziona meglio con una connessione internet molto veloce."< Espandi automaticamente La descrizione dei video viene espansa automaticamente. La descrizione dei video non viene espansa automaticamente. - Titolo del pannello - "Inserisci il titolo del pannello della descrizione nella tua lingua. - -Nota: l'opzione Espandi Automaticamente potrebbe non funzionare se il titolo inserito non corrisponde al titolo del pannello della descrizione." - Descrizione Shorts - Disattiva la riproduzione in background degli Shorts + Disattiva la riproduzione in background La riproduzione in background degli Shorts è disattivata. La riproduzione in background degli Shorts è attivata. Disattiva la ripresa del riproduttore @@ -1286,9 +1284,9 @@ Nota: solo gli scaffali con l'intestazione Shorts nella scheda Home sono nascost Predefinito Pausa Ripeti - Apri i Shorts nel riproduttore normale - I Shorts sono aperti nel riproduttore normale. - I Shorts non sono aperti nel riproduttore normale. + Apri nel riproduttore normale + Gli Shorts sono aperti nel riproduttore normale. + Gli Shorts non sono aperti nel riproduttore normale. Riproduttore Personalizza i componenti del riproduttore degli Shorts. diff --git a/patches/src/main/resources/youtube/translations/ja-rJP/strings.xml b/patches/src/main/resources/youtube/translations/ja-rJP/strings.xml index b91d26403..40df21f8d 100644 --- a/patches/src/main/resources/youtube/translations/ja-rJP/strings.xml +++ b/patches/src/main/resources/youtube/translations/ja-rJP/strings.xml @@ -4,7 +4,7 @@ 動画プレーヤーのアクセシビリティコントロールを有効にしますか? ユーザー補助サービスが有効になっているため、操作方法が変わります。 - ReVanced Extended + RVX 設定を検索 デフォルト値にリセットしました。 実験的な機能 @@ -19,7 +19,7 @@ "%1$s はインストールされていません。 ウェブサイトから %2$s をダウンロードしてください。" %s はインストールされていません。インストールしてください。 - Revanced Extended 設定の言語 + YouTube と Revanced Extended 設定の言語 アプリの設定言語に従う アラビア語 アゼルバイジャン語 @@ -74,9 +74,13 @@ 中国語 広告 + 終了画面の商品バナーを非表示 + 動画の終了時に表示される商品バナーを非表示にします。 + 動画の終了時に表示される商品バナーを非表示にします。 全画面広告を非表示 アプリ起動時に表示される全画面広告を非表示にします。 アプリ起動時に表示される全画面広告を非表示にします。 + 全画面広告を閉じました。 一般的な広告を非表示 動画以外の広告を非表示にします。 動画以外の広告を非表示にします。 @@ -160,13 +164,7 @@ DeArrow の詳細については、ここをタップしてください。" - 以下の欄を非表示にします: -・ニュース速報 -・続きを見る -・もう一度見る -・もう一度聴く -・他のチャンネルを探す -・ショッピング + 以下の欄を非表示にします:\n・ニュース速報\n・続きを見る\n・もう一度見る\n・もう一度聴く\n・他のチャンネルを探す\n・ショッピング チップ欄を非表示 フィードに表示される、似ている動画のチップ欄を非表示にします。 フィードに表示される、似ている動画のチップ欄を非表示にします。 @@ -188,9 +186,9 @@ DeArrow の詳細については、ここをタップしてください。"「最新の動画」ボタンを非表示 「最新の動画」ボタンを非表示にします。 「最新の動画」ボタンを非表示にします。 - ミックスプレイリストを非表示 - ミックスプレイリストを非表示にします。 - ミックスプレイリストを非表示にします。 + ミックスリストを非表示 + ミックスリストを非表示にします。 + ミックスリストを非表示にします。 映画欄を非表示 有料の映画、テレビ番組を非表示にします。 有料の映画、テレビ番組を非表示にします。 @@ -268,6 +266,9 @@ DeArrow の詳細については、ここをタップしてください。"フィードのフライアアウトメニューのフィルタを有効化 フィードのフライアアウトメニューのフィルタを有効化します。 フィードのフライアアウトメニューのフィルタを有効化します。 + フィードのフライアウトメニューのフィルタの種類 + 現在の設定: 設定したキーワードが部分的に一致した場合にメニューを非表示にします。<br><br>例えば、<br>「キューの最初に追加」<br>メニューを非表示にしたい場合、<b>「キュー」</b>や<b>「追加」</b>などをキーワードとして使用できます。 + 現在の設定: 設定したキーワードが完全に一致した場合にメニューを非表示にします。<br><br>例えば、<b>「キューの最初に追加」</b>メニューを非表示にしたい場合、<b>「キューの最初に追加」</b>のみをキーワードとして使用できます。 フィードのフライアウトメニューのフィルタ フィルタリングするフライアウトメニューの項目名をリストしてください 。(改行区切り) @@ -294,10 +295,10 @@ DeArrow の詳細については、ここをタップしてください。" キーワードフィルタリングについて - "ホーム / 登録チャンネル / 検索結果 は、キーワードやフレーズに一致するコンテンツを非表示にするようにフィルタリングされます。 + "ホーム / 登録チャンネル / 検索結果から、キーワードフレーズに一致するコンテンツが非表示になるようにフィルタリングします。 注意: -・一部のショート動画は非表示にならない場合があります。 +・ショートはチャンネル名で非表示にすることはできません。 ・一部の UI コンポーネントは非表示にならない場合があります。 ・キーワードで検索しても結果が表示されない場合があります。" 単語全体を一致させる @@ -352,19 +353,21 @@ DeArrow の詳細については、ここをタップしてください。"一般 起動時のページを変更 デフォルト - 新しい動画の発見 - コース + すべての登録チャンネル + チャンネルを探す + 学び 探索 - ファッション & 美容 + ファッションと美容 ゲーム 履歴 ライブラリ - 高評価した動画 + 高く評価した動画 ライブ ムービー&TV 音楽 ニュース 通知 + 再生リスト ポッドキャスト 検索 ショッピング @@ -372,6 +375,7 @@ DeArrow の詳細については、ここをタップしてください。"スポーツ 登録チャンネル トレンド + バーチャル リアリティ 後で見る クリップ 「起動時のページを変更」の種類 @@ -415,8 +419,10 @@ DeArrow の詳細については、ここをタップしてください。"タブレット タブレット(最小 600 dp) ライブ状態のアイコンをタップした際の動作を変更 - 現在の設定: 「ライブ」状態のアイコンをタップすると、チャンネルが開きます。 - 現在の設定: 「ライブ」状態のアイコンをタップすると、ライブ配信が開きます。 + "現在の設定: 「ライブ」状態のアイコンをタップすると、チャンネルが開きます。 + +注意: 「通常のプレーヤーでショートを再生」設定を有効にしている場合、ショートのライブ配信を通常のプレーヤーで開くとチャンネルは開きません。" + 現在の設定: 「ライブ」状態のアイコンをタップすると、ライブ配信が開きます。\n\n注意: 「通常のプレーヤーでショートを再生」設定を有効にしている場合、ショートのライブ配信を通常のプレーヤーで開くとチャンネルは開きません。 アプリのバージョンを偽装 アプリのバージョンを偽装できます。 アプリのバージョンを偽装できます。 @@ -442,6 +448,9 @@ DeArrow の詳細については、ここをタップしてください。"アカウントメニューを非表示 "アカウントメニューとマイページタブの要素を非表示にします。 一部のコンポーネントは非表示にならない可能性があります。" + アカウントメニューのフィルタリングの種類を選択 + 現在の設定: 設定したキーワードが部分的に一致した場合にメニューを非表示にします。<br><br>例えば、<b>「YouTube Premium に登録」</b>メニューを非表示にしたい場合、<b>「YouTube Premium」</b>や<b>「Premium に登録」</b>などをキーワードとして使用できます。 + 現在の設定: 設定したキーワードが完全に一致した場合にメニューを非表示にします。<br><br>例えば、<b>「YouTube Premium に登録」</b>メニューを非表示にしたい場合、<b>「YouTube Premium に登録」</b>のみをキーワードとして使用できます。 アカウントメニューフィルター フィルタリングするアカウントメニュー名のリスト。(改行区切り) ハンドルを非表示 @@ -455,13 +464,13 @@ DeArrow の詳細については、ここをタップしてください。"カスタムフィルターを有効化します。 カスタムフィルター - フィルタリングするコンポーネントパスビルダーの文字列を並べたリスト。(改行区切り) + フィルタリングするコンポーネントパスビルダーの文字列を並べたリストを改行で区切ってフィルタリングできます。 無効なカスタムフィルターです: %s。 - ボタンをフック - YouTube アプリ内の「YouTube Music」ボタンを置換します。 + ボタンを置換 + アプリ内のボタンを別のボタンに置き換えます。 - ダウンロードボタン + 「オフライン」ボタン プレイリストにダウンロードボタンを追加 「ダウンロード」ボタンで外部ダウンローダーを開きます。 「オフライン」ボタンで外部ダウンローダーを開きます。 @@ -481,62 +490,12 @@ DeArrow の詳細については、ここをタップしてください。"%s はインストールされていません。インストールしてください。 前提条件 ボタンを置換するには YouTube Music が必要です。ここをタップして YouTube Music をダウンロードします。 - - ミニプレーヤー - ミニプレーヤーのスタイルを変更します。 - ミニプレーヤーの種類 - 無効 - オリジナル - 最小 - タブレット - モダン1 - モダン2 - モダン3 - 角の丸みを有効化 - ミニプレーヤーの角を丸くします。 - ミニプレーヤーの角を丸くします。 - ダブルタップとピンチでサイズを変更 - "ダブルタップとピンチ(画面を 2 本指でつまむようにして拡大や縮小する操作)でサイズを変更できるようにします。 - -・ダブルタップするとミニプレーヤーのサイズを拡大できます。 -・再度ダブルタップすると元のサイズに戻ります。" - ダブルタップとピンチ(画面を 2 本指でつまむようにして拡大や縮小する操作)でサイズを変更できるようにします。\n\n・ダブルタップするとミニプレーヤーのサイズを拡大できます。\n・再度ダブルタップすると元のサイズに戻ります。 - ドラッグ&ドロップを有効化 - "ドラッグ&ドロップを有効化します。 - -ミニプレーヤーは画面のどの隅にもドラッグできます。" - ドラッグ&ドロップを有効化します。\n\nミニプレーヤーは画面のどの隅にもドラッグできます。 - 水平ドラッグジェスチャーを有効化 - "水平ドラッグジェスチャーを有効化します。 - -ミニプレーヤーは画面の左右にドラッグして移動できます。" - 水平ドラッグジェスチャーを有効化します。\n\nミニプレーヤーは画面の左右にドラッグして移動できます。 - 閉じるボタンを非表示 - ミニプレーヤー右上の「‪✕‬」ボタンを非表示にします。 - ミニプレーヤー右上の「‪✕‬」ボタンを非表示にします。 - 拡大/縮小ボタンを非表示 - "拡大/縮小のボタンを非表示にします。 - -ミニプレーヤーをスワイプして拡大/縮小できます。" - 拡大/縮小のボタンを非表示にします。\n\nミニプレーヤーをスワイプして拡大/縮小できます。 - サブテキストを非表示 - ミニプレーヤーに表示される「プロモーションを含みます」などの文章を非表示にします。 - ミニプレーヤーに表示される「プロモーションを含みます」などの文章を非表示にします。 - スキップボタンを非表示 - 次の動画/前の動画へスキップするボタンを非表示にします。 - 次の動画/前の動画へスキップするボタンを非表示にします。 - 初期の画面サイズ - ミニプレーヤーの初期の画面サイズを設定します。(単位: ピクセル) - ピクセルサイズは %1$s から %2$s の間でなければなりません。 - オーバーレイの不透明度 - 不透明度の値は 0 ~ 100 の間で、 0 が透明です。 - ミニプレーヤーのオーバーレイの不透明度は0 ~ 100の間でなければなりません。デフォルト値にリセットします。 ナビゲーションバー ナビゲーションバーセクションのコンポーネントを非表示または表示します。 幅の狭いナビゲーションボタンを有効化 - ナビゲーション ボタンの間隔は狭くなります。 - ナビゲーション ボタンの間隔を狭くします。 + ナビゲーションボタンの間隔を狭くします。 + ナビゲーションボタンの間隔を狭くします。 作成ボタンを非表示 「作成」ボタンを非表示にします。 「作成」ボタンを非表示にします。 @@ -571,6 +530,7 @@ DeArrow の詳細については、ここをタップしてください。"半透明のナビゲーションバーを有効化 ナビゲーションバー (ホーム、登録チャンネルなどのボタン) を半透明にします。 ナビゲーションバー (ホーム、登録チャンネルなどのボタン) を半透明にします。 + 特定の YouTube バージョンでは、この設定によりシステムナビゲーションバーが透明になるか、ピクチャーインピクチャーモードでレイアウトが崩れることがあります。 ナビゲーションバーを非表示 ナビゲーションバー(ホーム、登録チャンネルなどのボタン)を非表示にします。 ナビゲーションバー(ホーム、登録チャンネルなどのボタン)を非表示にします。 @@ -717,10 +677,10 @@ DeArrow の詳細については、ここをタップしてください。"プレーヤーのオーバーレイのカスタム不透明度 透明度の値は 0 〜 100 の範囲で、 0 が透明です。 プレーヤーのオーバーレイの不透明度は 0 ~ 100 の間でなければなりません。デフォルト値にリセットします。 - ミックスプレイリストの自動切り替えを無効化 - 自動再生がオフの場合、音楽を再生した際に自動的にミックスプレイリストに切り替わるのを無効化できます。\n\n自動再生は YouTube の設定で変更できます: 「設定 → 自動再生 → 次の動画を自動再生」 - "自動再生がオフの場合、音楽を再生した際にミックスプレイリストへの自動切り替えを無効化できます。\n\n自動再生は YouTube の設定で変更できます: 「設定 → 再生 → 次の動画を自動再生」" - 自動再生がオフの場合、音楽を再生した際にミックスプレイリストへの自動切り替えを無効化できます。\n\n自動再生は YouTube の設定で変更できます: 「設定 → 自動再生 → 次の動画を自動再生」 + ミックスリストの自動切り替えを無効化 + 自動再生がオンの状態で音楽を再生すると自動的にミックスリストに切り替わるのを無効化します。 + "自動再生がオンの状態で音楽を再生すると自動的にミックスリストに切り替わるのを無効化します。" + この機能を有効にすることにより、自動再生がオンの状態で音楽を再生するとミックスリストへの自動切り替えが無効になります。\n\n自動再生は YouTube の設定で変更できます: 「設定 → 自動再生 → 次の動画を自動再生」 プレーヤーのポップアップパネルを無効化 再生リストとライブチャットのパネルが自動で開くのを無効化します。 再生リストとライブチャットのパネルが自動で開くのを無効化します。 @@ -819,40 +779,19 @@ DeArrow の詳細については、ここをタップしてください。"「Thanks」ボタンを非表示 「Thanks」ボタンを非表示にします。 「Thanks」ボタンを非表示にします。 - - インデックスで非表示 - 1 番目のボタンを非表示 - 1 番目のボタンを非表示にします。 - 1 番目のボタンを非表示にします。 - 2 番目のボタンを非表示 - 2 番目のボタンを非表示にします。 - 2 番目のボタンを非表示にします。 - 3 番目のボタンを非表示 - 3 番目のボタンを非表示にします。 - 3 番目のボタンを非表示にします。 - 4 番目のボタンを非表示 - 4 番目のボタンを非表示にします。 - 4 番目のボタンを非表示にします。 - 5 番目のボタンを非表示 - 5 番目のボタンを非表示にします。 - 5 番目のボタンを非表示にします。 - 6 番目のボタンを非表示 - 6 番目のボタンを非表示にします。 - 6 番目のボタンを非表示にします。 - 7 番目のボタンを非表示 - 7 番目のボタンを非表示にします。 - 7 番目のボタンを非表示にします。 - 8 番目のボタンを非表示 - 8 番目のボタンを非表示にします。 - 8 番目のボタンを非表示にします。 - - ライブ配信でインデックスで非表示 - 「インデックスで非表示」について - "アクションボタンが初期化される前に、インデックスによってアクションボタンを非表示にします。 + + インデックスでアクションボタンを非表示 + "アクションボタンをインデックスで非表示にします。 + +注意: +・間違ったアクションボタンが非表示になったり、アクションボタンが非表示にならない可能性があります。 +・アクションボタンを非表示にすると空きスペースがなくなります。" + "アクションボタンを識別子フィルターで非表示にします。 注意: -・アクションボタンを非表示にすると、空きスペースが残りません。 -・ボタンの順序は YouTube のバージョン、地域、動画によって異なる場合があります。" +・右側のアクションボタンは非表示になります。 +・アクションボタンを非表示にすると、空きスペースが残ります。" + リミックスボタンのインデックス アンビエントモード アンビエントモードの制限を回避またはアンビエントモードを無効化します。 @@ -1075,6 +1014,60 @@ DeArrow の詳細については、ここをタップしてください。"動画ズーム時の触覚フィードバックを無効化 動画ズーム時の触覚フィードバックを無効化します。 動画ズーム時の触覚フィードバックを無効化します。 + + ミニプレーヤー + ミニプレーヤーに関連するコンポーネントを非表示または変更します。 + ミニプレーヤーの再開を無効化 + <b>ミニプレーヤー(続きを見る)</b>をアプリ起動時に再開しないようにします。\n\n注意: \n・<b>ミニプレーヤーの再開(続きを見る)</b>は YouTube Premium 加入者専用の機能です。\n・これを有効にすることにより、<b>ミニプレーヤーの再開(続きを見る)</b>が強制的に有効になるわけではありません。 + <b>ミニプレーヤー(続きを見る)</b>をアプリ起動時に再開しないようにします。\n\n注意: \n・<b>ミニプレーヤーの再開(続きを見る)</b>は YouTube Premium 加入者専用の機能です。\n・これを有効にすることにより、<b>ミニプレーヤーの再開(続きを見る)</b>が強制的に有効になるわけではありません。 + ミニプレーヤーの種類 + 無効 + オリジナル + 最小 + タブレット + モダン1 + モダン2 + モダン3 + モダン 4 + 角の丸みを有効化 + ミニプレーヤーの角を丸くします。 + ミニプレーヤーの角を丸くします。 + ダブルタップとピンチでサイズを変更 + "ダブルタップとピンチ(画面を 2 本指でつまむようにして拡大や縮小する操作)でサイズを変更できるようにします。 + +・ダブルタップするとミニプレーヤーのサイズを拡大できます。 +・再度ダブルタップすると元のサイズに戻ります。" + ダブルタップとピンチ(画面を 2 本指でつまむようにして拡大や縮小する操作)でサイズを変更できるようにします。\n\n・ダブルタップするとミニプレーヤーのサイズを拡大できます。\n・再度ダブルタップすると元のサイズに戻ります。 + ドラッグ&ドロップを有効化 + "ドラッグ&ドロップを有効化します。 + +ミニプレーヤーは画面のどの隅にもドラッグできます。" + ドラッグ&ドロップを有効化します。\n\nミニプレーヤーは画面のどの隅にもドラッグできます。 + 水平ドラッグジェスチャーを有効化 + "水平ドラッグジェスチャーを有効化します。 + +ミニプレーヤーは画面の左右にドラッグして移動できます。" + 水平ドラッグジェスチャーを有効化します。\n\nミニプレーヤーは画面の左右にドラッグして移動できます。 + オーバーレイボタンを非表示 + オーバーレイボタンを非表示にします。 + オーバーレイボタンを非表示にします。 + 拡大/縮小ボタンを非表示 + "拡大/縮小のボタンを非表示にします。 + +ミニプレーヤーをスワイプして拡大/縮小できます。" + 拡大/縮小のボタンを非表示にします。\n\nミニプレーヤーをスワイプして拡大/縮小できます。 + スキップボタンを非表示 + 次の動画/前の動画へスキップするボタンを非表示にします。 + 次の動画/前の動画へスキップするボタンを非表示にします。 + サブテキストを非表示 + ミニプレーヤーに表示される「プロモーションを含みます」などの文章を非表示にします。 + ミニプレーヤーに表示される「プロモーションを含みます」などの文章を非表示にします。 + 初期の画面サイズ + ミニプレーヤーの初期の画面サイズを設定します。(単位: ピクセル) + ピクセルサイズは %1$s から %2$s の間でなければなりません。 + オーバーレイの不透明度 + 不透明度の値は 0 ~ 100 の間で、 0 が透明です。 + ミニプレーヤーのオーバーレイの不透明度は0 ~ 100の間でなければなりません。デフォルト値にリセットします。 プレーヤーボタン プレーヤー内のボタンを非表示または表示します。 @@ -1178,9 +1171,11 @@ DeArrow の詳細については、ここをタップしてください。"シークバーの色のカスタマイズを有効化 シークバーの色のカスタマイズを有効化します。 シークバーの色のカスタマイズを有効化します。 - シークバーの色 - シークバーの色を16進数カラーコードで入力してください。 - 無効なシークバーの色の値です。 + シークバーのメインカラー + シークバーのメインカラーを 16 進数カラーコードで入力してください。 + シークバーのアクセントカラー + シークバーのアクセントカラーを 16 進数カラーコードで入力してください。 + 無効なシークバーの色です。 シークバーのタップを有効化 シークバーのタップを有効化します。 シークバーのタップを有効化します。 @@ -1209,7 +1204,7 @@ DeArrow の詳細については、ここをタップしてください。" 概要欄 - 概要欄のコンポーネントを非表示または表示 + 概要欄のコンポーネントを非表示または表示します。 数字の回転アニメーションを無効化 高評価数と再生回数の回転アニメーションを無効にします。 高評価数と再生回数の回転アニメーションを無効にします。 @@ -1249,10 +1244,6 @@ DeArrow の詳細については、ここをタップしてください。"概要欄を自動で展開 概要欄を自動で展開します。 概要欄を自動で展開します。 - 動画の概要欄を自動で開く - "動画の概要欄パネルのタイトルを入力してください。 -概要欄パネルのタイトルは各言語によって異なるため不正確な文字列を保存した場合、「概要欄の自動展開」が機能しないことがあります。" - 概要 ショート ショートのバックグラウンド再生を無効化 diff --git a/patches/src/main/resources/youtube/translations/ko-rKR/strings.xml b/patches/src/main/resources/youtube/translations/ko-rKR/strings.xml index 8e5f53140..aa4adb159 100644 --- a/patches/src/main/resources/youtube/translations/ko-rKR/strings.xml +++ b/patches/src/main/resources/youtube/translations/ko-rKR/strings.xml @@ -4,7 +4,7 @@ 플레이어에서 접근성 컨트롤을 표시하시겠습니까? 접근성 서비스가 켜져있기 때문에 플레이어 컨트롤을 변경합니다. - ReVanced Extended 설정 + RVX 설정 %s 검색 기본값으로 초기화합니다. 실험 기능 @@ -74,9 +74,13 @@ 중국어 광고 + 최종 화면에서 스토어 배너 숨기기 + 스토어 배너가 숨겨집니다. + 스토어 배너가 표시됩니다. 전체 화면 광고 숨기기 전체 화면 광고가 숨겨집니다. 전체 화면 광고가 표시됩니다. + 전체 화면 광고가 닫아집니다. 일반 레이아웃 광고 숨기기 일반 레이아웃 광고가 숨겨집니다. 일반 레이아웃 광고가 표시됩니다. @@ -98,9 +102,9 @@ 동영상 광고 숨기기 동영상 광고가 숨겨집니다. 동영상 광고가 표시됩니다. - 제품 보기 배너 숨기기 - 플레이어에서 제품 보기 배너가 숨겨집니다. - 플레이어에서 제품 보기 배너가 표시됩니다. + 플레이어에서 제품 보기 배너 숨기기 + 제품 보기 배너가 숨겨집니다. + 제품 보기 배너가 표시됩니다. 웹 검색 결과 숨기기 웹 검색 결과가 숨겨집니다. 웹 검색 결과가 표시됩니다. @@ -267,6 +271,9 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 피드 메뉴 구성요소 필터 활성화하기 피드 메뉴 구성요소 필터를 활성화합니다. 피드 메뉴 구성요소 필터를 비활성화합니다. + 피드 메뉴 구성요소 필터 유형 + 부분 일치하는 경우에도 필터링합니다.<br><br>• <b>\'현재 재생목록에서 다음 순서로 재생\'</b> 메뉴를 숨기려면, <b>\'다음 순서\'</b> 또는 <b>\'현재 재생목록에서\'</b>와 같은 부분 일치하는 키워드도 필터링 키워드로 사용할 수 있습니다. + 완전 일치하는 경우에만 필터링합니다.<br><br>• <b>\'현재 재생목록에서 다음 순서로 재생\'</b> 메뉴를 숨기려면, <b>\'현재 재생목록에서 다음 순서로 재생\'</b>와 같은 완전 일치하는 키워드만 필터링 키워드로 사용할 수 있습니다. 피드 메뉴 구성요소 필터 필터링할 메뉴 구성요소 이름을 줄바꿈으로 구분하여 설정합니다. @@ -344,12 +351,13 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 관련 동영상이 표시됩니다. "이 설정은 플레이어 화면에서 로드할 수 있는 최대 레이아웃 수를 제한합니다. -서버 측 변경으로 인해 플레이어 화면의 레이아웃이 변경되면 플레이어 화면에서 의도하지 않은 레이아웃이 숨겨질 수 있습니다." +서버 측 변경으로 인하여 플레이어 화면의 레이아웃이 변경되면 플레이어 화면에서 의도하지 않은 레이아웃이 숨겨질 수 있습니다." 오프셋 일반 앱 시작 페이지 변경하기 홈 (기본값) + 모든 구독 채널 채널 둘러보기 학습 프로그램 탐색 @@ -363,6 +371,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 음악 뉴스 알림 + 재생목록 팟캐스트 검색 쇼핑 @@ -370,6 +379,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 스포츠 구독 인기 급상승 + 가상 현실 나중에 볼 동영상 내 클립 앱 시작 페이지 유형 변경하기 @@ -399,7 +409,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 회색 구분선 숨기기 동영상들 사이에서 회색 구분선이 숨겨집니다. 동영상들 사이에서 회색 구분선이 표시됩니다. - 시청 경고 다이얼로그 제거하기 + 시청자 재량 다이얼로그 제거하기 "• 이 설정은 다이얼로그를 자동으로 허용하기만 하며, 연령 제한(성인인증 절차)을 우회할 수 없습니다.\n• 즉, 성인인증이 필요한 동영상에서 인증을 하려 할 때, 휴대폰 번호가 필요하다고 알려주는 소형 팝업창(다이얼로그) 없이 바로 휴대폰 번호 인증 페이지가 표시됩니다.\n• '당신은 혼자가 아닙니다' 페이지는 제거할 수 없으며, 해당 페이지에서 '확인하기' 버튼이 표시되지 않는다면 이 설정이 아닌 플레이어 설정에서 '정보 패널 숨기기'를 비활성화해야 합니다." 레이아웃 변경하기 기기 기본값 사용 @@ -408,8 +418,11 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 태블릿 태블릿 (최소 너비: 600 dp) 라이브 링 누르기 동작 변경하기 - 라이브 링이 있는 채널 아바타를 누르면 채널 페이지로 연결됩니다.\n\n• Shorts 실시간 스트림일 경우에는 채널 페이지로 연결되지 않을 수 있습니다. - 라이브 링이 있는 채널 아바타를 누르면 실시간 스트림 동영상으로 연결됩니다. + "라이브 링이 표시된 채널 아이콘을 누르면 채널 프로필으로 연결됩니다. + +알려진 문제점: +• '일반 플레이어에서 Shorts 재생하기' 설정으로 인하여 일반 플레이어에서 Shorts 실시간 스트림이 재생된다면, 채널 프로필으로 연결되지 않을 수 있습니다." + 라이브 링이 표시된 채널 아이콘을 누르면 실시간 스트림 동영상으로 연결됩니다. 앱 버전 변경하기 앱 버전을 변경합니다. 앱 버전을 변경하지 않습니다. @@ -431,6 +444,9 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 계정 메뉴 숨기기 "계정 메뉴 및 내 페이지에서 구성요소가 숨겨집니다. 일부 구성요소는 숨겨지지 않을 수 있습니다." + 계정 메뉴 필터 유형 + 부분 일치하는 경우에도 필터링합니다.<br><br>• <b>\'YouTube Premium 가입\'</b> 메뉴를 숨기려면, <b>\'YouTube Premium\'</b> 또는 <b>\'Premium\'</b>와 같은 부분 일치하는 키워드도 필터링 키워드로 사용할 수 있습니다. + 완전 일치하는 경우에만 필터링합니다.<br><br>• <b>\'YouTube Premium 가입\'</b> 메뉴를 숨기려면, <b>\'YouTube Premium 가입\'</b>와 같은 완전 일치하는 키워드만 필터링 키워드로 사용할 수 있습니다. 계정 메뉴 필터 편집하기 필터링할 계정 메뉴 이름을 줄바꿈으로 구분하여 설정합니다. 핸들(@사용자 아이디) 숨기기 @@ -448,7 +464,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 잘못된 필터 값입니다: %s 버튼 재정의 - \'앱 내에 있는 버튼을 누를 시 실행 동작\'을 재정의할 수 있습니다. + 앱 내에 있는 버튼을 누를 경우에 실행되는 동작을 재정의할 수 있습니다. 오프라인 저장 버튼 재생목록 오프라인 저장 버튼 재정의하기 @@ -470,55 +486,6 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." %s 이 설치되어 있지 않습니다. 설치하세요. 전제 조건 버튼 동작을 재정의하려면 YouTube Music이 필요합니다. 여기를 눌러서 YouTube Music을 다운로드하세요. - - 미니 플레이어 - 앱 내에서 최소화된 플레이어의 스타일을 변경할 수 있습니다. - 미니 플레이어 유형 - 사용 안함 - 기기 기본값 사용 - 최소화 - 태블릿 - 모던 스타일 1 - 모던 스타일 2 - 모던 스타일 3 - 둥근 모서리 활성화하기 - 모서리를 둥글게 활성화합니다. - 모서리를 각지게 활성화합니다. - \'두 번 누르기\' 및 \'핀치하여 크기 조정\' 활성화하기 - "'두 번 누르기 동작' 및 '핀치하여 크기 조정'을 활성화합니다 - -• 두 번 눌러서 미니 플레이어 크기를 늘릴 수 있습니다. -• 다시 두 번 누르면 원래 크기로 복원됩니다." - \'두 번 누르기 동작\' 및 \'핀치하여 크기 조정\'을 비활성화합니다 - 드래그 & 드롭 활성화하기 - "드래그 & 드롭을 활성화합니다. -• 미니 플레이어를 화면 구석으로 드래그 할 수 있습니다." - 드래그 & 드롭을 비활성화합니다.\n• YouTube v19.44.39 에서는 드래그 & 드롭을 비활성화하고 모던 스타일 1, 2, 3을 사용하면, 미니 플레이어 상태에서 \'위로 스와이프하여 화면 펼치기\'와 \'아래로 스와이프하여 화면 닫기\'를 사용할 수 없습니다.\n• YouTube v19.16.39 에서는 위에 설명된 기능은 전부 사용할 수는 있지만, 드래그 & 드롭 기능이 지원되지 않습니다. - 수평 드래그 제스처 활성화하기 - "수평 드래그 제스처를 활성화합니다. - -• 미니 플레이어 절반 정도를 왼쪽 밖 또는 오른쪽 밖으로 드래그하여 숨길 수 있습니다." - 수평 드래그 제스처를 비활성화합니다. - \'닫기\' 버튼 숨기기 - \'닫기\' 버튼이 숨겨집니다. - \'닫기\' 버튼이 표시됩니다. - \'펼치기\' & \'닫기\' 버튼 숨기기 - "'펼치기' & '닫기' 버튼이 숨겨집니다. - -• 미니 플레이어를 스와이프하여 펼치거나 닫을 수 있습니다." - \'펼치기\' & \'닫기\' 버튼이 표시됩니다. - 서브텍스트 숨기기 - 서브텍스트가 숨겨집니다.\n• 왼쪽 하단에서 표시되는 \'유료 광고 포함\'과 같은 라벨 - 서브텍스트가 표시됩니다.\n• 왼쪽 하단에서 표시되는 \'유료 광고 포함\'과 같은 라벨 - \'되감기\' & \'빨리 감기\' 버튼 숨기기 - \'되감기\' & \'빨리 감기\' 버튼이 숨겨집니다. - \'되감기\' & \'빨리 감기\' 버튼이 표시됩니다. - 화면 크기 초기값 - 화면 크기 초기값을 지정할 수 있습니다. (픽셀) - 픽셀 크기는 %1$s-%2$s 사이어야 합니다. - 미니 플레이어 오버레이 불투명도 - 불투명도 값은 0-100 사이이며, 0은 투명입니다. - 미니 플레이어 오버레이 불투명도 값은 0-100 사이어야 합니다. 하단바 하단바에서 섹션 구성요소를 숨기거나 표시할 수 있습니다. @@ -559,6 +526,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 반투명 하단바 활성화하기 하단바가 반투명합니다. 하단바가 불투명합니다. + 특정 YouTube 앱 버전에서는 이 설정으로 인하여 시스템 네비게이션 바가 투명해지거나 PIP 모드에서 레이아웃이 깨질 수 있습니다. 하단바 숨기기 하단바가 숨겨집니다. 하단바가 표시됩니다. @@ -702,7 +670,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 플레이어 사용자 정의 플레이어 오버레이 불투명도 불투명도 값은 0-100 사이이며, 0은 투명입니다. - 플레이어 오버레이 불투명도 값은 0-100 사이어야 합니다. + 플레이어 오버레이 불투명도 값은 0-100 사이여야 합니다. 믹스 재생목록 자동전환 비활성화하기 믹스 재생목록 자동전환을 비활성화합니다. "자동재생이 켜져 있으면 믹스 재생목록 자동전환을 활성화합니다. @@ -720,8 +688,8 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." • 동영상 재생 속도 오버레이를 비활성화하면 이전 레이아웃의 '왼쪽이나 오른쪽으로 슬라이드하여 탐색' 동작이 복원됩니다. • 이 설정을 비활성화해도 동영상 재생 속도 오버레이가 강제로 활성화되지는 않습니다." 동영상 재생 속도 오버레이 값 - 동영상 재생 속도 오버레이 값은 0-8.0 사이어야 합니다. - 재생 속도 오버레이 값은 0-8.0 사이어야 합니다. + 동영상 재생 속도 오버레이 값은 0-8.0 사이여야 합니다. + 재생 속도 오버레이 값은 0-8.0 사이여야 합니다. 동영상 하단에서 채널 워터마크 숨기기 동영상 하단에서 채널 워터마크가 숨겨집니다. 동영상 하단에서 채널 워터마크가 표시됩니다. @@ -756,12 +724,12 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 동작 추천바 숨기기 동작 추천바가 숨겨집니다. 동작 추천바가 표시됩니다. - 최종 화면에서 \'다음 재생 추천 동영상\' 숨기기 - "자동재생이 꺼져 있으면 최종 화면에서 '다음 재생 추천 동영상'이 숨겨집니다. + 최종 화면에서 다음 재생 추천 동영상 숨기기 + "자동재생이 꺼져 있으면 최종 화면에서 다음 재생 추천 동영상이 숨겨집니다. 자동재생은 YouTube 설정에서 변경할 수 있습니다: 설정 → 자동재생 / 재생 → 다음 동영상 자동재생" - 최종 화면에서 \'다음 재생 추천 동영상\'이 표시됩니다. + 최종 화면에서 다음 재생 추천 동영상이 표시됩니다. 자동재생 카운트다운 건너뛰기 자동재생이 활성화되어 있으면, 카운트다운 없이 다음 동영상을 재생합니다. 자동재생이 활성화되어 있으면, 카운트다운 종료 후에 다음 동영상을 재생합니다 @@ -810,39 +778,19 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." Thanks 버튼 숨기기 Thanks 버튼이 숨겨집니다. Thanks 버튼이 표시됩니다. - - 인덱스로 액션 버튼 숨기기 - 첫 번째 버튼 숨기기 - 첫 번째 버튼이 숨겨집니다. - 첫 번째 버튼이 표시됩니다. - 두 번째 버튼 숨기기 - 두 번째 버튼이 숨겨집니다. - 두 번째 버튼이 표시됩니다. - 세 번째 버튼 숨기기 - 세 번째 버튼이 숨겨집니다. - 세 번째 버튼이 표시됩니다. - 네 번째 버튼 숨기기 - 네 번째 버튼이 숨겨집니다. - 네 번째 버튼이 표시됩니다. - 다섯 번째 버튼 숨기기 - 다섯 번째 버튼이 숨겨집니다. - 다섯 번째 버튼이 표시됩니다. - 여섯 번째 버튼 숨기기 - 여섯 번째 버튼이 숨겨집니다. - 여섯 번째 버튼이 표시됩니다. - 일곱 번째 버튼 숨기기 - 일곱 번째 버튼이 숨겨집니다. - 일곱 번째 버튼이 표시됩니다. - 여덟 번째 버튼 숨기기 - 여덟 번째 버튼이 숨겨집니다. - 여덟 번째 버튼이 표시됩니다. - - 실시간 스트림에서 인덱스로 숨기기 - \'인덱스로 액션 버튼 숨기기\' 정보 - "액션 버튼이 초기화되기 전에 인덱스로 액션 버튼이 숨겨집니다. + + 인덱스로 액션 버튼 숨기기 + "인덱스로 액션 버튼이 숨겨집니다. -• 액션 버튼이 숨겨지면 빈 공간이 남겨지지 않습니다. -• 액션 버튼의 인덱스는 항상 같은 버튼이 아닐 수도 있습니다." +알림: +• 잘못된 액션 버튼이 숨겨지거나 액션 버튼이 숨겨지지 않을 수 있습니다. +• 액션 버튼이 숨겨지면 빈 공간이 남지 않습니다." + "식별자 필터로 액션 버튼이 숨겨집니다. + +알림: +• 올바른 액션 버튼이 숨겨집니다. +• 액션 버튼이 숨겨지면 빈 공간이 남습니다." + 리믹스 버튼 인덱스 앰비언트 모드 앰비언트 모드 제한을 우회하거나 앰비언트 모드를 비활성화할 수 있습니다. @@ -1036,7 +984,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 공유 버튼이 표시됩니다. 빠른 작업 상단 여백 재생바에서 빠른 작업 컨테이너까지의 간격을 0-32 사이에서 지정할 수 있습니다. - 빠른 작업 상단 여백 값은 0-32 사이어야 합니다. + 빠른 작업 상단 여백 값은 0-32 사이여야 합니다. 전체 화면에서 세로 모드 활성화하기 전체 화면에서 동영상의 방향을 세로 모드로 활성화합니다. @@ -1067,6 +1015,59 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 동영상을 확대할 때, 진동 피드백 비활성화하기 진동 피드백을 비활성화합니다. 진동 피드백을 활성화합니다. + + 미니 플레이어 + 미니 플레이어와 관련된 구성요소를 숨기거나 변경할 수 있습니다. + 미니플레이어 다시 시작 비활성화하기 + 앱을 시작할 때, <b>\'계속 시청하기\'</b>를 다시 시작하지 않습니다. + 앱을 시작할 때, <b>\'계속 시청하기\'</b>를 다시 시작합니다.<br><br>알림:<br>• <b>\'계속 시청하기\'</b>는 YouTube Premium 기능입니다.<br>• 이 설정은 <b>\'계속 시청하기\'</b>를 강제로 사용하도록 설정하지는 않습니다. + 미니 플레이어 유형 + 사용 안함 + 기기 기본값 사용 + 최소화 + 태블릿 + 모던 스타일 1 + 모던 스타일 2 + 모던 스타일 3 + 모던 스타일 4 + 둥근 모서리 활성화하기 + 모서리를 둥글게 활성화합니다. + 모서리를 각지게 활성화합니다. + \'두 번 누르기\' 및 \'핀치하여 크기 조정\' 활성화하기 + "'두 번 누르기 동작' 및 '핀치하여 크기 조정'을 활성화합니다 + +• 두 번 눌러서 미니 플레이어 크기를 늘릴 수 있습니다. +• 다시 두 번 누르면 원래 크기로 복원됩니다." + \'두 번 누르기 동작\' 및 \'핀치하여 크기 조정\'을 비활성화합니다 + 드래그 & 드롭 활성화하기 + "드래그 & 드롭을 활성화합니다. +• 미니 플레이어를 화면 구석으로 드래그 할 수 있습니다." + 드래그 & 드롭을 비활성화합니다.\n• YouTube v19.44.39 에서는 드래그 & 드롭을 비활성화하고 모던 스타일 1, 2, 3을 사용하면, 미니 플레이어 상태에서 \'위로 스와이프하여 화면 펼치기\'와 \'아래로 스와이프하여 화면 닫기\'를 사용할 수 없습니다.\n• YouTube v19.16.39 에서는 위에 설명된 기능은 전부 사용할 수는 있지만, 드래그 & 드롭 기능이 지원되지 않습니다. + 수평 드래그 제스처 활성화하기 + "수평 드래그 제스처를 활성화합니다. + +• 미니 플레이어 절반 정도를 왼쪽 밖 또는 오른쪽 밖으로 드래그하여 숨길 수 있습니다." + 수평 드래그 제스처를 비활성화합니다. + 오버레이 버튼 숨기기 + 오버레이 버튼이 숨겨집니다. + 오버레이 버튼이 표시됩니다. + \'펼치기\' & \'닫기\' 버튼 숨기기 + "'펼치기' & '닫기' 버튼이 숨겨집니다. + +• 미니 플레이어를 스와이프하여 펼치거나 닫을 수 있습니다." + \'펼치기\' & \'닫기\' 버튼이 표시됩니다. + \'되감기\' & \'빨리 감기\' 버튼 숨기기 + \'되감기\' & \'빨리 감기\' 버튼이 숨겨집니다. + \'되감기\' & \'빨리 감기\' 버튼이 표시됩니다. + 서브텍스트 숨기기 + 서브텍스트가 숨겨집니다.\n• 왼쪽 하단에서 표시되는 \'유료 광고 포함\'과 같은 라벨 + 서브텍스트가 표시됩니다.\n• 왼쪽 하단에서 표시되는 \'유료 광고 포함\'과 같은 라벨 + 화면 크기 초기값 + 화면 크기 초기값을 지정할 수 있습니다. (픽셀) + 픽셀 크기는 %1$s-%2$s 사이여야 합니다. + 미니 플레이어 오버레이 불투명도 + 불투명도 값은 0-100 사이이며, 0은 투명입니다. + 미니 플레이어 오버레이 불투명도 값은 0-100 사이여야 합니다. 플레이어 버튼 플레이어에서 버튼을 숨기거나 표시할 수 있습니다. @@ -1172,9 +1173,11 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 사용자 정의 재생바 색상 활성화하기 사용자 정의 재생바 색상을 활성화합니다. 사용자 정의 재생바 색상을 비활성화합니다. - 사용자 정의 재생바 색상 - 재생바 색상의 헥스 코드를 입력하세요. - 잘못된 재생바 색상값입니다. + 사용자 정의 재생바 메인 색상 + 사용하고 싶은 재생바 메인 색상의 헥스 코드를 입력하세요. + 사용자 정의 재생바 보조 색상 + 사용하고 싶은 재생바 보조 색상의 헥스 코드를 입력하세요. + 잘못된 재생바 색상입니다. 재생바 터치 조작 활성화하기 재생바 터치 조작을 활성화합니다. 재생바 터치 조작을 비활성화합니다. @@ -1191,7 +1194,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 타임스탬프가 숨겨집니다. 타임스탬프가 표시됩니다. 이전 재생바 썸네일 복원하기 - 재생바 상단에서 최소화된 썸네일을 표시합니다. + 재생바 상단에서 소형 썸네일을 표시합니다. 플레이어에서 전체 화면으로 된 썸네일을 표시합니다. 고화질 재생바 썸네일 활성화하기 고화질 재생바 썸네일을 활성화합니다. @@ -1243,10 +1246,6 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 동영상 설명 펼치기 동영상 설명이 자동으로 펼쳐집니다. 동영상 설명이 수동으로 펼쳐집니다. - 동영상 설명 패널 제목 - "동영상 설명 패널의 제목을 사용자의 언어로 입력하세요. -입력한 문자열이 동영상 설명 패널 제목과 일치하지 않으면 '동영상 설명 펼치기'가 작동되지 않을 수 있습니다. " - 설명 Shorts Shorts 백그라운드 재생 비활성화하기 @@ -1393,7 +1392,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 사운드 버튼이 표시됩니다. 애니메이션 / 피드백 - 좋아요 상단 애니메이션 비활성화하기 + 좋아요 버튼 상단 애니메이션 비활성화하기 좋아요 버튼 상단에 표시되는 애니메이션을 비활성화합니다. 좋아요 버튼 상단에 표시되는 애니메이션을 활성화합니다. 두 번 누르기 애니메이션 @@ -1444,7 +1443,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 사용자 정의 동작 정보 "이 설정은 아직 실험 기능이므로 완벽하게 작동한다는 보장은 없습니다. -대부분의 버그는 클라이언트 측의 제한으로 인해 수정할 수 없으므로 테스트 목적으로만 사용하세요." +대부분의 버그는 클라이언트 측의 제한으로 인하여 수정할 수 없으므로 테스트 목적으로만 사용하세요." 타임스탬프 활성화하기 "타임스탬프를 활성화합니다. @@ -1463,7 +1462,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 하단바가 표시됩니다. 빈 공간의 높이 비율 하단바가 숨겨졌을 때, 남는 빈 공간의 높이 비율을 0-100 사이에서 지정할 수 있습니다. (백분율) - 높이 비율은 0-100 사이어야 합니다 (백분율). + 높이 비율은 0-100 사이여야 합니다 (백분율). 툴바 숨기기 툴바가 숨겨집니다. 툴바가 표시됩니다. @@ -1508,12 +1507,12 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 스와이프 영역 크기는 50 보다 클 수 없습니다. 스와이프 오버레이 타임아웃 오버레이가 표시되는 시간을 지정할 수 있습니다. (밀리초) - 밝기 스와이프 감도 - 밝기 스와이프의 최소 거리를 1-1000 사이에서 지정할 수 있습니다. (퍼센트)\n\n최소 거리가 짧을수록 밝기 레벨이 더 빠르게 변경됩니다. - 밝기 스와이프 감도는 1-1000 사이어야 합니다. (퍼센트) - 볼륨 스와이프 감도 - 볼륨 스와이프의 최소 거리를 1-1000 사이에서 지정할 수 있습니다. (퍼센트)\n\n최소 거리가 짧을수록 볼륨 레벨이 더 빠르게 변경됩니다.\n\n권장 볼륨 스와이프 감도는 15단계 볼륨에서 100%, 150단계 볼륨에서 10%입니다. - 볼륨 스와이프 감도는 1-1000 사이어야 합니다. (퍼센트) + 밝기 스와이프 민감도 + 밝기 스와이프의 최소 거리를 1-1000 사이에서 지정할 수 있습니다. (백분율)\n\n최소 거리가 짧을수록 밝기 레벨이 더 빠르게 변경됩니다. + 밝기 스와이프 민감도는 1-1000 사이여야 합니다. (백분율) + 볼륨 스와이프 민감도 + 볼륨 스와이프의 최소 거리를 1-1000 사이에서 지정할 수 있습니다. (백분율)\n\n최소 거리가 짧을수록 볼륨 레벨이 더 빠르게 변경됩니다.\n\n권장 볼륨 스와이프 민감도는 15단계 볼륨에서 100%, 150단계 볼륨에서 10%입니다. + 볼륨 스와이프 민감도는 1-1000 사이여야 합니다. (백분율) HDR 자동 밝기 비활성화하기 HDR 자동 밝기를 비활성화합니다. HDR 자동 밝기를 활성화합니다. @@ -1594,7 +1593,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." AV1 코덱 응답 거부하기 "AV1 코덱 응답을 강제로 거부합니다. • 약 20초정도의 버퍼링 후에 다른 코덱으로 전환됩니다." - Fallback 프로세스로 인해 약 20초정도의 버퍼링이 발생합니다. + Fallback 프로세스로 인하여 약 20초정도의 버퍼링이 발생합니다. 기본 동영상 재생 속도 값을 %s으로 변경합니다. 모바일 네트워크 이용 시 기본 동영상 화질 값을 %s로 변경합니다. 동영상 화질을 설정할 수 없습니다. @@ -1861,7 +1860,7 @@ API Key를 발급받는 방법을 보려면 여기를 누르세요." 기본 앱 설정 열기 다른 앱에서 YouTube 링크를 RVX로 열려면 \'지원되는 링크 열기\'를 활성화하고 지원되는 링크를 추가하세요. 링크 추가가 잠겨있다면 순정 YouTube 앱 정보 → \'기본적으로 열기\'에서 \'지원되는 링크 열기\'를 비활성화한 후에 추가할 수 있습니다. GmsCore 설정 열기 - 알림 수신을 위한 클라우드 메시징 설정을 할 수 있습니다. + RVX 알림 수신을 위한 클라우드 메시징 설정을 할 수 있습니다. GmsCore가 설치되어 있지 않습니다. 설치하세요. 필수 조치 "GmsCore에 백그라운드에서 실행할 수 있는 권한이 없습니다. @@ -1877,13 +1876,13 @@ GmsCore 앱 배터리 최적화를 비활성화(제한 없음)하더라도, 배 앱 배터리 최적화를 비활성화(제한 없음)하려면 '계속하기' 버튼을 누르세요." 계속하기 공유 시트 변경하기 - Android 기본 공유 시트를 사용합니다.\n\n• 공유 버튼으로 바로 Android 기본 공유 메뉴를 실행할 수 있습니다. + Android 시스템 공유 시트를 사용합니다.\n\n• 공유 버튼으로 바로 Android 시스템 공유 메뉴를 실행할 수 있습니다. YouTube 기본 공유 시트를 사용합니다. OPUS 코덱 활성화하기 플레이어 응답에 OPUS 코덱이 포함된 경우에는 OPUS 코덱을 활성화합니다. 설정 가져오기 / 내보내기 - 설정을 가져오거나 내보낼 수 있습니다. + RVX 설정을 가져오거나 내보낼 수 있습니다. 파일로 가져오기 / 내보내기 설정 내보내기 @@ -1940,21 +1939,21 @@ GmsCore 앱 배터리 최적화를 비활성화(제한 없음)하더라도, 배 AVC의 최대 화질 값은 1080p이고, OPUS 코덱을 사용불가 및 HDR 동영상을 재생할 수 없으며, 동영상을 재생했을 경우에는 VP9 또는 AV1보다 더 많은 모바일 데이터를 사용되오니 주의하세요." 전문 통계에서 표시하기 - \'스트리밍 데이터를 가져오는 데 사용되는 클라이언트\'가 전문 통계에서 표시됩니다.\n\n• 만약 선택한 기본 클라이언트가 재생할 수 없는 동영상을 재생하려하거나 클라이언트 재생 문제가 발생하면, 그 문제를 해결하기 위해 다른 클라이언트가 사용되는 경우도 있기 때문에 전문 통계에서 다르게 표시될 수 있습니다. - \'스트리밍 데이터를 가져오는 데 사용되는 클라이언트\'가 전문 통계에서 숨겨집니다.\n\n• 만약 선택한 기본 클라이언트가 재생할 수 없는 동영상을 재생하려하거나 클라이언트 재생 문제가 발생하면, 그 문제를 해결하기 위해 다른 클라이언트가 사용되는 경우도 있기 때문에 전문 통계에서 다르게 표시될 수 있습니다. + 스트리밍 데이터를 가져오는 데 사용되는 클라이언트가 전문 통계에서 표시됩니다.\n\n• 만약 선택한 기본 클라이언트가 재생할 수 없는 동영상을 재생하려하거나 클라이언트 재생 문제가 발생하면, 그 문제를 해결하기 위해 다른 클라이언트가 사용되는 경우도 있기 때문에 전문 통계에서 다르게 표시될 수 있습니다. + 스트리밍 데이터를 가져오는 데 사용되는 클라이언트가 전문 통계에서 숨겨집니다.\n\n• 만약 선택한 기본 클라이언트가 재생할 수 없는 동영상을 재생하려하거나 클라이언트 재생 문제가 발생하면, 그 문제를 해결하기 위해 다른 클라이언트가 사용되는 경우도 있기 때문에 전문 통계에서 다르게 표시될 수 있습니다. Android VR 기본 오디오 스트림 언어 - PoToken / VisitorData - 사용할 PoToken - \'신뢰할 수 있는 브라우저에서 BotGuard로 발행한 PoToken\'을 사용할 수 있습니다. - 사용할 VisitorData - \'신뢰할 수 있는 브라우저에서 BotGuard로 발행한 VisitorData\'를 사용할 수 있습니다. - PoToken / VisitorData에 대한 정보 - "일부 클라이언트는 유효한 스트리밍 데이터 응답을 받기 위해 PoToken과 VisitorData가 필요합니다. + PoToken & VisitorData + PoToken 등록하기 + 정보 메뉴와 연결된 페이지에 있는 사용법을 따라하여 발급받은 PoToken을 등록할 수 있습니다. + VisitorData 등록하기 + 정보 메뉴와 연결된 페이지에 있는 사용법을 따라하여 발급받은 VisitorData를 등록할 수 있습니다. + PoToken & VisitorData에 대한 정보 + "일부 클라이언트는 유효한 스트리밍 데이터 응답을 받기 위해 PoToken과 VisitorData가 요구됩니다. -iOS를 기본 클라이언트로 사용하려는 경우에 이 값이 필요할 수 있습니다. +iOS를 기본 클라이언트로 사용하려는 경우에 이 값이 요구될 수 있습니다. -자세한 정보를 보려면 여기를 누르세요." +PoToken과 VisitorData를 발급받는 방법 및 자세한 정보를 보려면 여기를 누르세요." 시청 기록 시청 기록과 관련된 설정을 변경할 수 있습니다. @@ -1968,7 +1967,7 @@ iOS를 기본 클라이언트로 사용하려는 경우에 이 값이 필요할 • 시청 기록이 차단됩니다. • Google 계정의 시청 기록 설정을 따릅니다. "• Google 계정의 시청 기록 설정을 따릅니다. -• '광고 차단기', '광고 & 추적 차단 기능이 내장된 DNS 또는 VPN'으로 인해 시청 기록이 작동되지 않을 수 있습니다." +• DNS 또는 VPN으로 인하여 시청 기록이 작동되지 않을 수 있습니다." 패치 정보 diff --git a/patches/src/main/resources/youtube/translations/pl-rPL/strings.xml b/patches/src/main/resources/youtube/translations/pl-rPL/strings.xml index 8f545aaef..9d6bbba59 100644 --- a/patches/src/main/resources/youtube/translations/pl-rPL/strings.xml +++ b/patches/src/main/resources/youtube/translations/pl-rPL/strings.xml @@ -4,7 +4,7 @@ Włączyć gesty ułatwień dostępu dla odtwarzacza filmów? Twoje ustawienia są zmienione, ponieważ serwis ułatwień dostępu jest włączony. - ReVanced Extended + RVX Wyszukaj w %s Przywrócono domyślne wartości. Eksperymentalne flagi @@ -74,9 +74,13 @@ Pobierz %2$s ze strony internetowej." Chiński Reklamy + Baner sklepowy po zakończeniu filmu + Ukryty + Widoczny Pełnoekranowe reklamy Ukryte Widoczne + Zamknięto pełnoreklamowe reklamy. Ogólne reklamy Ukryte Widoczne @@ -264,6 +268,9 @@ Sklep" Filtr menu ze strony głównej Włączony Wyłączony + Typ filtrowania + Musi zawierać<br><br>Aby ukryć menu <br>\'Odtwórz jako następny w kolejce\'<br>, możesz użyć <b>\'Odtwórz jako następny\'</b> lub <b>\'następny w kolejce\'</b> jako słowa kluczowe. + Musi pasować<br><br>Aby ukryć menu <b>\'Odtwórz jako następny w kolejce\'</b>, możesz użyć jedynie <b>\'Odtwórz jako następny w kolejce\'</b> jako słowo kluczowe. Filtr menu ze strony głównej Lista nazw menu do filtrowania, która musi być oddzielona nowymi liniami. @@ -348,6 +355,7 @@ Jeśli układ ekranu odtwarzacza zmieni się w skutek zmian po stronie serwera, Ogólne Strona startowa Domyślna + Wszystkie subskrypcje Przeglądaj kanały Nauka Odkrywanie @@ -361,6 +369,7 @@ Jeśli układ ekranu odtwarzacza zmieni się w skutek zmian po stronie serwera, Muzyka Wiadomości Powiadomienia + Playlisty Podcasty Wyszukiwanie Zakupy @@ -368,6 +377,7 @@ Jeśli układ ekranu odtwarzacza zmieni się w skutek zmian po stronie serwera, Sport Subskrypcje Na czasie + Rzeczywistość wirtualna Do obejrzenia Twoje klipy Działanie zmieniania strony głównej @@ -406,7 +416,9 @@ Nie pomija to ograniczeń wiekowych, lecz akceptuje je automatycznie." Tabletowy Tabletowy (max. 600 dpi) Zachowanie po kliknięciu okręgu od transmisji na żywo - Otwarcie kanału + "Otwarcie kanału + +Ograniczenie: jeśli transmisja na żywo Shortsa jest otwierana w zwykłym odtwarzaczu przy użyciu opcji 'Otwieranie Shortsów w zwykłym odtwarzaczu', kanał się nie otworzy." Otwarcie transmisji na żywo Oszukiwanie wersji aplikacji Włączone @@ -433,6 +445,9 @@ Jeśli później zostanie to wyłączone, rekomendowane jest usunięcie danych a Menu konta "Ukrywa elementy menu konta i zakładki Ty. Niektóre komponenty mogą nie być ukryte." + Typ filtrowania + Musi zawierać<br><br>Aby ukryć menu <br>\'Przejdź na YouTube Premium\'<br>, możesz użyć <b>\'YouTube Premium\'</b> lub <b>\'Premium\'</b> jako słowa kluczowe. + Musi pasować<br><br>Aby ukryć menu <br>\'Przejdź na YouTube Premium\'<br>, możesz użyć jedynie <b>\'Przejdź na YouTube Premium\'</b> jako słowo kluczowe. Filtr menu konta Lista nazw menu konta do filtrowania, która musi być oddzielona nowymi liniami. Nicki @@ -473,54 +488,6 @@ Niektóre komponenty mogą nie być ukryte." Wymaganie wstępne YouTube Music jest wymagane do zmiany tego ustawienia. Stuknij tutaj, by pobrać YouTube Music. - - Miniodtwarzacz - Zmień styl miniodtwarzacza w aplikacji - Typ miniodtwarzacza - Wyłączony - Oryginalny - Minimalistyczny - Tabletowy - Nowoczesny 1 - Nowoczesny 2 - Nowoczesny 3 - Zaokrąglone boki - Włączone - Wyłączone (boki są kwadratowe) - Gest zmiany rozmiaru miniodtwarzacza - "Włączony - -• Kliknij podwójnie, by zwiększyć rozmiar miniodtwarzacza -• Ponownie kliknij podwójnie, by przywrócić oryginalny rozmiar" - Wyłączony - Gest przeciągnięcia i upuszczenia - "Włączony - -Miniodtwarzacz można przeciągnąć do dowolnego rogu ekranu." - Wyłączony - Gest przeciągania w poziomie - "Włączony - -Miniodtwarzacz można przeciągnąć poza ekran w lewo lub prawo." - Wyłączony - Przycisk zamykania - Ukryty - Widoczny - Przyciski rozwijania i zamykania - "Ukryte. Przesuń, aby rozwinąć lub zamknąć." - Widoczne - Podteksty - Ukryte - Widoczne - Przyciski przewijania do przodu i wstecz - Ukryte - Widoczne - Rozmiar początkowy - Początkowy rozmiar ekranu, w pikselach. - Rozmiar w pikselach musi znajdować się w przedziale od %1$s do %2$s. - Przezroczystość nakładki - Wartość przezroczystości musi wynosić między 0 a 100, gdzie 0 oznacza przezroczystość - Przezroczystość nakładki miniodtwarzacza musi wynosić między 0 a 100. Pasek nawigacji Ukryj lub pokazuj przyciski nawigacji @@ -561,6 +528,7 @@ Jeśli opcja nie przynosi skutku, spróbuj przełączyć się na tryb incognito. Wygląd paska nawigacji Półprzezroczysty Nieprzezroczysty + W niektórych wersjach YouTube, to ustawienie może sprawić systemowy pasek nawigacji przezroczystym, lub popsuć układ w trybie PiP. Pasek nawigacji Ukryty Widoczny @@ -812,39 +780,19 @@ Ustawienia → Autoodtwarzanie/Odtwarzanie → Autoodtwarzanie następnego filmu Przycisk od dziękowania Ukryty Widoczny - - Ukryj wg indeksu - Pierwszy przycisk - Ukryty - Widoczny - Drugi przycisk - Ukryty - Widoczny - Trzeci przycisk - Ukryty - Widoczny - Czwarty przycisk - Ukryty - Widoczny - Piąty przycisk - Ukryty - Widoczny - Szósty przycisk - Ukryty - Widoczny - Siódmy przycisk - Ukryty - Widoczny - Ósmy przycisk - Ukryty - Widoczny - - Ukryj wg indeksu w transmisjach na żywo - O ukrywaniu przycisków akcji po indeksie - "Ukrywa przyciski akcji według indeksu przed zainicjowaniem przycisków akcji. + + Typ ukrywania przycisków akcji + "Po indeksie -- ukrywanie przycisków akcji nie pozostawia pustego miejsca -- indeks przycisków akcji nie zawsze jest tym samym przyciskiem" +Informacje: +• mogą zostać ukryte niewłaściwe przyciski akcji, bądź nie zostaną wg ukryte +• ukrywanie przycisków akcji nie pozostawia pustego miejsca" + "Po identyfikatorze + +Informacje: +• zostaną ukryte właściwe przyciski akcji +• ukrywanie przycisków akcji pozostawia puste miejsce" + Indeks przycisku od remiksowania Oświetlenie kinowe Wyłącz oświetlenie kinowe lub obejdź jego ograniczenia @@ -1067,6 +1015,60 @@ Ograniczenie: tytuły filmów znikają po stuknięciu w nie." Wibracje podczas przybliżania filmu Wyłączone Włączone + + Miniodtwarzacz + Ukryj bądź zmień komponenty miniodtwarzacza + Wznawianie filmu w miniodtwaraczu + <b>Oglądaj dalej</b> nie pojawi się po uruchomieniu aplikacji. + <b>Oglądaj dalej</b> pojawi się po uruchomieniu aplikacji.<br><br>Informacje:<br>• <b>Oglądaj dalej</b> jest funkcją YouTube Premium<br>• Ustawienie nie wymusza włączenia opcji <b>Oglądaj dalej</b> + Typ miniodtwarzacza + Wyłączony + Oryginalny + Minimalistyczny + Tabletowy + Nowoczesny 1 + Nowoczesny 2 + Nowoczesny 3 + Nowoczesny 4 + Zaokrąglone boki + Włączone + Wyłączone (boki są kwadratowe) + Gest zmiany rozmiaru miniodtwarzacza + "Włączony + +• Kliknij podwójnie, by zwiększyć rozmiar miniodtwarzacza +• Ponownie kliknij podwójnie, by przywrócić oryginalny rozmiar" + Wyłączony + Gest przeciągnięcia i upuszczenia + "Włączony + +Miniodtwarzacz można przeciągnąć do dowolnego rogu ekranu." + Wyłączony + Gest przeciągania w poziomie + "Włączony + +Miniodtwarzacz można przeciągnąć poza ekran w lewo lub prawo." + Wyłączony + Przyciski w miniodtwarzaczu + Ukryte + Widoczne + Przyciski rozwijania i zamykania + "Ukryte + +Przesuń, by rozwinąć lub zamknąć." + Widoczne + Przyciski przewijania do przodu i wstecz + Ukryte + Widoczne + Podteksty + Ukryte + Widoczne + Rozmiar początkowy + Początkowy rozmiar ekranu, w pikselach. + Rozmiar w pikselach musi znajdować się w przedziale od %1$s do %2$s. + Przezroczystość nakładki + Wartość przezroczystości musi wynosić między 0 a 100, gdzie 0 oznacza przezroczystość + Przezroczystość nakładki miniodtwarzacza musi wynosić między 0 a 100. Przyciski w odtwarzaczu Ukryj lub pokazuj przyciski w odtwarzaczu @@ -1172,9 +1174,11 @@ Stuknij j przytrzymaj, by przełączać się pomiędzy typami wyświetlanej info Niestandardowy kolor paska postępu filmu Widoczny Niewidoczny - Niestandardowy kolor paska postępu filmu - Wpisz kod hex koloru paska postępu filmu - Nieprawidłowa wartość koloru paska postępu. + Niestandardowy kolor pierwszorzędny + Wpisz kod hex pierwszorzędnego koloru paska postępu filmu. + Niestandardowy kolor akcentu + Wpisz kod hex akcentu paska postępu filmu. + Nieprawidłowy kolor paska postępu filmu. Stukanie w pasek postępu filmu Włączone Wyłączone @@ -1243,10 +1247,6 @@ Funkcja działa najlepiej przy bardzo szybkim połączeniu internetowym."Otwieraj opisy filmów Automatycznie Ręcznie - Tytuł w panelu opisu filmu - "Wprowadź tytuł panelu opisu filmu w twoim języku. -Opcja rozwijania opisu filmu może nie działać, jeśli wprowadzony ciąg znaków nie zgadza się z tytułem panelu opisu filmu." - Opis Shortsy Odtwarzanie Shortsów w tle diff --git a/patches/src/main/resources/youtube/translations/pt-rBR/strings.xml b/patches/src/main/resources/youtube/translations/pt-rBR/strings.xml index e08aafd10..1eef8b0bd 100644 --- a/patches/src/main/resources/youtube/translations/pt-rBR/strings.xml +++ b/patches/src/main/resources/youtube/translations/pt-rBR/strings.xml @@ -4,7 +4,7 @@ Ativar os controles de acessibilidade para o reprodutor de vídeo? Seus controles foram modificados porque um serviço de acessibilidade está ativado. - ReVanced Extended + RVX Pesquisar %s Redefinir para os valores padrão. Sinalizadores experimentais @@ -74,9 +74,13 @@ Por favor, baixe %2$s do site." Chinês Anúncios + Ocultar banner da loja na tela final + O banner da loja está oculto. + O banner da loja será exibido. Ocultar anúncios em tela cheia Os anúncios em tela cheia estão ocultos. Os anúncios em tela cheia serão exibidos. + Os anúncios em tela cheia são fechados. Ocultar anúncios gerais Os anúncios gerais estão ocultos. Os anúncios gerais serão exibidos. @@ -264,6 +268,7 @@ Loja" Ativar filtro do menu flutuante do feed O filtro do menu flutuante do feed está ativado. O filtro do menu flutuante do feed está desativado. + Tipo de filtro de menu de flutuante do feed Filtro do menu flutuante do feed Lista de nomes do menu flutuante para filtrar separados por uma nova linha. @@ -345,6 +350,7 @@ Se o layout da tela do reprodutor mudar devido a alterações no lado do servido Geral Alterar a página inicial Padrão + Todas as inscrições Explorar canais Cursos / Aprendizagem Explorar @@ -358,6 +364,7 @@ Se o layout da tela do reprodutor mudar devido a alterações no lado do servido Música Notícias Notificações + Listas de reprodução Podcasts Buscar Compras @@ -365,6 +372,7 @@ Se o layout da tela do reprodutor mudar devido a alterações no lado do servido Esportes Inscrições Em alta + Realidade Virtual Assistir mais tarde Seus clipes Alterar tipo de página inicial @@ -403,7 +411,7 @@ Isso não ignora a restrição de idade, apenas aceita isso automaticamente."Tablet Tablet (Min 600 dp) Alterar ação de clique do anel ao vivo - O canal é aberto quando o anel ao vivo é clicado. + "O canal é aberto quando o anel ao vivo é clicado." A transmissão ao vivo é aberta quando o anel ao vivo é clicado. Falsificar versão do aplicativo Versão falsificada @@ -430,6 +438,7 @@ Se for desativado posteriormente, é recomendável limpar os dados do aplicativo Ocultar menu de contas "Ocultar elementos do menu de contas e aba Você Alguns componentes podem não ser ocultos" + Tipo de filtro do menu Conta Filtro do menu da conta Lista de nomes do menu de conta a serem filtrados separados por uma nova linha. Ocultar identificador @@ -469,56 +478,6 @@ Alguns componentes podem não ser ocultos" %s não está instalado. Por favor, instale-o. Pré-requisito O YouTube Music é necessário para substituir a ação do botão. Toque aqui para baixar o YouTube Music. - - Mini reprodutor - Alterar o estilo do reprodutor minimizado no aplicativo. - Tipo de mini reprodutor - Desativado - Original - Telefone - Tablet - Moderno 1 - Moderno 2 - Moderno 3 - Ativar cantos arredondados - Os cantos serão arredondados. - Os cantos serão quadrados. - Ativar toque duplo e pinçar para redimensionar - "A ação de toque duplo e pinça para redimensionar está ativada. - -• Toque duplo para aumentar o tamanho do mini reprodutor. -• Toque duplo novamente para restaurar o tamanho original." - O toque duplo e pinçar para redimensionar está desativado. - Ativar arrastar e soltar - "Arrastar e soltar está ativado. - -O mini reprodutor pode ser arrastado para qualquer canto da tela." - Arrastar e soltar está desativado. - Ativa o gesto de arrastar horizontalmente. - "Gesto de arrastar horizontalmente ativado. - -O mini reprodutor pode ser arrastado para fora da tela para a esquerda ou direita." - Gesto de arrastar horizontalmente desativado. - Ocultar botões de expansão e fechamento - Os botões estão ocultos.\n(deslize o mini reprodutor para expandir ou fechar) - Os botões de expansão e fechamento serão exibidos. - Ocultar botões de expandir e fechar - "Os botões estão ocultos. - -Deslize para expandir ou fechar." - Os botões de expandir e fechar serão exibidos. - Ocultar subtextos - Os subtextos estão ocultos. - Os subtextos serão exibidos. - Ocultar os botões avançar e retroceder - Os botões avançar e retroceder estão ocultos. - Os botões avançar e retroceder serão exibidos. - Tamanho inicial - Tamanho inicial na tela, em pixels. - O tamanho do pixel deve estar entre %1$s e %2$s. - Opacidade da sobreposição - Valor da opacidade entre 0-100, onde 0 é transparente. - A opacidade da sobreposição do mini reprodutor deve ser entre 0-100. Redefinir aos valores padrão. Barra de Navegação Ocultar ou mostrar componentes da seção de navegação. @@ -559,6 +518,7 @@ Se essa configuração não surtir efeito, tente alternar para o modo anônimo." Ativar barra de navegação transparente A barra de navegação está transparente. A barra de navegação está opaca. + Em certas versões do YouTube, esta configuração pode tornar a barra de navegação do sistema transparente ou o layout pode ser quebrado no modo PIP. Ocultar barra de navegação A barra de navegação está oculta. A barra de navegação será exibida. @@ -809,39 +769,19 @@ A reprodução automática pode ser alterada nas configurações do YouTube: Ocultar botão valeu O botão valeu está oculto. O botão valeu será exibido. - - Ocultar por índice - Ocultar o primeiro botão - O primeiro botão está oculto. - O primeiro botão será exibido. - Ocultar o segundo botão - O segundo botão está oculto. - O segundo botão será exibido. - Ocultar o terceiro botão - O terceiro botão está oculto. - O terceiro botão será exibido. - Ocultar o quarto botão - O quarto botão está oculto. - O quarto botão será exibido. - Ocultar o quinto botão - O quinto botão está oculto. - O quinto botão será exibido. - Ocultar o sexto botão - O sexto botão está oculto. - O sexto botão será exibido. - Ocultar o sétimo botão - O sétimo botão está oculto. - O sétimo botão será exibido. - Ocultar o oitavo botão - O oitavo botão está oculto. - O oitavo botão será exibido. - - Ocultar por índice na transmissão ao vivo - Sobre o Ocultar botão de ação por índice - "Oculte os botões de ação por índice antes que os botões de ação sejam inicializados. + + Ocultar o botão de ação por índice + "Os botões de ação são ocultos pelo índice. -- Ocultar os botões de ação não deixa espaço vazio. -- Índice dos botões de ação podem não ser sempre o mesmo botão." +Informação: +• Botões de ação errados podem ser ocultados, ou os botões de ação não podem ser ocultados. +• Ocultar botões de ação não deixam espaço vazio." + "Os botões de ação são ocultos pelo filtro de identificação. + +Informação: +• Botões de ação à direita estão ocultos. +• Ocultar botões de ação deixa espaço vazio." + Índice do botão remix Modo ambiente Ignorar restrições do modo ambiente ou desativar o modo ambiente. @@ -1064,6 +1004,57 @@ Limitação: Título do vídeo desaparece quando clicado." Desativar retorno tátil de zoom O retorno tátil está desativado. O retorno tátil está ativado. + + Mini reprodutor + Alterar o estilo do reprodutor minimizado no aplicativo. + Tipo de mini reprodutor + Desativado + Original + Telefone + Tablet + Moderno 1 + Moderno 2 + Moderno 3 + Moderno 4 + Ativar cantos arredondados + Os cantos serão arredondados. + Os cantos serão quadrados. + Ativar toque duplo e pinçar para redimensionar + "A ação de toque duplo e pinça para redimensionar está ativada. + +• Toque duplo para aumentar o tamanho do mini reprodutor. +• Toque duplo novamente para restaurar o tamanho original." + O toque duplo e pinçar para redimensionar está desativado. + Ativar arrastar e soltar + "Arrastar e soltar está ativado. + +O mini reprodutor pode ser arrastado para qualquer canto da tela." + Arrastar e soltar está desativado. + Ativa o gesto de arrastar horizontalmente. + "Gesto de arrastar horizontalmente ativado. + +O mini reprodutor pode ser arrastado para fora da tela para a esquerda ou direita." + Gesto de arrastar horizontalmente desativado. + Ocultar botões de sobreposição + Os botões de sobreposição estão ocultos. + Os botões de sobreposição serão exibidos. + Ocultar botões de expandir e fechar + "Os botões estão ocultos. + +Deslize para expandir ou fechar." + Os botões de expandir e fechar serão exibidos. + Ocultar os botões avançar e retroceder + Os botões avançar e retroceder estão ocultos. + Os botões avançar e retroceder serão exibidos. + Ocultar subtextos + Os subtextos estão ocultos. + Os subtextos serão exibidos. + Tamanho inicial + Tamanho inicial na tela, em pixels. + O tamanho do pixel deve estar entre %1$s e %2$s. + Opacidade da sobreposição + Valor da opacidade entre 0-100, onde 0 é transparente. + A opacidade da sobreposição do mini reprodutor deve ser entre 0-100. Redefinir aos valores padrão. Botões do reprodutor Oculte ou mostre botões em vídeos. @@ -1167,9 +1158,11 @@ Informação: Ativar cor personalizada da barra de progresso A cor personalizada da barra de progresso está ativada. A cor personalizada da barra de progresso está desativada. - Valor da cor personalizada da barra de progresso - Digite o código hexadecimal da cor da barra de progresso. - Valor de cor da barra de busca inválido. + Cor primária personalizada da barra de progresso + Digite o código hexadecimal da cor primária da barra de busca. + Cor de destaque personalizada para a barra de progresso + Digite o código hexadecimal da cor de destaque da barra de progresso. + Valor da cor da barra de progresso inválido. Ativar toque na barra de progresso O toque na barra de progresso está ativado. O toque na barra de progresso desativado. @@ -1238,11 +1231,6 @@ Este recurso funciona melhor com uma conexão de Internet muito rápida."Expandir descrição do vídeo A descrição do vídeo é expandida automaticamente. A descrição do vídeo é expandida manualmente. - Título no painel de descrição do vídeo - "Insira um título no painel de descrição do vídeo. -Estes caracteres variam dependendo do seu idioma. -'Expandir descrição do vídeo' pode não funcionar se você salvar uma string incorreta." - Descrição Shorts Desativar reprodução de fundo dos Shorts diff --git a/patches/src/main/resources/youtube/translations/ru-rRU/strings.xml b/patches/src/main/resources/youtube/translations/ru-rRU/strings.xml index 1be890ce9..802becbd6 100644 --- a/patches/src/main/resources/youtube/translations/ru-rRU/strings.xml +++ b/patches/src/main/resources/youtube/translations/ru-rRU/strings.xml @@ -4,7 +4,7 @@ Включить специальные возможности в плеере? Служба специальных возможностей включена. Управление изменено. - ReVanced Extended + RVX Поиск %s К значениям по умолчанию. Экспериментальные опции @@ -74,9 +74,13 @@ 中文 Реклама + Скрыть баннер магазина на конечном экране + Баннер магазина скрыт. + Баннер магазина отображается. Полноэкранная реклама Полноэкранная реклама скрыта. Полноэкранная реклама отображена. + Полноэкранная реклама закрыта. Реклама общего формата Реклама общего формата скрыта. Реклама общего формата отображена. @@ -266,6 +270,9 @@ Shorts Фильтр всплывающего меню ленты Фильтр всплывающего меню ленты включен. Фильтр всплывающего меню ленты отключен. + Тип фильтра выдвижного меню в ленте + Фильтруется, если содержат.<br><br>Чтобы скрыть меню <b>\'Воспроизвести следующим в очереди\'</b>, можете использовать <b>\'Воспроизвести следующим\'</b> или <b>\'в очереди\'</b> как ключевые слова. + Фильтруется, если совпадают.<br><br>Чтобы скрыть меню <b>\'Воспроизвести следующим в очереди\'</b>, можете использовать только <b>\'Воспроизвести следующим в очереди\'</b> как ключевые слова. Фильтр всплывающего меню ленты Список имен всплывающего меню ленты для скрытия.\nРазделять новой строкой. @@ -358,6 +365,7 @@ Shorts Основные настройки Начальная страница По умолчанию + Все подписки Обзор каналов Курсы / Обучение Навигатор @@ -371,6 +379,7 @@ Shorts Музыка Новости Уведомления + Плейлисты Подкасты Поиск Покупки @@ -378,6 +387,7 @@ Shorts Спорт Подписки В тренде + Виртуальная реальность Смотреть позже Ваши клипы Тип начальной страницы @@ -414,7 +424,7 @@ Shorts Планшет Планшет (Мин. 600 dip) Действие нажатия на точку прямого эфира - Открывается канал. + "Открывается канал." Открывается прямой эфир. Подмена версии приложения Версия приложения подменена @@ -441,6 +451,9 @@ Shorts Скрыть меню аккаунта "Скрыть элементы меню аккаунта и вкладки \"Вы\". Некоторые компоненты не могут быть скрыты." + Тип фильтра меню аккаунта + Фильтруется, если содержат.<br><br>Чтобы скрыть меню <b>\'Оформить подписку YouTube Premium\'</b>, можете использовать <b>\'YouTube Premium\'</b> или <b>\'Premium\'</b> как ключевые слова. + Фильтруется, если совпадают.<br><br>Чтобы скрыть меню <b>\'Оформить подписку YouTube Premium\'</b>, можете использовать только <b>\'Оформить подписку YouTube Premium\'</b> как ключевые слова. Фильтр элементов меню аккаунта Список элементов меню учетной записи для скрытия.\nРазделять новой строкой. Скрыть псевдоним @@ -481,56 +494,6 @@ Shorts Обязательное условие Для переопределения кнопки требуется YouTube Music. Нажмите здесь, чтобы загрузить YouTube Music. - - Мини-плеер - Стиль мини-плеера. - Тип мини-плеера - Отключено - Оригинал - Телефон - Планшет - Современный 1 - Современный 2 - Современный 3 - Скругления углов мини-плеера - Скругления углов включено. - Скругления углов отключено. - Изменение размера жестом - "Двойное нажатие и щипок для изменения размера включено. - -• Двойное нажатие для увеличения размера мини-плеера. -• Двойное нажатие еще раз для восстановления исходного размера." - Двойное нажатие и щипок для изменения размера отключено. - Перетаскивание мини-плеера - "Перетаскивание мини-плеера включено. - -Мини-плеер можно перемещать по экрану." - Перетаскивание мини-плеера отключено. - Жест горизонтального перетаскивания. - "Жест горизонтального перетаскивания включен. - -Мини-плеер можно перетаскивать за пределы экрана влево или вправо." - Жест горизонтального перетаскивания отключен. - Кнопки \"Развернуть\" и \"Закрыть\" - Кнопки скрыты. -(развернуть или закрыть мини-плеер жестом) - Кнопки \"Развернуть\" и \"Закрыть\" отображены. - Кнопки \"Развернуть\" и \"Закрыть\" - "Кнопки \"Развернуть\" и \"Закрыть\" скрыты. -Только жесты." - Кнопки \"Развернуть\" и \"Закрыть\" отображены. - Подстрочный текст - Подстрочный текст скрыт. - Подстрочный текст отображен. - Кнопки перемотки - Кнопки перемотки скрыты. - Кнопки перемотки отображены. - Начальный размер - Начальный размер мини-плеера в пикселях. - Размер в пикселях должен быть между %1$s и %2$s. - Непрозрачность мини-плеера - Значение непрозрачности в промежутке 0-100, где 0 это прозрачный. - Непрозрачность должна быть в диапазоне 0-100. Сброс по умолчанию. Панель навигации Настроить компоненты панели навигации. @@ -571,6 +534,7 @@ Shorts Полупрозрачность панели навигации Полупрозрачность включена. Полупрозрачность отключена. + В некоторых версиях YouTube этот параметр может сделать панель навигации прозрачной или нарушить макет в режиме PIP. Панель навигации Панель навигации скрыта. Панель навигации отображена. @@ -638,9 +602,25 @@ Shorts О приложении скрыто. О приложении отображено. + Панель взаимодействия + Скрыть или изменить компоненты, относящиеся к панели взаимодействия. Виджет коротких уведомлений Виджет коротких уведомлений скрыт. Виджет коротких уведомлений отображен. + Скрыть серверную панель взаимодействия + Серверная панель взаимодействия скрыта. + Серверная панель взаимодействия показывается. + Инвертировать тему панели взаимодействия + Тема панели взаимодействия инвертирована. + Тема панели взаимодействия не инвертирована. + Изменить фон серверной панели взаимодействия + Цвет фона серверной панели взаимодействия изменен. + Цвет фона серверной панели взаимодействия не изменен. + "Некоторые панели взаимодействия используют тему, заданную на стороне сервера, а не тему программы. + +Изменяется цвет фона этих панелей взаимодействия. + +Если есть серверные изменения, цвет фона панели взаимодействия может не измениться." Панель инструментов Скрыть или изменить компоненты на панели инструментов - строка поиска, кнопки и заголовок. @@ -727,9 +707,9 @@ Shorts Фильтр двойного нажатия Фильтр двойного нажатия скрыт. Фильтр двойного нажатия отображен. - Заставки следующих видео - Заставки следующих видео скрыты. - Заставки следующих видео отображены. + Карточки следующих видео + Карточки следующих видео скрыты. + Карточки следующих видео отображены. Покадровая лента при перемотке Покадровая лента при перемотке скрыта. Покадровая лента при перемотке отображена. @@ -805,39 +785,19 @@ Shorts Кнопка \"Спасибо\" Кнопка \"Спасибо\" скрыта. Кнопка \"Спасибо\" отображена. - - Скрытие по индексу - Первая кнопка - Скрыта. - Отображена. - Вторая кнопка - Скрыта. - Отображена. - Третья кнопка - Скрыта. - Отображена. - Четвертая кнопка - Скрыта. - Отображена. - Пятая кнопка - Скрыта. - Отображена. - Шестая кнопка - Скрыта. - Отображена. - Седьмая кнопка - Скрыта. - Отображена. - Восьмая кнопка - Скрыта. - Отображена. - - Скрытие по индексу в прямых эфирах - О скрытии по индексу - "Скрытие кнопок по индексу до их инициализации. + + Действия с кнопками по индексу + "Кнопки действий скрыты по индексу. -- Скрытие кнопок действий не оставляет пустого места. -- Индекс кнопок действий не всегда может быть одной и той же кнопкой." +Информация: +• Неправильные кнопки действий могут быть скрыты, остальные будут отображаться. +• Кнопки скрытия действий не оставляют пустого места." + "Кнопки действий скрыты фильтром идентификаторов. + +Информация: +• Кнопки действий справа скрыты. +• Скрытие кнопок действий оставляет пустое место." + Индекс кнопки \"Микс\" Окружающая подсветка Отключить окружающую подсветку или обойти ограничение в режиме экономии заряда батареи. @@ -972,19 +932,19 @@ Shorts Панель взаимодействия Панель взаимодействия отключена. Панель взаимодействия включена. - Полноэкранный режим при запуске видео - "Полноэкранный режим при запуске видео включен. + Переход в полноэкранный режим при запуске видео + "Переходит в полноэкранный режим при запуске видео. Не работает когда: - плеер свернут - картинка в картинке (PiP) - фоновый режим." - Полноэкранный режим при запуске видео отключен. - Полноэкранный режим в конце видео + Не переходит в полноэкранный режим при запуске видео. + Переход из полноэкранного режима в конце видео Отключено Портретный вид Альбомный вид - Авто поворот + Портретный и альбомный Секция заголовка видео "Показывает секцию заголовка видео в полноэкранном режиме. @@ -1063,6 +1023,60 @@ Shorts Виброотклик при жесте увеличения экрана Виброотклик отключен. Виброотклик включен. + + Мини-плеер + Стиль мини-плеера. + Отключить восстановление миниплеера + <b>Продолжить просмотр</b> не будет восстанавливаться при запуске программы. + <b>Продолжить просмотр</b> возобновляется при запуске программы.<br><br>Информация:<br>• <b>Продолжить просмотр</b> это функция YouTube Premium.<br>• Этот параметр не включает принудительно <b>Продолжить просмотр</b>. + Тип мини-плеера + Отключено + Оригинал + Телефон + Планшет + Современный 1 + Современный 2 + Современный 3 + Новейший 4 + Скругления углов мини-плеера + Скругления углов включено. + Скругления углов отключено. + Изменение размера жестом + "Двойное нажатие и щипок для изменения размера включено. + +• Двойное нажатие для увеличения размера мини-плеера. +• Двойное нажатие еще раз для восстановления исходного размера." + Двойное нажатие и щипок для изменения размера отключено. + Перетаскивание мини-плеера + "Перетаскивание мини-плеера включено. + +Мини-плеер можно перемещать по экрану." + Перетаскивание мини-плеера отключено. + Жест горизонтального перетаскивания. + "Жест горизонтального перетаскивания включен. + +Мини-плеер можно перетаскивать за пределы экрана влево или вправо." + Жест горизонтального перетаскивания отключен. + Скрыть наложенные кнопки + Наложенные кнопки скрыты. + Наложенные кнопки отображаются. + Скрыть кнопки развертывания и закрытия + "Кнопки скрыты. + +Проведите, чтобы развернуть или закрыть." + Кнопки развертывания и закрытия показываются. + Кнопки перемотки + Кнопки перемотки скрыты. + Кнопки перемотки отображены. + Подстрочный текст + Подстрочный текст скрыт. + Подстрочный текст отображен. + Начальный размер + Начальный размер мини-плеера в пикселях. + Размер в пикселях должен быть между %1$s и %2$s. + Непрозрачность мини-плеера + Значение непрозрачности в промежутке 0-100, где 0 это прозрачный. + Непрозрачность должна быть в диапазоне 0-100. Сброс по умолчанию. Кнопки плеера Скрыть или показать кнопки в видео. @@ -1165,9 +1179,11 @@ Shorts Цвет шкалы воспроизведения Используется пользовательский цвет шкалы воспроизведения. Используется оригинальный цвет шкалы воспроизведения. - HEX код цвета шкалы воспроизведения - Введите HEX код цвета шкалы воспроизведения. - Неверное значение цвета полосы прогресса. + Пользовательский основной цвет полосы прогресса + Введите hex код основного цвета полосы прогресса. + Пользовательский акцентный цвет полосы прогресса + Введите hex код акцентного цвета полосы прогресса. + Неверный цвет полосы прогресса. Перемотка нажатием Перемотка нажатием включена. Перемотка нажатием отключена. @@ -1236,11 +1252,6 @@ Shorts Развернуть описание видео Описание видео автоматически развернуто. Описание видео разворачивается вручную. - Название в панели описания видео - "Введите название на панели описания видео. -Символы различаются в зависимости от языка. -«Развернуть описание видео» может не работать, если вы сохраните неверную строку." - Описание Shorts Фоновое воспроизведение Shorts diff --git a/patches/src/main/resources/youtube/translations/tr-rTR/strings.xml b/patches/src/main/resources/youtube/translations/tr-rTR/strings.xml index 7b1c29ed5..09db58c12 100644 --- a/patches/src/main/resources/youtube/translations/tr-rTR/strings.xml +++ b/patches/src/main/resources/youtube/translations/tr-rTR/strings.xml @@ -4,7 +4,7 @@ Video oynatıcı için erişilebilirlik kontrolleri açılsın mı? Bir erişilebilirlik hizmeti açık olduğundan kontrolleriniz değiştirildi. - ReVanced Extended + RVX Arama %s Varsayılan değerlere sıfırla. Deneysel Parametreler @@ -19,8 +19,64 @@ "%1$s yüklü değil. Lütfen web sitesinden %2$s dosyasını indirin." %s kurulmamış. Lütfen önce indiriniz. + RVX dili + Uygulama dili + Arapça + Azerbaycanca + Bulgarca + Bengalce + Katalanca + Çekçe + Danca + Almanca + Yunanca + İngilizce + İspanyolca + Estonca + Farsça + Fince + Fransızca + Gujarati + Hintçe + Hırvatça + Macarca + Endonezce + İtalyanca + Japonca + Kazakça + Korece + Litvanca + Letonca + Makedonca + Moğolca + Marathi dili + Malayca + Birmanca + Flemenkçe + Odiyaca + Pencapça + Lehçe + Portekizce + Rumence + Rusça + Slovakça + Slovence + Sırpça + İsveççe + Svahili dili + Tamil Dili + Teluguca + Tayca + Türkçe + Ukraynaca + Urdu dili + Vietnamca + Çince Reklamlar + Bitiş ekranındaki mağaza afişini gizle + Mağaza afişi gizli. + Mağaza afişi gösteriliyor. Tam ekran reklamlarını gizle Tam ekran reklamları gizli. Tam ekran reklamları görünür. @@ -99,7 +155,15 @@ DeArrow hakkında daha fazla bilgi edinmek için buraya dokunun." Akış altyazı butonunu gizle Altyazılar butonu gizleniyor. Altyazılar butonu gizlenmiyor. - Atlıkarınca rafını gizle + Karosel raflarını gizle + "Karosel rafları gizlidir, örneğin: +• Son dakika haberleri +• İzlemeye devam edin +• Daha fazla kanal keşfedin +• Tekrar dinleyin +• Alışveriş +• Tekrar izleyin" + Karosel rafları gösteriliyor. Buna benzer daha çok video çipini gizle Silindirik öneri butonları ve menüleri gizleniyor Silindirik öneri butonları ve menüleri gösteriliyor @@ -201,6 +265,7 @@ Mağaza" Akış açılır menü filtresini etkinleştir Akış açılır menü filtresi etkin. Akış açılır menü filtresi devre dışı. + Akış açılır menü filtre tipi Akış açılır menü filtresi Yeni bir satırla ayrılmış olarak filtrelenecek Açılır pencere menüsü adlarının listesi. @@ -281,8 +346,11 @@ Sunucu tarafındaki değişiklikler nedeniyle oynatıcı ekranı düzeni değiş Genel Başlangıç ​​sayfasını değiştir Varsayılan + Tüm abonelikler Kanalları Tara + Kurslar / Öğrenme Keşfet + Moda & Güzellik Gaming Geçmiş Kitaplık @@ -290,12 +358,19 @@ Sunucu tarafındaki değişiklikler nedeniyle oynatıcı ekranı düzeni değiş Canlı Filmler Müzik + Haberler + Bildirimler + Oynatma listesi + Podcastler Ara + Alışveriş Shorts Sporlar Abonelikler Trendler + Sanal Gerçeklik Daha sonra izlenecekler + Klipleriniz Başlangıç sayfası türünü değiştir "Başlangıç sayfası her zaman değişir. @@ -310,6 +385,9 @@ Sınırlama: Araç çubuğundaki geri butonu çalışmayabilir." Açılış animasyonunu devre dışı bırak Açılış animasyonu etkin değil. Açılış animasyonu etkin. + Yarı saydam durum çubuğunu devre dışı bırak. + Durum çubuğu opak. + Durum çubuğu opak veya yarı saydam. Gradyan yükleme ekranını etkinleştir Gradyan yükleme ekranı etkin Gradyan yükleme ekranı devre dışı @@ -327,6 +405,11 @@ Sınırlama: Araç çubuğundaki geri butonu çalışmayabilir." Telefon (En fazla 480 dp) Tablet Tablet (En az 600 dp) + Canlı halka eylemini değiştir + "Canlı halkaya tıklandığında kanal açılır. + +Kısıtlamalar: Shorts canlı yayını 'Shorts'u normal oynatıcıda aç' ayarı nedeniyle normal oynatıcıda açıldığında kanal açılmaz." + Canlı halkaya tıklandığında canlı yayın açılır. Uygulama Versiyonunu taklit et Sürüm taklit ediliyor Sürüm taklit edilmiyor @@ -344,11 +427,14 @@ Daha sonra kapatılırsa kullanıcı arayüzü hatalarını önlemek için uygul 18.33.40 - Eski shorts aksiyon çubuğunu geri yükle 18.38.45 - Eski varsayılan video kalitesi davranışını geri getir 18.48.39 - Görüntülemelerin ve beğenilerin gerçek zamanlı olarak güncellenmesini devre dışı bırakır + 19.26.42 - Cairo ikonunu gezinme ve araç çubuğunda devre dışı bırak + 19.33.37 - Eski tarz oynatma hızı açılır panelini geri yükleyin Hesap Menüsü Hesap menüsünde ve siz sekmesinde gizli veya göster. Hesap menüsünü gizle "Özel filtredeki hesap menüsü elementlerini gizle." + Hesap menüsü filtresi türü Hesap menüsü filtresini düzenle Yeni bir satırla ayrılmış olarak filtrelenecek hesap menüsü adlarının listesi. Etiketi gizle @@ -388,56 +474,6 @@ Daha sonra kapatılırsa kullanıcı arayüzü hatalarını önlemek için uygul %s kurulu değil. Lütfen kurun. Önkoşul Düğme eylemini geçersiz kılmak için Resmi YouTube Music uygulaması gereklidir. YouTube Müzik\'i indirmek için buraya dokunun. - - Miniplayer - Uygulama içi simge durumuna küçültülmüş oynatıcının stilini değiştirin. - Miniplayer tipi - Devre dışı - Orjinal - Telefon - Tablet - Modern 1 - Modern 2 - Modern 3 - Yuvarlatılmış köşeleri etkinleştir - Köşeler yuvarlatılmış. - Köşeler kare şeklinde. - Boyutlandırmak için çift dokunmayı ve sıkıştırmayı etkinleştir - "Çift dokunma eylemi ve yeniden boyutlandırmak için çimdikleme etkinleştirildi. - -- Miniplayer boyutunu artırmak için çift dokunun. -- Orijinal boyuta geri dönmek için tekrar çift dokunun." - Çift dokunma ve sıkıştırma ile yeniden boyutlandırma özelliği devre dışı bırakıldı. - Sürükle ve Bırak\'ı etkinleştir - "Sürükle ve bırak etkin. - -Miniplayer'ı ekranın herhangi bir köşesine sürükleyebilirsiniz." - Sürükle ve bırak devre dışı. - Yatay sürükleme hareketini etkinleştir. - "Yatay sürükleme hareketi etkinleştirildi. - -Miniplayer ekranın soluna veya sağına sürüklenebilir." - Yatay sürükleme hareketi devre dışı. - Genişlet ve kapat butonlarını gizle - Butonlar gizlendi. \n(mini oynatıcıyı kaydırarak genişletin veya kapatın) - Genişletme ve kapatma butonları gösteriliyor. - Genişlet ve kapat butonlarını gizle - "Düğmeler gizlendi. - -Genişletmek veya kapatmak için kaydırın." - Genişletme ve kapatma butonları gösteriliyor. - Alt metinleri gizle - Alt metinler gizli. - Alt metinler gizlenmiyor. - Atla ileri ve geri butonlarını gizle - Atla ileri ve geri butonlarını gizli. - Atla ileri ve geri butonlarını gizlenmiyor. - Başlangıç boyutu - Başlangıçtaki boyut, piksel cinsinden. - Piksel boyutu %1$s ve %2$s arasında olmalıdır. - Kaplama opaklığı - 0-100 arası opaklık değeri, 0 şeffaftır. - Mini oynatıcının siyah arkaplan opaklığı 0 ila 100 arasında olmalıdır. Varsayılan değere sıfırlandı. Gezinme çubuğu Gezinme çubuğu bölümü bileşenlerini gizleyin veya gösterin. @@ -475,6 +511,10 @@ Not: Bunu etkinleştirmek aynı zamanda video reklamlarını da zorla gizler." + Yarı saydam gezinme çubuğunu etkinleştir + Gezinme çubuğu yarı saydam. + Gezinme çubuğu opaktır. + Bazı YouTube sürümlerinde, bu ayar sistem gezinme çubuğunu şeffaf hale getirebilir veya PIP modunda düzen bozulabilir. Gezinme çubuğunu gizle Gezinme çubuğu gizlendi. Gezinme çubuğu gösteriliyor. @@ -493,12 +533,28 @@ Bu ayar etkili olmazsa Gizli moda geçmeyi deneyin." Veri tasarrufu menüsünü gizle Veri tasarrufu menüsü gizlendi. Veri tasarrufu menüsü gösteriliyor. + Otomatik Oynat veya Oynatma Menüsünü gizle + Otomatik Oynat veya Oynatma Menüsünü gizleniyor. + Otomatik Oynat veya Oynatma Menüsünü gösteriliyor. Video kalitesi tercihleri menüsünü gizle Video kalitesi tercihleri menüsü gizlendi. Video kalitesi tercihleri menüsü gösteriliyor. Arkaplan menüsünü gizle Arkaplan menüsü gizlendi. Arkaplan menüsü gösteriliriyor. + \"TV\'de izle\" butonunu gizle + \"TV\'de izle\" butonunu gizleniyor. + \"TV\'de izle\" butonunu gösteriliyor. + Tüm geçmişi yönet menüsünü gizle + Tüm geçmişi yönet menüsü gizleniyor. + Tüm geçmişi yönet menüsü gösteriliyor. + YouTube menüsünde verilerini gizle + Erişilebilirlik menüsünü gizle + Erişilebilirlik menüsünü gizleniyor. + Erişilebilirlik menüsünü gösteriliyor. + Hakkında menüsünü gizle + Hakkında menüsü gizleniyor. + Hakkında menüsü gösteriliyor. Yapılan eylemi bildiren çubuğu gizle Gizleniyor @@ -516,6 +572,10 @@ Bu ayar etkili olmazsa Gizli moda geçmeyi deneyin." Geniş arama çubuğu YouTube başlığını içerir. Geniş arama çubuğu YouTube başlığını içermez. \"Siz\" sekmesinde de etkinleştir + "Bu ayarın etkinleştirilmesi, Siz sekmesindeki Ayarlar butonunu kaldıracaktır. + +Bu durumda lütfen şu yolu kullanın: +Siz sekmesi→ Kanalı görüntüle→ Menü→ Ayarlar" Yayınla butonunu gizle \"Yayınla\" butonu gizli durumda. Yansıtma düğmesi görünür. @@ -534,6 +594,9 @@ Bu ayar etkili olmazsa Gizli moda geçmeyi deneyin." Sesli arama düğmesini gizle Sesli arama butonu gizlendi. Sesli arama butonu gösteriliyor. + YouTube Doodle\'larını gizle + YouTube Doodle\'larını gizleniyor. + YouTube Doodle\'larını gösteriliyor. Oluştur butonunu değiştir Oluştur butonunu ayarlar butonu ile değiştir. Düğmeye atanacak eylem türü @@ -641,8 +704,7 @@ Otomatik oynatma YouTube ayarlarından değiştirilebilir: Teşekkürler butonunu gizle \"Teşekkürler\" butonu gizleniyor. \"Teşekkürler\" butonu gösteriliyor. - - + Ortam modu Ambiyans modu kısıtlamalarını atlayın veya ortam modunu devre dışı bırakın. @@ -850,6 +912,57 @@ Sınırlama: Tıklandığında video başlığı kayboluyor." Videoyu yakınlaştırırkenki titreşimi kapat Titreşim kapalı. Titreşim açık. + + Miniplayer + Uygulama içi simge durumuna küçültülmüş oynatıcının stilini değiştirin. + Miniplayer tipi + Devre dışı + Orjinal + Telefon + Tablet + Modern 1 + Modern 2 + Modern 3 + Modern 4 + Yuvarlatılmış köşeleri etkinleştir + Köşeler yuvarlatılmış. + Köşeler kare şeklinde. + Boyutlandırmak için çift dokunmayı ve sıkıştırmayı etkinleştir + "Çift dokunma eylemi ve yeniden boyutlandırmak için çimdikleme etkinleştirildi. + +- Miniplayer boyutunu artırmak için çift dokunun. +- Orijinal boyuta geri dönmek için tekrar çift dokunun." + Çift dokunma ve sıkıştırma ile yeniden boyutlandırma özelliği devre dışı bırakıldı. + Sürükle ve Bırak\'ı etkinleştir + "Sürükle ve bırak etkin. + +Miniplayer'ı ekranın herhangi bir köşesine sürükleyebilirsiniz." + Sürükle ve bırak devre dışı. + Yatay sürükleme hareketini etkinleştir. + "Yatay sürükleme hareketi etkinleştirildi. + +Miniplayer ekranın soluna veya sağına sürüklenebilir." + Yatay sürükleme hareketi devre dışı. + Arayüz düğmelerini gizle + Arayüz düğmeleri gizli. + Arayüz düğmeleri gösteriliyor. + Genişlet ve kapat butonlarını gizle + "Düğmeler gizlendi. + +Genişletmek veya kapatmak için kaydırın." + Genişletme ve kapatma butonları gösterilir. + Atla ileri ve geri butonlarını gizle + Atla ileri ve geri butonlarını gizli. + Atla ileri ve geri butonlarını gizlenmiyor. + Alt metinleri gizle + Alt metinler gizli. + Alt metinler gizlenmiyor. + Başlangıç boyutu + Başlangıçtaki boyut, piksel cinsinden. + Piksel boyutu %1$s ve %2$s arasında olmalıdır. + Kaplama opaklığı + 0-100 arası opaklık değeri, 0 şeffaftır. + Mini oynatıcının siyah arkaplan opaklığı 0 ila 100 arasında olmalıdır. Varsayılan değere sıfırlandı. Oynatıcı butonları Videolardaki düğmeleri gizle veya göster. @@ -934,8 +1047,6 @@ Bilgi: Özel zaman çubuğu rengini etkinleştir Özel zaman çubuğu rengi etkin Özel zaman çubuğu rengi kapalı - Zaman çubuğu renk kodu - Zaman çubuğunun olmasını istediğiniz rengin kodunu (6\'lı/hex) girin Zaman çubuğuna dokunmayı etkinleştir Zaman çubuğuna dokunma etkin Zaman çubuğuna dokunma kapalı @@ -990,9 +1101,6 @@ Bilgi: Açıklamayı genişlet Video açıklaması otomatikman genişletilir. Video açıklaması elle genişletilir. - Video açıklama panelindeki başlık - "Video açıklama panelinin başlığını kendi dilinizde girin. Girilen dize video açıklama paneli başlığıyla eşleşmiyorsa Video açıklamasını genişlet seçeneği çalışmayabilir." - Açıklama Shorts Shorts oynatıcıya devam edilmesini devre dışı bırak diff --git a/patches/src/main/resources/youtube/translations/uk-rUA/strings.xml b/patches/src/main/resources/youtube/translations/uk-rUA/strings.xml index c60ae35df..5088a7450 100644 --- a/patches/src/main/resources/youtube/translations/uk-rUA/strings.xml +++ b/patches/src/main/resources/youtube/translations/uk-rUA/strings.xml @@ -4,7 +4,7 @@ Увімкнути спеціальні можливості для відеоплеєра? Керування змінено, оскільки служба спеціальних можливостей увімкнена. - Розширені + RVX Пошук %s Скинуто. Експериментальні опції @@ -74,9 +74,13 @@ Китайська Реклама + Приховати банер магазину на кінцевому екрані + Банер магазину приховано. + Банер магазину показується. Приховати повноекранну рекламу Повноекранну рекламу приховано. Повноекранну рекламу показується. + Повноекранну рекламу закрито. Приховати загальну рекламу Загальну рекламу приховано. Загальну рекламу показується. @@ -264,6 +268,9 @@ Увімкнути фільтр висувного меню у стрічці Фільтр висувного меню у стрічці увімкнено. Фільтр висувного меню у стрічці вимкнено. + Тип фільтра висувного меню у стрічці + Фільтрується, якщо містять.<br><br>Щоб приховати меню <b>\'Відтворити наступним у черзі\'</b>, можете використати <b>\'Відтворити наступним\'</b> або <b>\'у черзі\'</b> як ключові слова. + Фільтрується, якщо збігаються.<br><br>Щоб приховати меню <b>\'Відтворити наступним у черзі\'</b>, можете використати лише <b>\'Відтворити наступним у черзі\'</b> як ключові слова. Фільтр висувного меню у стрічці Список назв висувного меню для фільтрування, розділених новим рядком. @@ -348,6 +355,7 @@ Загальне Змінити початкову сторінку Стандартна + Підписки Перегляд каналів Навчання Що нового @@ -361,6 +369,7 @@ Музика Новини Сповіщення + Списки відтворення Подкасти Пошук Покупки @@ -368,6 +377,7 @@ Спорт Підписки Тенденції + Віртуальна реальність Переглянути пізніше Ваші кліпи Тип зміни початкової сторінки @@ -406,7 +416,9 @@ Планшетний Планшетний (Мін 600 dp) Змінити дію натискання на кружечок наживо - При натисканні на кружечок наживо відкривається канал. + "При натисканні на кружечок наживо відкривається канал. + +Застереження: Коли відкрито пряму трансляцію Shorts у поточному плеєрі налаштуванням 'Відкривати Shorts у поточному плеєрі', канал не відкривається." При натисканні на кружечок наживо відкривається пряма трансляція. Підробити версію програми Версію підроблено @@ -433,6 +445,9 @@ Приховати меню акаунту "Приховуються елементи меню облікового запису і вкладки Ви. Деякі компоненти не можуть бути приховані." + Тип фільтра меню облікового запису + Фільтрується, якщо містять.<br><br>Щоб приховати меню <b>\'Оформити підписку YouTube Premium\'</b>, можете використати <b>\'YouTube Premium\'</b> або <b>\'Premium\'</b> як ключові слова. + Фільтрується, якщо збігаються.<br><br>Щоб приховати меню <b>\'Оформити підписку YouTube Premium\'</b>, можете використати лише <b>\'Оформити підписку YouTube Premium\'</b> як ключові слова. Фільтр меню облікового запису Список назв меню облікового запису для фільтрування, розділених новим рядком. Приховати ідентифікатор @@ -472,56 +487,6 @@ %s не встановлено. Будь ласка, встановіть. Передумова Для перевизначення дії кнопки потрібно YouTube Music. Натисніть тут, щоб завантажити YouTube Music. - - Мініплеєр - Зміна стилю мінімізованого плеєра в додатку. - Тип мініплеєра - Вимкнено - Оригінал - Мінімальний - Планшетний - Новітній 1 - Новітній 2 - Новітній 3 - Увімкнути округлені кути - Кути округлені. - Кути квадратні. - Увімкнути подвійне натискання та щипок для зміни розміру - "Подвійне натискання та щипок для зміни розміру увімкнено. - -• Двічі торкніться, щоб збільшити розмір мініплеєра. -• Двічі торкніться ще раз, щоб відновити початковий розмір." - Подвійне натискання та щипок для зміни розміру вимкнено. - Увімкнути перетягування - "Перетягування увімкнено. - -Мініплеєр можливо перетягнути в будь-який кут екрану." - Перетягування вимкнено. - Увімкнути жест горизонтального перетягування - "Жест горизонтального перетягування увімкнено. - -Мініплеєр можливо перетягнути за межі екрана ліворуч чи праворуч." - Жест горизонтального перетягування вимкнено. - Приховати кнопку закриття - Кнопку закриття приховано. - Кнопку закриття показується. - Приховати кнопки розгортання і закриття - "Кнопки приховано. - -Проведіть, щоб розгорнути або закрити." - Кнопки розгортання і закриття показується. - Приховати підтексти - Підтексти приховано. - Підтексти показується. - Приховати кнопки перемотування вперед та назад - Перемотування вперед та назад приховано. - Перемотування вперед та назад показується. - Початковий розмір - Початковий розмір на екрані, у пікселях. - Розмір у пікселях повинен бути від %1$s до %2$s. - Непрозорість затемнення - Значення непрозорості в межах 0-100, де 0 це прозоро. - Непрозорість затемнення мініплеєра має бути в межах 0-100. Панель навігації Приховувати чи показувати секцію компонентів панелі навігації. @@ -562,6 +527,7 @@ Увімкнути напівпрозорість панелі навігації Панель навігації напівпрозора. Панель навігації непрозора. + У певних версіях YouTube цей параметр може зробити панель навігації прозорою або порушити макет у режимі PIP. Приховати панель навігації Панель навігації приховано. Панель навігації показується. @@ -812,39 +778,19 @@ Приховати кнопку Дякую Кнопку Дякую приховано. Кнопку Дякую показується. - - Приховати за індексом - Приховати першу кнопку - Першу кнопку приховано. - Першу кнопку показується. - Приховати другу кнопку - Другу кнопку приховано. - Другу кнопку показується. - Приховати третю кнопку - Третю кнопку приховано. - Третю кнопку показується. - Приховати четверту кнопку - Четверту кнопку приховано. - Четверту кнопку показується. - Приховати п\'яту кнопку - П\'яту кнопку приховано. - П\'яту кнопку показується. - Приховати шосту кнопку - Шосту кнопку приховано. - Шосту кнопку показується. - Приховати сьому кнопку - Сьому кнопку приховано. - Сьому кнопку показується. - Приховати восьму кнопку - Восьму кнопку приховано. - Восьму кнопку показується. - - Приховати за індексом у прямій трансляції - Про Приховати кнопку дії за індексом - "Приховати кнопки дії за індексом до ініціалізації кнопок дій. + + Приховувати кнопки дії за індексом + "Кнопки дії приховуються за індексом. -- Приховування кнопок дій не залишає порожнього місця. -- Індекс кнопок дій може не завжди бути потрібної кнопки." +Інформація: +• Можуть неправильно приховуватися чи не приховуватися кнопки дії. +• Приховування кнопок дії не залишає порожніх місць." + "Кнопки дії приховуються за фільтром ідентифікаторів. + +Інформація: +• Праві кнопки дії приховано. +• Приховування кнопок дії залишає порожні місця." + Індекс кнопки Ремікс Кінематографічне освітлення Обходити обмеження кінематографічного освітлення чи вимкнути кінематографічне освітлення. @@ -1067,6 +1013,60 @@ Вимкнути вібрацію при зумі Вібрацію вимкнено. Вібрацію увімкнено. + + Мініплеєр + Приховати або змінити компоненти, пов\'язані з Мініплеєром. + Вимкнути відновлення Мініплеєра + <b>Продовжити перегляд</b> не відновлюватиметься при запуску програми. + <b>Продовжити перегляд</b> відновлюватиметься при запуску програми.<br><br>Інформація:<br>• <b>Продовжити перегляд</b> це функція YouTube Premium.<br>• Цей параметр не вмикає примусово <b>Продовжити перегляд</b>. + Тип мініплеєра + Вимкнено + Оригінал + Мінімальний + Планшетний + Новітній 1 + Новітній 2 + Новітній 3 + Новітній 4 + Увімкнути округлені кути + Кути округлені. + Кути квадратні. + Увімкнути подвійне натискання та щипок для зміни розміру + "Подвійне натискання та щипок для зміни розміру увімкнено. + +• Двічі торкніться, щоб збільшити розмір Мініплеєра. +• Двічі торкніться ще раз, щоб відновити початковий розмір." + Подвійне натискання та щипок для зміни розміру вимкнено. + Увімкнути перетягування + "Перетягування увімкнено. + +Мініплеєр можливо перетягнути в будь-який кут екрану." + Перетягування вимкнено. + Увімкнути жест горизонтального перетягування + "Жест горизонтального перетягування увімкнено. + +Мініплеєр можливо перетягнути за межі екрана ліворуч чи праворуч." + Жест горизонтального перетягування вимкнено. + Приховати накладені кнопки + Накладені кнопки приховано. + Накладені кнопки показується. + Приховати кнопки розгортання і закриття + "Кнопки приховано. + +Проведіть, щоб розгорнути або закрити." + Кнопки розгортання і закриття показується. + Приховати кнопки перемотування вперед та назад + Перемотування вперед та назад приховано. + Перемотування вперед та назад показується. + Приховати підтексти + Підтексти приховано. + Підтексти показується. + Початковий розмір + Початковий розмір на екрані, у пікселях. + Розмір у пікселях повинен бути від %1$s до %2$s. + Непрозорість затемнення + Значення непрозорості в межах 0-100, де 0 це прозоро. + Непрозорість затемнення мініплеєра має бути в межах 0-100. Кнопки плеєра Приховувати чи показувати кнопки у відео. @@ -1172,9 +1172,11 @@ Увімкнути користувацький колір смуги прогресу Користувацький колір смуги прогресу увімкнено. Користувацький колір смуги прогресу вимкнено. - Користувацьке значення кольору смуги прогресу - Введіть hex код кольору смуги прогресу. - Недійсне значення кольору смуги прогресу. + Користувацький основний колір смуги прогресу + Введіть hex код основного кольору смуги прогресу. + Користувацький акцентний колір смуги прогресу + Введіть hex код акцентного кольору смуги прогресу. + Недійсний колір смуги прогресу. Увімкнути натискання на смугу прогресу Натискання на смугу прогресу увімкнено. Натискання на смугу прогресу вимкнено. @@ -1243,11 +1245,6 @@ Розгортати опис відео Опис відео розгортається автоматично. Опис відео розгортається вручну. - Назва в панелі опису відео - "Введіть назву в панелі опису відео. -Ці символи варіюються залежно від вашої мови. -'Розгортати опис відео' може не працювати, якщо збережете неправильний рядок." - Опис YouTube Shorts Вимкнути фонове відтворення Shorts diff --git a/patches/src/main/resources/youtube/translations/vi-rVN/strings.xml b/patches/src/main/resources/youtube/translations/vi-rVN/strings.xml index a7663399e..7de3fd20e 100644 --- a/patches/src/main/resources/youtube/translations/vi-rVN/strings.xml +++ b/patches/src/main/resources/youtube/translations/vi-rVN/strings.xml @@ -4,7 +4,7 @@ Bật các điều khiển trợ năng cho trình phát video? Các điều khiển của bạn đã được sửa đổi vì dịch vụ trợ năng đang bật. - ReVanced Extended + RVX Tìm kiếm trong %s Đã đặt lại giá trị về mặc định. Tính năng thử nghiệm @@ -74,9 +74,13 @@ Tiếng Trung Quảng cáo + Ẩn dải quảng cáo cửa hàng ở cuối video + Dải quảng cáo cửa hàng đã ẩn. + Dải quảng cáo cửa hàng được hiển thị. Ẩn quảng cáo toàn màn hình Quảng cáo toàn màn hình đã ẩn. Quảng cáo toàn màn hình được hiển thị. + Quảng cáo toàn màn hình đã đóng. Ẩn quảng cáo chung Quảng cáo chung đã ẩn. Quảng cáo chung được hiển thị. @@ -158,7 +162,7 @@ Nhấn vào đây để tìm hiểu thêm về DeArrow." "Các kệ đã ẩn, chẳng hạn như: • Tin nổi bật • Tiếp tục xem -• Khám phá các chủ đề khác +• Khám phá thêm • Nghe lại • Mua sắm • Xem lại" @@ -264,6 +268,9 @@ Cửa hàng" Bộ lọc trình đơn tuỳ chọn trên bảng tin Bộ lọc trình đơn tuỳ chọn trên bảng tin đã bật. Bộ lọc trình đơn tuỳ chọn trên bảng tin đã tắt. + Cài đặt kiểu lọc + Lọc nếu trong đó có chứa từ khoá đã nhập.<br><br>Để ẩn mục <b>\"Phát tiếp theo trong hàng chờ\"</b>, bạn có thể sử dụng từ khoá <b>\"Phát tiếp theo\"</b> hoặc <b>\"hàng chờ\"</b>. + Lọc nếu khớp toàn bộ từ khoá đã nhập.<br><br>Để ẩn mục <b>\"Phát tiếp theo trong hàng chờ\"</b>, bạn chỉ có thể sử dụng từ khoá <b>\"Phát tiếp theo trong hàng chờ\"</b>. Cài đặt bộ lọc Nhập tên các mục thành phần của trình đơn tuỳ chọn mà bạn muốn lọc được phân cách bằng dòng. @@ -335,7 +342,7 @@ Bộ lọc này có thể không còn hoạt động nữa, thay vào đó, hãy "Các video có số lượt xem ít hoặc nhiều hơn con số bạn đã đặt sẽ bị ẩn trên thẻ Trang chủ/Kênh đăng ký/Kết quả tìm kiếm. Hạn chế: -• Không ẩn đối với các video Short. +• Không ẩn đối với các video ngắn. • Các video có 0 lượt xem cũng không bị lọc." Ẩn các video có liên quan Các video có liên quan đã ẩn. @@ -348,6 +355,7 @@ Nếu bố cục của màn hình trình phát thay đổi do các thay đổi t Tổng quan Thay đổi trang khởi động Mặc định + Tất cả kênh đã đăng ký Duyệt kênh Học tập Khám phá @@ -361,6 +369,7 @@ Nếu bố cục của màn hình trình phát thay đổi do các thay đổi t Âm nhạc Tin tức Thông báo + Danh sách phát Podcast Tìm kiếm Mua sắm @@ -368,6 +377,7 @@ Nếu bố cục của màn hình trình phát thay đổi do các thay đổi t Thể thao Kênh đăng ký Thịnh hành + Thực tế ảo Xem sau Đoạn video của bạn Thay đổi kiểu trang khởi động @@ -406,14 +416,16 @@ Tuỳ chọn này chỉ tự động chấp nhận hộp thoại cảnh báo, ch Máy tính bảng Máy tính bảng (Tối thiểu 600 dp) Thay đổi thao tác nhấn vào vòng phát trực tiếp - Mở kênh khi nhấn vào vòng phát trực tiếp. + "Mở kênh khi nhấn vào vòng phát trực tiếp. + +Hạn chế: Khi video ngắn phát trực tiếp được mở trong trình phát do cài đặt \"Mở video ngắn trong trình phát\", thì kênh sẽ không được mở." Mở video đang phát trực tiếp khi nhấn vào vòng phát trực tiếp. Giả mạo phiên bản ứng dụng Phiên bản được giả mạo Phiên bản không được giả mạo "Phiên bản ứng dụng sẽ được giả mạo thành một phiên bản cũ hơn của Youtube. -Điều này sẽ làm thay đổi giao diện và tính năng của ứng dụng, nhưng đồng thời cũng có thể xẩy ra một số lỗi không xác định. +Điều này sẽ làm thay đổi giao diện và tính năng của ứng dụng, nhưng đồng thời cũng có thể xảy ra một số lỗi không xác định. Nếu muốn tắt tính năng này sau đó, bạn nên xóa dữ liệu ứng dụng để tránh phát sinh lỗi giao diện." Tuỳ chọn phiên bản giả mạo @@ -433,6 +445,9 @@ Nếu muốn tắt tính năng này sau đó, bạn nên xóa dữ liệu ứng Bộ lọc trình đơn Tài khoản "Ẩn các mục của trình đơn Tài khoản và thẻ Bạn. Một số mục có thể không bị ẩn." + Cài đặt kiểu lọc + Lọc nếu trong đó có chứa từ khoá đã nhập.<br><br>Để ẩn mục <b>\"Mua Youtube Premium\"</b>, bạn có thể sử dụng từ khoá <b>\"YouTube Premium\"</b> hoặc <b>\"Premium\"</b>. + Lọc nếu khớp toàn bộ từ khoá đã nhập.<br><br>Để ẩn mục <b>\"Mua Youtube Premium\"</b>, bạn chỉ có thể sử dụng từ khoá <b>\"Mua Youtube Premium\"</b>. Cài đặt bộ lọc Nhập tên các mục thành phần của trình đơn Tài khoản mà bạn muốn lọc được phân cách bằng dòng. Ẩn tên hiển thị @@ -472,56 +487,6 @@ Một số mục có thể không bị ẩn." Hiện %s chưa được cài đặt. Hãy cài đặt và thử lại. Điều kiện tiên quyết Cần phải có YouTube Music để ghi đè chức năng của nút. Nhấn vào đây để tải YouTube Music. - - Trình phát thu nhỏ - Thay đổi kiểu trình phát thu nhỏ trong ứng dụng. - Loại trình phát thu nhỏ - Tắt - Gốc - Tối giản - Máy tính bảng - Hiện đại 1 - Hiện đại 2 - Hiện đại 3 - Bo tròn góc - Các góc của trình phát thu nhỏ được bo tròn. - Các góc của trình phát thu nhỏ như mặc định. - Nhấn đúp và chụm để thay đổi kích thước - "Thao tác nhấn đúp và chụm để thay đổi kích thước được bật. - -• Nhấn đúp để tăng kích thước trình phát thu nhỏ. -• Nhấn đúp lần nữa để khôi phục kích thước ban đầu." - Thao tác nhấn đúp và chụm để thay đổi kích thước đã tắt. - Kéo và thả - "Kéo và thả được bật. - -Trình phát thu nhỏ có thể được kéo đến bất kỳ góc nào trên màn hình." - Kéo và thả đã tắt. - Cử chỉ kéo ngang - "Cử chỉ kéo ngang được bật. - -Trình phát thu nhỏ có thể được kéo vào rìa bên trái hoặc phải trên màn hình." - Cử chỉ kéo ngang đã tắt. - Ẩn nút Đóng - Nút Đóng đã ẩn. - Nút Đóng được hiển thị. - Ẩn các nút Mở rộng và Đóng - "Các nút Mở rộng và Đóng đã ẩn. - -Vuốt trình phát thu nhỏ để mở rộng hoặc đóng." - Các nút Mở rộng và Đóng được hiển thị. - Ẩn các văn bản phụ - Các văn bản phụ đã ẩn. - Các văn bản phụ được hiển thị. - Ẩn các nút tua nhanh và tua lại - Các nút tua nhanh và tua lại đã ẩn. - Các nút tua nhanh và tua lại được hiển thị. - Kích thước ban đầu - Nhập kích thước ban đầu của trình phát thu nhỏ, tính bằng pixel. - Kích thước pixel phải nằm trong khoảng từ %1$s tới %2$s. - Độ mờ lớp phủ - Giá trị độ mờ của lớp phủ trình phát thu nhỏ trong khoảng từ 0 đến 100, trong đó 0 là trong suốt. - Độ mờ của lớp phủ trình phát thu nhỏ phải nằm trong khoảng 0 - 100. Thanh điều hướng Ẩn hoặc hiển thị các thành phần trên Thanh điều hướng. @@ -562,6 +527,7 @@ Nếu cài đặt này không có hiệu lực, hãy thử chuyển sang chế Thanh điều hướng trong suốt Thanh điều hướng trong suốt đang được áp dụng. Thanh điều hướng mặc định đang được áp dụng. + Trong một số phiên bản YouTube, cài đặt này có thể khiến thanh điều hướng hệ thống trở nên trong suốt hoặc gây ra lỗi giao diện trong chế độ Hình trong hình (PIP). Ẩn thanh điều hướng Thanh điều hướng đã ẩn. Thanh điều hướng được hiển thị. @@ -775,8 +741,8 @@ Cài đặt → Tự động phát/Phát → Tự động phát video tiếp the "Các tag có cụm từ như \"#\", \"Chiến dịch gây quỹ\", \"Cửa hàng\" và \"sản phẩm\" đã ẩn bên dưới tiêu đề video." "Các tag có các cụm từ như \"#\", \"Chiến dịch gây quỹ\", \"Cửa hàng\" và \"sản phẩm\" được hiển thị trong phụ đề video." - Nút thao tác - Ẩn hoặc hiển thị các nút thao tác bên dưới video. + Nút tương tác + Ẩn hoặc hiển thị các nút tương tác bên dưới video. Tắt hoạt ảnh các nút Thích và Không thích Các nút Thích và Không thích sẽ không sáng lên khi nhấn vào. Các nút Thích và Không thích sẽ sáng lên khi nhấn vào. @@ -810,39 +776,19 @@ Cài đặt → Tự động phát/Phát → Tự động phát video tiếp the Ẩn nút Cảm ơn Nút Cảm ơn đã ẩn. Nút Cảm ơn được hiển thị. - - Ẩn theo chỉ mục - Ẩn nút đầu tiên - Nút đầu tiên đã ẩn. - Nút đầu tiên được hiển thị. - Ẩn nút thứ hai - Nút thứ hai đã ẩn. - Nút thứ hai được hiển thị. - Ẩn nút thứ ba - Nút thứ ba đã ẩn. - Nút thứ ba được hiển thị. - Ẩn nút thứ tư - Nút thứ tư đã ẩn. - Nút thứ tư được hiển thị. - Ẩn nút thứ năm - Nút thứ năm đã ẩn. - Nút thứ năm được hiển thị. - Ẩn nút thứ sáu - Nút thứ sáu đã ẩn. - Nút thứ sáu được hiển thị. - Ẩn nút thứ bảy - Nút thứ bảy đã ẩn. - Nút thứ bảy được hiển thị. - Ẩn nút thứ tám - Nút thứ tám đã ẩn. - Nút thứ tám được hiển thị. - - Ẩn theo chỉ mục trong phát trực tiếp - Giới thiệu về Ẩn nút thao tác theo chỉ mục - "Ẩn các nút thao tác theo chỉ mục trước khi nút được khởi tạo. + + Ẩn nút thao tác theo thứ tự + "Các nút tương tác đã ẩn theo thứ tự. -- Khi các nút thao tác đã ẩn, không để lại khoảng trống. -- Chỉ mục của các nút thao tác có thể thay đổi, không phải lúc nào cũng là cùng một nút." +Chi tiết: +• Các nút tương tác có thể ẩn không đúng, hoặc không bị ẩn. +• Các nút tương tác đang bị ẩn sẽ không để lại khoảng trống." + "Các nút tương tác đã ẩn theo tên. + +Chi tiết: +• Các nút tương tác bên phải bị ẩn. +• Các nút tương tác đang bị ẩn sẽ để lại khoảng trống." + Vị trí nút Phối lại Chế độ môi trường xung quanh Tắt hoặc bỏ qua các hạn chế của Chế độ môi trường xung quanh. @@ -1065,6 +1011,60 @@ Hạn chế: Tiêu đề video sẽ biến mất khi nhấn vào." Tắt phản hồi xúc giác khi chụm để thu phóng Phản hồi xúc giác đã tắt. Phản hồi xúc giác được bật. + + Trình phát thu nhỏ + Ẩn hoặc thay đổi các thành phần liên quan đến trình phát thu nhỏ. + Tắt tính năng tiếp tục video trong trình phát thu nhỏ + <b>Tiếp tục xem</b> sẽ không còn được tiếp tục mỗi khi khởi động ứng dụng. + <b>Tiếp tục xem</b> sẽ được tiếp tục mỗi khi khởi động ứng dụng.<br><br>Chi tiết:<br>• <b>Tiếp tục xem</b> là một trong những tính năng của Youtube Premium.<br>• Cài đặt này không giúp cho <b>Tiếp tục xem</b> luôn được bật. + Loại trình phát thu nhỏ + Tắt + Mặc định + Tối giản + Máy tính bảng + Hiện đại 1 + Hiện đại 2 + Hiện đại 3 + Hiện đại 4 + Bo tròn góc + Các góc của trình phát thu nhỏ được bo tròn. + Các góc của trình phát thu nhỏ như mặc định. + Nhấn đúp và chụm để thay đổi kích thước + "Thao tác nhấn đúp và chụm để thay đổi kích thước được bật. + +• Nhấn đúp để tăng kích thước trình phát thu nhỏ. +• Nhấn đúp lần nữa để khôi phục kích thước ban đầu." + Thao tác nhấn đúp và chụm để thay đổi kích thước đã tắt. + Kéo và thả + "Kéo và thả được bật. + +Trình phát thu nhỏ có thể được kéo đến bất kỳ góc nào trên màn hình." + Kéo và thả đã tắt. + Cử chỉ kéo ngang + "Cử chỉ kéo ngang được bật. + +Trình phát thu nhỏ có thể được kéo vào rìa bên trái hoặc phải trên màn hình." + Cử chỉ kéo ngang đã tắt. + Ẩn các nút trong trình phát thu nhỏ + Các nút trong trình phát thu nhỏ đã ẩn. + Các nút trong trình phát thu nhỏ được hiển thị. + Ẩn các nút Mở rộng và Đóng + "Các nút Mở rộng và Đóng đã ẩn. + +Vuốt trình phát thu nhỏ để mở rộng hoặc đóng." + Các nút Mở rộng và Đóng được hiển thị. + Ẩn các nút tua nhanh và tua lại + Các nút tua nhanh và tua lại đã ẩn. + Các nút tua nhanh và tua lại được hiển thị. + Ẩn các văn bản phụ + Các văn bản phụ đã ẩn. + Các văn bản phụ được hiển thị. + Kích thước ban đầu + Nhập kích thước ban đầu của trình phát thu nhỏ, tính bằng pixel. + Kích thước pixel phải nằm trong khoảng từ %1$s tới %2$s. + Độ mờ lớp phủ + Giá trị độ mờ của lớp phủ trình phát thu nhỏ trong khoảng từ 0 đến 100, trong đó 0 là trong suốt. + Độ mờ của lớp phủ trình phát thu nhỏ phải nằm trong khoảng 0 - 100. Nút trong trình phát Ẩn hoặc hiển thị các nút trong trình phát. @@ -1170,9 +1170,11 @@ Nhấn và giữ để chuyển loại thông tin cần thêm vào." Màu thanh tiến trình tuỳ chỉnh Màu thanh tiến trình tuỳ chỉnh được áp dụng. Màu thanh tiến trình mặc định được áp dụng. - Thay đổi màu thanh tiến trình - Nhập mã màu hex của thanh tiến trình mà bạn muốn thay đổi. - Mã màu thanh tiến trình đã nhập không hợp lệ. + Tuỳ chỉnh màu chính của thanh tiến trình + Nhập mã hex cho màu chính của thanh tiến trình mà bạn muốn thay đổi. + Tuỳ chỉnh màu nhấn của thanh tiến trình + Nhập mã hex cho màu nhấn của thanh tiến trình mà bạn muốn thay đổi. + Mã hex mà bạn đã nhập không hợp lệ. Chạm thanh tiến trình để tua Chạm thanh tiến trình video để tua đã bật. Chạm thanh tiến trình video để tua đã tắt. @@ -1241,18 +1243,14 @@ Vì vậy, bạn nên bật tính năng này khi có kết nối mạng ổn đ Mở rộng mô tả video Mô tả video được mở rộng tự động. Mô tả video được mở rộng thủ công. - Tiêu đề trong bảng mô tả video - "Nhập tiêu đề vào bảng mô tả video. -Mở rộng mô tả video có thể không hoạt động nếu bạn nhập nội dung không khớp với tiêu đề thực tế của bảng mô tả video." - Mô tả Shorts Tắt tính năng phát trong nền cho trình Shorts Phát trong nền cho trình Shorts đã tắt. Phát trong nền cho trình Shorts được bật. - Tắt tính năng tiếp tục phát video Shorts - Trinh phát Shorts sẽ không tiếp tục khi ứng dụng khởi chạy. - Trinh phát Shorts sẽ tiếp tục khi ứng dụng khởi chạy. + Tắt tính năng tiếp tục video Shorts + Trinh phát Shorts sẽ không còn được tiếp tục khi khởi động ứng dụng. + Trinh phát Shorts vẫn sẽ tiếp tục khi khởi động ứng dụng. Ẩn nút nổi "Các nút nổi như \"Dùng âm thanh này\" đã ẩn trong thẻ kênh Shorts." "Các nút nổi như \"Dùng âm thanh này\" được hiển thị trong thẻ kênh Shorts." @@ -1263,7 +1261,7 @@ Mở rộng mô tả video có thể không hoạt động nếu bạn nhập n Ẩn trong hồ sơ kênh "Đã ẩn trong hồ sơ kênh. -Cụ thể: +Chi tiết: • Chỉ những kệ có tiêu đề Shorts trên thẻ trang chủ mới bị ẩn." Được hiển thị trong hồ sơ kênh. Ẩn trên thẻ Trang chủ và các video liên quan @@ -1295,8 +1293,8 @@ Cụ thể: Thanh kênh đã ẩn. Thanh kênh được hiển thị. Ẩn nhãn liên kết hướng tới video đầy đủ - Nhãn liên kết tới hướng video đầy đủ đã ẩn. - Nhãn liên kết tới video đầy đủ được hiển thị. + Nhãn liên kết hướng tới video đầy đủ đã ẩn. + Nhãn liên kết hướng tới video đầy đủ được hiển thị. Ẩn bảng thông tin Bảng thông tin đã ẩn. Bảng thông tin được hiển thị. @@ -1363,7 +1361,7 @@ Cụ thể: Nút \"Dùng âm thanh này\" đã ẩn. Nút \"Dùng âm thanh này\" được hiển thị. - Nút thao tác + Nút tương tác Ẩn nút Thích Nút Thích đã ẩn. Nút Thích được hiển thị. @@ -1508,7 +1506,7 @@ Không còn lề trên và dưới trong trình phát." Tắt độ sáng HDR tự động Độ sáng HDR tự động đã tắt. Độ sáng HDR tự động đã bật. - Vuốt để chuyển video + Tắt vuốt để chuyển video Vuốt lên/xuống trong chế độ toàn màn hình sẽ không phát video tiếp theo/trước đó. Vuốt lên/xuống trong chế độ toàn màn hình sẽ phát video tiếp theo/trước đó. Tắt vuốt để chuyển sang chế độ toàn màn hình (bên dưới trình phát) @@ -1917,7 +1915,7 @@ Nhấn vào Tiếp tục và cho phép thay đổi lựa chọn tối ưu hoá p • Âm lượng ổn định không khả dụng. • Tắt Bản âm thanh tự động không khả dụng. • Video dành cho trẻ em có thể không phát được khi bạn đã đăng xuất hoặc bật chế độ ẩn danh." - • Có thể xẩy ra sự cố phát (Không khuyến khích). + • Có thể xảy ra sự cố phát (Không khuyến khích). "• Phim hoặc video trả phí có thể không phát được. • Video dành cho trẻ em có thể không phát được khi bạn đã đăng xuất hoặc bật chế độ ẩn danh." Bắt buộc iOS sử dụng AVC (H.264) diff --git a/patches/src/main/resources/youtube/translations/zh-rCN/strings.xml b/patches/src/main/resources/youtube/translations/zh-rCN/strings.xml index c9e27093d..8d891cde6 100644 --- a/patches/src/main/resources/youtube/translations/zh-rCN/strings.xml +++ b/patches/src/main/resources/youtube/translations/zh-rCN/strings.xml @@ -4,7 +4,6 @@ 开启视频播放器的访问控制? 由于无障碍服务已启用,您的控制被修改 - ReVanced Extended 搜索 %s 重置为默认值 实验性功能 @@ -19,8 +18,64 @@ "%1$s 未安装 请从网站下载 %2$s" %s 未安装,请先安装它。 + RVX 语言 + 应用程序语言 + 阿拉伯语 + 阿塞拜疆语 + 保加利亚语 + 孟加拉语 + 加泰罗尼亚语 + 捷克语 + 丹麦语 + 德语 + 希腊语 + 英语 + 西班牙语 + 爱沙尼亚语 + 波斯语 + 芬兰语 + 法语 + 古吉拉特语 + 印地语 + 克罗地亚语 + 匈牙利语 + 印度尼西亚语 + 意大利语 + 日语 + 哈萨克语 + 韩语 + 立陶宛语 + 拉脱维亚语 + 马其顿语 + 蒙古语 + 马拉提语 + 马来西亚语 + 缅甸语 + 荷兰语 + 奥迪亚语 + 旁遮普语 + 波兰语 + 葡萄牙语 + 罗马尼亚语 + 俄语 + 斯洛伐克文 + 斯洛文尼亚语 + 塞尔维亚语 + 瑞典语 + 斯瓦希里语 + 泰米尔语 + 泰卢固语 + 泰语 + 土耳其语 + 乌克兰语 + 乌尔都语 + 越南语 + 中文 广告 + 隐藏片尾商店横幅 + 商店横幅已隐藏 + 商店横幅已显示 隐藏全屏广告 全屏广告已隐藏 全屏广告已显示 @@ -102,6 +157,13 @@ 隐藏字幕按钮 显示字幕按钮 隐藏轮播内容 + "隐藏以下分类: +• 突发新闻 +• 继续观看 +• 探索更多频道 +• 重新收听 +• 购物 +• 重新观看" 隐藏 Chips 视频栏 Chips 视频栏已隐藏 Chips 视频栏已显示 @@ -393,28 +455,6 @@ %s 未安装,请先安装 前提条件 需要 YouTube Music 来覆盖按钮操作,点击此处下载 YouTube Music - - 迷你播放器 - 更改应用最小化播放器样式 - 迷你播放器样式 - 原版 - 手机 - 平板 - Modern 1 - Modern 2 - Modern 3 - 隐藏展开和关闭按钮 - 按钮已隐藏\n(滑动迷你播放器来展开或关闭) - 展开和关闭按钮已显示 - 隐藏子文本 - 子文本已隐藏 - 子文本已显示 - 隐藏跳过和返回按钮 - 跳过和返回按钮已隐藏 - 跳过和返回按钮已显示 - 叠加层不透明度 - 不透明度值介于 0-100 之间,0 为透明 - 迷你播放器的叠加层不透明度必须介于 0-100 之间 重置为默认值 导航栏 隐藏或显示导航栏区域的组件 @@ -672,8 +712,7 @@ 隐藏感谢按钮 感谢按钮已隐藏 感谢按钮已显示 - - + 氛围模式 绕过氛围模式限制或禁用氛围模式 @@ -881,6 +920,25 @@ 缩放触感反馈 禁用缩放触感反馈 启用缩放触感反馈 + + 迷你播放器 + 更改应用最小化播放器样式 + 迷你播放器样式 + 原版 + 手机 + 平板 + Modern 1 + Modern 2 + Modern 3 + 隐藏跳过和返回按钮 + 跳过和返回按钮已隐藏 + 跳过和返回按钮已显示 + 隐藏子文本 + 子文本已隐藏 + 子文本已显示 + 叠加层不透明度 + 不透明度值介于 0-100 之间,0 为透明 + 迷你播放器的叠加层不透明度必须介于 0-100 之间 重置为默认值 播放器按钮 隐藏或显示视频中的按钮 @@ -962,8 +1020,6 @@ 启用自定义进度条颜色 自定义进度条颜色已启用 自定义进度条颜色已禁用 - 自定义进度条颜色 - 输入进度条颜色16进制代码 启用进度条点击 进度条点击已启用 进度条点击已禁用 @@ -1032,10 +1088,6 @@ 自动展开视频描述 自动展开视频描述 手动展开视频描述 - 视频描述面板中的标题 - "在视频描述面板中输入标题 -如果保存了不正确的字符串,则“展开视频描述”可能无法正常工作" - 描述 Shorts 禁止恢复 Shorts 播放器 diff --git a/patches/src/main/resources/youtube/translations/zh-rTW/strings.xml b/patches/src/main/resources/youtube/translations/zh-rTW/strings.xml index 0bc804a8c..22b51a325 100644 --- a/patches/src/main/resources/youtube/translations/zh-rTW/strings.xml +++ b/patches/src/main/resources/youtube/translations/zh-rTW/strings.xml @@ -2,9 +2,9 @@ 是否啟用影片播放器的無障礙控制? - 由於已啟用無障礙服務,因此您的控制選項已被修改。 + 由於已啟用無障礙服務,因此你的控制選項已被修改。 - ReVanced 擴充功能 + RVX 搜尋設定 重設為預設值。 實驗性功能 @@ -12,47 +12,47 @@ 重新啟動以套用更改後的介面 重新啟動以重新整理介面 一般 - 影片下載套件名 - 您安裝的外部下載器應用程式的套件名稱,例如 NewPipe 或 YTDLnis。 + 影片下載器套件名稱 + 你安裝的外部下載器應用程式的套件名稱,例如 NewPipe 或 YTDLnis。 外部下載器 警告 - "%1$s 未安裝 -請從網站下載 %2$s" - %s 未安裝,請先安裝 - RVX語言 - 應用語言 - 阿拉伯語 - 亞塞拜然語 - 保加利亞語 - 孟加拉語 - 加泰隆尼亞語 - 捷克語 - 丹麥語 - 德語 - 希臘語 - 英語 - 西班牙語 - 愛沙尼亞語 - 波斯語 - 芬蘭語 - 法語 - 古吉拉特語 - 印地語 - 克羅埃西亞語 - 匈牙利語 - 印尼語 - 義大利語 - 日語 - 哈薩克語 - 韓語 - 立陶宛語 - 拉脫維亞語 - 馬其頓語 - 蒙古語 - 馬拉提語 - 馬來語 - 緬甸語 - 荷蘭語 + "未安裝 %1$s。 +請從網站下載 %2$s。" + 未安裝 %s。請安裝該應用程式。 + RVX 語言 + 應用程式語言 + 阿拉伯文 + 亞塞拜然文 + 保加利亞文 + 孟加拉文 + 加泰隆尼亞文 + 捷克文 + 丹麥文 + 德文 + 希臘文 + 英文 + 西班牙文 + 愛沙尼亞文 + 波斯文 + 芬蘭文 + 法文 + 古吉拉特文 + 印地文 + 克羅埃西亞文 + 匈牙利文 + 印尼文 + 義大利文 + 日文 + 哈薩克文 + 韓文 + 立陶宛文 + 拉脫維亞文 + 馬其頓文 + 蒙古文 + 馬拉提文 + 馬來文 + 緬甸文 + 荷蘭文 歐迪亞語 旁遮普語 波蘭語 @@ -74,9 +74,13 @@ 中文 廣告 + 隱藏片尾商店橫幅 + 商店橫幅已隱藏。 + 商店橫幅已顯示。 隱藏全螢幕廣告 全螢幕廣告已隱藏 全螢幕廣告已顯示 + 全螢幕廣告已關閉。 隱藏一般廣告 一般廣告已隱藏 一般廣告已顯示 @@ -86,15 +90,15 @@ 隱藏付費推廣標籤 付費推廣標籤已隱藏 付費推廣標籤已顯示 - 隱藏玩家購物架 - 購物架已隱藏。 - 購物架已顯示。 - 隱藏促銷警報橫幅 - 促銷警報橫幅已隱藏。 - 顯示促銷警報橫幅。 - 隱藏自我推廣卡片 - 自我推廣卡片已隱藏 - 自我推廣卡片已顯示 + 隱藏播放器購物區塊 + 購物區塊已隱藏 + 購物區塊已顯示 + 隱藏促銷訊息橫幅 + 促銷訊息橫幅已隱藏 + 促銷訊息橫幅已顯示 + 隱藏自我推廣資訊卡 + 自我推廣資訊卡已隱藏 + 自我推廣資訊卡已顯示 隱藏影片廣告 影片廣告已隱藏 影片廣告已顯示 @@ -115,7 +119,7 @@ 訂閱標籤 你的標籤 原始縮圖 - DeArrow & 原始縮圖 + DeArrow 和原始縮圖 DeArrow & 靜態影片擷取 靜態影片擷取 關於 DeArrow @@ -264,6 +268,9 @@ 隱藏資訊彈出式選單 資訊彈出式選單已隱藏 資訊彈出式選單已顯示 + 資訊彈出式選單過濾類型 + 如果包含則進行過濾。<br><br>隱藏隊列中的 <b>\'播放隊列中的下一個\'</b> 選單中,您可以使用 <b>\'播放下一個\'</b> 或<b>\'在隊列\'</b> 作為關鍵字。 + 如果符合則進行過濾。<br><br>隱藏隊列中的 <b>\'播放隊列中的下一個\'</b> 選單中,您只能使用 <b>\'播放隊列中下一個\'</b> 作為關鍵字。 編輯資訊彈出式選單篩選 要過濾的動態彈出選單名稱清單,每行一個名稱 @@ -325,7 +332,7 @@ 低於觀看次數 觀看次數低於這個值的影片會被隱藏。 觀看次數的值 - 指定你的語言模板來修飾用戶界面中每個影片下顯示的播放量。每個關鍵字(在你的語言中的一個字/詞) -> 值(關鍵字的含義)必須單獨一行。在「->」符號之前是關鍵字。如果更改應用程序或系統語言,則需要重置此設定。\n\n示範:\n英語:10K views = K -> 1000,views -> views\n西班牙語:10 K vistas = K -> 1000,vistas -> views + 指定你的語言模板來修飾使用者。中每個影片下顯示的播放量。每個關鍵字(在你的語言中的一個字/詞) -> 值(關鍵字的含義)必須單獨一行。在「->」符號之前是關鍵字。如果更改應用程序或系統語言,則需要重置此設定。\n\n示範:\n英語:10K 觀看數 = K -> 1000,觀看數 -> 觀看數\n西班牙文:10 K 觀看數 = K -> 1000,觀看數 -> 觀看數 千 -> 1 000\n百萬 -> 1 000 000\n十億 -> 1 000 000 000\n觀看數 -> 觀看數 關於觀看次數篩選 "主頁/訂閱/搜尋結果將被過濾以隱藏觀看次數小於或大於指定數量的影片。 @@ -344,6 +351,7 @@ 一般設定 更改起始頁 預設 + 所有訂閱 瀏覽頻道 課程/學習 探索 @@ -357,6 +365,7 @@ 音樂 新聞 通知 + 播放清單 播客 搜尋 購物 @@ -364,6 +373,7 @@ 運動 訂閱 熱門 + 虛擬實境 稍後觀看 你的剪輯 變更起始頁類型 @@ -401,8 +411,8 @@ 手機 (最大 480 dip) 平板電腦 平板電腦 (最少 600 dip) - เปลี่ยนการกระทำคลิกวงแหวนสด - 點擊直播按鈕 頻道將會開啟。 + 變更點擊直播狀態頻道圖示的動作 + "點擊直播按鈕 頻道將會開啟。" 點擊直播按鈕 直播將會開啟。 偽裝應用程式版本 已偽裝版本 @@ -429,6 +439,9 @@ 隱藏帳戶選單 "隱藏帳戶選單和你的內容分頁的元素 部分元件可能不會隱藏" + 帳戶選單過濾類型 + 如果包含則進行過濾。<br><br>隱藏隊列中的 <b>\'獲取 YouTube Premium\'</b> 選單中,您可以使用 <b>\'YouTube Premium\'</b> 或 <b>\'Premium\'</b> 作為關鍵字。 + 如果匹配則進行過濾。<br><br>隱藏隊列中的 <b>\'獲取 YouTube Premium\'</b> 選單中,您可以使用 <b>\'獲取 YouTube Premium\'</b> 作為關鍵字。 編輯帳號選單篩選 要篩選的帳戶選單名稱清單,每行一個名稱 隱藏控制列 @@ -442,7 +455,7 @@ 自訂篩選已停用 編輯自訂篩選器 - 以換行分隔的名稱來過濾元件 + 依行分隔的名稱篩選元件 無效的自訂篩選器:%s 掛鉤按鈕 @@ -468,56 +481,6 @@ %s 未安裝。 請安裝它。 先決條件 YouTube音樂 需要覆蓋按鈕操作。 按此下載 YouTube音樂。 - - 最小化播放器 - 變更應用程式內最小化播放器的樣式。 - 最小化播放器類型 - 已停用 - 原始 - 電話 - 平板電腦 - Modern 1 - Modern 2 - Modern 3 - 啟用圓角角落 - 已設定角落為圓角。 - 已設定角落為方形。 - 啟用雙擊操作和縮放手勢來調整大小 - "啟用雙擊操作和縮放手勢來調整大小。 - -• 雙擊可增加迷你播放器的大小。 -• 再次雙擊可恢復原始大小。" - 已停用雙擊操作和縮放手勢來調整大小。 - 啟用拖曳 - "拖放已啟用。 - -迷你播放器可以拖曳到螢幕的任何角落。" - 拖曳已停用。 - 啟用水平拖曳手勢。 - "啟用水平拖曳手勢。 - -迷你播放器可以向左或向右拖曳出螢幕。" - 水平拖曳手勢已停用。 - 隱藏展開和關閉按鈕 - 按鈕已隱藏\n(滑動小型播放器即可展開或關閉) - 展開和關閉按鈕已顯示 - 隱藏擴展和關閉按鈕 - "按鈕被隱藏。 - -滑動即可展開或關閉。" - 顯示展開和關閉按鈕。 - 隱藏對話 - 對話已隱藏 - 對話已顯示 - 隱藏快轉和倒轉按鈕 - 快轉和倒轉按鈕已隱藏 - 快轉和倒轉按鈕已顯示 - 初始尺寸 - 初始螢幕尺寸(以像素為單位)。 - 像素大小必須介於 %1$s 和 %2$s 之間。 - 覆蓋層不透明度 - 不透明度值介於 0-100 之間,其中 0 表示透明 - 小型播放器覆蓋層不透明度必須介於 0-100 之間。重設為預設值。 導覽列 隱藏或顯示導覽列部分組件。 @@ -558,6 +521,7 @@ 啟用半透明導覽列 導覽列是半透明的。 導覽列不透明。 + 在某些 YouTube 版本中,此設定可以使系統導覽列透明,或在 PIP 模式下可以破壞佈局。 隱藏導覽列 導覽列已隱藏。 導覽列已顯示。 @@ -625,9 +589,25 @@ 關於選單已隱藏。 關於選單已顯示。 + 短暫通知 + 隱藏或更改與短暫通知相關的組件。 隱藏彈出訊息 彈出訊息已隱藏 彈出訊息已顯示 + 隱藏伺服器端短暫通知 + 伺服器端短暫通知已隱藏。 + 伺服器端短暫通知已顯示。 + 反轉短暫通知主題 + 短暫通知的主題是倒置的。 + 短暫通知的主題沒有倒置。 + 更改伺服器端短暫通知背景 + 伺服器端短暫通知的背景顏色已變更。 + 伺服器端短暫通知的背景顏色沒有改變。 + "一些短暫通知使用伺服器端定義的主題,而不是應用程式主題。 + +更改這些短暫通知的背景顏色。 + +如果伺服器端發生變化,短暫通知的背景顏色可能不會改變。" 工具欄 隱藏或變更工具欄上的元件,例如工具欄按鈕、搜尋欄、標題 @@ -792,39 +772,19 @@ 隱藏感謝按鈕 感謝按鈕已隱藏 感謝按鈕已顯示 - - 按索引隱藏 - 隱藏第一個按鈕 - 第一個按鈕已隱藏。 - 第一個按鈕已顯示。 - 隱藏第二個按鈕 - 第二個按鈕已隱藏。 - 第二個按鈕已顯示。 - 隱藏第三個按鈕 - 第三個按鈕已隱藏。 - 第三個按鈕已顯示。 - 隱藏第四個按鈕 - 第四個按鈕已隱藏。 - 第四個按鈕已顯示。 - 隱藏第五個按鈕 - 第五個按鈕已隱藏。 - 第五個按鈕已顯示。 - 隱藏第六個按鈕 - 第六個按鈕已隱藏。 - 第六個按鈕已顯示。 - 隱藏第七個按鈕 - 第七個按鈕已隱藏。 - 第七個按鈕已顯示。 - 隱藏第八個按鈕 - 第八個按鈕已隱藏。 - 第八個按鈕已顯示。 - - 在直播串流中按索引隱藏 - 關於按索引隱藏操作按鈕 - "在初始化操作按鈕之前按下索引隱藏操作按鈕。 + + 按索引隱藏操作按鈕 + "操作按鈕被索引隱藏。 -- 隱藏操作按鈕,不留任何空白。 -- 操作按鈕的索引可能不會總是相同的按鈕。" +資訊: +• 可能隱藏了錯誤的操作按鈕,或可能未隱藏操作按鈕。 +• 隱藏操作按鈕不留任何空白。" + "操作按鈕被標識符過濾器隱藏。 + +資訊: +• 右側操作按鈕被隱藏。 +• 隱藏操作按鈕會留下空白空間。" + 混音按鈕索引 微光模式 繞過微光模式限制或停用微光模式 @@ -1047,6 +1007,60 @@ 停用縮放觸覺反饋 縮放觸覺反饋已停用 縮放觸覺反饋已啟用 + + 最小化播放器 + 變更應用程式內最小化播放器的樣式。 + 停用恢復迷你播放器 + <b>繼續觀看</b> 不會在應用程式啟動時恢復。 + <b>繼續觀看</b> 將在應用程式啟動時恢復。<br><br>訊息:<br>• <b>繼續觀看</b> 是 YouTube Premium 功能。<br>• 此設定不會強制 <b>繼續觀看</b> 被啟用。 + 最小化播放器類型 + 已停用 + 原始 + 電話 + 平板電腦 + Modern 1 + Modern 2 + Modern 3 + Modern 4 + 啟用圓角角落 + 已設定角落為圓角。 + 已設定角落為方形。 + 啟用雙擊操作和縮放手勢來調整大小 + "啟用雙擊操作和縮放手勢來調整大小。 + +• 雙擊可增加迷你播放器的大小。 +• 再次雙擊可恢復原始大小。" + 已停用雙擊操作和縮放手勢來調整大小。 + 啟用拖曳 + "拖放已啟用。 + +迷你播放器可以拖曳到螢幕的任何角落。" + 拖曳已停用。 + 啟用水平拖曳手勢。 + "啟用水平拖曳手勢。 + +迷你播放器可以向左或向右拖曳出螢幕。" + 水平拖曳手勢已停用。 + 隱藏覆蓋按鈕 + 覆蓋按鈕已隱藏。 + 覆蓋按鈕已顯示。 + 隱藏展開和關閉按鈕 + "按鈕被隱藏。 + +滑動即可展開或關閉。" + 顯示展開和關閉按鈕。 + 隱藏快轉和倒轉按鈕 + 快轉和倒轉按鈕已隱藏 + 快轉和倒轉按鈕已顯示 + 隱藏對話 + 對話已隱藏 + 對話已顯示 + 初始尺寸 + 初始螢幕尺寸(以像素為單位)。 + 像素大小必須介於 %1$s 和 %2$s 之間。 + 覆蓋層不透明度 + 不透明度值介於 0-100 之間,其中 0 表示透明 + 小型播放器覆蓋層不透明度必須介於 0-100 之間。重設為預設值。 播放器按鈕 隱藏或顯示影片中的按鈕 @@ -1149,9 +1163,11 @@ 啟用自訂搜尋欄顏色 自定義進度條顏色已啟用 自定義進度條顏色已停用 - 自定義進度條顏色 - 輸入套用於搜尋欄的十六進制顏色代碼 - 搜尋欄顏色值無效。 + 自訂搜尋欄原色 + 輸入搜尋欄原色的十六進位代碼。 + 自訂搜尋欄強調色 + 輸入搜尋欄強調色的十六進位代碼。 + 搜尋欄顏色無效。 啟用進度條點擊 進度條點擊已啟用 進度條點擊已停用 @@ -1220,10 +1236,6 @@ 自動展開影片描述 自動展開影片描述 手動展開影片描述 - 影片描述面板中的標題 - "在影片描述面板中輸入標題 -如果保存了不正確的字符串,則「展開影片描述」可能無法正常工作" - 描述 短片 停用 短影片 後台播放