diff --git a/README.md b/README.md index 6273da940..cb7f710c0 100644 --- a/README.md +++ b/README.md @@ -11,72 +11,73 @@ See the [documentation](https://github.com/inotia00/revanced-documentation#readm | 💊 Patch | 📜 Description | 🏹 Target Version | |:--------:|:--------------:|:-----------------:| -| `Alternative thumbnails` | Adds options to replace video thumbnails using the DeArrow API or image captures from the video. | 18.29.38 ~ 19.44.39 | -| `Ambient mode control` | Adds options to disable Ambient mode and to bypass Ambient mode restrictions. | 18.29.38 ~ 19.44.39 | -| `Bypass URL redirects` | Adds an option to bypass URL redirects and open the original URL directly. | 18.29.38 ~ 19.44.39 | -| `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. | 18.29.38 ~ 19.44.39 | -| `Change form factor` | Adds an option to change the UI appearance to a phone, tablet, or automotive device. | 18.29.38 ~ 19.44.39 | -| `Change live ring click action` | Adds an option to open the channel instead of the live stream when clicking on the live ring. | 18.29.38 ~ 19.44.39 | -| `Change player flyout menu toggles` | Adds an option to use text toggles instead of switch toggles within the additional settings menu. | 18.29.38 ~ 19.44.39 | -| `Change share sheet` | Adds an option to change the in-app share sheet to the system share sheet. | 18.29.38 ~ 19.44.39 | -| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 18.29.38 ~ 19.44.39 | -| `Custom Shorts action buttons` | Changes, at compile time, the icon of the action buttons of the Shorts player. | 18.29.38 ~ 19.44.39 | -| `Custom branding icon for YouTube` | Changes the YouTube app icon to the icon specified in patch options. | 18.29.38 ~ 19.44.39 | -| `Custom branding name for YouTube` | Changes the YouTube app name to the name specified in patch options. | 18.29.38 ~ 19.44.39 | -| `Custom double tap length` | Adds Double-tap to seek values that are specified in patch options. | 18.29.38 ~ 19.44.39 | -| `Custom header for YouTube` | Applies a custom header in the top left corner within the app. | 18.29.38 ~ 19.44.39 | -| `Description components` | Adds options to hide and disable description components. | 18.29.38 ~ 19.44.39 | -| `Disable QUIC protocol` | Adds an option to disable CronetEngine's QUIC protocol. | 18.29.38 ~ 19.44.39 | -| `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 | -| `Enable debug logging` | Adds an option to enable debug logging. | 18.29.38 ~ 19.44.39 | -| `Enable gradient loading screen` | Adds an option to enable the gradient loading screen. | 18.29.38 ~ 19.44.39 | -| `Force hide player buttons background` | Removes, at compile time, the dark background surrounding the video player controls. | 18.29.38 ~ 19.44.39 | -| `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 | -| `Hide feed components` | Adds options to hide components related to feeds. | 18.29.38 ~ 19.44.39 | -| `Hide feed flyout menu` | Adds the ability to hide feed flyout menu components using a custom filter. | 18.29.38 ~ 19.44.39 | -| `Hide layout components` | Adds options to hide general layout components. | 18.29.38 ~ 19.44.39 | -| `Hide player buttons` | Adds options to hide buttons in the video player. | 18.29.38 ~ 19.44.39 | -| `Hide player flyout menu` | Adds options to hide player flyout menu components. | 18.29.38 ~ 19.44.39 | -| `Hide shortcuts` | Remove, at compile time, the app shortcuts that appears when the app icon is long pressed. | 18.29.38 ~ 19.44.39 | -| `Hook YouTube Music actions` | Adds support for opening music in RVX Music using the in-app YouTube Music button. | 18.29.38 ~ 19.44.39 | -| `Hook download actions` | Adds support to download videos with an external downloader app using the in-app download button. | 18.29.38 ~ 19.44.39 | -| `MaterialYou` | Applies the MaterialYou theme for Android 12+ devices. | 18.29.38 ~ 19.44.39 | -| `Miniplayer` | Adds options to change the in-app minimized player, and if patching target 19.16+ adds options to use modern miniplayers. | 18.29.38 ~ 19.44.39 | -| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 18.29.38 ~ 19.44.39 | -| `Open links externally` | Adds an option to always open links in your browser instead of the in-app browser. | 18.29.38 ~ 19.44.39 | -| `Overlay buttons` | Adds options to display useful overlay buttons in the video player. | 18.29.38 ~ 19.44.39 | -| `Player components` | Adds options to hide or change components related to the video player. | 18.29.38 ~ 19.44.39 | -| `Remove background playback restrictions` | Removes restrictions on background playback, including for music and kids videos. | 18.29.38 ~ 19.44.39 | -| `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. | 18.29.38 ~ 19.44.39 | -| `Return YouTube Dislike` | Adds an option to show the dislike count of videos using the Return YouTube Dislike API. | 18.29.38 ~ 19.44.39 | -| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 18.29.38 ~ 19.44.39 | -| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 18.29.38 ~ 19.44.39 | -| `Seekbar components` | Adds options to hide or change components related to the seekbar. | 18.29.38 ~ 19.44.39 | -| `Settings for YouTube` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 18.29.38 ~ 19.44.39 | -| `Shorts components` | Adds options to hide or change components related to YouTube Shorts. | 18.29.38 ~ 19.44.39 | -| `Snack bar components` | Adds options to hide or change components related to the snack bar. | 18.29.38 ~ 19.44.39 | -| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as sponsored content. | 18.29.38 ~ 19.44.39 | -| `Spoof app version` | Adds options to spoof the YouTube client version. This can be used to restore old UI elements and features. | 18.29.38 ~ 19.44.39 | -| `Spoof streaming data` | Adds options to spoof the streaming data to allow playback. | 18.29.38 ~ 19.44.39 | -| `Swipe controls` | Adds options for controlling volume and brightness with swiping, and whether to enter fullscreen when swiping down below the player. | 18.29.38 ~ 19.44.39 | -| `Theme` | Changes the app's themes to the values specified in patch options. | 18.29.38 ~ 19.44.39 | -| `Toolbar components` | Adds options to hide or change components located on the toolbar, such as the search bar, header, and toolbar buttons. | 18.29.38 ~ 19.44.39 | -| `Translations for YouTube` | Add translations or remove string resources. | 18.29.38 ~ 19.44.39 | -| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 18.29.38 ~ 19.44.39 | -| `Visual preferences icons for YouTube` | Adds icons to specific preferences in the settings. | 18.29.38 ~ 19.44.39 | -| `Watch history` | Adds an option to change the domain of the watch history or check its status. | 18.29.38 ~ 19.44.39 | +| `Alternative thumbnails` | Adds options to replace video thumbnails using the DeArrow API or image captures from the video. | 19.05.36 ~ 19.47.53 | +| `Ambient mode control` | Adds options to disable Ambient mode and to bypass Ambient mode restrictions. | 19.05.36 ~ 19.47.53 | +| `Bypass URL redirects` | Adds an option to bypass URL redirects and open the original URL directly. | 19.05.36 ~ 19.47.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. | 19.05.36 ~ 19.47.53 | +| `Change form factor` | Adds an option to change the UI appearance to a phone, tablet, or automotive device. | 19.05.36 ~ 19.47.53 | +| `Change live ring click action` | Adds an option to open the channel instead of the live stream when clicking on the live ring. | 19.05.36 ~ 19.47.53 | +| `Change player flyout menu toggles` | Adds an option to use text toggles instead of switch toggles within the additional settings menu. | 19.05.36 ~ 19.47.53 | +| `Change share sheet` | Adds an option to change the in-app share sheet to the system share sheet. | 19.05.36 ~ 19.47.53 | +| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 19.05.36 ~ 19.47.53 | +| `Custom Shorts action buttons` | Changes, at compile time, the icon of the action buttons of the Shorts player. | 19.05.36 ~ 19.47.53 | +| `Custom branding icon for YouTube` | Changes the YouTube app icon to the icon specified in patch options. | 19.05.36 ~ 19.47.53 | +| `Custom branding name for YouTube` | Changes the YouTube app name to the name specified in patch options. | 19.05.36 ~ 19.47.53 | +| `Custom double tap length` | Adds Double-tap to seek values that are specified in patch options. | 19.05.36 ~ 19.47.53 | +| `Custom header for YouTube` | Applies a custom header in the top left corner within the app. | 19.05.36 ~ 19.47.53 | +| `Description components` | Adds options to hide and disable description components. | 19.05.36 ~ 19.47.53 | +| `Disable QUIC protocol` | Adds an option to disable CronetEngine's QUIC protocol. | 19.05.36 ~ 19.47.53 | +| `Disable forced auto audio tracks` | Adds an option to disable audio tracks from being automatically enabled. | 19.05.36 ~ 19.47.53 | +| `Disable forced auto captions` | Adds an option to disable captions from being automatically enabled. | 19.05.36 ~ 19.47.53 | +| `Disable haptic feedback` | Adds options to disable haptic feedback when swiping in the video player. | 19.05.36 ~ 19.47.53 | +| `Disable layout updates` | Adds an option to disable layout updates by server. | 19.05.36 ~ 19.47.53 | +| `Disable resuming Miniplayer on startup` | Adds an option to disable the Miniplayer 'Continue watching' from resuming on app startup. | 19.05.36 ~ 19.47.53 | +| `Disable resuming Shorts on startup` | Adds an option to disable the Shorts player from resuming on app startup when Shorts were last being watched. | 19.05.36 ~ 19.47.53 | +| `Disable splash animation` | Adds an option to disable the splash animation on app startup. | 19.05.36 ~ 19.47.53 | +| `Enable OPUS codec` | Adds an option to enable the OPUS audio codec if the player response includes it. | 19.05.36 ~ 19.47.53 | +| `Enable debug logging` | Adds an option to enable debug logging. | 19.05.36 ~ 19.47.53 | +| `Enable gradient loading screen` | Adds an option to enable the gradient loading screen. | 19.05.36 ~ 19.47.53 | +| `Force hide player buttons background` | Removes, at compile time, the dark background surrounding the video player controls. | 19.05.36 ~ 19.47.53 | +| `Fullscreen components` | Adds options to hide or change components related to fullscreen. | 19.05.36 ~ 19.47.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. | 19.05.36 ~ 19.47.53 | +| `Hide Shorts dimming` | Removes, at compile time, the dimming effect at the top and bottom of Shorts videos. | 19.05.36 ~ 19.47.53 | +| `Hide accessibility controls dialog` | Removes, at compile time, accessibility controls dialog 'Turn on accessibility controls for the video player?'. | 19.05.36 ~ 19.47.53 | +| `Hide action buttons` | Adds options to hide action buttons under videos. | 19.05.36 ~ 19.47.53 | +| `Hide ads` | Adds options to hide ads. | 19.05.36 ~ 19.47.53 | +| `Hide comments components` | Adds options to hide components related to comments. | 19.05.36 ~ 19.47.53 | +| `Hide feed components` | Adds options to hide components related to feeds. | 19.05.36 ~ 19.47.53 | +| `Hide feed flyout menu` | Adds the ability to hide feed flyout menu components using a custom filter. | 19.05.36 ~ 19.47.53 | +| `Hide layout components` | Adds options to hide general layout components. | 19.05.36 ~ 19.47.53 | +| `Hide player buttons` | Adds options to hide buttons in the video player. | 19.05.36 ~ 19.47.53 | +| `Hide player flyout menu` | Adds options to hide player flyout menu components. | 19.05.36 ~ 19.47.53 | +| `Hide shortcuts` | Remove, at compile time, the app shortcuts that appears when the app icon is long pressed. | 19.05.36 ~ 19.47.53 | +| `Hook YouTube Music actions` | Adds support for opening music in RVX Music using the in-app YouTube Music button. | 19.05.36 ~ 19.47.53 | +| `Hook download actions` | Adds support to download videos with an external downloader app using the in-app download button. | 19.05.36 ~ 19.47.53 | +| `MaterialYou` | Applies the MaterialYou theme for Android 12+ devices. | 19.05.36 ~ 19.47.53 | +| `Miniplayer` | Adds options to change the in-app minimized player, and if patching target 19.16+ adds options to use modern miniplayers. | 19.05.36 ~ 19.47.53 | +| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 19.05.36 ~ 19.47.53 | +| `Open links externally` | Adds an option to always open links in your browser instead of the in-app browser. | 19.05.36 ~ 19.47.53 | +| `Overlay buttons` | Adds options to display useful overlay buttons in the video player. | 19.05.36 ~ 19.47.53 | +| `Player components` | Adds options to hide or change components related to the video player. | 19.05.36 ~ 19.47.53 | +| `Remove background playback restrictions` | Removes restrictions on background playback, including for music and kids videos. | 19.05.36 ~ 19.47.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. | 19.05.36 ~ 19.47.53 | +| `Return YouTube Dislike` | Adds an option to show the dislike count of videos using the Return YouTube Dislike API. | 19.05.36 ~ 19.47.53 | +| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 19.05.36 ~ 19.47.53 | +| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 19.05.36 ~ 19.47.53 | +| `Seekbar components` | Adds options to hide or change components related to the seekbar. | 19.05.36 ~ 19.47.53 | +| `Settings for YouTube` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 19.05.36 ~ 19.47.53 | +| `Shorts components` | Adds options to hide or change components related to YouTube Shorts. | 19.05.36 ~ 19.47.53 | +| `Snack bar components` | Adds options to hide or change components related to the snack bar. | 19.05.36 ~ 19.47.53 | +| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as sponsored content. | 19.05.36 ~ 19.47.53 | +| `Spoof app version` | Adds options to spoof the YouTube client version. This can be used to restore old UI elements and features. | 19.05.36 ~ 19.47.53 | +| `Spoof streaming data` | Adds options to spoof the streaming data to allow playback. | 19.05.36 ~ 19.47.53 | +| `Swipe controls` | Adds options for controlling volume and brightness with swiping, and whether to enter fullscreen when swiping down below the player. | 19.05.36 ~ 19.47.53 | +| `Theme` | Changes the app's themes to the values specified in patch options. | 19.05.36 ~ 19.47.53 | +| `Toolbar components` | Adds options to hide or change components located on the toolbar, such as the search bar, header, and toolbar buttons. | 19.05.36 ~ 19.47.53 | +| `Translations for YouTube` | Add translations or remove string resources. | 19.05.36 ~ 19.47.53 | +| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 19.05.36 ~ 19.47.53 | +| `Visual preferences icons for YouTube` | Adds icons to specific preferences in the settings. | 19.05.36 ~ 19.47.53 | +| `Watch history` | Adds an option to change the domain of the watch history or check its status. | 19.05.36 ~ 19.47.53 | ### [📦 `com.google.android.apps.youtube.music`](https://play.google.com/store/apps/details?id=com.google.android.apps.youtube.music) @@ -84,49 +85,48 @@ 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.10.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.10.51 | -| `Certificate spoof` | Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate. | 6.20.51 ~ 8.10.51 | -| `Change share sheet` | Adds an option to change the in-app share sheet to the system share sheet. | 6.20.51 ~ 8.10.51 | -| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 6.20.51 ~ 8.10.51 | -| `Custom branding icon for YouTube Music` | Changes the YouTube Music app icon to the icon specified in patch options. | 6.20.51 ~ 8.10.51 | -| `Custom branding name for YouTube Music` | Changes the YouTube Music app name to the name specified in patch options. | 6.20.51 ~ 8.10.51 | -| `Custom header for YouTube Music` | Applies a custom header in the top left corner within the app. | 6.20.51 ~ 8.10.51 | -| `Dark theme` | Changes the app's dark theme to the values specified in patch options. | 6.20.51 ~ 8.10.51 | -| `Disable Cairo splash animation` | Adds an option to disable Cairo splash animation. | 7.06.54 ~ 8.10.51 | -| `Disable DRC audio` | Adds an option to disable DRC (Dynamic Range Compression) audio. | 6.20.51 ~ 8.10.51 | -| `Disable QUIC protocol` | Adds an option to disable CronetEngine's QUIC protocol. | 6.20.51 ~ 8.10.51 | -| `Disable dislike redirection` | Adds an option to disable redirection to the next track when clicking the Dislike button. | 6.20.51 ~ 8.10.51 | -| `Disable forced auto captions` | Adds an option to disable captions from being automatically enabled. | 6.20.51 ~ 8.10.51 | -| `Disable music video in album` | Adds option to redirect music videos from albums for non-premium users. | 6.20.51 ~ 8.10.51 | -| `Enable OPUS codec` | Adds an option to enable the OPUS audio codec if the player response includes it. | 6.20.51 ~ 8.10.51 | -| `Enable debug logging` | Adds an option to enable debug logging. | 6.20.51 ~ 8.10.51 | -| `Enable landscape mode` | Adds an option to enable landscape mode when rotating the screen on phones. | 6.20.51 ~ 8.10.51 | -| `Flyout menu components` | Adds options to hide or change flyout menu components. | 6.20.51 ~ 8.10.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.10.51 | -| `Hide account components` | Adds options to hide components related to the account menu. | 6.20.51 ~ 8.10.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.10.51 | -| `Hide ads` | Adds options to hide ads. | 6.20.51 ~ 8.10.51 | -| `Hide layout components` | Adds options to hide general layout components. | 6.20.51 ~ 8.10.51 | -| `Hide overlay filter` | Removes, at compile time, the dark overlay that appears when player flyout menus are open. | 6.20.51 ~ 8.10.51 | -| `Hide player overlay filter` | Removes, at compile time, the dark overlay that appears when single-tapping in the player. | 6.20.51 ~ 8.10.51 | -| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 6.20.51 ~ 8.10.51 | -| `Player components` | Adds options to hide or change components related to the player. | 6.20.51 ~ 8.10.51 | -| `Remove background playback restrictions` | Removes restrictions on background playback, including for kids videos. | 6.20.51 ~ 8.10.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.10.51 | -| `Restore old style library shelf` | Adds an option to return the Library tab to the old style. | 6.20.51 ~ 8.10.51 | -| `Return YouTube Dislike` | Adds an option to show the dislike count of songs using the Return YouTube Dislike API. | 6.20.51 ~ 8.10.51 | -| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 6.20.51 ~ 8.10.51 | -| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 6.20.51 ~ 8.10.51 | -| `Settings for YouTube Music` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 6.20.51 ~ 8.10.51 | -| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections. | 6.20.51 ~ 8.10.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.10.51 | -| `Spoof player parameter` | Adds options to spoof player parameter to allow playback. | 6.20.51 ~ 8.10.51 | -| `Translations for YouTube Music` | Add translations or remove string resources. | 6.20.51 ~ 8.10.51 | -| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 6.20.51 ~ 8.10.51 | -| `Visual preferences icons for YouTube Music` | Adds icons to specific preferences in the settings. | 6.20.51 ~ 8.10.51 | -| `Watch history` | Adds an option to change the domain of the watch history or check its status. | 6.20.51 ~ 8.10.51 | +| `Bitrate default value` | Sets the audio quality to 'Always High' when you first install the app. | 6.20.51 ~ 8.12.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.12.53 | +| `Certificate spoof` | Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate. | 6.20.51 ~ 8.12.53 | +| `Change share sheet` | Adds an option to change the in-app share sheet to the system share sheet. | 6.20.51 ~ 8.12.53 | +| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 6.20.51 ~ 8.12.53 | +| `Custom branding icon for YouTube Music` | Changes the YouTube Music app icon to the icon specified in patch options. | 6.20.51 ~ 8.12.53 | +| `Custom branding name for YouTube Music` | Changes the YouTube Music app name to the name specified in patch options. | 6.20.51 ~ 8.12.53 | +| `Custom header for YouTube Music` | Applies a custom header in the top left corner within the app. | 6.20.51 ~ 8.12.53 | +| `Dark theme` | Changes the app's dark theme to the values specified in patch options. | 6.20.51 ~ 8.12.53 | +| `Disable Cairo splash animation` | Adds an option to disable Cairo splash animation. | 7.06.54 ~ 8.12.53 | +| `Disable DRC audio` | Adds an option to disable DRC (Dynamic Range Compression) audio. | 6.20.51 ~ 8.12.53 | +| `Disable QUIC protocol` | Adds an option to disable CronetEngine's QUIC protocol. | 6.20.51 ~ 8.12.53 | +| `Disable dislike redirection` | Adds an option to disable redirection to the next track when clicking the Dislike button. | 6.20.51 ~ 8.12.53 | +| `Disable forced auto captions` | Adds an option to disable captions from being automatically enabled. | 6.20.51 ~ 8.12.53 | +| `Disable music video in album` | Adds option to redirect music videos from albums for non-premium users. | 6.20.51 ~ 8.12.53 | +| `Enable OPUS codec` | Adds an option to enable the OPUS audio codec if the player response includes it. | 6.20.51 ~ 8.12.53 | +| `Enable debug logging` | Adds an option to enable debug logging. | 6.20.51 ~ 8.12.53 | +| `Enable landscape mode` | Adds an option to enable landscape mode when rotating the screen on phones. | 6.20.51 ~ 8.12.53 | +| `Flyout menu components` | Adds options to hide or change flyout menu components. | 6.20.51 ~ 8.12.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.12.53 | +| `Hide account components` | Adds options to hide components related to the account menu. | 6.20.51 ~ 8.12.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.12.53 | +| `Hide ads` | Adds options to hide ads. | 6.20.51 ~ 8.12.53 | +| `Hide layout components` | Adds options to hide general layout components. | 6.20.51 ~ 8.12.53 | +| `Hide overlay filter` | Removes, at compile time, the dark overlay that appears when player flyout menus are open. | 6.20.51 ~ 8.12.53 | +| `Hide player overlay filter` | Removes, at compile time, the dark overlay that appears when single-tapping in the player. | 6.20.51 ~ 8.12.53 | +| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 6.20.51 ~ 8.12.53 | +| `Player components` | Adds options to hide or change components related to the player. | 6.20.51 ~ 8.12.53 | +| `Remove background playback restrictions` | Removes restrictions on background playback, including for kids videos. | 6.20.51 ~ 8.12.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.12.53 | +| `Restore old style library shelf` | Adds an option to return the Library tab to the old style. | 6.20.51 ~ 8.12.53 | +| `Return YouTube Dislike` | Adds an option to show the dislike count of songs using the Return YouTube Dislike API. | 6.20.51 ~ 8.12.53 | +| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 6.20.51 ~ 8.12.53 | +| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 6.20.51 ~ 8.12.53 | +| `Settings for YouTube Music` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 6.20.51 ~ 8.12.53 | +| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections. | 6.20.51 ~ 8.12.53 | +| `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 ~ 8.10.52 | +| `Spoof player parameter` | Adds options to spoof player parameter to allow playback. | 6.20.51 ~ 8.12.53 | +| `Translations for YouTube Music` | Add translations or remove string resources. | 6.20.51 ~ 8.12.53 | +| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 6.20.51 ~ 8.12.53 | +| `Visual preferences icons for YouTube Music` | Adds icons to specific preferences in the settings. | 6.20.51 ~ 8.12.53 | +| `Watch history` | Adds an option to change the domain of the watch history or check its status. | 6.20.51 ~ 8.12.53 | ### [📦 `com.reddit.frontpage`](https://play.google.com/store/apps/details?id=com.reddit.frontpage) @@ -134,19 +134,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. | 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 | +| `Change package name` | Changes the package name for Reddit to the name specified in patch options. | 2024.17.0 ~ 2025.12.0 | +| `Custom branding name for Reddit` | Changes the Reddit app name to the name specified in patch options. | 2024.17.0 ~ 2025.12.0 | +| `Disable screenshot popup` | Adds an option to disable the popup that appears when taking a screenshot. | 2024.17.0 ~ 2025.12.0 | +| `Hide Recently Visited shelf` | Adds an option to hide the Recently Visited shelf in the sidebar. | 2024.17.0 ~ 2025.12.0 | +| `Hide ads` | Adds options to hide ads. | 2024.17.0 ~ 2025.12.0 | +| `Hide navigation buttons` | Adds options to hide buttons in the navigation bar. | 2024.17.0 ~ 2025.12.0 | +| `Hide recommended communities shelf` | Adds an option to hide the recommended communities shelves in subreddits. | 2024.17.0 ~ 2025.12.0 | +| `Open links directly` | Adds an option to skip over redirection URLs in external links. | 2024.17.0 ~ 2025.12.0 | +| `Open links externally` | Adds an option to always open links in your browser instead of in the in-app-browser. | 2024.17.0 ~ 2025.12.0 | +| `Premium icon` | Unlocks premium app icons. | 2024.17.0 ~ 2025.12.0 | +| `Remove subreddit dialog` | Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically. | 2024.17.0 ~ 2025.12.0 | +| `Sanitize sharing links` | Adds an option to sanitize sharing links by removing tracking query parameters. | 2024.17.0 ~ 2025.12.0 | +| `Settings for Reddit` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 2024.17.0 ~ 2025.12.0 | @@ -165,13 +165,11 @@ Example: "use":true, "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -189,7 +187,7 @@ Example: "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -201,7 +199,8 @@ Example: "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [] diff --git a/extensions/shared/build.gradle.kts b/extensions/shared/build.gradle.kts index 69641ab9e..d9511b8b8 100644 --- a/extensions/shared/build.gradle.kts +++ b/extensions/shared/build.gradle.kts @@ -26,6 +26,7 @@ android { dependencies { compileOnly(libs.annotation) compileOnly(libs.preference) + implementation(libs.collections4) implementation(libs.lang3) compileOnly(project(":extensions:shared:stub")) 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 71ae52a34..be5106d04 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 @@ -108,13 +108,13 @@ public class FlyoutPatch { 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 + 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 ); } } @@ -126,14 +126,14 @@ public class FlyoutPatch { 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 + 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 ); } } 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 f22e9aff4..1f3311ad1 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 @@ -9,13 +9,11 @@ import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.ImageView; +import android.widget.LinearLayout; import app.revanced.extension.music.settings.Settings; import app.revanced.extension.shared.utils.ResourceUtils; -/** - * @noinspection ALL - */ @SuppressWarnings("unused") public class GeneralPatch { @@ -79,6 +77,13 @@ public class GeneralPatch { } } + public static void hideSearchButton(View view) { + if (Settings.HIDE_SEARCH_BUTTON.get()) { + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, 0); + view.setLayoutParams(layoutParams); + } + } + public static boolean hideSoundSearchButton() { return Settings.HIDE_SOUND_SEARCH_BUTTON.get(); } @@ -123,7 +128,7 @@ public class GeneralPatch { *

* The {@link AlertDialog#getButton(int)} method must be used after {@link AlertDialog#show()} is called. * Otherwise {@link AlertDialog#getButton(int)} method will always return null. - * https://stackoverflow.com/a/4604145 + * Reference *

* That's why {@link AlertDialog#show()} is absolutely necessary. * Instead, use two tricks to hide Alertdialog. diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/AlbumMusicVideoPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/AlbumMusicVideoPatch.java index e275463d1..6c228af1a 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/AlbumMusicVideoPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/AlbumMusicVideoPatch.java @@ -7,15 +7,12 @@ import androidx.annotation.NonNull; import java.util.LinkedHashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; import app.revanced.extension.music.patches.misc.requests.PlaylistRequest; import app.revanced.extension.music.settings.Settings; import app.revanced.extension.music.shared.VideoInformation; import app.revanced.extension.music.utils.VideoUtils; -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 AlbumMusicVideoPatch { @@ -40,7 +37,7 @@ public class AlbumMusicVideoPatch { private static final String YOUTUBE_MUSIC_ALBUM_PREFIX = "OLAK"; - private static final AtomicBoolean isVideoLaunched = new AtomicBoolean(false); + private static volatile boolean isVideoLaunched = false; @NonNull private static volatile String playerResponseVideoId = ""; @@ -100,14 +97,6 @@ public class AlbumMusicVideoPatch { if (request == null) { return; } - // This hook is always called off the main thread, - // but this can later be called for the same video id from the main thread. - // This is not a concern, since the fetch will always be finished - // and never block the main thread. - // But if debugging, then still verify this is the situation. - if (BaseSettings.ENABLE_DEBUG_LOGGING.get() && !request.fetchCompleted() && Utils.isCurrentlyOnMainThread()) { - Logger.printException(() -> "Error: Blocking main thread"); - } String songId = request.getStream(); if (songId.isEmpty()) { Logger.printDebug(() -> "Official song not found, videoId: " + videoId); @@ -149,17 +138,16 @@ public class AlbumMusicVideoPatch { private static void openMusic(@NonNull String songId) { try { - isVideoLaunched.compareAndSet(false, true); - // The newly opened video is not a music video. // To prevent fetch requests from being sent, set the video id to the newly opened video VideoUtils.runOnMainThreadDelayed(() -> { + isVideoLaunched = true; playerResponseVideoId = songId; currentVideoId = songId; VideoUtils.openInYouTubeMusic(songId); - }, 1000); + VideoUtils.runOnMainThreadDelayed(() -> isVideoLaunched = false, 3000); + }, 1500); - VideoUtils.runOnMainThreadDelayed(() -> isVideoLaunched.compareAndSet(true, false), 2500); } catch (Exception ex) { Logger.printException(() -> "openMusic failure", ex); } @@ -191,7 +179,7 @@ public class AlbumMusicVideoPatch { * Injection point. */ public static boolean hideSnackBar() { - return DISABLE_MUSIC_VIDEO_IN_ALBUM && isVideoLaunched.get(); + return DISABLE_MUSIC_VIDEO_IN_ALBUM && isVideoLaunched; } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/SpoofPlayerParameterPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/SpoofPlayerParameterPatch.java index d5e5b014b..23082f50e 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/SpoofPlayerParameterPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/SpoofPlayerParameterPatch.java @@ -9,7 +9,6 @@ import androidx.annotation.Nullable; import org.apache.commons.lang3.BooleanUtils; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/requests/PlaylistRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/requests/PlaylistRequest.kt index 90fb6f963..62b98a5e3 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/requests/PlaylistRequest.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/requests/PlaylistRequest.kt @@ -2,8 +2,10 @@ package app.revanced.extension.music.patches.misc.requests import android.annotation.SuppressLint 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.innertube.client.YouTubeAppClient +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createApplicationRequestBody +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute +import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_PLAYLIST_PAGE import app.revanced.extension.shared.requests.Requester import app.revanced.extension.shared.settings.AppLanguage import app.revanced.extension.shared.utils.Logger @@ -136,10 +138,11 @@ class PlaylistRequest private constructor( Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" } try { - val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute( - PlayerRoutes.GET_PLAYLIST_PAGE, + val connection = getInnerTubeResponseConnectionFromRoute( + GET_PLAYLIST_PAGE, clientType ) + /** * For some reason, the tracks in Top Songs have the playlistId of the album: * [ReVanced_Extended#2835](https://github.com/inotia00/ReVanced_Extended/issues/2835) @@ -152,7 +155,7 @@ class PlaylistRequest private constructor( * So we can work around this by setting the language to English when sending the request. */ val requestBody = - PlayerRoutes.createApplicationRequestBody( + createApplicationRequestBody( clientType = clientType, videoId = videoId, playlistId = playlistId, 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 3f74973f2..05a214f8f 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 @@ -3,6 +3,7 @@ package app.revanced.extension.music.settings; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import static app.revanced.extension.music.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY; +import static app.revanced.extension.shared.utils.StringRef.str; import androidx.annotation.NonNull; @@ -109,6 +110,7 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_NOTIFICATION_BUTTON = new BooleanSetting("revanced_hide_notification_button", FALSE, true); public static final BooleanSetting HIDE_PLAYLIST_CARD_SHELF = new BooleanSetting("revanced_hide_playlist_card_shelf", FALSE, true); public static final BooleanSetting HIDE_SAMPLE_SHELF = new BooleanSetting("revanced_hide_samples_shelf", FALSE, true); + public static final BooleanSetting HIDE_SEARCH_BUTTON = new BooleanSetting("revanced_hide_search_button", FALSE, true); public static final BooleanSetting HIDE_SOUND_SEARCH_BUTTON = new BooleanSetting("revanced_hide_sound_search_button", FALSE, true); public static final BooleanSetting HIDE_TAP_TO_UPDATE_BUTTON = new BooleanSetting("revanced_hide_tap_to_update_button", FALSE, true); public static final BooleanSetting HIDE_VOICE_SEARCH_BUTTON = new BooleanSetting("revanced_hide_voice_search_button", FALSE, true); @@ -240,7 +242,10 @@ public class Settings extends BaseSettings { // region Migration // Old spoof versions that no longer work reliably. - if (SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0) { + String spoofAppVersionTarget = SPOOF_APP_VERSION_TARGET.get(); + if (spoofAppVersionTarget.compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0) { + Utils.showToastShort(str("revanced_spoof_app_version_target_invalid_toast", spoofAppVersionTarget)); + Utils.showToastShort(str("revanced_extended_reset_to_default_toast")); Logger.printInfo(() -> "Resetting spoof app version target"); SPOOF_APP_VERSION_TARGET.resetToDefault(); } diff --git a/extensions/shared/src/main/java/app/revanced/extension/reddit/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/reddit/settings/Settings.java index 2efc2eb37..eee9c0ae1 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/reddit/settings/Settings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/reddit/settings/Settings.java @@ -13,7 +13,7 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_NEW_POST_ADS = new BooleanSetting("revanced_hide_new_post_ads", TRUE, true); // Layout - public static final BooleanSetting DISABLE_SCREENSHOT_POPUP = new BooleanSetting("revanced_disable_screenshot_popup", TRUE); + public static final BooleanSetting DISABLE_SCREENSHOT_POPUP = new BooleanSetting("revanced_disable_screenshot_popup", TRUE, true); public static final BooleanSetting HIDE_CHAT_BUTTON = new BooleanSetting("revanced_hide_chat_button", FALSE, true); public static final BooleanSetting HIDE_CREATE_BUTTON = new BooleanSetting("revanced_hide_create_button", FALSE, true); public static final BooleanSetting HIDE_DISCOVER_BUTTON = new BooleanSetting("revanced_hide_discover_button", FALSE, true); diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeAppClient.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/client/YouTubeAppClient.kt similarity index 94% rename from extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeAppClient.kt rename to extensions/shared/src/main/java/app/revanced/extension/shared/innertube/client/YouTubeAppClient.kt index 8723b8297..bbd1ba9e9 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeAppClient.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/client/YouTubeAppClient.kt @@ -1,6 +1,7 @@ -package app.revanced.extension.shared.patches.client +package app.revanced.extension.shared.innertube.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 @@ -212,8 +213,15 @@ object YouTubeAppClient { return BaseSettings.SPOOF_STREAMING_DATA_IOS_FORCE_AVC.get() } + private fun useIOS(): Boolean { + return PatchStatus.SpoofStreamingDataIOS() && BaseSettings.SPOOF_STREAMING_DATA_TYPE_IOS.get() + } + fun availableClientTypes(preferredClient: ClientType): Array { - val availableClientTypes = ClientType.CLIENT_ORDER_TO_USE_YOUTUBE + val availableClientTypes = if (useIOS()) + ClientType.CLIENT_ORDER_TO_USE_IOS + else + ClientType.CLIENT_ORDER_TO_USE if (ArrayUtils.contains(availableClientTypes, preferredClient)) { val clientToUse: Array = arrayOfNulls(availableClientTypes.size) @@ -230,7 +238,7 @@ object YouTubeAppClient { } } - @Suppress("DEPRECATION") + @Suppress("DEPRECATION", "unused") enum class ClientType( /** * [YouTube client type](https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients) @@ -278,10 +286,6 @@ object YouTubeAppClient { * If true, 'Authorization' must be included. */ val requireAuth: Boolean = false, - /** - * Whether a poToken is required to get playback for more than 1 minute. - */ - val requirePoToken: Boolean = false, /** * Client name for innertube body. */ @@ -363,7 +367,7 @@ object YouTubeAppClient { else "iOS TV" ), - IOS( + IOS_DEPRECATED( id = 5, deviceMake = DEVICE_MAKE_IOS, deviceModel = DEVICE_MODEL_IOS, @@ -372,7 +376,6 @@ object YouTubeAppClient { userAgent = USER_AGENT_IOS, clientVersion = CLIENT_VERSION_IOS, supportsCookies = false, - requirePoToken = true, clientName = "IOS", friendlyName = if (forceAVC()) "iOS Force AVC" @@ -381,12 +384,20 @@ object YouTubeAppClient { ); companion object { - val CLIENT_ORDER_TO_USE_YOUTUBE: Array = arrayOf( + val CLIENT_ORDER_TO_USE: Array = arrayOf( ANDROID_VR_NO_AUTH, ANDROID_UNPLUGGED, ANDROID_CREATOR, IOS_UNPLUGGED, - IOS, + ANDROID_VR, + ) + + val CLIENT_ORDER_TO_USE_IOS: Array = arrayOf( + ANDROID_VR_NO_AUTH, + ANDROID_UNPLUGGED, + ANDROID_CREATOR, + IOS_UNPLUGGED, + IOS_DEPRECATED, ANDROID_VR, ) } 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/innertube/client/YouTubeMusicAppClient.java similarity index 98% rename from extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/MusicAppClient.java rename to extensions/shared/src/main/java/app/revanced/extension/shared/innertube/client/YouTubeMusicAppClient.java index 21b580c8d..24081156a 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/MusicAppClient.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/client/YouTubeMusicAppClient.java @@ -1,10 +1,10 @@ -package app.revanced.extension.shared.patches.client; +package app.revanced.extension.shared.innertube.client; import android.os.Build; import java.util.Locale; -public class MusicAppClient { +public class YouTubeMusicAppClient { // Response to the '/next' request is 'Please update to continue using the app': // https://github.com/inotia00/ReVanced_Extended/issues/2743 @@ -46,7 +46,7 @@ public class MusicAppClient { private static final String DEVICE_MAKE_IOS_MUSIC = "Apple"; private static final String OS_NAME_IOS_MUSIC = "iOS"; - private MusicAppClient() { + private YouTubeMusicAppClient() { } private static String androidUserAgent(String clientVersion) { diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeWebClient.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/client/YouTubeWebClient.kt similarity index 72% rename from extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeWebClient.kt rename to extensions/shared/src/main/java/app/revanced/extension/shared/innertube/client/YouTubeWebClient.kt index 331d1d9a4..3f9c5e8c0 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/client/YouTubeWebClient.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/client/YouTubeWebClient.kt @@ -1,14 +1,10 @@ -package app.revanced.extension.shared.patches.client +package app.revanced.extension.shared.innertube.client /** * Used to fetch video information. */ @Suppress("unused") 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 - */ private const val USER_AGENT_SAFARI = "Mozilla/5.0 (iPad; CPU OS 16_7_10 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1,gzip(gfe)" @@ -26,11 +22,11 @@ object YouTubeWebClient { * Client version. */ @JvmField - val clientVersion: String + val clientVersion: String, ) { MWEB( id = 2, - clientVersion = "2.20241202.07.00" + clientVersion = "2.20241202.07.00", ), WEB_REMIX( id = 29, diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/requests/InnerTubeRequestBody.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/requests/InnerTubeRequestBody.kt new file mode 100644 index 000000000..6e6a8c0d8 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/requests/InnerTubeRequestBody.kt @@ -0,0 +1,364 @@ +package app.revanced.extension.shared.innertube.requests + +import app.revanced.extension.shared.innertube.client.YouTubeAppClient +import app.revanced.extension.shared.innertube.client.YouTubeWebClient +import app.revanced.extension.shared.requests.Requester +import app.revanced.extension.shared.requests.Route.CompiledRoute +import app.revanced.extension.shared.settings.BaseSettings +import app.revanced.extension.shared.utils.Logger +import app.revanced.extension.shared.utils.StringRef.str +import app.revanced.extension.shared.utils.Utils +import org.apache.commons.lang3.StringUtils +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.io.IOException +import java.net.HttpURLConnection +import java.nio.charset.StandardCharsets +import java.util.Date +import java.util.Locale +import java.util.TimeZone + +@Suppress("deprecation") +object InnerTubeRequestBody { + + private const val YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/" + + private const val AUTHORIZATION_HEADER = "Authorization" + private val REQUEST_HEADER_KEYS = setOf( + AUTHORIZATION_HEADER, // Available only to logged-in users. + "X-GOOG-API-FORMAT-VERSION", + "X-Goog-Visitor-Id" + ) + + /** + * TCP connection and HTTP read timeout + */ + private const val CONNECTION_TIMEOUT_MILLISECONDS = 10 * 1000 // 10 Seconds. + + private val LOCALE: Locale = Utils.getContext().resources + .configuration.locale + private val LOCALE_COUNTRY: String = LOCALE.country + private val LOCALE_LANGUAGE: String = LOCALE.language + private val TIME_ZONE: TimeZone = TimeZone.getDefault() + private val TIME_ZONE_ID: String = TIME_ZONE.id + private val UTC_OFFSET_MINUTES: Int = TIME_ZONE.getOffset(Date().time) / 60000 + + @JvmStatic + fun createApplicationRequestBody( + clientType: YouTubeAppClient.ClientType, + videoId: String, + playlistId: String? = null, + botGuardPoToken: String = "", + visitorId: String = "", + setLocale: Boolean = false, + language: String = BaseSettings.SPOOF_STREAMING_DATA_LANGUAGE.get().language, + ): ByteArray { + val innerTubeBody = JSONObject() + + try { + val client = JSONObject() + client.put("deviceMake", clientType.deviceMake) + client.put("deviceModel", clientType.deviceModel) + client.put("clientName", clientType.clientName) + client.put("clientVersion", clientType.clientVersion) + client.put("osName", clientType.osName) + client.put("osVersion", clientType.osVersion) + if (clientType.androidSdkVersion != null) { + client.put("androidSdkVersion", clientType.androidSdkVersion) + if (clientType.gmscoreVersionCode != null) { + client.put("gmscoreVersionCode", clientType.gmscoreVersionCode) + } + } + client.put( + "hl", + if (setLocale) { + language + } else { + LOCALE_LANGUAGE + } + ) + client.put("gl", LOCALE_COUNTRY) + client.put("timeZone", TIME_ZONE_ID) + client.put("utcOffsetMinutes", "$UTC_OFFSET_MINUTES") + + val context = JSONObject() + context.put("client", client) + + innerTubeBody.put("context", context) + innerTubeBody.put("contentCheckOk", true) + innerTubeBody.put("racyCheckOk", true) + innerTubeBody.put("videoId", videoId) + + if (playlistId != null) { + innerTubeBody.put("playlistId", playlistId) + } + + if (!StringUtils.isAnyEmpty(botGuardPoToken, visitorId)) { + val serviceIntegrityDimensions = JSONObject() + serviceIntegrityDimensions.put("poToken", botGuardPoToken) + innerTubeBody.put("serviceIntegrityDimensions", serviceIntegrityDimensions) + } + } catch (e: JSONException) { + Logger.printException({ "Failed to create application innerTubeBody" }, e) + } + + return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8) + } + + @JvmStatic + fun createWebInnertubeBody( + clientType: YouTubeWebClient.ClientType, + videoId: String + ): ByteArray { + val innerTubeBody = JSONObject() + + try { + val client = JSONObject() + client.put("clientName", clientType.clientName) + client.put("clientVersion", clientType.clientVersion) + val context = JSONObject() + context.put("client", client) + + val lockedSafetyMode = JSONObject() + lockedSafetyMode.put("lockedSafetyMode", false) + val user = JSONObject() + user.put("user", lockedSafetyMode) + + innerTubeBody.put("context", context) + innerTubeBody.put("contentCheckOk", true) + innerTubeBody.put("racyCheckOk", true) + innerTubeBody.put("videoId", videoId) + } catch (e: JSONException) { + Logger.printException({ "Failed to create web innerTubeBody" }, e) + } + + return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8) + } + + private fun androidInnerTubeBody( + clientType: YouTubeAppClient.ClientType = YouTubeAppClient.ClientType.ANDROID + ): JSONObject { + val innerTubeBody = JSONObject() + + try { + val client = JSONObject() + client.put("deviceMake", clientType.deviceMake) + client.put("deviceModel", clientType.deviceModel) + client.put("clientName", clientType.clientName) + client.put("clientVersion", clientType.clientVersion) + client.put("osName", clientType.osName) + client.put("osVersion", clientType.osVersion) + client.put("androidSdkVersion", clientType.androidSdkVersion) + client.put("hl", LOCALE_LANGUAGE) + client.put("gl", LOCALE_COUNTRY) + client.put("timeZone", TIME_ZONE_ID) + client.put("utcOffsetMinutes", UTC_OFFSET_MINUTES.toString()) + + val context = JSONObject() + context.put("client", client) + + innerTubeBody.put("context", context) + innerTubeBody.put("contentCheckOk", true) + innerTubeBody.put("racyCheckOk", true) + } catch (e: JSONException) { + Logger.printException({ "Failed to create android innerTubeBody" }, e) + } + + return innerTubeBody + } + + @JvmStatic + fun createPlaylistRequestBody( + videoId: String, + ): ByteArray { + val innerTubeBody = androidInnerTubeBody() + + try { + innerTubeBody.put("params", "CAQ%3D") + // TODO: Implement an AlertDialog that allows changing the title of the playlist. + innerTubeBody.put("title", str("revanced_queue_manager_queue")) + + val videoIds = JSONArray() + videoIds.put(0, videoId) + innerTubeBody.put("videoIds", videoIds) + } catch (e: JSONException) { + Logger.printException({ "Failed to create create/playlist innerTubeBody" }, e) + } + + return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8) + } + + @JvmStatic + fun deletePlaylistRequestBody( + playlistId: String, + ): ByteArray { + val innerTubeBody = androidInnerTubeBody() + + try { + innerTubeBody.put("playlistId", playlistId) + } catch (e: JSONException) { + Logger.printException({ "Failed to create delete/playlist innerTubeBody" }, e) + } + + return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8) + } + + @JvmStatic + fun editPlaylistRequestBody( + videoId: String, + playlistId: String, + setVideoId: String?, + ): ByteArray { + val innerTubeBody = androidInnerTubeBody() + + try { + innerTubeBody.put("playlistId", playlistId) + + val actionsObject = JSONObject() + if (setVideoId != null && setVideoId.isNotEmpty()) { + actionsObject.put("action", "ACTION_REMOVE_VIDEO") + actionsObject.put("setVideoId", setVideoId) + } else { + actionsObject.put("action", "ACTION_ADD_VIDEO") + actionsObject.put("addedVideoId", videoId) + } + + val actionsArray = JSONArray() + actionsArray.put(0, actionsObject) + innerTubeBody.put("actions", actionsArray) + } catch (e: JSONException) { + Logger.printException({ "Failed to create edit/playlist innerTubeBody" }, e) + } + + return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8) + } + + @JvmStatic + fun getPlaylistsRequestBody( + playlistId: String, + ): ByteArray { + val innerTubeBody = androidInnerTubeBody() + + try { + innerTubeBody.put("playlistId", playlistId) + innerTubeBody.put("excludeWatchLater", false) + } catch (e: JSONException) { + Logger.printException({ "Failed to create get/playlists innerTubeBody" }, e) + } + + return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8) + } + + @JvmStatic + fun savePlaylistRequestBody( + playlistId: String, + libraryId: String, + ): ByteArray { + val innerTubeBody = androidInnerTubeBody() + + try { + innerTubeBody.put("playlistId", playlistId) + + val actionsObject = JSONObject() + actionsObject.put("action", "ACTION_ADD_PLAYLIST") + actionsObject.put("addedFullListId", libraryId) + + val actionsArray = JSONArray() + actionsArray.put(0, actionsObject) + innerTubeBody.put("actions", actionsArray) + } catch (e: JSONException) { + Logger.printException({ "Failed to create save/playlist innerTubeBody" }, e) + } + + return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8) + } + + @JvmStatic + fun getInnerTubeResponseConnectionFromRoute( + route: CompiledRoute, + clientType: YouTubeAppClient.ClientType, + requestHeader: Map? = null, + dataSyncId: String? = null, + connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS, + readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS, + ) = getInnerTubeResponseConnectionFromRoute( + route = route, + userAgent = clientType.userAgent, + clientId = clientType.id.toString(), + clientVersion = clientType.clientVersion, + supportsCookies = clientType.supportsCookies, + requestHeader = requestHeader, + dataSyncId = dataSyncId, + connectTimeout = connectTimeout, + readTimeout = readTimeout, + ) + + @JvmStatic + fun getInnerTubeResponseConnectionFromRoute( + route: CompiledRoute, + clientType: YouTubeWebClient.ClientType, + requestHeader: Map? = null, + dataSyncId: String? = null, + connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS, + readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS, + ) = getInnerTubeResponseConnectionFromRoute( + route = route, + userAgent = clientType.userAgent, + clientId = clientType.id.toString(), + clientVersion = clientType.clientVersion, + requestHeader = requestHeader, + dataSyncId = dataSyncId, + connectTimeout = connectTimeout, + readTimeout = readTimeout, + ) + + @Throws(IOException::class) + fun getInnerTubeResponseConnectionFromRoute( + route: CompiledRoute, + userAgent: String, + clientId: String, + clientVersion: String, + supportsCookies: Boolean = true, + requestHeader: Map? = null, + dataSyncId: String? = null, + connectTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS, + readTimeout: Int = CONNECTION_TIMEOUT_MILLISECONDS, + ): HttpURLConnection { + val connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route) + + connection.setRequestProperty("Content-Type", "application/json") + connection.setRequestProperty("User-Agent", userAgent) + connection.setRequestProperty("X-YouTube-Client-Name", clientId) + connection.setRequestProperty("X-YouTube-Client-Version", clientVersion) + + connection.useCaches = false + connection.doOutput = true + + connection.connectTimeout = connectTimeout + connection.readTimeout = readTimeout + + if (requestHeader != null) { + for (key in REQUEST_HEADER_KEYS) { + var value = requestHeader[key] + if (value != null) { + if (key == AUTHORIZATION_HEADER) { + if (!supportsCookies) { + continue + } + } + + connection.setRequestProperty(key, value) + } + } + } + + // Used to identify brand accounts + if (dataSyncId != null && dataSyncId.isNotEmpty()) { + connection.setRequestProperty("X-Goog-PageId", dataSyncId) + } + + return connection + } + +} \ No newline at end of file diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/requests/InnerTubeRoutes.kt b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/requests/InnerTubeRoutes.kt new file mode 100644 index 000000000..48ee66f8f --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/innertube/requests/InnerTubeRoutes.kt @@ -0,0 +1,108 @@ +package app.revanced.extension.shared.innertube.requests + +import app.revanced.extension.shared.requests.Route +import app.revanced.extension.shared.requests.Route.CompiledRoute + +object InnerTubeRoutes { + + @JvmField + val CREATE_PLAYLIST = compileRoute( + endpoint = "playlist/create", + fields = "playlistId", + ) + + @JvmField + val DELETE_PLAYLIST = compileRoute( + endpoint = "playlist/delete", + ) + + @JvmField + val EDIT_PLAYLIST = compileRoute( + endpoint = "browse/edit_playlist", + fields = "status," + "playlistEditResults", + ) + + @JvmField + val GET_CATEGORY = compileRoute( + endpoint = "player", + fields = "microformat.playerMicroformatRenderer.category", + ) + + @JvmField + val GET_PLAYLISTS = compileRoute( + endpoint = "playlist/get_add_to_playlist", + fields = "contents.addToPlaylistRenderer.playlists.playlistAddToOptionRenderer", + ) + + @JvmField + val GET_SET_VIDEO_ID = compileRoute( + endpoint = "next", + fields = "contents.singleColumnWatchNextResults." + + "playlist.playlist.contents.playlistPanelVideoRenderer." + + "playlistSetVideoId", + ) + + @JvmField + val GET_PLAYLIST_PAGE = compileRoute( + endpoint = "next", + fields = "contents.singleColumnWatchNextResults.playlist.playlist", + ) + + @JvmField + val GET_STREAMING_DATA = compileRoute( + endpoint = "player", + fields = "streamingData", + alt = "proto", + prettier = true, + ) + + @JvmField + val GET_VIDEO_ACTION_BUTTON = compileRoute( + endpoint = "next", + fields = "contents.singleColumnWatchNextResults." + + "results.results.contents.slimVideoMetadataSectionRenderer." + + "contents.elementRenderer.newElement.type.componentType." + + "model.videoActionBarModel.buttons.buttonViewModel" + ) + + @JvmField + val GET_VIDEO_DETAILS = compileRoute( + endpoint = "player", + fields = "videoDetails.channelId," + + "videoDetails.isLiveContent," + + "videoDetails.isUpcoming" + ) + + private fun compileRoute( + endpoint: String, + fields: String? = null, + alt: String? = null, + prettier: Boolean = false, + ): CompiledRoute { + var query = Array(4) { "&" } + var i = 0 + query[i] = "?" + + val sb = StringBuilder(endpoint) + if (prettier == false) { + sb.append(query[i++]) + sb.append("prettyPrint=false") + } + if (fields != null) { + sb.append(query[i++]) + sb.append("fields=") + sb.append(fields) + } + if (alt != null) { + sb.append(query[i++]) + sb.append("alt=") + sb.append(alt) + } + + return Route( + Route.Method.POST, + sb.toString() + ).compile() + } + +} \ No newline at end of file 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 e60a4ddf0..75711fe23 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 @@ -37,7 +37,7 @@ public class FullscreenAdsPatch { * 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()}) + * @param type dialog type (similar to {@link Enum#ordinal()}) */ public static void checkDialog(byte[] bytes, int type) { if (!HIDE_FULLSCREEN_ADS) { 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 8282eadf5..74632f8c1 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 @@ -11,4 +11,8 @@ public class PatchStatus { // Replace this with true If the Spoof streaming data patch succeeds in YouTube. return false; } + + public static boolean SpoofStreamingDataIOS() { + 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 20c987db3..a3625e834 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 @@ -2,7 +2,6 @@ package app.revanced.extension.shared.patches; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; - import static app.revanced.extension.shared.utils.Utils.newSpanUsingStylingOfAnotherSpan; import androidx.annotation.NonNull; diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/WatchHistoryPatch.java b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/WatchHistoryPatch.java index 46e3d1cf9..4d37cde1e 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/WatchHistoryPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/patches/WatchHistoryPatch.java @@ -2,8 +2,8 @@ package app.revanced.extension.shared.patches; import android.net.Uri; -import app.revanced.extension.shared.utils.Logger; import app.revanced.extension.shared.settings.BaseSettings; +import app.revanced.extension.shared.utils.Logger; @SuppressWarnings("unused") public final class WatchHistoryPatch { 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 index f26063a4f..a0faa7e06 100644 --- 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 @@ -1,11 +1,11 @@ package app.revanced.extension.shared.patches.spoof; -import app.revanced.extension.shared.patches.client.MusicAppClient.ClientType; -import app.revanced.extension.music.settings.Settings; +import app.revanced.extension.shared.innertube.client.YouTubeMusicAppClient.ClientType; +import app.revanced.extension.shared.settings.BaseSettings; @SuppressWarnings("unused") public class SpoofClientPatch extends BlockRequestPatch { - private static final ClientType CLIENT_TYPE = Settings.SPOOF_CLIENT_TYPE.get(); + private static final ClientType CLIENT_TYPE = BaseSettings.SPOOF_CLIENT_TYPE.get(); /** * Injection point. 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 ef9d62ae7..40d4ceb8a 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 @@ -10,21 +10,21 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; -import app.revanced.extension.shared.patches.client.YouTubeAppClient.ClientType; +import app.revanced.extension.shared.innertube.client.YouTubeAppClient.ClientType; +import app.revanced.extension.shared.patches.PatchStatus; import app.revanced.extension.shared.patches.spoof.requests.StreamingDataRequest; import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.utils.Logger; +import app.revanced.extension.shared.utils.ResourceUtils; import app.revanced.extension.shared.utils.Utils; @SuppressWarnings("unused") public class SpoofStreamingDataPatch extends BlockRequestPatch { - private static final String PO_TOKEN = - BaseSettings.SPOOF_STREAMING_DATA_PO_TOKEN.get(); - private static final String VISITOR_DATA = - BaseSettings.SPOOF_STREAMING_DATA_VISITOR_DATA.get(); private static final boolean SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION = SPOOF_STREAMING_DATA && BaseSettings.SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION.get(); + private static final boolean SPOOF_STREAMING_DATA_TYPE_IOS = + PatchStatus.SpoofStreamingDataIOS() && BaseSettings.SPOOF_STREAMING_DATA_TYPE_IOS.get(); /** * Any unreachable ip address. Used to intentionally fail requests. @@ -69,17 +69,27 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch { * Skip response encryption in OnesiePlayerRequest. */ public static boolean skipResponseEncryption(boolean original) { - if (SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION) { - return false; + if (!SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION) { + return original; } + return false; + } - return original; + /** + * Injection point. + * Turns off a feature flag that interferes with video playback. + */ + public static boolean usePlaybackStartFeatureFlag(boolean original) { + if (!SPOOF_STREAMING_DATA) { + return original; + } + return false; } /** * Injection point. */ - public static void fetchStreams(String url, Map requestHeaders) { + public static void fetchStreams(String url, Map requestHeader) { if (SPOOF_STREAMING_DATA) { String id = Utils.getVideoIdFromRequest(url); if (id == null) { @@ -89,7 +99,7 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch { return; } - StreamingDataRequest.fetchRequest(id, requestHeaders, VISITOR_DATA, PO_TOKEN); + StreamingDataRequest.fetchRequest(id, requestHeader); } } @@ -210,6 +220,18 @@ public class SpoofStreamingDataPatch extends BlockRequestPatch { return videoFormat; } + public static String[] getEntries() { + return SPOOF_STREAMING_DATA_TYPE_IOS + ? ResourceUtils.getStringArray("revanced_spoof_streaming_data_type_ios_entries") + : ResourceUtils.getStringArray("revanced_spoof_streaming_data_type_entries"); + } + + public static String[] getEntryValues() { + return SPOOF_STREAMING_DATA_TYPE_IOS + ? ResourceUtils.getStringArray("revanced_spoof_streaming_data_type_ios_entry_values") + : ResourceUtils.getStringArray("revanced_spoof_streaming_data_type_entry_values"); + } + public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability { @Override public boolean isAvailable() { 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 deleted file mode 100644 index bd99fc453..000000000 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/patches/spoof/requests/PlayerRoutes.kt +++ /dev/null @@ -1,223 +0,0 @@ -package app.revanced.extension.shared.patches.spoof.requests - -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 -import app.revanced.extension.shared.settings.BaseSettings -import app.revanced.extension.shared.utils.Logger -import app.revanced.extension.shared.utils.Utils -import org.apache.commons.lang3.StringUtils -import org.json.JSONException -import org.json.JSONObject -import java.io.IOException -import java.net.HttpURLConnection -import java.nio.charset.StandardCharsets -import java.util.Date -import java.util.Locale -import java.util.TimeZone - -@Suppress("deprecation") -object PlayerRoutes { - @JvmField - val GET_CATEGORY: CompiledRoute = Route( - Route.Method.POST, - "player" + - "?prettyPrint=false" + - "&fields=microformat.playerMicroformatRenderer.category" - ).compile() - - @JvmField - val GET_PLAYLIST_PAGE: CompiledRoute = Route( - Route.Method.POST, - "next" + - "?prettyPrint=false" + - "&fields=contents.singleColumnWatchNextResults.playlist.playlist" - ).compile() - - @JvmField - val GET_STREAMING_DATA: CompiledRoute = Route( - Route.Method.POST, - "player" + - "?fields=streamingData" + - "&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," + - "videoDetails.isLiveContent," + - "videoDetails.isUpcoming" - ).compile() - - private const val YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/" - - /** - * TCP connection and HTTP read timeout - */ - private const val CONNECTION_TIMEOUT_MILLISECONDS = 10 * 1000 // 10 Seconds. - - private val LOCALE: Locale = Utils.getContext().resources - .configuration.locale - private val LOCALE_COUNTRY: String = LOCALE.country - private val LOCALE_LANGUAGE: String = LOCALE.language - private val TIME_ZONE: TimeZone = TimeZone.getDefault() - private val TIME_ZONE_ID: String = TIME_ZONE.id - private val UTC_OFFSET_MINUTES: Int = TIME_ZONE.getOffset(Date().time) / 60000 - - @JvmStatic - fun createApplicationRequestBody( - clientType: YouTubeAppClient.ClientType, - videoId: String, - playlistId: String? = null, - botGuardPoToken: String = "", - visitorId: String = "", - setLocale: Boolean = false, - language: String = BaseSettings.SPOOF_STREAMING_DATA_LANGUAGE.get().language, - ): ByteArray { - val innerTubeBody = JSONObject() - - try { - val client = JSONObject() - client.put("deviceMake", clientType.deviceMake) - client.put("deviceModel", clientType.deviceModel) - client.put("clientName", clientType.clientName) - client.put("clientVersion", clientType.clientVersion) - client.put("osName", clientType.osName) - client.put("osVersion", clientType.osVersion) - if (clientType.androidSdkVersion != null) { - client.put("androidSdkVersion", clientType.androidSdkVersion) - if (clientType.gmscoreVersionCode != null) { - client.put("gmscoreVersionCode", clientType.gmscoreVersionCode) - } - } - client.put( - "hl", - if (setLocale) { - language - } else { - LOCALE_LANGUAGE - } - ) - client.put("gl", LOCALE_COUNTRY) - client.put("timeZone", TIME_ZONE_ID) - client.put("utcOffsetMinutes", "$UTC_OFFSET_MINUTES") - - val context = JSONObject() - context.put("client", client) - - innerTubeBody.put("context", context) - innerTubeBody.put("contentCheckOk", true) - innerTubeBody.put("racyCheckOk", true) - innerTubeBody.put("videoId", videoId) - - if (playlistId != null) { - innerTubeBody.put("playlistId", playlistId) - } - - if (!StringUtils.isAnyEmpty(botGuardPoToken, visitorId)) { - val serviceIntegrityDimensions = JSONObject() - serviceIntegrityDimensions.put("poToken", botGuardPoToken) - innerTubeBody.put("serviceIntegrityDimensions", serviceIntegrityDimensions) - } - } catch (e: JSONException) { - Logger.printException({ "Failed to create application innerTubeBody" }, e) - } - - return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8) - } - - @JvmStatic - fun createWebInnertubeBody( - clientType: YouTubeWebClient.ClientType, - videoId: String - ): ByteArray { - val innerTubeBody = JSONObject() - - try { - val client = JSONObject() - client.put("clientName", clientType.clientName) - client.put("clientVersion", clientType.clientVersion) - val context = JSONObject() - context.put("client", client) - - val lockedSafetyMode = JSONObject() - lockedSafetyMode.put("lockedSafetyMode", false) - val user = JSONObject() - user.put("user", lockedSafetyMode) - - innerTubeBody.put("context", context) - innerTubeBody.put("contentCheckOk", true) - innerTubeBody.put("racyCheckOk", true) - innerTubeBody.put("videoId", videoId) - } catch (e: JSONException) { - Logger.printException({ "Failed to create web innerTubeBody" }, e) - } - - return innerTubeBody.toString().toByteArray(StandardCharsets.UTF_8) - } - - @JvmStatic - fun getPlayerResponseConnectionFromRoute( - route: CompiledRoute, - clientType: YouTubeAppClient.ClientType - ): HttpURLConnection { - return getPlayerResponseConnectionFromRoute( - route, - clientType.userAgent, - clientType.id.toString(), - clientType.clientVersion - ) - } - - @JvmStatic - fun getPlayerResponseConnectionFromRoute( - route: CompiledRoute, - clientType: YouTubeWebClient.ClientType - ): HttpURLConnection { - return getPlayerResponseConnectionFromRoute( - route, - clientType.userAgent, - clientType.id.toString(), - clientType.clientVersion, - ) - } - - @Throws(IOException::class) - fun getPlayerResponseConnectionFromRoute( - route: CompiledRoute, - userAgent: String, - clientId: String, - clientVersion: String - ): HttpURLConnection { - val connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route) - - connection.setRequestProperty("Content-Type", "application/json") - connection.setRequestProperty("User-Agent", userAgent) - connection.setRequestProperty("X-YouTube-Client-Name", clientId) - connection.setRequestProperty("X-YouTube-Client-Version", clientVersion) - - connection.useCaches = false - connection.doOutput = true - - connection.connectTimeout = CONNECTION_TIMEOUT_MILLISECONDS - connection.readTimeout = CONNECTION_TIMEOUT_MILLISECONDS - return connection - } - -} \ No newline at end of file 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 425ccfcb3..cd8f717cb 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,14 +1,14 @@ package app.revanced.extension.shared.patches.spoof.requests import androidx.annotation.GuardedBy -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 +import app.revanced.extension.shared.innertube.client.YouTubeAppClient +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createApplicationRequestBody +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute +import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_STREAMING_DATA import app.revanced.extension.shared.settings.BaseSettings import app.revanced.extension.shared.utils.Logger +import app.revanced.extension.shared.utils.StringRef.str import app.revanced.extension.shared.utils.Utils -import org.apache.commons.lang3.StringUtils import java.io.BufferedInputStream import java.io.ByteArrayOutputStream import java.io.IOException @@ -32,21 +32,19 @@ import java.util.concurrent.TimeoutException * did use its own client streams. */ class StreamingDataRequest private constructor( - videoId: String, playerHeaders: Map, - visitorId: String, botGuardPoToken: String + videoId: String, + requestHeader: Map, ) { private val videoId: String private val future: Future init { - Objects.requireNonNull(playerHeaders) + Objects.requireNonNull(requestHeader) this.videoId = videoId this.future = Utils.submitOnBackgroundThread { fetch( videoId, - playerHeaders, - visitorId, - botGuardPoToken + requestHeader, ) } } @@ -86,33 +84,16 @@ class StreamingDataRequest private constructor( companion object { private const val AUTHORIZATION_HEADER = "Authorization" - private const val VISITOR_ID_HEADER = "X-Goog-Visitor-Id" - private val REQUEST_HEADER_KEYS = arrayOf( - AUTHORIZATION_HEADER, // Available only to logged-in users. - "X-GOOG-API-FORMAT-VERSION", - VISITOR_ID_HEADER - ) + private const val MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000 + private val SPOOF_STREAMING_DATA_TYPE: YouTubeAppClient.ClientType = BaseSettings.SPOOF_STREAMING_DATA_TYPE.get() - private val CLIENT_ORDER_TO_USE: Array = YouTubeAppClient.availableClientTypes(SPOOF_STREAMING_DATA_TYPE) - private val DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH: Boolean = SPOOF_STREAMING_DATA_TYPE == YouTubeAppClient.ClientType.ANDROID_VR_NO_AUTH - private var lastSpoofedClientType: YouTubeAppClient.ClientType? = null - - - /** - * 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 + private var lastSpoofedClientFriendlyName: String? = null @GuardedBy("itself") val cache: MutableMap = Collections.synchronizedMap( @@ -126,22 +107,24 @@ class StreamingDataRequest private constructor( @JvmStatic val lastSpoofedClientName: String - get() = lastSpoofedClientType - ?.friendlyName - ?: "Unknown" + get() { + return if (lastSpoofedClientFriendlyName != null) { + lastSpoofedClientFriendlyName!! + } else { + "Unknown" + } + } @JvmStatic fun fetchRequest( - videoId: String, fetchHeaders: Map, - visitorId: String, botGuardPoToken: String + videoId: String, + fetchHeaders: Map, ) { // Always fetch, even if there is an existing request for the same video. cache[videoId] = StreamingDataRequest( videoId, - fetchHeaders, - visitorId, - botGuardPoToken + fetchHeaders ) } @@ -150,71 +133,40 @@ class StreamingDataRequest private constructor( return cache[videoId] } - private fun handleConnectionError(toastMessage: String, ex: Exception?) { + private fun handleConnectionError( + toastMessage: String, + ex: Exception?, + showToast: Boolean = false, + ) { + if (showToast) Utils.showToastShort(toastMessage) Logger.printInfo({ toastMessage }, ex) } private fun send( clientType: YouTubeAppClient.ClientType, videoId: String, - playerHeaders: Map, - visitorId: String, - botGuardPoToken: String + requestHeader: Map, ): HttpURLConnection? { Objects.requireNonNull(clientType) Objects.requireNonNull(videoId) - Objects.requireNonNull(playerHeaders) + Objects.requireNonNull(requestHeader) val startTime = System.currentTimeMillis() Logger.printDebug { "Fetching video streams for: $videoId using client: $clientType" } try { val connection = - getPlayerResponseConnectionFromRoute(GET_STREAMING_DATA, clientType) - connection.connectTimeout = HTTP_TIMEOUT_MILLISECONDS - connection.readTimeout = HTTP_TIMEOUT_MILLISECONDS - - val usePoToken = - clientType.requirePoToken && !StringUtils.isAnyEmpty(botGuardPoToken, visitorId) - - for (key in REQUEST_HEADER_KEYS) { - var value = playerHeaders[key] - if (value != null) { - if (key == AUTHORIZATION_HEADER) { - if (!clientType.supportsCookies) { - Logger.printDebug { "Not including request header: $key" } - continue - } - } - if (key == VISITOR_ID_HEADER && usePoToken) { - val originalVisitorId: String = value - Logger.printDebug { "Original visitor id:\n$originalVisitorId" } - Logger.printDebug { "Replaced visitor id:\n$visitorId" } - value = visitorId - } - - connection.setRequestProperty(key, value) - } - } - - val requestBody: ByteArray - if (usePoToken) { - requestBody = createApplicationRequestBody( - clientType = clientType, - videoId = videoId, - botGuardPoToken = botGuardPoToken, - visitorId = visitorId, - setLocale = DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH, + getInnerTubeResponseConnectionFromRoute( + GET_STREAMING_DATA, + clientType, + requestHeader ) - Logger.printDebug { "Set poToken (botGuardPoToken):\n$botGuardPoToken" } - } else { - requestBody = - createApplicationRequestBody( - clientType = clientType, - videoId = videoId, - setLocale = DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH, - ) - } + + val requestBody = createApplicationRequestBody( + clientType = clientType, + videoId = videoId, + setLocale = DEFAULT_CLIENT_IS_ANDROID_VR_NO_AUTH, + ) connection.setFixedLengthStreamingMode(requestBody.size) connection.outputStream.write(requestBody) @@ -243,15 +195,15 @@ class StreamingDataRequest private constructor( } private fun fetch( - videoId: String, playerHeaders: Map, - visitorId: String, botGuardPoToken: String + videoId: String, + requestHeader: Map, ): ByteBuffer? { - lastSpoofedClientType = null + lastSpoofedClientFriendlyName = null // Retry with different client if empty response body is received. for (clientType in CLIENT_ORDER_TO_USE) { if (clientType.requireAuth && - playerHeaders[AUTHORIZATION_HEADER] == null + requestHeader[AUTHORIZATION_HEADER] == null ) { Logger.printDebug { "Skipped login-required client (incognito mode or not logged in)\nClient: $clientType\nVideo: $videoId" } continue @@ -259,9 +211,7 @@ class StreamingDataRequest private constructor( send( clientType, videoId, - playerHeaders, - visitorId, - botGuardPoToken + requestHeader, )?.let { connection -> try { // gzip encoding doesn't response with content length (-1), @@ -271,14 +221,14 @@ class StreamingDataRequest private constructor( } else { BufferedInputStream(connection.inputStream).use { inputStream -> ByteArrayOutputStream().use { stream -> - val buffer = ByteArray(2048) + val buffer = ByteArray(4096) var bytesRead: Int while ((inputStream.read(buffer) .also { bytesRead = it }) >= 0 ) { stream.write(buffer, 0, bytesRead) } - lastSpoofedClientType = clientType + lastSpoofedClientFriendlyName = clientType.friendlyName return ByteBuffer.wrap(stream.toByteArray()) } } @@ -289,7 +239,12 @@ class StreamingDataRequest private constructor( } } - handleConnectionError("Could not fetch any client streams", null) + handleConnectionError(str("revanced_spoof_streaming_data_failed_forbidden"), null, true) + handleConnectionError( + str("revanced_spoof_streaming_data_failed_forbidden_suggestion"), + null, + true + ) return null } } 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 004766e99..992c18370 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 @@ -3,10 +3,10 @@ package app.revanced.extension.shared.settings; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; +import app.revanced.extension.shared.innertube.client.YouTubeAppClient; +import app.revanced.extension.shared.innertube.client.YouTubeMusicAppClient; import app.revanced.extension.shared.patches.ReturnYouTubeUsernamePatch.DisplayFormat; 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; /** @@ -31,7 +31,7 @@ public class BaseSettings { * Some patches are in a shared path, so they are declared here. */ 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", MusicAppClient.ClientType.IOS_MUSIC_6_21, true); + public static final EnumSetting SPOOF_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_client_type", YouTubeMusicAppClient.ClientType.IOS_MUSIC_6_21, true); /** * These settings are used by YouTube. @@ -43,11 +43,9 @@ public class BaseSettings { "revanced_spoof_streaming_data_ios_force_avc_user_dialog_message"); public static final BooleanSetting SPOOF_STREAMING_DATA_SKIP_RESPONSE_ENCRYPTION = new BooleanSetting("revanced_spoof_streaming_data_skip_response_encryption", TRUE, true); public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE); + public static final BooleanSetting SPOOF_STREAMING_DATA_TYPE_IOS = new BooleanSetting("revanced_spoof_streaming_data_type_ios", FALSE, true, "revanced_spoof_streaming_data_type_ios_user_dialog_message"); // 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", YouTubeAppClient.ClientType.ANDROID_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); + public static final EnumSetting SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", YouTubeAppClient.ClientType.ANDROID_VR, true); /** * These settings are used by YouTube and YouTube Music. diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java index 168ef7375..9834cc5f0 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/AbstractPreferenceFragment.java @@ -22,16 +22,16 @@ import android.widget.ListView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.Objects; - import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.utils.Logger; +import app.revanced.extension.shared.utils.StringRef; import app.revanced.extension.shared.utils.Utils; @SuppressWarnings({"unused", "deprecation"}) public abstract class AbstractPreferenceFragment extends PreferenceFragment { + /** * Indicates that if a preference changes, * to apply the change from the Setting to the UI component. @@ -39,7 +39,11 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { public static boolean settingImportInProgress; /** - * Confirm and restart dialog button text and title. + * Prevents recursive calls during preference <-> UI syncing from showing extra dialogs. + */ + private static boolean updatingPreference; + + /** * Set by subclasses if Strings cannot be added as a resource. */ @Nullable @@ -52,7 +56,14 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { try { - Setting setting = Setting.getSettingFromPath(Objects.requireNonNull(str)); + if (updatingPreference) { + Logger.printDebug(() -> "Ignoring preference change as sync is in progress"); + return; + } + if (str == null) { + return; + } + Setting setting = Setting.getSettingFromPath(str); if (setting == null) { return; } @@ -73,10 +84,13 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { } } + updatingPreference = true; // Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'. + // Updating here can can cause a recursive call back into this same method. updatePreference(pref, setting, true, settingImportInProgress); // Update any other preference availability that may now be different. updateUIAvailability(); + updatingPreference = false; } catch (Exception ex) { Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex); } @@ -103,36 +117,39 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { Utils.verifyOnMainThread(); final var context = getActivity(); - showingUserDialogMessage = true; - assert setting.userDialogMessage != null; - new AlertDialog.Builder(context) - .setTitle(android.R.string.dialog_alert_title) - .setMessage(setting.userDialogMessage.toString()) - .setPositiveButton(android.R.string.ok, (dialog, id) -> { - // User confirmed, save to the Setting. - updatePreference(pref, setting, true, false); + final StringRef userDialogMessage = setting.userDialogMessage; + if (context != null && userDialogMessage != null) { + showingUserDialogMessage = true; - // Update availability of other preferences that may be changed. - updateUIAvailability(); + new AlertDialog.Builder(context) + .setTitle(android.R.string.dialog_alert_title) + .setMessage(userDialogMessage.toString()) + .setPositiveButton(android.R.string.ok, (dialog, id) -> { + // User confirmed, save to the Setting. + updatePreference(pref, setting, true, false); - if (setting.rebootApp) { - showRestartDialog(context); - } - }) - .setNegativeButton(android.R.string.cancel, (dialog, id) -> { - // Restore whatever the setting was before the change. - updatePreference(pref, setting, true, true); - }) - .setOnDismissListener(dialog -> showingUserDialogMessage = false) - .setCancelable(false) - .show(); + // Update availability of other preferences that may be changed. + updateUIAvailability(); + + if (setting.rebootApp) { + showRestartDialog(context); + } + }) + .setNegativeButton(android.R.string.cancel, (dialog, id) -> { + // Restore whatever the setting was before the change. + updatePreference(pref, setting, true, true); + }) + .setOnDismissListener(dialog -> showingUserDialogMessage = false) + .setCancelable(false) + .show(); + } } /** * Updates all Preferences values and their availability using the current values in {@link Setting}. */ protected void updateUIToSettingValues() { - updatePreferenceScreen(getPreferenceScreen(), true,true); + updatePreferenceScreen(getPreferenceScreen(), true, true); } /** @@ -146,14 +163,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { * @return If the preference is currently set to the default value of the Setting. */ protected boolean prefIsSetToDefault(Preference pref, Setting setting) { + Object defaultValue = setting.defaultValue; if (pref instanceof SwitchPreference switchPref) { - return switchPref.isChecked() == (Boolean) setting.defaultValue; + return switchPref.isChecked() == (Boolean) defaultValue; } + String defaultValueString = defaultValue.toString(); if (pref instanceof EditTextPreference editPreference) { - return editPreference.getText().equals(setting.defaultValue.toString()); + return editPreference.getText().equals(defaultValueString); } if (pref instanceof ListPreference listPref) { - return listPref.getValue().equals(setting.defaultValue.toString()); + return listPref.getValue().equals(defaultValueString); } throw new IllegalStateException("Must override method to handle " @@ -227,7 +246,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { /** * Updates a UI Preference with the {@link Setting} that backs it. * - * @param syncSetting If the UI should be synced {@link Setting} <-> Preference + * @param syncSetting If the UI should be synced {@link Setting} <-> Preference * @param applySettingToPreference If true, then apply {@link Setting} -> Preference. * If false, then apply {@link Setting} <- Preference. */ @@ -258,18 +277,19 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment { listPreference.setSummary(objectStringValue); } - public static void showRestartDialog(@NonNull final Context context) { + public static void showRestartDialog(@NonNull Context context) { if (restartDialogMessage == null) { restartDialogMessage = str("revanced_extended_restart_message"); } + showRestartDialog(context, restartDialogMessage); } - public static void showRestartDialog(@NonNull final Context context, String message) { + public static void showRestartDialog(@NonNull Context context, String message) { showRestartDialog(context, message, 0); } - public static void showRestartDialog(@NonNull final Context context, String message, long delay) { + public static void showRestartDialog(@NonNull Context context, String message, long delay) { Utils.verifyOnMainThread(); new AlertDialog.Builder(context) diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java index 43305c23c..a67639cc0 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/settings/preference/ResettableEditTextPreference.java @@ -10,6 +10,8 @@ import android.util.AttributeSet; import android.widget.Button; import android.widget.EditText; +import androidx.annotation.Nullable; + import java.util.Objects; import app.revanced.extension.shared.settings.Setting; @@ -19,6 +21,12 @@ import app.revanced.extension.shared.utils.Utils; @SuppressWarnings({"unused", "deprecation"}) public class ResettableEditTextPreference extends EditTextPreference { + /** + * Setting to reset. + */ + @Nullable + private Setting setting; + public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @@ -35,6 +43,10 @@ public class ResettableEditTextPreference extends EditTextPreference { super(context); } + public void setSetting(@Nullable Setting setting) { + this.setting = setting; + } + @Override protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { Utils.setEditTextDialogTheme(builder); @@ -44,7 +56,12 @@ public class ResettableEditTextPreference extends EditTextPreference { if (title != null) { builder.setTitle(getTitle()); } - final Setting setting = Setting.getSettingFromPath(getKey()); + if (setting == null) { + String key = getKey(); + if (key != null) { + setting = Setting.getSettingFromPath(key); + } + } if (setting != null) { builder.setNeutralButton(str("revanced_extended_settings_reset"), null); } @@ -65,8 +82,7 @@ public class ResettableEditTextPreference extends EditTextPreference { } button.setOnClickListener(v -> { try { - Setting setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey())); - String defaultStringValue = setting.defaultValue.toString(); + String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString(); EditText editText = getEditText(); editText.setText(defaultStringValue); editText.setSelection(defaultStringValue.length()); // move cursor to end of text diff --git a/extensions/shared/src/main/java/app/revanced/extension/shared/utils/PackageUtils.java b/extensions/shared/src/main/java/app/revanced/extension/shared/utils/PackageUtils.java index dcfd46864..c2f147dcf 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/shared/utils/PackageUtils.java +++ b/extensions/shared/src/main/java/app/revanced/extension/shared/utils/PackageUtils.java @@ -29,10 +29,20 @@ public class PackageUtils extends Utils { } } + @Nullable + public static Integer getTargetSDKVersion(@NonNull String packageName) { + ApplicationInfo applicationInfo = getApplicationInfo(packageName); + if (applicationInfo != null) { + return applicationInfo.targetSdkVersion; + } + + return null; + } + public static boolean isPackageEnabled(@NonNull String packageName) { - try { - return getContext().getPackageManager().getApplicationInfo(packageName, 0).enabled; - } catch (PackageManager.NameNotFoundException ignored) { + ApplicationInfo applicationInfo = getApplicationInfo(packageName); + if (applicationInfo != null) { + return applicationInfo.enabled; } return false; @@ -47,6 +57,16 @@ public class PackageUtils extends Utils { } // utils + @Nullable + private static ApplicationInfo getApplicationInfo(@NonNull String packageName) { + try { + return getContext().getPackageManager().getApplicationInfo(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + Logger.printException(() -> "Failed to get application Info!" + e); + } + return null; + } + @Nullable private static PackageInfo getPackageInfo() { try { 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 268a9e0be..956f60039 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 @@ -60,6 +60,7 @@ public class Utils { private static WeakReference activityRef = new WeakReference<>(null); @SuppressLint("StaticFieldLeak") private static volatile Context context; + private static Locale contextLocale; protected Utils() { } // utility class @@ -308,34 +309,51 @@ public class Utils { * @return Context with locale applied. */ public static Context getLocalizedContext(Context mContext) { - Activity mActivity = activityRef.get(); - if (mActivity == null) { - return mContext; - } - if (mContext == null) { - return null; + try { + Activity mActivity = activityRef.get(); + if (mActivity != null && mContext != null) { + AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); + + // Locale of Application. + Locale applicationLocale = language == AppLanguage.DEFAULT + ? mActivity.getResources().getConfiguration().locale + : language.getLocale(); + + // Locale of Context. + Locale contextLocale = mContext.getResources().getConfiguration().locale; + + // If they are different, overrides the Locale of the Context and resource. + if (applicationLocale != contextLocale) { + Utils.contextLocale = contextLocale; + + // If they are different, overrides the Locale of the Context and resource. + Locale.setDefault(applicationLocale); + Configuration configuration = new Configuration(mContext.getResources().getConfiguration()); + configuration.setLocale(applicationLocale); + return mContext.createConfigurationContext(configuration); + } + } + } catch (Exception ex) { + Logger.printException(() -> "getLocalizedContext failed", ex); } - AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); + return mContext; + } - // Locale of Application. - Locale applicationLocale = language == AppLanguage.DEFAULT - ? mActivity.getResources().getConfiguration().locale - : language.getLocale(); - - // Locale of Context. - Locale contextLocale = mContext.getResources().getConfiguration().locale; - - // If they are identical, no need to override them. - if (applicationLocale == contextLocale) { - return mContext; + public static void resetLocalizedContext() { + try { + if (contextLocale != null) { + Locale.setDefault(contextLocale); + Context mContext = getContext(); + if (mContext != null) { + Configuration config = mContext.getResources().getConfiguration(); + config.setLocale(contextLocale); + setContext(mContext.createConfigurationContext(config)); + } + } + } catch (Exception ex) { + Logger.printException(() -> "resetLocalizedContext failed", ex); } - - // If they are different, overrides the Locale of the Context and resource. - Locale.setDefault(applicationLocale); - Configuration configuration = new Configuration(mContext.getResources().getConfiguration()); - configuration.setLocale(applicationLocale); - return mContext.createConfigurationContext(configuration); } public static void setActivity(Activity mainActivity) { @@ -353,14 +371,6 @@ public class Utils { // Must initially set context to check the app language. context = appContext; Logger.initializationInfo(Utils.class, "Set context: " + appContext); - - AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); - if (language != AppLanguage.DEFAULT) { - // Create a new context with the desired language. - Configuration config = appContext.getResources().getConfiguration(); - config.setLocale(language.getLocale()); - context = appContext.createConfigurationContext(config); - } } public static void setClipboard(@NonNull String text) { @@ -538,14 +548,6 @@ public class Utils { return Build.VERSION.SDK_INT >= sdk; } - public static int dpToPx(float dp) { - if (context == null) { - return (int) dp; - } else { - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()); - } - } - public static int dpToPx(int dp) { if (context == null) { return dp; @@ -608,10 +610,10 @@ public class Utils { *
* Be aware the on start action can be called multiple times for some situations, * such as the user switching apps without dismissing the dialog then switching back to this app. - *
+ *
* This method is only useful during app startup and multiple patches may show their own dialog, * and the most important dialog can be called last (using a delay) so it's always on top. - *
+ *
* For all other situations it's better to not use this method and * call {@link AlertDialog#show()} on the dialog. */ 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 680846c13..eace3d445 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,7 +6,6 @@ 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") 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 index 5625e07d3..9284d8a62 100644 --- 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 @@ -6,7 +6,6 @@ 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 { @@ -44,7 +43,7 @@ object ChangeStartPagePatch { } appLaunched = true - Logger.printDebug{ "Changing browseId to $browseId" } + Logger.printDebug { "Changing browseId to $browseId" } return browseId } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/DownloadActionsPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/DownloadActionsPatch.java index 0c1607561..d3fd2c399 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/DownloadActionsPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/general/DownloadActionsPatch.java @@ -1,18 +1,34 @@ package app.revanced.extension.youtube.patches.general; -import app.revanced.extension.shared.settings.BooleanSetting; +import static app.revanced.extension.youtube.utils.VideoUtils.launchPlaylistExternalDownloader; +import static app.revanced.extension.youtube.utils.VideoUtils.launchVideoExternalDownloader; + +import android.view.View; + +import androidx.annotation.Nullable; + +import org.apache.commons.lang3.StringUtils; + +import java.util.Map; + import app.revanced.extension.shared.utils.Logger; +import app.revanced.extension.youtube.patches.utils.PlaylistPatch; import app.revanced.extension.youtube.settings.Settings; -import app.revanced.extension.youtube.utils.VideoUtils; @SuppressWarnings("unused") -public final class DownloadActionsPatch extends VideoUtils { +public final class DownloadActionsPatch { - private static final BooleanSetting overrideVideoDownloadButton = - Settings.OVERRIDE_VIDEO_DOWNLOAD_BUTTON; + private static final boolean OVERRIDE_PLAYLIST_DOWNLOAD_BUTTON = + Settings.OVERRIDE_PLAYLIST_DOWNLOAD_BUTTON.get(); - private static final BooleanSetting overridePlaylistDownloadButton = - Settings.OVERRIDE_PLAYLIST_DOWNLOAD_BUTTON; + private static final boolean OVERRIDE_VIDEO_DOWNLOAD_BUTTON = + Settings.OVERRIDE_VIDEO_DOWNLOAD_BUTTON.get(); + + private static final boolean OVERRIDE_VIDEO_DOWNLOAD_BUTTON_QUEUE_MANAGER = + OVERRIDE_VIDEO_DOWNLOAD_BUTTON && Settings.OVERRIDE_VIDEO_DOWNLOAD_BUTTON_QUEUE_MANAGER.get(); + + private static final String ELEMENTS_SENDER_VIEW = + "com.google.android.libraries.youtube.rendering.elements.sender_view"; /** * Injection point. @@ -23,17 +39,21 @@ public final class DownloadActionsPatch extends VideoUtils { *

* Appears to always be called from the main thread. */ - public static boolean inAppVideoDownloadButtonOnClick(String videoId) { + public static boolean inAppVideoDownloadButtonOnClick(@Nullable Map map, Object offlineVideoEndpointOuterClass, + @Nullable String videoId) { try { - if (!overrideVideoDownloadButton.get()) { - return false; - } - if (videoId == null || videoId.isEmpty()) { - return false; - } - launchVideoExternalDownloader(videoId); + if (OVERRIDE_VIDEO_DOWNLOAD_BUTTON && StringUtils.isNotEmpty(videoId)) { + if (OVERRIDE_VIDEO_DOWNLOAD_BUTTON_QUEUE_MANAGER) { + if (map != null && map.get(ELEMENTS_SENDER_VIEW) instanceof View view) { + PlaylistPatch.setContext(view.getContext()); + } + PlaylistPatch.prepareDialogBuilder(videoId); + } else { + launchVideoExternalDownloader(videoId); + } - return true; + return true; + } } catch (Exception ex) { Logger.printException(() -> "inAppVideoDownloadButtonOnClick failure", ex); } @@ -49,15 +69,10 @@ public final class DownloadActionsPatch extends VideoUtils { */ public static String inAppPlaylistDownloadButtonOnClick(String playlistId) { try { - if (!overridePlaylistDownloadButton.get()) { - return playlistId; + if (OVERRIDE_PLAYLIST_DOWNLOAD_BUTTON && StringUtils.isNotEmpty(playlistId)) { + launchPlaylistExternalDownloader(playlistId); + return ""; } - if (playlistId == null || playlistId.isEmpty()) { - return playlistId; - } - launchPlaylistExternalDownloader(playlistId); - - return ""; } catch (Exception ex) { Logger.printException(() -> "inAppPlaylistDownloadButtonOnClick failure", ex); } @@ -73,15 +88,10 @@ public final class DownloadActionsPatch extends VideoUtils { */ public static boolean inAppPlaylistDownloadMenuOnClick(String playlistId) { try { - if (!overridePlaylistDownloadButton.get()) { - return false; + if (OVERRIDE_PLAYLIST_DOWNLOAD_BUTTON && StringUtils.isNotEmpty(playlistId)) { + launchPlaylistExternalDownloader(playlistId); + return true; } - if (playlistId == null || playlistId.isEmpty()) { - return false; - } - launchPlaylistExternalDownloader(playlistId); - - return true; } catch (Exception ex) { Logger.printException(() -> "inAppPlaylistDownloadMenuOnClick failure", ex); } @@ -92,7 +102,7 @@ public final class DownloadActionsPatch extends VideoUtils { * Injection point. */ public static boolean overridePlaylistDownloadButtonVisibility() { - return overridePlaylistDownloadButton.get(); + return OVERRIDE_PLAYLIST_DOWNLOAD_BUTTON; } } 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 b5652471b..2c3c87989 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 @@ -40,6 +40,7 @@ import java.util.EnumMap; import java.util.Map; import java.util.Objects; +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; @@ -105,6 +106,34 @@ public class GeneralPatch { // endregion + // region [Disable layout updates] patch + + private static final String[] REQUEST_HEADER_KEYS = { + "X-Youtube-Cold-Config-Data", + "X-Youtube-Cold-Hash-Data", + "X-Youtube-Hot-Config-Data", + "X-Youtube-Hot-Hash-Data" + }; + + private static final boolean DISABLE_LAYOUT_UPDATES = + Settings.DISABLE_LAYOUT_UPDATES.get(); + + /** + * @param key Keys to be added to the header of CronetBuilder. + * @param value Values to be added to the header of CronetBuilder. + * @return Empty value if setting is enabled. + */ + public static String disableLayoutUpdates(String key, String value) { + if (DISABLE_LAYOUT_UPDATES && StringUtils.equalsAny(key, REQUEST_HEADER_KEYS)) { + Logger.printDebug(() -> "Blocking: " + key); + return ""; + } + + return value; + } + + // endregion + // region [Disable splash animation] patch public static boolean disableSplashAnimation(boolean original) { @@ -234,6 +263,17 @@ public class GeneralPatch { } } + public static int getLibraryDrawableId(int original) { + if (ExtendedUtils.IS_19_26_OR_GREATER && + !ExtendedUtils.isSpoofingToLessThan("19.27.00")) { + int libraryCairoId = ResourceUtils.getDrawableIdentifier("yt_outline_library_cairo_black_24"); + if (libraryCairoId != 0) { + return libraryCairoId; + } + } + return original; + } + public static boolean switchCreateWithNotificationButton(boolean original) { return Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get() || original; } 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 489ac1cae..e3177dd60 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 @@ -43,8 +43,8 @@ public final class OpenChannelOfLiveAvatarPatch { /** * Injection point. * - * @param playbackStartDescriptorMap map containing information about PlaybackStartDescriptor - * @param newlyLoadedVideoId id of the current video + * @param playbackStartDescriptorMap map containing information about PlaybackStartDescriptor + * @param newlyLoadedVideoId id of the current video */ public static void fetchChannelId(@NonNull Map playbackStartDescriptorMap, String newlyLoadedVideoId) { try { 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 1db71775a..64c3897aa 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,8 +2,10 @@ package app.revanced.extension.youtube.patches.general.requests import android.annotation.SuppressLint import androidx.annotation.GuardedBy -import app.revanced.extension.shared.patches.client.YouTubeWebClient -import app.revanced.extension.shared.patches.spoof.requests.PlayerRoutes +import app.revanced.extension.shared.innertube.client.YouTubeWebClient +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createWebInnertubeBody +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute +import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_VIDEO_DETAILS import app.revanced.extension.shared.requests.Requester import app.revanced.extension.shared.utils.Logger import app.revanced.extension.shared.utils.Utils @@ -86,12 +88,11 @@ class VideoDetailsRequest private constructor( Logger.printDebug { "Fetching video details request for: $videoId, using client: $clientTypeName" } try { - val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute( - PlayerRoutes.GET_VIDEO_DETAILS, + val connection = getInnerTubeResponseConnectionFromRoute( + GET_VIDEO_DETAILS, clientType ) - val requestBody = - PlayerRoutes.createWebInnertubeBody(clientType, videoId) + val requestBody = createWebInnertubeBody(clientType, videoId) connection.setFixedLengthStreamingMode(requestBody.size) connection.outputStream.write(requestBody) 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 e6172078d..0541d4f1b 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 @@ -15,7 +15,16 @@ public class BackgroundPlaybackPatch { */ public static boolean isBackgroundPlaybackAllowed(boolean original) { if (original) return true; - return ShortsPlayerState.getCurrent().isClosed(); + return ShortsPlayerState.getCurrent().isClosed() && + // 1. Shorts background playback is enabled. + // 2. Autoplay in feed is turned on. + // 3. Play Shorts from feed. + // 4. Media controls appear in status bar. + // (For unpatched YouTube with Premium accounts, media controls do not appear in the status bar) + // + // This is just a visual bug and does not affect Shorts background play in any way. + // To fix this, just check PlayerType. + PlayerType.getCurrent() != PlayerType.INLINE_MINIMAL; } /** diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/overlaybutton/ExternalDownload.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/overlaybutton/ExternalDownload.java index e6a572af6..450ee50f9 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/overlaybutton/ExternalDownload.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/overlaybutton/ExternalDownload.java @@ -6,7 +6,9 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; import app.revanced.extension.shared.utils.Logger; +import app.revanced.extension.youtube.patches.utils.PlaylistPatch; import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.youtube.shared.VideoInformation; import app.revanced.extension.youtube.utils.VideoUtils; @SuppressWarnings("unused") @@ -19,7 +21,14 @@ public class ExternalDownload extends BottomControlButton { bottomControlsViewGroup, "external_download_button", Settings.OVERLAY_BUTTON_EXTERNAL_DOWNLOADER, - view -> VideoUtils.launchVideoExternalDownloader(), + view -> { + if (Settings.OVERLAY_BUTTON_EXTERNAL_DOWNLOADER_QUEUE_MANAGER.get()) { + PlaylistPatch.setContext(view.getContext()); + PlaylistPatch.prepareDialogBuilder(VideoInformation.getVideoId()); + } else { + VideoUtils.launchVideoExternalDownloader(); + } + }, null ); } 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 f3207ae8f..279c62c67 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 @@ -1,5 +1,7 @@ package app.revanced.extension.youtube.patches.player; +import static app.revanced.extension.youtube.patches.player.ActionButtonsPatch.ActionButton.REMIX; + import androidx.annotation.Nullable; import org.apache.commons.lang3.ArrayUtils; @@ -8,8 +10,6 @@ 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; @@ -106,8 +106,8 @@ public class ActionButtonsPatch { /** * Injection point. * - * @param list Type list of litho components - * @param identifier Identifier of litho components + * @param list Type list of litho components + * @param identifier Identifier of litho components */ public static List hideActionButtonByIndex(@Nullable List list, @Nullable String identifier) { try { 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 00d74d8c5..6ee95843d 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 @@ -24,7 +24,6 @@ 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; @@ -34,7 +33,6 @@ 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; import app.revanced.extension.youtube.shared.VideoInformation; import app.revanced.extension.youtube.utils.VideoUtils; @@ -441,7 +439,7 @@ public class PlayerPatch { if (isLiveChatOrPlaylistPanel) { return true; } - return isAutoPopupPanel && ShortsPlayerState.getCurrent().isClosed(); + return isAutoPopupPanel && !RootView.isShortsActive(); } /** @@ -471,8 +469,8 @@ public class PlayerPatch { * Used in YouTube 20.05.46+. */ public static void disableAutoPlayerPopupPanels(@NonNull String newlyLoadedChannelId, @NonNull String newlyLoadedChannelName, - @NonNull String newlyLoadedVideoId, @NonNull String newlyLoadedVideoTitle, - final long newlyLoadedVideoLength, boolean newlyLoadedLiveStreamValue) { + @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), 3000L); } @@ -518,6 +516,12 @@ public class PlayerPatch { return SPEED_OVERLAY_VALUE; } + public static float speedOverlayRelativeValue(float original) { + return SPEED_OVERLAY_VALUE != 2.0f + ? 0f + : original; + } + public static boolean hideChannelWatermark(boolean original) { return !Settings.HIDE_CHANNEL_WATERMARK.get() && original; } @@ -540,6 +544,10 @@ public class PlayerPatch { return Settings.HIDE_FILMSTRIP_OVERLAY.get(); } + public static boolean hideFilmstripOverlay(boolean original) { + return !Settings.HIDE_FILMSTRIP_OVERLAY.get() && original; + } + public static boolean hideInfoCard(boolean original) { return !Settings.HIDE_INFO_CARDS.get() && original; } 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 8b3b99528..0975a9b6e 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 @@ -47,7 +47,7 @@ public class SeekbarColorPatch { /** * Empty seekbar gradient, if hide seekbar in feed is enabled. */ - private static final int[] HIDDEN_SEEKBAR_GRADIENT_COLORS = { 0x0, 0x0 }; + private static final int[] HIDDEN_SEEKBAR_GRADIENT_COLORS = {0x0, 0x0}; /** * Default YouTube seekbar color brightness. 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 index af38ab459..b485dec6f 100644 --- 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 @@ -1,8 +1,10 @@ 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.innertube.client.YouTubeAppClient +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createApplicationRequestBody +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute +import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_VIDEO_ACTION_BUTTON import app.revanced.extension.shared.requests.Requester import app.revanced.extension.shared.utils.Logger import app.revanced.extension.shared.utils.Utils @@ -20,10 +22,10 @@ import java.util.concurrent.TimeoutException class ActionButtonRequest private constructor( private val videoId: String, - private val playerHeaders: Map, + private val requestHeader: Map, ) { private val future: Future> = Utils.submitOnBackgroundThread { - fetch(videoId, playerHeaders) + fetch(videoId, requestHeader) } val array: Array @@ -52,14 +54,6 @@ class ActionButtonRequest private constructor( } 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") @@ -73,11 +67,11 @@ class ActionButtonRequest private constructor( }) @JvmStatic - fun fetchRequestIfNeeded(videoId: String, playerHeaders: Map) { + fun fetchRequestIfNeeded(videoId: String, requestHeader: Map) { Objects.requireNonNull(videoId) synchronized(cache) { if (!cache.containsKey(videoId)) { - cache[videoId] = ActionButtonRequest(videoId, playerHeaders) + cache[videoId] = ActionButtonRequest(videoId, requestHeader) } } } @@ -93,43 +87,28 @@ class ActionButtonRequest private constructor( 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? { + private fun sendRequest(videoId: String, requestHeader: Map): JSONObject? { Objects.requireNonNull(videoId) val startTime = System.currentTimeMillis() - // '/next' request does not require PoToken. + // '/next' endpoint 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 connection = getInnerTubeResponseConnectionFromRoute( + GET_VIDEO_ACTION_BUTTON, + clientType, + requestHeader, + ) - val requestBody = - PlayerRoutes.createApplicationRequestBody( - clientType = clientType, - videoId = videoId - ) + val requestBody = createApplicationRequestBody( + clientType = clientType, + videoId = videoId + ) connection.setFixedLengthStreamingMode(requestBody.size) connection.outputStream.write(requestBody) @@ -214,8 +193,11 @@ class ActionButtonRequest private constructor( return emptyArray() } - private fun fetch(videoId: String, playerHeaders: Map): Array { - val json = sendRequest(videoId, playerHeaders) + private fun fetch( + videoId: String, + requestHeader: Map + ): Array { + val json = sendRequest(videoId, requestHeader) if (json != null) { return parseResponse(json) } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/CustomActionsPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/CustomActionsPatch.java index 4bf010129..d781ba937 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/CustomActionsPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/CustomActionsPatch.java @@ -1,25 +1,15 @@ package app.revanced.extension.youtube.patches.shorts; import static app.revanced.extension.shared.utils.ResourceUtils.getString; -import static app.revanced.extension.shared.utils.Utils.dpToPx; import static app.revanced.extension.youtube.patches.components.ShortsCustomActionsFilter.isShortsFlyoutMenuVisible; +import static app.revanced.extension.youtube.shared.RootView.isShortsActive; import static app.revanced.extension.youtube.utils.ExtendedUtils.isSpoofingToLessThan; -import android.app.AlertDialog; import android.content.Context; -import android.graphics.ColorFilter; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.StateListDrawable; import android.support.v7.widget.RecyclerView; -import android.view.Gravity; import android.view.View; import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ScrollView; @@ -41,8 +31,7 @@ import app.revanced.extension.shared.utils.ResourceUtils; import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.youtube.patches.components.ShortsCustomActionsFilter; import app.revanced.extension.youtube.settings.Settings; -import app.revanced.extension.youtube.shared.ShortsPlayerState; -import app.revanced.extension.youtube.utils.ThemeUtils; +import app.revanced.extension.youtube.utils.ExtendedUtils; import app.revanced.extension.youtube.utils.VideoUtils; @SuppressWarnings("unused") @@ -66,7 +55,7 @@ public final class CustomActionsPatch { if (!SHORTS_CUSTOM_ACTIONS_TOOLBAR_ENABLED) { return; } - if (ShortsPlayerState.getCurrent().isClosed()) { + if (!isShortsActive()) { return; } if (!isMoreButton(enumString)) { @@ -90,105 +79,28 @@ public final class CustomActionsPatch { }), 0); } - private static void showMoreButtonDialog(Context context) { - ScrollView scrollView = new ScrollView(context); - LinearLayout container = new LinearLayout(context); + private static void showMoreButtonDialog(Context mContext) { + ScrollView mScrollView = new ScrollView(mContext); + LinearLayout mLinearLayout = new LinearLayout(mContext); + mLinearLayout.setOrientation(LinearLayout.VERTICAL); + mLinearLayout.setPadding(0, 0, 0, 0); - container.setOrientation(LinearLayout.VERTICAL); - container.setPadding(0, 0, 0, 0); - - Map toolbarMap = new LinkedHashMap<>(arrSize); + Map actionsMap = new LinkedHashMap<>(arrSize); for (CustomAction customAction : CustomAction.values()) { if (customAction.settings.get()) { String title = customAction.getLabel(); int iconId = customAction.getDrawableId(); Runnable action = customAction.getOnClickAction(); - LinearLayout itemLayout = createItemLayout(context, title, iconId); - toolbarMap.putIfAbsent(itemLayout, action); - container.addView(itemLayout); + LinearLayout itemLayout = ExtendedUtils.createItemLayout(mContext, title, iconId); + actionsMap.putIfAbsent(itemLayout, action); + mLinearLayout.addView(itemLayout); } } - scrollView.addView(container); + mScrollView.addView(mLinearLayout); - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setView(scrollView); - - AlertDialog dialog = builder.create(); - dialog.show(); - - toolbarMap.forEach((view, action) -> - view.setOnClickListener(v -> { - action.run(); - dialog.dismiss(); - }) - ); - toolbarMap.clear(); - - Window window = dialog.getWindow(); - if (window == null) { - return; - } - - // round corners - GradientDrawable dialogBackground = new GradientDrawable(); - dialogBackground.setCornerRadius(32); - window.setBackgroundDrawable(dialogBackground); - - // fit screen width - int dialogWidth = (int) (context.getResources().getDisplayMetrics().widthPixels * 0.95); - window.setLayout(dialogWidth, ViewGroup.LayoutParams.WRAP_CONTENT); - - // move dialog to bottom - WindowManager.LayoutParams layoutParams = window.getAttributes(); - layoutParams.gravity = Gravity.BOTTOM; - - // adjust the vertical offset - layoutParams.y = dpToPx(5); - - window.setAttributes(layoutParams); - } - - private static LinearLayout createItemLayout(Context context, String title, int iconId) { - // Item Layout - LinearLayout itemLayout = new LinearLayout(context); - itemLayout.setOrientation(LinearLayout.HORIZONTAL); - itemLayout.setPadding(dpToPx(16), dpToPx(12), dpToPx(16), dpToPx(12)); - itemLayout.setGravity(Gravity.CENTER_VERTICAL); - itemLayout.setClickable(true); - itemLayout.setFocusable(true); - - // Create a StateListDrawable for the background - StateListDrawable background = new StateListDrawable(); - ColorDrawable pressedDrawable = new ColorDrawable(ThemeUtils.getPressedElementColor()); - ColorDrawable defaultDrawable = new ColorDrawable(ThemeUtils.getBackgroundColor()); - background.addState(new int[]{android.R.attr.state_pressed}, pressedDrawable); - background.addState(new int[]{}, defaultDrawable); - itemLayout.setBackground(background); - - // Icon - ColorFilter cf = new PorterDuffColorFilter(ThemeUtils.getForegroundColor(), PorterDuff.Mode.SRC_ATOP); - ImageView iconView = new ImageView(context); - iconView.setImageResource(iconId); - iconView.setColorFilter(cf); - LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(dpToPx(24), dpToPx(24)); - iconParams.setMarginEnd(dpToPx(16)); - iconView.setLayoutParams(iconParams); - itemLayout.addView(iconView); - - // Text container - LinearLayout textContainer = new LinearLayout(context); - textContainer.setOrientation(LinearLayout.VERTICAL); - TextView titleView = new TextView(context); - titleView.setText(title); - titleView.setTextSize(16); - titleView.setTextColor(ThemeUtils.getForegroundColor()); - textContainer.addView(titleView); - - itemLayout.addView(textContainer); - - return itemLayout; + ExtendedUtils.showBottomSheetDialog(mContext, mScrollView, actionsMap); } private static boolean isMoreButton(String enumString) { @@ -206,7 +118,7 @@ public final class CustomActionsPatch { if (!SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU_ENABLED) { return; } - if (ShortsPlayerState.getCurrent().isClosed()) { + if (!isShortsActive()) { return; } if (bottomSheetMenuObject == null) { @@ -224,7 +136,7 @@ public final class CustomActionsPatch { if (!SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU_ENABLED) { return; } - if (ShortsPlayerState.getCurrent().isClosed()) { + if (!isShortsActive()) { return; } for (CustomAction customAction : CustomAction.values()) { @@ -243,6 +155,34 @@ public final class CustomActionsPatch { Logger.printInfo(() -> customAction.name() + bottomSheetMenuClass + bottomSheetMenuList + bottomSheetMenuObject); } + /** + * Injection point. + */ + public static boolean onBottomSheetMenuItemClick(View view) { + try { + if (view instanceof ViewGroup viewGroup) { + TextView textView = Utils.getChildView(viewGroup, v -> v instanceof TextView); + if (textView != null) { + String menuTitle = textView.getText().toString(); + for (CustomAction customAction : CustomAction.values()) { + if (customAction.getLabel().equals(menuTitle)) { + View.OnLongClickListener onLongClick = customAction.getOnLongClickListener(); + if (onLongClick != null) { + view.setOnLongClickListener(onLongClick); + } + customAction.getOnClickAction().run(); + return true; + } + } + } + } + } catch (Exception ex) { + Logger.printException(() -> "onBottomSheetMenuItemClick failed"); + } + + return false; + } + /** * Injection point. */ @@ -252,7 +192,7 @@ public final class CustomActionsPatch { } recyclerView.getViewTreeObserver().addOnDrawListener(() -> { try { - if (ShortsPlayerState.getCurrent().isClosed()) { + if (!isShortsActive()) { return; } contextRef = new WeakReference<>(recyclerView.getContext()); @@ -267,8 +207,9 @@ public final class CustomActionsPatch { if (recyclerView.getChildAt(childCount - i - 1) instanceof ViewGroup parentViewGroup) { childCount = recyclerView.getChildCount(); if (childCount > 3 && parentViewGroup.getChildAt(1) instanceof TextView textView) { + String menuTitle = textView.getText().toString(); for (CustomAction customAction : CustomAction.values()) { - if (customAction.getLabel().equals(textView.getText().toString())) { + if (customAction.getLabel().equals(menuTitle)) { View.OnClickListener onClick = customAction.getOnClickListener(); View.OnLongClickListener onLongClick = customAction.getOnLongClickListener(); recyclerViewRef = new WeakReference<>(recyclerView); @@ -384,6 +325,11 @@ public final class CustomActionsPatch { true ) ), + SPEED_DIALOG( + Settings.SHORTS_CUSTOM_ACTIONS_SPEED_DIALOG, + "yt_outline_play_arrow_half_circle_black_24", + () -> VideoUtils.showPlaybackSpeedDialog(contextRef.get()) + ), REPEAT_STATE( Settings.SHORTS_CUSTOM_ACTIONS_REPEAT_STATE, "yt_outline_arrow_repeat_1_black_24", diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsRepeatStatePatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsRepeatStatePatch.java index 50933c1f7..6e73485b7 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsRepeatStatePatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsRepeatStatePatch.java @@ -2,6 +2,8 @@ package app.revanced.extension.youtube.patches.shorts; import android.app.Activity; +import androidx.annotation.Nullable; + import java.lang.ref.WeakReference; import java.util.Objects; @@ -29,10 +31,15 @@ public class ShortsRepeatStatePatch { static void setYTEnumValue(Enum ytBehavior) { for (ShortsLoopBehavior rvBehavior : values()) { - if (ytBehavior.name().endsWith(rvBehavior.name())) { - rvBehavior.ytEnumValue = ytBehavior; - - Logger.printDebug(() -> rvBehavior + " set to YT enum: " + ytBehavior.name()); + String ytName = ytBehavior.name(); + if (ytName.endsWith(rvBehavior.name())) { + if (rvBehavior.ytEnumValue != null) { + Logger.printException(() -> "Conflicting behavior names: " + rvBehavior + + " ytBehavior: " + ytName); + } else { + rvBehavior.ytEnumValue = ytBehavior; + Logger.printDebug(() -> rvBehavior + " set to YT enum: " + ytName); + } return; } } @@ -77,25 +84,39 @@ public class ShortsRepeatStatePatch { /** * Injection point. */ - public static Enum changeShortsRepeatBehavior(Enum original) { + @Nullable + public static Enum changeShortsRepeatBehavior(@Nullable Enum original) { try { - final ShortsLoopBehavior behavior = ExtendedUtils.IS_19_34_OR_GREATER && + ShortsLoopBehavior behavior = ExtendedUtils.IS_19_34_OR_GREATER && isAppInBackgroundPiPMode() ? Settings.CHANGE_SHORTS_BACKGROUND_REPEAT_STATE.get() : Settings.CHANGE_SHORTS_REPEAT_STATE.get(); + Enum overrideBehavior = behavior.ytEnumValue; - if (behavior != ShortsLoopBehavior.UNKNOWN && behavior.ytEnumValue != null) { - Logger.printDebug(() -> behavior.ytEnumValue == original - ? "Changing Shorts repeat behavior from: " + original.name() + " to: " + behavior.ytEnumValue - : "Behavior setting is same as original. Using original: " + original.name() - ); + if (behavior != ShortsLoopBehavior.UNKNOWN && overrideBehavior != null) { + Logger.printDebug(() -> { + String name = original == null ? "unknown (null)" : original.name(); + return overrideBehavior == original + ? "Behavior setting is same as original. Using original: " + name + : "Changing Shorts repeat behavior from: " + name + " to: " + overrideBehavior.name(); + }); - return behavior.ytEnumValue; + // For some reason, in YouTube 20.09+, 'UNKNOWN' functions as 'Pause'. + return ExtendedUtils.IS_20_09_OR_GREATER && behavior == ShortsLoopBehavior.END_SCREEN + ? ShortsLoopBehavior.UNKNOWN.ytEnumValue + : overrideBehavior; } } catch (Exception ex) { - Logger.printException(() -> "changeShortsRepeatState failure", ex); + Logger.printException(() -> "changeShortsRepeatBehavior failure", ex); } return original; } + + /** + * Injection point. + */ + public static boolean isAutoPlay(@Nullable Enum original) { + return ShortsLoopBehavior.SINGLE_PLAY.ytEnumValue == original; + } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/swipe/SwipeControlsPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/swipe/SwipeControlsPatch.java index f4b0d16ad..794b9f0e1 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/swipe/SwipeControlsPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/swipe/SwipeControlsPatch.java @@ -4,6 +4,7 @@ import android.view.View; import java.lang.ref.WeakReference; +import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.youtube.settings.Settings; @SuppressWarnings({"unused", "deprecation"}) @@ -59,4 +60,20 @@ public class SwipeControlsPatch { return engagementOverlayView != null && engagementOverlayView.getVisibility() == View.VISIBLE; } + public static final class SwipeOverlayTextSizeAvailability implements Setting.Availability { + @Override + public boolean isAvailable() { + return (Settings.ENABLE_SWIPE_BRIGHTNESS.get() || Settings.ENABLE_SWIPE_VOLUME.get()) && + !Settings.SWIPE_OVERLAY_ALTERNATIVE_UI.get(); + } + } + + public static final class SwipeOverlayModernUIAvailability implements Setting.Availability { + @Override + public boolean isAvailable() { + return (Settings.ENABLE_SWIPE_BRIGHTNESS.get() || Settings.ENABLE_SWIPE_VOLUME.get()) && + Settings.SWIPE_OVERLAY_ALTERNATIVE_UI.get(); + } + } + } 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 67e65bd5c..00990e95c 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 @@ -1,20 +1,24 @@ package app.revanced.extension.youtube.patches.utils; import app.revanced.extension.shared.utils.Logger; +import app.revanced.extension.youtube.shared.EngagementPanel; import app.revanced.extension.youtube.shared.PlayerType; +import app.revanced.extension.youtube.shared.ShortsPlayerState; @SuppressWarnings("unused") 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.isMaximizedOrFullscreenOrPiP()) { + if (playbackSpeed == DEFAULT_YOUTUBE_PLAYBACK_SPEED) { + if (PlayerType.getCurrent().isMaximizedOrFullscreenOrPiP() + // Since RVX has a default playback speed setting for Shorts, + // Playback speed reset should also be prevented in Shorts. + || ShortsPlayerState.getCurrent().isOpen() && EngagementPanel.isOpen()) { + Logger.printDebug(() -> "Ignore changing playback speed, as it is invalid request"); - Logger.printDebug(() -> "Ignore changing playback speed, as it is invalid request: " + playerType.name()); - - return true; + return true; + } } return false; diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlaylistPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlaylistPatch.java new file mode 100644 index 000000000..484ad319a --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlaylistPatch.java @@ -0,0 +1,508 @@ +package app.revanced.extension.youtube.patches.utils; + +import static app.revanced.extension.shared.utils.StringRef.str; + +import android.content.Context; +import android.view.KeyEvent; +import android.widget.LinearLayout; +import android.widget.ScrollView; + +import androidx.annotation.GuardedBy; +import androidx.annotation.NonNull; + +import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar; + +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.bidimap.DualHashBidiMap; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedHashMap; +import java.util.Map; + +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.requests.CreatePlaylistRequest; +import app.revanced.extension.youtube.patches.utils.requests.DeletePlaylistRequest; +import app.revanced.extension.youtube.patches.utils.requests.EditPlaylistRequest; +import app.revanced.extension.youtube.patches.utils.requests.GetPlaylistsRequest; +import app.revanced.extension.youtube.patches.utils.requests.SavePlaylistRequest; +import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.youtube.utils.ExtendedUtils; +import app.revanced.extension.youtube.utils.VideoUtils; +import kotlin.Pair; + +// TODO: Implement sync queue and clean up code. +@SuppressWarnings({"unused", "StaticFieldLeak"}) +public class PlaylistPatch extends VideoUtils { + 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" + }; + private static final boolean QUEUE_MANAGER = + Settings.OVERLAY_BUTTON_EXTERNAL_DOWNLOADER_QUEUE_MANAGER.get() + || Settings.OVERRIDE_VIDEO_DOWNLOAD_BUTTON_QUEUE_MANAGER.get(); + + private static Context mContext; + private static volatile String authorization = ""; + public static volatile String dataSyncId = ""; + public static volatile boolean isIncognito = false; + private static volatile Map requestHeader; + private static volatile String playlistId = ""; + private static volatile String videoId = ""; + + private static String checkFailedAuth; + private static String checkFailedPlaylistId; + private static String checkFailedQueue; + private static String checkFailedVideoId; + private static String checkFailedGeneric; + + private static String fetchFailedAdd; + private static String fetchFailedCreate; + private static String fetchFailedDelete; + private static String fetchFailedRemove; + private static String fetchFailedSave; + + private static String fetchSucceededAdd; + private static String fetchSucceededCreate; + private static String fetchSucceededDelete; + private static String fetchSucceededRemove; + private static String fetchSucceededSave; + + static { + Context mContext = Utils.getContext(); + if (mContext != null && mContext.getResources() != null) { + checkFailedAuth = str("revanced_queue_manager_check_failed_auth"); + checkFailedPlaylistId = str("revanced_queue_manager_check_failed_playlist_id"); + checkFailedQueue = str("revanced_queue_manager_check_failed_queue"); + checkFailedVideoId = str("revanced_queue_manager_check_failed_video_id"); + checkFailedGeneric = str("revanced_queue_manager_check_failed_generic"); + + fetchFailedAdd = str("revanced_queue_manager_fetch_failed_add"); + fetchFailedCreate = str("revanced_queue_manager_fetch_failed_create"); + fetchFailedDelete = str("revanced_queue_manager_fetch_failed_delete"); + fetchFailedRemove = str("revanced_queue_manager_fetch_failed_remove"); + fetchFailedSave = str("revanced_queue_manager_fetch_failed_save"); + + fetchSucceededAdd = str("revanced_queue_manager_fetch_succeeded_add"); + fetchSucceededCreate = str("revanced_queue_manager_fetch_succeeded_create"); + fetchSucceededDelete = str("revanced_queue_manager_fetch_succeeded_delete"); + fetchSucceededRemove = str("revanced_queue_manager_fetch_succeeded_remove"); + fetchSucceededSave = str("revanced_queue_manager_fetch_succeeded_save"); + } + } + + @GuardedBy("itself") + private static final BidiMap lastVideoIds = new DualHashBidiMap<>(); + + /** + * Injection point. + */ + public static boolean onKeyLongPress(int keyCode) { + if (!QUEUE_MANAGER || keyCode != KeyEvent.KEYCODE_BACK) { + return false; + } + if (mContext == null) { + handleCheckError(checkFailedQueue); + return false; + } + prepareDialogBuilder(""); + return true; + } + + /** + * Injection point. + */ + public static void removeFromQueue(@Nullable String setVideoId) { + if (StringUtils.isNotEmpty(setVideoId)) { + synchronized (lastVideoIds) { + String videoId = lastVideoIds.inverseBidiMap().get(setVideoId); + if (videoId != null) { + lastVideoIds.remove(videoId, setVideoId); + EditPlaylistRequest.clearVideoId(videoId); + } + } + } + } + + /** + * Injection point. + */ + public static void setPivotBar(PivotBar view) { + if (QUEUE_MANAGER) { + mContext = view.getContext(); + } + } + + /** + * Injection point. + */ + public static void setRequestHeaders(String url, Map requestHeaders) { + if (QUEUE_MANAGER) { + try { + // Save requestHeaders whenever an account is switched. + String auth = requestHeaders.get(AUTHORIZATION_HEADER); + if (auth == null || authorization.equals(auth)) { + return; + } + for (String key : REQUEST_HEADER_KEYS) { + if (requestHeaders.get(key) == null) { + return; + } + } + authorization = auth; + requestHeader = requestHeaders; + } catch (Exception ex) { + Logger.printException(() -> "setRequestHeaders failure", ex); + } + } + } + + /** + * Invoked by extension. + */ + public static void setContext(Context context) { + mContext = context; + } + + /** + * Invoked by extension. + */ + public static void prepareDialogBuilder(@NonNull String currentVideoId) { + if (authorization.isEmpty() || (dataSyncId.isEmpty() && isIncognito)) { + handleCheckError(checkFailedAuth); + return; + } + if (currentVideoId.isEmpty()) { + buildBottomSheetDialog(QueueManager.noVideoIdQueueEntries); + } else { + videoId = currentVideoId; + synchronized (lastVideoIds) { + QueueManager[] customActionsEntries = playlistId.isEmpty() || lastVideoIds.get(currentVideoId) == null + ? QueueManager.addToQueueEntries + : QueueManager.removeFromQueueEntries; + + buildBottomSheetDialog(customActionsEntries); + } + } + } + + private static void buildBottomSheetDialog(QueueManager[] queueManagerEntries) { + ScrollView mScrollView = new ScrollView(mContext); + LinearLayout mLinearLayout = new LinearLayout(mContext); + mLinearLayout.setOrientation(LinearLayout.VERTICAL); + mLinearLayout.setPadding(0, 0, 0, 0); + + Map actionsMap = new LinkedHashMap<>(queueManagerEntries.length); + + for (QueueManager queueManager : queueManagerEntries) { + String title = queueManager.label; + int iconId = queueManager.drawableId; + Runnable action = queueManager.onClickAction; + LinearLayout itemLayout = ExtendedUtils.createItemLayout(mContext, title, iconId); + actionsMap.putIfAbsent(itemLayout, action); + mLinearLayout.addView(itemLayout); + } + + mScrollView.addView(mLinearLayout); + + ExtendedUtils.showBottomSheetDialog(mContext, mScrollView, actionsMap); + } + + private static void fetchQueue(boolean remove, boolean openPlaylist, boolean openVideo) { + try { + String currentPlaylistId = playlistId; + String currentVideoId = videoId; + synchronized (lastVideoIds) { + if (currentPlaylistId.isEmpty()) { // Queue is empty, create new playlist. + CreatePlaylistRequest.fetchRequestIfNeeded(currentVideoId, requestHeader, dataSyncId); + runOnMainThreadDelayed(() -> { + CreatePlaylistRequest request = CreatePlaylistRequest.getRequestForVideoId(currentVideoId); + if (request != null) { + Pair playlistIds = request.getPlaylistId(); + if (playlistIds != null) { + String createdPlaylistId = playlistIds.getFirst(); + String setVideoId = playlistIds.getSecond(); + if (createdPlaylistId != null && setVideoId != null) { + playlistId = createdPlaylistId; + lastVideoIds.putIfAbsent(currentVideoId, setVideoId); + showToast(fetchSucceededCreate); + Logger.printDebug(() -> "Queue successfully created, playlistId: " + createdPlaylistId + ", setVideoId: " + setVideoId); + if (openPlaylist) { + openQueue(currentVideoId, openVideo); + } + return; + } + } + } + showToast(fetchFailedCreate); + }, 1000); + } else { // Queue is not empty, add or remove video. + String setVideoId = lastVideoIds.get(currentVideoId); + EditPlaylistRequest.fetchRequestIfNeeded(currentVideoId, currentPlaylistId, setVideoId, requestHeader, dataSyncId); + + runOnMainThreadDelayed(() -> { + EditPlaylistRequest request = EditPlaylistRequest.getRequestForVideoId(currentVideoId); + if (request != null) { + String fetchedSetVideoId = request.getResult(); + Logger.printDebug(() -> "fetchedSetVideoId: " + fetchedSetVideoId); + if (remove) { // Remove from queue. + if (StringUtils.isEmpty(fetchedSetVideoId)) { + lastVideoIds.remove(currentVideoId, setVideoId); + showToast(fetchSucceededRemove); + if (openPlaylist) { + openQueue(currentVideoId, openVideo); + } + return; + } + showToast(fetchFailedRemove); + } else { // Add to queue. + if (StringUtils.isNotEmpty(fetchedSetVideoId)) { + lastVideoIds.putIfAbsent(currentVideoId, fetchedSetVideoId); + showToast(fetchSucceededAdd); + Logger.printDebug(() -> "Video successfully added, setVideoId: " + fetchedSetVideoId); + if (openPlaylist) { + openQueue(currentVideoId, openVideo); + } + return; + } + showToast(fetchFailedAdd); + } + } + }, 1000); + } + } + } catch (Exception ex) { + Logger.printException(() -> "fetchQueue failure", ex); + } + } + + private static void saveToPlaylist() { + String currentPlaylistId = playlistId; + if (currentPlaylistId.isEmpty()) { + handleCheckError(checkFailedQueue); + return; + } + try { + GetPlaylistsRequest.fetchRequestIfNeeded(currentPlaylistId, requestHeader, dataSyncId); + runOnMainThreadDelayed(() -> { + GetPlaylistsRequest request = GetPlaylistsRequest.getRequestForPlaylistId(currentPlaylistId); + if (request != null) { + Pair[] playlists = request.getPlaylists(); + if (playlists != null) { + ScrollView mScrollView = new ScrollView(mContext); + LinearLayout mLinearLayout = new LinearLayout(mContext); + mLinearLayout.setOrientation(LinearLayout.VERTICAL); + mLinearLayout.setPadding(0, 0, 0, 0); + + Map actionsMap = new LinkedHashMap<>(playlists.length); + + int libraryIconId = QueueManager.SAVE_QUEUE.drawableId; + + for (Pair playlist : playlists) { + String playlistId = playlist.getFirst(); + String title = playlist.getSecond(); + Runnable action = () -> saveToPlaylist(playlistId, title); + LinearLayout itemLayout = ExtendedUtils.createItemLayout(mContext, title, libraryIconId); + actionsMap.putIfAbsent(itemLayout, action); + mLinearLayout.addView(itemLayout); + } + + mScrollView.addView(mLinearLayout); + + ExtendedUtils.showBottomSheetDialog(mContext, mScrollView, actionsMap); + GetPlaylistsRequest.clear(); + } + } + }, 1000); + } catch (Exception ex) { + Logger.printException(() -> "saveToPlaylist failure", ex); + } + } + + private static void saveToPlaylist(@Nullable String libraryId, @Nullable String libraryTitle) { + try { + if (StringUtils.isEmpty(libraryId)) { + handleCheckError(checkFailedPlaylistId); + return; + } + SavePlaylistRequest.fetchRequestIfNeeded(playlistId, libraryId, requestHeader, dataSyncId); + + runOnMainThreadDelayed(() -> { + SavePlaylistRequest request = SavePlaylistRequest.getRequestForLibraryId(libraryId); + if (request != null) { + Boolean result = request.getResult(); + if (BooleanUtils.isTrue(result)) { + showToast(String.format(fetchSucceededSave, libraryTitle)); + SavePlaylistRequest.clear(); + return; + } + showToast(fetchFailedSave); + } + }, 1000); + } catch (Exception ex) { + Logger.printException(() -> "saveToPlaylist failure", ex); + } + } + + private static void removeQueue() { + String currentPlaylistId = playlistId; + if (currentPlaylistId.isEmpty()) { + handleCheckError(checkFailedQueue); + return; + } + try { + DeletePlaylistRequest.fetchRequestIfNeeded(currentPlaylistId, requestHeader, dataSyncId); + runOnMainThreadDelayed(() -> { + DeletePlaylistRequest request = DeletePlaylistRequest.getRequestForPlaylistId(currentPlaylistId); + if (request != null) { + Boolean result = request.getResult(); + if (BooleanUtils.isTrue(result)) { + playlistId = ""; + synchronized (lastVideoIds) { + lastVideoIds.clear(); + } + CreatePlaylistRequest.clear(); + DeletePlaylistRequest.clear(); + EditPlaylistRequest.clear(); + GetPlaylistsRequest.clear(); + SavePlaylistRequest.clear(); + showToast(fetchSucceededDelete); + return; + } + } + showToast(fetchFailedDelete); + }, 1000); + } catch (Exception ex) { + Logger.printException(() -> "removeQueue failure", ex); + } + } + + private static void downloadVideo() { + String currentVideoId = videoId; + launchVideoExternalDownloader(currentVideoId); + } + + private static void openQueue() { + openQueue("", false); + } + + private static void openQueue(String currentVideoId, boolean openVideo) { + String currentPlaylistId = playlistId; + if (currentPlaylistId.isEmpty()) { + handleCheckError(checkFailedQueue); + return; + } + if (openVideo) { + if (StringUtils.isEmpty(currentVideoId)) { + handleCheckError(checkFailedVideoId); + return; + } + // Open a video from a playlist + openPlaylist(currentPlaylistId, currentVideoId); + } else { + // Open a playlist + openPlaylist(currentPlaylistId); + } + } + + private static void handleCheckError(String reason) { + showToast(String.format(checkFailedGeneric, reason)); + } + + private static void showToast(String reason) { + Utils.showToastShort(reason); + } + + private enum QueueManager { + ADD_TO_QUEUE( + "revanced_queue_manager_add_to_queue", + "yt_outline_list_add_black_24", + () -> fetchQueue(false, false, false) + ), + ADD_TO_QUEUE_AND_OPEN_QUEUE( + "revanced_queue_manager_add_to_queue_and_open_queue", + "yt_outline_list_add_black_24", + () -> fetchQueue(false, true, false) + ), + ADD_TO_QUEUE_AND_PLAY_VIDEO( + "revanced_queue_manager_add_to_queue_and_play_video", + "yt_outline_list_play_arrow_black_24", + () -> fetchQueue(false, true, true) + ), + REMOVE_FROM_QUEUE( + "revanced_queue_manager_remove_from_queue", + "yt_outline_trash_can_black_24", + () -> fetchQueue(true, false, false) + ), + REMOVE_FROM_QUEUE_AND_OPEN_QUEUE( + "revanced_queue_manager_remove_from_queue_and_open_queue", + "yt_outline_trash_can_black_24", + () -> fetchQueue(true, true, false) + ), + OPEN_QUEUE( + "revanced_queue_manager_open_queue", + "yt_outline_list_view_black_24", + PlaylistPatch::openQueue + ), + // For some reason, the 'playlist/delete' endpoint is unavailable. + REMOVE_QUEUE( + "revanced_queue_manager_remove_queue", + "yt_outline_slash_circle_left_black_24", + PlaylistPatch::removeQueue + ), + SAVE_QUEUE( + "revanced_queue_manager_save_queue", + "yt_outline_bookmark_black_24", + PlaylistPatch::saveToPlaylist + ), + EXTERNAL_DOWNLOADER( + "revanced_queue_manager_external_downloader", + "yt_outline_download_black_24", + PlaylistPatch::downloadVideo + ); + + public final int drawableId; + + @NonNull + public final String label; + + @NonNull + public final Runnable onClickAction; + + QueueManager(@NonNull String label, @NonNull String icon, @NonNull Runnable onClickAction) { + this.drawableId = ResourceUtils.getDrawableIdentifier(icon); + this.label = ResourceUtils.getString(label); + this.onClickAction = onClickAction; + } + + public static final QueueManager[] addToQueueEntries = { + ADD_TO_QUEUE, + ADD_TO_QUEUE_AND_OPEN_QUEUE, + ADD_TO_QUEUE_AND_PLAY_VIDEO, + OPEN_QUEUE, + //REMOVE_QUEUE, + EXTERNAL_DOWNLOADER, + SAVE_QUEUE, + }; + + public static final QueueManager[] removeFromQueueEntries = { + REMOVE_FROM_QUEUE, + REMOVE_FROM_QUEUE_AND_OPEN_QUEUE, + OPEN_QUEUE, + //REMOVE_QUEUE, + EXTERNAL_DOWNLOADER, + SAVE_QUEUE, + }; + + public static final QueueManager[] noVideoIdQueueEntries = { + OPEN_QUEUE, + //REMOVE_QUEUE, + SAVE_QUEUE, + }; + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/CreatePlaylistRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/CreatePlaylistRequest.kt new file mode 100644 index 000000000..6496c77c2 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/CreatePlaylistRequest.kt @@ -0,0 +1,281 @@ +package app.revanced.extension.youtube.patches.utils.requests + +import androidx.annotation.GuardedBy +import app.revanced.extension.shared.innertube.client.YouTubeAppClient +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createApplicationRequestBody +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createPlaylistRequestBody +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute +import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.CREATE_PLAYLIST +import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_SET_VIDEO_ID +import app.revanced.extension.shared.requests.Requester +import app.revanced.extension.shared.utils.Logger +import app.revanced.extension.shared.utils.Utils +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 CreatePlaylistRequest private constructor( + private val videoId: String, + private val requestHeader: Map, + private val dataSyncId: String, +) { + private val future: Future> = Utils.submitOnBackgroundThread { + fetch( + videoId, + requestHeader, + dataSyncId, + ) + } + + val playlistId: Pair? + get() { + try { + return future[MAX_MILLISECONDS_TO_WAIT_FOR_FETCH.toLong(), TimeUnit.MILLISECONDS] + } catch (ex: TimeoutException) { + Logger.printInfo( + { "getPlaylistId timed out" }, + ex + ) + } catch (ex: InterruptedException) { + Logger.printException( + { "getPlaylistId interrupted" }, + ex + ) + Thread.currentThread().interrupt() // Restore interrupt status flag. + } catch (ex: ExecutionException) { + Logger.printException( + { "getPlaylistId failure" }, + ex + ) + } + + return null + } + + companion object { + 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 clear() { + synchronized(cache) { + cache.clear() + } + } + + @JvmStatic + fun fetchRequestIfNeeded( + videoId: String, + requestHeader: Map, + dataSyncId: String, + ) { + Objects.requireNonNull(videoId) + synchronized(cache) { + if (!cache.containsKey(videoId)) { + cache[videoId] = CreatePlaylistRequest( + videoId, + requestHeader, + dataSyncId, + ) + } + } + } + + @JvmStatic + fun getRequestForVideoId(videoId: String): CreatePlaylistRequest? { + synchronized(cache) { + return cache[videoId] + } + } + + private fun handleConnectionError(toastMessage: String, ex: Exception?) { + Logger.printInfo({ toastMessage }, ex) + } + + private fun sendCreatePlaylistRequest( + videoId: String, + requestHeader: Map, + dataSyncId: String, + ): JSONObject? { + Objects.requireNonNull(videoId) + + val startTime = System.currentTimeMillis() + // 'playlist/create' endpoint does not require PoToken. + val clientType = YouTubeAppClient.ClientType.ANDROID + val clientTypeName = clientType.name + Logger.printDebug { "Fetching create playlist request for: $videoId, using client: $clientTypeName" } + + try { + val connection = getInnerTubeResponseConnectionFromRoute( + CREATE_PLAYLIST, + clientType, + requestHeader, + dataSyncId, + ) + + val requestBody = createPlaylistRequestBody(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({ "sendCreatePlaylistRequest failed" }, ex) + } finally { + Logger.printDebug { "video: " + videoId + " took: " + (System.currentTimeMillis() - startTime) + "ms" } + } + + return null + } + + private fun sendSetVideoIdRequest( + videoId: String, + playlistId: String, + requestHeader: Map, + dataSyncId: String, + ): JSONObject? { + Objects.requireNonNull(playlistId) + + val startTime = System.currentTimeMillis() + // 'playlist/create' endpoint does not require PoToken. + val clientType = YouTubeAppClient.ClientType.ANDROID + val clientTypeName = clientType.name + Logger.printDebug { "Fetching set video id request for: $playlistId, using client: $clientTypeName" } + + try { + val connection = getInnerTubeResponseConnectionFromRoute( + GET_SET_VIDEO_ID, + clientType, + requestHeader, + dataSyncId, + ) + + val requestBody = createApplicationRequestBody( + clientType = clientType, + videoId = videoId, + playlistId = playlistId + ) + + 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({ "sendSetVideoIdRequest failed" }, ex) + } finally { + Logger.printDebug { "playlist: " + playlistId + " took: " + (System.currentTimeMillis() - startTime) + "ms" } + } + + return null + } + + private fun parseCreatePlaylistResponse(json: JSONObject): String? { + try { + return json.getString("playlistId") + } catch (e: JSONException) { + val jsonForMessage = json.toString() + Logger.printException( + { "Fetch failed while processing response data for response: $jsonForMessage" }, + e + ) + } + + return null + } + + private fun parseSetVideoIdResponse(json: JSONObject): String? { + try { + val secondaryContentsJsonObject = + json.getJSONObject("contents") + .getJSONObject("singleColumnWatchNextResults") + .getJSONObject("playlist") + .getJSONObject("playlist") + .getJSONArray("contents") + .get(0) + + if (secondaryContentsJsonObject is JSONObject) { + return secondaryContentsJsonObject + .getJSONObject("playlistPanelVideoRenderer") + .getString("playlistSetVideoId") + } + } catch (e: JSONException) { + val jsonForMessage = json.toString() + Logger.printException( + { "Fetch failed while processing response data for response: $jsonForMessage" }, + e + ) + } + + return null + } + + private fun fetch( + videoId: String, + requestHeader: Map, + dataSyncId: String, + ): Pair? { + val createPlaylistJson = sendCreatePlaylistRequest( + videoId, + requestHeader, + dataSyncId + ) + if (createPlaylistJson != null) { + val playlistId = parseCreatePlaylistResponse(createPlaylistJson) + if (playlistId != null) { + val setVideoIdJson = sendSetVideoIdRequest( + videoId, + playlistId, + requestHeader, + dataSyncId + ) + if (setVideoIdJson != null) { + val setVideoId = parseSetVideoIdResponse(setVideoIdJson) + if (setVideoId != null) { + return Pair(playlistId, setVideoId) + } + } + } + } + + return null + } + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/DeletePlaylistRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/DeletePlaylistRequest.kt new file mode 100644 index 000000000..ea7ca60b2 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/DeletePlaylistRequest.kt @@ -0,0 +1,187 @@ +package app.revanced.extension.youtube.patches.utils.requests + +import androidx.annotation.GuardedBy +import app.revanced.extension.shared.innertube.client.YouTubeAppClient +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.deletePlaylistRequestBody +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute +import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.DELETE_PLAYLIST +import app.revanced.extension.shared.requests.Requester +import app.revanced.extension.shared.utils.Logger +import app.revanced.extension.shared.utils.Utils +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 DeletePlaylistRequest private constructor( + private val playlistId: String, + private val requestHeader: Map, + private val dataSyncId: String, +) { + private val future: Future = Utils.submitOnBackgroundThread { + fetch( + playlistId, + requestHeader, + dataSyncId, + ) + } + + val result: Boolean? + get() { + try { + return future[MAX_MILLISECONDS_TO_WAIT_FOR_FETCH.toLong(), TimeUnit.MILLISECONDS] + } catch (ex: TimeoutException) { + Logger.printInfo( + { "getResult timed out" }, + ex + ) + } catch (ex: InterruptedException) { + Logger.printException( + { "getResult interrupted" }, + ex + ) + Thread.currentThread().interrupt() // Restore interrupt status flag. + } catch (ex: ExecutionException) { + Logger.printException( + { "getResult failure" }, + ex + ) + } + + return null + } + + companion object { + 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 clear() { + synchronized(cache) { + cache.clear() + } + } + + @JvmStatic + fun fetchRequestIfNeeded( + playlistId: String, + requestHeader: Map, + dataSyncId: String, + ) { + Objects.requireNonNull(playlistId) + synchronized(cache) { + if (!cache.containsKey(playlistId)) { + cache[playlistId] = DeletePlaylistRequest( + playlistId, + requestHeader, + dataSyncId, + ) + } + } + } + + @JvmStatic + fun getRequestForPlaylistId(playlistId: String): DeletePlaylistRequest? { + synchronized(cache) { + return cache[playlistId] + } + } + + private fun handleConnectionError(toastMessage: String, ex: Exception?) { + Logger.printInfo({ toastMessage }, ex) + } + + private fun sendRequest( + playlistId: String, + requestHeader: Map, + dataSyncId: String, + ): JSONObject? { + Objects.requireNonNull(playlistId) + + val startTime = System.currentTimeMillis() + // 'playlist/delete' endpoint does not require PoToken. + val clientType = YouTubeAppClient.ClientType.ANDROID + val clientTypeName = clientType.name + Logger.printDebug { "Fetching delete playlist request, playlistId: $playlistId, using client: $clientTypeName" } + + try { + val connection = getInnerTubeResponseConnectionFromRoute( + DELETE_PLAYLIST, + clientType, + requestHeader, + dataSyncId + ) + + val requestBody = deletePlaylistRequestBody(playlistId) + + 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({ "sendRequest failed" }, ex) + } finally { + Logger.printDebug { "playlist: " + playlistId + " took: " + (System.currentTimeMillis() - startTime) + "ms" } + } + + return null + } + + private fun parseResponse(json: JSONObject): Boolean? { + try { + return json.has("command") + } catch (e: JSONException) { + val jsonForMessage = json.toString() + Logger.printException( + { "Fetch failed while processing response data for response: $jsonForMessage" }, + e + ) + } + + return null + } + + private fun fetch( + playlistId: String, + requestHeader: Map, + dataSyncId: String, + ): Boolean? { + val json = sendRequest( + playlistId, + requestHeader, + dataSyncId, + ) + if (json != null) { + return parseResponse(json) + } + + return null + } + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/EditPlaylistRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/EditPlaylistRequest.kt new file mode 100644 index 000000000..a76277543 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/EditPlaylistRequest.kt @@ -0,0 +1,225 @@ +package app.revanced.extension.youtube.patches.utils.requests + +import androidx.annotation.GuardedBy +import app.revanced.extension.shared.innertube.client.YouTubeAppClient +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.editPlaylistRequestBody +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute +import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.EDIT_PLAYLIST +import app.revanced.extension.shared.requests.Requester +import app.revanced.extension.shared.utils.Logger +import app.revanced.extension.shared.utils.Utils +import org.apache.commons.lang3.StringUtils +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 EditPlaylistRequest private constructor( + private val videoId: String, + private val playlistId: String, + private val setVideoId: String?, + private val requestHeader: Map, + private val dataSyncId: String, +) { + private val future: Future = Utils.submitOnBackgroundThread { + fetch( + videoId, + playlistId, + setVideoId, + requestHeader, + dataSyncId, + ) + } + + val result: String? + get() { + try { + return future[MAX_MILLISECONDS_TO_WAIT_FOR_FETCH.toLong(), TimeUnit.MILLISECONDS] + } catch (ex: TimeoutException) { + Logger.printInfo( + { "getResult timed out" }, + ex + ) + } catch (ex: InterruptedException) { + Logger.printException( + { "getResult interrupted" }, + ex + ) + Thread.currentThread().interrupt() // Restore interrupt status flag. + } catch (ex: ExecutionException) { + Logger.printException( + { "getResult failure" }, + ex + ) + } + + return null + } + + companion object { + 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 clear() { + synchronized(cache) { + cache.clear() + } + } + + @JvmStatic + fun clearVideoId(videoId: String) { + synchronized(cache) { + cache.remove(videoId) + } + } + + @JvmStatic + fun fetchRequestIfNeeded( + videoId: String, + playlistId: String, + setVideoId: String?, + requestHeader: Map, + dataSyncId: String, + ) { + Objects.requireNonNull(videoId) + synchronized(cache) { + if (!cache.containsKey(videoId)) { + cache[videoId] = EditPlaylistRequest( + videoId, + playlistId, + setVideoId, + requestHeader, + dataSyncId, + ) + } + } + } + + @JvmStatic + fun getRequestForVideoId(videoId: String): EditPlaylistRequest? { + synchronized(cache) { + return cache[videoId] + } + } + + private fun handleConnectionError(toastMessage: String, ex: Exception?) { + Logger.printInfo({ toastMessage }, ex) + } + + private fun sendRequest( + videoId: String, + playlistId: String, + setVideoId: String?, + requestHeader: Map, + dataSyncId: String, + ): JSONObject? { + Objects.requireNonNull(videoId) + + val startTime = System.currentTimeMillis() + // 'browse/edit_playlist' endpoint does not require PoToken. + val clientType = YouTubeAppClient.ClientType.ANDROID + val clientTypeName = clientType.name + Logger.printDebug { "Fetching edit playlist request, videoId: $videoId, playlistId: $playlistId, setVideoId: $setVideoId, using client: $clientTypeName" } + + try { + val connection = getInnerTubeResponseConnectionFromRoute( + EDIT_PLAYLIST, + clientType, + requestHeader, + dataSyncId + ) + + val requestBody = editPlaylistRequestBody( + videoId = videoId, + playlistId = playlistId, + setVideoId = setVideoId + ) + + 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({ "sendRequest failed" }, ex) + } finally { + Logger.printDebug { "video: " + videoId + " took: " + (System.currentTimeMillis() - startTime) + "ms" } + } + + return null + } + + private fun parseResponse(json: JSONObject, remove: Boolean): String? { + try { + if (json.getString("status") == "STATUS_SUCCEEDED") { + if (remove) { + return "" + } + val playlistEditResultsJSONObject = + json.getJSONArray("playlistEditResults").get(0) + + if (playlistEditResultsJSONObject is JSONObject) { + return playlistEditResultsJSONObject + .getJSONObject("playlistEditVideoAddedResultData") + .getString("setVideoId") + } + } + } catch (e: JSONException) { + val jsonForMessage = json.toString() + Logger.printException( + { "Fetch failed while processing response data for response: $jsonForMessage" }, + e + ) + } + + return null + } + + private fun fetch( + videoId: String, + playlistId: String, + setVideoId: String?, + requestHeader: Map, + dataSyncId: String, + ): String? { + val json = sendRequest( + videoId, + playlistId, + setVideoId, + requestHeader, + dataSyncId, + ) + if (json != null) { + return parseResponse(json, StringUtils.isNotEmpty(setVideoId)) + } + + return null + } + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/GetPlaylistsRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/GetPlaylistsRequest.kt new file mode 100644 index 000000000..94361f9ef --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/GetPlaylistsRequest.kt @@ -0,0 +1,222 @@ +package app.revanced.extension.youtube.patches.utils.requests + +import androidx.annotation.GuardedBy +import app.revanced.extension.shared.innertube.client.YouTubeAppClient +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getPlaylistsRequestBody +import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_PLAYLISTS +import app.revanced.extension.shared.requests.Requester +import app.revanced.extension.shared.utils.Logger +import app.revanced.extension.shared.utils.Utils +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 GetPlaylistsRequest private constructor( + private val playlistId: String, + private val requestHeader: Map, + private val dataSyncId: String, +) { + private val future: Future>> = Utils.submitOnBackgroundThread { + fetch( + playlistId, + requestHeader, + dataSyncId, + ) + } + + val playlists: Array>? + get() { + try { + return future[MAX_MILLISECONDS_TO_WAIT_FOR_FETCH.toLong(), TimeUnit.MILLISECONDS] + } catch (ex: TimeoutException) { + Logger.printInfo( + { "getPlaylists timed out" }, + ex + ) + } catch (ex: InterruptedException) { + Logger.printException( + { "getPlaylists interrupted" }, + ex + ) + Thread.currentThread().interrupt() // Restore interrupt status flag. + } catch (ex: ExecutionException) { + Logger.printException( + { "getPlaylists failure" }, + ex + ) + } + + return null + } + + companion object { + 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 clear() { + synchronized(cache) { + cache.clear() + } + } + + @JvmStatic + fun fetchRequestIfNeeded( + playlistId: String, + requestHeader: Map, + dataSyncId: String, + ) { + Objects.requireNonNull(playlistId) + synchronized(cache) { + if (!cache.containsKey(playlistId)) { + cache[playlistId] = GetPlaylistsRequest( + playlistId, + requestHeader, + dataSyncId, + ) + } + } + } + + @JvmStatic + fun getRequestForPlaylistId(playlistId: String): GetPlaylistsRequest? { + synchronized(cache) { + return cache[playlistId] + } + } + + private fun handleConnectionError(toastMessage: String, ex: Exception?) { + Logger.printInfo({ toastMessage }, ex) + } + + private fun sendRequest( + playlistId: String, + requestHeader: Map, + dataSyncId: String, + ): JSONObject? { + Objects.requireNonNull(playlistId) + + val startTime = System.currentTimeMillis() + // 'playlist/get_add_to_playlist' endpoint does not require PoToken. + val clientType = YouTubeAppClient.ClientType.ANDROID + val clientTypeName = clientType.name + Logger.printDebug { "Fetching get playlists request, playlistId: $playlistId, using client: $clientTypeName" } + + try { + val connection = getInnerTubeResponseConnectionFromRoute( + GET_PLAYLISTS, + clientType, + requestHeader, + dataSyncId + ) + + val requestBody = getPlaylistsRequestBody(playlistId) + + 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({ "sendRequest failed" }, ex) + } finally { + Logger.printDebug { "playlist: " + playlistId + " took: " + (System.currentTimeMillis() - startTime) + "ms" } + } + + return null + } + + private fun parseResponse(json: JSONObject): Array>? { + try { + val addToPlaylistRendererJsonObject = + json.getJSONArray("contents").get(0) + + if (addToPlaylistRendererJsonObject is JSONObject) { + val playlistsJsonArray = + addToPlaylistRendererJsonObject + .getJSONObject("addToPlaylistRenderer") + .getJSONArray("playlists") + + val playlistsLength = playlistsJsonArray.length() + val playlists: Array?> = + arrayOfNulls(playlistsLength) + + for (i in 0..playlistsLength - 1) { + val elementsJsonObject = + playlistsJsonArray.get(i) + + if (elementsJsonObject is JSONObject) { + val playlistAddToOptionRendererJSONObject = + elementsJsonObject + .getJSONObject("playlistAddToOptionRenderer") + + val playlistId = playlistAddToOptionRendererJSONObject + .getString("playlistId") + val playlistTitle = + (playlistAddToOptionRendererJSONObject + .getJSONObject("title") + .getJSONArray("runs") + .get(0) as JSONObject) + .getString("text") + + playlists[i] = Pair(playlistId, playlistTitle) + } + } + + val finalPlaylists = playlists.filterNotNull().toTypedArray() + if (finalPlaylists.isNotEmpty()) { + return finalPlaylists + } + } + } catch (e: JSONException) { + val jsonForMessage = json.toString() + Logger.printException( + { "Fetch failed while processing response data for response: $jsonForMessage" }, + e + ) + } + + return null + } + + private fun fetch( + playlistId: String, + requestHeader: Map, + dataSyncId: String, + ): Array>? { + val json = sendRequest(playlistId, requestHeader, dataSyncId) + if (json != null) { + return parseResponse(json) + } + + return null + } + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/SavePlaylistRequest.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/SavePlaylistRequest.kt new file mode 100644 index 000000000..221dfc737 --- /dev/null +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/requests/SavePlaylistRequest.kt @@ -0,0 +1,193 @@ +package app.revanced.extension.youtube.patches.utils.requests + +import androidx.annotation.GuardedBy +import app.revanced.extension.shared.innertube.client.YouTubeAppClient +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.savePlaylistRequestBody +import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.EDIT_PLAYLIST +import app.revanced.extension.shared.requests.Requester +import app.revanced.extension.shared.utils.Logger +import app.revanced.extension.shared.utils.Utils +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 SavePlaylistRequest private constructor( + private val playlistId: String, + private val libraryId: String, + private val requestHeader: Map, + private val dataSyncId: String, +) { + private val future: Future = Utils.submitOnBackgroundThread { + fetch( + playlistId, + libraryId, + requestHeader, + dataSyncId, + ) + } + + val result: Boolean? + get() { + try { + return future[MAX_MILLISECONDS_TO_WAIT_FOR_FETCH.toLong(), TimeUnit.MILLISECONDS] + } catch (ex: TimeoutException) { + Logger.printInfo( + { "getResult timed out" }, + ex + ) + } catch (ex: InterruptedException) { + Logger.printException( + { "getResult interrupted" }, + ex + ) + Thread.currentThread().interrupt() // Restore interrupt status flag. + } catch (ex: ExecutionException) { + Logger.printException( + { "getResult failure" }, + ex + ) + } + + return null + } + + companion object { + 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 clear() { + synchronized(cache) { + cache.clear() + } + } + + @JvmStatic + fun fetchRequestIfNeeded( + playlistId: String, + libraryId: String, + requestHeader: Map, + dataSyncId: String, + ) { + Objects.requireNonNull(playlistId) + synchronized(cache) { + cache[libraryId] = SavePlaylistRequest( + playlistId, + libraryId, + requestHeader, + dataSyncId, + ) + } + } + + @JvmStatic + fun getRequestForLibraryId(libraryId: String): SavePlaylistRequest? { + synchronized(cache) { + return cache[libraryId] + } + } + + private fun handleConnectionError(toastMessage: String, ex: Exception?) { + Logger.printInfo({ toastMessage }, ex) + } + + private fun sendRequest( + playlistId: String, + libraryId: String, + requestHeader: Map, + dataSyncId: String, + ): JSONObject? { + Objects.requireNonNull(playlistId) + Objects.requireNonNull(libraryId) + + val startTime = System.currentTimeMillis() + // 'browse/edit_playlist' endpoint does not require PoToken. + val clientType = YouTubeAppClient.ClientType.ANDROID + val clientTypeName = clientType.name + Logger.printDebug { "Fetching edit playlist request, playlistId: $playlistId, libraryId: $libraryId, using client: $clientTypeName" } + + try { + val connection = getInnerTubeResponseConnectionFromRoute( + EDIT_PLAYLIST, + clientType, + requestHeader, + dataSyncId + ) + + val requestBody = savePlaylistRequestBody(libraryId, playlistId) + + 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({ "sendRequest failed" }, ex) + } finally { + Logger.printDebug { "playlistId: $playlistId libraryId: $libraryId took: ${(System.currentTimeMillis() - startTime)}ms" } + } + + return null + } + + private fun parseResponse(json: JSONObject): Boolean? { + try { + return json.getString("status") == "STATUS_SUCCEEDED" + } catch (e: JSONException) { + val jsonForMessage = json.toString() + Logger.printException( + { "Fetch failed while processing response data for response: $jsonForMessage" }, + e + ) + } + + return null + } + + private fun fetch( + playlistId: String, + libraryId: String, + requestHeader: Map, + dataSyncId: String, + ): Boolean? { + val json = sendRequest( + playlistId, + libraryId, + requestHeader, + dataSyncId, + ) + if (json != null) { + return parseResponse(json) + } + + return null + } + } +} diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/AV1CodecPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/AV1CodecPatch.java index 71f5dd9d6..8cf4980d1 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/AV1CodecPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/AV1CodecPatch.java @@ -1,17 +1,10 @@ package app.revanced.extension.youtube.patches.video; -import static app.revanced.extension.shared.utils.StringRef.str; - -import app.revanced.extension.shared.utils.Logger; -import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.youtube.settings.Settings; @SuppressWarnings("unused") public class AV1CodecPatch { - private static final int LITERAL_VALUE_AV01 = 1635135811; - private static final int LITERAL_VALUE_DOLBY_VISION = 1685485123; private static final String VP9_CODEC = "video/x-vnd.on2.vp9"; - private static long lastTimeResponse = 0; /** * Replace the SW AV01 codec to VP9 codec. @@ -22,32 +15,4 @@ public class AV1CodecPatch { public static String replaceCodec(String original) { return Settings.REPLACE_AV1_CODEC.get() ? VP9_CODEC : original; } - - /** - * Replace the SW AV01 codec request with a Dolby Vision codec request. - * This request is invalid, so it falls back to codecs other than AV01. - *

- * Limitation: Fallback process causes about 15-20 seconds of buffering. - * - * @param literalValue literal value of the codec - */ - public static int rejectResponse(int literalValue) { - if (!Settings.REJECT_AV1_CODEC.get()) - return literalValue; - - Logger.printDebug(() -> "Response: " + literalValue); - - if (literalValue != LITERAL_VALUE_AV01) - return literalValue; - - final long currentTime = System.currentTimeMillis(); - - // Ignore the invoke within 20 seconds. - if (currentTime - lastTimeResponse > 20000) { - lastTimeResponse = currentTime; - Utils.showToastShort(str("revanced_reject_av1_codec_toast")); - } - - return LITERAL_VALUE_DOLBY_VISION; - } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/CustomPlaybackSpeedPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/CustomPlaybackSpeedPatch.java index cca3c4ff4..83e343397 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/CustomPlaybackSpeedPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/CustomPlaybackSpeedPatch.java @@ -75,30 +75,30 @@ public class CustomPlaybackSpeedPatch { return isCustomPlaybackSpeedEnabled() ? 0 : original; } - public static String[] getListEntries() { + public static String[] getEntries() { return isCustomPlaybackSpeedEnabled() ? customSpeedEntries : defaultSpeedEntries; } - public static String[] getListEntryValues() { + public static String[] getEntryValues() { return isCustomPlaybackSpeedEnabled() ? customSpeedEntryValues : defaultSpeedEntryValues; } - public static String[] getTrimmedListEntries() { + public static String[] getTrimmedEntries() { if (playbackSpeedEntries == null) { - final String[] playbackSpeedWithAutoEntries = getListEntries(); + final String[] playbackSpeedWithAutoEntries = getEntries(); playbackSpeedEntries = Arrays.copyOfRange(playbackSpeedWithAutoEntries, 1, playbackSpeedWithAutoEntries.length); } return playbackSpeedEntries; } - public static String[] getTrimmedListEntryValues() { + public static String[] getTrimmedEntryValues() { if (playbackSpeedEntryValues == null) { - final String[] playbackSpeedWithAutoEntryValues = getListEntryValues(); + final String[] playbackSpeedWithAutoEntryValues = getEntryValues(); playbackSpeedEntryValues = Arrays.copyOfRange(playbackSpeedWithAutoEntryValues, 1, playbackSpeedWithAutoEntryValues.length); } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java index 5211c6a47..58897b35d 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/PlaybackSpeedPatch.java @@ -1,12 +1,14 @@ package app.revanced.extension.youtube.patches.video; import static app.revanced.extension.shared.utils.StringRef.str; +import static app.revanced.extension.youtube.shared.RootView.isShortsActive; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import org.apache.commons.lang3.BooleanUtils; +import app.revanced.extension.shared.settings.BooleanSetting; +import app.revanced.extension.shared.settings.FloatSetting; import app.revanced.extension.shared.utils.Logger; import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.youtube.patches.utils.PatchStatus; @@ -17,25 +19,57 @@ import app.revanced.extension.youtube.whitelist.Whitelist; @SuppressWarnings("unused") public class PlaybackSpeedPatch { + private static final FloatSetting DEFAULT_PLAYBACK_SPEED = + Settings.DEFAULT_PLAYBACK_SPEED; + private static final FloatSetting DEFAULT_PLAYBACK_SPEED_SHORTS = + Settings.DEFAULT_PLAYBACK_SPEED_SHORTS; + private static final boolean DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC = Settings.DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC.get(); private static final long TOAST_DELAY_MILLISECONDS = 750; private static long lastTimeSpeedChanged; + private static float lastSelectedPlaybackSpeed = 1.0f; + + private static volatile String channelId = ""; + private static volatile String videoId = ""; private static boolean isLiveStream; + private static volatile String channelIdShorts = ""; + private static volatile String videoIdShorts = ""; + private static boolean isLiveStreamShorts; + /** * Injection point. */ public static void newVideoStarted(@NonNull String newlyLoadedChannelId, @NonNull String newlyLoadedChannelName, @NonNull String newlyLoadedVideoId, @NonNull String newlyLoadedVideoTitle, final long newlyLoadedVideoLength, boolean newlyLoadedLiveStreamValue) { - isLiveStream = newlyLoadedLiveStreamValue; - Logger.printDebug(() -> "newVideoStarted: " + newlyLoadedVideoId); + if (isShortsActive()) { + channelIdShorts = newlyLoadedChannelId; + videoIdShorts = newlyLoadedVideoId; + isLiveStreamShorts = newlyLoadedLiveStreamValue; - final float defaultPlaybackSpeed = getDefaultPlaybackSpeed(newlyLoadedChannelId, newlyLoadedVideoId); - Logger.printDebug(() -> "overridePlaybackSpeed: " + defaultPlaybackSpeed); + Logger.printDebug(() -> "newVideoStarted: " + newlyLoadedVideoId); + } else { + channelId = newlyLoadedChannelId; + videoId = newlyLoadedVideoId; + isLiveStream = newlyLoadedLiveStreamValue; - VideoInformation.overridePlaybackSpeed(defaultPlaybackSpeed); + Logger.printDebug(() -> "newShortsVideoStarted: " + newlyLoadedVideoId); + } + } + + /** + * Injection point. + */ + public static void newShortsVideoStarted(@NonNull String newlyLoadedChannelId, @NonNull String newlyLoadedChannelName, + @NonNull String newlyLoadedVideoId, @NonNull String newlyLoadedVideoTitle, + final long newlyLoadedVideoLength, boolean newlyLoadedLiveStreamValue) { + channelIdShorts = newlyLoadedChannelId; + videoIdShorts = newlyLoadedVideoId; + isLiveStreamShorts = newlyLoadedLiveStreamValue; + + Logger.printDebug(() -> "newShortsVideoStarted: " + newlyLoadedVideoId); } /** @@ -65,17 +99,34 @@ public class PlaybackSpeedPatch { /** * Injection point. */ - public static float getPlaybackSpeedInShorts(final float playbackSpeed) { - if (VideoInformation.lastPlayerResponseIsShort() && - Settings.ENABLE_DEFAULT_PLAYBACK_SPEED_SHORTS.get() - ) { - float defaultPlaybackSpeed = getDefaultPlaybackSpeed(VideoInformation.getChannelId(), null); - Logger.printDebug(() -> "overridePlaybackSpeed in Shorts: " + defaultPlaybackSpeed); + public static float getPlaybackSpeed(float playbackSpeed) { + boolean isShorts = isShortsActive(); + String currentChannelId = isShorts ? channelIdShorts : channelId; + String currentVideoId = isShorts ? videoIdShorts : videoId; + boolean currentVideoIsLiveStream = isShorts ? isLiveStreamShorts : isLiveStream; + boolean currentVideoIsWhitelisted = Whitelist.isChannelWhitelistedPlaybackSpeed(currentChannelId); + boolean currentVideoIsMusic = !isShorts && isMusic(); - return defaultPlaybackSpeed; + if (currentVideoIsLiveStream || currentVideoIsWhitelisted || currentVideoIsMusic) { + Logger.printDebug(() -> "changing playback speed to: 1.0"); + VideoInformation.setPlaybackSpeed(1.0f); + return 1.0f; } - return playbackSpeed; + float defaultPlaybackSpeed = isShorts ? DEFAULT_PLAYBACK_SPEED_SHORTS.get() : DEFAULT_PLAYBACK_SPEED.get(); + + if (defaultPlaybackSpeed < 0) { + float finalPlaybackSpeed = isShorts ? playbackSpeed : lastSelectedPlaybackSpeed; + VideoInformation.overridePlaybackSpeed(finalPlaybackSpeed); + Logger.printDebug(() -> "changing playback speed to: " + finalPlaybackSpeed); + return finalPlaybackSpeed; + } else { + if (isShorts) { + VideoInformation.setPlaybackSpeed(defaultPlaybackSpeed); + } + Logger.printDebug(() -> "changing playback speed to: " + defaultPlaybackSpeed); + return defaultPlaybackSpeed; + } } /** @@ -86,51 +137,57 @@ public class PlaybackSpeedPatch { */ public static void userSelectedPlaybackSpeed(float playbackSpeed) { try { - if (PatchStatus.RememberPlaybackSpeed() && - Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED.get()) { - // With the 0.05x menu, if the speed is set by integrations to higher than 2.0x - // then the menu will allow increasing without bounds but the max speed is - // still capped to under 8.0x. - playbackSpeed = Math.min(playbackSpeed, CustomPlaybackSpeedPatch.PLAYBACK_SPEED_MAXIMUM - 0.05f); + boolean isShorts = isShortsActive(); + if (PatchStatus.RememberPlaybackSpeed()) { + BooleanSetting rememberPlaybackSpeedLastSelectedSetting = isShorts + ? Settings.REMEMBER_PLAYBACK_SPEED_SHORTS_LAST_SELECTED + : Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED; + FloatSetting playbackSpeedSetting = isShorts + ? DEFAULT_PLAYBACK_SPEED_SHORTS + : DEFAULT_PLAYBACK_SPEED; + BooleanSetting showToastSetting = isShorts + ? Settings.REMEMBER_PLAYBACK_SPEED_SHORTS_LAST_SELECTED_TOAST + : Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST; - // Prevent toast spamming if using the 0.05x adjustments. - // Show exactly one toast after the user stops interacting with the speed menu. - final long now = System.currentTimeMillis(); - lastTimeSpeedChanged = now; + if (rememberPlaybackSpeedLastSelectedSetting.get()) { + // With the 0.05x menu, if the speed is set by integrations to higher than 2.0x + // then the menu will allow increasing without bounds but the max speed is + // still capped to under 8.0x. + playbackSpeed = Math.min(playbackSpeed, CustomPlaybackSpeedPatch.PLAYBACK_SPEED_MAXIMUM - 0.05f); - final float finalPlaybackSpeed = playbackSpeed; - Utils.runOnMainThreadDelayed(() -> { - if (lastTimeSpeedChanged != now) { - // The user made additional speed adjustments and this call is outdated. - return; - } + // Prevent toast spamming if using the 0.05x adjustments. + // Show exactly one toast after the user stops interacting with the speed menu. + final long now = System.currentTimeMillis(); + lastTimeSpeedChanged = now; - if (Settings.DEFAULT_PLAYBACK_SPEED.get() == finalPlaybackSpeed) { - // User changed to a different speed and immediately changed back. - // Or the user is going past 8.0x in the glitched out 0.05x menu. - return; - } - Settings.DEFAULT_PLAYBACK_SPEED.save(finalPlaybackSpeed); + final float finalPlaybackSpeed = playbackSpeed; + Utils.runOnMainThreadDelayed(() -> { + if (lastTimeSpeedChanged != now) { + // The user made additional speed adjustments and this call is outdated. + return; + } + if (playbackSpeedSetting.get() == finalPlaybackSpeed) { + // User changed to a different speed and immediately changed back. + // Or the user is going past 8.0x in the glitched out 0.05x menu. + return; + } + playbackSpeedSetting.save(finalPlaybackSpeed); - if (!Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST.get()) { - return; - } - Utils.showToastShort(str("revanced_remember_playback_speed_toast", (finalPlaybackSpeed + "x"))); - }, TOAST_DELAY_MILLISECONDS); + if (showToastSetting.get()) { + Utils.showToastShort(str(isShorts ? "revanced_remember_playback_speed_toast_shorts" : "revanced_remember_playback_speed_toast", (finalPlaybackSpeed + "x"))); + } + }, TOAST_DELAY_MILLISECONDS); + } + } else if (!isShorts) { + lastSelectedPlaybackSpeed = playbackSpeed; } } catch (Exception ex) { Logger.printException(() -> "userSelectedPlaybackSpeed failure", ex); } } - private static float getDefaultPlaybackSpeed(@NonNull String channelId, @Nullable String videoId) { - return (isLiveStream || Whitelist.isChannelWhitelistedPlaybackSpeed(channelId) || isMusic(videoId)) - ? 1.0f - : Settings.DEFAULT_PLAYBACK_SPEED.get(); - } - - private static boolean isMusic(@Nullable String videoId) { - if (DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC && videoId != null) { + private static boolean isMusic() { + if (DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC && !videoId.isEmpty()) { try { MusicRequest request = MusicRequest.getRequestForVideoId(videoId); final boolean isMusic = request != null && BooleanUtils.toBoolean(request.getStream()); diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/VideoQualityPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/VideoQualityPatch.java index 45d84038c..0ce60f14e 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/VideoQualityPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/video/VideoQualityPatch.java @@ -1,6 +1,7 @@ package app.revanced.extension.youtube.patches.video; import static app.revanced.extension.shared.utils.StringRef.str; +import static app.revanced.extension.youtube.shared.RootView.isShortsActive; import androidx.annotation.NonNull; @@ -14,8 +15,10 @@ import app.revanced.extension.youtube.shared.VideoInformation; @SuppressWarnings("unused") public class VideoQualityPatch { private static final int DEFAULT_YOUTUBE_VIDEO_QUALITY = -2; - private static final IntegerSetting mobileQualitySetting = Settings.DEFAULT_VIDEO_QUALITY_MOBILE; - private static final IntegerSetting wifiQualitySetting = Settings.DEFAULT_VIDEO_QUALITY_WIFI; + private static final IntegerSetting shortsQualityMobile = Settings.DEFAULT_VIDEO_QUALITY_MOBILE_SHORTS; + private static final IntegerSetting shortsQualityWifi = Settings.DEFAULT_VIDEO_QUALITY_WIFI_SHORTS; + private static final IntegerSetting videoQualityMobile = Settings.DEFAULT_VIDEO_QUALITY_MOBILE; + private static final IntegerSetting videoQualityWifi = Settings.DEFAULT_VIDEO_QUALITY_WIFI; @NonNull public static String videoId = ""; @@ -35,12 +38,11 @@ public class VideoQualityPatch { public static void newVideoStarted(@NonNull String newlyLoadedChannelId, @NonNull String newlyLoadedChannelName, @NonNull String newlyLoadedVideoId, @NonNull String newlyLoadedVideoTitle, final long newlyLoadedVideoLength, boolean newlyLoadedLiveStreamValue) { - if (PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL) - return; - if (videoId.equals(newlyLoadedVideoId)) - return; - videoId = newlyLoadedVideoId; - setVideoQuality(Settings.SKIP_PRELOADED_BUFFER.get() ? 250 : 750); + if (PlayerType.getCurrent() != PlayerType.INLINE_MINIMAL && + !videoId.equals(newlyLoadedVideoId)) { + videoId = newlyLoadedVideoId; + setVideoQuality(750); + } } /** @@ -53,42 +55,62 @@ public class VideoQualityPatch { ); } - private static void setVideoQuality(final long delayMillis) { - final int defaultQuality = Utils.getNetworkType() == Utils.NetworkType.MOBILE - ? mobileQualitySetting.get() - : wifiQualitySetting.get(); + private static void setVideoQuality(long delayMillis) { + boolean isShorts = isShortsActive(); + IntegerSetting defaultQualitySetting = Utils.getNetworkType() == Utils.NetworkType.MOBILE + ? isShorts ? shortsQualityMobile : videoQualityMobile + : isShorts ? shortsQualityWifi : videoQualityWifi; - if (defaultQuality == DEFAULT_YOUTUBE_VIDEO_QUALITY) - return; + int defaultQuality = defaultQualitySetting.get(); - Utils.runOnMainThreadDelayed(() -> { - final int qualityToUseFinal = VideoInformation.getAvailableVideoQuality(defaultQuality); - Logger.printDebug(() -> "Changing video quality to: " + qualityToUseFinal); - VideoInformation.overrideVideoQuality(qualityToUseFinal); - }, delayMillis - ); + if (defaultQuality != DEFAULT_YOUTUBE_VIDEO_QUALITY) { + Utils.runOnMainThreadDelayed(() -> { + final int qualityToUseFinal = VideoInformation.getAvailableVideoQuality(defaultQuality); + Logger.printDebug(() -> "Changing video quality to: " + qualityToUseFinal); + VideoInformation.overrideVideoQuality(qualityToUseFinal); + }, delayMillis + ); + } } private static void userSelectedVideoQuality(final int defaultQuality) { - if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.get()) - return; - if (defaultQuality == DEFAULT_YOUTUBE_VIDEO_QUALITY) - return; + if (defaultQuality != DEFAULT_YOUTUBE_VIDEO_QUALITY) { + final Utils.NetworkType networkType = Utils.getNetworkType(); + String networkTypeMessage = networkType == Utils.NetworkType.MOBILE + ? str("revanced_remember_video_quality_mobile") + : str("revanced_remember_video_quality_wifi"); - final Utils.NetworkType networkType = Utils.getNetworkType(); + if (isShortsActive()) { + if (Settings.REMEMBER_VIDEO_QUALITY_SHORTS_LAST_SELECTED.get()) { + IntegerSetting defaultQualitySetting = networkType == Utils.NetworkType.MOBILE + ? shortsQualityMobile + : shortsQualityWifi; - switch (networkType) { - case NONE -> { - Utils.showToastShort(str("revanced_remember_video_quality_none")); - return; + defaultQualitySetting.save(defaultQuality); + + if (Settings.REMEMBER_VIDEO_QUALITY_SHORTS_LAST_SELECTED_TOAST.get()) { + Utils.showToastShort(str( + "revanced_remember_video_quality_toast_shorts", + networkTypeMessage, (defaultQuality + "p") + )); + } + } + } else { + if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.get()) { + IntegerSetting defaultQualitySetting = networkType == Utils.NetworkType.MOBILE + ? videoQualityMobile + : videoQualityWifi; + + defaultQualitySetting.save(defaultQuality); + + if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) { + Utils.showToastShort(str( + "revanced_remember_video_quality_toast", + networkTypeMessage, (defaultQuality + "p") + )); + } + } } - case MOBILE -> mobileQualitySetting.save(defaultQuality); - default -> wifiQualitySetting.save(defaultQuality); } - - if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) - return; - - Utils.showToastShort(str("revanced_remember_video_quality_" + networkType.getName(), defaultQuality + "p")); } } \ No newline at end of file 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 d48ca8b51..a5283ad3d 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,9 +2,13 @@ package app.revanced.extension.youtube.patches.video.requests import android.annotation.SuppressLint import androidx.annotation.GuardedBy -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.innertube.client.YouTubeAppClient +import app.revanced.extension.shared.innertube.client.YouTubeWebClient +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createApplicationRequestBody +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.createWebInnertubeBody +import app.revanced.extension.shared.innertube.requests.InnerTubeRequestBody.getInnerTubeResponseConnectionFromRoute +import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_CATEGORY +import app.revanced.extension.shared.innertube.requests.InnerTubeRoutes.GET_PLAYLIST_PAGE import app.revanced.extension.shared.requests.Requester import app.revanced.extension.shared.utils.Logger import app.revanced.extension.shared.utils.Utils @@ -124,12 +128,12 @@ class MusicRequest private constructor( Logger.printDebug { "Fetching playlist request for: $videoId, using client: $clientTypeName" } try { - val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute( - PlayerRoutes.GET_PLAYLIST_PAGE, + val connection = getInnerTubeResponseConnectionFromRoute( + GET_PLAYLIST_PAGE, clientType ) val requestBody = - PlayerRoutes.createApplicationRequestBody( + createApplicationRequestBody( clientType = clientType, videoId = videoId, playlistId = "RD$videoId" @@ -168,12 +172,11 @@ class MusicRequest private constructor( Logger.printDebug { "Fetching microformat request for: $videoId, using client: $clientTypeName" } try { - val connection = PlayerRoutes.getPlayerResponseConnectionFromRoute( - PlayerRoutes.GET_CATEGORY, + val connection = getInnerTubeResponseConnectionFromRoute( + GET_CATEGORY, clientType ) - val requestBody = - PlayerRoutes.createWebInnertubeBody(clientType, videoId) + val requestBody = createWebInnertubeBody(clientType, videoId) connection.setFixedLengthStreamingMode(requestBody.size) connection.outputStream.write(requestBody) diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/returnyoutubedislike/ReturnYouTubeDislike.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/returnyoutubedislike/ReturnYouTubeDislike.java index ca5ff0aaa..b3b23057c 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/returnyoutubedislike/ReturnYouTubeDislike.java @@ -115,7 +115,7 @@ public class ReturnYouTubeDislike { private static final Rect middleSeparatorBounds; /** - * Left separator horizontal padding for Rolling Number layout. + * Horizontal padding between the left and middle separator. */ public static final int leftSeparatorShapePaddingPixels; private static final ShapeDrawable leftSeparatorShape; @@ -131,7 +131,7 @@ public class ReturnYouTubeDislike { (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp); middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize); - leftSeparatorShapePaddingPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10.0f, dp); + leftSeparatorShapePaddingPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8.4f, dp); leftSeparatorShape = new ShapeDrawable(new RectShape()); leftSeparatorShape.setBounds(leftSeparatorBounds); 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 6ea0ffe24..1b0bed356 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 @@ -28,6 +28,8 @@ 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.settings.preference.SharedPrefCategory; +import app.revanced.extension.shared.utils.Logger; +import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.youtube.patches.alternativethumbnails.AlternativeThumbnailsPatch.DeArrowAvailability; import app.revanced.extension.youtube.patches.alternativethumbnails.AlternativeThumbnailsPatch.StillImagesAvailability; import app.revanced.extension.youtube.patches.alternativethumbnails.AlternativeThumbnailsPatch.ThumbnailOption; @@ -40,6 +42,7 @@ import app.revanced.extension.youtube.patches.player.ExitFullscreenPatch.Fullscr 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.swipe.SwipeControlsPatch; import app.revanced.extension.youtube.patches.utils.PatchStatus; import app.revanced.extension.youtube.shared.PlaylistIdPrefix; import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings; @@ -148,7 +151,6 @@ public class Settings extends BaseSettings { new ChangeStartPagePatch.ChangeStartPageTypeAvailability()); public static final BooleanSetting DISABLE_AUTO_AUDIO_TRACKS = new BooleanSetting("revanced_disable_auto_audio_tracks", FALSE); public static final BooleanSetting DISABLE_SPLASH_ANIMATION = new BooleanSetting("revanced_disable_splash_animation", PatchStatus.SplashAnimation(), true); - public static final BooleanSetting DISABLE_TRANSLUCENT_STATUS_BAR = new BooleanSetting("revanced_disable_translucent_status_bar", TRUE, true); public static final BooleanSetting ENABLE_GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_enable_gradient_loading_screen", FALSE, true); public static final BooleanSetting HIDE_FLOATING_MICROPHONE = new BooleanSetting("revanced_hide_floating_microphone", TRUE, true); public static final BooleanSetting HIDE_GRAY_SEPARATOR = new BooleanSetting("revanced_hide_gray_separator", TRUE); @@ -156,6 +158,8 @@ public class Settings extends BaseSettings { public static final EnumSetting CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message"); public static final BooleanSetting CHANGE_LIVE_RING_CLICK_ACTION = new BooleanSetting("revanced_change_live_ring_click_action", FALSE, true); + public static final BooleanSetting DISABLE_LAYOUT_UPDATES = new BooleanSetting("revanced_disable_layout_updates", false, true, "revanced_disable_layout_updates_user_dialog_message"); + public static final BooleanSetting DISABLE_TRANSLUCENT_STATUS_BAR = new BooleanSetting("revanced_disable_translucent_status_bar", FALSE, true, "revanced_disable_translucent_status_bar_user_dialog_message"); public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", false, true, "revanced_spoof_app_version_user_dialog_message"); public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", PatchStatus.SpoofAppVersionDefaultString(), true, parent(SPOOF_APP_VERSION)); @@ -179,12 +183,14 @@ 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, "revanced_enable_translucent_navigation_bar_user_dialog_message"); + public static final BooleanSetting ENABLE_TRANSLUCENT_NAVIGATION_BAR = new BooleanSetting("revanced_enable_translucent_navigation_bar", FALSE, true); public static final BooleanSetting HIDE_NAVIGATION_BAR = new BooleanSetting("revanced_hide_navigation_bar", FALSE, true); // PreferenceScreen: General - Override buttons - public static final BooleanSetting OVERRIDE_PLAYLIST_DOWNLOAD_BUTTON = new BooleanSetting("revanced_override_playlist_download_button", FALSE); - public static final BooleanSetting OVERRIDE_VIDEO_DOWNLOAD_BUTTON = new BooleanSetting("revanced_override_video_download_button", FALSE); + public static final BooleanSetting OVERRIDE_PLAYLIST_DOWNLOAD_BUTTON = new BooleanSetting("revanced_override_playlist_download_button", FALSE, true); + public static final BooleanSetting OVERRIDE_VIDEO_DOWNLOAD_BUTTON = new BooleanSetting("revanced_override_video_download_button", FALSE, true); + public static final BooleanSetting OVERRIDE_VIDEO_DOWNLOAD_BUTTON_QUEUE_MANAGER = new BooleanSetting("revanced_override_video_download_button_queue_manager", FALSE, true, + "revanced_queue_manager_user_dialog_message", parent(OVERRIDE_VIDEO_DOWNLOAD_BUTTON)); public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME_PLAYLIST = new StringSetting("revanced_external_downloader_package_name_playlist", "com.deniscerri.ytdl"); public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME_VIDEO = new StringSetting("revanced_external_downloader_package_name_video", "com.deniscerri.ytdl"); public static final BooleanSetting OVERRIDE_YOUTUBE_MUSIC_BUTTON = new BooleanSetting("revanced_override_youtube_music_button", FALSE, true @@ -332,7 +338,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); + public static final BooleanSetting DISABLE_ENGAGEMENT_PANEL = new BooleanSetting("revanced_disable_engagement_panel", FALSE, true); 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)); @@ -394,6 +400,8 @@ public class Settings extends BaseSettings { public static final BooleanSetting OVERLAY_BUTTON_COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_overlay_button_copy_video_url_timestamp", FALSE); public static final BooleanSetting OVERLAY_BUTTON_MUTE_VOLUME = new BooleanSetting("revanced_overlay_button_mute_volume", FALSE); public static final BooleanSetting OVERLAY_BUTTON_EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_overlay_button_external_downloader", FALSE); + public static final BooleanSetting OVERLAY_BUTTON_EXTERNAL_DOWNLOADER_QUEUE_MANAGER = new BooleanSetting("revanced_overlay_button_external_downloader_queue_manager", FALSE, true, + "revanced_queue_manager_user_dialog_message", parent(OVERLAY_BUTTON_EXTERNAL_DOWNLOADER)); public static final BooleanSetting OVERLAY_BUTTON_SPEED_DIALOG = new BooleanSetting("revanced_overlay_button_speed_dialog", FALSE); public static final BooleanSetting OVERLAY_BUTTON_PLAY_ALL = new BooleanSetting("revanced_overlay_button_play_all", FALSE); public static final EnumSetting OVERLAY_BUTTON_PLAY_ALL_TYPE = new EnumSetting<>("revanced_overlay_button_play_all_type", PlaylistIdPrefix.ALL_CONTENTS_WITH_TIME_DESCENDING); @@ -490,12 +498,13 @@ public class Settings extends BaseSettings { public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL = new BooleanSetting("revanced_shorts_custom_actions_copy_video_url", FALSE, true); public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_shorts_custom_actions_external_downloader", FALSE, true); public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_OPEN_VIDEO = new BooleanSetting("revanced_shorts_custom_actions_open_video", FALSE, true); + public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_SPEED_DIALOG = new BooleanSetting("revanced_shorts_custom_actions_speed_dialog", FALSE, true); public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_REPEAT_STATE = new BooleanSetting("revanced_shorts_custom_actions_repeat_state", FALSE, true); public static final BooleanSetting ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU = new BooleanSetting("revanced_enable_shorts_custom_actions_flyout_menu", FALSE, true, - parentsAny(SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL, SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL_TIMESTAMP, SHORTS_CUSTOM_ACTIONS_EXTERNAL_DOWNLOADER, SHORTS_CUSTOM_ACTIONS_OPEN_VIDEO, SHORTS_CUSTOM_ACTIONS_REPEAT_STATE)); + parentsAny(SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL, SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL_TIMESTAMP, SHORTS_CUSTOM_ACTIONS_EXTERNAL_DOWNLOADER, SHORTS_CUSTOM_ACTIONS_OPEN_VIDEO, SHORTS_CUSTOM_ACTIONS_SPEED_DIALOG, SHORTS_CUSTOM_ACTIONS_REPEAT_STATE)); public static final BooleanSetting ENABLE_SHORTS_CUSTOM_ACTIONS_TOOLBAR = new BooleanSetting("revanced_enable_shorts_custom_actions_toolbar", FALSE, true, - parentsAny(SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL, SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL_TIMESTAMP, SHORTS_CUSTOM_ACTIONS_EXTERNAL_DOWNLOADER, SHORTS_CUSTOM_ACTIONS_OPEN_VIDEO, SHORTS_CUSTOM_ACTIONS_REPEAT_STATE)); + parentsAny(SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL, SHORTS_CUSTOM_ACTIONS_COPY_VIDEO_URL_TIMESTAMP, SHORTS_CUSTOM_ACTIONS_EXTERNAL_DOWNLOADER, SHORTS_CUSTOM_ACTIONS_OPEN_VIDEO, SHORTS_CUSTOM_ACTIONS_SPEED_DIALOG, SHORTS_CUSTOM_ACTIONS_REPEAT_STATE)); // Experimental Flags public static final BooleanSetting ENABLE_TIME_STAMP = new BooleanSetting("revanced_enable_shorts_time_stamp", FALSE, true); @@ -515,9 +524,15 @@ public class Settings extends BaseSettings { public static final BooleanSetting ENABLE_SWIPE_PRESS_TO_ENGAGE = new BooleanSetting("revanced_enable_swipe_press_to_engage", FALSE, true, parentsAny(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)); public static final BooleanSetting ENABLE_SWIPE_HAPTIC_FEEDBACK = new BooleanSetting("revanced_enable_swipe_haptic_feedback", TRUE, true, parentsAny(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)); public static final BooleanSetting SWIPE_LOCK_MODE = new BooleanSetting("revanced_swipe_gestures_lock_mode", FALSE, true, parentsAny(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)); + public static final BooleanSetting SWIPE_OVERLAY_ALTERNATIVE_UI = new BooleanSetting("revanced_swipe_overlay_alternative_ui", TRUE, true, parentsAny(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)); + public static final BooleanSetting SWIPE_SHOW_CIRCULAR_OVERLAY = new BooleanSetting("revanced_swipe_show_circular_overlay", FALSE, true, + new SwipeControlsPatch.SwipeOverlayModernUIAvailability()); + public static final BooleanSetting SWIPE_OVERLAY_MINIMAL_STYLE = new BooleanSetting("revanced_swipe_overlay_minimal_style", FALSE, true, + new SwipeControlsPatch.SwipeOverlayModernUIAvailability()); + public static final IntegerSetting SWIPE_OVERLAY_BACKGROUND_OPACITY = new IntegerSetting("revanced_swipe_overlay_background_opacity", 60, true, parentsAny(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)); + public static final IntegerSetting SWIPE_OVERLAY_TEXT_SIZE = new IntegerSetting("revanced_swipe_overlay_text_size", 20, true, + new SwipeControlsPatch.SwipeOverlayTextSizeAvailability()); public static final IntegerSetting SWIPE_MAGNITUDE_THRESHOLD = new IntegerSetting("revanced_swipe_magnitude_threshold", 0, true, parentsAny(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)); - public static final IntegerSetting SWIPE_OVERLAY_BACKGROUND_ALPHA = new IntegerSetting("revanced_swipe_overlay_background_alpha", 127, true, parentsAny(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)); - public static final IntegerSetting SWIPE_OVERLAY_TEXT_SIZE = new IntegerSetting("revanced_swipe_overlay_text_size", 20, true, parentsAny(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)); public static final IntegerSetting SWIPE_OVERLAY_RECT_SIZE = new IntegerSetting("revanced_swipe_overlay_rect_size", 20, true, parentsAny(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)); public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true, parentsAny(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)); @@ -536,30 +551,38 @@ public class Settings extends BaseSettings { public static final FloatSetting SWIPE_BRIGHTNESS_VALUE = new FloatSetting("revanced_swipe_brightness_value", -1.0f, false, false); - // PreferenceScreen: Video - public static final FloatSetting DEFAULT_PLAYBACK_SPEED = new FloatSetting("revanced_default_playback_speed", -2.0f); - public static final IntegerSetting DEFAULT_VIDEO_QUALITY_MOBILE = new IntegerSetting("revanced_default_video_quality_mobile", -2); - public static final IntegerSetting DEFAULT_VIDEO_QUALITY_WIFI = new IntegerSetting("revanced_default_video_quality_wifi", -2); + // PreferenceScreen: Video - Codec public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE, true); + public static final BooleanSetting DISABLE_VP9_CODEC = new BooleanSetting("revanced_disable_vp9_codec", FALSE, true); + public static final BooleanSetting REPLACE_AV1_CODEC = new BooleanSetting("revanced_replace_av1_codec", FALSE, true); + + // PreferenceScreen: Video - Playback speed + public static final FloatSetting DEFAULT_PLAYBACK_SPEED = new FloatSetting("revanced_default_playback_speed", -2.0f); + public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", TRUE); + public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_playback_speed_last_selected_toast", TRUE, parent(REMEMBER_PLAYBACK_SPEED_LAST_SELECTED)); + public static final FloatSetting DEFAULT_PLAYBACK_SPEED_SHORTS = new FloatSetting("revanced_default_playback_speed_shorts", -2.0f); + public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_SHORTS_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_shorts_last_selected", TRUE); + public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_SHORTS_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_playback_speed_shorts_last_selected_toast", TRUE, parent(REMEMBER_PLAYBACK_SPEED_SHORTS_LAST_SELECTED)); public static final BooleanSetting ENABLE_CUSTOM_PLAYBACK_SPEED = new BooleanSetting("revanced_enable_custom_playback_speed", FALSE, true); public static final BooleanSetting CUSTOM_PLAYBACK_SPEED_MENU_TYPE = new BooleanSetting("revanced_custom_playback_speed_menu_type", FALSE, parent(ENABLE_CUSTOM_PLAYBACK_SPEED)); public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds", "0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.25\n2.5", true, parent(ENABLE_CUSTOM_PLAYBACK_SPEED)); - public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", TRUE); - public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_playback_speed_last_selected_toast", TRUE, parent(REMEMBER_PLAYBACK_SPEED_LAST_SELECTED)); + + // PreferenceScreen: Video - Video quality + public static final IntegerSetting DEFAULT_VIDEO_QUALITY_MOBILE = new IntegerSetting("revanced_default_video_quality_mobile", -2); + public static final IntegerSetting DEFAULT_VIDEO_QUALITY_WIFI = new IntegerSetting("revanced_default_video_quality_wifi", -2); public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", TRUE); public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_video_quality_last_selected_toast", TRUE, parent(REMEMBER_VIDEO_QUALITY_LAST_SELECTED)); + public static final IntegerSetting DEFAULT_VIDEO_QUALITY_MOBILE_SHORTS = new IntegerSetting("revanced_default_video_quality_mobile_shorts", -2, true); + public static final IntegerSetting DEFAULT_VIDEO_QUALITY_WIFI_SHORTS = new IntegerSetting("revanced_default_video_quality_wifi_shorts", -2, true); + public static final BooleanSetting REMEMBER_VIDEO_QUALITY_SHORTS_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_shorts_last_selected", TRUE); + public static final BooleanSetting REMEMBER_VIDEO_QUALITY_SHORTS_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_video_quality_shorts_last_selected_toast", TRUE, parent(REMEMBER_VIDEO_QUALITY_SHORTS_LAST_SELECTED)); public static final BooleanSetting RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE, true); - // Experimental Flags - public static final BooleanSetting DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC = new BooleanSetting("revanced_disable_default_playback_speed_music", FALSE, true); - public static final BooleanSetting DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC_TYPE = new BooleanSetting("revanced_disable_default_playback_speed_music_type", FALSE, true, parent(DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC)); - public static final BooleanSetting ENABLE_DEFAULT_PLAYBACK_SPEED_SHORTS = new BooleanSetting("revanced_enable_default_playback_speed_shorts", FALSE); public static final BooleanSetting SKIP_PRELOADED_BUFFER = new BooleanSetting("revanced_skip_preloaded_buffer", FALSE, true, "revanced_skip_preloaded_buffer_user_dialog_message"); public static final BooleanSetting SKIP_PRELOADED_BUFFER_TOAST = new BooleanSetting("revanced_skip_preloaded_buffer_toast", TRUE); public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true); - public static final BooleanSetting DISABLE_VP9_CODEC = new BooleanSetting("revanced_disable_vp9_codec", FALSE, true); - public static final BooleanSetting REPLACE_AV1_CODEC = new BooleanSetting("revanced_replace_av1_codec", FALSE, true); - public static final BooleanSetting REJECT_AV1_CODEC = new BooleanSetting("revanced_reject_av1_codec", FALSE, true); + public static final BooleanSetting DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC = new BooleanSetting("revanced_disable_default_playback_speed_music", FALSE, true); + public static final BooleanSetting DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC_TYPE = new BooleanSetting("revanced_disable_default_playback_speed_music_type", FALSE, true, parent(DISABLE_DEFAULT_PLAYBACK_SPEED_MUSIC)); // PreferenceScreen: Miscellaneous public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE); @@ -611,24 +634,34 @@ public class Settings extends BaseSettings { public static final StringSetting SB_CATEGORY_SPONSOR = new StringSetting("sb_sponsor", SKIP_AUTOMATICALLY.reVancedKeyValue); public static final StringSetting SB_CATEGORY_SPONSOR_COLOR = new StringSetting("sb_sponsor_color", "#00D400"); + public static final FloatSetting SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f); public static final StringSetting SB_CATEGORY_SELF_PROMO = new StringSetting("sb_selfpromo", SKIP_AUTOMATICALLY.reVancedKeyValue); public static final StringSetting SB_CATEGORY_SELF_PROMO_COLOR = new StringSetting("sb_selfpromo_color", "#FFFF00"); + public static final FloatSetting SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f); public static final StringSetting SB_CATEGORY_INTERACTION = new StringSetting("sb_interaction", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_INTERACTION_COLOR = new StringSetting("sb_interaction_color", "#CC00FF"); + public static final FloatSetting SB_CATEGORY_INTERACTION_OPACITY = new FloatSetting("sb_interaction_opacity", 0.8f); public static final StringSetting SB_CATEGORY_HIGHLIGHT = new StringSetting("sb_highlight", MANUAL_SKIP.reVancedKeyValue); public static final StringSetting SB_CATEGORY_HIGHLIGHT_COLOR = new StringSetting("sb_highlight_color", "#FF1684"); + public static final FloatSetting SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f); public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#00FFFF"); + public static final FloatSetting SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f); public static final StringSetting SB_CATEGORY_OUTRO = new StringSetting("sb_outro", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_OUTRO_COLOR = new StringSetting("sb_outro_color", "#0202ED"); + public static final FloatSetting SB_CATEGORY_OUTRO_OPACITY = new FloatSetting("sb_outro_opacity", 0.8f); public static final StringSetting SB_CATEGORY_PREVIEW = new StringSetting("sb_preview", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_PREVIEW_COLOR = new StringSetting("sb_preview_color", "#008FD6"); + public static final FloatSetting SB_CATEGORY_PREVIEW_OPACITY = new FloatSetting("sb_preview_opacity", 0.8f); public static final StringSetting SB_CATEGORY_FILLER = new StringSetting("sb_filler", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_FILLER_COLOR = new StringSetting("sb_filler_color", "#7300FF"); + public static final FloatSetting SB_CATEGORY_FILLER_OPACITY = new FloatSetting("sb_filler_opacity", 0.8f); public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC = new StringSetting("sb_music_offtopic", MANUAL_SKIP.reVancedKeyValue); public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC_COLOR = new StringSetting("sb_music_offtopic_color", "#FF9900"); + public static final FloatSetting SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY = new FloatSetting("sb_music_offtopic_opacity", 0.8f); public static final StringSetting SB_CATEGORY_UNSUBMITTED = new StringSetting("sb_unsubmitted", SKIP_AUTOMATICALLY.reVancedKeyValue); public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFF"); + public static final FloatSetting SB_CATEGORY_UNSUBMITTED_OPACITY = new FloatSetting("sb_unsubmitted_opacity", 1.0f); // SB Setting not exported public static final LongSetting SB_LAST_VIP_CHECK = new LongSetting("sb_last_vip_check", 0L, false, false); @@ -637,6 +670,16 @@ public class Settings extends BaseSettings { static { // region Migration initialized + + // Old spoof versions that no longer work reliably. + String spoofAppVersionTarget = SPOOF_APP_VERSION_TARGET.get(); + if (spoofAppVersionTarget.compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0) { + Utils.showToastShort(str("revanced_spoof_app_version_target_invalid_toast", spoofAppVersionTarget)); + Utils.showToastShort(str("revanced_extended_reset_to_default_toast")); + Logger.printInfo(() -> "Resetting spoof app version target"); + SPOOF_APP_VERSION_TARGET.resetToDefault(); + } + // Categories were previously saved without a 'sb_' key prefix, so they need an additional adjustment. Set> sbCategories = new HashSet<>(Arrays.asList( SB_CATEGORY_SPONSOR, diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java index 5eab38ba7..6a2fd721f 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java @@ -1,6 +1,7 @@ package app.revanced.extension.youtube.settings.preference; import static com.google.android.apps.youtube.app.settings.videoquality.VideoQualitySettingsActivity.setToolbarLayoutParams; +import static app.revanced.extension.shared.settings.BaseSettings.SPOOF_STREAMING_DATA_TYPE; import static app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment.showRestartDialog; import static app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment.updateListPreferenceSummary; import static app.revanced.extension.shared.utils.ResourceUtils.getXmlIdentifier; @@ -9,6 +10,7 @@ import static app.revanced.extension.shared.utils.Utils.getChildView; import static app.revanced.extension.shared.utils.Utils.isSDKAbove; import static app.revanced.extension.shared.utils.Utils.showToastShort; import static app.revanced.extension.youtube.settings.Settings.DEFAULT_PLAYBACK_SPEED; +import static app.revanced.extension.youtube.settings.Settings.DEFAULT_PLAYBACK_SPEED_SHORTS; import static app.revanced.extension.youtube.settings.Settings.HIDE_PREVIEW_COMMENT; import static app.revanced.extension.youtube.settings.Settings.HIDE_PREVIEW_COMMENT_TYPE; @@ -31,6 +33,7 @@ import android.preference.PreferenceGroup; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import android.preference.SwitchPreference; +import android.util.Pair; import android.util.TypedValue; import android.view.ViewGroup; import android.view.WindowInsets; @@ -55,12 +58,17 @@ import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import app.revanced.extension.shared.patches.spoof.SpoofStreamingDataPatch; +import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BooleanSetting; +import app.revanced.extension.shared.settings.EnumSetting; import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.utils.Logger; import app.revanced.extension.shared.utils.ResourceUtils; +import app.revanced.extension.shared.utils.StringRef; import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.youtube.patches.video.CustomPlaybackSpeedPatch; +import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.utils.ExtendedUtils; import app.revanced.extension.youtube.utils.ThemeUtils; @@ -74,14 +82,19 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { @SuppressLint("SuspiciousIndentation") private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { try { - if (str == null) return; - Setting setting = Setting.getSettingFromPath(Objects.requireNonNull(str)); + if (str == null) { + return; + } - if (setting == null) return; + Setting setting = Setting.getSettingFromPath(str); + if (setting == null) { + return; + } Preference mPreference = findPreference(str); - - if (mPreference == null) return; + if (mPreference == null) { + return; + } if (mPreference instanceof SwitchPreference switchPreference) { BooleanSetting boolSetting = (BooleanSetting) setting; @@ -108,9 +121,13 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { } else { Setting.privateSetValueFromString(setting, listPreference.getValue()); } - if (setting.equals(DEFAULT_PLAYBACK_SPEED)) { - listPreference.setEntries(CustomPlaybackSpeedPatch.getListEntries()); - listPreference.setEntryValues(CustomPlaybackSpeedPatch.getListEntryValues()); + if (setting.equals(DEFAULT_PLAYBACK_SPEED) || setting.equals(DEFAULT_PLAYBACK_SPEED_SHORTS)) { + listPreference.setEntries(CustomPlaybackSpeedPatch.getEntries()); + listPreference.setEntryValues(CustomPlaybackSpeedPatch.getEntryValues()); + } + if (setting.equals(SPOOF_STREAMING_DATA_TYPE)) { + listPreference.setEntries(SpoofStreamingDataPatch.getEntries()); + listPreference.setEntryValues(SpoofStreamingDataPatch.getEntryValues()); } if (!(mPreference instanceof app.revanced.extension.youtube.settings.preference.SegmentCategoryListPreference)) { updateListPreferenceSummary(listPreference, setting); @@ -122,18 +139,11 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { ReVancedSettingsPreference.initializeReVancedSettings(); - if (settingImportInProgress) { - return; - } - - if (!showingUserDialogMessage) { + if (!settingImportInProgress && !showingUserDialogMessage) { final Context context = getActivity(); - if (setting.userDialogMessage != null - && mPreference instanceof SwitchPreference switchPreference - && setting.defaultValue instanceof Boolean defaultValue - && switchPreference.isChecked() != defaultValue) { - showSettingUserDialogConfirmation(context, switchPreference, (BooleanSetting) setting); + if (setting.userDialogMessage != null && !prefIsSetToDefault(mPreference, setting)) { + showSettingUserDialogConfirmation(context, mPreference, setting); } else if (setting.rebootApp) { showRestartDialog(context); } @@ -143,25 +153,56 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { } }; - private void showSettingUserDialogConfirmation(Context context, SwitchPreference switchPreference, BooleanSetting setting) { + /** + * @return If the preference is currently set to the default value of the Setting. + */ + private boolean prefIsSetToDefault(Preference pref, Setting setting) { + Object defaultValue = setting.defaultValue; + if (pref instanceof SwitchPreference switchPref) { + return switchPref.isChecked() == (Boolean) defaultValue; + } + String defaultValueString = defaultValue.toString(); + if (pref instanceof EditTextPreference editPreference) { + return editPreference.getText().equals(defaultValueString); + } + if (pref instanceof ListPreference listPref) { + return listPref.getValue().equals(defaultValueString); + } + + throw new IllegalStateException("Must override method to handle " + + "preference type: " + pref.getClass()); + } + + private void showSettingUserDialogConfirmation(Context context, Preference pref, Setting setting) { Utils.verifyOnMainThread(); - showingUserDialogMessage = true; - assert setting.userDialogMessage != null; - new AlertDialog.Builder(context) - .setTitle(str("revanced_extended_confirm_user_dialog_title")) - .setMessage(setting.userDialogMessage.toString()) - .setPositiveButton(android.R.string.ok, (dialog, id) -> { - if (setting.rebootApp) { - showRestartDialog(context); - } - }) - .setNegativeButton(android.R.string.cancel, (dialog, id) -> { - switchPreference.setChecked(setting.defaultValue); // Recursive call that resets the Setting value. - }) - .setOnDismissListener(dialog -> showingUserDialogMessage = false) - .setCancelable(false) - .show(); + final StringRef userDialogMessage = setting.userDialogMessage; + if (context != null && userDialogMessage != null) { + showingUserDialogMessage = true; + + new AlertDialog.Builder(context) + .setTitle(str("revanced_extended_confirm_user_dialog_title")) + .setMessage(userDialogMessage.toString()) + .setPositiveButton(android.R.string.ok, (dialog, id) -> { + if (setting.rebootApp) { + showRestartDialog(context); + } + }) + .setNegativeButton(android.R.string.cancel, (dialog, id) -> { + // Restore whatever the setting was before the change. + if (setting instanceof BooleanSetting booleanSetting && + pref instanceof SwitchPreference switchPreference) { + switchPreference.setChecked(booleanSetting.defaultValue); + } else if (setting instanceof EnumSetting enumSetting && + pref instanceof ListPreference listPreference) { + listPreference.setValue(enumSetting.defaultValue.toString()); + updateListPreferenceSummary(listPreference, setting); + } + }) + .setOnDismissListener(dialog -> showingUserDialogMessage = false) + .setCancelable(false) + .show(); + } } static PreferenceManager mPreferenceManager; @@ -197,6 +238,9 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { } } + Integer targetSDKVersion = ExtendedUtils.getTargetSDKVersion(getContext().getPackageName()); + boolean isEdgeToEdgeSupported = isSDKAbove(35) && targetSDKVersion != null && targetSDKVersion >= 35; + for (PreferenceScreen mPreferenceScreen : preferenceScreenMap.values()) { mPreferenceScreen.setOnPreferenceClickListener( preferenceScreen -> { @@ -205,11 +249,24 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { .findViewById(android.R.id.content) .getParent(); - // Fix required for Android 15 - if (isSDKAbove(35)) { + // Edge-to-edge is enforced if the following conditions are met: + // 1. targetSDK is 35 or greater (YouTube 19.44.39 or greater). + // 2. user is using Android 15 or greater. + // + // Related Issues: + // https://github.com/ReVanced/revanced-patches/issues/3976 + // https://github.com/ReVanced/revanced-patches/issues/4606 + // + // Docs: + // https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets + // + // Since ReVanced Settings Activity do not use AndroidX libraries, + // You will need to manually fix the layout breakage caused by edge-to-edge. + if (isEdgeToEdgeSupported) { rootView.setOnApplyWindowInsetsListener((v, insets) -> { Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars()); - v.setPadding(0, statusInsets.top, 0, 0); + Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars()); + v.setPadding(0, statusInsets.top, 0, navInsets.bottom); return insets; }); } @@ -283,9 +340,13 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { } else if (preference instanceof EditTextPreference editTextPreference) { editTextPreference.setText(setting.get().toString()); } else if (preference instanceof ListPreference listPreference) { - if (setting.equals(DEFAULT_PLAYBACK_SPEED)) { - listPreference.setEntries(CustomPlaybackSpeedPatch.getListEntries()); - listPreference.setEntryValues(CustomPlaybackSpeedPatch.getListEntryValues()); + if (setting.equals(DEFAULT_PLAYBACK_SPEED) || setting.equals(DEFAULT_PLAYBACK_SPEED_SHORTS)) { + listPreference.setEntries(CustomPlaybackSpeedPatch.getEntries()); + listPreference.setEntryValues(CustomPlaybackSpeedPatch.getEntryValues()); + } + if (setting.equals(SPOOF_STREAMING_DATA_TYPE)) { + listPreference.setEntries(SpoofStreamingDataPatch.getEntries()); + listPreference.setEntryValues(SpoofStreamingDataPatch.getEntryValues()); } if (!(preference instanceof app.revanced.extension.youtube.settings.preference.SegmentCategoryListPreference)) { updateListPreferenceSummary(listPreference, setting); @@ -298,6 +359,10 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { originalPreferenceScreen = getPreferenceManager().createPreferenceScreen(getActivity()); copyPreferences(getPreferenceScreen(), originalPreferenceScreen); + + sortPreferenceListMenu(Settings.CHANGE_START_PAGE); + sortPreferenceListMenu(Settings.SPOOF_STREAMING_DATA_LANGUAGE); + sortPreferenceListMenu(BaseSettings.REVANCED_LANGUAGE); } catch (Exception th) { Logger.printException(() -> "Error during onCreate()", th); } @@ -312,9 +377,69 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { @Override public void onDestroy() { mSharedPreferences.unregisterOnSharedPreferenceChangeListener(listener); + Utils.resetLocalizedContext(); super.onDestroy(); } + /** + * Sorts a preference list by menu entries, but preserves the first value as the first entry. + * + * @noinspection SameParameterValue + */ + private static void sortListPreferenceByValues(ListPreference listPreference, int firstEntriesToPreserve) { + CharSequence[] entries = listPreference.getEntries(); + CharSequence[] entryValues = listPreference.getEntryValues(); + final int entrySize = entries.length; + + if (entrySize != entryValues.length) { + // Xml array declaration has a missing/extra entry. + throw new IllegalStateException(); + } + + // Since the text of Preference is Spanned, CharSequence#toString() should not be used. + // If CharSequence#toString() is used, Spanned styling, such as HTML syntax, will be broken. + List> firstPairs = new ArrayList<>(firstEntriesToPreserve); + List> pairsToSort = new ArrayList<>(entrySize); + + for (int i = 0; i < entrySize; i++) { + Pair pair = new Pair<>(entries[i], entryValues[i]); + if (i < firstEntriesToPreserve) { + firstPairs.add(pair); + } else { + pairsToSort.add(pair); + } + } + + pairsToSort.sort((pair1, pair2) + -> pair1.first.toString().compareToIgnoreCase(pair2.first.toString())); + + CharSequence[] sortedEntries = new CharSequence[entrySize]; + CharSequence[] sortedEntryValues = new CharSequence[entrySize]; + + int i = 0; + for (Pair pair : firstPairs) { + sortedEntries[i] = pair.first; + sortedEntryValues[i] = pair.second; + i++; + } + + for (Pair pair : pairsToSort) { + sortedEntries[i] = pair.first; + sortedEntryValues[i] = pair.second; + i++; + } + + listPreference.setEntries(sortedEntries); + listPreference.setEntryValues(sortedEntryValues); + } + + private void sortPreferenceListMenu(EnumSetting setting) { + Preference preference = findPreference(setting.key); + if (preference instanceof ListPreference languagePreference) { + sortListPreferenceByValues(languagePreference, 1); + } + } + /** * Recursively stores all preferences and their dependencies grouped by their parent PreferenceGroup. * 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 c3c22520b..e4cc256bf 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,7 +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.utils.ExtendedUtils.isSpoofingToLessThan; import android.preference.Preference; import android.preference.SwitchPreference; @@ -43,11 +42,11 @@ public class ReVancedSettingsPreference extends ReVancedPreferenceFragment { enableDisablePreferences(); AmbientModePreferenceLinks(); - ExternalDownloaderPreferenceLinks(); FullScreenPanelPreferenceLinks(); NavigationPreferenceLinks(); RYDPreferenceLinks(); SeekBarPreferenceLinks(); + ShortsPreferenceLinks(); SpeedOverlayPreferenceLinks(); QuickActionsPreferenceLinks(); TabletLayoutLinks(); @@ -65,18 +64,6 @@ public class ReVancedSettingsPreference extends ReVancedPreferenceFragment { ); } - /** - * Enable/Disable Preference for External downloader settings - */ - private static void ExternalDownloaderPreferenceLinks() { - // Override download button will not work if spoofed with YouTube 18.24.xx or earlier. - enableDisablePreferences( - isSpoofingToLessThan("18.24.00"), - Settings.OVERRIDE_VIDEO_DOWNLOAD_BUTTON, - Settings.OVERRIDE_PLAYLIST_DOWNLOAD_BUTTON - ); - } - /** * Enable/Disable Preferences not working in tablet layout */ @@ -200,6 +187,19 @@ public class ReVancedSettingsPreference extends ReVancedPreferenceFragment { ); } + /** + * Enable/Disable Preference related to Shorts settings + */ + private static void ShortsPreferenceLinks() { + if (!PatchStatus.RememberPlaybackSpeed()) { + enableDisablePreferences( + true, + Settings.SHORTS_CUSTOM_ACTIONS_SPEED_DIALOG + ); + Settings.SHORTS_CUSTOM_ACTIONS_SPEED_DIALOG.save(false); + } + } + /** * Enable/Disable Preference related to Speed overlay settings */ diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SegmentCategoryListPreference.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SegmentCategoryListPreference.java index b94ee3135..68b8727a5 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SegmentCategoryListPreference.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SegmentCategoryListPreference.java @@ -1,6 +1,7 @@ package app.revanced.extension.youtube.settings.preference; import static app.revanced.extension.shared.utils.StringRef.str; +import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor; import android.app.AlertDialog; import android.content.Context; @@ -12,41 +13,51 @@ import android.text.InputType; import android.text.TextWatcher; import android.util.AttributeSet; import android.widget.EditText; -import android.widget.LinearLayout; -import android.widget.TableLayout; -import android.widget.TableRow; +import android.widget.GridLayout; import android.widget.TextView; +import java.util.Locale; import java.util.Objects; import app.revanced.extension.shared.utils.Logger; import app.revanced.extension.shared.utils.Utils; -import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour; import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory; @SuppressWarnings({"unused", "deprecation"}) public class SegmentCategoryListPreference extends ListPreference { - private SegmentCategory mCategory; - private EditText mEditText; - private int mClickedDialogEntryIndex; + private SegmentCategory category; + private TextView colorDotView; + private EditText colorEditText; + private EditText opacityEditText; + /** + * #RRGGBB + */ + private int categoryColor; + /** + * [0, 1] + */ + private float categoryOpacity; + private int selectedDialogEntryIndex; private void init() { final SegmentCategory segmentCategory = SegmentCategory.byCategoryKey(getKey()); - final boolean isHighlightCategory = segmentCategory == SegmentCategory.HIGHLIGHT; - mCategory = Objects.requireNonNull(segmentCategory); + category = Objects.requireNonNull(segmentCategory); + // Edit: Using preferences to sync together multiple pieces - // of code together is messy and should be rethought. + // of code is messy and should be rethought. setKey(segmentCategory.behaviorSetting.key); setDefaultValue(segmentCategory.behaviorSetting.defaultValue); + final boolean isHighlightCategory = category == SegmentCategory.HIGHLIGHT; setEntries(isHighlightCategory ? CategoryBehaviour.getBehaviorDescriptionsWithoutSkipOnce() : CategoryBehaviour.getBehaviorDescriptions()); setEntryValues(isHighlightCategory ? CategoryBehaviour.getBehaviorKeyValuesWithoutSkipOnce() : CategoryBehaviour.getBehaviorKeyValues()); - updateTitle(); + + updateTitleFromCategory(); } public SegmentCategoryListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { @@ -73,28 +84,41 @@ public class SegmentCategoryListPreference extends ListPreference { protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { try { Utils.setEditTextDialogTheme(builder); - super.onPrepareDialogBuilder(builder); + + categoryColor = category.getColorNoOpacity(); + categoryOpacity = category.getOpacity(); Context context = builder.getContext(); - TableLayout table = new TableLayout(context); - table.setOrientation(LinearLayout.HORIZONTAL); - table.setPadding(70, 0, 150, 0); - - TableRow row = new TableRow(context); + GridLayout gridLayout = new GridLayout(context); + gridLayout.setPadding(70, 0, 150, 0); // Padding for the entire layout. + gridLayout.setColumnCount(3); + gridLayout.setRowCount(2); + GridLayout.LayoutParams gridParams = new GridLayout.LayoutParams(); + gridParams.rowSpec = GridLayout.spec(0); // First row. + gridParams.columnSpec = GridLayout.spec(0); // First column. TextView colorTextLabel = new TextView(context); colorTextLabel.setText(str("revanced_sb_color_dot_label")); - row.addView(colorTextLabel); + colorTextLabel.setLayoutParams(gridParams); + gridLayout.addView(colorTextLabel); - TextView colorDotView = new TextView(context); - colorDotView.setText(mCategory.getCategoryColorDot()); - colorDotView.setPadding(30, 0, 30, 0); - row.addView(colorDotView); + gridParams = new GridLayout.LayoutParams(); + gridParams.rowSpec = GridLayout.spec(0); // First row. + gridParams.columnSpec = GridLayout.spec(1); // Second column. + gridParams.setMargins(0, 0, 10, 0); + colorDotView = new TextView(context); + colorDotView.setLayoutParams(gridParams); + gridLayout.addView(colorDotView); + updateCategoryColorDot(); - mEditText = new EditText(context); - mEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS); - mEditText.setText(mCategory.colorString()); - mEditText.addTextChangedListener(new TextWatcher() { + gridParams = new GridLayout.LayoutParams(); + gridParams.rowSpec = GridLayout.spec(0); // First row. + gridParams.columnSpec = GridLayout.spec(2); // Third column. + colorEditText = new EditText(context); + colorEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS); + colorEditText.setTextLocale(Locale.US); + colorEditText.setText(category.getColorString()); + colorEditText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @@ -104,44 +128,111 @@ public class SegmentCategoryListPreference extends ListPreference { } @Override - public void afterTextChanged(Editable s) { + public void afterTextChanged(Editable edit) { try { - String colorString = s.toString(); + String colorString = edit.toString(); + final int colorStringLength = colorString.length(); + if (!colorString.startsWith("#")) { - s.insert(0, "#"); // recursively calls back into this method + edit.insert(0, "#"); // Recursively calls back into this method. return; } - if (colorString.length() > 7) { - s.delete(7, colorString.length()); + + final int maxColorStringLength = 7; // #RRGGBB + if (colorStringLength > maxColorStringLength) { + edit.delete(maxColorStringLength, colorStringLength); return; } - final int color = Color.parseColor(colorString); - colorDotView.setText(SegmentCategory.getCategoryColorDot(color)); + + categoryColor = Color.parseColor(colorString); + updateCategoryColorDot(); } catch (IllegalArgumentException ex) { - // ignore + // Ignore. } } }); - mEditText.setLayoutParams(new TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 1f)); - row.addView(mEditText); + colorEditText.setLayoutParams(gridParams); + gridLayout.addView(colorEditText); - table.addView(row); - builder.setView(table); - builder.setTitle(mCategory.title.toString()); + gridParams = new GridLayout.LayoutParams(); + gridParams.rowSpec = GridLayout.spec(1); // Second row. + gridParams.columnSpec = GridLayout.spec(0, 1); // First and second column. + TextView opacityLabel = new TextView(context); + opacityLabel.setText(str("revanced_sb_color_opacity_label")); + opacityLabel.setLayoutParams(gridParams); + gridLayout.addView(opacityLabel); + + gridParams = new GridLayout.LayoutParams(); + gridParams.rowSpec = GridLayout.spec(1); // Second row. + gridParams.columnSpec = GridLayout.spec(2); // Third column. + opacityEditText = new EditText(context); + opacityEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL); + opacityEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable edit) { + try { + String editString = edit.toString(); + final int opacityStringLength = editString.length(); + + final int maxOpacityStringLength = 4; // [0.00, 1.00] + if (opacityStringLength > maxOpacityStringLength) { + edit.delete(maxOpacityStringLength, opacityStringLength); + return; + } + + final float opacity = opacityStringLength == 0 + ? 0 + : Float.parseFloat(editString); + if (opacity < 0) { + categoryOpacity = 0; + edit.replace(0, opacityStringLength, "0"); + return; + } else if (opacity > 1.0f) { + categoryOpacity = 1; + edit.replace(0, opacityStringLength, "1.0"); + return; + } else if (!editString.endsWith(".")) { + // Ignore "0." and "1." until the user finishes entering a valid number. + categoryOpacity = opacity; + } + + updateCategoryColorDot(); + } catch (NumberFormatException ex) { + // Should never happen. + Logger.printException(() -> "Could not parse opacity string", ex); + } + } + }); + opacityEditText.setLayoutParams(gridParams); + gridLayout.addView(opacityEditText); + updateOpacityText(); + + builder.setView(gridLayout); + builder.setTitle(category.title.toString()); builder.setPositiveButton(android.R.string.ok, (dialog, which) -> onClick(dialog, DialogInterface.BUTTON_POSITIVE)); builder.setNeutralButton(str("revanced_sb_reset_color"), (dialog, which) -> { try { - mCategory.resetColor(); - updateTitle(); + category.resetColorAndOpacity(); + updateTitleFromCategory(); Utils.showToastShort(str("revanced_sb_color_reset")); } catch (Exception ex) { Logger.printException(() -> "setNeutralButton failure", ex); } }); builder.setNegativeButton(android.R.string.cancel, null); - mClickedDialogEntryIndex = findIndexOfValue(getValue()); - builder.setSingleChoiceItems(getEntries(), mClickedDialogEntryIndex, (dialog, which) -> mClickedDialogEntryIndex = which); + + selectedDialogEntryIndex = findIndexOfValue(getValue()); + builder.setSingleChoiceItems(getEntries(), selectedDialogEntryIndex, + (dialog, which) -> selectedDialogEntryIndex = which); } catch (Exception ex) { Logger.printException(() -> "onPrepareDialogBuilder failure", ex); } @@ -150,31 +241,50 @@ public class SegmentCategoryListPreference extends ListPreference { @Override protected void onDialogClosed(boolean positiveResult) { try { - if (positiveResult && mClickedDialogEntryIndex >= 0 && getEntryValues() != null) { - String value = getEntryValues()[mClickedDialogEntryIndex].toString(); + if (positiveResult && selectedDialogEntryIndex >= 0 && getEntryValues() != null) { + String value = getEntryValues()[selectedDialogEntryIndex].toString(); if (callChangeListener(value)) { setValue(value); - mCategory.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value))); + category.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value))); SegmentCategory.updateEnabledCategories(); } - String colorString = mEditText.getText().toString(); try { - if (!colorString.equals(mCategory.colorString())) { - mCategory.setColor(colorString); + String colorString = colorEditText.getText().toString(); + if (!colorString.equals(category.getColorString()) || categoryOpacity != category.getOpacity()) { + category.setColor(colorString); + category.setOpacity(categoryOpacity); Utils.showToastShort(str("revanced_sb_color_changed")); } } catch (IllegalArgumentException ex) { Utils.showToastShort(str("revanced_sb_color_invalid")); } - updateTitle(); + + updateTitleFromCategory(); } } catch (Exception ex) { Logger.printException(() -> "onDialogClosed failure", ex); } } - private void updateTitle() { - setTitle(mCategory.getTitleWithColorDot()); - setEnabled(Settings.SB_ENABLED.get()); + private void applyOpacityToCategoryColor() { + categoryColor = applyOpacityToColor(categoryColor, categoryOpacity); + } + + private void updateTitleFromCategory() { + categoryColor = category.getColorNoOpacity(); + categoryOpacity = category.getOpacity(); + applyOpacityToCategoryColor(); + + setTitle(category.getTitleWithColorDot(categoryColor)); + } + + private void updateCategoryColorDot() { + applyOpacityToCategoryColor(); + + colorDotView.setText(SegmentCategory.getCategoryColorDot(categoryColor)); + } + + private void updateOpacityText() { + opacityEditText.setText(String.format(Locale.US, "%.2f", categoryOpacity)); } } \ No newline at end of file diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SponsorBlockSettingsPreference.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SponsorBlockSettingsPreference.java index d3107381f..e98b55d65 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SponsorBlockSettingsPreference.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/settings/preference/SponsorBlockSettingsPreference.java @@ -233,6 +233,7 @@ public class SponsorBlockSettingsPreference extends ReVancedPreferenceFragment { statsCategory = new PreferenceCategory(mActivity); statsCategory.setLayoutResource(preferencesCategoryLayout); statsCategory.setTitle(str("revanced_sb_stats")); + statsCategory.setEnabled(Settings.SB_ENABLED.get()); mPreferenceScreen.addPreference(statsCategory); fetchAndDisplayStats(); @@ -261,7 +262,6 @@ public class SponsorBlockSettingsPreference extends ReVancedPreferenceFragment { final String key = category.keyValue; if (mPreferenceManager.findPreference(key) instanceof SegmentCategoryListPreference segmentCategoryListPreference) { segmentCategoryListPreference.setTitle(category.getTitleWithColorDot()); - segmentCategoryListPreference.setEnabled(Settings.SB_ENABLED.get()); } } } catch (Exception ex) { diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/RootView.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/RootView.java index 3d18b32b2..cad23ab5f 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/RootView.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/RootView.java @@ -58,6 +58,10 @@ public final class RootView { return PlayerType.getCurrent().isMaximizedOrFullscreen() || isActionBarVisible.get(); } + public static boolean isShortsActive() { + return ShortsPlayerState.getCurrent().isOpen(); + } + /** * Get current BrowseId. * Rest of the implementation added by patch. diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/ShortsPlayerState.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/ShortsPlayerState.kt index e3e56c58e..76f201265 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/ShortsPlayerState.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/shared/ShortsPlayerState.kt @@ -48,4 +48,12 @@ enum class ShortsPlayerState { fun isClosed(): Boolean { return this == CLOSED } + + /** + * Check if the shorts player is [OPEN]. + * Useful for checking if a shorts player is open. + */ + fun isOpen(): Boolean { + return this == OPEN + } } \ No newline at end of file diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java index d4228f3ec..11a588fba 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockSettings.java @@ -139,7 +139,7 @@ public class SponsorBlockSettings { for (SegmentCategory category : categories) { JSONObject categoryObject = new JSONObject(); String categoryKey = category.keyValue; - categoryObject.put("color", category.colorString()); + categoryObject.put("color", category.getColorString()); barTypesObject.put(categoryKey, categoryObject); if (category.behaviour != CategoryBehaviour.IGNORE) { diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockUtils.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockUtils.java index 56dc52977..bee1d6a98 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockUtils.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SponsorBlockUtils.java @@ -6,7 +6,12 @@ import android.annotation.TargetApi; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import android.text.Html; +import android.graphics.Color; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; import android.widget.EditText; import androidx.annotation.NonNull; @@ -32,11 +37,9 @@ import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockViewController /** * Not thread safe. All fields/methods must be accessed from the main thread. - * - * @noinspection deprecation */ public class SponsorBlockUtils { - private static final String LOCKED_COLOR = "#FFC83D"; + private static final int LOCKED_COLOR = Color.parseColor("#FFC83D"); private static final String MANUAL_EDIT_TIME_TEXT_HINT = "hh:mm:ss.sss"; private static final Pattern manualEditTimePattern @@ -162,28 +165,34 @@ public class SponsorBlockUtils { SegmentVote[] voteOptions = (segment.category == SegmentCategory.HIGHLIGHT) ? SegmentVote.voteTypesWithoutCategoryChange // highlight segments cannot change category : SegmentVote.values(); - CharSequence[] items = new CharSequence[voteOptions.length]; + final int voteOptionsLength = voteOptions.length; + final boolean userIsVip = Settings.SB_USER_IS_VIP.get(); + CharSequence[] items = new CharSequence[voteOptionsLength]; - for (int i = 0; i < voteOptions.length; i++) { + for (int i = 0; i < voteOptionsLength; i++) { SegmentVote voteOption = voteOptions[i]; - String title = voteOption.title.toString(); - if (Settings.SB_USER_IS_VIP.get() && segment.isLocked && voteOption.shouldHighlight) { - items[i] = Html.fromHtml(String.format("%s", LOCKED_COLOR, title)); - } else { - items[i] = title; + CharSequence title = voteOption.title.toString(); + if (userIsVip && segment.isLocked && voteOption.highlightIfVipAndVideoIsLocked) { + SpannableString coloredTitle = new SpannableString(title); + coloredTitle.setSpan(new ForegroundColorSpan(LOCKED_COLOR), + 0, title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + title = coloredTitle; } + items[i] = title; } - new AlertDialog.Builder(context) - .setItems(items, (dialog1, which1) -> { - SegmentVote voteOption = voteOptions[which1]; - switch (voteOption) { - case UPVOTE, DOWNVOTE -> - SBRequester.voteForSegmentOnBackgroundThread(segment, voteOption); - case CATEGORY_CHANGE -> onNewCategorySelect(segment, context); - } - }) - .show(); + new AlertDialog.Builder(context).setItems(items, (dialog1, which1) -> { + SegmentVote voteOption = voteOptions[which1]; + switch (voteOption) { + case UPVOTE: + case DOWNVOTE: + SBRequester.voteForSegmentOnBackgroundThread(segment, voteOption); + break; + case CATEGORY_CHANGE: + onNewCategorySelect(segment, context); + break; + } + }).show(); } catch (Exception ex) { Logger.printException(() -> "segmentVoteClickListener failure", ex); } @@ -287,22 +296,33 @@ public class SponsorBlockUtils { if (segment.category == SegmentCategory.UNSUBMITTED) { continue; } - StringBuilder htmlBuilder = new StringBuilder(); - htmlBuilder.append(String.format(" %s
", - segment.category.color, segment.category.title)); - htmlBuilder.append(formatSegmentTime(segment.start)); - if (segment.category != SegmentCategory.HIGHLIGHT) { - htmlBuilder.append(" to ").append(formatSegmentTime(segment.end)); + + SpannableStringBuilder spannableBuilder = new SpannableStringBuilder(); + + spannableBuilder.append(segment.category.getTitleWithColorDot()); + spannableBuilder.append('\n'); + + String startTime = formatSegmentTime(segment.start); + if (segment.category == SegmentCategory.HIGHLIGHT) { + spannableBuilder.append(startTime); + } else { + String toFromString = str("revanced_sb_vote_segment_time_to_from", + startTime, formatSegmentTime(segment.end)); + spannableBuilder.append(toFromString); } - htmlBuilder.append("
"); - if (i + 1 != numberOfSegments) // prevents trailing new line after last segment - htmlBuilder.append("
"); - titles[i] = Html.fromHtml(htmlBuilder.toString()); + + if (i + 1 != numberOfSegments) { + // prevents trailing new line after last segment + spannableBuilder.append('\n'); + } + + spannableBuilder.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), + 0, spannableBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + titles[i] = spannableBuilder; } - new AlertDialog.Builder(context) - .setItems(titles, segmentVoteClickListener) - .show(); + new AlertDialog.Builder(context).setItems(titles, segmentVoteClickListener).show(); } catch (Exception ex) { Logger.printException(() -> "onVotingClicked failure", ex); } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategory.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategory.java index 3d1e90f66..c16fbe3d2 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategory.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategory.java @@ -3,30 +3,41 @@ package app.revanced.extension.youtube.sponsorblock.objects; import static app.revanced.extension.shared.utils.StringRef.sf; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_FILLER; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_FILLER_COLOR; +import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_FILLER_OPACITY; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_HIGHLIGHT; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_HIGHLIGHT_COLOR; +import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_HIGHLIGHT_OPACITY; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTERACTION; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTERACTION_COLOR; +import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTERACTION_OPACITY; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTRO; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTRO_COLOR; +import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_INTRO_OPACITY; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_MUSIC_OFFTOPIC; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_MUSIC_OFFTOPIC_COLOR; +import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_OUTRO; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_OUTRO_COLOR; +import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_OUTRO_OPACITY; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_PREVIEW; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_PREVIEW_COLOR; +import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_PREVIEW_OPACITY; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SELF_PROMO; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SELF_PROMO_COLOR; +import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SELF_PROMO_OPACITY; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SPONSOR; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SPONSOR_COLOR; +import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_SPONSOR_OPACITY; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_UNSUBMITTED; import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_UNSUBMITTED_COLOR; +import static app.revanced.extension.youtube.settings.Settings.SB_CATEGORY_UNSUBMITTED_OPACITY; import android.graphics.Color; import android.graphics.Paint; -import android.text.Html; -import android.text.Spanned; +import android.text.Spannable; +import android.text.SpannableString; import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -34,45 +45,46 @@ import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; +import app.revanced.extension.shared.settings.FloatSetting; import app.revanced.extension.shared.settings.StringSetting; import app.revanced.extension.shared.utils.Logger; import app.revanced.extension.shared.utils.StringRef; import app.revanced.extension.shared.utils.Utils; import app.revanced.extension.youtube.settings.Settings; -@SuppressWarnings({"deprecation", "StaticFieldLeak"}) +@SuppressWarnings("StaticFieldLeak") public enum SegmentCategory { SPONSOR("sponsor", sf("revanced_sb_segments_sponsor"), sf("revanced_sb_skip_button_sponsor"), sf("revanced_sb_skipped_sponsor"), - SB_CATEGORY_SPONSOR, SB_CATEGORY_SPONSOR_COLOR), + SB_CATEGORY_SPONSOR, SB_CATEGORY_SPONSOR_COLOR, SB_CATEGORY_SPONSOR_OPACITY), SELF_PROMO("selfpromo", sf("revanced_sb_segments_selfpromo"), sf("revanced_sb_skip_button_selfpromo"), sf("revanced_sb_skipped_selfpromo"), - SB_CATEGORY_SELF_PROMO, SB_CATEGORY_SELF_PROMO_COLOR), + SB_CATEGORY_SELF_PROMO, SB_CATEGORY_SELF_PROMO_COLOR, SB_CATEGORY_SELF_PROMO_OPACITY), INTERACTION("interaction", sf("revanced_sb_segments_interaction"), sf("revanced_sb_skip_button_interaction"), sf("revanced_sb_skipped_interaction"), - SB_CATEGORY_INTERACTION, SB_CATEGORY_INTERACTION_COLOR), + SB_CATEGORY_INTERACTION, SB_CATEGORY_INTERACTION_COLOR, SB_CATEGORY_INTERACTION_OPACITY), /** * Unique category that is treated differently than the rest. */ HIGHLIGHT("poi_highlight", sf("revanced_sb_segments_highlight"), sf("revanced_sb_skip_button_highlight"), sf("revanced_sb_skipped_highlight"), - SB_CATEGORY_HIGHLIGHT, SB_CATEGORY_HIGHLIGHT_COLOR), + SB_CATEGORY_HIGHLIGHT, SB_CATEGORY_HIGHLIGHT_COLOR, SB_CATEGORY_HIGHLIGHT_OPACITY), INTRO("intro", sf("revanced_sb_segments_intro"), sf("revanced_sb_skip_button_intro_beginning"), sf("revanced_sb_skip_button_intro_middle"), sf("revanced_sb_skip_button_intro_end"), sf("revanced_sb_skipped_intro_beginning"), sf("revanced_sb_skipped_intro_middle"), sf("revanced_sb_skipped_intro_end"), - SB_CATEGORY_INTRO, SB_CATEGORY_INTRO_COLOR), + SB_CATEGORY_INTRO, SB_CATEGORY_INTRO_COLOR, SB_CATEGORY_INTRO_OPACITY), OUTRO("outro", sf("revanced_sb_segments_outro"), sf("revanced_sb_skip_button_outro"), sf("revanced_sb_skipped_outro"), - SB_CATEGORY_OUTRO, SB_CATEGORY_OUTRO_COLOR), + SB_CATEGORY_OUTRO, SB_CATEGORY_OUTRO_COLOR, SB_CATEGORY_OUTRO_OPACITY), PREVIEW("preview", sf("revanced_sb_segments_preview"), sf("revanced_sb_skip_button_preview_beginning"), sf("revanced_sb_skip_button_preview_middle"), sf("revanced_sb_skip_button_preview_end"), sf("revanced_sb_skipped_preview_beginning"), sf("revanced_sb_skipped_preview_middle"), sf("revanced_sb_skipped_preview_end"), - SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR), + SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR, SB_CATEGORY_PREVIEW_OPACITY), FILLER("filler", sf("revanced_sb_segments_filler"), sf("revanced_sb_skip_button_filler"), sf("revanced_sb_skipped_filler"), - SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR), + SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR, SB_CATEGORY_FILLER_OPACITY), MUSIC_OFFTOPIC("music_offtopic", sf("revanced_sb_segments_nomusic"), sf("revanced_sb_skip_button_nomusic"), sf("revanced_sb_skipped_nomusic"), - SB_CATEGORY_MUSIC_OFFTOPIC, SB_CATEGORY_MUSIC_OFFTOPIC_COLOR), + SB_CATEGORY_MUSIC_OFFTOPIC, SB_CATEGORY_MUSIC_OFFTOPIC_COLOR, SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY), UNSUBMITTED("unsubmitted", StringRef.empty, sf("revanced_sb_skip_button_unsubmitted"), sf("revanced_sb_skipped_unsubmitted"), - SB_CATEGORY_UNSUBMITTED, SB_CATEGORY_UNSUBMITTED_COLOR), - ; + SB_CATEGORY_UNSUBMITTED, SB_CATEGORY_UNSUBMITTED_COLOR, SB_CATEGORY_UNSUBMITTED_OPACITY); private static final StringRef skipSponsorTextCompact = sf("revanced_sb_skip_button_compact"); private static final StringRef skipSponsorTextCompactHighlight = sf("revanced_sb_skip_button_compact_highlight"); @@ -111,12 +123,10 @@ public enum SegmentCategory { mValuesMap.put(value.keyValue, value); } - @NonNull public static SegmentCategory[] categoriesWithoutUnsubmitted() { return categoriesWithoutUnsubmitted; } - @NonNull public static SegmentCategory[] categoriesWithoutHighlights() { return categoriesWithoutHighlights; } @@ -127,7 +137,7 @@ public enum SegmentCategory { } /** - * Must be called if behavior of any category is changed + * Must be called if behavior of any category is changed. */ public static void updateEnabledCategories() { Utils.verifyOnMainThread(); @@ -154,30 +164,32 @@ public enum SegmentCategory { updateEnabledCategories(); } - @NonNull - public final String keyValue; - @NonNull - public final StringSetting behaviorSetting; - @NonNull - private final StringSetting colorSetting; + public static int applyOpacityToColor(int color, float opacity) { + if (opacity < 0 || opacity > 1.0f) { + throw new IllegalArgumentException("Invalid opacity: " + opacity); + } + final int opacityInt = (int) (255 * opacity); + return (color & 0x00FFFFFF) | (opacityInt << 24); + } + + public final String keyValue; + public final StringSetting behaviorSetting; // TODO: Replace with EnumSetting. + private final StringSetting colorSetting; + private final FloatSetting opacitySetting; - @NonNull public final StringRef title; /** * Skip button text, if the skip occurs in the first quarter of the video */ - @NonNull public final StringRef skipButtonTextBeginning; /** * Skip button text, if the skip occurs in the middle half of the video */ - @NonNull public final StringRef skipButtonTextMiddle; /** * Skip button text, if the skip occurs in the last quarter of the video */ - @NonNull public final StringRef skipButtonTextEnd; /** * Skipped segment toast, if the skip occurred in the first quarter of the video @@ -198,10 +210,7 @@ public enum SegmentCategory { @NonNull public final Paint paint; - /** - * Value must be changed using {@link #setColor(String)}. - */ - public int color; + private int color; /** * Value must be changed using {@link #setBehaviour(CategoryBehaviour)}. @@ -213,17 +222,20 @@ public enum SegmentCategory { SegmentCategory(String keyValue, StringRef title, StringRef skipButtonText, StringRef skippedToastText, - StringSetting behavior, StringSetting color) { + StringSetting behavior, + StringSetting color, FloatSetting opacity) { this(keyValue, title, skipButtonText, skipButtonText, skipButtonText, skippedToastText, skippedToastText, skippedToastText, - behavior, color); + behavior, + color, opacity); } SegmentCategory(String keyValue, StringRef title, StringRef skipButtonTextBeginning, StringRef skipButtonTextMiddle, StringRef skipButtonTextEnd, StringRef skippedToastBeginning, StringRef skippedToastMiddle, StringRef skippedToastEnd, - StringSetting behavior, StringSetting color) { + StringSetting behavior, + StringSetting color, FloatSetting opacity) { this.keyValue = Objects.requireNonNull(keyValue); this.title = Objects.requireNonNull(title); this.skipButtonTextBeginning = Objects.requireNonNull(skipButtonTextBeginning); @@ -234,6 +246,7 @@ public enum SegmentCategory { this.skippedToastEnd = Objects.requireNonNull(skippedToastEnd); this.behaviorSetting = Objects.requireNonNull(behavior); this.colorSetting = Objects.requireNonNull(color); + this.opacitySetting = Objects.requireNonNull(opacity); this.paint = new Paint(); loadFromSettings(); } @@ -250,11 +263,14 @@ public enum SegmentCategory { this.behaviour = savedBehavior; String colorString = colorSetting.get(); + final float opacity = opacitySetting.get(); try { setColor(colorString); + setOpacity(opacity); } catch (Exception ex) { - Logger.printException(() -> "Invalid color: " + colorString, ex); + Logger.printException(() -> "Invalid color: " + colorString + " opacity: " + opacity, ex); colorSetting.resetToDefault(); + opacitySetting.resetToDefault(); loadFromSettings(); } } @@ -264,45 +280,77 @@ public enum SegmentCategory { this.behaviorSetting.save(behaviour.reVancedKeyValue); } - /** - * @return HTML color format string - */ - @NonNull - public String colorString() { - return String.format("#%06X", color); - } - - public void setColor(@NonNull String colorString) throws IllegalArgumentException { - final int color = Color.parseColor(colorString) & 0xFFFFFF; - this.color = color; + private void updateColor() { + color = applyOpacityToColor(color, opacitySetting.get()); paint.setColor(color); - paint.setAlpha(255); - colorSetting.save(colorString); // Save after parsing. } - public void resetColor() { + /** + * @param opacity Segment color opacity between [0, 1]. + */ + public void setOpacity(float opacity) throws IllegalArgumentException { + if (opacity < 0 || opacity > 1) { + throw new IllegalArgumentException("Invalid opacity: " + opacity); + } + + opacitySetting.save(opacity); + updateColor(); + } + + public float getOpacity() { + return opacitySetting.get(); + } + + public void resetColorAndOpacity() { setColor(colorSetting.defaultValue); + setOpacity(opacitySetting.defaultValue); } - @NonNull - private static String getCategoryColorDotHTML(int color) { - color &= 0xFFFFFF; - return String.format("", color); + /** + * @param colorString Segment color with #RRGGBB format. + */ + public void setColor(String colorString) throws IllegalArgumentException { + color = Color.parseColor(colorString); + colorSetting.save(colorString); + + updateColor(); } - @NonNull - public static Spanned getCategoryColorDot(int color) { - return Html.fromHtml(getCategoryColorDotHTML(color)); + /** + * @return Integer color of #RRGGBB format. + */ + public int getColorNoOpacity() { + return color & 0x00FFFFFF; } - @NonNull - public Spanned getCategoryColorDot() { + /** + * @return Hex color string of #RRGGBB format with no opacity level. + */ + public String getColorString() { + return String.format(Locale.US, "#%06X", getColorNoOpacity()); + } + + private static SpannableString getCategoryColorDotSpan(String text, int color) { + SpannableString dotSpan = new SpannableString('⬤' + text); + dotSpan.setSpan(new ForegroundColorSpan(color), 0, 1, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return dotSpan; + } + + public static SpannableString getCategoryColorDot(int color) { + return getCategoryColorDotSpan("", color); + } + + public SpannableString getCategoryColorDot() { return getCategoryColorDot(color); } - @NonNull - public Spanned getTitleWithColorDot() { - return Html.fromHtml(getCategoryColorDotHTML(color) + " " + title); + public SpannableString getTitleWithColorDot(int categoryColor) { + return getCategoryColorDotSpan(" " + title, categoryColor); + } + + public SpannableString getTitleWithColorDot() { + return getTitleWithColorDot(color); } /** @@ -310,7 +358,6 @@ public enum SegmentCategory { * @param videoLength length of the video * @return the skip button text */ - @NonNull StringRef getSkipButtonText(long segmentStartTime, long videoLength) { if (Settings.SB_COMPACT_SKIP_BUTTON.get()) { return (this == SegmentCategory.HIGHLIGHT) @@ -319,7 +366,7 @@ public enum SegmentCategory { } if (videoLength == 0) { - return skipButtonTextBeginning; // video is still loading. Assume it's the beginning + return skipButtonTextBeginning; // Video is still loading. Assume it's the beginning. } final float position = segmentStartTime / (float) videoLength; if (position < 0.25f) { @@ -335,10 +382,9 @@ public enum SegmentCategory { * @param videoLength length of the video * @return 'skipped segment' toast message */ - @NonNull StringRef getSkippedToastText(long segmentStartTime, long videoLength) { if (videoLength == 0) { - return skippedToastBeginning; // video is still loading. Assume it's the beginning + return skippedToastBeginning; // Video is still loading. Assume it's the beginning. } final float position = segmentStartTime / (float) videoLength; if (position < 0.25f) { diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SponsorSegment.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SponsorSegment.java index 51208c1cc..0bd5aa435 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SponsorSegment.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SponsorSegment.java @@ -24,12 +24,15 @@ public class SponsorSegment implements Comparable { @NonNull public final StringRef title; public final int apiVoteType; - public final boolean shouldHighlight; + /** + * If the option should be highlighted for VIP users. + */ + public final boolean highlightIfVipAndVideoIsLocked; - SegmentVote(@NonNull StringRef title, int apiVoteType, boolean shouldHighlight) { + SegmentVote(@NonNull StringRef title, int apiVoteType, boolean highlightIfVipAndVideoIsLocked) { this.title = title; this.apiVoteType = apiVoteType; - this.shouldHighlight = shouldHighlight; + this.highlightIfVipAndVideoIsLocked = highlightIfVipAndVideoIsLocked; } } diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/SwipeControlsConfigurationProvider.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/SwipeControlsConfigurationProvider.kt index 3d3c5d83d..dbe9cf385 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/SwipeControlsConfigurationProvider.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/SwipeControlsConfigurationProvider.kt @@ -125,7 +125,7 @@ class SwipeControlsConfigurationProvider( * get the background color for text on the overlay, as a color int */ val overlayTextBackgroundColor: Int - get() = Color.argb(Settings.SWIPE_OVERLAY_BACKGROUND_ALPHA.get(), 0, 0, 0) + get() = overlayBackgroundOpacity /** * get the foreground color for text on the overlay, as a color int @@ -133,6 +133,59 @@ class SwipeControlsConfigurationProvider( val overlayForegroundColor: Int get() = Color.WHITE + /** + * Gets the opacity value (0-100%) is converted to an alpha value (0-255) for transparency. + * If the opacity value is out of range, it resets to the default and displays a warning message. + */ + val overlayBackgroundOpacity: Int + get() { + var opacity = validateValue( + Settings.SWIPE_OVERLAY_BACKGROUND_OPACITY, + 0, + 100, + "revanced_swipe_overlay_background_opacity_invalid_toast" + ) + + opacity = opacity * 255 / 100 + return Color.argb(opacity, 0, 0, 0) + } + + /** + * The color of the progress overlay. + */ + val overlayProgressColor: Int + get() = 0xBFFFFFFF.toInt() + + /** + * The color used for the background of the progress overlay fill. + */ + val overlayFillBackgroundPaint: Int + get() = 0x80D3D3D3.toInt() + + /** + * The color used for the text and icons in the overlay. + */ + val overlayTextColor: Int + get() = Color.WHITE + + /** + * A flag that determines whether to use the alternate UI. + */ + val isAlternativeUI: Boolean + get() = Settings.SWIPE_OVERLAY_ALTERNATIVE_UI.get() + + /** + * A flag that determines if the overlay should only show the icon. + */ + val overlayShowOverlayMinimalStyle: Boolean + get() = isAlternativeUI && Settings.SWIPE_OVERLAY_MINIMAL_STYLE.get() + + /** + * A flag that determines if the progress bar should be circular. + */ + val isCircularProgressBar: Boolean + get() = isAlternativeUI && Settings.SWIPE_SHOW_CIRCULAR_OVERLAY.get() + // endregion // region behaviour diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/SwipeControlsHostActivity.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/SwipeControlsHostActivity.kt index 3ebebc252..f2b5e4aa4 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/SwipeControlsHostActivity.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/SwipeControlsHostActivity.kt @@ -24,7 +24,7 @@ import java.lang.ref.WeakReference * The main controller for volume and brightness swipe controls. * note that the superclass is overwritten to the superclass of the MainActivity at patch time * - * @smali Lapp/revanced/integrations/youtube/swipecontrols/SwipeControlsHostActivity; + * @smali Lapp/revanced/extension/youtube/swipecontrols/SwipeControlsHostActivity; */ class SwipeControlsHostActivity : Activity() { /** diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/views/SwipeControlsOverlayLayout.kt b/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/views/SwipeControlsOverlayLayout.kt index 6d2cef606..f3ba568b5 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/views/SwipeControlsOverlayLayout.kt +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/swipecontrols/views/SwipeControlsOverlayLayout.kt @@ -1,14 +1,18 @@ package app.revanced.extension.youtube.swipecontrols.views +import android.annotation.SuppressLint import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.os.Handler import android.os.Looper +import android.util.AttributeSet import android.util.TypedValue import android.view.HapticFeedbackConstants import android.view.View -import android.view.ViewGroup import android.widget.RelativeLayout import android.widget.TextView import app.revanced.extension.shared.utils.ResourceUtils.ResourceType @@ -17,6 +21,7 @@ import app.revanced.extension.shared.utils.StringRef.str import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider import app.revanced.extension.youtube.swipecontrols.misc.SwipeControlsOverlay import app.revanced.extension.youtube.swipecontrols.misc.applyDimension +import kotlin.math.min import kotlin.math.round /** @@ -33,36 +38,82 @@ class SwipeControlsOverlayLayout( */ constructor(context: Context) : this(context, SwipeControlsConfigurationProvider(context)) - private val feedbackTextView: TextView private val autoBrightnessIcon: Drawable + private val lowBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_low") + private val mediumBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_medium") + private val highBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_high") + private val fullBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_full") private val manualBrightnessIcon: Drawable private val mutedVolumeIcon: Drawable + private val lowVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_low") private val normalVolumeIcon: Drawable + private val feedbackTextView: TextView + private val fullVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_high") - private fun getDrawable(name: String, width: Int, height: Int): Drawable { - return resources.getDrawable( + private val circularProgressView: CircularProgressView = CircularProgressView( + context, + config.overlayBackgroundOpacity, + config.overlayShowOverlayMinimalStyle, + config.overlayProgressColor, + config.overlayFillBackgroundPaint, + config.overlayTextColor + ).apply { + layoutParams = LayoutParams(300, 300).apply { + addRule(CENTER_IN_PARENT, TRUE) + } + visibility = GONE // Initially hidden + } + private val horizontalProgressView: HorizontalProgressView + + private val isAlternativeUI: Boolean = config.isAlternativeUI + + private fun getDrawable(name: String, width: Int? = null, height: Int? = null): Drawable { + val drawable = resources.getDrawable( getIdentifier(name, ResourceType.DRAWABLE, context), - context.theme - ).apply { - setTint(config.overlayForegroundColor) - setBounds( + context.theme, + ) + + if (width != null && height != null) { + drawable.setTint(config.overlayForegroundColor) + drawable.setBounds( 0, 0, width, height, ) + } else { + drawable.setTint(config.overlayTextColor) } + return drawable } init { + // Initialize horizontal progress bar + val screenWidth = resources.displayMetrics.widthPixels + val layoutWidth = (screenWidth * 2 / 3).toInt() // 2/3 of screen width + horizontalProgressView = HorizontalProgressView( + context, + config.overlayBackgroundOpacity, + config.overlayShowOverlayMinimalStyle, + config.overlayProgressColor, + config.overlayFillBackgroundPaint, + config.overlayTextColor + ).apply { + layoutParams = LayoutParams(layoutWidth, 100).apply { + addRule(CENTER_HORIZONTAL) + topMargin = 40 // Top margin + } + visibility = GONE // Initially hidden + } + // init views val feedbackYTextViewPadding = 5.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) val feedbackXTextViewPadding = 12.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) val compoundIconPadding = 4.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP) feedbackTextView = TextView(context).apply { layoutParams = LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT, ).apply { addRule(CENTER_IN_PARENT, TRUE) setPadding( @@ -81,19 +132,36 @@ class SwipeControlsOverlayLayout( compoundDrawablePadding = compoundIconPadding visibility = GONE } - addView(feedbackTextView) - // get icons scaled, assuming square icons - val iconHeight = round(feedbackTextView.lineHeight * .8).toInt() - autoBrightnessIcon = getDrawable("ic_sc_brightness_auto", iconHeight, iconHeight) - manualBrightnessIcon = getDrawable("ic_sc_brightness_manual", iconHeight, iconHeight) - mutedVolumeIcon = getDrawable("ic_sc_volume_mute", iconHeight, iconHeight) - normalVolumeIcon = getDrawable("ic_sc_volume_normal", iconHeight, iconHeight) + if (isAlternativeUI) { + addView(circularProgressView) + addView(horizontalProgressView) + + autoBrightnessIcon = getDrawable("revanced_ic_sc_brightness_auto") + manualBrightnessIcon = getDrawable("revanced_ic_sc_brightness_manual") + mutedVolumeIcon = getDrawable("revanced_ic_sc_volume_mute") + normalVolumeIcon = getDrawable("revanced_ic_sc_volume_normal") + } else { + addView(feedbackTextView) + // get icons scaled, assuming square icons + val iconHeight = round(feedbackTextView.lineHeight * .8).toInt() + autoBrightnessIcon = + getDrawable("revanced_ic_sc_brightness_auto", iconHeight, iconHeight) + manualBrightnessIcon = + getDrawable("revanced_ic_sc_brightness_manual", iconHeight, iconHeight) + mutedVolumeIcon = getDrawable("revanced_ic_sc_volume_mute", iconHeight, iconHeight) + normalVolumeIcon = getDrawable("revanced_ic_sc_volume_normal", iconHeight, iconHeight) + } } private val feedbackHideHandler = Handler(Looper.getMainLooper()) private val feedbackHideCallback = Runnable { - feedbackTextView.visibility = View.GONE + if (isAlternativeUI) { + circularProgressView.visibility = GONE + horizontalProgressView.visibility = GONE + } else { + feedbackTextView.visibility = GONE + } } /** @@ -117,21 +185,81 @@ class SwipeControlsOverlayLayout( } } + /** + * Displays the progress bar with the appropriate value, icon, and type (brightness or volume). + */ + private fun showFeedbackView( + value: String, + progress: Int, + max: Int, + icon: Drawable, + isBrightness: Boolean + ) { + feedbackHideHandler.removeCallbacks(feedbackHideCallback) + feedbackHideHandler.postDelayed(feedbackHideCallback, config.overlayShowTimeoutMillis) + + val viewToShow = + if (config.isCircularProgressBar) circularProgressView else horizontalProgressView + viewToShow.apply { + setProgress(progress, max, value, isBrightness) + this.icon = icon + visibility = VISIBLE + } + } + override fun onVolumeChanged(newVolume: Int, maximumVolume: Int) { - showFeedbackView( - "$newVolume", - if (newVolume > 0) normalVolumeIcon else mutedVolumeIcon, - ) + if (isAlternativeUI) { + val volumePercentage = (newVolume.toFloat() / maximumVolume) * 100 + val icon = when { + newVolume == 0 -> mutedVolumeIcon + volumePercentage < 33 -> lowVolumeIcon + volumePercentage < 66 -> normalVolumeIcon + else -> fullVolumeIcon + } + showFeedbackView("$newVolume", newVolume, maximumVolume, icon, isBrightness = false) + } else { + showFeedbackView( + "$newVolume", + if (newVolume > 0) normalVolumeIcon else mutedVolumeIcon, + ) + } } override fun onBrightnessChanged(brightness: Double) { if (config.shouldLowestValueEnableAutoBrightness && brightness <= 0) { - showFeedbackView( - str("revanced_swipe_lowest_value_auto_brightness_overlay_text"), - autoBrightnessIcon, - ) + if (isAlternativeUI) { + showFeedbackView( + str("revanced_swipe_lowest_value_auto_brightness_overlay_text"), + 0, + 100, + autoBrightnessIcon, + isBrightness = true, + ) + } else { + showFeedbackView( + str("revanced_swipe_lowest_value_auto_brightness_overlay_text"), + autoBrightnessIcon, + ) + } } else if (brightness >= 0) { - showFeedbackView("${round(brightness).toInt()}%", manualBrightnessIcon) + if (isAlternativeUI) { + val brightnessValue = round(brightness).toInt() + val icon = when { + brightnessValue < 25 -> lowBrightnessIcon + brightnessValue < 50 -> mediumBrightnessIcon + brightnessValue < 75 -> highBrightnessIcon + else -> fullBrightnessIcon + } + showFeedbackView( + "$brightnessValue%", + brightnessValue, + 100, + icon, + isBrightness = true + ) + } else { + showFeedbackView("${round(brightness).toInt()}%", manualBrightnessIcon) + } } } @@ -145,3 +273,255 @@ class SwipeControlsOverlayLayout( } } } + +/** + * Abstract base class for progress views. + */ +abstract class AbstractProgressView( + context: Context, + overlayBackgroundOpacity: Int, + protected val overlayShowOverlayMinimalStyle: Boolean, + overlayProgressColor: Int, + overlayFillBackgroundPaint: Int, + protected val overlayTextColor: Int, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + + // Combined paint creation function for both fill and stroke styles + private fun createPaint( + color: Int, + style: Paint.Style = Paint.Style.FILL, + strokeCap: Paint.Cap = Paint.Cap.BUTT, + strokeWidth: Float = 0f + ) = Paint(Paint.ANTI_ALIAS_FLAG).apply { + this.style = style + this.color = color + this.strokeCap = strokeCap + this.strokeWidth = strokeWidth + } + + // Initialize paints + val backgroundPaint = createPaint(overlayBackgroundOpacity, style = Paint.Style.FILL) + val progressPaint = createPaint( + overlayProgressColor, + style = Paint.Style.STROKE, + strokeCap = Paint.Cap.ROUND, + strokeWidth = 20f + ) + val fillBackgroundPaint = createPaint(overlayFillBackgroundPaint, style = Paint.Style.FILL) + val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = overlayTextColor + textAlign = Paint.Align.CENTER + textSize = 40f // Can adjust based on need + } + + protected var progress = 0 + protected var maxProgress = 100 + protected var displayText: String = "0" + protected var isBrightness = true + var icon: Drawable? = null + + fun setProgress(value: Int, max: Int, text: String, isBrightnessMode: Boolean) { + progress = value + maxProgress = max + displayText = text + isBrightness = isBrightnessMode + invalidate() + } + + override fun onDraw(canvas: Canvas) { + // Base class implementation can be empty + } +} + +/** + * Custom view for rendering a circular progress indicator with icons and text. + */ +@SuppressLint("ViewConstructor") +class CircularProgressView( + context: Context, + overlayBackgroundOpacity: Int, + overlayShowOverlayMinimalStyle: Boolean, + overlayProgressColor: Int, + overlayFillBackgroundPaint: Int, + overlayTextColor: Int, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : AbstractProgressView( + context, + overlayBackgroundOpacity, + overlayShowOverlayMinimalStyle, + overlayProgressColor, + overlayFillBackgroundPaint, + overlayTextColor, + attrs, + defStyleAttr +) { + private val rectF = RectF() + + init { + textPaint.textSize = 40f // Override default text size for circular view + progressPaint.strokeWidth = 20f + fillBackgroundPaint.strokeWidth = 20f + progressPaint.strokeCap = Paint.Cap.ROUND + fillBackgroundPaint.strokeCap = Paint.Cap.BUTT + progressPaint.style = Paint.Style.STROKE + fillBackgroundPaint.style = Paint.Style.STROKE + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + val size = min(width, height).toFloat() + rectF.set(20f, 20f, size - 20f, size - 20f) + + canvas.drawOval(rectF, fillBackgroundPaint) // Draw the outer ring. + canvas.drawCircle( + width / 2f, + height / 2f, + size / 3, + backgroundPaint + ) // Draw the inner circle. + + // Select the paint for drawing based on whether it's brightness or volume. + val sweepAngle = (progress.toFloat() / maxProgress) * 360 + canvas.drawArc(rectF, -90f, sweepAngle, false, progressPaint) // Draw the progress arc. + + // Draw the icon in the center. + icon?.let { + val iconSize = if (overlayShowOverlayMinimalStyle) 100 else 80 + val iconX = (width - iconSize) / 2 + val iconY = (height / 2) - if (overlayShowOverlayMinimalStyle) 50 else 80 + it.setBounds(iconX, iconY, iconX + iconSize, iconY + iconSize) + it.draw(canvas) + } + + // If not a minimal style mode, draw the text inside the ring. + if (!overlayShowOverlayMinimalStyle) { + canvas.drawText(displayText, width / 2f, height / 2f + 60f, textPaint) + } + } +} + +/** + * Custom view for rendering a rectangular progress bar with icons and text. + */ +@SuppressLint("ViewConstructor") +class HorizontalProgressView( + context: Context, + overlayBackgroundOpacity: Int, + overlayShowOverlayMinimalStyle: Boolean, + overlayProgressColor: Int, + overlayFillBackgroundPaint: Int, + overlayTextColor: Int, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : AbstractProgressView( + context, + overlayBackgroundOpacity, + overlayShowOverlayMinimalStyle, + overlayProgressColor, + overlayFillBackgroundPaint, + overlayTextColor, + attrs, + defStyleAttr +) { + + private val iconSize = 60f + private val padding = 40f + + init { + textPaint.textSize = 36f // Override default text size for horizontal view + progressPaint.strokeWidth = 0f + progressPaint.strokeCap = Paint.Cap.BUTT + progressPaint.style = Paint.Style.FILL + fillBackgroundPaint.style = Paint.Style.FILL + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + val width = width.toFloat() + val height = height.toFloat() + + // Radius for rounded corners + val cornerRadius = min(width, height) / 2 + + // Calculate the total width for the elements + val minimalElementWidth = 5 * padding + iconSize + + // Calculate the starting point (X) to center the elements + val minimalStartX = (width - minimalElementWidth) / 2 + + // Draw the background + if (!overlayShowOverlayMinimalStyle) { + canvas.drawRoundRect(0f, 0f, width, height, cornerRadius, cornerRadius, backgroundPaint) + } else { + canvas.drawRoundRect( + minimalStartX, + 0f, + minimalStartX + minimalElementWidth, + height, + cornerRadius, + cornerRadius, + backgroundPaint + ) + } + + if (!overlayShowOverlayMinimalStyle) { + // Draw the fill background + val startX = 2 * padding + iconSize + val endX = width - 4 * padding + val fillWidth = endX - startX + + canvas.drawRoundRect( + startX, + height / 2 - 5f, + endX, + height / 2 + 5f, + 10f, 10f, + fillBackgroundPaint + ) + + // Draw the progress + val progressWidth = (progress.toFloat() / maxProgress) * fillWidth + canvas.drawRoundRect( + startX, + height / 2 - 5f, + startX + progressWidth, + height / 2 + 5f, + 10f, 10f, + progressPaint + ) + } + + // Draw the icon + icon?.let { + val iconX = if (!overlayShowOverlayMinimalStyle) { + padding + } else { + padding + minimalStartX + } + val iconY = height / 2 - iconSize / 2 + it.setBounds( + iconX.toInt(), + iconY.toInt(), + (iconX + iconSize).toInt(), + (iconY + iconSize).toInt() + ) + it.draw(canvas) + } + + // Draw the text on the right + val textX = if (!overlayShowOverlayMinimalStyle) { + width - 2 * padding + } else { + minimalStartX + minimalElementWidth - 2 * padding + } + val textY = height / 2 + textPaint.textSize / 3 + + // Draw the text + canvas.drawText(displayText, textX, textY, textPaint) + } +} 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 fae38500b..378ddea77 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 @@ -2,8 +2,27 @@ package app.revanced.extension.youtube.utils; import static app.revanced.extension.shared.utils.StringRef.str; +import android.app.AlertDialog; +import android.content.Context; +import android.graphics.ColorFilter; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.StateListDrawable; +import android.view.Gravity; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + import androidx.annotation.NonNull; +import java.util.Map; + import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.FloatSetting; import app.revanced.extension.shared.settings.IntegerSetting; @@ -12,14 +31,20 @@ import app.revanced.extension.shared.utils.PackageUtils; import app.revanced.extension.youtube.settings.Settings; public class ExtendedUtils extends PackageUtils { + + private static boolean isVersionOrGreater(String version) { + return getAppVersionName().compareTo(version) >= 0; + } + @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; - public static final boolean IS_19_26_OR_GREATER = getAppVersionName().compareTo("19.26.00") >= 0; - public static final boolean IS_19_28_OR_GREATER = getAppVersionName().compareTo("19.28.00") >= 0; - public static final boolean IS_19_29_OR_GREATER = getAppVersionName().compareTo("19.29.00") >= 0; - public static final boolean IS_19_34_OR_GREATER = getAppVersionName().compareTo("19.34.00") >= 0; + public static final boolean IS_19_17_OR_GREATER = isVersionOrGreater("19.17.00"); + public static final boolean IS_19_20_OR_GREATER = isVersionOrGreater("19.20.00"); + public static final boolean IS_19_21_OR_GREATER = isVersionOrGreater("19.21.00"); + public static final boolean IS_19_26_OR_GREATER = isVersionOrGreater("19.26.00"); + public static final boolean IS_19_28_OR_GREATER = isVersionOrGreater("19.28.00"); + public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00"); + public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00"); + public static final boolean IS_20_09_OR_GREATER = isVersionOrGreater("20.09.00"); public static int validateValue(IntegerSetting settings, int min, int max, String message) { int value = settings.get(); @@ -114,4 +139,88 @@ public class ExtendedUtils extends PackageUtils { } return additionalSettingsEnabled; } + + public static void showBottomSheetDialog(Context mContext, ScrollView mScrollView, + Map actionsMap) { + runOnMainThreadDelayed(() -> { + AlertDialog.Builder builder = new AlertDialog.Builder(mContext); + builder.setView(mScrollView); + + AlertDialog dialog = builder.create(); + dialog.show(); + + actionsMap.forEach((view, action) -> + view.setOnClickListener(v -> { + action.run(); + dialog.dismiss(); + }) + ); + actionsMap.clear(); + + Window window = dialog.getWindow(); + if (window == null) { + return; + } + + // round corners + GradientDrawable dialogBackground = new GradientDrawable(); + dialogBackground.setCornerRadius(32); + window.setBackgroundDrawable(dialogBackground); + + // fit screen width + int dialogWidth = (int) (mContext.getResources().getDisplayMetrics().widthPixels * 0.95); + window.setLayout(dialogWidth, ViewGroup.LayoutParams.WRAP_CONTENT); + + // move dialog to bottom + WindowManager.LayoutParams layoutParams = window.getAttributes(); + layoutParams.gravity = Gravity.BOTTOM; + + // adjust the vertical offset + layoutParams.y = dpToPx(5); + + window.setAttributes(layoutParams); + }, 250); + } + + public static LinearLayout createItemLayout(Context mContext, String title, int iconId) { + // Item Layout + LinearLayout itemLayout = new LinearLayout(mContext); + itemLayout.setOrientation(LinearLayout.HORIZONTAL); + itemLayout.setPadding(dpToPx(16), dpToPx(12), dpToPx(16), dpToPx(12)); + itemLayout.setGravity(Gravity.CENTER_VERTICAL); + itemLayout.setClickable(true); + itemLayout.setFocusable(true); + + // Create a StateListDrawable for the background + StateListDrawable background = new StateListDrawable(); + ColorDrawable pressedDrawable = new ColorDrawable(ThemeUtils.getPressedElementColor()); + ColorDrawable defaultDrawable = new ColorDrawable(ThemeUtils.getBackgroundColor()); + background.addState(new int[]{android.R.attr.state_pressed}, pressedDrawable); + background.addState(new int[]{}, defaultDrawable); + itemLayout.setBackground(background); + + // Icon + ColorFilter cf = new PorterDuffColorFilter(ThemeUtils.getForegroundColor(), PorterDuff.Mode.SRC_ATOP); + ImageView iconView = new ImageView(mContext); + iconView.setImageResource(iconId); + iconView.setColorFilter(cf); + LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(dpToPx(24), dpToPx(24)); + iconParams.setMarginEnd(dpToPx(16)); + iconView.setLayoutParams(iconParams); + itemLayout.addView(iconView); + + // Text container + LinearLayout textContainer = new LinearLayout(mContext); + textContainer.setOrientation(LinearLayout.VERTICAL); + TextView titleView = new TextView(mContext); + titleView.setText(title); + titleView.setTextSize(16); + titleView.setTextColor(ThemeUtils.getForegroundColor()); + textContainer.addView(titleView); + + itemLayout.addView(textContainer); + + return itemLayout; + } + } \ No newline at end of file diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/VideoUtils.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/VideoUtils.java index 68f364829..6d8d407c4 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/VideoUtils.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/VideoUtils.java @@ -63,7 +63,7 @@ public class VideoUtils extends IntentUtils { return builder.toString(); } - private static String getVideoScheme(String videoId, boolean isShorts) { + public static String getVideoScheme(String videoId, boolean isShorts) { return String.format( Locale.ENGLISH, isShorts ? VIDEO_SCHEME_INTENT_FORMAT : VIDEO_SCHEME_LINK_FORMAT, @@ -128,6 +128,22 @@ public class VideoUtils extends IntentUtils { launchView(getChannelUrl(channelId), getContext().getPackageName()); } + public static void openPlaylist(@NonNull String playlistId) { + openPlaylist(playlistId, ""); + } + + public static void openPlaylist(@NonNull String playlistId, @NonNull String videoId) { + final StringBuilder sb = new StringBuilder(); + if (videoId.isEmpty()) { + sb.append(getPlaylistUrl(playlistId)); + } else { + sb.append(getVideoScheme(videoId, false)); + sb.append("&list="); + sb.append(playlistId); + } + launchView(sb.toString(), getContext().getPackageName()); + } + public static void openVideo() { openVideo(VideoInformation.getVideoId()); } @@ -177,8 +193,8 @@ public class VideoUtils extends IntentUtils { } public static void showPlaybackSpeedDialog(@NonNull Context context) { - final String[] playbackSpeedEntries = CustomPlaybackSpeedPatch.getTrimmedListEntries(); - final String[] playbackSpeedEntryValues = CustomPlaybackSpeedPatch.getTrimmedListEntryValues(); + final String[] playbackSpeedEntries = CustomPlaybackSpeedPatch.getTrimmedEntries(); + final String[] playbackSpeedEntryValues = CustomPlaybackSpeedPatch.getTrimmedEntryValues(); final float playbackSpeed = VideoInformation.getPlaybackSpeed(); final int index = Arrays.binarySearch(playbackSpeedEntryValues, String.valueOf(playbackSpeed)); @@ -186,6 +202,7 @@ public class VideoUtils extends IntentUtils { new AlertDialog.Builder(context) .setSingleChoiceItems(playbackSpeedEntries, index, (mDialog, mIndex) -> { final float selectedPlaybackSpeed = Float.parseFloat(playbackSpeedEntryValues[mIndex] + "f"); + VideoInformation.setPlaybackSpeed(selectedPlaybackSpeed); VideoInformation.overridePlaybackSpeed(selectedPlaybackSpeed); userSelectedPlaybackSpeed(selectedPlaybackSpeed); mDialog.dismiss(); diff --git a/extensions/shared/src/main/java/com/google/android/apps/youtube/app/settings/videoquality/VideoQualitySettingsActivity.java b/extensions/shared/src/main/java/com/google/android/apps/youtube/app/settings/videoquality/VideoQualitySettingsActivity.java index a969c6bd4..f9c0c50f3 100644 --- a/extensions/shared/src/main/java/com/google/android/apps/youtube/app/settings/videoquality/VideoQualitySettingsActivity.java +++ b/extensions/shared/src/main/java/com/google/android/apps/youtube/app/settings/videoquality/VideoQualitySettingsActivity.java @@ -1,7 +1,9 @@ package com.google.android.apps.youtube.app.settings.videoquality; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; +import android.content.res.Resources; import android.os.Bundle; import android.util.TypedValue; import android.view.View; @@ -25,8 +27,8 @@ import app.revanced.extension.youtube.utils.ThemeUtils; @SuppressWarnings("deprecation") public class VideoQualitySettingsActivity extends Activity { - private static final String rvxSettingsLabel = ResourceUtils.getString("revanced_extended_settings_title"); - private static final String searchLabel = ResourceUtils.getString("revanced_extended_settings_search_title"); + private static String rvxSettingsLabel; + private static String searchLabel; private static WeakReference searchViewRef = new WeakReference<>(null); private static WeakReference closeButtonRef = new WeakReference<>(null); private ReVancedPreferenceFragment fragment; @@ -71,6 +73,10 @@ public class VideoQualitySettingsActivity extends Activity { return; } + // Set label + rvxSettingsLabel = getString("revanced_extended_settings_title"); + searchLabel = getString("revanced_extended_settings_search_title"); + // Set toolbar setToolbar(); @@ -85,6 +91,14 @@ public class VideoQualitySettingsActivity extends Activity { } } + @SuppressLint("DiscouragedApi") + private String getString(String str) { + Context baseContext = getBaseContext(); + Resources resources = baseContext.getResources(); + int identifier = resources.getIdentifier(str, "string", baseContext.getPackageName()); + return resources.getString(identifier); + } + private void filterPreferences(String query) { if (fragment == null) return; fragment.filterPreferences(query); diff --git a/gradle.properties b/gradle.properties index 8b8969587..fbb95566f 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.5.1 +version = 5.6.1 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4376956d5..616c34f24 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,12 +6,14 @@ smali = "3.0.5" gson = "2.12.1" agp = "8.2.2" annotation = "1.9.1" +collections4 = "4.5.0-M3" lang3 = "3.17.0" preference = "1.2.1" [libraries] gson = { module = "com.google.code.gson:gson", version.ref = "gson" } annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" } +collections4 = { module = "org.apache.commons:commons-collections4", version.ref = "collections4" } lang3 = { module = "org.apache.commons:commons-lang3", version.ref = "lang3" } preference = { module = "androidx.preference:preference", version.ref = "preference" } diff --git a/patches.json b/patches.json index 93b46884c..cff3a24c6 100644 --- a/patches.json +++ b/patches.json @@ -11,13 +11,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -32,13 +30,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -59,7 +55,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -73,13 +69,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -101,7 +95,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -116,13 +110,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -141,7 +133,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -157,13 +149,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -180,13 +170,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -199,7 +187,8 @@ "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [ @@ -227,13 +216,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -256,7 +243,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -272,13 +259,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -300,7 +285,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -314,13 +299,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -356,13 +339,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -394,13 +375,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -457,7 +436,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [ @@ -505,7 +484,8 @@ "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [ @@ -532,13 +512,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -574,7 +552,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [ @@ -617,13 +595,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -647,13 +623,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -687,7 +661,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [ @@ -720,7 +694,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [ @@ -768,13 +742,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -794,7 +766,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -816,7 +788,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -838,7 +810,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -853,13 +825,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -881,11 +851,19 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] }, + { + "name": "Disable edge-to-edge display", + "description": "Disable forced edge-to-edge display on Android 15+ by changing the app\u0027s target SDK version. This patch does not work if the app is installed by mounting.", + "use": false, + "dependencies": [], + "compatiblePackages": null, + "options": [] + }, { "name": "Disable forced auto audio tracks", "description": "Adds an option to disable audio tracks from being automatically enabled.", @@ -895,13 +873,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -923,7 +899,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -938,13 +914,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -958,13 +932,29 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" + ] + }, + "options": [] + }, + { + "name": "Disable layout updates", + "description": "Adds an option to disable layout updates by server.", + "use": true, + "dependencies": [ + "Settings for YouTube" + ], + "compatiblePackages": { + "com.google.android.youtube": [ + "19.05.36", + "19.16.39", + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -987,7 +977,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -1001,13 +991,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1022,13 +1010,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1038,13 +1024,13 @@ "description": "Adds an option to disable the popup that appears when taking a screenshot.", "use": true, "dependencies": [ - "Settings for Reddit", - "ResourcePatch" + "Settings for Reddit" ], "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [] @@ -1059,13 +1045,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1087,7 +1071,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -1102,13 +1086,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1129,7 +1111,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -1143,13 +1125,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1163,13 +1143,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1191,7 +1169,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -1219,7 +1197,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -1233,13 +1211,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1260,13 +1236,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1288,7 +1262,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [ @@ -1348,13 +1322,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -1414,7 +1386,8 @@ "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [] @@ -1428,13 +1401,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1448,13 +1419,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1476,7 +1445,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -1502,7 +1471,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -1519,13 +1488,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1553,7 +1520,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -1568,7 +1535,8 @@ "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [] @@ -1587,13 +1555,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1610,13 +1576,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1637,13 +1601,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1658,13 +1620,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1689,7 +1649,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -1708,13 +1668,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1729,7 +1687,8 @@ "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [] @@ -1751,7 +1710,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -1769,13 +1728,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1793,13 +1750,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1818,7 +1773,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -1833,7 +1788,8 @@ "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [] @@ -1848,13 +1804,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -1905,13 +1859,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1921,19 +1873,18 @@ "description": "Adds support to download videos with an external downloader app using the in-app download button.", "use": true, "dependencies": [ + "BytecodePatch", "BytecodePatch", "ResourcePatch", "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1948,13 +1899,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -1970,13 +1919,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2001,7 +1948,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -2019,13 +1966,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2041,7 +1986,8 @@ "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [] @@ -2057,7 +2003,8 @@ "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [] @@ -2072,13 +2019,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2092,18 +2037,17 @@ "BytecodePatch", "BytecodePatch", "ResourcePatch", + "BytecodePatch", "ResourcePatch", "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -2174,7 +2118,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -2198,13 +2142,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2217,7 +2159,8 @@ "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [] @@ -2238,7 +2181,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -2254,13 +2197,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2275,7 +2216,8 @@ "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [] @@ -2297,7 +2239,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -2312,13 +2254,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2339,7 +2279,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -2361,7 +2301,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -2379,13 +2319,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2408,7 +2346,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -2423,13 +2361,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2451,7 +2387,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -2466,7 +2402,8 @@ "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [] @@ -2481,13 +2418,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2507,13 +2442,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2529,7 +2462,8 @@ "compatiblePackages": { "com.reddit.frontpage": [ "2024.17.0", - "2025.05.1" + "2025.05.1", + "2025.12.0" ] }, "options": [ @@ -2560,13 +2494,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -2632,7 +2564,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [ @@ -2673,13 +2605,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2694,13 +2624,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -2792,7 +2720,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -2808,13 +2736,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -2851,7 +2777,10 @@ "compatiblePackages": { "com.google.android.apps.youtube.music": [ "6.51.53", - "7.16.53" + "7.16.53", + "7.25.53", + "8.05.51", + "8.10.52" ] }, "options": [] @@ -2868,38 +2797,11 @@ ], "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": "Spoof client", - "description": "Adds options to spoof the client to allow playback.", - "use": false, - "dependencies": [ - "Settings for YouTube Music", - "ResourcePatch", - "BytecodePatch", - "ResourcePatch", - "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", - "8.10.51" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2922,7 +2824,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -2940,16 +2842,24 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, - "options": [] + "options": [ + { + "key": "useIOSClient", + "title": "Use iOS client", + "description": "Add setting to set iOS client (Deprecated) as default client.", + "required": false, + "type": "kotlin.Boolean", + "default": false, + "values": null + } + ] }, { "name": "Swipe controls", @@ -2965,13 +2875,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -2986,13 +2894,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -3048,13 +2954,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -3068,13 +2972,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -3093,7 +2995,7 @@ "description": "A list of translations to be added for the RVX settings, separated by commas.", "required": true, "type": "kotlin.String", - "default": "ar, bg-rBG, de-rDE, el-rGR, es-rES, fr-rFR, hu-rHU, it-rIT, ja-rJP, ko-rKR, pl-rPL, pt-rBR, ru-rRU, tr-rTR, uk-rUA, vi-rVN, zh-rCN, zh-rTW", + "default": "ar, bg-rBG, de-rDE, el-rGR, es-rES, fr-rFR, hu-rHU, id-rID, in, it-rIT, ja-rJP, ko-rKR, pl-rPL, pt-rBR, ru-rRU, tr-rTR, uk-rUA, vi-rVN, zh-rCN, zh-rTW", "values": null }, { @@ -3123,7 +3025,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [ @@ -3174,7 +3076,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -3197,13 +3099,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] @@ -3217,13 +3117,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [ @@ -3270,7 +3168,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [ @@ -3308,7 +3206,7 @@ "7.16.53", "7.25.53", "8.05.51", - "8.10.51" + "8.12.53" ] }, "options": [] @@ -3323,13 +3221,11 @@ ], "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" + "19.43.41", + "19.44.39", + "19.47.53" ] }, "options": [] diff --git a/patches/api/patches.api b/patches/api/patches.api index 30b027aeb..11f542538 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -6,6 +6,10 @@ public final class app/revanced/patches/all/misc/connectivity/wifi/spoof/SpoofWi public static final fun getSpoofWifiPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/all/misc/display/edgetoedge/EdgeToEdgeDisplayPatchKt { + public static final fun getEdgeToEdgeDisplayPatch ()Lapp/revanced/patcher/patch/ResourcePatch; +} + 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; @@ -253,6 +257,7 @@ public final class app/revanced/patches/music/utils/resourceid/SharedResourceIdP public static final fun getBottomSheetRecyclerView ()J public static final fun getButtonContainer ()J public static final fun getButtonIconPaddingMedium ()J + public static final fun getChannelHandle ()J public static final fun getChipCloud ()J public static final fun getColorGrey ()J public static final fun getDarkBackground ()J @@ -281,6 +286,7 @@ public final class app/revanced/patches/music/utils/resourceid/SharedResourceIdP public static final fun getPrivacyTosFooter ()J public static final fun getQualityAuto ()J public static final fun getRemixGenericButtonSize ()J + public static final fun getSearchButton ()J public static final fun getSlidingDialogAnimation ()J public static final fun getTapBloomView ()J public static final fun getText1 ()J @@ -452,10 +458,6 @@ public final class app/revanced/patches/reddit/utils/extension/SharedExtensionPa public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } -public final class app/revanced/patches/reddit/utils/resourceid/SharedResourceIdPatchKt { - public static final fun getScreenShotShareBanner ()J -} - public final class app/revanced/patches/reddit/utils/settings/SettingsPatchKt { public static final fun getSettingsPatch ()Lapp/revanced/patcher/patch/ResourcePatch; public static final fun is_2024_26_or_greater ()Z @@ -550,10 +552,9 @@ public final class app/revanced/patches/shared/mapping/ResourceElement { } public final class app/revanced/patches/shared/mapping/ResourceMappingPatchKt { - public static final fun get (Ljava/util/List;Lapp/revanced/patches/shared/mapping/ResourceType;Ljava/lang/String;)J - public static final fun get (Ljava/util/List;Ljava/lang/String;Ljava/lang/String;)J + public static final fun getResourceId (Lapp/revanced/patches/shared/mapping/ResourceType;Ljava/lang/String;)J + public static final fun getResourceId (Ljava/lang/String;Ljava/lang/String;)J public static final fun getResourceMappingPatch ()Lapp/revanced/patcher/patch/ResourcePatch; - public static final fun getResourceMappings ()Ljava/util/List; } public final class app/revanced/patches/shared/mapping/ResourceType : java/lang/Enum { @@ -715,6 +716,10 @@ public final class app/revanced/patches/youtube/general/toolbar/ToolBarComponent public static final fun getToolBarComponentsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/youtube/general/updates/LayoutUpdatesPatchKt { + public static final fun getLayoutUpdatesPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/youtube/layout/actionbuttons/ShortsActionButtonsPatchKt { public static final fun getShortsActionButtonsPatch ()Lapp/revanced/patcher/patch/ResourcePatch; } @@ -866,6 +871,7 @@ public final class app/revanced/patches/youtube/player/seekbar/SeekbarComponents public final class app/revanced/patches/youtube/shorts/components/FingerprintsKt { public static final fun indexOfAddLiveHeaderElementsContainerInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I + public static final fun indexOfDismissInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I } public final class app/revanced/patches/youtube/shorts/components/ShortsComponentPatchKt { @@ -1013,6 +1019,10 @@ public final class app/revanced/patches/youtube/utils/playertype/PlayerTypeHookP public static final fun getPlayerTypeHookPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/youtube/utils/playlist/PlaylistPatchKt { + public static final fun getPlaylistPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/youtube/utils/playservice/VersionCheckPatchKt { public static final fun getVersionCheckPatch ()Lapp/revanced/patcher/patch/ResourcePatch; public static final fun is_18_31_or_greater ()Z @@ -1020,12 +1030,16 @@ public final class app/revanced/patches/youtube/utils/playservice/VersionCheckPa public static final fun is_18_39_or_greater ()Z public static final fun is_18_42_or_greater ()Z public static final fun is_18_49_or_greater ()Z + public static final fun is_19_01_or_greater ()Z public static final fun is_19_02_or_greater ()Z public static final fun is_19_04_or_greater ()Z + public static final fun is_19_05_or_greater ()Z public static final fun is_19_09_or_greater ()Z + public static final fun is_19_11_or_greater ()Z public static final fun is_19_15_or_greater ()Z public static final fun is_19_16_or_greater ()Z public static final fun is_19_17_or_greater ()Z + public static final fun is_19_18_or_greater ()Z public static final fun is_19_23_or_greater ()Z public static final fun is_19_25_or_greater ()Z public static final fun is_19_26_or_greater ()Z @@ -1040,10 +1054,13 @@ public final class app/revanced/patches/youtube/utils/playservice/VersionCheckPa public static final fun is_19_44_or_greater ()Z 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_19_50_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 static final fun is_20_09_or_greater ()Z public static final fun is_20_10_or_greater ()Z + public static final fun is_20_12_or_greater ()Z } public final class app/revanced/patches/youtube/utils/recyclerview/RecyclerViewTreeObserverPatchKt { @@ -1145,7 +1162,6 @@ public final class app/revanced/patches/youtube/utils/resourceid/SharedResourceI public static final fun getRelatedChipCloudMargin ()J public static final fun getRightComment ()J public static final fun getScrimOverlay ()J - public static final fun getScrubbing ()J public static final fun getSeekEasyHorizontalTouchOffsetToStartScrubbing ()J public static final fun getSeekUndoEduOverlayStub ()J public static final fun getSettingsFragment ()J @@ -1160,13 +1176,17 @@ public final class app/revanced/patches/youtube/utils/resourceid/SharedResourceI public static final fun getTotalTime ()J public static final fun getTouchArea ()J public static final fun getVarispeedUnavailableTitle ()J + public static final fun getVerticalTouchOffsetToEnterFineScrubbing ()J + public static final fun getVerticalTouchOffsetToStartFineScrubbing ()J public static final fun getVideoQualityBottomSheet ()J public static final fun getVideoQualityUnavailableAnnouncement ()J public static final fun getVideoZoomSnapIndicator ()J public static final fun getVoiceSearch ()J public static final fun getYouTubeControlsOverlaySubtitleButton ()J public static final fun getYouTubeLogo ()J + public static final fun getYtCallToAction ()J public static final fun getYtFillBell ()J + public static final fun getYtOutlineLibrary ()J public static final fun getYtOutlineMoonZ ()J public static final fun getYtOutlinePictureInPictureWhite ()J public static final fun getYtOutlineVideoCamera ()J 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 index 3758030cd..354e91f3e 100644 --- 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 @@ -152,7 +152,10 @@ private enum class MethodCall( RegisterNetworkCallback1( "Landroid/net/ConnectivityManager;", "registerNetworkCallback", - arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"), + arrayOf( + "Landroid/net/NetworkRequest;", + "Landroid/net/ConnectivityManager\$NetworkCallback;" + ), "V", ), RegisterNetworkCallback2( @@ -174,13 +177,20 @@ private enum class MethodCall( RequestNetwork1( "Landroid/net/ConnectivityManager;", "requestNetwork", - arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;"), + arrayOf( + "Landroid/net/NetworkRequest;", + "Landroid/net/ConnectivityManager\$NetworkCallback;" + ), "V", ), RequestNetwork2( "Landroid/net/ConnectivityManager;", "requestNetwork", - arrayOf("Landroid/net/NetworkRequest;", "Landroid/net/ConnectivityManager\$NetworkCallback;", "I"), + arrayOf( + "Landroid/net/NetworkRequest;", + "Landroid/net/ConnectivityManager\$NetworkCallback;", + "I" + ), "V", ), RequestNetwork3( diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/display/edgetoedge/EdgeToEdgeDisplayPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/display/edgetoedge/EdgeToEdgeDisplayPatch.kt new file mode 100644 index 000000000..9fdb3fd84 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/display/edgetoedge/EdgeToEdgeDisplayPatch.kt @@ -0,0 +1,39 @@ +package app.revanced.patches.all.misc.display.edgetoedge + +import app.revanced.patcher.patch.resourcePatch +import app.revanced.util.Utils.printWarn +import app.revanced.util.getNode +import org.w3c.dom.Element + +@Suppress("unused") +val edgeToEdgeDisplayPatch = resourcePatch( + name = "Disable edge-to-edge display", + description = "Disable forced edge-to-edge display on Android 15+ by changing the app's target SDK version. " + + "This patch does not work if the app is installed by mounting.", + use = false, +) { + execute { + document("AndroidManifest.xml").use { document -> + // Ideally, the patch should only be applied when targetSdkVersion is 35 or greater. + // Since ApkTool does not add targetSdkVersion to AndroidManifest, there is no way to check targetSdkVersion. + // Instead, it checks compileSdkVersion and prints a warning. + try { + val manifestElement = document.getNode("manifest") as Element + val compileSdkVersion = + Integer.parseInt(manifestElement.getAttribute("android:compileSdkVersion")) + if (compileSdkVersion < 35) { + printWarn("This app may not be forcing edge to edge display (compileSdkVersion: $compileSdkVersion)") + } + } catch (_: Exception) { + printWarn("Failed to check compileSdkVersion") + } + + // Change targetSdkVersion to 34. + document.getElementsByTagName("manifest").item(0).also { + it.appendChild(it.ownerDocument.createElement("uses-sdk").also { element -> + element.setAttribute("android:targetSdkVersion", "34") + }) + } + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/music/account/components/AccountComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/account/components/AccountComponentsPatch.kt index f15dc184b..f5a9970cd 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/account/components/AccountComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/account/components/AccountComponentsPatch.kt @@ -4,23 +4,27 @@ 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.replaceInstruction +import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.music.utils.extension.Constants.ACCOUNT_CLASS_DESCRIPTOR import app.revanced.patches.music.utils.patch.PatchList.HIDE_ACCOUNT_COMPONENTS +import app.revanced.patches.music.utils.resourceid.channelHandle import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch 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.util.fingerprint.matchOrThrow import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.getReference 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 import com.android.tools.smali.dexlib2.iface.reference.MethodReference @Suppress("unused") @@ -84,17 +88,50 @@ val accountComponentsPatch = bytecodePatch( } // account switcher - namesInactiveAccountThumbnailSizeFingerprint.matchOrThrow().let { - it.method.apply { - val targetIndex = it.patternMatch!!.startIndex - val targetRegister = getInstruction(targetIndex).registerA + val textViewField = with( + channelHandleFingerprint + .methodOrThrow(namesInactiveAccountThumbnailSizeFingerprint) + ) { + val literalIndex = indexOfFirstLiteralInstructionOrThrow(channelHandle) + getInstruction( + indexOfFirstInstructionOrThrow(literalIndex) { + opcode == Opcode.IPUT_OBJECT && + getReference()?.type == "Landroid/widget/TextView;" + }, + ).getReference() + } - addInstructions( - targetIndex, """ - invoke-static {v$targetRegister}, $ACCOUNT_CLASS_DESCRIPTOR->hideHandle(Z)Z - move-result v$targetRegister - """ - ) + namesInactiveAccountThumbnailSizeFingerprint.methodOrThrow().apply { + var hook = false + + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + val reference = + (instruction as? ReferenceInstruction)?.reference + instruction.opcode == Opcode.IGET_OBJECT && + reference is FieldReference && + reference == textViewField + } + .map { (index, _) -> index } + .forEach { index -> + val insertIndex = index - 1 + if (!hook && getInstruction(insertIndex).opcode == Opcode.IF_NEZ) { + val insertRegister = + getInstruction(insertIndex).registerA + + addInstructions( + insertIndex, """ + invoke-static {v$insertRegister}, $ACCOUNT_CLASS_DESCRIPTOR->hideHandle(Z)Z + move-result v$insertRegister + """ + ) + hook = true + } + } + + if (!hook) { + throw PatchException("Could not find TextUtils.isEmpty() index") } } diff --git a/patches/src/main/kotlin/app/revanced/patches/music/account/components/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/account/components/Fingerprints.kt index 8af714611..bb28af644 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/account/components/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/account/components/Fingerprints.kt @@ -1,11 +1,11 @@ package app.revanced.patches.music.account.components import app.revanced.patches.music.utils.resourceid.accountSwitcherAccessibility +import app.revanced.patches.music.utils.resourceid.channelHandle import app.revanced.patches.music.utils.resourceid.menuEntry import app.revanced.patches.music.utils.resourceid.namesInactiveAccountThumbnailSize import app.revanced.patches.music.utils.resourceid.tosFooter import app.revanced.util.fingerprint.legacyFingerprint -import com.android.tools.smali.dexlib2.Opcode internal val accountSwitcherAccessibilityLabelFingerprint = legacyFingerprint( name = "accountSwitcherAccessibilityLabelFingerprint", @@ -14,6 +14,12 @@ internal val accountSwitcherAccessibilityLabelFingerprint = legacyFingerprint( literals = listOf(accountSwitcherAccessibility) ) +internal val channelHandleFingerprint = legacyFingerprint( + name = "channelHandleFingerprint", + returnType = "V", + literals = listOf(channelHandle), +) + internal val menuEntryFingerprint = legacyFingerprint( name = "menuEntryFingerprint", returnType = "V", @@ -24,19 +30,6 @@ internal val namesInactiveAccountThumbnailSizeFingerprint = legacyFingerprint( name = "namesInactiveAccountThumbnailSizeFingerprint", returnType = "V", parameters = listOf("L", "Ljava/lang/Object;"), - opcodes = listOf( - Opcode.IF_NEZ, - Opcode.IGET_OBJECT, - Opcode.INVOKE_VIRTUAL, - Opcode.IGET_OBJECT, - Opcode.INVOKE_VIRTUAL, - Opcode.GOTO, - Opcode.IGET_OBJECT, - Opcode.INVOKE_VIRTUAL, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT, - Opcode.IF_EQZ - ), literals = listOf(namesInactiveAccountThumbnailSize) ) 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 23df8ddbd..84381b787 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 @@ -115,7 +115,8 @@ val adsPatch = bytecodePatch( .methodOrThrow(getPremiumDialogParentFingerprint) .apply { val setContentViewIndex = indexOfSetContentViewInstruction(this) - val dialogInstruction = getInstruction(setContentViewIndex) + val dialogInstruction = + getInstruction(setContentViewIndex) val dialogRegister = dialogInstruction.registerC val viewRegister = dialogInstruction.registerD 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 a70e8e57a..8c05ff53a 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 @@ -97,8 +97,6 @@ internal val showDialogCommandFingerprint = legacyFingerprint( name = "showDialogCommandFingerprint", returnType = "V", opcodes = listOf( - Opcode.IF_EQ, - Opcode.IGET_OBJECT, Opcode.INVOKE_VIRTUAL, Opcode.IGET, // get dialog code ), diff --git a/patches/src/main/kotlin/app/revanced/patches/music/general/components/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/general/components/Fingerprints.kt index 664966147..319724ff3 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/general/components/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/general/components/Fingerprints.kt @@ -5,6 +5,7 @@ import app.revanced.patches.music.utils.resourceid.historyMenuItem import app.revanced.patches.music.utils.resourceid.musicTasteBuilderShelf import app.revanced.patches.music.utils.resourceid.offlineSettingsMenuItem import app.revanced.patches.music.utils.resourceid.playerOverlayChip +import app.revanced.patches.music.utils.resourceid.searchButton import app.revanced.patches.music.utils.resourceid.toolTipContentView import app.revanced.patches.music.utils.resourceid.topBarMenuItemImageView import app.revanced.util.fingerprint.legacyFingerprint @@ -118,6 +119,17 @@ internal val preferenceScreenFingerprint = legacyFingerprint( } ) +internal val searchActionViewFingerprint = legacyFingerprint( + name = "searchActionViewFingerprint", + returnType = "Landroid/view/View;", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = emptyList(), + literals = listOf(searchButton), + customFingerprint = { _, classDef -> + classDef.type.endsWith("/SearchActionProvider;") + } +) + internal val searchBarFingerprint = legacyFingerprint( name = "searchBarFingerprint", returnType = "V", diff --git a/patches/src/main/kotlin/app/revanced/patches/music/general/components/LayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/general/components/LayoutComponentsPatch.kt index 3154b1828..ad11402eb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/general/components/LayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/general/components/LayoutComponentsPatch.kt @@ -20,6 +20,7 @@ import app.revanced.patches.music.utils.playservice.is_8_05_or_greater import app.revanced.patches.music.utils.playservice.versionCheckPatch import app.revanced.patches.music.utils.resourceid.musicTasteBuilderShelf import app.revanced.patches.music.utils.resourceid.playerOverlayChip +import app.revanced.patches.music.utils.resourceid.searchButton import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch import app.revanced.patches.music.utils.resourceid.topBarMenuItemImageView import app.revanced.patches.music.utils.settings.CategoryType @@ -198,6 +199,23 @@ val layoutComponentsPatch = bytecodePatch( // endregion + // region patch for hide search button + + searchActionViewFingerprint.methodOrThrow().apply { + val constIndex = + indexOfFirstLiteralInstructionOrThrow(searchButton) + val targetIndex = + indexOfFirstInstructionOrThrow(constIndex, Opcode.MOVE_RESULT_OBJECT) + val targetRegister = getInstruction(targetIndex).registerA + + addInstruction( + targetIndex + 1, + "invoke-static {v$targetRegister}, $GENERAL_CLASS_DESCRIPTOR->hideSearchButton(Landroid/view/View;)V" + ) + } + + // endregion + // region patch for hide sound search button if (is_6_48_or_greater) { @@ -353,6 +371,11 @@ val layoutComponentsPatch = bytecodePatch( "revanced_hide_samples_shelf", "false" ) + addSwitchPreference( + CategoryType.GENERAL, + "revanced_hide_search_button", + "false" + ) if (is_6_48_or_greater) { addSwitchPreference( CategoryType.GENERAL, 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 f910e84d1..895555ee0 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 @@ -32,25 +32,32 @@ private val spoofAppVersionBytecodePatch = bytecodePatch( ) execute { - if (!is_6_43_or_greater || is_7_25_or_greater) { + if (!is_6_43_or_greater) { return@execute } - if (is_7_17_or_greater) { - findMethodOrThrow(PATCH_STATUS_CLASS_DESCRIPTOR) { - name == "SpoofAppVersionDefaultString" - }.replaceInstruction( - 0, - "const-string v0, \"7.16.53\"" - ) + var defaultVersionString = "6.42.55" + + if (is_7_17_or_greater && !is_7_25_or_greater) { + defaultVersionString = "7.16.53" + defaultValue = "true" + findMethodOrThrow(PATCH_STATUS_CLASS_DESCRIPTOR) { name == "SpoofAppVersionDefaultBoolean" }.replaceInstruction( 0, "const/4 v0, 0x1" ) - - defaultValue = "true" } + if (is_7_25_or_greater) { + defaultVersionString = "7.17.52" + } + + findMethodOrThrow(PATCH_STATUS_CLASS_DESCRIPTOR) { + name == "SpoofAppVersionDefaultString" + }.replaceInstruction( + 0, + "const-string v0, \"$defaultVersionString\"" + ) } } @@ -63,6 +70,9 @@ val spoofAppVersionPatch = resourcePatch( YOUTUBE_MUSIC_PACKAGE_NAME( "6.51.53", "7.16.53", + "7.25.53", + "8.05.51", + "8.10.52", ), ) @@ -73,13 +83,19 @@ val spoofAppVersionPatch = resourcePatch( ) execute { - 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.") + if (!is_6_43_or_greater) { + printWarn("\"${SPOOF_APP_VERSION.title}\" is not supported in this version. Use YouTube Music 6.51.53 or later.") return@execute } - if (is_7_17_or_greater) { + if (!is_7_17_or_greater) { + appendAppVersion("6.42.55") + } + if (is_7_17_or_greater && !is_7_25_or_greater) { appendAppVersion("7.16.53") } + if (is_7_25_or_greater) { + appendAppVersion("7.17.52") + } addSwitchPreference( CategoryType.GENERAL, diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/header/ChangeHeaderPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/header/ChangeHeaderPatch.kt index 7d0b38f5a..3ae23745c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/layout/header/ChangeHeaderPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/header/ChangeHeaderPatch.kt @@ -206,7 +206,8 @@ val changeHeaderPatch = resourcePatch( printWarn(warnings) } - val isLegacyLogoExists = get("res").resolve("drawable-xxhdpi").resolve("ytm_logo.png").exists() + val isLegacyLogoExists = + get("res").resolve("drawable-xxhdpi").resolve("ytm_logo.png").exists() if (is_7_27_or_greater && isLegacyLogoExists) { document("res/layout/signin_fragment.xml").use { document -> document.doRecursively node@{ node -> diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/splash/CairoSplashAnimationPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/splash/CairoSplashAnimationPatch.kt index d918c8163..c2e71c3bf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/splash/CairoSplashAnimationPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/splash/CairoSplashAnimationPatch.kt @@ -24,7 +24,7 @@ import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionReversedOrThrow 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.reference.MethodReference private const val EXTENSION_METHOD_DESCRIPTOR = @@ -41,7 +41,7 @@ val cairoSplashAnimationPatch = bytecodePatch( "7.16.53", "7.25.53", "8.05.51", - "8.10.51", + "8.12.53", ), ) @@ -57,7 +57,7 @@ val cairoSplashAnimationPatch = bytecodePatch( return@execute } else if (!is_7_20_or_greater) { cairoSplashAnimationConfigFingerprint.injectLiteralInstructionBooleanCall( - 45635386L, + CAIRO_SPLASH_ANIMATION_FEATURE_FLAG, EXTENSION_METHOD_DESCRIPTOR ) } else { @@ -69,18 +69,13 @@ val cairoSplashAnimationPatch = bytecodePatch( opcode == Opcode.INVOKE_VIRTUAL && getReference()?.name == "setContentView" } + 1 - val viewStubFindViewByIdIndex = indexOfFirstInstructionOrThrow(literalIndex) { - val reference = getReference() - opcode == Opcode.INVOKE_VIRTUAL && - reference?.name == "findViewById" && - reference.definingClass != "Landroid/view/View;" - } + val freeIndex = indexOfFirstInstructionOrThrow(insertIndex, Opcode.CONST) val freeRegister = - getInstruction(viewStubFindViewByIdIndex).registerD - val jumpIndex = indexOfFirstInstructionReversedOrThrow( - viewStubFindViewByIdIndex, - Opcode.IGET_OBJECT - ) + getInstruction(freeIndex).registerA + val jumpIndex = indexOfFirstInstructionOrThrow(insertIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.parameterTypes?.firstOrNull() == "Ljava/lang/Runnable;" + } + 1 addInstructionsWithLabels( insertIndex, """ diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/splash/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/splash/Fingerprints.kt index 05fbdf843..0c04718df 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/splash/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/splash/Fingerprints.kt @@ -5,6 +5,8 @@ import app.revanced.patches.music.utils.resourceid.mainActivityLaunchAnimation import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.indexOfFirstLiteralInstruction +internal const val CAIRO_SPLASH_ANIMATION_FEATURE_FLAG = 45635386L + /** * This fingerprint is compatible with YouTube Music v7.06.53+ */ @@ -20,7 +22,7 @@ internal val cairoSplashAnimationConfigFingerprint = legacyFingerprint( if (is_7_20_or_greater) { method.indexOfFirstLiteralInstruction(mainActivityLaunchAnimation) >= 0 } else { - method.indexOfFirstLiteralInstruction(45635386) >= 0 + method.indexOfFirstLiteralInstruction(CAIRO_SPLASH_ANIMATION_FEATURE_FLAG) >= 0 } } ) 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 index fdc321bca..9eb30851d 100644 --- 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 @@ -1,13 +1,13 @@ 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 +import app.revanced.patches.shared.trackingurlhook.hookWatchHistory +import app.revanced.patches.shared.trackingurlhook.trackingUrlHookPatch @Suppress("unused") val watchHistoryPatch = bytecodePatch( diff --git a/patches/src/main/kotlin/app/revanced/patches/music/navigation/components/NavigationBarComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/navigation/components/NavigationBarComponentsPatch.kt index e4b459e25..8cc05af21 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/navigation/components/NavigationBarComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/navigation/components/NavigationBarComponentsPatch.kt @@ -124,14 +124,17 @@ val navigationBarComponentsPatch = bytecodePatch( opcode == Opcode.IGET_OBJECT && getReference()?.type == "Ljava/lang/String;" } - val browseIdReference = getInstruction(browseIdIndex).reference as FieldReference + val browseIdReference = + getInstruction(browseIdIndex).reference as FieldReference val fieldName = browseIdReference.name val componentIndex = indexOfFirstInstructionOrThrow(stringIndex) { opcode == Opcode.IGET_OBJECT && getReference()?.toString() == browseIdReference.toString() } - val browseIdRegister = getInstruction(componentIndex).registerA - val componentRegister = getInstruction(componentIndex).registerB + val browseIdRegister = + getInstruction(componentIndex).registerA + val componentRegister = + getInstruction(componentIndex).registerB val enumIndex = it.patternMatch!!.startIndex + 3 val enumRegister = getInstruction(enumIndex).registerA 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 20921e3c4..be66489ad 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 @@ -54,7 +54,7 @@ internal val engagementPanelHeightFingerprint = legacyFingerprint( parameters = emptyList(), customFingerprint = { method, _ -> AccessFlags.FINAL.isSet(method.accessFlags) && - method.containsLiteralInstruction(1) && + method.containsLiteralInstruction(1) && method.indexOfFirstInstruction { opcode == Opcode.INVOKE_VIRTUAL && getReference()?.name == "booleanValue" 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 c39664845..cbc3ceffe 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 @@ -746,15 +746,22 @@ val playerComponentsPatch = bytecodePatch( val freeRegister = getInstruction(bottomSheetBehaviorIndex).registerD + val getFieldIndex = bottomSheetBehaviorIndex - 2 + val getFieldReference = + getInstruction(getFieldIndex).reference + val getFieldInstruction = getInstruction(getFieldIndex) + addInstructionsWithLabels( - bottomSheetBehaviorIndex - 2, + getFieldIndex + 1, """ invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->enableSwipeToDismissMiniPlayer()Z move-result v$freeRegister if-nez v$freeRegister, :dismiss + iget-object v${getFieldInstruction.registerA}, v${getFieldInstruction.registerB}, $getFieldReference """, ExternalLabel("dismiss", getInstruction(bottomSheetBehaviorIndex + 1)) ) + removeInstruction(getFieldIndex) } ?: throw PatchException("Could not find targetMethod") } 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 3169e345f..eb3882209 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 @@ -16,7 +16,7 @@ internal object Constants { "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.05.51", // This was the latest version supported by the previous RVX patch. - "8.10.51", // This is the latest version supported by the RVX patch. + "8.12.53", // 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/extension/SharedExtensionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/extension/SharedExtensionPatch.kt index dd6883783..7e155f261 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/extension/SharedExtensionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/extension/SharedExtensionPatch.kt @@ -1,14 +1,8 @@ package app.revanced.patches.music.utils.extension import app.revanced.patches.music.utils.extension.hooks.applicationInitHook -import app.revanced.patches.music.utils.extension.hooks.mainActivityBaseContextHook -import app.revanced.patches.shared.extension.hooks.cronetEngineContextHook -import app.revanced.patches.shared.extension.hooks.firebaseInitProviderContextHook import app.revanced.patches.shared.extension.sharedExtensionPatch val sharedExtensionPatch = sharedExtensionPatch( applicationInitHook, - cronetEngineContextHook, - firebaseInitProviderContextHook, - mainActivityBaseContextHook, ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/extension/hooks/MainActivityBaseContextHook.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/extension/hooks/MainActivityBaseContextHook.kt deleted file mode 100644 index 549eff2ba..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/extension/hooks/MainActivityBaseContextHook.kt +++ /dev/null @@ -1,32 +0,0 @@ -package app.revanced.patches.music.utils.extension.hooks - -import app.revanced.patches.shared.extension.extensionHook -import app.revanced.util.getReference -import app.revanced.util.indexOfFirstInstructionOrThrow -import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction -import com.android.tools.smali.dexlib2.iface.reference.MethodReference - -private var attachBaseContextIndex = -1 - -internal val mainActivityBaseContextHook = extensionHook( - insertIndexResolver = { method -> - attachBaseContextIndex = method.indexOfFirstInstructionOrThrow { - getReference()?.name == "attachBaseContext" - } - - attachBaseContextIndex + 1 - }, - contextRegisterResolver = { method -> - val overrideInstruction = - method.implementation!!.instructions.elementAt(attachBaseContextIndex) - as FiveRegisterInstruction - "v${overrideInstruction.registerD}" - }, -) { - returns("V") - parameters("Landroid/content/Context;") - custom { method, classDef -> - classDef.type == "Lcom/google/android/apps/youtube/music/activities/MusicActivity;" && - method.name == "attachBaseContext" - } -} 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 840bf6be2..2a77ff5d3 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 @@ -22,7 +22,6 @@ 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 @@ -31,6 +30,7 @@ import app.revanced.patches.shared.indexOfBrandInstruction import app.revanced.patches.shared.indexOfManufacturerInstruction import app.revanced.patches.shared.indexOfModelInstruction import app.revanced.patches.shared.indexOfReleaseInstruction +import app.revanced.patches.shared.spoof.blockrequest.blockRequestPatch import app.revanced.util.findMethodOrThrow import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall import app.revanced.util.fingerprint.matchOrThrow @@ -63,9 +63,11 @@ private const val CLIENT_INFO_CLASS_DESCRIPTOR = @Suppress("unused") val spoofClientPatch = bytecodePatch( - SPOOF_CLIENT.title, - SPOOF_CLIENT.summary, - false, + // Removed from the patch list to avoid user confusion: + // https://github.com/inotia00/ReVanced_Extended/issues/2832#issuecomment-2745941171 + // SPOOF_CLIENT.title, + // SPOOF_CLIENT.summary, + // false, ) { compatibleWith(COMPATIBLE_PACKAGE) 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 e1b1bce52..7d0682188 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 @@ -9,9 +9,8 @@ import app.revanced.patches.shared.mapping.ResourceType.ID import app.revanced.patches.shared.mapping.ResourceType.LAYOUT import app.revanced.patches.shared.mapping.ResourceType.STRING import app.revanced.patches.shared.mapping.ResourceType.STYLE -import app.revanced.patches.shared.mapping.get +import app.revanced.patches.shared.mapping.getResourceId import app.revanced.patches.shared.mapping.resourceMappingPatch -import app.revanced.patches.shared.mapping.resourceMappings var accountSwitcherAccessibility = -1L private set @@ -25,6 +24,8 @@ var buttonContainer = -1L private set var buttonIconPaddingMedium = -1L private set +var channelHandle = -1L + private set var chipCloud = -1L private set var colorGrey = -1L @@ -83,6 +84,8 @@ var qualityAuto = -1L private set var remixGenericButtonSize = -1L private set +var searchButton = -1L + private set var slidingDialogAnimation = -1L private set var tapBloomView = -1L @@ -124,213 +127,61 @@ internal val sharedResourceIdPatch = resourcePatch( dependsOn(resourceMappingPatch) execute { - accountSwitcherAccessibility = resourceMappings[ - STRING, - "account_switcher_accessibility_label", - ] - actionBarLogo = resourceMappings[ - DRAWABLE, - "action_bar_logo", - ] - actionBarLogoRingo2 = resourceMappings[ - DRAWABLE, - "action_bar_logo_ringo2", - ] - bottomSheetRecyclerView = resourceMappings[ - LAYOUT, - "bottom_sheet_recycler_view" - ] - buttonContainer = resourceMappings[ - ID, - "button_container" - ] - buttonIconPaddingMedium = resourceMappings[ - DIMEN, - "button_icon_padding_medium" - ] - chipCloud = resourceMappings[ - LAYOUT, - "chip_cloud" - ] - colorGrey = resourceMappings[ - COLOR, - "ytm_color_grey_12" - ] - darkBackground = resourceMappings[ - ID, - "dark_background" - ] - designBottomSheetDialog = resourceMappings[ - LAYOUT, - "design_bottom_sheet_dialog" - ] - elementsContainer = resourceMappings[ - ID, - "elements_container" - ] - endButtonsContainer = resourceMappings[ - ID, - "end_buttons_container" - ] - floatingLayout = resourceMappings[ - ID, - "floating_layout" - ] - historyMenuItem = resourceMappings[ - ID, - "history_menu_item" - ] - inlineTimeBarAdBreakMarkerColor = resourceMappings[ - COLOR, - "inline_time_bar_ad_break_marker_color" - ] - inlineTimeBarProgressColor = resourceMappings[ - COLOR, - "inline_time_bar_progress_color" - ] - interstitialsContainer = resourceMappings[ - ID, - "interstitials_container" - ] - isTablet = resourceMappings[ - BOOL, - "is_tablet" - ] - likeDislikeContainer = resourceMappings[ - ID, - "like_dislike_container" - ] - mainActivityLaunchAnimation = resourceMappings[ - LAYOUT, - "main_activity_launch_animation" - ] - menuEntry = resourceMappings[ - LAYOUT, - "menu_entry" - ] - miniPlayerDefaultText = resourceMappings[ - STRING, - "mini_player_default_text" - ] - miniPlayerMdxPlaying = resourceMappings[ - STRING, - "mini_player_mdx_playing" - ] - miniPlayerPlayPauseReplayButton = resourceMappings[ - ID, - "mini_player_play_pause_replay_button" - ] - miniPlayerViewPager = resourceMappings[ - ID, - "mini_player_view_pager" - ] - modernDialogBackground = resourceMappings[ - DRAWABLE, - "modern_dialog_background" - ] - musicNotifierShelf = resourceMappings[ - LAYOUT, - "music_notifier_shelf" - ] - musicTasteBuilderShelf = resourceMappings[ - LAYOUT, - "music_tastebuilder_shelf" - ] - namesInactiveAccountThumbnailSize = resourceMappings[ - DIMEN, - "names_inactive_account_thumbnail_size" - ] - offlineSettingsMenuItem = resourceMappings[ - ID, - "offline_settings_menu_item" - ] - playerOverlayChip = resourceMappings[ - ID, - "player_overlay_chip" - ] - playerViewPager = resourceMappings[ - ID, - "player_view_pager" - ] - privacyTosFooter = resourceMappings[ - ID, - "privacy_tos_footer" - ] - qualityAuto = resourceMappings[ - STRING, - "quality_auto" - ] - remixGenericButtonSize = resourceMappings[ - DIMEN, - "remix_generic_button_size" - ] - slidingDialogAnimation = resourceMappings[ - STYLE, - "SlidingDialogAnimation" - ] - tapBloomView = resourceMappings[ - ID, - "tap_bloom_view" - ] - text1 = resourceMappings[ - ID, - "text1" - ] - toolTipContentView = resourceMappings[ - LAYOUT, - "tooltip_content_view" - ] - topEnd = resourceMappings[ - ID, - "TOP_END" - ] - topStart = resourceMappings[ - ID, - "TOP_START" - ] - topBarMenuItemImageView = resourceMappings[ - ID, - "top_bar_menu_item_image_view" - ] - tosFooter = resourceMappings[ - ID, - "tos_footer" - ] - touchOutside = resourceMappings[ - ID, - "touch_outside" - ] - trimSilenceSwitch = resourceMappings[ - ID, - "trim_silence_switch" - ] - varispeedUnavailableTitle = resourceMappings[ - STRING, - "varispeed_unavailable_title" - ] - ytFillSamples = resourceMappings[ - DRAWABLE, - "yt_fill_samples_vd_theme_24", - ] - ytFillYouTubeMusic = resourceMappings[ - DRAWABLE, - "yt_fill_youtube_music_vd_theme_24", - ] - ytOutlineSamples = resourceMappings[ - DRAWABLE, - "yt_outline_samples_vd_theme_24", - ] - ytOutlineYouTubeMusic = resourceMappings[ - DRAWABLE, - "yt_outline_youtube_music_vd_theme_24", - ] - ytmLogo = resourceMappings[ - DRAWABLE, - "ytm_logo", - ] - ytmLogoRingo2 = resourceMappings[ - DRAWABLE, - "ytm_logo_ringo2", - ] + accountSwitcherAccessibility = getResourceId(STRING, "account_switcher_accessibility_label") + actionBarLogo = getResourceId(DRAWABLE, "action_bar_logo") + actionBarLogoRingo2 = getResourceId(DRAWABLE, "action_bar_logo_ringo2") + bottomSheetRecyclerView = getResourceId(LAYOUT, "bottom_sheet_recycler_view") + buttonContainer = getResourceId(ID, "button_container") + buttonIconPaddingMedium = getResourceId(DIMEN, "button_icon_padding_medium") + channelHandle = getResourceId(ID, "channel_handle") + chipCloud = getResourceId(LAYOUT, "chip_cloud") + colorGrey = getResourceId(COLOR, "ytm_color_grey_12") + darkBackground = getResourceId(ID, "dark_background") + designBottomSheetDialog = getResourceId(LAYOUT, "design_bottom_sheet_dialog") + elementsContainer = getResourceId(ID, "elements_container") + endButtonsContainer = getResourceId(ID, "end_buttons_container") + floatingLayout = getResourceId(ID, "floating_layout") + historyMenuItem = getResourceId(ID, "history_menu_item") + inlineTimeBarAdBreakMarkerColor = + getResourceId(COLOR, "inline_time_bar_ad_break_marker_color") + inlineTimeBarProgressColor = getResourceId(COLOR, "inline_time_bar_progress_color") + interstitialsContainer = getResourceId(ID, "interstitials_container") + isTablet = getResourceId(BOOL, "is_tablet") + likeDislikeContainer = getResourceId(ID, "like_dislike_container") + mainActivityLaunchAnimation = getResourceId(LAYOUT, "main_activity_launch_animation") + menuEntry = getResourceId(LAYOUT, "menu_entry") + miniPlayerDefaultText = getResourceId(STRING, "mini_player_default_text") + miniPlayerMdxPlaying = getResourceId(STRING, "mini_player_mdx_playing") + miniPlayerPlayPauseReplayButton = getResourceId(ID, "mini_player_play_pause_replay_button") + miniPlayerViewPager = getResourceId(ID, "mini_player_view_pager") + modernDialogBackground = getResourceId(DRAWABLE, "modern_dialog_background") + musicNotifierShelf = getResourceId(LAYOUT, "music_notifier_shelf") + musicTasteBuilderShelf = getResourceId(LAYOUT, "music_tastebuilder_shelf") + namesInactiveAccountThumbnailSize = + getResourceId(DIMEN, "names_inactive_account_thumbnail_size") + offlineSettingsMenuItem = getResourceId(ID, "offline_settings_menu_item") + playerOverlayChip = getResourceId(ID, "player_overlay_chip") + playerViewPager = getResourceId(ID, "player_view_pager") + privacyTosFooter = getResourceId(ID, "privacy_tos_footer") + qualityAuto = getResourceId(STRING, "quality_auto") + remixGenericButtonSize = getResourceId(DIMEN, "remix_generic_button_size") + searchButton = getResourceId(LAYOUT, "search_button") + slidingDialogAnimation = getResourceId(STYLE, "SlidingDialogAnimation") + tapBloomView = getResourceId(ID, "tap_bloom_view") + text1 = getResourceId(ID, "text1") + toolTipContentView = getResourceId(LAYOUT, "tooltip_content_view") + topEnd = getResourceId(ID, "TOP_END") + topStart = getResourceId(ID, "TOP_START") + topBarMenuItemImageView = getResourceId(ID, "top_bar_menu_item_image_view") + tosFooter = getResourceId(ID, "tos_footer") + touchOutside = getResourceId(ID, "touch_outside") + trimSilenceSwitch = getResourceId(ID, "trim_silence_switch") + varispeedUnavailableTitle = getResourceId(STRING, "varispeed_unavailable_title") + ytFillSamples = getResourceId(DRAWABLE, "yt_fill_samples_vd_theme_24") + ytFillYouTubeMusic = getResourceId(DRAWABLE, "yt_fill_youtube_music_vd_theme_24") + ytOutlineSamples = getResourceId(DRAWABLE, "yt_outline_samples_vd_theme_24") + ytOutlineYouTubeMusic = getResourceId(DRAWABLE, "yt_outline_youtube_music_vd_theme_24") + ytmLogo = getResourceId(DRAWABLE, "ytm_logo") + ytmLogoRingo2 = getResourceId(DRAWABLE, "ytm_logo_ringo2") } } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/music/utils/videotype/VideoTypeHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/utils/videotype/VideoTypeHookPatch.kt index bc0ec4167..dc4643d26 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/utils/videotype/VideoTypeHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/utils/videotype/VideoTypeHookPatch.kt @@ -25,7 +25,8 @@ val videoTypeHookPatch = bytecodePatch( videoTypeFingerprint.methodOrThrow(videoTypeParentFingerprint).apply { val getEnumIndex = indexOfGetEnumInstruction(this) - val enumClass = (getInstruction(getEnumIndex).reference as MethodReference).definingClass + val enumClass = + (getInstruction(getEnumIndex).reference as MethodReference).definingClass val referenceIndex = indexOfFirstInstructionOrThrow(getEnumIndex) { opcode == Opcode.SGET_OBJECT && getReference()?.type == enumClass diff --git a/patches/src/main/kotlin/app/revanced/patches/music/video/playerresponse/PlayerResponseMethodHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/video/playerresponse/PlayerResponseMethodHookPatch.kt index 85e51a62b..647b9cc2b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/video/playerresponse/PlayerResponseMethodHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/video/playerresponse/PlayerResponseMethodHookPatch.kt @@ -71,7 +71,8 @@ val playerResponseMethodHookPatch = bytecodePatch( val beforeVideoIdHooks = hooks.filterIsInstance().asReversed() val videoIdHooks = hooks.filterIsInstance().asReversed() - val videoIdAndPlaylistIdHooks = hooks.filterIsInstance().asReversed() + val videoIdAndPlaylistIdHooks = + hooks.filterIsInstance().asReversed() val afterVideoIdHooks = hooks.filterIsInstance().asReversed() // Add the hooks in this specific order as they insert instructions at the beginning of the method. 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 51c8c7e54..74a75cc5e 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 @@ -27,14 +27,6 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference 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, @@ -94,11 +86,20 @@ val adsPatch = bytecodePatch( if (is_2025_06_or_greater) { listOf( commentAdCommentScreenAdViewFingerprint, - commentAdDetailListHeaderViewFingerprint + commentAdDetailListHeaderViewFingerprint, + commentsViewModelFingerprint ).forEach { fingerprint -> fingerprint.methodOrThrow().hook() } } else { + 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 + } + classes.forEach { classDef -> classDef.methods.forEach { method -> if (method.isCommentAdsMethod()) { 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 70df18225..9d434fbd5 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 @@ -8,6 +8,7 @@ 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 +import com.android.tools.smali.dexlib2.iface.reference.TypeReference internal val adPostFingerprint = legacyFingerprint( name = "adPostFingerprint", @@ -49,6 +50,20 @@ internal val commentAdDetailListHeaderViewFingerprint = legacyFingerprint( }, ) +internal val commentsViewModelFingerprint = legacyFingerprint( + name = "commentsViewModelFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("L", "Z", "L", "I"), + customFingerprint = { method, classDef -> + classDef.superclass == "Lcom/reddit/screen/presentation/CompositionViewModel;" && + method.indexOfFirstInstruction { + opcode == Opcode.NEW_INSTANCE && + getReference()?.type?.startsWith("Lcom/reddit/postdetail/comment/refactor/CommentsViewModel\$LoadAdsSeparately\$") == true + } >= 0 + }, +) + internal val newAdPostFingerprint = legacyFingerprint( name = "newAdPostFingerprint", returnType = "L", diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/navigation/NavigationButtonsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/navigation/NavigationButtonsPatch.kt index 473e2498d..4ab992ec7 100644 --- a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/navigation/NavigationButtonsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/navigation/NavigationButtonsPatch.kt @@ -56,7 +56,8 @@ val navigationButtonsPatch = bytecodePatch( if (bottomNavScreenFingerprint.resolvable()) { val bottomNavScreenMutableClass = with(bottomNavScreenFingerprint.methodOrThrow()) { val startIndex = indexOfGetDimensionPixelSizeInstruction(this) - val targetIndex = indexOfFirstInstructionOrThrow(startIndex, Opcode.NEW_INSTANCE) + val targetIndex = + indexOfFirstInstructionOrThrow(startIndex, Opcode.NEW_INSTANCE) val targetReference = getInstruction(targetIndex).reference.toString() @@ -65,7 +66,9 @@ val navigationButtonsPatch = bytecodePatch( ?: throw ClassNotFoundException("Failed to find class $targetReference") } - bottomNavScreenOnGlobalLayoutFingerprint.second.matchOrNull(bottomNavScreenMutableClass) + bottomNavScreenOnGlobalLayoutFingerprint.second.matchOrNull( + bottomNavScreenMutableClass + ) ?.let { it.method.apply { val startIndex = it.patternMatch!!.startIndex @@ -82,7 +85,8 @@ val navigationButtonsPatch = bytecodePatch( // Legacy method. bottomNavScreenHandlerFingerprint.methodOrThrow().apply { val targetIndex = indexOfGetItemsInstruction(this) + 1 - val targetRegister = getInstruction(targetIndex).registerA + val targetRegister = + getInstruction(targetIndex).registerA addInstructions( targetIndex + 1, """ diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/Fingerprints.kt index 13cc83e84..01f630b2b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/Fingerprints.kt @@ -1,36 +1,15 @@ package app.revanced.patches.reddit.layout.screenshotpopup -import app.revanced.patches.reddit.utils.resourceid.screenShotShareBanner -import app.revanced.util.containsLiteralInstruction import app.revanced.util.fingerprint.legacyFingerprint -import com.android.tools.smali.dexlib2.Opcode +import app.revanced.util.or +import com.android.tools.smali.dexlib2.AccessFlags -/** - * Reddit 2025.06.0 ~ - */ -internal val screenshotTakenBannerFingerprint = legacyFingerprint( +internal val screenshotBannerContainerFingerprint = legacyFingerprint( name = "screenshotTakenBannerFingerprint", - returnType = "L", - opcodes = listOf( - Opcode.CONST_4, - Opcode.IF_NE, - ), - customFingerprint = { method, classDef -> - method.containsLiteralInstruction(screenShotShareBanner) && - classDef.type.startsWith("Lcom/reddit/sharing/screenshot/composables/") && - method.name == "invoke" - } -) - -/** - * ~ Reddit 2025.05.1 - */ -internal val screenshotTakenBannerLegacyFingerprint = legacyFingerprint( - name = "screenshotTakenBannerLegacyFingerprint", returnType = "V", - parameters = listOf("Landroidx/compose/runtime/", "I"), - customFingerprint = { method, classDef -> - classDef.type.endsWith("\$ScreenshotTakenBannerKt\$lambda-1\$1;") && - method.name == "invoke" - } + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + strings = listOf( + "bannerContainer", + "scope", + ) ) diff --git a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/ScreenshotPopupPatch.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/ScreenshotPopupPatch.kt index a8ceffdb9..4de050ce9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/ScreenshotPopupPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/reddit/layout/screenshotpopup/ScreenshotPopupPatch.kt @@ -2,22 +2,26 @@ package app.revanced.patches.reddit.layout.screenshotpopup import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +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.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.DISABLE_SCREENSHOT_POPUP -import app.revanced.patches.reddit.utils.resourceid.screenShotShareBanner -import app.revanced.patches.reddit.utils.resourceid.sharedResourceIdPatch -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.findMutableMethodOf +import app.revanced.util.fingerprint.methodCall import app.revanced.util.fingerprint.methodOrThrow +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction import app.revanced.util.indexOfFirstInstructionOrThrow -import app.revanced.util.indexOfFirstInstructionReversedOrThrow -import app.revanced.util.indexOfFirstLiteralInstructionOrThrow +import app.revanced.util.indexOfFirstStringInstruction 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 private const val EXTENSION_METHOD_DESCRIPTOR = "$PATCHES_PATH/ScreenshotPopupPatch;->disableScreenshotPopup()Z" @@ -29,41 +33,80 @@ val screenshotPopupPatch = bytecodePatch( ) { compatibleWith(COMPATIBLE_PACKAGE) - dependsOn( - settingsPatch, - sharedResourceIdPatch, - ) + dependsOn(settingsPatch) execute { - if (is_2025_06_or_greater) { - screenshotTakenBannerFingerprint.methodOrThrow().apply { - val literalIndex = indexOfFirstLiteralInstructionOrThrow(screenShotShareBanner) - val insertIndex = indexOfFirstInstructionReversedOrThrow(literalIndex, Opcode.CONST_4) - val insertRegister = getInstruction(insertIndex).registerA - val jumpIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.SGET_OBJECT) + val screenshotTriggerSharingListenerMethodCall = + screenshotBannerContainerFingerprint.methodCall() - addInstructionsWithLabels( - insertIndex, """ - invoke-static {}, $EXTENSION_METHOD_DESCRIPTOR - move-result v$insertRegister - if-nez v$insertRegister, :hidden - """, ExternalLabel("hidden", getInstruction(jumpIndex)) - ) + fun indexOfScreenshotTriggerInstruction(method: Method) = + method.indexOfFirstInstruction { + getReference()?.toString() == screenshotTriggerSharingListenerMethodCall } - } else { - screenshotTakenBannerLegacyFingerprint.methodOrThrow().apply { + + val isScreenshotTriggerMethod: Method.() -> Boolean = { + indexOfScreenshotTriggerInstruction(this) >= 0 + } + + var hookCount = 0 + + fun MutableMethod.hook() { + if (returnType == "V") { addInstructionsWithLabels( 0, """ invoke-static {}, $EXTENSION_METHOD_DESCRIPTOR move-result v0 - if-eqz v0, :dismiss + if-eqz v0, :shown return-void - """, ExternalLabel("dismiss", getInstruction(0)) + """, ExternalLabel("shown", getInstruction(0)) ) + + hookCount++ + } else if (returnType.startsWith("L")) { // Reddit 2025.06+ + val insertIndex = + indexOfFirstStringInstruction("screenshotTriggerSharingListener") + + if (insertIndex >= 0) { + val insertRegister = + getInstruction(insertIndex).registerA + val triggerIndex = + indexOfScreenshotTriggerInstruction(this) + val jumpIndex = + indexOfFirstInstructionOrThrow(triggerIndex, Opcode.RETURN_OBJECT) + + addInstructionsWithLabels( + insertIndex, """ + invoke-static {}, $EXTENSION_METHOD_DESCRIPTOR + move-result v$insertRegister + if-nez v$insertRegister, :hidden + """, ExternalLabel("hidden", getInstruction(jumpIndex)) + ) + + hookCount++ + } } } + screenshotBannerContainerFingerprint + .methodOrThrow() + .hook() + + classes.forEach { classDef -> + classDef.methods.forEach { method -> + if (method.isScreenshotTriggerMethod()) { + proxy(classDef) + .mutableClass + .findMutableMethodOf(method) + .hook() + } + } + } + + if (hookCount == 0) { + throw PatchException("Failed to find hook method") + } + updatePatchStatus( "enableScreenshotPopup", DISABLE_SCREENSHOT_POPUP 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 7d51cb4a0..ccd7e4064 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 @@ -42,7 +42,8 @@ val subRedditDialogPatch = bytecodePatch( .apply { listOfIsLoggedInInstruction(this) .forEach { index -> - val register = getInstruction(index + 1).registerA + val register = + getInstruction(index + 1).registerA addInstructions( index + 2, """ 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 9589f9dd7..acb655efa 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 @@ -10,7 +10,8 @@ internal object Constants { REDDIT_PACKAGE_NAME, 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. + "2025.05.1", // This was the latest version supported by the previous RVX patch. + "2025.12.0", // 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/resourceid/SharedResourceIdPatch.kt b/patches/src/main/kotlin/app/revanced/patches/reddit/utils/resourceid/SharedResourceIdPatch.kt deleted file mode 100644 index 8fcffa95d..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/reddit/utils/resourceid/SharedResourceIdPatch.kt +++ /dev/null @@ -1,23 +0,0 @@ -package app.revanced.patches.reddit.utils.resourceid - -import app.revanced.patcher.patch.resourcePatch -import app.revanced.patches.shared.mapping.ResourceType.STRING -import app.revanced.patches.shared.mapping.get -import app.revanced.patches.shared.mapping.resourceMappingPatch -import app.revanced.patches.shared.mapping.resourceMappings - -var screenShotShareBanner = -1L - private set - -internal val sharedResourceIdPatch = resourcePatch( - description = "sharedResourceIdPatch" -) { - dependsOn(resourceMappingPatch) - - execute { - screenShotShareBanner = resourceMappings[ - STRING, - "screenshot_share_banner_title", - ] - } -} \ No newline at end of file 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 55f2354d3..16c749ce0 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 @@ -62,7 +62,7 @@ fun baseAdsPatch( ) } - val getAdvertisingIdMethod = with (advertisingIdFingerprint.methodOrThrow()) { + val getAdvertisingIdMethod = with(advertisingIdFingerprint.methodOrThrow()) { val getAdvertisingIdIndex = indexOfGetAdvertisingIdInstruction(this) getWalkerMethod(getAdvertisingIdIndex) } diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/extension/hooks/CronetEngineContextHook.kt b/patches/src/main/kotlin/app/revanced/patches/shared/extension/hooks/CronetEngineContextHook.kt deleted file mode 100644 index e31cf801b..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/shared/extension/hooks/CronetEngineContextHook.kt +++ /dev/null @@ -1,48 +0,0 @@ -package app.revanced.patches.shared.extension.hooks - -import app.revanced.patches.shared.extension.extensionHook -import app.revanced.util.getReference -import app.revanced.util.indexOfFirstInstruction -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.iface.instruction.FiveRegisterInstruction -import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction3rc -import com.android.tools.smali.dexlib2.iface.reference.MethodReference - -private var initIndex = -1 -private var isRange = true - -internal val cronetEngineContextHook = extensionHook( - insertIndexResolver = { method -> - initIndex = method.indexOfFirstInstruction(Opcode.INVOKE_DIRECT_RANGE) - - if (initIndex < 0) { - initIndex = method.indexOfFirstInstructionOrThrow(Opcode.INVOKE_DIRECT) - isRange = false - } - - initIndex - }, - contextRegisterResolver = { method -> - val initInstruction = - method.implementation!!.instructions.elementAt(initIndex) - if (isRange) { - val overrideInstruction = initInstruction as Instruction3rc - "v${overrideInstruction.startRegister + 1}" - } else { - val overrideInstruction = initInstruction as FiveRegisterInstruction - "v${overrideInstruction.registerD}" - } - }, -) { - returns("Lorg/chromium/net/CronetEngine;") - accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC) - strings("Could not create CronetEngine") - custom { method, classDef -> - method.indexOfFirstInstruction { - (opcode == Opcode.INVOKE_DIRECT || opcode == Opcode.INVOKE_DIRECT_RANGE) && - getReference()?.parameterTypes?.firstOrNull() == "Landroid/content/Context;" - } >= 0 - } -} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/extension/hooks/FirebaseInitProviderContextHook.kt b/patches/src/main/kotlin/app/revanced/patches/shared/extension/hooks/FirebaseInitProviderContextHook.kt deleted file mode 100644 index e52f23dfa..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/shared/extension/hooks/FirebaseInitProviderContextHook.kt +++ /dev/null @@ -1,37 +0,0 @@ -package app.revanced.patches.shared.extension.hooks - -import app.revanced.patches.shared.extension.extensionHook -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.instruction.FiveRegisterInstruction -import com.android.tools.smali.dexlib2.iface.reference.MethodReference - -private var getResourcesIndex = -1 - -internal val firebaseInitProviderContextHook = extensionHook( - insertIndexResolver = { method -> - getResourcesIndex = indexOfGerResourcesInstruction(method) - - getResourcesIndex + 2 - }, - contextRegisterResolver = { method -> - val overrideInstruction = - method.implementation!!.instructions.elementAt(getResourcesIndex) - as FiveRegisterInstruction - - "v${overrideInstruction.registerC}" - }, -) { - strings("firebase_database_url") - custom { method, _ -> - indexOfGerResourcesInstruction(method) >= 0 - } -} - -private fun indexOfGerResourcesInstruction(method: Method) = - method.indexOfFirstInstruction { - opcode == Opcode.INVOKE_VIRTUAL && - getReference()?.toString() =="Landroid/content/Context;->getResources()Landroid/content/res/Resources;" - } diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/gms/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/gms/Fingerprints.kt index 952b0c350..1e79c58e2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/gms/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/gms/Fingerprints.kt @@ -6,8 +6,6 @@ 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 import com.android.tools.smali.dexlib2.iface.reference.StringReference import com.android.tools.smali.dexlib2.util.MethodUtil 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 0a5d12a9a..9fb601010 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 @@ -83,9 +83,9 @@ fun gmsCoreSupportPatch( key = "gmsCoreVendorGroupId", default = "app.revanced", values = - mapOf( - "ReVanced" to "app.revanced", - ), + mapOf( + "ReVanced" to "app.revanced", + ), title = "GmsCore vendor group ID", description = "The vendor's group ID for GmsCore.", required = true, diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/mapping/ResourceMappingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/mapping/ResourceMappingPatch.kt index f48e8f201..03c4b24af 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/mapping/ResourceMappingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/mapping/ResourceMappingPatch.kt @@ -2,71 +2,50 @@ package app.revanced.patches.shared.mapping import app.revanced.patcher.patch.resourcePatch import org.w3c.dom.Element -import java.util.Collections -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit -// TODO: Probably renaming the patch/this is a good idea. -lateinit var resourceMappings: List - private set +data class ResourceElement(val type: String, val name: String, val id: Long) + +private lateinit var resourceMappings: MutableMap + +private fun setResourceId(type: String, name: String, id: Long) { + resourceMappings[type + name] = ResourceElement(type, name, id) +} + +fun getResourceId(resourceType: ResourceType, name: String) = + getResourceId(resourceType.value, name) + +/** + * @return A resource id of the given resource type and name. + * @throws PatchException if the resource is not found. + */ +fun getResourceId(type: String, name: String) = resourceMappings[type + name]?.id + ?: -1L val resourceMappingPatch = resourcePatch( description = "resourceMappingPatch" ) { - val threadCount = Runtime.getRuntime().availableProcessors() - val threadPoolExecutor = Executors.newFixedThreadPool(threadCount) - - val resourceMappings = Collections.synchronizedList(mutableListOf()) - execute { - // Save the file in memory to concurrently read from it. - val resourceXmlFile = get("res/values/public.xml").readBytes() + document("res/values/public.xml").use { document -> + val resources = document.documentElement.childNodes + val resourcesLength = resources.length + resourceMappings = HashMap(2 * resourcesLength) - for (threadIndex in 0 until threadCount) { - threadPoolExecutor.execute thread@{ - document(resourceXmlFile.inputStream()).use { document -> + for (i in 0 until resourcesLength) { + val node = resources.item(i) as? Element ?: continue + if (node.nodeName != "public") continue - val resources = document.documentElement.childNodes - val resourcesLength = resources.length - val jobSize = resourcesLength / threadCount + val nameAttribute = node.getAttribute("name") + if (nameAttribute.startsWith("APKTOOL")) continue - val batchStart = jobSize * threadIndex - val batchEnd = jobSize * (threadIndex + 1) - element@ for (i in batchStart until batchEnd) { - // Prevent out of bounds. - if (i >= resourcesLength) return@thread + val typeAttribute = node.getAttribute("type") + val id = node.getAttribute("id").substring(2).toLong(16) - val node = resources.item(i) - if (node !is Element) continue - - val nameAttribute = node.getAttribute("name") - val typeAttribute = node.getAttribute("type") - - if (node.nodeName != "public" || nameAttribute.startsWith("APKTOOL")) continue - - val id = node.getAttribute("id").substring(2).toLong(16) - - resourceMappings.add(ResourceElement(typeAttribute, nameAttribute, id)) - } - } + setResourceId(typeAttribute, nameAttribute, id) } } - - threadPoolExecutor.also { it.shutdown() }.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS) - - app.revanced.patches.shared.mapping.resourceMappings = resourceMappings } } -operator fun List.get(type: String, name: String) = resourceMappings.firstOrNull { - it.type == type && it.name == name -}?.id ?: -1L - -operator fun List.get(resourceType: ResourceType, name: String) = - get(resourceType.value, name) - -data class ResourceElement(val type: String, val name: String, val id: Long) - enum class ResourceType(val value: String) { ATTR("attr"), BOOL("bool"), 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 82ca978e6..39e251e22 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 @@ -147,7 +147,8 @@ fun ResourcePatchContext.baseTranslationsPatch( val length = text.length if (!text.endsWith("DEFAULT") && length >= 2 && - text.subSequence(length - 2, length) !in filteredAppLanguages) { + text.subSequence(length - 2, length) !in filteredAppLanguages + ) { nodesToRemove.add(item) } } 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 315b1d9ac..c8aa32933 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 @@ -57,8 +57,6 @@ internal val showDialogCommandFingerprint = legacyFingerprint( name = "showDialogCommandFingerprint", returnType = "V", opcodes = listOf( - Opcode.IF_EQ, - Opcode.IGET_OBJECT, Opcode.INVOKE_VIRTUAL, Opcode.IGET, // get dialog code ), 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 655606d54..736e1d580 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 @@ -262,8 +262,10 @@ val feedComponentsPatch = bytecodePatch( val insertIndex = indexOfBufferParserInstruction(this) if (is_19_46_or_greater) { - val objectIndex = indexOfFirstInstructionReversedOrThrow(insertIndex, Opcode.IGET_OBJECT) - val objectRegister = getInstruction(objectIndex).registerA + val objectIndex = + indexOfFirstInstructionReversedOrThrow(insertIndex, Opcode.IGET_OBJECT) + val objectRegister = + getInstruction(objectIndex).registerA addInstructionsWithLabels( insertIndex, """ @@ -275,7 +277,8 @@ val feedComponentsPatch = bytecodePatch( ) } else { val objectIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_OBJECT) - val objectRegister = getInstruction(objectIndex).registerA + val objectRegister = + getInstruction(objectIndex).registerA val jumpIndex = it.patternMatch!!.startIndex addInstructionsWithLabels( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/Fingerprints.kt index e9e4a2e29..8782607c6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/Fingerprints.kt @@ -6,6 +6,7 @@ import app.revanced.patches.youtube.utils.resourceid.compactListItem import app.revanced.patches.youtube.utils.resourceid.editSettingsAction import app.revanced.patches.youtube.utils.resourceid.fab import app.revanced.patches.youtube.utils.resourceid.toolTipContentView +import app.revanced.patches.youtube.utils.resourceid.ytCallToAction import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionReversed @@ -19,13 +20,7 @@ internal val accountListFingerprint = legacyFingerprint( name = "accountListFingerprint", returnType = "V", accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL or AccessFlags.SYNTHETIC, - opcodes = listOf( - Opcode.IGET_OBJECT, - Opcode.INVOKE_VIRTUAL, - Opcode.IGET_OBJECT, - Opcode.INVOKE_VIRTUAL, - Opcode.IGET - ) + literals = listOf(ytCallToAction), ) internal val accountListParentFingerprint = legacyFingerprint( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/LayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/LayoutComponentsPatch.kt index 7782e7741..a48e5eab6 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/LayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/components/LayoutComponentsPatch.kt @@ -20,6 +20,7 @@ import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.resourceid.accountSwitcherAccessibility import app.revanced.patches.youtube.utils.resourceid.fab import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch +import app.revanced.patches.youtube.utils.resourceid.ytCallToAction import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall @@ -102,6 +103,7 @@ val layoutComponentsPatch = bytecodePatch( "$GENERAL_CLASS_DESCRIPTOR->disableTranslucentStatusBar(Z)Z" ) + settingArray += "PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS" settingArray += "SETTINGS: DISABLE_TRANSLUCENT_STATUS_BAR" } @@ -122,17 +124,19 @@ val layoutComponentsPatch = bytecodePatch( // region patch for hide account menu // for you tab - accountListFingerprint.matchOrThrow(accountListParentFingerprint).let { - it.method.apply { - val targetIndex = it.patternMatch!!.startIndex + 3 - val targetInstruction = getInstruction(targetIndex) - - addInstruction( - targetIndex, - "invoke-static {v${targetInstruction.registerC}, v${targetInstruction.registerD}}, " + - "$GENERAL_CLASS_DESCRIPTOR->hideAccountList(Landroid/view/View;Ljava/lang/CharSequence;)V" - ) + accountListFingerprint.methodOrThrow(accountListParentFingerprint).apply { + val literalIndex = indexOfFirstLiteralInstructionOrThrow(ytCallToAction) + val targetIndex = indexOfFirstInstructionOrThrow(literalIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "setText" } + val targetInstruction = getInstruction(targetIndex) + + addInstruction( + targetIndex, + "invoke-static {v${targetInstruction.registerC}, v${targetInstruction.registerD}}, " + + "$GENERAL_CLASS_DESCRIPTOR->hideAccountList(Landroid/view/View;Ljava/lang/CharSequence;)V" + ) } // for tablet and old clients diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/downloads/DownloadActionsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/downloads/DownloadActionsPatch.kt index d6f4d54b4..7a36adba1 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/downloads/DownloadActionsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/downloads/DownloadActionsPatch.kt @@ -11,6 +11,7 @@ import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PAC import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_PATH import app.revanced.patches.youtube.utils.patch.PatchList.HOOK_DOWNLOAD_ACTIONS import app.revanced.patches.youtube.utils.pip.pipStateHookPatch +import app.revanced.patches.youtube.utils.playlist.playlistPatch 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 @@ -41,6 +42,7 @@ val downloadActionsPatch = bytecodePatch( dependsOn( pipStateHookPatch, + playlistPatch, sharedResourceIdPatch, settingsPatch, ) @@ -52,7 +54,7 @@ val downloadActionsPatch = bytecodePatch( offlineVideoEndpointFingerprint.methodOrThrow().apply { addInstructionsWithLabels( 0, """ - invoke-static/range {p3 .. p3}, $EXTENSION_CLASS_DESCRIPTOR->inAppVideoDownloadButtonOnClick(Ljava/lang/String;)Z + invoke-static/range {p1 .. p3}, $EXTENSION_CLASS_DESCRIPTOR->inAppVideoDownloadButtonOnClick(Ljava/util/Map;Ljava/lang/Object;Ljava/lang/String;)Z move-result v0 if-eqz v0, :show_native_downloader return-void 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 cb105e27d..fa987061f 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 @@ -90,7 +90,8 @@ val openChannelOfLiveAvatarPatch = bytecodePatch( ) val playbackStartIndex = indexOfPlaybackStartDescriptorInstruction(this) + 1 - val playbackStartRegister = getInstruction(playbackStartIndex).registerA + val playbackStartRegister = + getInstruction(playbackStartIndex).registerA val mapIndex = indexOfFirstInstructionOrThrow(playbackStartIndex) { val reference = getReference() @@ -169,15 +170,24 @@ val openChannelOfLiveAvatarPatch = bytecodePatch( val playbackStartIndex = indexOfFirstInstructionOrThrow { getReference()?.returnType == PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR } - val mapIndex = indexOfFirstInstructionReversedOrThrow(playbackStartIndex, Opcode.IPUT) + val mapIndex = + indexOfFirstInstructionReversedOrThrow(playbackStartIndex, Opcode.IPUT) val mapRegister = getInstruction(mapIndex).registerA - val playbackStartRegister = getInstruction(playbackStartIndex + 1).registerA - val videoIdRegister = getInstruction(playbackStartIndex).registerC + val playbackStartRegister = + getInstruction(playbackStartIndex + 1).registerA + val videoIdRegister = + getInstruction(playbackStartIndex).registerC addInstructionsWithLabels( playbackStartIndex + 2, """ move-object/from16 v$mapRegister, p2 - ${fetchChannelIdInstructions(playbackStartRegister, mapRegister, videoIdRegister)} + ${ + fetchChannelIdInstructions( + playbackStartRegister, + mapRegister, + videoIdRegister + ) + } """ ) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/navigation/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/navigation/Fingerprints.kt index 33821a219..23fbde7f5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/navigation/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/navigation/Fingerprints.kt @@ -1,6 +1,7 @@ package app.revanced.patches.youtube.general.navigation import app.revanced.patches.youtube.utils.resourceid.ytFillBell +import app.revanced.patches.youtube.utils.resourceid.ytOutlineLibrary import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.or import com.android.tools.smali.dexlib2.AccessFlags @@ -74,6 +75,11 @@ internal val setEnumMapFingerprint = legacyFingerprint( literals = listOf(ytFillBell), ) +internal val setEnumMapSecondaryFingerprint = legacyFingerprint( + name = "setEnumMapSecondaryFingerprint", + literals = listOf(ytOutlineLibrary), +) + internal const val TRANSLUCENT_NAVIGATION_BAR_FEATURE_FLAG = 45630927L internal val translucentNavigationBarFingerprint = legacyFingerprint( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/navigation/NavigationBarComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/navigation/NavigationBarComponentsPatch.kt index ee584d225..e96975ac3 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/navigation/NavigationBarComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/navigation/NavigationBarComponentsPatch.kt @@ -15,6 +15,7 @@ import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_28_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch +import app.revanced.patches.youtube.utils.resourceid.ytOutlineLibrary import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.util.ResourceGroup @@ -25,6 +26,7 @@ import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.indexOfFirstLiteralInstructionOrThrow import app.revanced.util.indexOfFirstStringInstructionOrThrow import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction @@ -56,6 +58,14 @@ private val navigationBarComponentsResourcePatch = resourcePatch( ) ) } + + copyResources( + "youtube/navigationbuttons", + ResourceGroup( + "drawable-xxxhdpi", + "yt_outline_library_cairo_black_24.png" + ) + ) } } } @@ -204,6 +214,18 @@ val navigationBarComponentsPatch = bytecodePatch( """ ) } + + setEnumMapSecondaryFingerprint.methodOrThrow().apply { + val index = indexOfFirstLiteralInstructionOrThrow(ytOutlineLibrary) + val register = getInstruction(index).registerA + + addInstructions( + index + 1, """ + invoke-static {v$register}, $GENERAL_CLASS_DESCRIPTOR->getLibraryDrawableId(I)I + move-result v$register + """ + ) + } } // endregion 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 83821d0e9..1ae0ee983 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 @@ -80,7 +80,8 @@ private val snackBarComponentsBytecodePatch = bytecodePatch( bottomUiContainerThemeFingerprint.matchOrThrow().let { it.method.apply { val darkThemeIndex = it.patternMatch!!.startIndex + 2 - val darkThemeReference = getInstruction(darkThemeIndex).reference.toString() + val darkThemeReference = + getInstruction(darkThemeIndex).reference.toString() implementation!!.instructions .withIndex() @@ -91,7 +92,8 @@ private val snackBarComponentsBytecodePatch = bytecodePatch( .map { (index, _) -> index } .reversed() .forEach { index -> - val appThemeIndex = indexOfFirstInstructionReversedOrThrow(index, Opcode.MOVE_RESULT_OBJECT) + val appThemeIndex = + indexOfFirstInstructionReversedOrThrow(index, Opcode.MOVE_RESULT_OBJECT) val appThemeRegister = getInstruction(appThemeIndex).registerA val darkThemeRegister = diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch.kt index 5177e36c7..ae0259731 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch.kt @@ -16,7 +16,7 @@ import app.revanced.patches.youtube.utils.patch.PatchList.SPOOF_APP_VERSION import app.revanced.patches.youtube.utils.playservice.is_18_34_or_greater import app.revanced.patches.youtube.utils.playservice.is_18_39_or_greater import app.revanced.patches.youtube.utils.playservice.is_18_49_or_greater -import app.revanced.patches.youtube.utils.playservice.is_19_17_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_01_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_23_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_28_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_34_or_greater @@ -45,6 +45,15 @@ private val spoofAppVersionBytecodePatch = bytecodePatch( dependsOn(versionCheckPatch) execute { + if (is_19_01_or_greater) { + findMethodOrThrow(PATCH_STATUS_CLASS_DESCRIPTOR) { + name == "SpoofAppVersionDefaultString" + }.replaceInstruction( + 0, + "const-string v0, \"19.01.34\"" + ) + } + if (!is_19_23_or_greater) { return@execute } @@ -72,13 +81,6 @@ private val spoofAppVersionBytecodePatch = bytecodePatch( """, ExternalLabel("ignore", getInstruction(jumpIndex)) ) } - - findMethodOrThrow(PATCH_STATUS_CLASS_DESCRIPTOR) { - name == "SpoofAppVersionDefaultString" - }.replaceInstruction( - 0, - "const-string v0, \"18.38.45\"" - ) } } @@ -108,35 +110,43 @@ val spoofAppVersionPatch = resourcePatch( SPOOF_APP_VERSION ) - if (!is_19_17_or_greater) { + // TODO: Remove this when the legacy code for YouTube 18.xx is cleaned up. + if (!is_19_01_or_greater) { appendAppVersion("17.41.37") appendAppVersion("18.05.40") appendAppVersion("18.17.43") - if (!is_18_34_or_greater) { + + if (is_18_34_or_greater) { + appendAppVersion("18.33.40") + } else { return@execute } - appendAppVersion("18.33.40") - } - if (!is_18_39_or_greater) { + if (is_18_39_or_greater) { + appendAppVersion("18.38.45") + } else { + return@execute + } + + if (is_18_49_or_greater) { + appendAppVersion("18.48.39") + } + return@execute } - appendAppVersion("18.38.45") - if (!is_18_49_or_greater) { + appendAppVersion("19.01.34") + + if (is_19_28_or_greater) { + appendAppVersion("19.26.42") + } else { return@execute } - appendAppVersion("18.48.39") - if (!is_19_28_or_greater) { + if (is_19_34_or_greater) { + appendAppVersion("19.33.37") + } else { return@execute } - appendAppVersion("19.26.42") - - if (!is_19_34_or_greater) { - return@execute - } - appendAppVersion("19.33.37") - } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt index f928fd94d..49753224d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch.kt @@ -38,6 +38,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 app.revanced.util.replaceLiteralInstructionCall @@ -268,29 +269,54 @@ val toolBarComponentsPatch = bytecodePatch( createSearchSuggestionsFingerprint.methodOrThrow().apply { val iteratorIndex = indexOfIteratorInstruction(this) - val replaceIndex = indexOfFirstInstructionOrThrow(iteratorIndex) { + val replaceIndex = indexOfFirstInstruction(iteratorIndex) { opcode == Opcode.IGET_OBJECT && getReference()?.type == "Landroid/widget/ImageView;" } - val jumpIndex = indexOfFirstInstructionOrThrow(replaceIndex) { - opcode == Opcode.INVOKE_STATIC && - getReference()?.toString() == "Landroid/net/Uri;->parse(Ljava/lang/String;)Landroid/net/Uri;" - } + 4 - val replaceIndexInstruction = getInstruction(replaceIndex) - val freeRegister = replaceIndexInstruction.registerA - val classRegister = replaceIndexInstruction.registerB - val replaceIndexReference = - getInstruction(replaceIndex).reference + if (replaceIndex > -1) { + val uriIndex = indexOfFirstInstructionOrThrow(replaceIndex) { + opcode == Opcode.INVOKE_STATIC && + getReference()?.toString() == "Landroid/net/Uri;->parse(Ljava/lang/String;)Landroid/net/Uri;" + } + val jumpIndex = indexOfFirstInstructionOrThrow(uriIndex, Opcode.CONST_4) + val replaceIndexInstruction = getInstruction(replaceIndex) + val freeRegister = replaceIndexInstruction.registerA + val classRegister = replaceIndexInstruction.registerB + val replaceIndexReference = + getInstruction(replaceIndex).reference - addInstructionsWithLabels( - replaceIndex + 1, """ - invoke-static { }, $GENERAL_CLASS_DESCRIPTOR->hideSearchTermThumbnail()Z - move-result v$freeRegister - if-nez v$freeRegister, :hidden - iget-object v$freeRegister, v$classRegister, $replaceIndexReference - """, ExternalLabel("hidden", getInstruction(jumpIndex)) - ) - removeInstruction(replaceIndex) + addInstructionsWithLabels( + replaceIndex + 1, """ + invoke-static { }, $GENERAL_CLASS_DESCRIPTOR->hideSearchTermThumbnail()Z + move-result v$freeRegister + if-nez v$freeRegister, :hidden + iget-object v$freeRegister, v$classRegister, $replaceIndexReference + """, ExternalLabel("hidden", getInstruction(jumpIndex)) + ) + removeInstruction(replaceIndex) + } else { // only for YT 20.03 + val insertIndex = indexOfFirstInstructionOrThrow(iteratorIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.toString() == "Landroid/widget/ImageView;->setVisibility(I)V" + } - 1 + if (getInstruction(insertIndex).opcode != Opcode.CONST_4) { + throw PatchException("Failed to find insert index") + } + val freeRegister = getInstruction(insertIndex).registerA + val uriIndex = indexOfFirstInstructionOrThrow(insertIndex) { + opcode == Opcode.INVOKE_STATIC && + getReference()?.toString() == "Landroid/net/Uri;->parse(Ljava/lang/String;)Landroid/net/Uri;" + } + val jumpIndex = indexOfFirstInstructionOrThrow(uriIndex, Opcode.CONST_4) + + addInstructionsWithLabels( + insertIndex, """ + invoke-static { }, $GENERAL_CLASS_DESCRIPTOR->hideSearchTermThumbnail()Z + move-result v$freeRegister + if-nez v$freeRegister, :hidden + """, ExternalLabel("hidden", getInstruction(jumpIndex)) + ) + } } if (is_19_16_or_greater) { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/updates/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/updates/Fingerprints.kt new file mode 100644 index 000000000..beffc631c --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/updates/Fingerprints.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.youtube.general.updates + +import app.revanced.util.fingerprint.legacyFingerprint +import app.revanced.util.or +import com.android.tools.smali.dexlib2.AccessFlags + +internal val cronetHeaderFingerprint = legacyFingerprint( + name = "cronetHeaderFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("Ljava/lang/String;", "Ljava/lang/String;"), + strings = listOf("Accept-Encoding"), + // In YouTube 19.16.39 or earlier, there are two methods with almost the same structure. + // Check the fields of the class to identify them correctly. + customFingerprint = { _, classDef -> + classDef.fields.find { it.type == "J" } != null + } +) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/general/updates/LayoutUpdatesPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/general/updates/LayoutUpdatesPatch.kt new file mode 100644 index 000000000..1543fc746 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/general/updates/LayoutUpdatesPatch.kt @@ -0,0 +1,50 @@ +package app.revanced.patches.youtube.general.updates + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR +import app.revanced.patches.youtube.utils.patch.PatchList.DISABLE_LAYOUT_UPDATES +import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference +import app.revanced.patches.youtube.utils.settings.settingsPatch +import app.revanced.util.fingerprint.matchOrThrow + +@Suppress("unused") +val layoutUpdatesPatch = bytecodePatch( + DISABLE_LAYOUT_UPDATES.title, + DISABLE_LAYOUT_UPDATES.summary, +) { + compatibleWith(COMPATIBLE_PACKAGE) + + dependsOn(settingsPatch) + + execute { + + cronetHeaderFingerprint.matchOrThrow().let { + it.method.apply { + val index = it.stringMatches!!.first().index + + addInstructions( + index, """ + invoke-static {p1, p2}, $GENERAL_CLASS_DESCRIPTOR->disableLayoutUpdates(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + move-result-object p2 + """ + ) + } + } + + // region add settings + + addPreference( + arrayOf( + "PREFERENCE_SCREEN: GENERAL", + "PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS", + "SETTINGS: DISABLE_LAYOUT_UPDATES" + ), + DISABLE_LAYOUT_UPDATES + ) + + // endregion + + } +} 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 af1a960b9..6cb122b1d 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 @@ -86,7 +86,11 @@ val shortsActionButtonsPatch = resourcePatch( // Some directory is missing in the bundles. if (inputStreamForLegacy != null && fromFileResolved.exists()) { - Files.copy(inputStreamForLegacy, fromFileResolved.toPath(), StandardCopyOption.REPLACE_EXISTING) + Files.copy( + inputStreamForLegacy, + fromFileResolved.toPath(), + StandardCopyOption.REPLACE_EXISTING + ) } if (is_19_36_or_greater) { @@ -95,7 +99,11 @@ val shortsActionButtonsPatch = resourcePatch( // Some directory is missing in the bundles. if (inputStreamForNew != null && toFileResolved.exists()) { - Files.copy(inputStreamForNew, toFileResolved.toPath(), StandardCopyOption.REPLACE_EXISTING) + Files.copy( + inputStreamForNew, + toFileResolved.toPath(), + StandardCopyOption.REPLACE_EXISTING + ) } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch.kt index a4cedb451..1ccc3e575 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch.kt @@ -10,6 +10,7 @@ import app.revanced.patches.youtube.utils.playservice.is_19_17_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_32_or_greater 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.restoreOldSplashAnimationIncluded import app.revanced.patches.youtube.utils.settings.ResourceUtils.updatePatchStatusIcon import app.revanced.patches.youtube.utils.settings.getBytecodeContext import app.revanced.patches.youtube.utils.settings.settingsPatch @@ -208,22 +209,38 @@ val customBrandingIconPatch = resourcePatch( // Change splash screen. if (restoreOldSplashAnimationOption == true) { + restoreOldSplashAnimationIncluded = true + oldSplashAnimationResourceGroups.let { resourceGroups -> resourceGroups.forEach { copyResources("$appIconResourcePath/splash", it) } } - val styleMap = mutableMapOf() - styleMap["Base.Theme.YouTube.Launcher"] = - "@style/Theme.AppCompat.DayNight.NoActionBar" + val styleList = if (is_19_32_or_greater) + listOf( + Triple( + "values-night-v31", + "Theme.YouTube.Home", + "@style/Base.V27.Theme.YouTube.Home" + ), + Triple( + "values-v31", + "Theme.YouTube.Home", + "@style/Base.V27.Theme.YouTube.Home" + ), + ) + else + listOf( + Triple( + "values-v31", + "Base.Theme.YouTube.Launcher", + "@style/Theme.AppCompat.DayNight.NoActionBar" + ), + ) - if (is_19_32_or_greater) { - styleMap["Theme.YouTube.Home"] = "@style/Base.V27.Theme.YouTube.Home" - } - - styleMap.forEach { (nodeAttributeName, nodeAttributeParent) -> - document("res/values-v31/styles.xml").use { document -> + styleList.forEach { (directory, nodeAttributeName, nodeAttributeParent) -> + document("res/$directory/styles.xml").use { document -> val resourcesNode = document.getElementsByTagName("resources").item(0) as Element @@ -231,21 +248,27 @@ val customBrandingIconPatch = resourcePatch( style.setAttribute("name", nodeAttributeName) style.setAttribute("parent", nodeAttributeParent) - val primaryItem = document.createElement("item") - primaryItem.setAttribute("name", "android:windowSplashScreenAnimatedIcon") - primaryItem.textContent = "@drawable/avd_anim" - val secondaryItem = document.createElement("item") - secondaryItem.setAttribute( + val splashScreenAnimatedIcon = document.createElement("item") + splashScreenAnimatedIcon.setAttribute( + "name", + "android:windowSplashScreenAnimatedIcon" + ) + splashScreenAnimatedIcon.textContent = "@drawable/avd_anim" + + // Deprecated in Android 13+ + val splashScreenAnimationDuration = document.createElement("item") + splashScreenAnimationDuration.setAttribute( "name", "android:windowSplashScreenAnimationDuration" ) - secondaryItem.textContent = if (appIcon.startsWith("revancify")) - "1500" - else - "1000" + splashScreenAnimationDuration.textContent = + if (appIcon.startsWith("revancify")) + "1500" + else + "1000" - style.appendChild(primaryItem) - style.appendChild(secondaryItem) + style.appendChild(splashScreenAnimatedIcon) + style.appendChild(splashScreenAnimationDuration) resourcesNode.appendChild(style) } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/SharedThemePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/SharedThemePatch.kt index a27467bcd..cdee030cd 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/SharedThemePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/SharedThemePatch.kt @@ -61,8 +61,10 @@ val sharedThemePatch = resourcePatch( 0 -> when (nodeAttributeName) { "Base.Theme.YouTube.Launcher.Dark", "Base.Theme.YouTube.Launcher.Cairo.Dark" -> "@color/yt_black1" + "Base.Theme.YouTube.Launcher.Light", "Base.Theme.YouTube.Launcher.Cairo.Light" -> "@color/yt_white1" + else -> "null" } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/translations/TranslationsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/translations/TranslationsPatch.kt index a72aa6957..a9b768e9d 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/translations/TranslationsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/translations/TranslationsPatch.kt @@ -11,8 +11,8 @@ import app.revanced.patches.youtube.utils.settings.settingsPatch // Array of supported translations, each represented by its language code. private val SUPPORTED_TRANSLATIONS = setOf( - "ar", "bg-rBG", "de-rDE", "el-rGR", "es-rES", "fr-rFR", "hu-rHU", "it-rIT", "ja-rJP", "ko-rKR", - "pl-rPL", "pt-rBR", "ru-rRU", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW" + "ar", "bg-rBG", "de-rDE", "el-rGR", "es-rES", "fr-rFR", "hu-rHU", "id-rID", "in", "it-rIT", "ja-rJP", + "ko-rKR", "pl-rPL", "pt-rBR", "ru-rRU", "tr-rTR", "uk-rUA", "vi-rVN", "zh-rCN", "zh-rTW" ) @Suppress("unused") 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 index cf0098362..8a2fd8dde 100644 --- 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 @@ -32,8 +32,10 @@ val accessibilityPatch = bytecodePatch( .methods .first { method -> method.name == "" } .apply { - val lifecycleObserverIndex = indexOfFirstInstructionReversedOrThrow(Opcode.NEW_INSTANCE) - val lifecycleObserverClass = getInstruction(lifecycleObserverIndex).reference.toString() + val lifecycleObserverIndex = + indexOfFirstInstructionReversedOrThrow(Opcode.NEW_INSTANCE) + val lifecycleObserverClass = + getInstruction(lifecycleObserverIndex).reference.toString() findMethodOrThrow(lifecycleObserverClass) { accessFlags == AccessFlags.PUBLIC or AccessFlags.FINAL && 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 c6380be34..4865bfbc1 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 @@ -85,17 +85,19 @@ val backgroundPlaybackPatch = bytecodePatch( backgroundPlaybackManagerCairoFragmentPrimaryFingerprint, backgroundPlaybackManagerCairoFragmentSecondaryFingerprint ).forEach { fingerprint -> - fingerprint.matchOrThrow(backgroundPlaybackManagerCairoFragmentParentFingerprint).let { - it.method.apply { - val insertIndex = it.patternMatch!!.startIndex + 4 - val insertRegister = getInstruction(insertIndex).registerA + 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" - ) + addInstruction( + insertIndex, + "const/4 v$insertRegister, 0x0" + ) + } } - } } pipInputConsumerFeatureFlagFingerprint.injectLiteralInstructionBooleanCall( 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 ce57443ec..692b78489 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 @@ -57,20 +57,26 @@ val actionButtonsPatch = bytecodePatch( findMethodOrThrow(parameters[1].type) { name == "toString" } - val identifierReference = with (conversionContextToStringMethod) { + val identifierReference = with(conversionContextToStringMethod) { val identifierStringIndex = indexOfFirstStringInstructionOrThrow(", identifierProperty=") val identifierStringAppendIndex = indexOfFirstInstructionOrThrow(identifierStringIndex, Opcode.INVOKE_VIRTUAL) - val identifierStringAppendIndexRegister = getInstruction(identifierStringAppendIndex).registerD + 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 - } + 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 } 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 a36f776ab..ebfab4be0 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 @@ -10,11 +10,12 @@ import app.revanced.patches.youtube.utils.resourceid.endScreenElementLayoutCircl 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 import app.revanced.patches.youtube.utils.resourceid.tapBloomView import app.revanced.patches.youtube.utils.resourceid.touchArea +import app.revanced.patches.youtube.utils.resourceid.verticalTouchOffsetToEnterFineScrubbing +import app.revanced.patches.youtube.utils.resourceid.verticalTouchOffsetToStartFineScrubbing import app.revanced.patches.youtube.utils.resourceid.videoZoomSnapIndicator import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.getReference @@ -88,6 +89,8 @@ internal val speedOverlayFingerprint = legacyFingerprint( literals = listOf(SPEED_OVERLAY_FEATURE_FLAG), ) +internal const val SPEED_OVERLAY_LEGACY_FEATURE_FLAG = 45411328L + /** * This value is the key for the playback speed overlay value. * Deprecated in YouTube v19.18.41+. @@ -97,7 +100,7 @@ internal val speedOverlayFloatValueFingerprint = legacyFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, opcodes = listOf(Opcode.DOUBLE_TO_FLOAT), - literals = listOf(45411328L), + literals = listOf(SPEED_OVERLAY_LEGACY_FEATURE_FLAG), ) internal val speedOverlayTextValueFingerprint = legacyFingerprint( @@ -124,6 +127,9 @@ internal val crowdfundingBoxFingerprint = legacyFingerprint( literals = listOf(donationCompanion), ) +/** + * ~ YouTube 20.11 + */ internal val filmStripOverlayConfigFingerprint = legacyFingerprint( name = "filmStripOverlayConfigFingerprint", returnType = "Z", @@ -138,11 +144,48 @@ internal val filmStripOverlayInteractionFingerprint = legacyFingerprint( parameters = listOf("L") ) -internal val filmStripOverlayParentFingerprint = legacyFingerprint( - name = "filmStripOverlayParentFingerprint", +internal val filmStripOverlayEnterParentFingerprint = legacyFingerprint( + name = "filmStripOverlayEnterParentFingerprint", returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, - literals = listOf(scrubbing), + literals = listOf(verticalTouchOffsetToEnterFineScrubbing), +) + +/** + * YouTube 20.12 ~ + */ +internal val filmStripOverlayMotionEventPrimaryFingerprint = legacyFingerprint( + name = "filmStripOverlayMotionEventPrimaryFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("Landroid/view/MotionEvent;"), + opcodes = listOf( + Opcode.IGET_OBJECT, + Opcode.INVOKE_INTERFACE, + ), +) + +/** + * YouTube 20.12 ~ + */ +internal val filmStripOverlayMotionEventSecondaryFingerprint = legacyFingerprint( + name = "filmStripOverlayMotionEventSecondaryFingerprint", + returnType = "Z", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("Landroid/view/MotionEvent;"), + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.NEG_FLOAT, + ), +) + +internal val filmStripOverlayStartParentFingerprint = legacyFingerprint( + name = "filmStripOverlayStartParentFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + literals = listOf(verticalTouchOffsetToStartFineScrubbing), ) internal val filmStripOverlayPreviewFingerprint = legacyFingerprint( 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 8cc9ce104..2886c7063 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 @@ -24,12 +24,15 @@ 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 +import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch import app.revanced.patches.youtube.utils.fix.suggestedvideoendscreen.suggestedVideoEndScreenPatch 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_19_18_or_greater 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.is_20_12_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 @@ -45,23 +48,27 @@ 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.findMutableMethodOf import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall import app.revanced.util.fingerprint.injectLiteralInstructionViewCall import app.revanced.util.fingerprint.matchOrThrow 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 +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.NarrowLiteralInstruction 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.ThreeRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference @@ -70,7 +77,11 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference private val speedOverlayPatch = bytecodePatch( description = "speedOverlayPatch" ) { - dependsOn(sharedResourceIdPatch) + dependsOn( + sharedExtensionPatch, + sharedResourceIdPatch, + versionCheckPatch, + ) execute { fun MutableMethod.hookSpeedOverlay( @@ -87,11 +98,19 @@ private val speedOverlayPatch = bytecodePatch( ) } - val resolvable = restoreSlideToSeekBehaviorFingerprint.resolvable() && - speedOverlayFingerprint.resolvable() && - speedOverlayFloatValueFingerprint.resolvable() + fun MutableMethod.hookRelativeSpeedValue(startIndex: Int) { + val relativeIndex = indexOfFirstInstructionOrThrow(startIndex, Opcode.CMPL_FLOAT) + val relativeRegister = getInstruction(relativeIndex).registerB - if (resolvable) { + addInstructions( + relativeIndex, """ + invoke-static {v$relativeRegister}, $PLAYER_CLASS_DESCRIPTOR->speedOverlayRelativeValue(F)F + move-result v$relativeRegister + """ + ) + } + + if (!is_19_18_or_greater) { // Used on YouTube 18.29.38 ~ YouTube 19.17.41 // region patch for Disable speed overlay (Enable slide to seek) @@ -110,17 +129,53 @@ private val speedOverlayPatch = bytecodePatch( // region patch for Custom speed overlay float value - speedOverlayFloatValueFingerprint.matchOrThrow().let { - it.method.apply { - val index = it.patternMatch!!.startIndex - val register = getInstruction(index).registerA + val speedFieldReference = with(speedOverlayFloatValueFingerprint.methodOrThrow()) { + val literalIndex = + indexOfFirstLiteralInstructionOrThrow(SPEED_OVERLAY_LEGACY_FEATURE_FLAG) + val floatIndex = + indexOfFirstInstructionOrThrow(literalIndex, Opcode.DOUBLE_TO_FLOAT) + val floatRegister = getInstruction(floatIndex).registerA - addInstructions( - index + 1, """ - invoke-static {v$register}, $PLAYER_CLASS_DESCRIPTOR->speedOverlayValue(F)F - move-result v$register + addInstructions( + floatIndex + 1, """ + invoke-static {v$floatRegister}, $PLAYER_CLASS_DESCRIPTOR->speedOverlayValue(F)F + move-result v$floatRegister """ - ) + ) + + val speedFieldIndex = indexOfFirstInstructionOrThrow(literalIndex) { + opcode == Opcode.IPUT && + getReference()?.type == "F" + } + + getInstruction(speedFieldIndex).reference.toString() + } + + fun indexOfFirstSpeedFieldInstruction(method: Method) = + method.indexOfFirstInstruction { + opcode == Opcode.IGET && + getReference()?.toString() == speedFieldReference + } + + val isSyntheticMethod: Method.() -> Boolean = { + name == "run" && + accessFlags == AccessFlags.PUBLIC or AccessFlags.FINAL && + parameterTypes.isEmpty() && + indexOfFirstSpeedFieldInstruction(this) >= 0 && + indexOfFirstInstruction(Opcode.CMPL_FLOAT) >= 0 + } + + classes.forEach { classDef -> + classDef.methods.forEach { method -> + if (method.isSyntheticMethod()) { + proxy(classDef) + .mutableClass + .findMutableMethodOf(method) + .apply { + val speedFieldIndex = indexOfFirstSpeedFieldInstruction(this) + hookRelativeSpeedValue(speedFieldIndex) + } + } } } @@ -241,6 +296,8 @@ private val speedOverlayPatch = bytecodePatch( move-result v$speedOverlayFloatValueRegister """ ) + + hookRelativeSpeedValue(speedOverlayFloatValueIndex) } // Removed in YouTube 20.03+ @@ -345,18 +402,6 @@ val playerComponentsPatch = bytecodePatch( return "" } - fun MutableMethod.hookFilmstripOverlay() { - addInstructionsWithLabels( - 0, """ - invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->hideFilmstripOverlay()Z - move-result v0 - if-eqz v0, :shown - const/4 v0, 0x0 - return v0 - """, ExternalLabel("shown", getInstruction(0)) - ) - } - // region patch for custom player overlay opacity youtubeControlsOverlayFingerprint.methodOrThrow().apply { @@ -527,16 +572,76 @@ val playerComponentsPatch = bytecodePatch( // region patch for hide filmstrip overlay - arrayOf( - filmStripOverlayConfigFingerprint, - filmStripOverlayInteractionFingerprint, - filmStripOverlayPreviewFingerprint - ).forEach { fingerprint -> - fingerprint.methodOrThrow(filmStripOverlayParentFingerprint).hookFilmstripOverlay() + fun MutableMethod.hookFilmstripOverlay( + index: Int = 0, + register: Int = 0 + ) { + val stringInstructions = if (returnType == "Z") + """ + const/4 v$register, 0x0 + return v$register + """ + else if (returnType == "V") + """ + return-void + """ + else + throw Exception("This case should never happen.") + + addInstructionsWithLabels( + index, """ + invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->hideFilmstripOverlay()Z + move-result v$register + if-eqz v$register, :shown + """ + stringInstructions + """ + :shown + nop + """ + ) } - // Removed in YouTube 20.05+ - if (!is_20_05_or_greater) { + val filmStripOverlayFingerprints = mutableListOf( + filmStripOverlayInteractionFingerprint, + filmStripOverlayPreviewFingerprint + ) + + if (is_20_12_or_greater) { + filmStripOverlayMotionEventPrimaryFingerprint.matchOrThrow( + filmStripOverlayStartParentFingerprint + ).let { + it.method.apply { + val index = it.patternMatch!!.startIndex + val register = getInstruction(index).registerA + + hookFilmstripOverlay(index, register) + } + } + + filmStripOverlayMotionEventSecondaryFingerprint.matchOrThrow( + filmStripOverlayStartParentFingerprint + ).let { + it.method.apply { + val index = it.patternMatch!!.startIndex + 2 + val register = getInstruction(index).registerA + + addInstructions( + index, """ + invoke-static {v$register}, $PLAYER_CLASS_DESCRIPTOR->hideFilmstripOverlay(Z)Z + move-result v$register + """ + ) + } + } + } else { + filmStripOverlayFingerprints += filmStripOverlayConfigFingerprint + } + + filmStripOverlayFingerprints.forEach { fingerprint -> + fingerprint.methodOrThrow(filmStripOverlayEnterParentFingerprint).hookFilmstripOverlay() + } + + // Removed in YouTube 20.03+ + if (!is_20_03_or_greater) { youtubeControlsOverlayFingerprint.methodOrThrow().apply { val constIndex = indexOfFirstLiteralInstructionOrThrow(fadeDurationFast) val constRegister = getInstruction(constIndex).registerA @@ -564,7 +669,7 @@ val playerComponentsPatch = bytecodePatch( ) removeInstruction(insertIndex) } - } else { + } else if (is_20_05_or_greater) { // 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( 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 12be08c50..ac753a6fe 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 @@ -15,7 +15,7 @@ import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_CLASS_DESCR import app.revanced.patches.youtube.utils.patch.PatchList.DESCRIPTION_COMPONENTS import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch import app.revanced.patches.youtube.utils.playservice.is_18_49_or_greater -import app.revanced.patches.youtube.utils.playservice.is_19_02_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_05_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.recyclerview.recyclerViewTreeObserverHook import app.revanced.patches.youtube.utils.recyclerview.recyclerViewTreeObserverPatch @@ -93,8 +93,7 @@ val descriptionComponentsPatch = bytecodePatch( // region patch for disable video description interaction and expand video description - // since these patches are still A/B tested, they are classified as 'Experimental flags'. - if (is_19_02_or_greater) { + if (is_19_05_or_greater) { textViewComponentFingerprint.methodOrThrow().apply { val insertIndex = indexOfTextIsSelectableInstruction(this) val insertInstruction = getInstruction(insertIndex) 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 0fccda3a4..dd98d2908 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 @@ -9,7 +9,7 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference /** * This fingerprint is compatible with YouTube v18.35.xx~ - * Nonetheless, the patch works in YouTube v19.02.xx~ + * Nonetheless, the patch works in YouTube v19.05.xx~ */ internal val textViewComponentFingerprint = legacyFingerprint( name = "textViewComponentFingerprint", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/Fingerprints.kt index 19470906e..70d3db075 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/Fingerprints.kt @@ -1,8 +1,10 @@ package app.revanced.patches.youtube.player.flyoutmenu.hide +import app.revanced.patches.youtube.utils.indexOfAddHeaderViewInstruction import app.revanced.patches.youtube.utils.resourceid.bottomSheetFooterText import app.revanced.patches.youtube.utils.resourceid.subtitleMenuSettingsFooterInfo import app.revanced.patches.youtube.utils.resourceid.videoQualityBottomSheet +import app.revanced.util.containsLiteralInstruction import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstruction @@ -18,33 +20,18 @@ internal val advancedQualityBottomSheetFingerprint = legacyFingerprint( returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("L", "L", "L"), - opcodes = listOf( - Opcode.IGET_OBJECT, - Opcode.INVOKE_STATIC, - Opcode.CONST, - Opcode.CONST_4, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT, - Opcode.CONST, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT, - Opcode.CONST_16, - Opcode.INVOKE_VIRTUAL, - Opcode.CONST, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT, - Opcode.CHECK_CAST, - Opcode.CONST, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT, - Opcode.IGET_OBJECT, - Opcode.IGET_OBJECT, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT, - Opcode.IGET_OBJECT, - Opcode.CONST_STRING - ), - literals = listOf(videoQualityBottomSheet), + customFingerprint = custom@{ method, _ -> + if (!method.containsLiteralInstruction(videoQualityBottomSheet)) { + return@custom false + } + if (indexOfAddHeaderViewInstruction(method) < 0) { + return@custom false + } + val implementation = method.implementation + ?: return@custom false + + implementation.instructions.elementAt(0).opcode == Opcode.IGET_OBJECT + } ) internal val captionsBottomSheetFingerprint = legacyFingerprint( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/PlayerFlyoutMenuPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/PlayerFlyoutMenuPatch.kt index 07c2df304..8f7ea6837 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/PlayerFlyoutMenuPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/flyoutmenu/hide/PlayerFlyoutMenuPatch.kt @@ -10,6 +10,7 @@ import app.revanced.patches.shared.litho.lithoFilterPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE 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.indexOfAddHeaderViewInstruction import app.revanced.patches.youtube.utils.patch.PatchList.HIDE_PLAYER_FLYOUT_MENU import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch import app.revanced.patches.youtube.utils.playservice.is_18_39_or_greater @@ -25,7 +26,6 @@ import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall import app.revanced.util.fingerprint.injectLiteralInstructionViewCall 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 @@ -75,10 +75,7 @@ val playerFlyoutMenuPatch = bytecodePatch( qualityMenuViewInflateFingerprint ).forEach { fingerprint -> fingerprint.methodOrThrow().apply { - val insertIndex = indexOfFirstInstructionOrThrow { - opcode == Opcode.INVOKE_VIRTUAL && - getReference()?.name == "addHeaderView" - } + val insertIndex = indexOfAddHeaderViewInstruction(this) val insertRegister = getInstruction(insertIndex).registerD addInstructions( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/Fingerprints.kt index bda5fdde7..cec249b45 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/Fingerprints.kt @@ -7,7 +7,6 @@ import app.revanced.patches.youtube.utils.resourceid.quickActionsElementContaine import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.or import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.util.MethodUtil internal val broadcastReceiverFingerprint = legacyFingerprint( name = "broadcastReceiverFingerprint", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/FullscreenComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/FullscreenComponentsPatch.kt index 2a56b37c6..11d7c5e3c 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/FullscreenComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/fullscreen/FullscreenComponentsPatch.kt @@ -37,7 +37,6 @@ import app.revanced.patches.youtube.utils.youtubeControlsOverlayFingerprint import app.revanced.patches.youtube.video.information.hookBackgroundPlayVideoInformation import app.revanced.patches.youtube.video.information.videoEndMethod import app.revanced.patches.youtube.video.information.videoInformationPatch -import app.revanced.util.Utils.printWarn import app.revanced.util.addInstructionsAtControlFlowLabel import app.revanced.util.findMethodOrThrow import app.revanced.util.fingerprint.methodOrThrow @@ -313,8 +312,6 @@ val fullscreenComponentsPatch = bytecodePatch( } settingArray += "SETTINGS: KEEP_LANDSCAPE_MODE" - } else { - printWarn("\"Keep landscape mode\" is not supported in this version. Use YouTube 19.16.39 or earlier.") } // endregion diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/Fingerprints.kt index df9835b17..219f4cd15 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/Fingerprints.kt @@ -63,6 +63,7 @@ internal val miniplayerResponseModelSizeCheckFingerprint = legacyFingerprint( // 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 diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/MiniplayerPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/MiniplayerPatch.kt index bdd835f99..18ab867db 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/MiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/miniplayer/general/MiniplayerPatch.kt @@ -50,6 +50,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstructio import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction 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.TypeReference import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter @@ -145,10 +146,15 @@ val miniplayerPatch = bytecodePatch( // region Legacy tablet Miniplayer hooks. miniplayerOverrideFingerprint.matchOrThrow().let { - val appNameStringIndex = it.stringMatches!!.first().index + 2 - it.method.apply { - val walkerMethod = getWalkerMethod(appNameStringIndex) + val stringIndex = it.stringMatches!!.first().index + val walkerIndex = indexOfFirstInstructionOrThrow(stringIndex) { + val reference = getReference() + reference?.returnType == "Z" && + reference.parameterTypes.size == 1 && + reference.parameterTypes.firstOrNull() == "Landroid/content/Context;" + } + val walkerMethod = getWalkerMethod(walkerIndex) walkerMethod.apply { findReturnIndicesReversed().forEach { index -> @@ -233,7 +239,8 @@ val miniplayerPatch = bytecodePatch( val register = getInstruction(targetIndex).registerA addInstructions( - targetIndex + 1, """ + targetIndex + 1, + """ invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getMiniplayerDefaultSize(I)I move-result v$register """, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsPatch.kt index 67ce333ab..0dc46c49b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsPatch.kt @@ -15,6 +15,7 @@ import app.revanced.patches.youtube.utils.patch.PatchList.OVERLAY_BUTTONS import app.revanced.patches.youtube.utils.pip.pipStateHookPatch import app.revanced.patches.youtube.utils.playercontrols.hookBottomControlButton import app.revanced.patches.youtube.utils.playercontrols.playerControlsPatch +import app.revanced.patches.youtube.utils.playlist.playlistPatch 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 @@ -74,6 +75,7 @@ val overlayButtonsPatch = resourcePatch( cfBottomUIPatch, pipStateHookPatch, playerControlsPatch, + playlistPatch, sharedResourceIdPatch, settingsPatch, ) @@ -254,7 +256,8 @@ val overlayButtonsPatch = resourcePatch( width != "0.0dip", ) - val isButton = id.endsWith("_button") && id != "@id/multiview_button" || id == "@id/youtube_controls_fullscreen_button_stub" + val isButton = + id.endsWith("_button") && id != "@id/multiview_button" || id == "@id/youtube_controls_fullscreen_button_stub" // Adjust TimeBar and Chapter bottom padding val timBarItem = mutableMapOf( @@ -284,7 +287,10 @@ val overlayButtonsPatch = resourcePatch( if (id.equals("@+id/bottom_margin")) { node.setAttribute("android:layout_height", marginBottom) } else if (id.equals("@id/time_bar_reference_view")) { - node.setAttribute("yt:layout_constraintBottom_toTopOf", "@id/quick_actions_container") + node.setAttribute( + "yt:layout_constraintBottom_toTopOf", + "@id/quick_actions_container" + ) } } } 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 22584d1ab..aba912974 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 @@ -7,10 +7,13 @@ 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.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 kotlin.collections.listOf +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.reference.MethodReference internal val shortsSeekbarColorFingerprint = legacyFingerprint( name = "shortsSeekbarColorFingerprint", @@ -113,27 +116,21 @@ internal val seekbarTappingFingerprint = legacyFingerprint( name = "seekbarTappingFingerprint", returnType = "Z", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = listOf("L"), - opcodes = listOf( - Opcode.IPUT_OBJECT, - Opcode.INVOKE_VIRTUAL, - Opcode.RETURN, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT, - Opcode.IF_EQZ, - Opcode.INVOKE_VIRTUAL, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT, - Opcode.IF_EQZ, - Opcode.INT_TO_FLOAT, - Opcode.INT_TO_FLOAT, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT, - Opcode.IF_EQZ - ), - customFingerprint = { method, _ -> method.name == "onTouchEvent" } + parameters = listOf("Landroid/view/MotionEvent;"), + customFingerprint = { method, classDef -> + classDef.interfaces.contains("Landroid/view/View${'$'}OnLayoutChangeListener;") && + classDef.fields.find { it.type == "[Lcom/google/android/libraries/youtube/player/features/overlay/timebar/TimelineMarker;" } != null && + method.name == "onTouchEvent" && + indexOfPointInstruction(method) >= 0 + } ) +internal fun indexOfPointInstruction(method: Method) = + method.indexOfFirstInstructionReversed { + opcode == Opcode.INVOKE_DIRECT && + getReference()?.toString() == "Landroid/graphics/Point;->(II)V" + } + internal val seekbarThumbnailsQualityFingerprint = legacyFingerprint( name = "seekbarThumbnailsQualityFingerprint", 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 441ec5f4c..9654b5956 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 @@ -11,14 +11,12 @@ import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.shared.drawable.addDrawableColorHook import app.revanced.patches.shared.drawable.drawableColorHookPatch import app.revanced.patches.shared.mainactivity.onCreateMethod -import app.revanced.patches.youtube.layout.branding.icon.customBrandingIconPatch import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.youtube.utils.extension.Constants.PATCH_STATUS_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.extension.Constants.PLAYER_PATH import app.revanced.patches.youtube.utils.flyoutmenu.flyoutMenuHookPatch import app.revanced.patches.youtube.utils.mainactivity.mainActivityResolvePatch -import app.revanced.patches.youtube.utils.patch.PatchList.CUSTOM_BRANDING_ICON_FOR_YOUTUBE import app.revanced.patches.youtube.utils.patch.PatchList.SEEKBAR_COMPONENTS import app.revanced.patches.youtube.utils.playerButtonsResourcesFingerprint import app.revanced.patches.youtube.utils.playerButtonsVisibilityFingerprint @@ -37,6 +35,7 @@ import app.revanced.patches.youtube.utils.seekbarFingerprint import app.revanced.patches.youtube.utils.seekbarOnDrawFingerprint import app.revanced.patches.youtube.utils.settings.ResourceUtils.addPreference import app.revanced.patches.youtube.utils.settings.ResourceUtils.getContext +import app.revanced.patches.youtube.utils.settings.ResourceUtils.restoreOldSplashAnimationIncluded import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.patches.youtube.utils.totalTimeFingerprint import app.revanced.patches.youtube.video.information.videoInformationPatch @@ -48,7 +47,6 @@ import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall import app.revanced.util.fingerprint.matchOrThrow import app.revanced.util.fingerprint.methodOrThrow import app.revanced.util.fingerprint.resolvable -import app.revanced.util.getBooleanOptionValue import app.revanced.util.getReference import app.revanced.util.getWalkerMethod import app.revanced.util.indexOfFirstInstructionOrThrow @@ -122,9 +120,6 @@ val seekbarComponentsPatch = bytecodePatch( execute { - val restoreOldSplashAnimationIncluded = CUSTOM_BRANDING_ICON_FOR_YOUTUBE.included == true && - customBrandingIconPatch.getBooleanOptionValue("restoreOldSplashAnimation").value == true - var settingArray = arrayOf( "PREFERENCE_SCREEN: PLAYER", "SETTINGS: SEEKBAR_COMPONENTS" @@ -132,60 +127,71 @@ val seekbarComponentsPatch = bytecodePatch( // region patch for enable seekbar tapping patch - seekbarTappingFingerprint.matchOrThrow().let { - it.method.apply { - val tapSeekIndex = it.patternMatch!!.startIndex + 1 - val tapSeekClass = getInstruction(tapSeekIndex) - .getReference()!! - .definingClass + seekbarTappingFingerprint.methodOrThrow().apply { + val pointIndex = indexOfPointInstruction(this) + val pointInstruction = getInstruction(pointIndex) + val freeRegister = pointInstruction.registerE + val xAxisRegister = pointInstruction.registerD - val tapSeekMethods = findMethodsOrThrow(tapSeekClass) - var pMethodCall = "" - var oMethodCall = "" - - for (method in tapSeekMethods) { - if (method.implementation == null) - continue - - val instructions = method.implementation!!.instructions - // here we make sure we actually find the method because it has more than 7 instructions - if (instructions.count() != 10) - continue - - // we know that the 7th instruction has the opcode CONST_4 - val instruction = instructions.elementAt(6) - if (instruction.opcode != Opcode.CONST_4) - continue - - // the literal for this instruction has to be either 1 or 2 - val literal = (instruction as NarrowLiteralInstruction).narrowLiteral - - // method founds - if (literal == 1) - pMethodCall = "${method.definingClass}->${method.name}(I)V" - else if (literal == 2) - oMethodCall = "${method.definingClass}->${method.name}(I)V" - } - - if (pMethodCall.isEmpty()) { - throw PatchException("pMethod not found") - } - if (oMethodCall.isEmpty()) { - throw PatchException("oMethod not found") - } - - val insertIndex = it.patternMatch!!.startIndex + 2 - - addInstructionsWithLabels( - insertIndex, """ - invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->enableSeekbarTapping()Z - move-result v0 - if-eqz v0, :disabled - invoke-virtual { p0, v2 }, $pMethodCall - invoke-virtual { p0, v2 }, $oMethodCall - """, ExternalLabel("disabled", getInstruction(insertIndex)) - ) + val tapSeekIndex = indexOfFirstInstructionOrThrow(pointIndex) { + val reference = getReference() + opcode == Opcode.INVOKE_VIRTUAL && + reference?.returnType == "V" && + reference.parameterTypes.isEmpty() } + val thisInstanceRegister = + getInstruction(tapSeekIndex).registerC + + val tapSeekClass = getInstruction(tapSeekIndex) + .getReference()!! + .definingClass + + val tapSeekMethods = findMethodsOrThrow(tapSeekClass) + var pMethodCall = "" + var oMethodCall = "" + + for (method in tapSeekMethods) { + if (method.implementation == null) + continue + + val instructions = method.implementation!!.instructions + // here we make sure we actually find the method because it has more than 7 instructions + if (instructions.count() != 10) + continue + + // we know that the 7th instruction has the opcode CONST_4 + val instruction = instructions.elementAt(6) + if (instruction.opcode != Opcode.CONST_4) + continue + + // the literal for this instruction has to be either 1 or 2 + val literal = (instruction as NarrowLiteralInstruction).narrowLiteral + + // method founds + if (literal == 1) + pMethodCall = "${method.definingClass}->${method.name}(I)V" + else if (literal == 2) + oMethodCall = "${method.definingClass}->${method.name}(I)V" + } + + if (pMethodCall.isEmpty()) { + throw PatchException("pMethod not found") + } + if (oMethodCall.isEmpty()) { + throw PatchException("oMethod not found") + } + + val insertIndex = tapSeekIndex + 1 + + addInstructionsWithLabels( + insertIndex, """ + invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->enableSeekbarTapping()Z + move-result v$freeRegister + if-eqz v$freeRegister, :disabled + invoke-virtual { v$thisInstanceRegister, v$xAxisRegister }, $pMethodCall + invoke-virtual { v$thisInstanceRegister, v$xAxisRegister }, $oMethodCall + """, ExternalLabel("disabled", getInstruction(insertIndex)) + ) } // endregion @@ -269,7 +275,10 @@ val seekbarComponentsPatch = bytecodePatch( playerSeekbarHandleColorPrimaryFingerprint, playerSeekbarHandleColorSecondaryFingerprint ).forEach { - it.methodOrThrow().addColorChangeInstructions(ytStaticBrandRed, "getVideoPlayerSeekbarColorAccent") + 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 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 00f4cc521..92d882cd9 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 @@ -20,8 +20,36 @@ 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.FieldReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference -import kotlin.collections.listOf + +internal val bottomSheetMenuDismissFingerprint = legacyFingerprint( + name = "bottomSheetMenuDismissFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = emptyList(), + customFingerprint = { method, _ -> + indexOfDismissInstruction(method) >= 0 + } +) + +fun indexOfDismissInstruction(method: Method) = + method.indexOfFirstInstruction { + val reference = getReference() + reference?.name == "dismiss" && + reference.returnType == "V" && + reference.parameterTypes.isEmpty() + } + +internal val bottomSheetMenuItemClickFingerprint = legacyFingerprint( + name = "bottomSheetMenuItemClickFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "V", + parameters = listOf("Landroid/widget/AdapterView;", "Landroid/view/View;", "I", "J"), + customFingerprint = { method, _ -> + method.name == "onItemClick" + } +) internal val bottomSheetMenuListBuilderFingerprint = legacyFingerprint( name = "bottomSheetMenuListBuilderFingerprint", @@ -71,6 +99,41 @@ internal val reelEnumStaticFingerprint = legacyFingerprint( returnType = "L" ) +/** + * YouTube 18.49.36 ~ + */ +internal val reelPlaybackRepeatFingerprint = legacyFingerprint( + name = "reelPlaybackRepeatFingerprint", + returnType = "V", + parameters = listOf("L"), + strings = listOf("YoutubePlayerState is in throwing an Error.") +) + +internal val reelPlaybackFingerprint = legacyFingerprint( + name = "reelPlaybackFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("J"), + returnType = "V", + customFingerprint = { method, _ -> + indexOfMilliSecondsInstruction(method) >= 0 && + indexOfInitializationInstruction(method) >= 0 + } +) + +private fun indexOfMilliSecondsInstruction(method: Method) = + method.indexOfFirstInstruction { + getReference()?.name == "MILLISECONDS" + } + +internal fun indexOfInitializationInstruction(method: Method) = + method.indexOfFirstInstruction { + val reference = getReference() + opcode == Opcode.INVOKE_DIRECT && + reference?.name == "" && + reference.parameterTypes.size == 3 && + reference.parameterTypes.firstOrNull() == "I" + } + internal const val SHORTS_HUD_FEATURE_FLAG = 45644023L /** 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 e327d3e75..e124d373a 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 @@ -33,10 +33,13 @@ import app.revanced.patches.youtube.utils.patch.PatchList.SHORTS_COMPONENTS import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch import app.revanced.patches.youtube.utils.playservice.is_18_31_or_greater import app.revanced.patches.youtube.utils.playservice.is_18_34_or_greater +import app.revanced.patches.youtube.utils.playservice.is_18_49_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_02_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_11_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_28_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_34_or_greater +import app.revanced.patches.youtube.utils.playservice.is_20_09_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.recyclerview.recyclerViewTreeObserverHook import app.revanced.patches.youtube.utils.recyclerview.recyclerViewTreeObserverPatch @@ -60,6 +63,7 @@ import app.revanced.patches.youtube.utils.settings.ResourceUtils.getContext import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.patches.youtube.utils.toolbar.hookToolBar import app.revanced.patches.youtube.utils.toolbar.toolBarHookPatch +import app.revanced.patches.youtube.utils.videoIdFingerprintShorts import app.revanced.patches.youtube.video.information.hookShortsVideoInformation import app.revanced.patches.youtube.video.information.videoInformationPatch import app.revanced.patches.youtube.video.playbackstart.PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR @@ -75,7 +79,6 @@ import app.revanced.util.cloneMutable import app.revanced.util.copyResources import app.revanced.util.findMethodOrThrow import app.revanced.util.findMutableMethodOf -import app.revanced.util.fingerprint.definingClassOrThrow import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall import app.revanced.util.fingerprint.matchOrThrow import app.revanced.util.fingerprint.methodOrThrow @@ -94,6 +97,7 @@ import app.revanced.util.or import app.revanced.util.replaceLiteralInstructionCall 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.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction @@ -335,7 +339,35 @@ private val shortsCustomActionsPatch = bytecodePatch( } } - recyclerViewTreeObserverHook("$EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR->onFlyoutMenuCreate(Landroid/support/v7/widget/RecyclerView;)V") + if (is_19_11_or_greater) { + // The type of the Shorts flyout menu is RecyclerView. + recyclerViewTreeObserverHook("$EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR->onFlyoutMenuCreate(Landroid/support/v7/widget/RecyclerView;)V") + } else { + // The type of the Shorts flyout menu is ListView. + val dismissReference = with( + bottomSheetMenuDismissFingerprint.methodOrThrow( + bottomSheetMenuListBuilderFingerprint + ) + ) { + val dismissIndex = indexOfDismissInstruction(this) + getInstruction(dismissIndex).reference + } + + bottomSheetMenuItemClickFingerprint + .methodOrThrow(bottomSheetMenuListBuilderFingerprint) + .addInstructionsWithLabels( + 0, + """ + invoke-static/range {p2 .. p2}, $EXTENSION_CUSTOM_ACTIONS_CLASS_DESCRIPTOR->onBottomSheetMenuItemClick(Landroid/view/View;)Z + move-result v0 + if-eqz v0, :ignore + invoke-virtual {p0}, $dismissReference + return-void + :ignore + nop + """, + ) + } // endregion @@ -403,9 +435,7 @@ private val shortsRepeatPatch = bytecodePatch( "setMainActivity" ) - val reelEnumClass = reelEnumConstructorFingerprint.definingClassOrThrow() - - reelEnumConstructorFingerprint.methodOrThrow().apply { + val endScreenReference = with(reelEnumConstructorFingerprint.methodOrThrow()) { val insertIndex = indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID) addInstructions( @@ -413,7 +443,7 @@ private val shortsRepeatPatch = bytecodePatch( """ # Pass the first enum value to extension. # Any enum value of this type will work. - sget-object v0, $reelEnumClass->a:$reelEnumClass + sget-object v0, $definingClass->a:$definingClass invoke-static { v0 }, $EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR->setYTShortsRepeatEnum(Ljava/lang/Enum;)V """, ) @@ -422,50 +452,125 @@ private val shortsRepeatPatch = bytecodePatch( indexOfFirstStringInstructionOrThrow("REEL_LOOP_BEHAVIOR_END_SCREEN") val endScreenReferenceIndex = indexOfFirstInstructionOrThrow(endScreenStringIndex, Opcode.SPUT_OBJECT) - val endScreenReference = - getInstruction(endScreenReferenceIndex).reference.toString() - val enumMethod = reelEnumStaticFingerprint.methodOrThrow(reelEnumConstructorFingerprint) + getInstruction(endScreenReferenceIndex).reference.toString() + } + + lateinit var insertMethod: MutableMethod + var insertMethodFound = false + + if (is_18_49_or_greater) { + insertMethod = reelPlaybackRepeatFingerprint.methodOrThrow() + } else { + val isInsertMethod: Method.() -> Boolean = { + parameters.size == 1 && + parameterTypes.first().startsWith("L") && + returnType == "V" && + indexOfFirstInstruction { + getReference()?.toString() == endScreenReference + } >= 0 + } classes.forEach { classDef -> - classDef.methods.filter { method -> - method.parameters.size == 1 && - method.parameters[0].startsWith("L") && - method.returnType == "V" && - method.indexOfFirstInstruction { - getReference()?.toString() == endScreenReference - } >= 0 - }.forEach { targetMethod -> - proxy(classDef) - .mutableClass - .findMutableMethodOf(targetMethod) - .apply { - implementation!!.instructions - .withIndex() - .filter { (_, instruction) -> - val reference = - (instruction as? ReferenceInstruction)?.reference - reference is MethodReference && - MethodUtil.methodSignaturesMatch(enumMethod, reference) - } - .map { (index, _) -> index } - .reversed() - .forEach { index -> - val register = - getInstruction(index + 1).registerA - - addInstructions( - index + 2, """ - invoke-static {v$register}, $EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR->changeShortsRepeatBehavior(Ljava/lang/Enum;)Ljava/lang/Enum; - move-result-object v$register - """ - ) - } + if (!insertMethodFound) { + classDef.methods.forEach { method -> + if (method.isInsertMethod()) { + insertMethodFound = true + insertMethod = proxy(classDef) + .mutableClass + .findMutableMethodOf(method) } + } } } } + val enumMethod = reelEnumStaticFingerprint.methodOrThrow(reelEnumConstructorFingerprint) + + insertMethod.apply { + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + val reference = + (instruction as? ReferenceInstruction)?.reference + reference is MethodReference && + MethodUtil.methodSignaturesMatch(enumMethod, reference) + } + .map { (index, _) -> index } + .reversed() + .forEach { index -> + val register = + getInstruction(index + 1).registerA + + addInstructions( + index + 2, """ + invoke-static {v$register}, $EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR->changeShortsRepeatBehavior(Ljava/lang/Enum;)Ljava/lang/Enum; + move-result-object v$register + """ + ) + } + } + + // As of YouTube 20.09, Google has removed the code for 'Autoplay' and 'Pause' from this method. + // Manually add the 'Autoplay' code that Google removed. + // Tested on YouTube 20.10. + if (is_20_09_or_greater) { + val (directReference, virtualReference) = with( + reelPlaybackFingerprint.methodOrThrow( + videoIdFingerprintShorts + ) + ) { + val directIndex = indexOfInitializationInstruction(this) + val virtualIndex = indexOfFirstInstructionOrThrow(directIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.parameterTypes?.size == 1 + } + + Pair( + getInstruction(directIndex).reference as MethodReference, + getInstruction(virtualIndex).reference as MethodReference + ) + } + + insertMethod.apply { + val extensionIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_STATIC && + getReference()?.definingClass == EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR + } + val enumRegister = + getInstruction(extensionIndex + 1).registerA + val freeIndex = indexOfFirstInstructionOrThrow(extensionIndex) { + opcode == Opcode.SGET_OBJECT && + getReference()?.name != "a" + } + val freeRegister = getInstruction(freeIndex).registerA + val getIndex = indexOfFirstInstructionOrThrow(extensionIndex) { + val reference = getReference() + opcode == Opcode.IGET_OBJECT && + reference?.definingClass == definingClass && + reference.type == virtualReference.definingClass + } + val getReference = getInstruction(getIndex).reference + + addInstructionsWithLabels( + extensionIndex + 2, """ + invoke-static {v$enumRegister}, $EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR->isAutoPlay(Ljava/lang/Enum;)Z + move-result v$freeRegister + if-eqz v$freeRegister, :ignore + new-instance v0, ${directReference.definingClass} + const/4 v1, 0x3 + const/4 v2, 0x0 + invoke-direct {v0, v1, v2, v2}, $directReference + iget-object v3, p0, $getReference + invoke-virtual {v3, v0}, $virtualReference + return-void + :ignore + nop + """ + ) + } + } + if (is_19_34_or_greater) { shortsHUDFeatureFingerprint.injectLiteralInstructionBooleanCall( SHORTS_HUD_FEATURE_FLAG, @@ -919,7 +1024,8 @@ val shortsComponentPatch = bytecodePatch( getReference()?.returnType == PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR } val freeRegister = getInstruction(index).registerC - val playbackStartRegister = getInstruction(index + 1).registerA + val playbackStartRegister = + getInstruction(index + 1).registerA addInstructionsWithLabels( index + 2, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch.kt index af0c104b5..3b82fb404 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch.kt @@ -160,13 +160,13 @@ val swipeControlsPatch = bytecodePatch( val reference = getReference() opcode == Opcode.INVOKE_VIRTUAL && reference?.returnType == "V" && - reference.parameterTypes.size == 0 + reference.parameterTypes.isEmpty() } val targetIndex = indexOfFirstInstructionOrThrow(middleIndex + 1) { val reference = getReference() opcode == Opcode.INVOKE_VIRTUAL && reference?.returnType == "V" && - reference.parameterTypes.size == 0 + reference.parameterTypes.isEmpty() } if (getInstruction(targetIndex - 1).opcode != Opcode.IGET_OBJECT) { throw PatchException( @@ -242,10 +242,16 @@ val swipeControlsPatch = bytecodePatch( "youtube/swipecontrols", ResourceGroup( "drawable", - "ic_sc_brightness_auto.xml", - "ic_sc_brightness_manual.xml", - "ic_sc_volume_mute.xml", - "ic_sc_volume_normal.xml" + "revanced_ic_sc_brightness_auto.xml", + "revanced_ic_sc_brightness_full.xml", + "revanced_ic_sc_brightness_high.xml", + "revanced_ic_sc_brightness_low.xml", + "revanced_ic_sc_brightness_manual.xml", + "revanced_ic_sc_brightness_medium.xml", + "revanced_ic_sc_volume_high.xml", + "revanced_ic_sc_volume_low.xml", + "revanced_ic_sc_volume_mute.xml", + "revanced_ic_sc_volume_normal.xml", ) ) 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 7488b3d1e..b7ecbe4cb 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 @@ -16,6 +16,7 @@ import app.revanced.patches.youtube.utils.resourceid.totalTime import app.revanced.patches.youtube.utils.resourceid.varispeedUnavailableTitle import app.revanced.patches.youtube.utils.resourceid.videoQualityBottomSheet import app.revanced.patches.youtube.utils.sponsorblock.sponsorBlockBytecodePatch +import app.revanced.util.containsLiteralInstruction import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstruction @@ -23,6 +24,7 @@ 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.FieldReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference internal val bottomSheetMenuItemBuilderFingerprint = legacyFingerprint( @@ -107,25 +109,26 @@ internal val qualityMenuViewInflateFingerprint = legacyFingerprint( returnType = "L", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("L", "L", "L"), - opcodes = listOf( - Opcode.INVOKE_SUPER, - Opcode.CONST, - Opcode.CONST_4, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT, - Opcode.CONST, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT, - Opcode.CONST_16, - Opcode.INVOKE_VIRTUAL, - Opcode.CONST, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT_OBJECT, - Opcode.CHECK_CAST - ), - literals = listOf(videoQualityBottomSheet), + customFingerprint = custom@{ method, _ -> + if (!method.containsLiteralInstruction(videoQualityBottomSheet)) { + return@custom false + } + if (indexOfAddHeaderViewInstruction(method) < 0) { + return@custom false + } + val implementation = method.implementation + ?: return@custom false + + implementation.instructions.elementAt(0).opcode == Opcode.INVOKE_SUPER + } ) +internal fun indexOfAddHeaderViewInstruction(method: Method) = + method.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "addHeaderView" + } + internal val rollingNumberTextViewAnimationUpdateFingerprint = legacyFingerprint( name = "rollingNumberTextViewAnimationUpdateFingerprint", returnType = "V", @@ -219,6 +222,29 @@ internal val videoEndFingerprint = legacyFingerprint( literals = listOf(45368273L), ) +/** + * This fingerprint is compatible with all versions of YouTube starting from v18.29.38 to supported versions. + * This method is invoked only in Shorts. + * Accurate video information is invoked even when the user moves Shorts upward or downward. + */ +internal val videoIdFingerprintShorts = legacyFingerprint( + name = "videoIdFingerprintShorts", + returnType = "V", + parameters = listOf(PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR), + opcodes = listOf( + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT + ), + customFingerprint = custom@{ method, _ -> + if (method.containsLiteralInstruction(45365621L)) + return@custom true + + method.indexOfFirstInstruction { + getReference()?.name == "reelWatchEndpoint" + } >= 0 + } +) + /** * Several instructions are added to this method by different patches. * Therefore, patches using this fingerprint should not use the [Opcode] pattern, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/compatibility/Constants.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/compatibility/Constants.kt index 0ac2b447c..a28a5ca7a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/compatibility/Constants.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/compatibility/Constants.kt @@ -9,13 +9,12 @@ internal object Constants { val COMPATIBLE_PACKAGE: Pair?> = Pair( YOUTUBE_PACKAGE_NAME, setOf( - "18.29.38", // This is the last version where the 'Zoomed to fill' setting works. - "18.33.40", // This is the last version that do not use litho components in Shorts. - "18.38.44", // This is the last version with no delay in applying video quality on the server side. - "18.48.39", // This is the last version that do not use Rolling Number. "19.05.36", // This is the last version with the least YouTube experimental flag. "19.16.39", // This is the last version where the 'Restore old seekbar thumbnails' setting works. - "19.44.39", // This is the latest version supported by the RVX patch. + "19.43.41", // This is the latest version where edge-to-edge display is not enforced on Android 15+. + "19.44.39", // This is the only version that has experimental shortcut icons. + "19.47.53", // This was the latest version supported by the previous RVX patch. + "20.03.43", // 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/youtube/utils/engagement/EngagementPanelHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/engagement/EngagementPanelHookPatch.kt index 6af2931bc..bd58204fe 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 @@ -6,6 +6,7 @@ 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.Constants.SHARED_PATH +import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch import app.revanced.util.findMethodOrThrow import app.revanced.util.fingerprint.methodOrThrow @@ -29,7 +30,10 @@ internal var engagementPanelIdRegister = 0 val engagementPanelHookPatch = bytecodePatch( description = "engagementPanelHookPatch" ) { - dependsOn(sharedResourceIdPatch) + dependsOn( + sharedExtensionPatch, + sharedResourceIdPatch, + ) execute { fun Method.setFreeIndex(startIndex: Int) { diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/extension/SharedExtensionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/extension/SharedExtensionPatch.kt index e2ba7dc77..bab6ee3da 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/extension/SharedExtensionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/extension/SharedExtensionPatch.kt @@ -1,17 +1,9 @@ package app.revanced.patches.youtube.utils.extension -import app.revanced.patches.shared.extension.hooks.cronetEngineContextHook -import app.revanced.patches.shared.extension.hooks.firebaseInitProviderContextHook import app.revanced.patches.shared.extension.sharedExtensionPatch import app.revanced.patches.youtube.utils.extension.hooks.applicationInitHook -import app.revanced.patches.youtube.utils.extension.hooks.mainActivityBaseContextHook -import app.revanced.patches.youtube.utils.extension.hooks.urlActivityBaseContextHook // TODO: Move this to a "Hook.kt" file. Same for other extension hook patches. val sharedExtensionPatch = sharedExtensionPatch( applicationInitHook, - cronetEngineContextHook, - firebaseInitProviderContextHook, - mainActivityBaseContextHook, - urlActivityBaseContextHook, ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/extension/hooks/MainActivityBaseContextHook.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/extension/hooks/MainActivityBaseContextHook.kt deleted file mode 100644 index 1f17ed305..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/extension/hooks/MainActivityBaseContextHook.kt +++ /dev/null @@ -1,36 +0,0 @@ -package app.revanced.patches.youtube.utils.extension.hooks - -import app.revanced.patches.shared.extension.extensionHook -import app.revanced.util.getReference -import app.revanced.util.indexOfFirstInstructionOrThrow -import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction -import com.android.tools.smali.dexlib2.iface.reference.MethodReference - -private var attachBaseContextIndex = -1 - -internal val mainActivityBaseContextHook = extensionHook( - insertIndexResolver = { method -> - attachBaseContextIndex = method.indexOfFirstInstructionOrThrow { - getReference()?.name == "attachBaseContext" - } - - attachBaseContextIndex + 1 - }, - contextRegisterResolver = { method -> - val overrideInstruction = - method.implementation!!.instructions.elementAt(attachBaseContextIndex) - as FiveRegisterInstruction - "v${overrideInstruction.registerD}" - }, -) { - returns("V") - parameters("Landroid/content/Context;") - custom { method, classDef -> - method.name == "attachBaseContext" && - ( - classDef.endsWith("/MainActivity;") || - // Old versions of YouTube called this class "WatchWhileActivity" instead. - classDef.endsWith("/WatchWhileActivity;") - ) - } -} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/extension/hooks/UrlActivityBaseContextHook.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/extension/hooks/UrlActivityBaseContextHook.kt deleted file mode 100644 index b14d0b71e..000000000 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/extension/hooks/UrlActivityBaseContextHook.kt +++ /dev/null @@ -1,32 +0,0 @@ -package app.revanced.patches.youtube.utils.extension.hooks - -import app.revanced.patches.shared.extension.extensionHook -import app.revanced.util.getReference -import app.revanced.util.indexOfFirstInstructionOrThrow -import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction -import com.android.tools.smali.dexlib2.iface.reference.MethodReference - -private var attachBaseContextIndex = -1 - -internal val urlActivityBaseContextHook = extensionHook( - insertIndexResolver = { method -> - attachBaseContextIndex = method.indexOfFirstInstructionOrThrow { - getReference()?.name == "attachBaseContext" - } - - attachBaseContextIndex + 1 - }, - contextRegisterResolver = { method -> - val overrideInstruction = - method.implementation!!.instructions.elementAt(attachBaseContextIndex) - as FiveRegisterInstruction - "v${overrideInstruction.registerD}" - }, -) { - returns("V") - parameters("Landroid/content/Context;") - custom { method, classDef -> - classDef.endsWith("/Shell_UrlActivity;") && - method.name == "attachBaseContext" - } -} 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 ae2d3c8cf..223de2ee9 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 @@ -116,7 +116,7 @@ val cairoFragmentPatch = resourcePatch( ?.let { node -> node.insertNode("Preference", node) { for (index in 0 until node.attributes.length) { - with (node.attributes.item(index)) { + with(node.attributes.item(index)) { setAttribute(nodeName, nodeValue) } } 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 d96edd681..b4f56f4fe 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 @@ -1,10 +1,10 @@ package app.revanced.patches.youtube.utils.fix.cairo +import app.revanced.patches.youtube.utils.resourceid.settingsFragment +import app.revanced.patches.youtube.utils.resourceid.settingsFragmentCairo import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.or import com.android.tools.smali.dexlib2.AccessFlags -import app.revanced.patches.youtube.utils.resourceid.settingsFragment -import app.revanced.patches.youtube.utils.resourceid.settingsFragmentCairo import com.android.tools.smali.dexlib2.Opcode /** diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/playbackspeed/PlaybackSpeedWhilePlayingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/playbackspeed/PlaybackSpeedWhilePlayingPatch.kt index 26e4b110e..ee5ab2ead 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/playbackspeed/PlaybackSpeedWhilePlayingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/playbackspeed/PlaybackSpeedWhilePlayingPatch.kt @@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWith import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.youtube.utils.engagement.engagementPanelHookPatch import app.revanced.patches.youtube.utils.extension.Constants.UTILS_PATH import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch @@ -22,6 +23,7 @@ val playbackSpeedWhilePlayingPatch = bytecodePatch( ) { dependsOn( sharedExtensionPatch, + engagementPanelHookPatch, playerTypeHookPatch, versionCheckPatch, ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/splash/DarkModeSplashScreenPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/splash/DarkModeSplashScreenPatch.kt index 198a22138..2e3276993 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/splash/DarkModeSplashScreenPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/splash/DarkModeSplashScreenPatch.kt @@ -1,66 +1,39 @@ package app.revanced.patches.youtube.utils.fix.splash import app.revanced.patcher.patch.resourcePatch -import app.revanced.patches.youtube.layout.branding.icon.customBrandingIconPatch -import app.revanced.patches.youtube.utils.patch.PatchList.CUSTOM_BRANDING_ICON_FOR_YOUTUBE import app.revanced.patches.youtube.utils.playservice.is_19_32_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch -import app.revanced.util.getBooleanOptionValue import org.w3c.dom.Element +/** + * Fix the splash screen dark mode background color. + * In earlier versions of the app this is white and makes no sense for dark mode. + * This is only required for 19.32 and greater, but is applied to all targets. + * Only dark mode needs this fix as light mode correctly uses the custom color. + * + * This is a bug in unpatched YouTube. + * Should always be applied even if the `Theme` patch is excluded. + */ val darkModeSplashScreenPatch = resourcePatch( description = "darkModeSplashScreenPatch" ) { dependsOn(versionCheckPatch) - finalize { - val restoreOldSplashAnimationIncluded = is_19_32_or_greater && - CUSTOM_BRANDING_ICON_FOR_YOUTUBE.included == true && - customBrandingIconPatch.getBooleanOptionValue("restoreOldSplashAnimation").value == true + execute { + if (!is_19_32_or_greater) { + return@execute + } - /** - * Fix the splash screen dark mode background color. - * In earlier versions of the app this is white and makes no sense for dark mode. - * This is only required for 19.32 and greater, but is applied to all targets. - * Only dark mode needs this fix as light mode correctly uses the custom color. - * - * This is a bug in unpatched YouTube. - * Should always be applied even if the `Theme` patch is excluded. - */ - if (restoreOldSplashAnimationIncluded) { - document("res/values-night/styles.xml").use { document -> - val resourcesNode = document.getElementsByTagName("resources").item(0) as Element - val childNodes = resourcesNode.childNodes - - for (i in 0 until childNodes.length) { - val node = childNodes.item(i) as? Element ?: continue - val nodeAttributeName = node.getAttribute("name") - if (nodeAttributeName.startsWith("Theme.YouTube.Launcher")) { - val nodeAttributeParent = node.getAttribute("parent") - - val style = document.createElement("style") - style.setAttribute("name", "Theme.YouTube.Home") - style.setAttribute("parent", nodeAttributeParent) - - val windowItem = document.createElement("item") - windowItem.setAttribute("name", "android:windowBackground") - windowItem.textContent = "@color/yt_black1" - style.appendChild(windowItem) - - resourcesNode.removeChild(node) - resourcesNode.appendChild(style) - } - } - } - } else { - document("res/values-night-v27/styles.xml").use { document -> + arrayOf( + "values-night" to "@style/Base.V23.Theme.YouTube.Home", + "values-night-v27" to "@style/Base.V27.Theme.YouTube.Home", + ).forEach { (directory, parent) -> + document("res/$directory/styles.xml").use { document -> // Create a night mode specific override for the splash screen background. val style = document.createElement("style") style.setAttribute("name", "Theme.YouTube.Home") - style.setAttribute("parent", "@style/Base.V27.Theme.YouTube.Home") + style.setAttribute("parent", parent) - // Fix status and navigation bar showing white on some Android devices, - // such as SDK 28 Android 10 medium tablet. val colorSplashBackgroundColor = "@color/yt_black1" arrayOf( "android:navigationBarColor" to colorSplashBackgroundColor, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/Fingerprints.kt index fa1662872..214e81a70 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fix/streamingdata/Fingerprints.kt @@ -153,3 +153,18 @@ internal val onesieEncryptionAlternativeFeatureFlagFingerprint = legacyFingerpri name = "onesieEncryptionAlternativeFeatureFlagFingerprint", literals = listOf(ONESIE_ENCRYPTION_ALTERNATIVE_FEATURE_FLAG), ) + +// Feature flag that enables different code for parsing and starting video playback, +// but it's exact purpose is not known. If this flag is enabled while stream spoofing +// then videos will never start playback and load forever. +// Flag does not seem to affect playback if spoofing is off. +// YouTube 19.50 ~ +internal const val PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG = 45665455L + +internal val playbackStartDescriptorFeatureFlagFingerprint = legacyFingerprint( + name = "playbackStartDescriptorFeatureFlagFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = emptyList(), + returnType = ("Z"), + literals = listOf(PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG) +) 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 0befb7493..0ce563db6 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 @@ -8,6 +8,7 @@ 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.booleanOption 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 @@ -19,6 +20,7 @@ import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PAC 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.playservice.is_19_34_or_greater +import app.revanced.patches.youtube.utils.playservice.is_19_50_or_greater import app.revanced.patches.youtube.utils.playservice.is_20_10_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.request.buildRequestPatch @@ -60,12 +62,23 @@ val spoofStreamingDataPatch = bytecodePatch( versionCheckPatch, ) + val useIOSClient by booleanOption( + key = "useIOSClient", + default = false, + title = "Use iOS client", + description = "Add setting to set iOS client (Deprecated) as default client.", + ) + execute { var settingArray = arrayOf( "SETTINGS: SPOOF_STREAMING_DATA" ) + var patchStatusArray = arrayOf( + "SpoofStreamingData" + ) + // region Get replacement streams at player requests. hookBuildRequest("$EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V") @@ -333,24 +346,38 @@ val spoofStreamingDataPatch = bytecodePatch( "$EXTENSION_CLASS_DESCRIPTOR->skipResponseEncryption(Z)Z" ) - if (is_20_10_or_greater) { - onesieEncryptionAlternativeFeatureFlagFingerprint.injectLiteralInstructionBooleanCall( - ONESIE_ENCRYPTION_ALTERNATIVE_FEATURE_FLAG, - "$EXTENSION_CLASS_DESCRIPTOR->skipResponseEncryption(Z)Z" + if (is_19_50_or_greater) { + playbackStartDescriptorFeatureFlagFingerprint.injectLiteralInstructionBooleanCall( + PLAYBACK_START_CHECK_ENDPOINT_USED_FEATURE_FLAG, + "$EXTENSION_CLASS_DESCRIPTOR->usePlaybackStartFeatureFlag(Z)Z" ) + + if (is_20_10_or_greater) { + onesieEncryptionAlternativeFeatureFlagFingerprint.injectLiteralInstructionBooleanCall( + ONESIE_ENCRYPTION_ALTERNATIVE_FEATURE_FLAG, + "$EXTENSION_CLASS_DESCRIPTOR->skipResponseEncryption(Z)Z" + ) + } } settingArray += "SETTINGS: SKIP_RESPONSE_ENCRYPTION" } + if (useIOSClient == true) { + settingArray += "SETTINGS: USE_IOS_DEPRECATED" + patchStatusArray += "SpoofStreamingDataIOS" + } + // endregion - findMethodOrThrow("$PATCHES_PATH/PatchStatus;") { - name == "SpoofStreamingData" - }.replaceInstruction( - 0, - "const/4 v0, 0x1" - ) + patchStatusArray.forEach { methodName -> + findMethodOrThrow("$PATCHES_PATH/PatchStatus;") { + name == methodName + }.replaceInstruction( + 0, + "const/4 v0, 0x1" + ) + } addPreference( settingArray, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fullscreen/FullscreenButtonHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fullscreen/FullscreenButtonHookPatch.kt index b40668b3f..97a12da54 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fullscreen/FullscreenButtonHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/fullscreen/FullscreenButtonHookPatch.kt @@ -22,7 +22,6 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction 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.TypeReference -import kotlin.collections.mutableListOf private const val EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR = "$EXTENSION_PATH/utils/VideoUtils;" @@ -59,7 +58,10 @@ val fullscreenButtonHookPatch = bytecodePatch( getReference()?.name == "addListener" } val animatorListenerAdapterClass = getInstruction( - indexOfFirstInstructionReversedOrThrow(addListenerIndex, Opcode.NEW_INSTANCE) + indexOfFirstInstructionReversedOrThrow( + addListenerIndex, + Opcode.NEW_INSTANCE + ) ).reference.toString() return Pair( findMethodOrThrow(animatorListenerAdapterClass) { parameters.isEmpty() }, 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 c8dca6a6f..508b49496 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_LAYOUT_UPDATES( + "Disable layout updates", + "Adds an option to disable layout updates by server." + ), DISABLE_RESUMING_MINIPLAYER_ON_STARTUP( "Disable resuming Miniplayer on startup", "Adds an option to disable the Miniplayer 'Continue watching' from resuming on app startup." diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/Fingerprints.kt index 3a1f528b5..0446b0376 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playertype/Fingerprints.kt @@ -86,7 +86,7 @@ internal val appCompatToolbarBackButtonFingerprint = legacyFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, returnType = "Landroid/graphics/drawable/Drawable;", parameters = emptyList(), - customFingerprint = { _, classDef -> + customFingerprint = { _, classDef -> classDef.type == "Landroid/support/v7/widget/Toolbar;" }, ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/Fingerprints.kt new file mode 100644 index 000000000..10c9e073d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/Fingerprints.kt @@ -0,0 +1,71 @@ +package app.revanced.patches.youtube.utils.playlist + +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.FieldReference + +internal val accountIdentityFingerprint = legacyFingerprint( + name = "accountIdentityFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + customFingerprint = { method, _ -> + method.definingClass.endsWith("${'$'}AutoValue_AccountIdentity;") + } +) + +internal val editPlaylistConstructorFingerprint = legacyFingerprint( + name = "editPlaylistConstructorFingerprint", + returnType = "V", + strings = listOf("browse/edit_playlist") +) + +internal val editPlaylistFingerprint = legacyFingerprint( + name = "editPlaylistFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("Ljava/util/List;"), + opcodes = listOf( + Opcode.CHECK_CAST, + Opcode.IGET_OBJECT, + ), +) + +internal val playlistEndpointFingerprint = legacyFingerprint( + name = "playlistEndpointFingerprint", + returnType = "L", + parameters = listOf("L", "Ljava/lang/String;"), + customFingerprint = { method, _ -> + method.indexOfFirstInstruction { + opcode == Opcode.SGET_OBJECT && + getReference()?.name == "playlistEditEndpoint" + } >= 0 && indexOfSetVideoIdInstruction(method) >= 0 + } +) + +internal fun indexOfSetVideoIdInstruction(method: Method) = + method.indexOfFirstInstruction { + opcode == Opcode.IPUT_OBJECT && + getReference()?.type == "Ljava/lang/String;" + } + +internal val setPivotBarVisibilityFingerprint = legacyFingerprint( + name = "setPivotBarVisibilityFingerprint", + accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, + returnType = "V", + parameters = listOf("Z"), + opcodes = listOf( + Opcode.CHECK_CAST, + Opcode.IF_EQZ, + ), +) + +internal val setPivotBarVisibilityParentFingerprint = legacyFingerprint( + name = "setPivotBarVisibilityFingerprint", + parameters = listOf("Z"), + strings = listOf("FEnotifications_inbox"), +) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/PlaylistPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/PlaylistPatch.kt new file mode 100644 index 000000000..c0327377b --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playlist/PlaylistPatch.kt @@ -0,0 +1,104 @@ +package app.revanced.patches.youtube.utils.playlist + +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.PatchException +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.shared.mainactivity.getMainActivityMethod +import app.revanced.patches.youtube.utils.extension.Constants.UTILS_PATH +import app.revanced.patches.youtube.utils.extension.sharedExtensionPatch +import app.revanced.patches.youtube.utils.mainactivity.mainActivityResolvePatch +import app.revanced.patches.youtube.utils.request.buildRequestPatch +import app.revanced.patches.youtube.utils.request.hookBuildRequest +import app.revanced.util.fingerprint.matchOrThrow +import app.revanced.util.fingerprint.methodOrThrow +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 + +private const val EXTENSION_CLASS_DESCRIPTOR = + "$UTILS_PATH/PlaylistPatch;" + +val playlistPatch = bytecodePatch( + description = "playlistPatch", +) { + dependsOn( + sharedExtensionPatch, + mainActivityResolvePatch, + buildRequestPatch, + ) + + execute { + // In Incognito mode, sending a request always seems to fail. + accountIdentityFingerprint.methodOrThrow().addInstructions( + 1, """ + sput-object p3, $EXTENSION_CLASS_DESCRIPTOR->dataSyncId:Ljava/lang/String; + sput-boolean p4, $EXTENSION_CLASS_DESCRIPTOR->isIncognito:Z + """ + ) + + // Get the header to use the auth token. + hookBuildRequest("$EXTENSION_CLASS_DESCRIPTOR->setRequestHeaders(Ljava/lang/String;Ljava/util/Map;)V") + + // Open the queue manager by pressing and holding the back button. + getMainActivityMethod("onKeyLongPress") + .addInstructionsWithLabels( + 0, """ + invoke-static/range {p1 .. p1}, $EXTENSION_CLASS_DESCRIPTOR->onKeyLongPress(I)Z + move-result v0 + if-eqz v0, :ignore + return v0 + :ignore + nop + """ + ) + + val setVideoIdReference = with(playlistEndpointFingerprint.methodOrThrow()) { + val setVideoIdIndex = indexOfSetVideoIdInstruction(this) + getInstruction(setVideoIdIndex).reference as FieldReference + } + + // Users deleted videos via YouTube's flyout menu. + editPlaylistFingerprint + .matchOrThrow(editPlaylistConstructorFingerprint) + .let { + it.method.apply { + val castIndex = it.patternMatch!!.startIndex + val castClass = + getInstruction(castIndex).reference.toString() + + if (castClass != setVideoIdReference.definingClass) { + throw PatchException("Method signature parameter did not match: $castClass") + } + val castRegister = getInstruction(castIndex).registerA + val insertIndex = castIndex + 1 + val insertRegister = + getInstruction(insertIndex).registerA + + addInstructions( + insertIndex, """ + iget-object v$insertRegister, v$castRegister, $setVideoIdReference + invoke-static {v$insertRegister}, $EXTENSION_CLASS_DESCRIPTOR->removeFromQueue(Ljava/lang/String;)V + """ + ) + } + } + + setPivotBarVisibilityFingerprint + .matchOrThrow(setPivotBarVisibilityParentFingerprint) + .let { + it.method.apply { + val viewIndex = it.patternMatch!!.startIndex + val viewRegister = getInstruction(viewIndex).registerA + addInstruction( + viewIndex + 1, + "invoke-static {v$viewRegister}," + + " $EXTENSION_CLASS_DESCRIPTOR->setPivotBar(Lcom/google/android/libraries/youtube/rendering/ui/pivotbar/PivotBar;)V", + ) + } + } + } +} 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 ef0ca18fa..19bf4774f 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 @@ -15,18 +15,26 @@ var is_18_42_or_greater = false private set var is_18_49_or_greater = false private set +var is_19_01_or_greater = false + private set var is_19_02_or_greater = false private set var is_19_04_or_greater = false private set +var is_19_05_or_greater = false + private set var is_19_09_or_greater = false private set +var is_19_11_or_greater = false + private set var is_19_15_or_greater = false private set var is_19_16_or_greater = false private set var is_19_17_or_greater = false private set +var is_19_18_or_greater = false + private set var is_19_23_or_greater = false private set var is_19_25_or_greater = false @@ -55,14 +63,20 @@ var is_19_46_or_greater = false private set var is_19_49_or_greater = false private set +var is_19_50_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 +var is_20_09_or_greater = false + private set var is_20_10_or_greater = false private set +var is_20_12_or_greater = false + private set val versionCheckPatch = resourcePatch( description = "versionCheckPatch", @@ -83,12 +97,16 @@ val versionCheckPatch = resourcePatch( is_18_39_or_greater = 234000000 <= playStoreServicesVersion is_18_42_or_greater = 234302000 <= playStoreServicesVersion is_18_49_or_greater = 235000000 <= playStoreServicesVersion - is_19_02_or_greater = 240204000 < playStoreServicesVersion + is_19_01_or_greater = 240204000 < playStoreServicesVersion + is_19_02_or_greater = 240299000 < playStoreServicesVersion is_19_04_or_greater = 240502000 <= playStoreServicesVersion + is_19_05_or_greater = 240602000 <= playStoreServicesVersion is_19_09_or_greater = 241002000 <= playStoreServicesVersion + is_19_11_or_greater = 241199000 <= playStoreServicesVersion is_19_15_or_greater = 241602000 <= playStoreServicesVersion is_19_16_or_greater = 241702000 <= playStoreServicesVersion is_19_17_or_greater = 241802000 <= playStoreServicesVersion + is_19_18_or_greater = 241902000 <= playStoreServicesVersion is_19_23_or_greater = 242402000 <= playStoreServicesVersion is_19_25_or_greater = 242599000 <= playStoreServicesVersion is_19_26_or_greater = 242705000 <= playStoreServicesVersion @@ -103,9 +121,12 @@ val versionCheckPatch = resourcePatch( is_19_44_or_greater = 244505000 <= playStoreServicesVersion is_19_46_or_greater = 244705000 <= playStoreServicesVersion is_19_49_or_greater = 245005000 <= playStoreServicesVersion + is_19_50_or_greater = 245105000 <= playStoreServicesVersion is_20_02_or_greater = 250299000 <= playStoreServicesVersion is_20_03_or_greater = 250405000 <= playStoreServicesVersion is_20_05_or_greater = 250605000 <= playStoreServicesVersion + is_20_09_or_greater = 251006000 <= playStoreServicesVersion is_20_10_or_greater = 251105000 <= playStoreServicesVersion + is_20_12_or_greater = 251305000 <= playStoreServicesVersion } } 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 6bf586431..edce5c86e 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 @@ -11,9 +11,8 @@ import app.revanced.patches.shared.mapping.ResourceType.LAYOUT import app.revanced.patches.shared.mapping.ResourceType.STRING import app.revanced.patches.shared.mapping.ResourceType.STYLE import app.revanced.patches.shared.mapping.ResourceType.XML -import app.revanced.patches.shared.mapping.get +import app.revanced.patches.shared.mapping.getResourceId import app.revanced.patches.shared.mapping.resourceMappingPatch -import app.revanced.patches.shared.mapping.resourceMappings var accountSwitcherAccessibility = -1L private set @@ -193,8 +192,6 @@ var rightComment = -1L private set var scrimOverlay = -1L private set -var scrubbing = -1L - private set var seekEasyHorizontalTouchOffsetToStartScrubbing = -1L private set var seekUndoEduOverlayStub = -1L @@ -225,6 +222,10 @@ var videoQualityBottomSheet = -1L private set var varispeedUnavailableTitle = -1L private set +var verticalTouchOffsetToEnterFineScrubbing = -1L + private set +var verticalTouchOffsetToStartFineScrubbing = -1L + private set var videoQualityUnavailableAnnouncement = -1L private set var videoZoomSnapIndicator = -1L @@ -235,8 +236,12 @@ var youTubeControlsOverlaySubtitleButton = -1L private set var youTubeLogo = -1L private set +var ytCallToAction = -1L + private set var ytFillBell = -1L private set +var ytOutlineLibrary = -1L + private set var ytOutlineMoonZ = -1L private set var ytOutlinePictureInPictureWhite = -1L @@ -262,485 +267,142 @@ internal val sharedResourceIdPatch = resourcePatch( dependsOn(resourceMappingPatch) execute { - accountSwitcherAccessibility = resourceMappings[ - STRING, - "account_switcher_accessibility_label" - ] - actionBarRingo = resourceMappings[ - LAYOUT, - "action_bar_ringo" - ] - actionBarRingoBackground = resourceMappings[ - LAYOUT, - "action_bar_ringo_background" - ] - adAttribution = resourceMappings[ - ID, - "ad_attribution" - ] - appearance = resourceMappings[ - STRING, - "app_theme_appearance_dark" - ] - appRelatedEndScreenResults = resourceMappings[ - LAYOUT, - "app_related_endscreen_results" - ] - autoNavPreviewStub = resourceMappings[ - ID, - "autonav_preview_stub" - ] - autoNavScrollCancelPadding = resourceMappings[ - DIMEN, - "autonav_scroll_cancel_padding" - ] - autoNavToggle = resourceMappings[ - ID, - "autonav_toggle" - ] - backgroundCategory = resourceMappings[ - STRING, - "pref_background_and_offline_category" - ] - badgeLabel = resourceMappings[ - ID, - "badge_label" - ] - bar = resourceMappings[ - LAYOUT, - "bar" - ] - barContainerHeight = resourceMappings[ - DIMEN, - "bar_container_height" - ] - bottomBarContainer = resourceMappings[ - ID, - "bottom_bar_container" - ] - bottomSheetFooterText = resourceMappings[ - ID, - "bottom_sheet_footer_text" - ] - bottomSheetRecyclerView = resourceMappings[ - LAYOUT, - "bottom_sheet_recycler_view" - ] - bottomUiContainerStub = resourceMappings[ - ID, - "bottom_ui_container_stub" - ] - captionToggleContainer = resourceMappings[ - ID, - "caption_toggle_container" - ] - castMediaRouteButton = resourceMappings[ - LAYOUT, - "castmediaroutebutton" - ] - cfFullscreenButton = resourceMappings[ - ID, - "cf_fullscreen_button" - ] - channelListSubMenu = resourceMappings[ - LAYOUT, - "channel_list_sub_menu" - ] - compactLink = resourceMappings[ - LAYOUT, - "compact_link" - ] - compactListItem = resourceMappings[ - LAYOUT, - "compact_list_item" - ] - componentLongClickListener = resourceMappings[ - ID, - "component_long_click_listener" - ] - contentPill = resourceMappings[ - LAYOUT, - "content_pill" - ] - controlsLayoutStub = resourceMappings[ - ID, - "controls_layout_stub" - ] - darkBackground = resourceMappings[ - ID, - "dark_background" - ] - darkSplashAnimation = resourceMappings[ - ID, - "dark_splash_animation" - ] - designBottomSheet = resourceMappings[ - ID, - "design_bottom_sheet" - ] - donationCompanion = resourceMappings[ - LAYOUT, - "donation_companion" - ] - drawerContentView = resourceMappings[ - ID, - "drawer_content_view" - ] - drawerResults = resourceMappings[ - ID, - "drawer_results" - ] - easySeekEduContainer = resourceMappings[ - ID, - "easy_seek_edu_container" - ] - editSettingsAction = resourceMappings[ - STRING, - "edit_settings_action" - ] - endScreenElementLayoutCircle = resourceMappings[ - LAYOUT, - "endscreen_element_layout_circle" - ] - endScreenElementLayoutIcon = resourceMappings[ - LAYOUT, - "endscreen_element_layout_icon" - ] - endScreenElementLayoutVideo = resourceMappings[ - LAYOUT, - "endscreen_element_layout_video" - ] - emojiPickerIcon = resourceMappings[ - ID, - "emoji_picker_icon" - ] - expandButtonDown = resourceMappings[ - LAYOUT, - "expand_button_down" - ] - fab = resourceMappings[ - ID, - "fab" - ] - fadeDurationFast = resourceMappings[ - INTEGER, - "fade_duration_fast" - ] - filterBarHeight = resourceMappings[ - DIMEN, - "filter_bar_height" - ] - floatyBarTopMargin = resourceMappings[ - DIMEN, - "floaty_bar_button_top_margin" - ] - fullScreenButton = resourceMappings[ - ID, - "fullscreen_button" - ] - fullScreenEngagementAdContainer = resourceMappings[ - ID, - "fullscreen_engagement_ad_container" - ] - fullScreenEngagementOverlay = resourceMappings[ - LAYOUT, - "fullscreen_engagement_overlay" - ] - fullScreenEngagementPanel = resourceMappings[ - ID, - "fullscreen_engagement_panel_holder" - ] - horizontalCardList = resourceMappings[ - LAYOUT, - "horizontal_card_list" - ] - imageOnlyTab = resourceMappings[ - LAYOUT, - "image_only_tab" - ] - inlineTimeBarColorizedBarPlayedColorDark = resourceMappings[ - 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" - ] - insetOverlayViewLayout = resourceMappings[ - ID, - "inset_overlay_view_layout" - ] - interstitialsContainer = resourceMappings[ - ID, - "interstitials_container" - ] - insetElementsWrapper = resourceMappings[ - LAYOUT, - "inset_elements_wrapper" - ] - menuItemView = resourceMappings[ - ID, - "menu_item_view" - ] - metaPanel = resourceMappings[ - ID, - "metapanel" - ] - miniplayerMaxSize = resourceMappings[ - DIMEN, - "miniplayer_max_size", - ] - modernMiniPlayerClose = resourceMappings[ - ID, - "modern_miniplayer_close" - ] - modernMiniPlayerExpand = resourceMappings[ - ID, - "modern_miniplayer_expand" - ] - modernMiniPlayerForwardButton = resourceMappings[ - ID, - "modern_miniplayer_forward_button" - ] - modernMiniPlayerOverlayActionButton = resourceMappings[ - ID, - "modern_miniplayer_overlay_action_button" - ] - modernMiniPlayerRewindButton = resourceMappings[ - ID, - "modern_miniplayer_rewind_button" - ] - musicAppDeeplinkButtonView = resourceMappings[ - ID, - "music_app_deeplink_button_view" - ] - notificationBigPictureIconWidth = resourceMappings[ - DIMEN, - "notification_big_picture_icon_width" - ] - offlineActionsVideoDeletedUndoSnackbarText = resourceMappings[ - STRING, - "offline_actions_video_deleted_undo_snackbar_text" - ] - playerCollapseButton = resourceMappings[ - ID, - "player_collapse_button" - ] - playerControlPreviousButtonTouchArea = resourceMappings[ - ID, - "player_control_previous_button_touch_area" - ] - playerControlNextButtonTouchArea = resourceMappings[ - ID, - "player_control_next_button_touch_area" - ] - playerVideoTitleView = resourceMappings[ - ID, - "player_video_title_view" - ] - posterArtWidthDefault = resourceMappings[ - DIMEN, - "poster_art_width_default" - ] - qualityAuto = resourceMappings[ - STRING, - "quality_auto" - ] - quickActionsElementContainer = resourceMappings[ - ID, - "quick_actions_element_container" - ] - reelDynRemix = resourceMappings[ - ID, - "reel_dyn_remix" - ] - reelDynShare = resourceMappings[ - ID, - "reel_dyn_share" - ] - reelFeedbackLike = resourceMappings[ - ID, - "reel_feedback_like" - ] - reelFeedbackPause = resourceMappings[ - ID, - "reel_feedback_pause" - ] - reelFeedbackPlay = resourceMappings[ - ID, - "reel_feedback_play" - ] - reelForcedMuteButton = resourceMappings[ - ID, - "reel_player_forced_mute_button" - ] - reelPlayerFooter = resourceMappings[ - LAYOUT, - "reel_player_dyn_footer_vert_stories3" - ] - reelPlayerRightPivotV2Size = resourceMappings[ - DIMEN, - "reel_player_right_pivot_v2_size" - ] - reelRightDislikeIcon = resourceMappings[ - DRAWABLE, - "reel_right_dislike_icon" - ] - reelRightLikeIcon = resourceMappings[ - DRAWABLE, - "reel_right_like_icon" - ] - reelTimeBarPlayedColor = resourceMappings[ - COLOR, - "reel_time_bar_played_color" - ] - reelVodTimeStampsContainer = resourceMappings[ - ID, - "reel_vod_timestamps_container" - ] - reelWatchPlayer = resourceMappings[ - ID, - "reel_watch_player" - ] - relatedChipCloudMargin = resourceMappings[ - LAYOUT, - "related_chip_cloud_reduced_margins" - ] - rightComment = resourceMappings[ - DRAWABLE, - "ic_right_comment_32c" - ] - scrimOverlay = resourceMappings[ - ID, - "scrim_overlay" - ] - scrubbing = resourceMappings[ - DIMEN, - "vertical_touch_offset_to_enter_fine_scrubbing" - ] - seekEasyHorizontalTouchOffsetToStartScrubbing = resourceMappings[ - DIMEN, - "seek_easy_horizontal_touch_offset_to_start_scrubbing" - ] - seekUndoEduOverlayStub = resourceMappings[ - ID, - "seek_undo_edu_overlay_stub" - ] - settingsFragment = resourceMappings[ - XML, - "settings_fragment" - ] - settingsFragmentCairo = resourceMappings[ - XML, - "settings_fragment_cairo" - ] - slidingDialogAnimation = resourceMappings[ - STYLE, - "SlidingDialogAnimation" - ] - subtitleMenuSettingsFooterInfo = resourceMappings[ - STRING, - "subtitle_menu_settings_footer_info" - ] - suggestedAction = resourceMappings[ - LAYOUT, - "suggested_action" - ] - tapBloomView = resourceMappings[ - ID, - "tap_bloom_view" - ] - titleAnchor = resourceMappings[ - ID, - "title_anchor" - ] - toolbarContainerId = resourceMappings[ - ID, - "toolbar_container" - ] - toolTipContentView = resourceMappings[ - LAYOUT, - "tooltip_content_view" - ] - totalTime = resourceMappings[ - STRING, - "total_time" - ] - touchArea = resourceMappings[ - ID, - "touch_area" - ] - videoQualityBottomSheet = resourceMappings[ - LAYOUT, - "video_quality_bottom_sheet_list_fragment_title" - ] - varispeedUnavailableTitle = resourceMappings[ - STRING, - "varispeed_unavailable_title" - ] - videoQualityUnavailableAnnouncement = resourceMappings[ - STRING, - "video_quality_unavailable_announcement" - ] - videoZoomSnapIndicator = resourceMappings[ - ID, - "video_zoom_snap_indicator" - ] - voiceSearch = resourceMappings[ - ID, - "voice_search" - ] - youTubeControlsOverlaySubtitleButton = resourceMappings[ - LAYOUT, - "youtube_controls_overlay_subtitle_button" - ] - youTubeLogo = resourceMappings[ - ID, - "youtube_logo" - ] - ytFillBell = resourceMappings[ - DRAWABLE, - "yt_fill_bell_black_24" - ] - ytOutlineMoonZ = resourceMappings[ - DRAWABLE, - "yt_outline_moon_z_vd_theme_24" - ] - ytOutlinePictureInPictureWhite = resourceMappings[ - DRAWABLE, - "yt_outline_picture_in_picture_white_24" - ] - ytOutlineVideoCamera = resourceMappings[ - DRAWABLE, - "yt_outline_video_camera_black_24" - ] - ytOutlineXWhite = resourceMappings[ - DRAWABLE, - "yt_outline_x_white_24" - ] - ytPremiumWordMarkHeader = resourceMappings[ - ATTR, - "ytPremiumWordmarkHeader" - ] - ytTextSecondary = resourceMappings[ - ATTR, - "ytTextSecondary", - ] - ytStaticBrandRed = resourceMappings[ - ATTR, - "ytStaticBrandRed", - ] - ytWordMarkHeader = resourceMappings[ - ATTR, - "ytWordmarkHeader" - ] - ytYoutubeMagenta = resourceMappings[ - COLOR, - "yt_youtube_magenta", - ] + accountSwitcherAccessibility = getResourceId(STRING, "account_switcher_accessibility_label") + actionBarRingo = getResourceId(LAYOUT, "action_bar_ringo") + actionBarRingoBackground = getResourceId(LAYOUT, "action_bar_ringo_background") + adAttribution = getResourceId(ID, "ad_attribution") + appearance = getResourceId(STRING, "app_theme_appearance_dark") + appRelatedEndScreenResults = getResourceId(LAYOUT, "app_related_endscreen_results") + autoNavPreviewStub = getResourceId(ID, "autonav_preview_stub") + autoNavScrollCancelPadding = getResourceId(DIMEN, "autonav_scroll_cancel_padding") + autoNavToggle = getResourceId(ID, "autonav_toggle") + backgroundCategory = getResourceId(STRING, "pref_background_and_offline_category") + badgeLabel = getResourceId(ID, "badge_label") + bar = getResourceId(LAYOUT, "bar") + barContainerHeight = getResourceId(DIMEN, "bar_container_height") + bottomBarContainer = getResourceId(ID, "bottom_bar_container") + bottomSheetFooterText = getResourceId(ID, "bottom_sheet_footer_text") + bottomSheetRecyclerView = getResourceId(LAYOUT, "bottom_sheet_recycler_view") + bottomUiContainerStub = getResourceId(ID, "bottom_ui_container_stub") + captionToggleContainer = getResourceId(ID, "caption_toggle_container") + castMediaRouteButton = getResourceId(LAYOUT, "castmediaroutebutton") + cfFullscreenButton = getResourceId(ID, "cf_fullscreen_button") + channelListSubMenu = getResourceId(LAYOUT, "channel_list_sub_menu") + compactLink = getResourceId(LAYOUT, "compact_link") + compactListItem = getResourceId(LAYOUT, "compact_list_item") + componentLongClickListener = getResourceId(ID, "component_long_click_listener") + contentPill = getResourceId(LAYOUT, "content_pill") + controlsLayoutStub = getResourceId(ID, "controls_layout_stub") + darkBackground = getResourceId(ID, "dark_background") + darkSplashAnimation = getResourceId(ID, "dark_splash_animation") + designBottomSheet = getResourceId(ID, "design_bottom_sheet") + donationCompanion = getResourceId(LAYOUT, "donation_companion") + drawerContentView = getResourceId(ID, "drawer_content_view") + drawerResults = getResourceId(ID, "drawer_results") + easySeekEduContainer = getResourceId(ID, "easy_seek_edu_container") + editSettingsAction = getResourceId(STRING, "edit_settings_action") + endScreenElementLayoutCircle = getResourceId(LAYOUT, "endscreen_element_layout_circle") + endScreenElementLayoutIcon = getResourceId(LAYOUT, "endscreen_element_layout_icon") + endScreenElementLayoutVideo = getResourceId(LAYOUT, "endscreen_element_layout_video") + emojiPickerIcon = getResourceId(ID, "emoji_picker_icon") + expandButtonDown = getResourceId(LAYOUT, "expand_button_down") + fab = getResourceId(ID, "fab") + fadeDurationFast = getResourceId(INTEGER, "fade_duration_fast") + filterBarHeight = getResourceId(DIMEN, "filter_bar_height") + floatyBarTopMargin = getResourceId(DIMEN, "floaty_bar_button_top_margin") + fullScreenButton = getResourceId(ID, "fullscreen_button") + fullScreenEngagementAdContainer = getResourceId(ID, "fullscreen_engagement_ad_container") + fullScreenEngagementOverlay = getResourceId(LAYOUT, "fullscreen_engagement_overlay") + fullScreenEngagementPanel = getResourceId(ID, "fullscreen_engagement_panel_holder") + horizontalCardList = getResourceId(LAYOUT, "horizontal_card_list") + imageOnlyTab = getResourceId(LAYOUT, "image_only_tab") + inlineTimeBarColorizedBarPlayedColorDark = + getResourceId(COLOR, "inline_time_bar_colorized_bar_played_color_dark") + inlineTimeBarLiveSeekAbleRange = getResourceId(COLOR, "inline_time_bar_live_seekable_range") + inlineTimeBarPlayedNotHighlightedColor = + getResourceId(COLOR, "inline_time_bar_played_not_highlighted_color") + insetOverlayViewLayout = getResourceId(ID, "inset_overlay_view_layout") + interstitialsContainer = getResourceId(ID, "interstitials_container") + insetElementsWrapper = getResourceId(LAYOUT, "inset_elements_wrapper") + menuItemView = getResourceId(ID, "menu_item_view") + metaPanel = getResourceId(ID, "metapanel") + miniplayerMaxSize = getResourceId(DIMEN, "miniplayer_max_size") + modernMiniPlayerClose = getResourceId(ID, "modern_miniplayer_close") + modernMiniPlayerExpand = getResourceId(ID, "modern_miniplayer_expand") + modernMiniPlayerForwardButton = getResourceId(ID, "modern_miniplayer_forward_button") + modernMiniPlayerOverlayActionButton = + getResourceId(ID, "modern_miniplayer_overlay_action_button") + modernMiniPlayerRewindButton = getResourceId(ID, "modern_miniplayer_rewind_button") + musicAppDeeplinkButtonView = getResourceId(ID, "music_app_deeplink_button_view") + notificationBigPictureIconWidth = + getResourceId(DIMEN, "notification_big_picture_icon_width") + offlineActionsVideoDeletedUndoSnackbarText = + getResourceId(STRING, "offline_actions_video_deleted_undo_snackbar_text") + playerCollapseButton = getResourceId(ID, "player_collapse_button") + playerControlPreviousButtonTouchArea = + getResourceId(ID, "player_control_previous_button_touch_area") + playerControlNextButtonTouchArea = + getResourceId(ID, "player_control_next_button_touch_area") + playerVideoTitleView = getResourceId(ID, "player_video_title_view") + posterArtWidthDefault = getResourceId(DIMEN, "poster_art_width_default") + qualityAuto = getResourceId(STRING, "quality_auto") + quickActionsElementContainer = getResourceId(ID, "quick_actions_element_container") + reelDynRemix = getResourceId(ID, "reel_dyn_remix") + reelDynShare = getResourceId(ID, "reel_dyn_share") + reelFeedbackLike = getResourceId(ID, "reel_feedback_like") + reelFeedbackPause = getResourceId(ID, "reel_feedback_pause") + reelFeedbackPlay = getResourceId(ID, "reel_feedback_play") + reelForcedMuteButton = getResourceId(ID, "reel_player_forced_mute_button") + reelPlayerFooter = getResourceId(LAYOUT, "reel_player_dyn_footer_vert_stories3") + reelPlayerRightPivotV2Size = getResourceId(DIMEN, "reel_player_right_pivot_v2_size") + reelRightDislikeIcon = getResourceId(DRAWABLE, "reel_right_dislike_icon") + reelRightLikeIcon = getResourceId(DRAWABLE, "reel_right_like_icon") + reelTimeBarPlayedColor = getResourceId(COLOR, "reel_time_bar_played_color") + reelVodTimeStampsContainer = getResourceId(ID, "reel_vod_timestamps_container") + reelWatchPlayer = getResourceId(ID, "reel_watch_player") + relatedChipCloudMargin = getResourceId(LAYOUT, "related_chip_cloud_reduced_margins") + rightComment = getResourceId(DRAWABLE, "ic_right_comment_32c") + scrimOverlay = getResourceId(ID, "scrim_overlay") + seekEasyHorizontalTouchOffsetToStartScrubbing = + getResourceId(DIMEN, "seek_easy_horizontal_touch_offset_to_start_scrubbing") + seekUndoEduOverlayStub = getResourceId(ID, "seek_undo_edu_overlay_stub") + settingsFragment = getResourceId(XML, "settings_fragment") + settingsFragmentCairo = getResourceId(XML, "settings_fragment_cairo") + slidingDialogAnimation = getResourceId(STYLE, "SlidingDialogAnimation") + subtitleMenuSettingsFooterInfo = getResourceId(STRING, "subtitle_menu_settings_footer_info") + suggestedAction = getResourceId(LAYOUT, "suggested_action") + tapBloomView = getResourceId(ID, "tap_bloom_view") + titleAnchor = getResourceId(ID, "title_anchor") + toolbarContainerId = getResourceId(ID, "toolbar_container") + toolTipContentView = getResourceId(LAYOUT, "tooltip_content_view") + totalTime = getResourceId(STRING, "total_time") + touchArea = getResourceId(ID, "touch_area") + videoQualityBottomSheet = + getResourceId(LAYOUT, "video_quality_bottom_sheet_list_fragment_title") + varispeedUnavailableTitle = getResourceId(STRING, "varispeed_unavailable_title") + verticalTouchOffsetToEnterFineScrubbing = + getResourceId(DIMEN, "vertical_touch_offset_to_enter_fine_scrubbing") + verticalTouchOffsetToStartFineScrubbing = + getResourceId(DIMEN, "vertical_touch_offset_to_start_fine_scrubbing") + videoQualityUnavailableAnnouncement = + getResourceId(STRING, "video_quality_unavailable_announcement") + videoZoomSnapIndicator = getResourceId(ID, "video_zoom_snap_indicator") + voiceSearch = getResourceId(ID, "voice_search") + youTubeControlsOverlaySubtitleButton = + getResourceId(LAYOUT, "youtube_controls_overlay_subtitle_button") + youTubeLogo = getResourceId(ID, "youtube_logo") + ytCallToAction = getResourceId(ATTR, "ytCallToAction") + ytFillBell = getResourceId(DRAWABLE, "yt_fill_bell_black_24") + ytOutlineLibrary = getResourceId(DRAWABLE, "yt_outline_library_black_24") + ytOutlineMoonZ = getResourceId(DRAWABLE, "yt_outline_moon_z_vd_theme_24") + ytOutlinePictureInPictureWhite = + getResourceId(DRAWABLE, "yt_outline_picture_in_picture_white_24") + ytOutlineVideoCamera = getResourceId(DRAWABLE, "yt_outline_video_camera_black_24") + ytOutlineXWhite = getResourceId(DRAWABLE, "yt_outline_x_white_24") + ytPremiumWordMarkHeader = getResourceId(ATTR, "ytPremiumWordmarkHeader") + ytTextSecondary = getResourceId(ATTR, "ytTextSecondary") + ytStaticBrandRed = getResourceId(ATTR, "ytStaticBrandRed") + ytWordMarkHeader = getResourceId(ATTR, "ytWordmarkHeader") + ytYoutubeMagenta = getResourceId(COLOR, "yt_youtube_magenta") } } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/Fingerprints.kt index 0a8ea07c6..c7d6c602e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/returnyoutubedislike/Fingerprints.kt @@ -7,6 +7,7 @@ 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 +import com.android.tools.smali.dexlib2.iface.reference.StringReference /** * This fingerprint is compatible with YouTube v18.30.xx+ @@ -54,7 +55,14 @@ internal val rollingNumberMeasureTextParentFingerprint = legacyFingerprint( internal val rollingNumberSetterFingerprint = legacyFingerprint( name = "rollingNumberSetterFingerprint", opcodes = listOf(Opcode.CHECK_CAST), - literals = listOf(45427773L), + customFingerprint = { method, _ -> + method.indexOfFirstInstruction { + opcode == Opcode.CONST_STRING && + getReference() + ?.string.toString() + .startsWith("RollingNumberType required properties missing! Need") + } >= 0 + } ) internal val shortsTextViewFingerprint = legacyFingerprint( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt index 7b84b5f93..43cd0c4f1 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/settings/ResourceUtils.kt @@ -25,6 +25,7 @@ internal object ResourceUtils { const val RVX_PREFERENCE_PATH = "res/xml/revanced_prefs.xml" const val YOUTUBE_SETTINGS_PATH = "res/xml/settings_fragment.xml" + var restoreOldSplashAnimationIncluded = false var youtubeMusicPackageName = YOUTUBE_MUSIC_PACKAGE_NAME var youtubePackageName = YOUTUBE_PACKAGE_NAME diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/Fingerprints.kt index 9c2e47803..c8648d9d9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/Fingerprints.kt @@ -3,7 +3,6 @@ package app.revanced.patches.youtube.video.information import app.revanced.patches.youtube.utils.PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.resourceid.notificationBigPictureIconWidth import app.revanced.patches.youtube.utils.resourceid.qualityAuto -import app.revanced.util.containsLiteralInstruction import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstruction @@ -133,29 +132,6 @@ fun indexOfPlayerResponseModelInterfaceInstruction(methodDef: Method) = getReference()?.definingClass == PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR } -/** - * This fingerprint is compatible with all versions of YouTube starting from v18.29.38 to supported versions. - * This method is invoked only in Shorts. - * Accurate video information is invoked even when the user moves Shorts upward or downward. - */ -internal val videoIdFingerprintShorts = legacyFingerprint( - name = "videoIdFingerprintShorts", - returnType = "V", - parameters = listOf(PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR), - opcodes = listOf( - Opcode.INVOKE_INTERFACE, - Opcode.MOVE_RESULT_OBJECT - ), - customFingerprint = custom@{ method, _ -> - if (method.containsLiteralInstruction(45365621L)) - return@custom true - - method.indexOfFirstInstruction { - getReference()?.name == "reelWatchEndpoint" - } >= 0 - } -) - internal val videoQualityListFingerprint = legacyFingerprint( name = "videoQualityListFingerprint", returnType = "V", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt index fa9d1075c..824364d2b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt @@ -5,7 +5,6 @@ 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.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.MutableClass @@ -19,12 +18,14 @@ import app.revanced.patches.youtube.utils.extension.Constants.SHARED_PATH import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch import app.revanced.patches.youtube.utils.videoEndFingerprint +import app.revanced.patches.youtube.utils.videoIdFingerprintShorts import app.revanced.patches.youtube.video.playerresponse.Hook import app.revanced.patches.youtube.video.playerresponse.addPlayerResponseMethodHook import app.revanced.patches.youtube.video.playerresponse.playerResponseMethodHookPatch import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId import app.revanced.patches.youtube.video.videoid.hookVideoId import app.revanced.patches.youtube.video.videoid.videoIdPatch +import app.revanced.util.addInstructionsAtControlFlowLabel import app.revanced.util.addStaticFieldToExtension import app.revanced.util.cloneMutable import app.revanced.util.findMethodOrThrow @@ -93,6 +94,8 @@ private var mdxConstructorInsertIndex = -1 private lateinit var videoTimeConstructorMethod: MutableMethod private var videoTimeConstructorInsertIndex = 2 +private lateinit var setPlaybackSpeedMethodReference: MethodReference + // Used by other patches. internal lateinit var speedSelectionInsertMethod: MutableMethod internal lateinit var videoEndMethod: MutableMethod @@ -395,12 +398,13 @@ val videoInformationPatch = bytecodePatch( Opcode.IGET_OBJECT ) val setPlaybackSpeedContainerClassFieldReference = - getInstruction(setPlaybackSpeedContainerClassFieldIndex).reference.toString() + getInstruction(setPlaybackSpeedContainerClassFieldIndex).reference val setPlaybackSpeedClassFieldReference = - getInstruction(speedSelectionValueInstructionIndex + 1).reference.toString() - val setPlaybackSpeedMethodReference = - getInstruction(speedSelectionValueInstructionIndex + 2).reference.toString() + getInstruction(speedSelectionValueInstructionIndex + 1).reference + + setPlaybackSpeedMethodReference = + getInstruction(speedSelectionValueInstructionIndex + 2).reference as MethodReference // add override playback speed method it.classDef.methods.add( @@ -449,41 +453,93 @@ val videoInformationPatch = bytecodePatch( } } - playbackSpeedClassFingerprint.matchOrThrow().let { result -> - result.method.apply { - val index = result.patternMatch!!.endIndex - val register = getInstruction(index).registerA - val playbackSpeedClass = this.returnType - - // set playback speed class - replaceInstruction( - index, - "sput-object v$register, $EXTENSION_CLASS_DESCRIPTOR->playbackSpeedClass:$playbackSpeedClass" - ) - addInstruction( - index + 1, - "return-object v$register" - ) + videoIdFingerprintShorts.matchOrThrow().let { + it.method.apply { + val shortsPlaybackSpeedClassField = it.classDef.fields.find { field -> + field.type == setPlaybackSpeedMethodReference.definingClass + } ?: throw PatchException("Failed to find hook field") val smaliInstructions = """ if-eqz v0, :ignore - invoke-virtual {v0, p0}, $playbackSpeedClass->overridePlaybackSpeed(F)V + invoke-virtual {v0, p0}, $definingClass->overridePlaybackSpeed(F)V :ignore return-void - """ + """ addStaticFieldToExtension( EXTENSION_CLASS_DESCRIPTOR, "overridePlaybackSpeed", - "playbackSpeedClass", - playbackSpeedClass, - smaliInstructions, - false + "playbackSpeedShortsClass", + definingClass, + smaliInstructions + ) + + // add override playback speed method + it.classDef.methods.add( + ImmutableMethod( + definingClass, + "overridePlaybackSpeed", + listOf(ImmutableMethodParameter("F", annotations, null)), + "V", + AccessFlags.PUBLIC or AccessFlags.PUBLIC, + annotations, + null, + ImmutableMethodImplementation( + 3, """ + # Check if the playback speed is not auto (-2.0f) + const/4 v0, 0x0 + cmpg-float v0, v2, v0 + if-lez v0, :ignore + + # Get the container class field. + iget-object v0, v1, $shortsPlaybackSpeedClassField + + # For some reason, in YouTube 19.44.39 this value is sometimes null. + if-eqz v0, :ignore + + # Invoke setPlaybackSpeed on that class. + invoke-virtual {v0, v2}, $setPlaybackSpeedMethodReference + + :ignore + return-void + """.toInstructions(), null, null + ) + ).toMutable() ) } } + playbackSpeedClassFingerprint.methodOrThrow().apply { + val index = indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT) + val register = getInstruction(index).registerA + val playbackSpeedClass = this.returnType + + // set playback speed class + addInstructionsAtControlFlowLabel( + index, + "sput-object v$register, $EXTENSION_CLASS_DESCRIPTOR->playbackSpeedClass:$playbackSpeedClass" + ) + + val smaliInstructions = + """ + if-eqz v0, :ignore + invoke-virtual {v0, p0}, $playbackSpeedClass->overridePlaybackSpeed(F)V + return-void + :ignore + nop + """ + + addStaticFieldToExtension( + EXTENSION_CLASS_DESCRIPTOR, + "overridePlaybackSpeed", + "playbackSpeedClass", + playbackSpeedClass, + smaliInstructions, + false + ) + } + /** * Hook current video quality */ diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/Fingerprints.kt index 28ede6d2b..53abb9489 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/Fingerprints.kt @@ -2,9 +2,12 @@ package app.revanced.patches.youtube.video.playback import app.revanced.util.containsLiteralInstruction 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.FieldReference internal val av1CodecFingerprint = legacyFingerprint( name = "av1CodecFingerprint", @@ -17,29 +20,6 @@ internal val av1CodecFingerprint = legacyFingerprint( } ) -internal val byteBufferArrayFingerprint = legacyFingerprint( - name = "byteBufferArrayFingerprint", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - returnType = "I", - parameters = emptyList(), - opcodes = listOf( - Opcode.SHL_INT_LIT8, - Opcode.SHL_INT_LIT8, - Opcode.OR_INT_2ADDR, - Opcode.SHL_INT_LIT8, - Opcode.OR_INT_2ADDR, - Opcode.OR_INT_2ADDR, - Opcode.RETURN - ) -) - -internal val byteBufferArrayParentFingerprint = legacyFingerprint( - name = "byteBufferArrayParentFingerprint", - accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, - returnType = "C", - parameters = listOf("Ljava/nio/charset/Charset;", "[C") -) - internal val deviceDimensionsModelToStringFingerprint = legacyFingerprint( name = "deviceDimensionsModelToStringFingerprint", returnType = "L", @@ -61,27 +41,37 @@ internal val playbackSpeedChangedFromRecyclerViewFingerprint = legacyFingerprint accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("L"), opcodes = listOf( - Opcode.IGET_OBJECT, - Opcode.INVOKE_INTERFACE, - Opcode.MOVE_RESULT_OBJECT, - Opcode.IF_EQZ, - Opcode.IGET_OBJECT, Opcode.INVOKE_INTERFACE, Opcode.MOVE_RESULT_OBJECT, Opcode.IGET, Opcode.INVOKE_VIRTUAL + ), + customFingerprint = { method, _ -> + method.indexOfFirstInstruction { + opcode == Opcode.IGET && + getReference()?.type == "F" + } >= 0 + } +) + +internal val loadVideoParamsFingerprint = legacyFingerprint( + name = "loadVideoParamsFingerprint", + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + parameters = listOf("L"), + opcodes = listOf( + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT, + Opcode.IPUT, + Opcode.INVOKE_INTERFACE, ) ) -internal val playbackSpeedInitializeFingerprint = legacyFingerprint( - name = "playbackSpeedInitializeFingerprint", - returnType = "F", - accessFlags = AccessFlags.PRIVATE or AccessFlags.STATIC, - parameters = listOf("L"), - opcodes = listOf( - Opcode.IGET, - Opcode.RETURN - ) +internal val loadVideoParamsParentFingerprint = legacyFingerprint( + name = "loadVideoParamsParentFingerprint", + returnType = "Z", + parameters = listOf("J"), + strings = listOf("LoadVideoParams.playerListener = null") ) internal val qualityChangedFromRecyclerViewFingerprint = legacyFingerprint( diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt index 895d7b505..8052b44fd 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playback/VideoPlaybackPatch.kt @@ -24,8 +24,8 @@ import app.revanced.patches.youtube.utils.recyclerview.recyclerViewTreeObserverP 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.patches.youtube.utils.videoEndFingerprint import app.revanced.patches.youtube.video.information.hookBackgroundPlayVideoInformation +import app.revanced.patches.youtube.video.information.hookShortsVideoInformation import app.revanced.patches.youtube.video.information.hookVideoInformation import app.revanced.patches.youtube.video.information.onCreateHook import app.revanced.patches.youtube.video.information.speedSelectionInsertMethod @@ -44,6 +44,7 @@ import app.revanced.util.indexOfFirstStringInstructionOrThrow import app.revanced.util.updatePatchStatus import com.android.tools.smali.dexlib2.Opcode 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 @@ -158,21 +159,32 @@ val videoPlaybackPatch = bytecodePatch( } } - playbackSpeedInitializeFingerprint.matchOrThrow(videoEndFingerprint).let { + loadVideoParamsFingerprint.matchOrThrow(loadVideoParamsParentFingerprint).let { it.method.apply { - val insertIndex = it.patternMatch!!.endIndex - val insertRegister = getInstruction(insertIndex).registerA + val targetIndex = it.patternMatch!!.endIndex + val targetReference = + getInstruction(targetIndex).reference as MethodReference - addInstructions( - insertIndex, """ - invoke-static {v$insertRegister}, $EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->getPlaybackSpeedInShorts(F)F - move-result v$insertRegister - """ - ) + findMethodOrThrow(definingClass) { + name == targetReference.name + }.apply { + val insertIndex = implementation!!.instructions.lastIndex + val insertRegister = + getInstruction(insertIndex).registerA + + addInstructions( + insertIndex, """ + invoke-static {v$insertRegister}, $EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->getPlaybackSpeed(F)F + move-result v$insertRegister + """ + ) + } } } hookBackgroundPlayVideoInformation("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->newVideoStarted(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZ)V") + hookShortsVideoInformation("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->newShortsVideoStarted(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZ)V") + hookVideoInformation("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->newVideoStarted(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JZ)V") hookPlayerResponseVideoId("$EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->fetchMusicRequest(Ljava/lang/String;Z)V") updatePatchStatus(PATCH_STATUS_CLASS_DESCRIPTOR, "RememberPlaybackSpeed") @@ -294,25 +306,6 @@ val videoPlaybackPatch = bytecodePatch( settingArray += "SETTINGS: REPLACE_AV1_CODEC" } - // reject av1 codec response - - byteBufferArrayFingerprint.matchOrThrow(byteBufferArrayParentFingerprint).let { - it.method.apply { - val insertIndex = it.patternMatch!!.endIndex - val insertRegister = - getInstruction(insertIndex).registerA - - addInstructions( - insertIndex, """ - invoke-static {v$insertRegister}, $EXTENSION_AV1_CODEC_CLASS_DESCRIPTOR->rejectResponse(I)I - move-result v$insertRegister - """ - ) - } - } - - // endregion - // region patch for disable VP9 codec vp9CapabilityFingerprint.methodOrThrow().apply { 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 1b4e38b32..39b967dee 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 @@ -26,7 +26,8 @@ val playbackStartDescriptorPatch = bytecodePatch( && reference.returnType == "Ljava/lang/String;" } - playbackStartVideoIdReference = getInstruction(stringMethodIndex).reference + playbackStartVideoIdReference = + getInstruction(stringMethodIndex).reference } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch.kt index 9e8d23aeb..ba1505fe9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch.kt @@ -39,7 +39,8 @@ val playerResponseMethodHookPatch = bytecodePatch( playerResponseMethod.apply { val setIndex = parameterTypes.indexOfFirst { it == "Ljava/util/Set;" } val parameterSize = parameterTypes.size - val relativeIndex = parameterTypes.subList(setIndex, parameterSize - 1).indexOfFirst { it == "Z" } + val relativeIndex = + parameterTypes.subList(setIndex, parameterSize - 1).indexOfFirst { it == "Z" } // YouTube 18.29 ~ 19.22 : p11 // YouTube 19.23 ~ 20.09 : p12 diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/videoid/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/videoid/Fingerprints.kt index 3567bf0fd..1a8979a08 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/videoid/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/videoid/Fingerprints.kt @@ -29,7 +29,7 @@ internal val videoIdFingerprint = legacyFingerprint( ?: return@custom false val instructions = implementation.instructions val instructionCount = instructions.count() - if (instructionCount < 30) { + if (instructionCount < 25) { return@custom false } diff --git a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index 18edf0a80..1e5910f03 100644 --- a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -17,9 +17,8 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable -import app.revanced.patches.shared.mapping.get +import app.revanced.patches.shared.mapping.getResourceId import app.revanced.patches.shared.mapping.resourceMappingPatch -import app.revanced.patches.shared.mapping.resourceMappings import app.revanced.util.Utils.printWarn import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -134,7 +133,7 @@ internal fun MutableMethod.addInstructionsAtControlFlowLabel( * @see [indexOfFirstResourceIdOrThrow], [indexOfFirstLiteralInstructionReversed] */ fun Method.indexOfFirstResourceId(resourceName: String): Int { - val resourceId = resourceMappings["id", resourceName] + val resourceId = getResourceId("id", resourceName) if (resourceId == -1L) { printWarn("Could not find resource type: id name: $name") return -1 diff --git a/patches/src/main/kotlin/app/revanced/util/FilesCompat.kt b/patches/src/main/kotlin/app/revanced/util/FilesCompat.kt index 6bd4a93ed..1e54ba551 100644 --- a/patches/src/main/kotlin/app/revanced/util/FilesCompat.kt +++ b/patches/src/main/kotlin/app/revanced/util/FilesCompat.kt @@ -16,7 +16,7 @@ internal object FilesCompat { // Check for the existence of java.nio.file.Files class Class.forName("java.nio.file.Files") false - } catch (_ : ClassNotFoundException) { + } catch (_: ClassNotFoundException) { // Under Android 8.0 true } diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-hdpi/action_bar_logo.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-hdpi/action_bar_logo.png index 5c4fb04e2..0b45aa087 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-hdpi/action_bar_logo.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-hdpi/action_bar_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-hdpi/logo_music.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-hdpi/logo_music.png index c7985512b..962f20dd6 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-hdpi/logo_music.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-hdpi/logo_music.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-hdpi/ytm_logo.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-hdpi/ytm_logo.png index c7985512b..962f20dd6 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-hdpi/ytm_logo.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-hdpi/ytm_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-mdpi/action_bar_logo.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-mdpi/action_bar_logo.png index f0667b5e0..4c92895d0 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-mdpi/action_bar_logo.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-mdpi/action_bar_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-mdpi/logo_music.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-mdpi/logo_music.png index 92f52a841..a45e2fc3c 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-mdpi/logo_music.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-mdpi/logo_music.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-mdpi/ytm_logo.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-mdpi/ytm_logo.png index 92f52a841..a45e2fc3c 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-mdpi/ytm_logo.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-mdpi/ytm_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xhdpi/action_bar_logo.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xhdpi/action_bar_logo.png index 77c065696..21ce0f9f9 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xhdpi/action_bar_logo.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xhdpi/action_bar_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xhdpi/logo_music.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xhdpi/logo_music.png index f7226d9a2..c68ed80b9 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xhdpi/logo_music.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xhdpi/logo_music.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xhdpi/ytm_logo.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xhdpi/ytm_logo.png index f7226d9a2..c68ed80b9 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xhdpi/ytm_logo.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xhdpi/ytm_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxhdpi/action_bar_logo.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxhdpi/action_bar_logo.png index 6b6958a67..b5e91333e 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxhdpi/action_bar_logo.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxhdpi/action_bar_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxhdpi/logo_music.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxhdpi/logo_music.png index db804b2cf..9e4db4b51 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxhdpi/logo_music.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxhdpi/logo_music.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxhdpi/ytm_logo.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxhdpi/ytm_logo.png index db804b2cf..9e4db4b51 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxhdpi/ytm_logo.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxhdpi/ytm_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxxhdpi/action_bar_logo.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxxhdpi/action_bar_logo.png index 91f4d3895..51eae7444 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxxhdpi/action_bar_logo.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxxhdpi/action_bar_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxxhdpi/logo_music.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxxhdpi/logo_music.png index 73d72b9d8..0f86b5b57 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxxhdpi/logo_music.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxxhdpi/logo_music.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxxhdpi/ytm_logo.png b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxxhdpi/ytm_logo.png index 73d72b9d8..0f86b5b57 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxxhdpi/ytm_logo.png and b/patches/src/main/resources/music/branding/afn_blue/header/drawable-xxxhdpi/ytm_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-hdpi/adaptiveproduct_youtube_music_foreground_color_108.png b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-hdpi/adaptiveproduct_youtube_music_foreground_color_108.png index 9a83fdf59..96d698a1b 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-hdpi/adaptiveproduct_youtube_music_foreground_color_108.png and b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-hdpi/adaptiveproduct_youtube_music_foreground_color_108.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-hdpi/ic_launcher_release.png b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-hdpi/ic_launcher_release.png index 8f7ff25a1..facd33f08 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-hdpi/ic_launcher_release.png and b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-hdpi/ic_launcher_release.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-mdpi/adaptiveproduct_youtube_music_foreground_color_108.png b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-mdpi/adaptiveproduct_youtube_music_foreground_color_108.png index 16ae17465..d2ec417b8 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-mdpi/adaptiveproduct_youtube_music_foreground_color_108.png and b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-mdpi/adaptiveproduct_youtube_music_foreground_color_108.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-mdpi/ic_launcher_release.png b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-mdpi/ic_launcher_release.png index 9fbf6ca92..893556968 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-mdpi/ic_launcher_release.png and b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-mdpi/ic_launcher_release.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xhdpi/adaptiveproduct_youtube_music_foreground_color_108.png b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xhdpi/adaptiveproduct_youtube_music_foreground_color_108.png index f502f1bfc..d4e2ce6c2 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xhdpi/adaptiveproduct_youtube_music_foreground_color_108.png and b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xhdpi/adaptiveproduct_youtube_music_foreground_color_108.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xhdpi/ic_launcher_release.png b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xhdpi/ic_launcher_release.png index b9372f249..c2423831c 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xhdpi/ic_launcher_release.png and b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xhdpi/ic_launcher_release.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png index c4c79c94f..cc7399907 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png and b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxhdpi/ic_launcher_release.png b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxhdpi/ic_launcher_release.png index 1f0852d78..9c6fb1bb1 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxhdpi/ic_launcher_release.png and b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxhdpi/ic_launcher_release.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png index 593c2d12c..fc550d6a7 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png and b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxxhdpi/ic_launcher_release.png b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxxhdpi/ic_launcher_release.png index c72ec992e..1004a01cb 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxxhdpi/ic_launcher_release.png and b/patches/src/main/resources/music/branding/afn_blue/launcher/mipmap-xxxhdpi/ic_launcher_release.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-hdpi/action_bar_logo_release.png b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-hdpi/action_bar_logo_release.png index f4a88d418..fcd05103f 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-hdpi/action_bar_logo_release.png and b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-hdpi/action_bar_logo_release.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-hdpi/record.png b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-hdpi/record.png index aecd283f8..738045256 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-hdpi/record.png and b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-hdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-large-hdpi/record.png b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-large-hdpi/record.png index 7532ae435..8eb77b3ca 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-large-hdpi/record.png and b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-large-hdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-large-mdpi/record.png b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-large-mdpi/record.png index 1e783ad8a..69d3e81f8 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-large-mdpi/record.png and b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-large-mdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-large-xhdpi/record.png b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-large-xhdpi/record.png index 5442cc495..9d967925f 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-large-xhdpi/record.png and b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-large-xhdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-mdpi/record.png b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-mdpi/record.png index f6880b431..332198ecf 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-mdpi/record.png and b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-mdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xhdpi/record.png b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xhdpi/record.png index 9c3360029..73a437a6a 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xhdpi/record.png and b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xhdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xlarge-hdpi/record.png b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xlarge-hdpi/record.png index 32e299191..9faaceee9 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xlarge-hdpi/record.png and b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xlarge-hdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xlarge-mdpi/record.png b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xlarge-mdpi/record.png index 9c3360029..73a437a6a 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xlarge-mdpi/record.png and b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xlarge-mdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xxhdpi/record.png b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xxhdpi/record.png index 32e299191..9faaceee9 100644 Binary files a/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xxhdpi/record.png and b/patches/src/main/resources/music/branding/afn_blue/splash/drawable-xxhdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-hdpi/action_bar_logo.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-hdpi/action_bar_logo.png index a0e16ab45..0d9dce8f7 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-hdpi/action_bar_logo.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-hdpi/action_bar_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-hdpi/logo_music.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-hdpi/logo_music.png index 7907c64c6..eaa780d34 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-hdpi/logo_music.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-hdpi/logo_music.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-hdpi/ytm_logo.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-hdpi/ytm_logo.png index 7907c64c6..eaa780d34 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-hdpi/ytm_logo.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-hdpi/ytm_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-mdpi/action_bar_logo.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-mdpi/action_bar_logo.png index ee56c1c55..7a130710d 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-mdpi/action_bar_logo.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-mdpi/action_bar_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-mdpi/logo_music.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-mdpi/logo_music.png index 97d837917..abce8fa06 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-mdpi/logo_music.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-mdpi/logo_music.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-mdpi/ytm_logo.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-mdpi/ytm_logo.png index 97d837917..abce8fa06 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-mdpi/ytm_logo.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-mdpi/ytm_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-xhdpi/action_bar_logo.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-xhdpi/action_bar_logo.png index 01b53c316..996ebdd76 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-xhdpi/action_bar_logo.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-xhdpi/action_bar_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-xhdpi/logo_music.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-xhdpi/logo_music.png index 82fc6916a..d460220ef 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-xhdpi/logo_music.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-xhdpi/logo_music.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-xhdpi/ytm_logo.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-xhdpi/ytm_logo.png index 82fc6916a..d460220ef 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-xhdpi/ytm_logo.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-xhdpi/ytm_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-xxhdpi/action_bar_logo.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-xxhdpi/action_bar_logo.png index 4571ff72e..440a587bc 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-xxhdpi/action_bar_logo.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-xxhdpi/action_bar_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-xxhdpi/logo_music.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-xxhdpi/logo_music.png index be1c30561..d4ca8cfc5 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-xxhdpi/logo_music.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-xxhdpi/logo_music.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-xxhdpi/ytm_logo.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-xxhdpi/ytm_logo.png index be1c30561..d4ca8cfc5 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-xxhdpi/ytm_logo.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-xxhdpi/ytm_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-xxxhdpi/action_bar_logo.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-xxxhdpi/action_bar_logo.png index 304d7a330..126418ebb 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-xxxhdpi/action_bar_logo.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-xxxhdpi/action_bar_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-xxxhdpi/logo_music.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-xxxhdpi/logo_music.png index fac6da3bc..09de95c09 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-xxxhdpi/logo_music.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-xxxhdpi/logo_music.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/header/drawable-xxxhdpi/ytm_logo.png b/patches/src/main/resources/music/branding/afn_red/header/drawable-xxxhdpi/ytm_logo.png index fac6da3bc..09de95c09 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/header/drawable-xxxhdpi/ytm_logo.png and b/patches/src/main/resources/music/branding/afn_red/header/drawable-xxxhdpi/ytm_logo.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-hdpi/adaptiveproduct_youtube_music_foreground_color_108.png b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-hdpi/adaptiveproduct_youtube_music_foreground_color_108.png index ec15c01bc..165a2a5ad 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-hdpi/adaptiveproduct_youtube_music_foreground_color_108.png and b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-hdpi/adaptiveproduct_youtube_music_foreground_color_108.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-hdpi/ic_launcher_release.png b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-hdpi/ic_launcher_release.png index 6f9765153..b95c08fa2 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-hdpi/ic_launcher_release.png and b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-hdpi/ic_launcher_release.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-mdpi/adaptiveproduct_youtube_music_foreground_color_108.png b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-mdpi/adaptiveproduct_youtube_music_foreground_color_108.png index 28aaf6b15..e669d9831 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-mdpi/adaptiveproduct_youtube_music_foreground_color_108.png and b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-mdpi/adaptiveproduct_youtube_music_foreground_color_108.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-mdpi/ic_launcher_release.png b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-mdpi/ic_launcher_release.png index ebf57c2c0..056110fb3 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-mdpi/ic_launcher_release.png and b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-mdpi/ic_launcher_release.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xhdpi/adaptiveproduct_youtube_music_foreground_color_108.png b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xhdpi/adaptiveproduct_youtube_music_foreground_color_108.png index 78b0f8339..0a93afaae 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xhdpi/adaptiveproduct_youtube_music_foreground_color_108.png and b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xhdpi/adaptiveproduct_youtube_music_foreground_color_108.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xhdpi/ic_launcher_release.png b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xhdpi/ic_launcher_release.png index 2d6360f41..561413ec2 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xhdpi/ic_launcher_release.png and b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xhdpi/ic_launcher_release.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png index 84d1d81cb..88ad126c4 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png and b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxhdpi/ic_launcher_release.png b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxhdpi/ic_launcher_release.png index 365e18cdb..1071dddeb 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxhdpi/ic_launcher_release.png and b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxhdpi/ic_launcher_release.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png index 09ae721f0..8777ed175 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png and b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxxhdpi/adaptiveproduct_youtube_music_foreground_color_108.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxxhdpi/ic_launcher_release.png b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxxhdpi/ic_launcher_release.png index 0894c72f3..9450ec944 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxxhdpi/ic_launcher_release.png and b/patches/src/main/resources/music/branding/afn_red/launcher/mipmap-xxxhdpi/ic_launcher_release.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/splash/drawable-hdpi/action_bar_logo_release.png b/patches/src/main/resources/music/branding/afn_red/splash/drawable-hdpi/action_bar_logo_release.png index 7f79f7d43..6e865e32e 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/splash/drawable-hdpi/action_bar_logo_release.png and b/patches/src/main/resources/music/branding/afn_red/splash/drawable-hdpi/action_bar_logo_release.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/splash/drawable-hdpi/record.png b/patches/src/main/resources/music/branding/afn_red/splash/drawable-hdpi/record.png index b9de7c89c..2faa8665b 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/splash/drawable-hdpi/record.png and b/patches/src/main/resources/music/branding/afn_red/splash/drawable-hdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/splash/drawable-large-hdpi/record.png b/patches/src/main/resources/music/branding/afn_red/splash/drawable-large-hdpi/record.png index 5859963e7..7cc75e66c 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/splash/drawable-large-hdpi/record.png and b/patches/src/main/resources/music/branding/afn_red/splash/drawable-large-hdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/splash/drawable-large-mdpi/record.png b/patches/src/main/resources/music/branding/afn_red/splash/drawable-large-mdpi/record.png index 61d466154..190464c85 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/splash/drawable-large-mdpi/record.png and b/patches/src/main/resources/music/branding/afn_red/splash/drawable-large-mdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/splash/drawable-large-xhdpi/record.png b/patches/src/main/resources/music/branding/afn_red/splash/drawable-large-xhdpi/record.png index 5b34f7983..b9bba9b87 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/splash/drawable-large-xhdpi/record.png and b/patches/src/main/resources/music/branding/afn_red/splash/drawable-large-xhdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/splash/drawable-mdpi/record.png b/patches/src/main/resources/music/branding/afn_red/splash/drawable-mdpi/record.png index 03547727e..3622cd9c3 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/splash/drawable-mdpi/record.png and b/patches/src/main/resources/music/branding/afn_red/splash/drawable-mdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/splash/drawable-xhdpi/record.png b/patches/src/main/resources/music/branding/afn_red/splash/drawable-xhdpi/record.png index 176c876cd..ad72d5314 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/splash/drawable-xhdpi/record.png and b/patches/src/main/resources/music/branding/afn_red/splash/drawable-xhdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/splash/drawable-xlarge-hdpi/record.png b/patches/src/main/resources/music/branding/afn_red/splash/drawable-xlarge-hdpi/record.png index dbb56cc2d..87afd2196 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/splash/drawable-xlarge-hdpi/record.png and b/patches/src/main/resources/music/branding/afn_red/splash/drawable-xlarge-hdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/splash/drawable-xlarge-mdpi/record.png b/patches/src/main/resources/music/branding/afn_red/splash/drawable-xlarge-mdpi/record.png index 176c876cd..ad72d5314 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/splash/drawable-xlarge-mdpi/record.png and b/patches/src/main/resources/music/branding/afn_red/splash/drawable-xlarge-mdpi/record.png differ diff --git a/patches/src/main/resources/music/branding/afn_red/splash/drawable-xxhdpi/record.png b/patches/src/main/resources/music/branding/afn_red/splash/drawable-xxhdpi/record.png index dbb56cc2d..a04c1560c 100644 Binary files a/patches/src/main/resources/music/branding/afn_red/splash/drawable-xxhdpi/record.png and b/patches/src/main/resources/music/branding/afn_red/splash/drawable-xxhdpi/record.png differ 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 d019ea5b1..448b5f306 100644 --- a/patches/src/main/resources/music/settings/host/values/arrays.xml +++ b/patches/src/main/resources/music/settings/host/values/arrays.xml @@ -66,10 +66,8 @@ HANDLE_USERNAME - @string/revanced_spoof_app_version_target_entry_6_42_55 - 6.42.55 @string/revanced_spoof_client_type_entry_android_music_4_27 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 e7f6491dd..a8639ed84 100644 --- a/patches/src/main/resources/music/settings/host/values/strings.xml +++ b/patches/src/main/resources/music/settings/host/values/strings.xml @@ -187,6 +187,8 @@ Limitations: Hides the playlist card shelf in the feed. Hide Samples shelf Hides the Samples shelf in the feed. + Hide search button + Hides the search button in the toolbar. Hide sound search button Hides the sound search button in the search bar. Hide Tap to update button @@ -207,6 +209,8 @@ This does not bypass the age restriction. It just accepts it automatically."Select the spoof app version target. 6.42.55 - Disable real-time lyrics 7.16.53 - Restore old action bar + 7.17.52 - Comments section does not appear in home feed + Invalid spoof app version: %s. 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 44d4e10f7..a0dfda287 100644 --- a/patches/src/main/resources/music/translations/el-rGR/strings.xml +++ b/patches/src/main/resources/music/translations/el-rGR/strings.xml @@ -172,6 +172,8 @@ Απόκρυψη της ενότητας καρτών λίστας αναπαραγωγής στη ροή. Απόκρυψη ενότητας «Δείγματα» Απόκρυψη της ενότητας «Δείγματα» στη ροή. + Απόκρυψη κουμπιού αναζήτησης + Απόκρυψη του κουμπιού αναζήτησης στη γραμμή εργαλείων. Απόκρυψη κουμπιού ηχητικής αναζήτησης Απόκρυψη του κουμπιού ηχητικής αναζήτησης στην γραμμή αναζήτησης. Απόκρυψη κουμπιού «Πατήστε για ενημέρωση» @@ -192,6 +194,8 @@ Επιλέξτε την έκδοση εφαρμογής που θα χρησιμοποιηθεί. 6.42.55 - Απενεργοποίηση στίχων σε πραγματικό χρόνο 7.16.53 - Επαναφορά παλιάς γραμμής ενεργειών + 7.17.52 - Η ενότητα σχολίων δεν εμφανίζεται στην αρχική ροή + Μη έγκυρη έκδοση για παραποίηση: %s. Γραμμή πλοήγησης Προσαρμοσμένο χρώμα γραμμής πλοήγησης @@ -213,6 +217,18 @@ Απόκρυψη της γραμμής πλοήγησης. Απόκρυψη ονομασιών γραμμής πλοήγησης Απόκρυψη ονομασιών των κουμπιών στη γραμμή πλοήγησης. + Αντικατάσταση κουμπιού «Δείγματα» + Αντικατάσταση του κουμπιού «Δείγματα» με το κουμπί «Αναζήτηση». + Αντικατάσταση κουμπιού «Αναβάθμιση» + Αντικατάσταση του κουμπιού «Αναβάθμιση» με το κουμπί «Ρυθμίσεις». + Σχετικά με την αντικατάσταση κουμπιού + "Αυτή η λειτουργία είναι πειραματική. +Υπάρχουν δομικοί περιορισμοί της τροποποίησης, καθώς ενέργειες όπως η Αναζήτηση και οι Ρυθμίσεις στο YouTube Music δεν είναι δημόσιες. + +Γνωστά προβλήματα: +• Όταν κλείνει μια αντικατασταθείσα ενέργεια, όπως η Αναζήτηση και οι Ρυθμίσεις, ανοίγει η αρχική σελίδα. + +Πατήστε για να ανοίξετε την ρύθμιση «Αλλαγή αρχικής σελίδας»." Οθόνη αναπαραγωγής Προσθήκη κουμπιού επόμενου κομματιού στην ελαχιστοποιημένη οθόνη αναπαραγωγής @@ -452,6 +468,10 @@ iOS Music 6.21 iOS Music 7.04 Παραποίηση παραμέτρου προγράμματος αναπαραγωγής + "Παραποίηση παραμέτρου του προγράμματος αναπαραγωγής για την αποφυγή προβλημάτων αναπαραγωγής. + +Περιορισμός: +• Μερικές φορές οι υπότιτλοι βρίσκονται στην κορυφή της οθόνης αναπαραγωγής αντί του κάτω μέρους." Τύπος ιστορικού παρακολούθησης "• Αρχικός: Ακολουθεί τις ρυθμίσεις ιστορικού παρακολούθησης του λογαριασμού Google σας, αλλά το ιστορικό παρακολούθησης μπορεί να μη λειτουργεί λόγω χρήσης VPN ή εναλλακτικού DNS. • Αντικατάσταση του domain: Ακολουθεί τις ρυθμίσεις ιστορικού παρακολούθησης του λογαριασμού Google σας. 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 6e6a966e7..6a26c225a 100644 --- a/patches/src/main/resources/music/translations/es-rES/strings.xml +++ b/patches/src/main/resources/music/translations/es-rES/strings.xml @@ -25,7 +25,7 @@ Ocultar botones Me gusta y No me gusta Oculta los botones \"Me gusta\" y \"no me gusta\". No funciona en el diseño del reproductor antiguo. Ocultar botón de comentarios - Oculta botón de comentarios. + Oculta el botón de comentarios. Ocultar botón Guardar Oculta botón Guardar. Ocultar botón Descargar @@ -170,6 +170,8 @@ Problemas conocidos: Oculta la tarjeta de lista de reproducción del feed. Ocultar estante de Samples Oculta estante de Samples en el feed. + Ocultar botón de búsqueda + Oculta el botón de búsqueda en la barra de herramientas. Ocultar botón de búsqueda de sonido Oculta el botón de búsqueda de sonido en la barra de búsqueda. Ocultar el botón Toque para actualizar @@ -211,6 +213,8 @@ Esto no evita la restricción de edad. Solo la acepta automáticamente."Oculta barra de navegación. Ocultar etiquetas en barra de navegación Oculta las etiquetas en la barra de navegación. + "Esta característica es experimental. +Hay limitaciones estructurales en el parcheo, actividades tales como Buscar y Ajustes en YouTube Music no son públicas." Reproductor Añade un botón de siguiente al minireproductor 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 60fe87248..22191d3b8 100644 --- a/patches/src/main/resources/music/translations/hu-rHU/strings.xml +++ b/patches/src/main/resources/music/translations/hu-rHU/strings.xml @@ -20,6 +20,7 @@ Elrejti a Szolgáltatási feltételeket a fiókmenüben. Műveletsáv + Lejátszási gombok sorának mozgatása a lejátszás gomb alá. A tetszik és nem tetszik gombok elrejtése Elrejti a tetszik és nem tetszik gombokat. Nem működik a régi lejátszóval. Megjegyzés gomb elrejtése @@ -32,6 +33,7 @@ Elrejti a rádió gombot. Megosztás gomb elrejtése Elrejti a Megosztás gombot. + "Zene/Videó gomb elrejtése (Ez a gomb csak néhány felhasználónak elérhető)" 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,6 +51,11 @@ 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. + +Limitáció; +•Néha lehet hogy csak fekete képet látsz a kezdő képernyő helyett." + Teljes képernyős hírdetések bezárva. Általános hirdetések elrejtése Elrejti az általános hirdetéseket. Zenei hirdetések elrejtése @@ -57,6 +64,7 @@ Töltsd le a(z) %2$s weboldalról." Elrejti a promóció címkét. Felugró prémium hírdetések elrejtése Elrejti a prémium promóciós felugró ablakokat. + A teljes képernyős hírdetések be lettek zárva. Prémium megújítás szalaghírdetés elrejtése Elrejti a prémium megújítás szalaghírdetést. Promóciós figyelmeztető banner elrejtése @@ -88,6 +96,7 @@ Korlátozások: Rész menü elrejtése Podcast menü elrejtése Súgó & visszajelzés menü elrejtése + A nem érdekel gomb elrejtése Fül elrejtése a Gyors elérés menüben Következő lejátszása menü elrejtése Minőség menü elrejtése @@ -201,8 +210,21 @@ Ez nem kerüli meg a korhatárkorlátozást. Csak automatikusan elfogadja azt."< Elrejti a navigációs sávot. Navigációs címkék elrejtése Elrejti a szöveget a navigációs gombok alatt. + Minták gomb kicserélése + Kicseréli a minták gombot egy kereső gombra. + Előfizetés gomb kicserélése + A létrehozás gombot beállítások gombra cseréli. + A kicserélés gombról + "Ez a funkció kísérleti jellegű. +A javításnak vannak strukturális korlátai, mivel az olyan tevékenységek, mint a keresés és a beállítások a youtube zenében nem nyilvánosak. + +Ismert problémák: +- Amikor egy helyettesített tevékenység, például a Keresés és a Beállítások bezárásra kerül, megnyílik a kezdőlap. + +Kattintson a 'Kezdőlap módosítása' beállítások megnyitásához." Lejátszó + Minilejátszó következő gomb engedélyezése Lejtászó gesztus letiltása Letiltja a zeneszámok váltását a lejátszóban. Zen mód bekapcsolása 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 30f83dae9..9cb7c3a54 100644 --- a/patches/src/main/resources/music/translations/it-rIT/strings.xml +++ b/patches/src/main/resources/music/translations/it-rIT/strings.xml @@ -1,6 +1,7 @@ + Impostazioni RVX Resetta ai valori iniziali. Riavvia per caricare il layout normalmente @@ -14,17 +15,134 @@ Nascondi componente vuoto Nasconde i componenti vuoti nel menu dell\'account Nascondi l\'intestazione + Nasconde l\'handle nel menù dell\'account. Nascondi contenitore termini Nasconde il contenitore dei termini di servizio. + Action bar + Cambia la posizione della Action bar + Sposta l\'Action bar sotto il pulsante Play. + Nascondi pulsanti Mi Piace e Non Mi Piace + Non funziona con il vecchio layout del player. + Nascondi il pulsante Commenti + Nascondi il pulsante Commenti. + Nascondi il pulsante Salva + Nasconde il pulsante Salva. + Nascondi il pulsante Download + Nasconde il pulsante del download. + Nascondi pulsante Radio + Nasconde il pulsante Radio. + Nascondi il pulsante Condividi + Nasconde il pulsante Condividi. + Nascondi pulsante Brano / Video + "Nasconde il pulsante Brano / Video. (Il pulsante è visibile solo per alcuni utenti)" + Nascondi le etichette della barra delle azioni + Nasconde le etichette dei pulsanti di azione. + Sovrascrivi il pulsante di azione Download + "Il pulsante Download apre il downloader esterno. + +• Sovrascrive solo il pulsante Download nel player. +• Non sovrascrive il pulsante Download nel menu flyout o nella scheda Libreria." Nome del pacchetto downloader esterno Nome del pacchetto dell\'app downloader esterna installata, come NewPipe o Seal. + Downloader esterno + Attenzione + "%1$s non è installato. +Si prega di scaricare %2$s dal sito web." %s non è installato. Installalo. + Annunci + Nascondi gli annunci a schermo intero + "Nasconde gli annunci a schermo intero. + +Limitazioni: +• A volte potrebbe apparire una schermata nera invece della Home." + Annunci a schermo intero chiusi. + Nascondi annunci generici + Nasconde annunci generici. Nascondi le pubblicità musicali + Nasconde gli annunci durante la riproduzione. + Nascondi l\'etichetta della promozione a pagamento + Nasconde l\'etichetta della promozione a pagamento. + Nascondi popup promozionali premium + Nasconde popup promozionali premium. + I popup promozionali Premium sono chiusi. + Nascondi banner di rinnovo premium + Nasconde il banner di rinnovo premium. + Nascondi banner di avviso promozionale + Nasconde il banner di avviso promozionale. + Menù a comparsa + Aggiungi interruttore per tagliare le parti di silenzio + "Aggiunge un interruttore per tagliare le parti di silenzio, nel menù a comparsa della velocità di riproduzione. + +Info: +• Questa funzione è per i podcast. +• Questa funzione è ancora in fase di sviluppo, potrebbe essere instabile." Abilita dialogo compatto + "Abilita il menù a comparsa compatto sui telefoni. + +Limitazioni: +• Le copertine degli album nella scheda Raccolta diventano più piccole quando viene organizzata in una griglia. +• Il layout del timer di spegnimento può apparire insolito." + Nascondi pulsanti Mi Piace e Non Mi Piace + Nascondi componente a tre tasti + Nascondi il menù Aggiungi alla coda + Nascondi il menù Sottotitoli + Nascondi il menu Elimina playlist + Nascondi il menu Ignora coda + Nascondi menù Download + Nascondi il menù Modifica playlist + Nascondi il menu Vai all\'album + Nascondi il menu Vai all\'artista + Nascondi il menù Vai all\'episodio + Nascondi il menù Vai al podcast + Nascondi il menu Aiuto & feedback + Nascondi il menu Non interessato + Nascondi Aggiungi al menu Selezione rapida + Nascondi il menu Riproduci successivo + Nascondi il menu Qualità + Nascondi il menu Rimuovi della libreria + Nascondi il menu Rimuovi della playlist + Nascondi il menu Segnala + Nascondi il menu Salva episodio per dopo + Nascondi il menu Salva nella libreria + Nascondi il menu Salva nella playlist + Nascondi il menu Condivisione + Nascondi il menu Riproduzione casuale + Nascondi il menu Orario di spegnimento + Nascondi il menu Avvia radio + Nascondi il menu Statistiche per nerd + Nascondi il menu Iscriviti / Annulla Iscrizione + Nascondi dal menu Sblocca dalla selezione rapida + Nascondi il menu Visualizza crediti brano + Continua a guardare + Continua il video dall\'orario corrente quando si passa a YouTube. + Guarda su YouTube + Url video non valido. + Sostituisci il menu Ignora coda + Sostituisce il menu Ignora coda con il menu Guarda su YouTube. + Sostituisci il menu Segnala + Sostituisce il menu Segnala con il menu Velocità di riproduzione. + Mantieni Segnala nei commenti + Mantiene intatto il menu Segnala nella sezione commenti. + Generale + Cambia la pagina iniziale + Seleziona in quale pagina si apre l\'app. + Predefinito + Classifiche + Episodi per Dopo + Esplora + Cronologia + Libreria + Musica Piaciuta + Podcast + Esempi + Cerca + Sottoscrizioni + Disattiva il reindirizzamento Non mi piace + Disabilita il reindirizzamento alla traccia successiva quando si fa clic sul pulsante Non mi piace. Disabilita i sottotitoli automatici forzati Sottotitoli automatici forzati disabilitati. Abilita la modalità orizzontale @@ -34,46 +152,346 @@ Modifica i filtri personalizzati Filtra i nomi dei componenti separati da righe. + Filtro personalizzato non valido: %s. Nasconde lo scaffale dei pulsanti Nasconde lo scaffale dei pulsanti dalla home page e da Explorer. + Nascondi scaffale carosello Nasconde lo scaffale del carosello dalla home page e da Explorer. Nascondi il bottone cast Nascondo il pulsante cast nella parte superiore della homepage e in cima al riproduttore. + Nascondi barra categoria + Nasconde la barra della categoria. + Nascondi il pulsante fluttuante + Nasconde il pulsante fluttuante nella scheda Libreria. + Nascondi pulsante Cronologia + Nasconde il pulsante Cronologia nella barra degli strumenti. + Nascondi pulsante Notifiche + Nasconde il pulsante Notifiche nella barra degli strumenti. + Nascondi lo scaffale della scheda playlist + Nasconde lo scaffale della scheda playlist nel feed. + Nascondi scaffale Esempi + Nasconde lo scaffale Esempi nel feed. + Nascondi il pulsante Cerca + Nasconde il pulsante Cerca nella barra degli strumenti. + Nascondi il pulsante Ricerca sonora + Nasconde il pulsante di ricerca sonora nella barra di ricerca. + Nascondi il pulsante Tocca per aggiornare + Nascondi il pulsante Tocca per aggiornare. + Nascondi il pulsante di ricerca vocale + Nasconde il pulsante di ricerca vocale nella barra di ricerca. + Ripristina la vecchia scheda Libreria + Ripristina la scheda Libreria al vecchio stile. (Sperimentale) + Rimuovi la finestra di dialogo sulla discrezione dello spettatore + "Rimuove la finestra di dialogo sulla discrezione dello spettatore. +Questo non aggira la restrizione di età. Semplicemente la accetta automaticamente." Versione dell\'app falsificata + "Falsifica la versione client con una versione precedente. + +• Ciò cambierà l'aspetto dell'app, ma potrebbero verificarsi effetti collaterali sconosciuti. +• Se disabilitato in seguito, la vecchia interfaccia utente potrebbe rimanere finché i dati dell'app non vengono cancellati." + Falsifica destinazione della versione app + Seleziona la destinazione falsificata della versione app. + 6.42.55 - Disabilita i testi in tempo reale + 7.16.53 - Ripristina la vecchia barra d\'azione + 7.17.52 - La sezione commenti non viene visualizzata nel feed principale + Versione dell\'app da falsificare non valida: %s. + Barra di navigazione + Abilita colore personalizzato nella barra di navigazione + Imposta il colore della barra di navigazione. + Valore personalizzato del colore nella barra di navigazione + Digita il codice esadecimale del colore nella barra di navigazione. + Valore del colore nella barra di navigazione non valido. + Nascondi il pulsante Home + Nasconde il pulsante Home. + Nasconde il pulsante Esempi + Nasconde il pulsante Esempi. + Nascondi il pulsante Esplora + Nasconde il pulsante Esplora. + Nascondi il pulsante Raccolta + Nasconde il pulsante Raccolta. + Nascondi il pulsante Aggiorna + Nasconde il pulsante Aggiorna. + Nascondi la barra di navigazione + Nasconde la barra di navigazione. Nascondi etichetta di navigazione + Nasconde l\'etichetta sotto ogni pulsante di navigazione. + Sostituisci il pulsante Esempi + Sostituisce il pulsante Esempi con il pulsante Cerca. + Sostituisci il pulsante Aggiorna + Sostituisce il pulsante Aggiorna con il pulsante Impostazioni. + Informazioni sul pulsante Sostituisci + "Questa funzionalità è sperimentale. +Ci sono delle limitazioni strutturali della patch, poiché Attività come Ricerca e Impostazioni in YouTube Music non sono pubbliche. + +Problemi noti: +• Quando un'attività sostituita come Ricerca e Impostazioni viene chiusa, si apre la pagina iniziale. + +Fai clic per aprire le impostazioni \"Modifica pagina iniziale\"." + Lettore + Aggiungi il pulsante successivo nel miniplayer + Aggiunge un pulsante traccia successiva al miniplayer. + Aggiungi il pulsante precedente nel miniplayer + Aggiunge un pulsante traccia precedente al miniplayer. + Cambia il colore del miniplayer + Cambia il colore del miniplayer in modo che corrisponda al lettore a schermo intero. + Cambia il colore di sfondo del lettore + Cambia il colore di sfondo del lettore con un colore personalizzato. + Colore primario di sfondo del lettore + "Digita il codice esadecimale del colore primario dello sfondo del lettore. + +Se possibile usa colori scuri, poiché l'app non supporta temi chiari." + Colore secondario di sfondo del lettore + "Digita il codice esadecimale del colore secondario dello sfondo del lettore. + +Se possibile usa colori scuri, poiché l'app non supporta temi chiari." + Colore di sfondo lettore non valido. + Cambia la posizione della barra di ricerca + Sposta la barra di ricerca sotto il pulsante play. + Disabilita gestualità dal miniplayer + Disabilita lo scorrimento per cambiare le tracce nel miniplayer. + Disabilita gestualità dal lettore + Disabilita lo scorrimento per cambiare le tracce nel lettore. + Abilita miniplayer forzato + Abilita il miniplayer forzato quando si passa a una nuova traccia. + Abilita lo scorrimento per chiudere il miniplayer + Abilita lo scorrimento verso il basso per chiudere il miniplayer. + Abilita barra di ricerca spessa + "Abilita la barra di ricerca spessa. + +Limitazioni: i segmenti SponsorBlock non vengono mostrati nella barra di ricerca." Abilita la modalità zen Aggiunge una sfumatura grigia al riproduttore video per ridurre l\'affaticamento degli occhi. + Abilita la modalità Zen nei podcast + Abilita la modalità Zen nei podcast. + Nascondi linee guida del canale + Nasconde le linee guida del canale in cima alla sezione commenti. + Nascondi filtro sovrapposizione doppio tocco + Nasconde la sovrapposizione scura che appare quando si tocca due volte per cercare. + Nascondi i pulsanti Timestamp e Emoji + Nasconde i pulsanti emoji e timestamp durante la digitazione dei commenti. + Nascondi il pulsante Condividi a schermo intero + Nasconde il pulsante Condividi nel lettore a schermo intero. + Nascondi l\'interruttore Brano / Video + Nasconde l\'interruttore Brano / Video nel lettore. Ricorda lo stato di ripetizione Ricorda lo stato della ripetizione. Ricorda lo stato della riproduzione casuale Ricorda lo stato della riproduzione casuale. + Ripristina i vecchi pannelli popup dei commenti + Ripristina i pannelli popup dei commenti al vecchio stile. + Ripristina lo sfondo del vecchio lettore + Ripristina lo sfondo del lettore al vecchio stile. + Ripristina l\'interfaccia del vecchio lettore + "Ripristina l'interfaccia del lettore al vecchio stile. +Alcune funzionalità potrebbero non funzionare correttamente nell'interfaccia del vecchio lettore." + Menu delle Impostazioni + Nascondi il menu Centro Famiglia + Nascondi il menu Generale + Nascondi il menu Riproduzione + Nascondi il menu Salvataggio dati + Nascondi il menu Download & archiviazione + Nascondi il menu Notifiche + Nascondi il menu Privacy & dati + Nascondi il menu Raccomandazioni + Nascondi il menu Ottieni Music premium + Nascondi il menù Informazioni + Video Modifica velocità di riproduzione personalizzate Aggiungi o modifica le velocità di riproduzione disponibili + Ricorda le modifiche alla velocità di riproduzione + Ricorda l\'ultima velocità di riproduzione selezionata. + Mostra una notifica toast + Mostra una notifica toast quando viene cambiata la velocità di riproduzione predefinita. + Ricorda le modifiche alla qualità video + Ricorda l\'ultima qualità video selezionata. + Mostra una notifica toast + Mostra una notifica toast quando viene cambiata la qualità di riproduzione predefinita. Velocità di riproduzione personalizzate non valide. Ripristina i valori predefiniti. + Velocità di riproduzione personalizzate non valide. + Modifica della velocità predefinita in %s. + Modifica della qualità predefinita dei dati mobili in %s. + Impossibile impostare la qualità. + Modifica della qualità Wi-Fi predefinita in %s. + Restituisci YouTube Dislike + Abilita Restituisci YouTube Dislike Mostra il numero di \"Non mi piace\" dei video. \"Non mi piace\" in percentuale Al posto del numero di \"Non mi piace\", viene mostrata la percentuale dei Non mi piace. Pulsante \"Mi piace\" compatto Nasconde il separatore del pulsante \"Mi piace\". + Mostra i Mi piace stimati + Mostra il conteggio dei Mi piace stimati dei video. + Mostra una notifica toast se l\'API non è disponibile + Mostra una notifica toast se l\'API Restituisci YouTube Dislike non è disponibile. Informazioni + ReturnYouTubeDislike.com I dati vengono forniti dall\'API Return YouTube Dislike. Tocca qui per saperne di più. + Dislikes temporaneamente non disponibili (API scaduta). + Dislikes non disponibili (stato %d). \"Non mi piace\" non disponibile (limite API client raggiunto) + Dislikes non disponibili (%s). + Nascosto + Restituisci il nome utente di YouTube + Abilita Restituisci il nome utente di YouTube + Sostituisce gli handles con i nomi utente nei commenti. + Formato di visualizzazione + Seleziona il formato di visualizzazione del nome utente. + Nome utente + Nome utente (@handle) + \@handle (Nome utente) + Chiave API dati YouTube + La chiave sviluppatore per utilizzare l\'API v3 dei dati YouTube. + Informazioni sulla chiave API dei dati YouTube + "È richiesta una chiave sviluppatore YouTube Data API v3 per sostituire gli handle con i nomi utente. + +La quota giornaliera per le chiavi API nel piano gratuito è di 10.000 e 1 quota viene utilizzata per sostituire un handle con un nome utente per 1 commento. + +Fai clic per vedere come emettere una chiave API." + Emetti la chiave sviluppatore YouTube Data API v3 + 1. Vai su <a href=%1$s>Crea un nuovo progetto</a>.<br>2. Fai clic su <b>CREA</b>pulsante.<br>3. Vai su <a href=%2$s>YouTube Data API v3</a>.<br>4. Fai clic sul pulsante <b>ABILITA</b>pulsante.<br>5. Fai clic su <b>CREA CREDENZIALI</b>pulsante.<br>6. Seleziona l\'opzione <b>Dati pubblici</b>opzioni.<br>7. Fai clic su <b>PROSSIMO</b>pulsante.<br>8. Copia la chiave API.<br><br>※ La chiave API non dovrebbe mai essere condivisa con altri, quindi non è inclusa nelle impostazioni di importazione/esportazione. + SponsorBlock + Abilita SponsorBlock + SponsorBlock è un sistema di crowdsourcing per saltare parti fastidiose dei video di YouTube. + Mostra una notifica toast se l\'API non è disponibile + Mostra una notifica toast se l\'API SponsorBlock non è disponibile. + Mostra una notifica toast quando si salta automaticamente + Mostra una notifica toast quando un segmento viene saltato automaticamente. + Modifica l\'URL dell\'API + L\'indirizzo usato da SponsorBlock per contattare il server. Non cambiarlo a meno che tu non sappia cosa stai facendo. + Reimposta l\'URL dell\'API. + URL API non valido. + URL API modificato. + Modifica il comportamento dei segmenti + Sponsor + Promozioni a pagamento, referral a pagamento e pubblicità diretta. Non per autopromozione o ringraziamenti gratuiti a cause, creatori, siti web o prodotti di loro gradimento. + Promozione non retribuita / Autopromozione + Simile a Sponsor, eccetto per la promozione non retribuita o autopromozione. Include sezioni su merchandising, donazioni o informazioni su chi ha collaborato con loro. + Promemoria interazione (Iscriviti) + Una breve promemoria per mettere mi piace, iscriversi o seguirli su altre piattaforme durante la visione. Se è lungo o riguarda qualcosa di specifico, dovrebbe essere considerato autopromozione. + Animazione di Intermezzo / Introduzione + Intervallo senza contenuto sostanziale, come pause, scene statiche o ripetute che non includono transizioni contenenti informazioni. + Schede finali / Crediti + I crediti o quando appaiono le schede finali. Non per conclusioni con informazioni. + Anteprima / Riepilogo / Gancio + Raccolta di clip che mostrano cosa sta succedendo o cosa è successo nel video o in altri video di una serie, dove tutte le informazioni vengono ripetute altrove. + Riempitivo Tangente / Scherzi + Scene tangenziali aggiunte solo come riempitivo o umorismo che non sono necessarie per comprendere il contenuto principale del video. Non include segmenti che forniscono contesto o dettagli di background. + Musica: Sezione non musicale + Da utilizzare solo nei video musicali. Sezioni di video musicali senza musica, che non siano già coperte da un\'altra categoria. + Sponsor saltato. + Autopromozione saltata. + Promemoria indesiderato saltato. + Introduzione saltata. + Intermezzo saltato. + Intermezzo saltato. + Conclusione saltata. + Anteprima saltata. + Anteprima saltata. + Riepilogo saltato. + Riempitivo saltato. + Sezione non musicale saltata. + Segmenti multipli saltati. + Salta automaticamente + Disabilita + SponsorBlock temporaneamente non disponibile. + SponsorBlock temporaneamente non disponibile (Stato: %d). + SponsorBlock temporaneamente non disponibile (API scaduta). + Colore: + Colore modificato. + Colore ripristinato. + Codice colore non valido. + Resetta colore + I dati sono forniti dall\'API di SponsorBlock. Tocca qui per saperne di più e vedere i download per altre piattaforme. + Informazioni + sponsor.ajay.app + Varie Importa/Esporta Importa o esporta le impostazioni come testo. + Esporta le impostazioni in un file + Importa le impostazioni da un file + Importa / Esporta le impostazioni come testo + Esportazione impostazioni fallita. + Impostazioni esportate con successo. Importa Copia + Importazione fallita: %s. Ripristino impostazioni ai valori predefiniti Impostazioni %d importate + Reimposta + Impostazioni copiate negli appunti. + Aggira le restrizioni regionali delle immagini + Ignora il dominio bloccato in alcune regioni in modo che le miniature della playlist, gli avatar del canale, ecc. possano essere ricevuti. + Cambia foglio di condivisione + Sostituisce il foglio di condivisione in-app con il foglio di condivisione di sistema. + Disabilita animazione di avvio Cairo + Disabilita l\'animazione di avvio Cairo all\'avvio dell\'applicazione. + Disabilita audio DRC + Disabilita DRC (Compressione Gamma Dinamica) applicato all\'audio. + Disabilita video musicali nell\'album + "Quando un utente non premium riproduce una canzone inclusa in un album, a volte viene riprodotto il video musicale al posto della canzone ufficiale. + +Trova la canzone ufficiale se viene rilevato un video musicale in riproduzione da un album. + +Limitazioni: I video per bambini potrebbero non essere reindirizzati." + Tipo di reindirizzamento + Specifica come reindirizzare alla canzone ufficiale. + Reindirizzamento + Tocca l\'interruttore Brano / Video + Tocca e tieni premuto l\'interruttore Brano / Video + Disabilita il protocollo QUIC + "Disabilita il protocollo QUIC di CronetEngine." Abilita la registrazione del debug Stampa il registro di debug. + Abilita la registrazione di debug del buffer + Include il buffer nel registro di debug. Abilita il codec opus "Abilita il codec Opus 250/251 durante la riproduzione dell'audio." + Sanitizza i link di condivisione + Sanifica i link di condivisione rimuovendo i parametri di tracciamento. + Falsifica client + Falsifica il client per prevenire problemi di riproduzione. + Client predefinito + Definisce un client predefinito per la falsificazione. + Android Music 4.27.53 + Android Music 5.29.53 + iOS Music 6.21 + iOS Music 7.04 + Falsifica i parametri del lettore + "Falsifica il parametro del lettore per evitare problemi di riproduzione. + +Effetto collaterale: +• A volte i sottotitoli si trovano nella parte superiore del lettore anziché in quella inferiore." + Guarda il tipo di cronologia + "• Originale: segue le impostazioni della cronologia delle visualizzazioni dell'account Google, ma la cronologia delle visualizzazioni potrebbe non funzionare a causa di DNS o VPN. +• Sostituisci dominio: segue le impostazioni della cronologia delle visualizzazioni dell'account Google. +• Blocca cronologia delle visualizzazioni: la cronologia delle visualizzazioni è bloccata." + Originale + Sostituisci dominio + Blocca la cronologia delle visualizzazioni + Apri le impostazioni predefinite dell\'app + Per aprire i link di YouTube Music in RVX Music, abilita Apri link supportati e abilita tutti gli indirizzi web supportati. + Apri le impostazioni GmsCore + Per ricevere notifiche in RVX Music, abilita Cloud Messaging. + GmsCore non è installato, installalo. + Azione necessaria + "GmsCore non ha il permesso di funzionare in background. + +Segui la guida \"Don't kill my app!\" per il tuo dispositivo e applica le istruzioni alla tua installazione di GmsCore. + +Questo è necessario affinché l'app funzioni." + Apri sito web + "Le ottimizzazioni della batteria di GmsCore devono essere disattivate per evitare problemi. + +Disattivare le ottimizzazioni della batteria per GmsCore non influirà negativamente sull'utilizzo della batteria. + +Tocca il pulsante Continua e consenti le modifiche di ottimizzazione." + Continua 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 841b47b0e..fbe5ecfdf 100644 --- a/patches/src/main/resources/music/translations/ja-rJP/strings.xml +++ b/patches/src/main/resources/music/translations/ja-rJP/strings.xml @@ -172,6 +172,8 @@ プレイリストシェルフを非表示にします。 サンプルシェルフを非表示 フィードからサンプルシェルフを非表示にします。 + 検索ボタンを非表示 + ツールバーの検索ボタンを非表示にします。 サウンドサーチボタンを非表示 検索バーのサウンドサーチボタンを非表示にします。 「タップして更新」ボタンを非表示 @@ -192,6 +194,8 @@ 偽装するバージョンを選択してください。 6.42.55 - リアルタイムの歌詞を無効化 7.16.53 - 古いアクションバーを復元 + 7.17.52 - コメントセクションがホームフィードに表示されません + 無効な偽装アプリバージョンです: %s ナビゲーションバー カスタムナビゲーションバーの色を有効にする 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 6bdbd316c..7f7bbfbef 100644 --- a/patches/src/main/resources/music/translations/ko-rKR/strings.xml +++ b/patches/src/main/resources/music/translations/ko-rKR/strings.xml @@ -2,7 +2,7 @@ RVX Music 설정 - 기본값으로 초기화합니다. + 기본값으로 초기화하였습니다. 레이아웃을 정상적으로 불러오기 위해 다시 시작합니다. 새로고침 및 다시 시작 @@ -58,7 +58,7 @@ 알려진 문제점: • 간혹 홈 피드 대신 검은색 빈 화면이 표시될 수 있습니다." - 전체 화면 광고가 닫아집니다. + 전체 화면 광고가 닫아졌습니다. 일반 레이아웃 광고 제거 일반 레이아웃 광고를 숨깁니다. 미디어 광고 제거 @@ -67,7 +67,7 @@ 유료 광고 포함 라벨을 숨깁니다. Premium 프로모션 팝업 제거 Premium 프로모션 팝업을 숨깁니다. - Premium 프로모션 팝업이 닫아집니다. + Premium 프로모션 팝업이 닫아졌습니다. Premium 갱신 배너 제거 Premium 갱신 배너를 숨깁니다. Premium 프로모션 알림 배너 제거 @@ -172,6 +172,8 @@ 피드에서 재생목록 카드 선반을 숨깁니다. 샘플 선반 제거 피드에서 샘플 선반을 숨깁니다. + 검색 버튼 제거 + 툴바에서 검색 버튼을 숨깁니다. 노래 검색 버튼 제거 툴바에서 노래 검색 버튼을 숨깁니다. 탭하여 업데이트 버튼 제거 @@ -191,6 +193,8 @@ 변경할 앱 버전을 선택하세요. 6.42.55 - 실시간 가사를 비활성화합니다. 7.16.53 - 이전 액션바로 복원합니다. + 7.17.52 - 홈 피드에서 댓글 섹션이 표시되지 않습니다. + 변경할 앱 버전이 잘못되었습니다: %s 하단바 사용자 정의 하단바 색상 활성화 @@ -309,7 +313,7 @@ YouTube Music의 검색 및 설정과 같은 활동은 공개되지 않으므로 기본 동영상 화질 값으로 변경되었을 때, 팝업 메시지를 표시합니다. 사용자 정의 재생 속도는 %s배속보다 작아야 합니다. 잘못된 재생 속도 값입니다. - 기본 재생 속도 값을 %s 로 변경합니다. + 기본 재생 속도 값을 %s 로 변경하였습니다. 모바일 네트워크 이용 시 기본 동영상 품질 값을 %s 로 변경합니다. 동영상 품질을 설정할 수 없습니다. Wi-Fi 이용 시 기본 동영상 품질 값을 %s 로 변경합니다. 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 d69e52c5c..e847ee27a 100644 --- a/patches/src/main/resources/music/translations/pl-rPL/strings.xml +++ b/patches/src/main/resources/music/translations/pl-rPL/strings.xml @@ -172,12 +172,14 @@ Ograniczenia: Ukrywa półkę z rekomendowanymi playlistami na stronie głównej. Ukryj półkę z samplami Ukrywa półke z samplami na stronie głównej. + Ukryj przycisk wyszukiwania + Ukrywa przycisk wyszukiwania z paska narzędzi. Ukryj przycisk od rozpoznawania piosenek - Ukrywa przycisk od rozpoznawania piosenek w pasku wyszukiwania. + Ukrywa przycisk od rozpoznawania piosenek z paska wyszukiwania. Ukryj przycisk \'Stuknij, aby zaktualizować\' Ukrywa przycisk \'Stuknij, aby zaktualizować\'. Ukryj przycisk od wyszukiwania głosowego - Ukrywa przycisk od wyszukiwania głosowego w pasku wyszukiwania. + Ukrywa przycisk od wyszukiwania głosowego z paska wyszukiwania. Włącz stary styl półek biblioteki Przywraca zakładkę biblioteki do starego stylu. (Eksperymentalne) Usuń okno dialogowe treści ograniczonej do oglądania @@ -192,6 +194,8 @@ Nie pomija to ograniczeń wiekowych, lecz akceptuje je automatycznie." Wybierz wersję, którą chcesz oszukiwać. 6.42.55 - Wyłącza teksty w czasie rzeczywistym 7.16.53 - Przywraca stary pasek akcji + 7.17.52 - Sekcja komentarzy nie pojawia się na stronie głównej + Nieprawidłowa oszukiwana wersja aplikacji: %s. Pasek nawigacji Włącz niestandardowy kolor paska nawigacji 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 356d67cc6..b9501f56a 100644 --- a/patches/src/main/resources/music/translations/pt-rBR/strings.xml +++ b/patches/src/main/resources/music/translations/pt-rBR/strings.xml @@ -172,6 +172,8 @@ Limitações: Oculta o painel de cartão de lista de reprodução no feed. Ocultar painel Descobertas Oculta o painel Descobertas no feed. + Ocultar botão de pesquisa + Oculta o botão de pesquisa na barra de ferramentas. Ocultar botão de pesquisa de som Oculta o botão de pesquisa de som na barra de pesquisa. Ocultar botão Toque para atualizar @@ -192,6 +194,8 @@ Isso não ignora a restrição de idade, apenas aceita isso automaticamente."Selecione a versão do app para falsificação. 6.42.55 - Desativar letras em tempo real 7.16.53 - Restaurar barra de ação antiga + 7.17.52 - A seção de comentários não aparece no feed inicial + Versão da falsificação do app inválida: %s. Barra de Navegação Ativar cor personalizada da barra de navegação @@ -213,6 +217,18 @@ Isso não ignora a restrição de idade, apenas aceita isso automaticamente."Oculta a barra de navegação. Ocultar rótulos de navegação Oculta o rótulo abaixo de cada botão de navegação. + Substituir botão Descobertas + Substitui o botão Descobertas pelo botão de Pesquisa. + Substituir botão Upgrade + Substitui o botão Upgrade pelo botão de Configurações. + Sobre a substituição de botão + "Este recurso é experimental. +Existem limitações estruturais do patch, pois atividades como Pesquisa e Configurações no YouTube Music não são públicas. + +Problemas conhecidos: +• Quando uma atividade substituída, como Pesquisa e Configurações é fechada, a página inicial se abre. + +Clique para abrir as configurações de 'Alterar página inicial'." Reprodutor Adicionar o botão próximo ao mini reprodutor @@ -453,6 +469,11 @@ Informações: Android Music 5.29.53 iOS Music 6.21 iOS Music 7.04 + Falsificação de parâmetro do reprodutor + "Falsificar o parâmetro de reprodutor para evitar problemas de reprodução. + +Efeito lateral: +• Às vezes as legendas estão localizadas na parte superior do reprodutor ao invés da parte inferior." 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. 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 6272aedbb..2faf67d36 100644 --- a/patches/src/main/resources/music/translations/ru-rRU/strings.xml +++ b/patches/src/main/resources/music/translations/ru-rRU/strings.xml @@ -172,6 +172,8 @@ Скрывает полку с заставкой плейлиста в ленте. Скрыть полку \"Семплы\" Скрывает полку \"Семплы\" в ленте. + Скрыть кнопку поиска + Скрывает кнопку поиска на панели инструментов. Скрыть кнопку поиска звука Скрывает кнопку поиска звука в строке поиска. Скрыть кнопку \"Обновить\" @@ -192,6 +194,8 @@ Выберите целевую версию приложения для подмены. 6.42.55 - Отключает динамических текстов 7.16.53 - Восстановить старую панель действий + 7.17.52 - Раздел комментариев не отображается на вкладке \"Главная\" + Неверная версия подмены: %s. Панель навигации Включить пользовательский цвет панели навигации 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 b1d6d001b..5cabf4fc6 100644 --- a/patches/src/main/resources/music/translations/uk-rUA/strings.xml +++ b/patches/src/main/resources/music/translations/uk-rUA/strings.xml @@ -165,13 +165,15 @@ Приховати плаваючу кнопку Приховує плаваючу кнопку у вкладці \"Бібліотека\". Приховати кнопку історії - Приховує кнопку історії на панелі інструментів вкладки \"Бібліотека\". + Приховує кнопку історії на панелі інструментів. Приховати кнопку сповіщень Приховує кнопку сповіщень на панелі інструментів. Приховати полицю карток списку відтворення Приховує полицю карток списку відтворення в стрічці. Приховати полицю \"Семпли\" Приховує полицю \"Семпли для вас\" у стрічці. + Приховати кнопку пошуку + Приховує кнопку пошуку на панелі інструментів. Приховати кнопку пошуку музики Приховує кнопку пошуку музики у панелі пошуку. Приховати кнопку оновлення @@ -192,6 +194,8 @@ Виберіть зі списку цільову версію для підміни. 6.42.55 - Вимкнення динамічних текстів 7.16.53 - Відновлення старої панелі дій + 7.17.52 - Розділ коментарів не показується в домашній стрічці + Невірна версія підміни: %s. Панель навігації Увімкнути користувацький колір панелі навігації 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 5cd971de8..a3cfea1ce 100644 --- a/patches/src/main/resources/music/translations/vi-rVN/strings.xml +++ b/patches/src/main/resources/music/translations/vi-rVN/strings.xml @@ -171,6 +171,8 @@ Hạn chế: Ẩn kệ thẻ danh sách phát ở thẻ Trang chủ. Ẩn thẻ Đoạn nhạc Ẩn kệ Đoạn nhạc ở thẻ Trang chủ. + Ẩn nút Tìm kiếm + Ẩn nút Tìm kiếm khỏi thanh công cụ. Ẩn nút Tìm kiếm bằng âm thanh Ẩn nút Tìm kiếm bằng âm thanh kế bên thanh tìm kiếm. Ẩn nút Chạm để nâng cấp @@ -191,6 +193,8 @@ Hạn chế: Chọn phiên bản YouTube Music mà bạn muốn giả mạo. 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ũ + 7.17.52 - Phần bình luận không xuất hiện trên thẻ Trang chủ + Phiên bản ứng dụng đã chọn không hợp lệ: %s. Thanh điều hướng Màu thanh điều hướng tuỳ chỉnh diff --git a/patches/src/main/resources/youtube/branding/youtube/settings/drawable/revanced_extended_settings_key_icon.xml b/patches/src/main/resources/youtube/branding/youtube/settings/drawable/revanced_extended_settings_key_icon.xml index 274b2e64f..f261ad58c 100644 --- a/patches/src/main/resources/youtube/branding/youtube/settings/drawable/revanced_extended_settings_key_icon.xml +++ b/patches/src/main/resources/youtube/branding/youtube/settings/drawable/revanced_extended_settings_key_icon.xml @@ -13,7 +13,7 @@ - \ No newline at end of file + diff --git a/patches/src/main/resources/youtube/branding/youtube/splash/drawable/$avd_anim__0.xml b/patches/src/main/resources/youtube/branding/youtube/splash/drawable/$avd_anim__0.xml index 4d4459196..661ba43e1 100644 --- a/patches/src/main/resources/youtube/branding/youtube/splash/drawable/$avd_anim__0.xml +++ b/patches/src/main/resources/youtube/branding/youtube/splash/drawable/$avd_anim__0.xml @@ -7,9 +7,9 @@ - + - \ No newline at end of file + diff --git a/patches/src/main/resources/youtube/navigationbuttons/drawable-xxxhdpi/yt_outline_library_cairo_black_24.png b/patches/src/main/resources/youtube/navigationbuttons/drawable-xxxhdpi/yt_outline_library_cairo_black_24.png new file mode 100644 index 000000000..a91f3f8fd Binary files /dev/null and b/patches/src/main/resources/youtube/navigationbuttons/drawable-xxxhdpi/yt_outline_library_cairo_black_24.png differ 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 9ca518f23..e0efdc769 100644 --- a/patches/src/main/resources/youtube/settings/host/values/arrays.xml +++ b/patches/src/main/resources/youtube/settings/host/values/arrays.xml @@ -199,8 +199,10 @@ @string/revanced_language_DEFAULT + @string/revanced_language_AM @string/revanced_language_AR @string/revanced_language_AZ + @string/revanced_language_BE @string/revanced_language_BG @string/revanced_language_BN @string/revanced_language_CA @@ -215,6 +217,7 @@ @string/revanced_language_FI @string/revanced_language_FR @string/revanced_language_GU + @string/revanced_language_HE @string/revanced_language_HI @string/revanced_language_HR @string/revanced_language_HU @@ -238,6 +241,7 @@ @string/revanced_language_RO @string/revanced_language_RU @string/revanced_language_SK + @string/revanced_language_SQ @string/revanced_language_SL @string/revanced_language_SR @string/revanced_language_SV @@ -253,8 +257,10 @@ DEFAULT + AM AR AZ + BE BG BN CA @@ -269,6 +275,7 @@ FI FR GU + HE HI HR HU @@ -292,6 +299,7 @@ RO RU SK + SQ SL SR SV @@ -400,14 +408,26 @@ @string/revanced_spoof_streaming_data_type_entry_android_vr_no_auth @string/revanced_spoof_streaming_data_type_entry_android_unplugged @string/revanced_spoof_streaming_data_type_entry_ios_unplugged - @string/revanced_spoof_streaming_data_type_entry_ios ANDROID_VR ANDROID_VR_NO_AUTH ANDROID_UNPLUGGED IOS_UNPLUGGED - IOS + + + @string/revanced_spoof_streaming_data_type_entry_android_vr + @string/revanced_spoof_streaming_data_type_entry_android_vr_no_auth + @string/revanced_spoof_streaming_data_type_entry_android_unplugged + @string/revanced_spoof_streaming_data_type_entry_ios_unplugged + @string/revanced_spoof_streaming_data_type_entry_ios_deprecated + + + ANDROID_VR + ANDROID_VR_NO_AUTH + ANDROID_UNPLUGGED + IOS_UNPLUGGED + IOS_DEPRECATED 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 b174045f1..cadaf9aef 100644 --- a/patches/src/main/resources/youtube/settings/host/values/strings.xml +++ b/patches/src/main/resources/youtube/settings/host/values/strings.xml @@ -22,60 +22,152 @@ "%1$s is not installed. Please download %2$s from the website." %s is not installed. Please install it. + + Add to queue + Add to queue and open queue + Add to queue and play video + External downloader + Open queue + Queue + Remove from queue + Remove from queue and open queue + Remove queue + Save queue + "Instead of opening an external downloader, open the queue manager dialog. + +You can also open the queue manager by pressing and holding the back button on the navigation bar. + +This feature is still in progress, so most features may not work. + +Please use it for debugging purposes only." + Login required + Queue manager unavailable (%s). + Could not identify playlist + Queue is empty + Could not identify video + Failed to add video. + Failed to create queue. + Failed to delete queue. + Failed to remove video. + Failed to save queue. + Video successfully added. + Queue successfully created. + Queue successfully deleted. + Video successfully removed. + Queue successfully saved to \'%s\'. + RVX language App language - Arabic - Azerbaijani - Bulgarian - Bengali - Catalan - Czech - Danish - German - Greek - English - Spanish - Estonian - Persian - Finnish - French - Gujarati - Hindi - Croatian - Hungarian - Indonesian - Italian - Japanese - Kazakh - Korean - Lithuanian - Latvian - Macedonian - Mongolian - Marathi - Malay - Burmese - Dutch - Odia - Punjabi - Polish - Portuguese - Romanian - Russian - Slovak - Slovene - Serbian - Swedish - Swahili - Tamil - Telugu - Thai - Turkish - Ukrainian - Urdu - Vietnamese - Chinese - + "Amharic +አማርኛ" + "Arabic +العربية" + "Azerbaijani +Azərbaycan" + "Belarusian +беларуская" + "Bulgarian +Български" + "Bengali +বাংলা" + "Catalan +Català" + "Czech +Čeština" + "Danish +Dansk" + "German +Deutsch" + "Greek +Ελληνικά" + "English +English" + "Spanish +Español" + "Estonian +Eesti" + "Persian +فارسی" + "Finnish +Suomi" + "French +Français" + "Gujarati +ગુજરાતી" + "Hebrew +עברי" + "Hindi +हिन्दी" + "Croatian +Hrvatski" + "Hungarian +Magyar" + "Indonesian +Indonesia" + "Italian +Italiano" + "Japanese +日本語" + "Kazakh +Қазақ тілі" + "Korean +한국어" + "Lithuanian +Lietuvių" + "Latvian +Latviešu" + "Macedonian +Македонски" + "Mongolian +Монгол" + "Marathi +मराठी" + "Malay +Melayu" + "Burmese +ဗမာ" + "Dutch +Nederlands" + "Odia +ଓଡ଼ିଆ" + "Punjabi +ਪੰਜਾਬੀ" + "Polish +Polski" + "Portuguese +Português" + "Romanian +Română" + "Russian +Русский" + "Slovak +Slovenčina" + "Albanian +Shqip" + "Slovene +Slovenščina" + "Serbian +Српски" + "Swedish +Svenska" + "Swahili +Kiswahili" + "Tamil +தமிழ்" + "Telugu +తెలుగు" + "Thai +ไทย" + "Turkish +Türkçe" + "Ukrainian +Українська" + "Urdu +اردو" + "Vietnamese +Tiếng Việt" + "Chinese +中文" Ads @@ -422,9 +514,6 @@ Limitation: Back button on the toolbar may not work." Disable splash animation Splash animation is disabled. Splash animation is enabled. - Disable translucent status bar - Status bar is opaque. - Status bar is opaque or translucent. Enable gradient loading screen Gradient loading screen is enabled. Gradient loading screen is disabled. @@ -460,6 +549,23 @@ Automotive layout • Shorts open in the regular player. • Feed is organized by topics and channels. • Video description cannot be opened when 'Spoof streaming data' is turned off." + Disable layout updates + Layout will not be updated by the server. + Layout will be updated by the server. + "App layout reverts to the layout it was using when it was first installed. + +Some server-side layouts may not revert. + +Changes include: +• Components in the player flyout menu (or related settings) may not work. +• Rolling numbers are not animated. +• The Library tab is used. +• The Music section of video description may not work. +• Account switch button may not appear on the Library tab. Use the 'Enable wide search bar in You tab' setting." + Disable translucent status bar + Status bar is opaque. + Status bar is opaque or translucent. + For some manufacturer ROMs running Android 12+, enabling this feature may make the system navigation bar transparent. Spoof app version Version spoofed Version not spoofed @@ -477,8 +583,10 @@ If later turned off, it is recommended to clear the app data to prevent UI bugs. 18.33.40 - Restore old Shorts action bar 18.38.45 - Restore old default video quality behavior 18.48.39 - Disable views and likes from being updated in real time + 19.01.34 - Disable video description interaction 19.26.42 - Disable Cairo icon in navigation and toolbar 19.33.37 - Restore old playback speed flyout panel + Invalid spoof app version: %s. Account menu @@ -521,6 +629,9 @@ Some components may not be hidden." Override video download button Native video download button opens your external downloader. Native video download button opens the native in-app downloader. + Queue manager + Native video download button opens the queue manager. + Native video download button opens your external downloader. Playlist downloader package name Package name of your installed external downloader app, such as YTDLnis. @@ -578,7 +689,6 @@ 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. @@ -1190,6 +1300,8 @@ Tap and hold to copy video timestamp." Tap to mute volume of the current video. Tap again to unmute. Show external downloader button Tap to launch external downloader. + Queue manager + Instead of launching an external downloader, open the queue manager. Show speed dialog button "Tap to open speed dialog. Tap and hold to reset playback speed to 1.0x. Tap and hold again to reset back to default speed." @@ -1510,7 +1622,6 @@ Info: "Custom actions are enabled in flyout menu. Limitations: -• Does not work if app version is spoofed to 18.49.37 or earlier. • Does not work with live stream." Custom actions are disabled in flyout menu. Enable custom actions in toolbar @@ -1535,6 +1646,10 @@ Press and hold the More button to show the Custom actions dialog." Show open video menu Open video menu is shown. Open video menu is hidden. + Speed dialog + Show speed dialog menu + Speed dialog menu is shown. + Speed dialog menu is hidden. Repeat state Show repeat state menu Repeat state menu is shown. @@ -1582,10 +1697,14 @@ No margins on top and bottom of player." Lowest value of the brightness gesture activates auto-brightness. Lowest value of the brightness gesture does not activate auto-brightness. Enable brightness gesture - Brightness swipe is enabled. + "Fullscreen brightness swipe is enabled. + +Adjust brightness by swiping vertically on the left side of the screen." Brightness swipe is disabled. Enable volume gesture - Volume swipe is enabled. + "Fullscreen volume swipe is enabled. + +Adjust volume by swiping vertically on the right side of the screen." Volume swipe is disabled. Enable save and restore brightness Save and restore brightness when exiting or entering fullscreen. @@ -1599,10 +1718,20 @@ No margins on top and bottom of player." Swipe gestures in Lock screen mode Swipe gestures are enabled in Lock screen mode. Swipe gestures are disabled in Lock screen mode. - Swipe background visibility - The visibility of the swipe overlay background. Swipe magnitude threshold The threshold for a swipe to occur. + Swipe overlay alternative UI + Alternative UI is used. + Legacy UI is used. + Enable minimal style + Minimal overlay style is enabled. + Minimal overlay style is disabled. + Show circular overlay + Circular overlay is shown. + Horizontal overlay is shown. + Swipe overlay background opacity + Opacity value between 0-100. + Swipe opacity must be between 0-100. Swipe overlay text size The text size in the swipe overlay. Swipe overlay screen size @@ -1610,6 +1739,7 @@ No margins on top and bottom of player." Swipeable area size cannot be more than 50. Swipe overlay timeout The amount of milliseconds the overlay is visible. + Brightness swipe sensitivity Configure the minimum distance for brightness swiping between 1 and 1000 (%).\nThe shorter the minimum distance, the faster the brightness level changes. Brightness swipe sensitivity must be between 1-1000 (%). @@ -1637,12 +1767,45 @@ No margins on top and bottom of player." Video - Default playback speed - Default video quality on mobile network - Default video quality on Wi-Fi network + + Codec Disable HDR video HDR video is disabled. HDR video is enabled. + Disable VP9 codec + "VP9 codec is disabled. + +Info: +• Maximum resolution is 1080p. +• Video playback will use more internet data than VP9. +• VP9 codec is still used for HDR video." + VP9 codec is enabled. + Replace software AV1 codec + Replaces the software AV1 codec with the VP9 codec. + + + Playback speed + Default playback speed + Remember playback speed changes + Playback speed changes apply to all videos. + Playback speed changes only apply to the current video. + Show a toast + A toast will be shown when changing the default playback speed. + A toast will not be shown when changing the default playback speed. + + Default playback speed on Shorts + Remember playback speed changes on Shorts + "Playback speed changes apply to all Shorts. + +Info: +• The only way to change the playback speed in the Shorts player is to use the 'Speed ​​dialog' in 'Custom actions'. +• If you didn't include the 'Shorts components' patch, this wouldn't be available." + Playback speed changes only apply to the current Short. + Show a toast + A toast will be shown when changing the default playback speed on Shorts. + A toast will not be shown when changing the default playback speed on Shorts. + Changed default playback speed to: %s. + Changed default Shorts playback speed to: %s. Enable custom playback speed Custom playback speed is enabled. Custom playback speed is disabled. @@ -1651,63 +1814,60 @@ No margins on top and bottom of player." Old style flyout menu is used. Edit custom playback speeds Add or change available playback speeds. - Remember playback speed changes - Playback speed changes apply to all videos. - Playback speed changes only apply to the current video. - Show a toast - A toast will be shown when changing the default playback speed. - A toast will not be shown when changing the default playback speed. + Custom speeds must be less than %sx. + Invalid custom playback speeds. + + + Video quality + Default video quality on mobile network + Default video quality on Wi-Fi network Remember video quality changes Quality changes apply to all videos. Quality changes only apply to the current video. Show a toast A toast will be shown when changing the default video quality. A toast will not be shown when changing the default video quality. + Default Shorts quality on mobile network + Default Shorts quality on Wi-Fi network + Remember Shorts quality changes + Quality changes apply to all Shorts. + Quality changes only apply to the current Short. + Show a toast + A toast will be shown when changing the default Shorts quality. + A toast will not be shown when changing the default Shorts quality. + mobile + wifi + Changed default %1$s quality to: %2$s. + Changed Shorts %1$s quality to: %2$s. Restore old video quality menu Old video quality menu is shown. Old video quality menu is not shown. - Disable playback speed for music - Default playback speed is disabled for music. - Default playback speed is enabled for music. - Validate using categories - Default playback speed is disabled if the video category is Music. - Default playback speed is disabled for videos playable on YouTube Music. - Enable Shorts default playback speed - Default playback speed applies to Shorts. - Default playback speed does not apply to Shorts. Skipped preloaded buffer. Skip preloaded buffer "Skips the preloaded buffer at the start of videos to immediately apply the default video quality. Info: • When the video starts, there is a delay of approximately 0.3 seconds. -• Does not apply to HDR videos, live stream videos, or videos shorter than 15 seconds." +• Does not apply to Shorts, HDR videos, live stream videos, or videos shorter than 15 seconds. +• The 'Spoof streaming data' setting also removes preloaded buffers, so this setting is not needed if you use that setting." Turning on this setting may cause video playback issues. Show a toast when skipping Toast is shown. Toast is not shown. Spoof device dimensions "Spoofs the device dimensions to the maximum value. -High quality may be unlocked on some videos that require high device dimensions, but not all videos." - Disable VP9 codec - "VP9 codec is disabled. -• Maximum resolution is 1080p. -• Video playback will use more internet data than VP9. -• VP9 codec is still used for HDR video." - VP9 codec is enabled. - Replace software AV1 codec - Replaces the software AV1 codec with the VP9 codec. - Reject software AV1 codec response - "Forcefully rejects the software AV1 codec response. -A different codec will be applied after about 20 seconds of buffering." - Fallback process causes about 20 seconds of buffering. - Changing default speed to %s. - Changing default mobile data quality to %s. - Failed to set video quality. - Changing default Wi-Fi quality to %s. - Custom speeds must be less than %sx. - Invalid custom playback speeds. +Info: +• High quality may be unlocked on some videos that require high device dimensions, but not all videos. +• This setting is not available if 'Spoof streaming data' is turned on." + + + Disable playback speed for music + Default playback speed is disabled for music. + Default playback speed is enabled for music. + Validate using categories + Default playback speed is disabled if the video category is Music. + Default playback speed is disabled for videos playable on YouTube Music. @@ -1853,6 +2013,7 @@ Click to see how to issue an API key." Show in seekbar Disable + Opacity: Color: Color changed. Color reset. @@ -1925,6 +2086,8 @@ Click to see how to issue an API key." Downvote Change category There are no segments to vote for. + + %1$s to %2$s Choose the segment category Category is disabled in settings. Enable category to submit. @@ -2058,8 +2221,8 @@ Tap the continue button and allow optimization changes." Android VR "Android VR (No auth)" - "iOS -(PoToken required)" + "iOS +(Deprecated)" "iOS TV (Login required)" Spoofing side effects @@ -2067,10 +2230,20 @@ Tap the continue button and allow optimization changes." • Stable volume is not available. • Disable forced auto audio tracks is not available. • Kids videos may not play when logged out or in incognito mode." - • There may be playback issues (PoToken required). + • Whole ASNs/IP ranges may be blocked by the server. "• Stable volume is not available. • Movies or paid videos may not play. • Kids videos may not play when logged out or in incognito mode." + Use iOS client + "iOS client added to available clients. + +WARNING: iOS client is deprecated. Any issues that arise while using it are at your own risk." + iOS client not added to available clients. + "When requesting YouTube API endpoints using iOS, you will need the Auth tokens issued by the iOS device and the PoTokens issued by iOSGuard. + +This means that streaming requests via iOS will be missing both the Auth tokens and the PoTokens, and the server may consider the user as a bot and block the entire ASN/IP range. + +USE AT YOUR OWN RISK!" Force iOS AVC (H.264) Video codec is forced to AVC (H.264). Video codec is determined automatically. @@ -2089,6 +2262,8 @@ AVC has a maximum resolution of 1080p, Opus audio codec is not available, and vi Client used to fetch streaming data is shown in Stats for nerds. Client used to fetch streaming data is hidden in Stats for nerds. VR default audio stream language + Could not fetch any client streams. + You may not be logged in. PoToken / VisitorData 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 136f93c31..3b906b092 100644 --- a/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/patches/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -148,6 +148,7 @@ + SETTINGS: HOOK_DOWNLOAD_ACTIONS --> @@ -253,9 +254,6 @@ - - @@ -275,6 +273,12 @@ + + + + @@ -695,8 +701,11 @@ - + + + + @@ -722,32 +731,44 @@ @@ -788,15 +809,15 @@ - - - - - - - - - + + + + + + + + + @@ -828,7 +849,7 @@ @@ -837,13 +858,13 @@ SETTINGS: SKIP_RESPONSE_ENCRYPTION --> + + + + + + + + diff --git a/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_full.xml b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_full.xml new file mode 100644 index 000000000..c0d45293c --- /dev/null +++ b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_full.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_high.xml b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_high.xml new file mode 100644 index 000000000..fe45b31be --- /dev/null +++ b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_high.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_low.xml b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_low.xml new file mode 100644 index 000000000..66010e253 --- /dev/null +++ b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_low.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_manual.xml b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_manual.xml new file mode 100644 index 000000000..a5dc31c48 --- /dev/null +++ b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_manual.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_medium.xml b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_medium.xml new file mode 100644 index 000000000..fc191d25e --- /dev/null +++ b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_brightness_medium.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_volume_high.xml b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_volume_high.xml new file mode 100644 index 000000000..5dfb76a0e --- /dev/null +++ b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_volume_high.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_volume_low.xml b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_volume_low.xml new file mode 100644 index 000000000..e52b1fe4a --- /dev/null +++ b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_volume_low.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_volume_mute.xml b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_volume_mute.xml new file mode 100644 index 000000000..59b9e72e4 --- /dev/null +++ b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_volume_mute.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_volume_normal.xml b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_volume_normal.xml new file mode 100644 index 000000000..602cd2a64 --- /dev/null +++ b/patches/src/main/resources/youtube/swipecontrols/drawable/revanced_ic_sc_volume_normal.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/patches/src/main/resources/youtube/translations/ar/strings.xml b/patches/src/main/resources/youtube/translations/ar/strings.xml index f2271ae74..fead0585a 100644 --- a/patches/src/main/resources/youtube/translations/ar/strings.xml +++ b/patches/src/main/resources/youtube/translations/ar/strings.xml @@ -19,59 +19,150 @@ "لم يتم تثبيت %1$s. الرجاء تنزيل %2$s من الموقع." %s لم يتم تثبيته. الرجاء تثبيته. + إضافة إلى قائمة الانتظار + إضافة إلى قائمة الانتظار وفتح قائمة الانتظار + إضافة إلى قائمة الانتظار وتشغيل الفيديو + أداة التنزيل الخارجي + فتح قائمة الانتظار + قائمة الإنتظار + إزالة من قائمة الانتظار + إزالة من قائمة الانتظار وفتح قائمة الانتظار + إزالة قائمة الانتظار + حفظ قائمة الانتظار + "بدلاً من فتح برنامج تنزيل خارجي، افتح نافذة مدير قائمة الانتظار. + +يمكنك أيضًا فتح مدير قائمة الانتظار بالضغط باستمرار على زر الرجوع في شريط التنقل. + +هذه الميزة لا تزال قيد التطوير، لذا قد لا تعمل معظم الميزات. + +يرجى استخدامها لأغراض تصحيح الأخطاء فقط." + مطلوب تسجيل الدخول + مدير قائمة الانتظار غير متاح (%s). + تعذر التعرف على قائمة التشغيل + قائمة الانتظار فارغة + تعذر التعرف على الفيديو + فشل في إضافة الفيديو. + فشل في إنشاء قائمة الانتظار. + فشل في حذف قائمة الانتظار. + فشل في إزالة الفيديو. + فشل في حفظ قائمة الانتظار. + تم إضافة الفيديو بنجاح. + تم إنشاء قائمة الانتظار بنجاح. + تم حذف قائمة الانتظار بنجاح. + تم إزالة الفيديو بنجاح. + تم حفظ قائمة الانتظار بنجاح في \'%s\'. لغة RVX لغة التطبيق - العربيّة - Azerbaijani - Bulgarian - Bengali - Catalan - Czech - Danish - German - Greek - English - Spanish - Estonian - فارسى - Finnish - French - Gujarati - Hindi - Croatian - Hungarian - Indonesian - Italian - Japanese - Kazakh - Korean - Lithuanian - Latvian - Macedonian - Mongolian - Marathi - Malay - Burmese - Dutch - Odia - Punjabi - Polish - Portugese - Romanian - Russian - Slovak - Slovene - Serbian - Swedish - Swahili - Tamil - Telugu - Thai - Turkish - Ukrainian - Urdu - Vietnamese - Chinese + "الأمهرية +አማርኛ" + "العربية +العربية" + "الأذربيجانية +Azərbaycan" + "البيلاروسية +беларуская" + "البلغارية +Български" + "البنغالية +বাংলা" + "الكاتالونية +Català" + "التشيكية +Čeština" + "الدانماركية +Dansk" + "الألمانية +Deutsch" + "اليونانية +Ελληνικά" + "الإنجليزية +English" + "الأسبانية +Español" + "الإستونية +Eesti" + "الفارسية +فارسی" + "الفنلندية +Suomi" + "الفرنسية +Français" + "الغوجاراتية +ગુજરાતી" + "العبرية +עברי" + "الهندية +हिन्दी" + "الكرواتية +Hrvatski" + "المجرية +Magyar" + "الإندونيسية +Indonesia" + "الإيطالية +Italiano" + "اليابانية +日本語" + "الكازاخستانية +Қазақ тілі" + "الكورية +한국어" + "الليتوانية +Lietuvių" + "اللاتفية +Latviešu" + "المقدونية +Македонски" + "المنغولية +Монгол" + "الماراثية +मराठी" + "الملايو +Melayu" + "البورمية +ဗမာ" + "الهولندية +Nederlands" + "أوديا +ଓଡ଼ିଆ" + "البنجابية +ਪੰਜਾਬੀ" + "البولندية +Polski" + "البرتغالية +Português" + "الرومانية +Română" + "الروسية +Русский" + "السلوفاكية +Slovenčina" + "الألبانية +Shqip" + "السلوفينية +Slovenščina" + "الصربية +Српски" + "السويدية +Svenska" + "السواحيلية +Kiswahili" + "التاميلية +தமிழ்" + "التيلجو +తెలుగు" + "التايلاندية +ไทย" + "التركية +Türkçe" + "الأوكرانية +Українська" + "الأردية +اردو" + "الفيتنامية +Tiếng Việt" + "الصينية +中文" الإعلانات إخفاء لافتة شاشة المتجر النهائية @@ -275,34 +366,34 @@ قائمة بأسماء القائمة المنبثقة المراد تصفيتها، مفصولة بسطور جديدة. عامل تصفية الفيديو - إخفاء الفيديوهات بواسطة الكلمات الرئيسية أو المشاهدة. + إخفاء الفيديوهات بواسطة الكلمات المفتاحية أو المشاهدة. - تصفية الكلمات الرئيسية - إخفاء التعليقات بواسطة الكلمات الرئيسية + تصفية الكلمات المفتاحية + إخفاء التعليقات بواسطة الكلمات المفتاحية يتم تصفية التعليقات. لا يتم تصفية التعليقات. - إخفاء فيديوهات الصفحة الرئيسية بواسطة الكلمات الرئيسية + إخفاء فيديوهات الصفحة الرئيسية بواسطة الكلمات المفتاحية يتم تصفية الفيديوهات في موجز الصفحة الرئيسية. لا يتم تصفية الفيديوهات في موجز الصفحة الرئيسية. - إخفاء نتائج البحث عن طريق الكلمات الرئيسية + إخفاء نتائج البحث عن طريق الكلمات المفتاحية يتم تصفية نتائج البحث. لا يتم تصفية نتائج البحث. - إخفاء الفيديوهات الخاصة بالاشتراك عن طريق الكلمات الرئيسية + إخفاء الفيديوهات الخاصة بالاشتراك عن طريق الكلمات المفتاحية يتم تصفية الفيديوهات في موجز الاشتراكات. لا يتم تصفية الفيديوهات في موجز الاشتراكات. - الكلمات الرئيسية المراد إخفاؤها - "الكلمات والعبارات الرئيسية التي يجب إخفاؤها، مفصولة بأسطر جديدة. + الكلمات المفتاحية المراد إخفاؤها + "الكلمات والعبارات المفتاحية التي يجب إخفاؤها، مفصولة بأسطر جديدة. -يمكن أن تكون الكلمات الرئيسية عبارة عن أسماء قنوات أو أي نص يظهر في عناوين الفيديو. +يمكن أن تكون الكلمات المفتاحية عبارة عن أسماء قنوات أو أي نص يظهر في عناوين الفيديو. يجب إدخال الكلمات التي تحتوي على أحرف كبيرة في المنتصف باستخدام الأحرف الكبيرة (على سبيل المثال: iPhone، TikTok، LeBlanc)." - حول تصفية الكلمات الرئيسية - "الصفحة الرئيسية / الاشتراكات / يتم تصفية نتائج البحث لإخفاء المحتوى الذي يطابق عبارات الكلمات الرئيسية. + حول تصفية الكلمات المفتاحية + "الصفحة الرئيسية / الاشتراكات / يتم تصفية نتائج البحث لإخفاء المحتوى الذي يطابق عبارات الكلمات المفتاحية. القيود: • لا يمكن إخفاء فيديوهات Shorts حسب اسم القناة. • قد لا تكون بعض مكونات واجهة المستخدم مخفية. - • قد لا يؤدي البحث عن كلمة رئيسية إلى ظهور أية نتائج." + • قد لا يؤدي البحث عن كلمة مفتاحية إلى ظهور أية نتائج." مطابقة الكلمات كاملة سيؤدي وضع علامة اقتباس مزدوجة حول كلمة رئيسية/عبارة إلى منع التطابقات الجزئية لعناوين الفيديو وأسماء القنوات.<br><br>على سبيل المثال،<br><b>\"ai\"</b> سيخفي الفيديو: <b>How does AI work?</b><br>ولكن لن يخفي: <b>What does fair use mean?</b> لا يمكن استخدام الكلمة الرئيسية: %s. @@ -394,9 +485,6 @@ تعطيل تأثيرات الحركة تم تعطيل تأثيرات الحركة. تم تمكين تأثيرات الحركة. - تعطيل شريط الحالة الشفاف - شريط الحالة غير شفاف. - شريط الحالة معتم أو شفاف. تمكين شاشة التحميل المتدرجة تم تمكين شاشة التحميل المتدرجة الملونة. تم تعطيل شاشة التحميل المتدرجة الملونة. @@ -429,6 +517,23 @@ • فتح فيديوهات Shorts في المشغل العادي. • تنظيم الخلاصة حسب المواضيع والقنوات. • لا يمكن فتح وصف الفيديو عند إيقاف تشغيل Spoof' Streaming Data'." + تعطيل تحديثات التخطيط + لن يتم تحديث التخطيط بواسطة الخادم. + سيتم تحديث التخطيط بواسطة الخادم. + "يعود تصميم التطبيق إلى التصميم الذي كان عليه عند تثبيته لأول مرة. + +قد لا تعود بعض تصميمات الخادم إلى حالتها الأصلية. + +تشمل التغييرات ما يلي: +• قد لا تعمل المكونات في قائمة المشغل المنبثقة (أو الإعدادات ذات الصلة). +• لا تظهر الأرقام المتحركة. +• يتم استخدام علامة تبويب المكتبة. +• قد لا يعمل قسم الموسيقى في وصف الفيديو. +• قد لا يظهر زر تبديل الحساب في علامة تبويب المكتبة. استخدم إعداد 'تفعيل شريط البحث العريض في علامة التبويب أنت'." + تعطيل شريط الحالة الشفاف + شريط الحالة غير شفاف. + شريط الحالة معتم أو شفاف. + بالنسبة لبعض الـ ROM الخاصة بالشركة المصنعة التي تعمل بنظام Android 12 أو أحدث، قد يؤدي تمكين هذه الميزة إلى جعل شريط التنقل في النظام شفافًا. إصدار تطبيق وهمي تم تغيير اصدار التطبيق لم يتم تغيير اصدار التطبيق @@ -446,8 +551,10 @@ 18.33.40 - استعادة شريط إجراءات Shorts القديم 18.38.45 - استعادة سلوك جودة الفيديو الافتراضي القديم 18.48.39 - تعطيل تحديث المشاهدات والإعجابات في الوقت الفعلي + 19.01.34 - تعطيل تفاعل وصف الفيديو 19.26.42 - تعطيل أيقونة Cairo في شريط التنقل وشريط الأدوات 19.33.37 - استعادة لوحة التحكم القديمة لسرعة التشغيل + إصدار التطبيق الوهمي غير صالح: %s. قائمة الحساب إخفاء أو عرض العناصر في قائمة الحساب وعلامة التبويب أنت. @@ -483,6 +590,9 @@ تجاوز زر تنزيل الفيديو يفتح زر تنزيل الفيديو الأصلي أداة التنزيل الخارجية. يفتح زر تنزيل الفيديو أداة التنزيل الأصلية داخل التطبيق. + مدير قائمة الانتظار + يفتح زر تنزيل الفيديو الأصلي مدير قائمة الانتظار. + يفتح زر تنزيل الفيديو الأصلي أداة التنزيل الخارجية لديك. اسم حزمة تنزيل قائمة التشغيل اسم الحزمة لتطبيق التنزيل الخارجي المثبت لديك، مثل YTDLnis. @@ -536,7 +646,6 @@ تمكين شريط التنقل الشفاف شريط التنقل شفاف. شريط التنقل غير شفاف. - في بعض إصدارات YouTube، قد يؤدي هذا الإعداد إلى جعل شريط التنقل في النظام شفافًا أو قد يتم كسر التخطيط في وضع صورة داخل صورة. إخفاء شريط التنقل تم إخفاء شريط التنقل. يتم عرض شريط التنقل. @@ -1115,6 +1224,8 @@ انقر لكتم صوت الفيديو الحالي. انقر مرة أخرى لإلغاء الكتم. عرض زر التنزيل الخارجي انقر لتشغيل برنامَج التنزيل الخارجي. + مدير قائمة الانتظار + بدلًا من تشغيل برنامج تنزيل خارجي، افتح مدير قائمة الانتظار. عرض مربع زر حوار السرعة "انقر لفتح مربع حوار السرعة. انقر مع الاستمرار لضبط سرعة التشغيل على 1.0x. انقر مع الاستمرار مرة أخرى لإعادة ضبط السرعة الافتراضية." @@ -1414,7 +1525,6 @@ "تم تمكين الإجراءات المخصصة في القائمة المنبثقة. القيود: -• لا يعمل إذا تم تغيير إصدار التطبيق إلى 18.49.37 أو إصدار أقدم. • لا يعمل مع البث المباشر." تم تعطيل الإجراءات المخصصة في القائمة المنبثقة. تمكين الإجراءات المخصصة في شريط الأدوات @@ -1439,6 +1549,10 @@ عرض قائمة فتح الفيديو يتم عرض قائمة فتح الفيديو. تم إخفاء قائمة فتح الفيديو. + مربع حوار السرعة + عرض قائمة مربع حوار السرعة + يتم عرض قائمة حوار السرعة. + تم إخفاء قائمة حوار السرعة. حالة التكرار عرض قائمة حالة التكرار يتم عرض قائمة حالة التكرار. @@ -1482,10 +1596,14 @@ أدنى قيمة لإيماءة السطوع تعمل على تنشيط السطوع التلقائي. أدنى قيمة لإيماءة السطوع لا تعمل على تنشيط السطوع التلقائي. تمكين التحكم بالسطوع عن طريق إيماءة التمرير - تم تمكين التحكم بمستوى السطوع عن طريق الإيماءة. + "تم تمكين ميزة التمرير السريع لضبط سطوع الشاشة بالكامل. + + اضبط السطوع بالتمرير عموديًا على يسار الشاشة." تم تعطيل التحكم بمستوى السطوع عن طريق الإيماءة. تمكين التحكم بالصوت عن طريق إيماءة التمرير - تم تمكين التحكم بمستوى الصوت عن طريق الإيماءة. + "تم تمكين ميزة التمرير على كامل الشاشة للتحكم في مستوى الصوت. + +اضبط مستوى الصوت بالتمرير عموديًا على يمين الشاشة." تم تعطيل التحكم بمستوى الصوت عن طريق الإيماءة. تمكين حفظ واستعادة السطوع حفظ واستعادة السطوع عند الخروج أو الدخول إلى وضع ملء الشاشة. @@ -1499,10 +1617,20 @@ إيماءات التمرير في وضع قفل الشاشة تم تمكين إيماءات التمرير في وضع شاشة القفل. تم تعطيل إيماءات التمرير في وضع شاشة القفل. - شفافية خلفية واجهة إيماءة التمرير - شفافية خلفية واجهة التمرير. مقدار حد التمرير الحد الأقصى للتمرير قبل اكتشاف الإيماءة. + واجهة التمرير السريع البديلة + يتم استخدام واجهة المستخدم البديلة. + يتم استخدام واجهة المستخدم القديمة. + تمكين النمط البسيط + تم تمكين نمط الواجهة البسيط. + تم تعطيل نمط الواجهة الأدنى. + عرض الواجهة الدائرية + يتم عرض الواجهة الدائرية. + يتم عرض الواجهة الأفقية. + تعتيم خلفية واجهة التمرير السريع + قيمة الشفافية بين 0-100. + يجب أن تكون شفافية التمرير بين 0-100. حجم نص واجهة إيماءة التمرير حجم النص في واجهة التمرير. حجم واجهة إيماءة التمرير @@ -1536,12 +1664,43 @@ تلقائي الفيديو - سرعة التشغيل الافتراضية - جودة الفيديو الافتراضية على شبكة الجوَّال - جودة الفيديو الافتراضية على شبكة Wi-Fi + + الترميز تعطيل فيديو HDR تم تعطيل فيديو HDR. تم تمكين فيديو HDR. + تعطيل ترميز VP9 + "تم تعطيل برنامج ترميز VP9. + +معلومات: +• الحد الأقصى للدقة هو 1080P. +• سيستهلك تشغيل الفيديو بيانات إنترنت أكثر من VP9. +• لا يزال برنامج ترميز VP9 مستخدمًا لفيديو HDR." + تم تمكين ترميز VP9. + استبدال برنامج الترميز AV1 + يستبدل برنامج الترميز AV1 ببرنامج الترميز VP9. + + سرعة التشغيل + سرعة التشغيل الافتراضية + تذكر التغيرات في سرعة التشغيل + تنطبق تغييرات سرعة التشغيل على جميع الفيديوهات. + تنطبق تغييرات سرعة التشغيل على الفيديو الحالي فقط. + عرض ملاحظة + سيتم عرض ملاحظة عند تغيير سرعة التشغيل الافتراضية. + لن يتم عرض ملاحظة عند تغيير سرعة التشغيل الافتراضية. + سرعة التشغيل الافتراضية على فيديوهات Shorts + تذكر التغيرات في سرعة التشغيل على فيديوهات Shorts + "تنطبق تغييرات سرعة التشغيل على جميع فيديوهات Shorts. + +معلومات: +• الطريقة الوحيدة لتغيير سرعة التشغيل في مشغل فيديوهات Shorts هي استخدام \"مربع حوار السرعة\" ضمن \"الإجراءات المخصصة\". +• إذا لم تُضمّن تعديل 'Shorts components'، فلن يكون هذا متاحًا." + تنطبق تغييرات سرعة التشغيل على فيديو Shorts الحالي فقط. + عرض ملاحظة + سيتم عرض ملاحظة عند تغيير سرعة التشغيل الافتراضية في فيديوهات Shorts. + لن يتم عرض ملاحظة عند تغيير سرعة التشغيل الافتراضية في فيديوهات Shorts. + تغيير سرعة التشغيل الافتراضية إلى: %s. + تغيير سرعة تشغيل Shorts الافتراضية إلى: %s. تمكين سرعة التشغيل المخصصة تم تمكين سرعة التشغيل المخصصة. تم تعطيل سرعة التشغيل المخصصة. @@ -1550,62 +1709,59 @@ يتم استخدام القائمة المنبثقة بالمظهر القديم. تعديل سرعة التشغيل المخصصة إضافة أو تغيير سرعات التشغيل المتاحة. - تذكر التغيرات في سرعة التشغيل - تنطبق تغييرات سرعة التشغيل على جميع الفيديوهات. - تنطبق تغييرات سرعة التشغيل على الفيديو الحالي فقط. - عرض ملاحظة - سيتم عرض ملاحظة عند تغيير سرعة التشغيل الافتراضية. - لن يتم عرض ملاحظة عند تغيير سرعة التشغيل الافتراضية. + يجب أن تكون السرعات المخصصة أقل من %sx. + سرعات التشغيل المخصصة غير صالحة. + + جودة الفيديو + جودة الفيديو الافتراضية على شبكة الجوَّال + جودة الفيديو الافتراضية على شبكة Wi-Fi تذكر تغييرات جودة الفيديو تنطبق تغييرات الجودة على جميع الفيديوهات. تنطبق تغييرات الجودة على الفيديو الحالي فقط. عرض ملاحظة سيتم عرض ملاحظة عند تغيير جودة الفيديو الافتراضية. لن يتم عرض ملاحظة عند تغيير جودة الفيديو الافتراضية. + جودة فيديوهات Shorts الافتراضية على شبكة الجوَّال + جودة فيديوهات Shorts الافتراضية على شبكة Wi-Fi + تذكر تغييرات جودة فيديوهات Shorts + تنطبق تغييرات الجودة على جميع فيديوهات Shorts. + تنطبق تغييرات الجودة على فيديو Shorts الحالي فقط. + عرض ملاحظة + سيتم عرض ملاحظة عند تغيير جودة فيديوهات Shorts الافتراضية. + لن يتم عرض ملاحظة عند تغيير جودة فيديوهات Shorts الافتراضية. + الجوّال + Wi-Fi + تم تغيير جودة %1$s الافتراضية إلى: %2$s. + تم تغيير جودة Shorts %1$s الافتراضية إلى: %2$s. استعادة قائمة جودة الفيديو القديمة يتم عرض قائمة جودة الفيديو القديمة. لا يتم عرض قائمة جودة الفيديو القديمة. + تم تخطي التخزين المؤقت الذي تم تحميله مسبقًا. + تخطي التخزين المؤقت المحمل مسبقًا + "لتخطي التخزين المؤقت المحمل مسبقًا في بداية الفيديوهات لتطبيق جودة الفيديو الافتراضية على الفور. + +معلومات: +• عند بَدْء تشغيل الفيديو، يكون هناك تأخير قدره 0.3 ثانية تقريبًا. +• لا ينطبق على فيديوهات Shorts, فيديوهات HDR, فيديوهات البث المباشر, الفيديوهات التي تقل مدتها عن 15 ثوانٍ. +• يؤدي إعداد 'Spoof Streaming Data' أيضًا إلى إزالة المخازن المؤقتة المحملة مسبقًا، وبالتالي لا تكون هناك حاجة إلى هذا الإعداد إذا كنت تستخدم هذا الإعداد." + تشغيل هذا الإعداد قد يسبب مشاكل في تشغيل الفيديو. + عرض ملاحظة عند التخطي + يتم عرض الملاحظة. + لا يتم عرض الملاحظة. + إيهام أبعاد الجهاز + "يُغيّر أبعاد الجهاز إلى أقصى قيمة. + +معلومات: +• قد تكون الجودة العالية متاحة لبعض الفيديوهات التي تتطلب أبعاد جهاز عالية، ولكن ليس لجميعها. + +• لا يتوفر هذا الإعداد عند تمكين 'Spoof Streaming Data'." + تعطيل سرعة التشغيل للموسيقى تم تعطيل سرعة التشغيل الافتراضية للموسيقى. تم تمكين سرعة التشغيل الافتراضية للموسيقى. التحقق باستخدام الفئات سيتم تعطيل سرعة التشغيل الافتراضية إذا كانت فئة الفيديو هي موسيقى. تم تعطيل سرعة التشغيل الافتراضية لمقاطع الفيديو القابلة للتشغيل على YouTube Music. - تمكين سرعة التشغيل الافتراضية لفيديوهات Shorts - تنطبق سرعة التشغيل الافتراضية على Shorts. - لا تنطبق سرعة التشغيل الافتراضية على Shorts. - تم تخطي التخزين المؤقت الذي تم تحميله مسبقًا. - تخطي التخزين المؤقت المحمل مسبقًا - "لتخطي التخزين المؤقت المحمل مسبقًا في بداية الفيديوهات لتطبيق جودة الفيديو الافتراضية على الفور. - -• عند بَدْء تشغيل الفيديو، يكون هناك تأخير قدره 0.3 ثانية تقريبًا. -• لا ينطبق على فيديوهات HDR, فيديوهات البث المباشر, الفيديوهات التي تقل مدتها عن 15 ثوانٍ." - تشغيل هذا الإعداد قد يسبب مشاكل في تشغيل الفيديو. - عرض ملاحظة عند التخطي - يتم عرض الملاحظة. - لا يتم عرض الملاحظة. - إيهام أبعاد الجهاز - "يوهم أبعاد الجهاز من أجل فتح جودة فيديو أعلى قد لا تكون متوفرة على جهازك. -قد يتم توفير الجودة العالية في بعض الفيديوهات التي تتطلب أبعادًا عالية للجهاز، ولكن ليس كل الفيديوهات." - تعطيل ترميز VP9 - "تم تعطيل برنامج ترميز VP9. - -• الحد الأقصى للدقة هو 1080P. -• سيستهلك تشغيل الفيديو بيانات إنترنت أكثر من VP9. -• لا يزال برنامج ترميز VP9 مستخدمًا لفيديو HDR." - تم تمكين ترميز VP9. - استبدال برنامج الترميز AV1 - يستبدل برنامج الترميز AV1 ببرنامج الترميز VP9. - رفض استجابة برنامج الترميز AV1 - "يرفض قسرًا استجابة برنامج ترميز AV1. -سيتم تطبيق برنامج ترميز مختلف بعد حوالي 20 ثانية من التخزين المؤقت." - تؤدي العملية الاحتياطية إلى حوالي 20 ثانية من التخزين المؤقت. - تغيير السرعة الافتراضية إلى %s. - تغيير جودة بيانات الجوّال الافتراضية إلى %s. - فشل في تعيين جودة الفيديو. - تغيير جودة Wi-Fi الافتراضية إلى %s. - يجب أن تكون السرعات المخصصة أقل من %sx. - سرعات التشغيل المخصصة غير صالحة. Return YouTube Dislike تمكين Return YouTube Dislike @@ -1735,6 +1891,7 @@ عرض زر التخطي عرض في شريط تقدم الفيديو تعطيل + الشفافية: اللون: تم تغيير اللون. إعادة ضبط اللون. @@ -1801,6 +1958,8 @@ اعتراض تغيير الفئة لا توجد مقاطع للتصويت عليها. + + %1$s إلى %2$s اختر فئة المقطع الفئة معطلة في الإعدادات. تمكين الفئة للإرسال. مقطع SponsorBlock جديد @@ -1920,8 +2079,8 @@ Android VR "Android VR (بدون مصادقة)" - "iOS -(يتطلب PoToken)" + "iOS +(مُهمَل)" "iOS TV (يتطلب تسجيل الدخول)" التأثيرات الجانبية للتزييف @@ -1929,10 +2088,20 @@ • مستوى الصوت الثابت غير متاح. • لا يتوفر تعطيل المقطع الصوتي التلقائي المفروض. • قد لا يتم تشغيل الفيديوهات المخصصة للأطفال عند تسجيل الخروج أو في وضع التصفح المتخفي." - • قد تكون هناك مشكلات في التشغيل (يتطلب PoToken). + • قد يتم حظر نطاقات ASNs/IP بالكامل بواسطة الخادم. "• مستوى الصوت الثابت غير متوفر. • قد لا يتم تشغيل الأفلام أو الفيديوهات المدفوعة. • قد لا يتم تشغيل الفيديوهات المخصصة للأطفال عند تسجيل الخروج أو في وضع التصفح المتخفي." + استخدام عميل iOS + "تمت إضافة عميل iOS إلى العملاء المتاحين. + +تحذير: عميل iOS قديم. أي مشاكل قد تظهر أثناء استخدامه تكون على مسؤوليتك الخاصة." + لم يتم إضافة عميل iOS إلى العملاء المتاحين. + "عند طلب YouTube API Endpoints باستخدام iOS، ستحتاج إلى رموز المصادقة الصادرة عن جهاز iOS ورموز PoTokens الصادرة عن iOSGuard. + +هذا يعني أن طلبات البث عبر iOS ستفتقد رموز المصادقة ورموز PoTokens، وقد يعتبر الخادم المستخدم برنامجًا آليًا بوت ويحظر نطاق ASN/IP بالكامل. + +استخدمه على مسؤوليتك الخاصة!" فرض iOS AVC (H.264) يتم فرض ترميز الفيديو على AVC (H.264). يتم تحديد ترميز الفيديو تلقائيًا. @@ -1952,6 +2121,8 @@ AVC لديه حد أقصى للدقة 1080p، لا يتوفر ترميز الص يتم عرض العميل المستخدم لجلب بيانات البث في إحصاءات تقنية. تم إخفاء العميل المستخدم لجلب بيانات البث في إحصاءات تقنية. لغة البث الصوتي الافتراضية للواقع الافتراضي VR + لم يتمكن من جلب أي تدفقات عميل. + ربما لم تتمكن من تسجيل الدخول. PoToken / VisitorData استخدام PoToken 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 5d43133aa..7674cc0c9 100644 --- a/patches/src/main/resources/youtube/translations/bg-rBG/strings.xml +++ b/patches/src/main/resources/youtube/translations/bg-rBG/strings.xml @@ -18,59 +18,9 @@ "%1$s не е инсталиран. Моля, изтеглете %2$s от уебсайта." %s не е инсталирано. Моля инсталирайте го. + Добави към опашката RVX език Език на приложението - Арабски - Азербайджански - Български - Бенгалски - Каталонски - Чешки - Датски - Немски - Гръцки - Английски - Испански - Естонски - Персийски - Финландски - Френски - Гуджарати - Хинди - Хърватски - Унгарски - Индонезийски - Италиански - Японски - Казахстански - Корейски - Литовски - Латвийски - Македонски - Монголски - Маратхи - Малайски - Бирмански - Холандски - Ория - Пенджабски - Полски - Португалски - Румънски - Руски - Словашки - Словенски - Сръбски - Шведски - Суахили - Тамилски - Телугу - Тайландски - Турски - Украински - Урду - Виетнамски - Китайски Реклами Скриване на рекламите в режим на цял екран @@ -371,9 +321,6 @@ Анимация при стартиране на приложението Новата начална анимация е изключена. Новата начална анимация е включена. - Деактивирайте полупрозрачната лента на прогреса - Лентата на прогреса е непрозрачна. - Лентата на прогреса е непрозрачна или полупрозрачна. Градиентен екрана за зареждане Екранът за зареждане с градиент е активиран. Екранът за зареждане с градиент е деактивиран. @@ -388,6 +335,9 @@ Действие на кликване върху точка за предаване на живо "Каналът се отваря, когато се щракне върху пръстена на живо." Потокът на живо се отваря, когато се щракне върху пръстена на живо. + Деактивирайте полупрозрачната лента на прогреса + Лентата на прогреса е непрозрачна. + Лентата на прогреса е непрозрачна или полупрозрачна. Променете версията на приложението Подправена версия Не подправена версия @@ -1379,10 +1329,10 @@ Когато намалите яркостта с жест до минимум, се активира автоматична яркост. Когато намалите яркостта с жест до минимум, автоматичната яркост Не се активира. Задаване на яркост чрез плъзгане - Задаването на яркост чрез плъзгане е включено. + "Задаването на яркост чрез плъзгане е включено." Задаването на яркост чрез плъзгане е изключено. Настройване на звука чрез плъзгане - Настройването на звука чрез плъзгане е включено. + "Настройването на звука чрез плъзгане е включено." Настройването на звука чрез плъзгане е изключено. Вкл. запазване и възстановяване на яркост Запазване и възстаовяване яркостта при включване или изключване на цял екран. @@ -1396,8 +1346,6 @@ Плъзне в режим Заключен екран Жестовете за плъзгане са активирани в режим „Заключен екран“. Жестовете за плъзгане са деактивирани в режим „Заключен екран“. - Видимост на фона на плъзгащите контроли - Видимостта на фона на плъзгащите контроли. Праг на величината на плъзгане Амплитудата на движение, разпозната като жест. Размер на текста при плъзгане @@ -1431,12 +1379,26 @@ Авто Видео - Скорост на възпроизвеждане по подразбиране - Предпочитано качество при мобилни данни - Предпочитано качество при Wi-Fi + Изкл. HDR клипове HDR клиповете са изключени. HDR клиповете са включени. + Деактивирайте кодека VP9 + "Кодек VP9 е деактивиран. +• Максималната разделителна способност е 1080p. +• Възпроизвеждането на видео ще използва повече интернет данни от VP9. +• За да получите възпроизвеждане на HDR, HDR видеото все още използва кодека VP9." + VP9 кодек е включен. + Сменете софтуерния кодек AV1 + Заменя софтуерния кодек AV1 с кодека VP9. + + Скорост на възпроизвеждане по подразбиране + Запомнете промените в скоростта на възпроизвеждане + Промените в скоростта на възпроизвеждане се отнасят за всички видеоклипове. + Промените в скоростта на възпроизвеждане се отнасят само за текущия видеоклип. + Покажи съобщение + При промяна на скоростта на възпроизвеждане по подразбиране в долната част на екрана се появява съобщение. + Няма съобщение в долната част на екрана при промяна на скоростта на възпроизвеждане по подразбиране. Вкл. на скорост на видеото по избор Скоростта по избор на видеото е включена. Скоростта по избор на видеото е изключена. @@ -1445,12 +1407,11 @@ Използва се падащ панел в стар стил. Редактиране на скоростите по избор на видеото Добавяне или смяна на възможните скорости. - Запомнете промените в скоростта на възпроизвеждане - Промените в скоростта на възпроизвеждане се отнасят за всички видеоклипове. - Промените в скоростта на възпроизвеждане се отнасят само за текущия видеоклип. - Покажи съобщение - При промяна на скоростта на възпроизвеждане по подразбиране в долната част на екрана се появява съобщение. - Няма съобщение в долната част на екрана при промяна на скоростта на възпроизвеждане по подразбиране. + Скоростите по избор трябва да са по-малки от %sx. Връщане на стойностите по подразбиране. + Невалидна скорост на видеото. Връщане на стойности по подразбиране. + + Предпочитано качество при мобилни данни + Предпочитано качество при Wi-Fi Запомнете промените в качеството на видеото Промените в качеството се отнасят за всички видеоклипове. Промените в качеството се отнасят само за текущия видеоклип. @@ -1460,15 +1421,6 @@ Възстановете старото меню за качество на видеото Показва се старото меню за видео качество. Старото меню за видео качество е скрито. - Деактивирайте скоростта на възпроизвеждане за музика - Скоростта на възпроизвеждане по подразбиране е деактивирана за музика. - Скоростта на възпроизвеждане по подразбиране е активирана за музика. - Изберете скорост въз основа на категориите - Скоростта по подразбиране за категорията YouTube Music е деактивирана. - Скоростта по подразбиране за категорията YouTube Music е активирана. - Променете скоростта на възпроизвеждане на Shorts - Скоростта на възпроизвеждане по подразбиране се прилага за Shorts. - Скоростта на възпроизвеждане по подразбиране не се прилага за Shorts. Пропуснат предварително зареден буфер. Пропусни предварително зареден буфер "Пропуска предварително заредения буфер в началото на видеоклиповете, за да приложи незабавно качеството на видеото по подразбиране. @@ -1481,25 +1433,13 @@ Уведомлението се показва. Уведомлението е скрито. Лъжливи параметри на устройството - "Преоразмерява вашето устройство, за да покже видеоклипове с по-високо качество, които може да не са налични на вашето устройство." - Деактивирайте кодека VP9 - "Кодек VP9 е деактивиран. -• Максималната разделителна способност е 1080p. -• Възпроизвеждането на видео ще използва повече интернет данни от VP9. -• За да получите възпроизвеждане на HDR, HDR видеото все още използва кодека VP9." - VP9 кодек е включен. - Сменете софтуерния кодек AV1 - Заменя софтуерния кодек AV1 с кодека VP9. - Отхвърлете софтуерния кодек AV1 - "Принудително отхвърляне на софтуерния кодек AV1 -След приблизително 20 секунди буфериране ще бъде приложен друг кодек." - Буфериране поради софтуерен кодек Av1 (прибл. 20 сек.). - Смяна на скоростта на видеото на %s. - Смяна на качеството при мобилни данни на %s. - Грешка при настройка на качеството на видеото. - Смяна на качеството при Wi-Fi на%s. - Скоростите по избор трябва да са по-малки от %sx. Връщане на стойностите по подразбиране. - Невалидна скорост на видеото. Връщане на стойности по подразбиране. + + Деактивирайте скоростта на възпроизвеждане за музика + Скоростта на възпроизвеждане по подразбиране е деактивирана за музика. + Скоростта на възпроизвеждане по подразбиране е активирана за музика. + Изберете скорост въз основа на категориите + Скоростта по подразбиране за категорията YouTube Music е деактивирана. + Скоростта по подразбиране за категорията YouTube Music е активирана. Return YouTube Dislike (показва нехаресванията) Вкл. на Return YouTube Dislike @@ -1695,6 +1635,7 @@ Отрицателен вот Промяна на категорията Няма сегменти, за които да гласувате. + Изберете категорията на частта Категорията е изкл. в настройките. Вкл. я за да можете да изпратите. Нова част в SponsorBlock @@ -1803,8 +1744,6 @@ Android VR "Android VR (Няма удостоверение)" - "iOS -(Необходим PoToken)" "iOS (Необходим вход)" Ефекти от замяната @@ -1812,7 +1751,6 @@ • Не е наличен стабилен звук. • Деактивиране на принудителни автоматични аудио записи не е налично. • Детските видеоклипове може да не се възпроизвеждат, когато сте излезли или сте в режим инкогнито." - • Възможно е да има проблеми с възпроизвеждането (необходим е PoToken). "• Филми или платени видеоклипове може да не се възпроизвеждат. • Детските видеоклипове може да не се възпроизвеждат, когато сте излезли или сте в режим инкогнито." Принудително AVC (H.264) за iOS 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 00a572af5..530305072 100644 --- a/patches/src/main/resources/youtube/translations/de-rDE/strings.xml +++ b/patches/src/main/resources/youtube/translations/de-rDE/strings.xml @@ -2,7 +2,7 @@ Bedienungshilfen für den Video-Player aktivieren? - Ihre Steuerungen wurden angepasst, da ein Barrierefreiheitsdienst aktiviert ist. + Ihre Steuerung wurde geändert, da ein Barrierefreiheitsdienst aktiv ist. RVX Suche %s @@ -19,12 +19,107 @@ "%1$s ist nicht installiert. Bitte lade %2$s von der Webseite herunter." %s ist nicht installiert. Bitte installieren. + Zur Warteschlange hinzufügen + Zur Warteschlange hinzufügen & öffnen + Zur Warteschlange hinzufügen und Video abspielen + Externer Downloader + Warteschlange öffnen + Warteschlange + Aus der Warteschlange entfernen + Aus der Warteschlange entfernen und die Warteschlange öffnen + Warteschlange löschen + Warteschlange speichern + "Öffnen Sie statt eines externen Downloaders den Warteschlangenmanager. + +Sie können den Warteschlangenmanager auch öffnen, indem Sie die Zurück-Taste in der Navigationsleiste gedrückt halten. + +Diese Funktion befindet sich noch in der Entwicklung, daher sind einige Funktionen möglicherweise nicht verfügbar. + +Bitte verwenden Sie sie nur zu Debugging-Zwecken." + Anmeldung erforderlich + Warteschlange Manager nicht verfügbar (%s). + Playlist konnte nicht identifiziert werden + Warteschlange ist leer + Video konnte nicht identifiziert werden + Video konnte nicht hinzugefügt werden. + Fehler beim Erstellen der Warteschlange. + Fehler beim Löschen der Warteschlange. + Fehler beim Entfernen des Videos. + Fehler beim Speichern der Warteschlange. + Video erfolgreich hinzugefügt. + Warteschlange erfolgreich erstellt. + Warteschlange erfolgreich gelöscht. + Video erfolgreich entfernt. + Warteschlange erfolgreich in \'%s \' gespeichert. RVX Sprache + App Sprache + "Amharic +አማርኛ" + "Arabisch +العربية" + "Azerbaijani +Azərbaycan" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" Werbung + Endbild-Banner ausblenden + Store-Banner ist ausgeblendet. + Store-Banner wird angezeigt. Vollbildwerbung verstecken Vollbildwerbung wird versteckt. Vollbildwerbung wird angezeigt. + Vollbildwerbung wurde geschlossen. Allgemeine Werbung ausblenden Allgemeine Werbung ist ausgeblendet. Allgemeine Werbung wird angezeigt. @@ -53,7 +148,7 @@ Bitte lade %2$s von der Webseite herunter." Web Suchergebnisse sind versteckt. Web Suchergebnisse werden angezeigt. YouTube Premium Werbung ausblenden - YouTube Premium Werbung wird ausgeblendet. + YouTube Premium Werbung wird versteckt. YouTube Premium Werbung wird angezeigt. Alternatives Vorschaubild @@ -103,6 +198,13 @@ Tippen Sie hier, um mehr über DeArrow zu erfahren." Schaltfläche \"Untertitel\" ist versteckt. Schaltfläche \"Untertitel\" wird angezeigt. Karussellregal ausblenden + "Versteckt folgende Abschnitte: +- Aktuelle Nachrichten +- Weiterschauen +- Entdecke mehr Kanäle +- Shopping +- Erneut anschauen" + Karussellregale werden angezeigt. Chips-Abschnitt verstecken Chips-Abschnitt wird versteckt. Chips-Abschnitt wird angezeigt @@ -165,10 +267,10 @@ Tippen Sie hier, um mehr über DeArrow zu erfahren." In den Suchergebnissen angezeigt. Kanal Profil - Komponenten im Kanalprofil ausblenden oder anzeigen. + Elemente im YouTube-Einstellungsmenü verstecken Aktivieren der Benutzerdefinierten Filter - Kanal-Tab-Filter ist aktiviert. - Kanal-Tab-Filter ist deaktiviert. + Benutzerdefinierter Filter ist aktiviert + Benutzerdefinierter Filter ist deaktiviert Kanal-Tab-Filter Liste der zu filternden Kanalnamen, getrennt durch Zeilenumbrüche. "Verkürzt @@ -182,7 +284,7 @@ Store" Links am oberen Rand des Kanalprofils werden angezeigt. Für dich ausblenden Chips-Abschnitt wird versteckt. - \'For You\' shelves are shown. + Für Sie ist Regal gezeigt. Shop-Button ausblenden Die Schaltfläche Store durchsuchen wird ausgeblendet Die Schaltfläche Store durchsuchen wird angezeigt @@ -204,6 +306,9 @@ Store" Feed Flyout Menüfilter aktivieren Feed Flyout Menüfilter ist aktiviert. Feed Flyout-Menüfilter ist deaktiviert. + Feed Flyout Menü Filtertyp + Filter, wenn vorhanden.<br><br>Um die <b>\'Nächste in der Warteschlange abspielen\'</b> Menü zu verbergen, Sie können <b>\'Wiedergabe weiter\'</b> oder <b>\'in der Warteschlange\'</b> als Schlüsselwörter verwenden. + Filtern wenn zutreffend.<br><br>Um die <b>\'Nächste in der Warteschlange abspielen\' zu verstecken</b> Menü, Sie können nur <b>\'Wiedergabe als nächstes in der Warteschlange\'</b> als Schlüsselwörter verwenden. Feed Flyout Menüfilter Liste der Filter für das Account Menü, durch Zeilenumbrüche getrennt. @@ -284,8 +389,11 @@ Wenn sich das Layout des Wiedergabebildschirms aufgrund serverseitiger Änderung Allgemein Startseite ändern Standard + Alle Abonnements Kanäle durchstöbern + Kurse / Lernen Entdecken + Mode & Schönheit Spiele Verlauf Bibliothek @@ -293,12 +401,19 @@ Wenn sich das Layout des Wiedergabebildschirms aufgrund serverseitiger Änderung Live Filme Musik + Neuigkeiten + Benachrichtigungen + Wiedergabelisten + Podcasts Suchen + Einkaufen Shorts Sport Abonnements Beliebt + Virtuelle Realität Später ansehen + Ihre Clips Startseitentyp ändern "Startseite ändert sich immer. @@ -325,6 +440,44 @@ Einschränkung: Zurück-Taste in der Symbolleiste funktioniert möglicherweise n Diskretion des Betrachters entfernen "Entfernt den Diskretionsdialog des Betrachters. Dies umgeht nicht die Altersbeschränkung. Es akzeptiert ihn nur automatisch." + Live-Ring Klick-Aktion ändern + "Der Kanal öffnet sich, wenn der Live-Ring angeklickt wird. + +Einschränkung: Wenn der Live-Stream der Shorts im regulären Player geöffnet wird, wird der Channel nicht geöffnet." + Der Live-Stream wird geöffnet, wenn der Live-Ring angeklickt wird. + Anordnungsformfactor + Standard + Telefon + Telefon (max. 480 dp) + Tablet + Tablet (min. 600 dp) + Automobil + "Änderungen umfassen: + +Tablet Layout +• Community Beiträge sind ausgeblendet. + +Automotive Layout +• Shorts werden im regulären Player geöffnet. +• Der Feed ist nach Themen und Kanälen organisiert. +• Die Videobeschreibung kann nicht geöffnet werden, wenn 'Streaming Daten fälschen' deaktiviert ist." + Layoutaktualisierungen deaktivieren + Layout wird vom Server nicht aktualisiert. + Layout wird vom Server aktualisiert. + "Das App-Layout wird auf das Layout zurückgesetzt, das bei der ersten Installation verwendet wurde. + +Einige serverseitige Layouts werden möglicherweise nicht zurückgesetzt. + +Änderungen umfassen: +• Komponenten im Player-Ausklappmenü (oder zugehörige Einstellungen) funktionieren möglicherweise nicht. +• Rollende Zahlen sind nicht animiert. +• Der Tab Mediathek wird verwendet. +• Der Musikbereich in der Videobeschreibung funktioniert möglicherweise nicht. +• Die Kontowechsel-Schaltfläche wird möglicherweise nicht im Mediathek Tab angezeigt. Verwende die Einstellung Breite Suchleiste im 'Du' Tab aktivieren." + Transparente Statusleiste deaktivieren + Statusleiste ist undurchsichtig. + Die Statusleiste ist undurchsichtig oder durchscheinend. + Bei einigen HerstellerROMs mit Android 12+ kann das Aktivieren dieser Funktion die Navigationsleiste des Systems transparent machen. Spoof App Version Version gefälscht Version nicht gefälscht @@ -342,12 +495,19 @@ Wenn später ausgeschaltet wird empfohlen, die App-Daten zu löschen, um UI-Fehl 18.33.40 - Alte Shorts Aktionsleiste wiederherstellen 18.38.45 - Altes Standard-Video-Qualitätsverhalten wiederherstellen 18.48.39 - Deaktiviert Ansichten und gefällt es, in Echtzeit aktualisiert zu werden + 19.01.34 - Videobeschreibungsinteraktion deaktivieren + 19.26.42 - Kairo-Symbol in der Navigationsleiste und der Symbolleiste deaktivieren + 19.33.37 - Alte Wiedergabegeschwindigkeits-Flyout-Anzeige wiederherstellen + Ungültige Spoof App Version: %s. Account Menü Zeige oder verstecke Element im Account Menü und mein YouTube. Account Menü verstecken "Elemente des Account Menüs und Mein YouTube. Manche Komponenten könnten nicht versteckt werden." + Kontomenü Filtertyp + Filter, wenn vorhanden.<br><br>Um die <b>\'Nächste in der Warteschlange abspielen\'</b> Menü zu verbergen, Sie können <b>\'Wiedergabe weiter\'</b> oder <b>\'in der Warteschlange\'</b> als Schlüsselwörter verwenden. + Filtern wenn zutreffend.<br><br>Um die <b>\'Nächste in der Warteschlange abspielen\' zu verstecken</b> Menü, Sie können nur <b>\'Wiedergabe als nächstes in der Warteschlange\'</b> als Schlüsselwörter verwenden. Konto-Menüfilter bearbeiten Liste der Filter für das Account Menü, durch Zeilenumbrüche getrennt. Verstecke Handle @@ -367,8 +527,18 @@ Manche Komponenten könnten nicht versteckt werden." Hook Buttons Überschreibt die Klick-Aktion von In-App-Tasten. - Download-Button + Herunterladen-Button + Herunterladen-Button der Playlist überschreiben + Der native Playlist-Download-Button wird immer angezeigt, und in öffentlichen Playlists öffnet er deinen externen Downloader. + Der native Playlist-Download-Button, falls angezeigt, öffnet den nativen In-App-Downloader. Überschreibe Video-Download-Button + Der Native Video-Download-Button öffnet Ihren externen Download. + Nativer Video-Download-Button öffnet den nativen In-App Downloader. + Warteschlangen Manager + Nativer Video-Download-Button öffnet den Warteschlangenmanager. + Der Native Video-Download-Button öffnet Ihren externen Download. + Playlist Downloader Paketname + Paketname Ihrer installierten externen Downloader-App, wie z. B. YTDLnis. YouTube Musik-Button überschreiben YouTube Music Button öffnet RVX Music. @@ -379,6 +549,7 @@ Manche Komponenten könnten nicht versteckt werden." Warnung %s ist nicht installiert. Bitte installieren. Voraussetzung + YouTube Musik wird benötigt, um die Schaltfläche zu überschreiben. Tippen Sie hier, um YouTube Musik herunterzuladen. Navigationsleiste Komponenten der Navigationsleiste ausblenden oder anzeigen. @@ -416,6 +587,9 @@ Hinweis: Durch Aktivieren dieser Option wird auch die Videowerbung zwangsweise a Auch Werbung wird nicht mehr in Shorts blockiert. Wenn diese Einstellung nicht wirksam ist, versuchen Sie in den Inkognito-Modus zu wechseln." + Transparente Navigationsleiste aktivieren + Navigationsleiste ist transparent. + Navigationsleiste ist nicht transparent. Navigationsleiste verstecken Navigationsleiste ist ausgeblendet. Navigationsleiste wird angezeigt. @@ -434,6 +608,9 @@ Wenn diese Einstellung nicht wirksam ist, versuchen Sie in den Inkognito-Modus z \"Niedrigerer Datenverbrauch\" ausblenden \"Niedrigerer Datenverbrauch\" wird ausgeblendet. \"Niedrigerer Datenverbrauch\" wird angezeigt. + Autoplay oder Wiedergabemenü ausblenden + Autoplay oder Wiedergabemenü ist ausgeblendet. + Autoplay oder Wiedergabemenü wird angezeigt. Einstellungen für Videoqualität ausblenden Einstellungen für Videoqualität wird ausgeblendet. Einstellungen für Videoqualität wird angezeigt. @@ -480,9 +657,25 @@ Wenn diese Einstellung nicht wirksam ist, versuchen Sie in den Inkognito-Modus z \"Über\" wird ausgeblendet. \"Über\" wird angezeigt. + Snackbar + Komponenten im Zusammenhang mit Snackbar ausblenden oder ändern. Snackbar verstecken Snackbar ist versteckt Snackbar wird angezeigt + Serverseitige Snackbar ausblenden + Die Snackbar auf dem Server ist ausgeblendet. + Die Snackbar auf dem Server wird angezeigt. + Snackbar Thema umkehren + Das Thema der Snackbar ist umgekehrt. + Das Thema der Snackbar ist nicht invertiert. + Hintergrund der serverseitigen Snackbar ändern + Die Hintergrundfarbe der serverseitigen Snackbar hat sich geändert. + Die Hintergrundfarbe der serverseitigen Snackbar hat sich nicht geändert. + "Einige Snackbars verwenden ein auf der Serverseite definiertes Theme und nicht das App-Theme. + +Ändern Sie die Hintergrundfarbe dieser Snackbars. + +Bei serverseitigen Änderungen kann sich die Hintergrundfarbe der Snackleiste nicht ändern." Werkzeugleiste Verstecke oder ändere Toolbar-Komponenten, wie die Suchleiste, Buttons und Header. @@ -496,6 +689,15 @@ Wenn diese Einstellung nicht wirksam ist, versuchen Sie in den Inkognito-Modus z Breite Suchleiste enthält YouTube Header Breite Suchleiste enthält nicht YouTube Header. Aktiviere breite Suchleiste in deinem Tab + "Die breite Suchleiste ist in der Registerkarte Sie aktiviert. + +Um auf die Einstellungen zuzugreifen, benutzen Sie bitte den folgenden Pfad: +Registerkarte → Kanal → Einstellungen anzeigen" + Die breite Suchleiste ist im Register You deaktiviert. + "Wenn Sie diese Einstellung aktivieren, werden die Einstellungen im Tab \"Einstellungen\" deaktiviert. + +In diesem Fall Sie müssen den folgenden Pfad verwenden, um auf die Einstellungen zuzugreifen: +Registerkarte → Kanal → Menü → Einstellungen anzeigen" Verstecke Erstellen Schaltfläche Cast Button ist versteckt. Der Cast button wird angezeigt. @@ -534,6 +736,11 @@ Tippe und halte zum Öffnen der RVX-Einstellungen." Die Transparenz der Spieler-Überlagerung muss zwischen 0-100 liegen. Zurückgesetzt auf Standardwerte. Mix-Playlists deaktivieren Mix-Playlists sind deaktiviert. + "Mix-Wiedergabelisten automatisch schalten, wenn das Autoplay eingeschaltet ist. + +Autoplay kann in den YouTube-Einstellungen geändert werden: +Einstellungen → Autoplay / Wiedergabe → Nächstes Video Autoplay" + Aktivieren dieser Funktion deaktiviert den automatischen Wechsel zu YouTube-Mix, wenn Musik abgespielt wird, während Autoplay eingeschaltet ist. Player-Popup-Panels deaktivieren Auto-Player-Popup-Panels sind deaktiviert. Auto-Player-Popup-Panels sind deaktiviert. @@ -594,6 +801,9 @@ Einstellungen → Autoplay → Nächstes Video automatisch abspielen" Zoom-Overlay ausblenden Zoom-Overlay ist ausgeblendet. Zoom-Overlay wird angezeigt. + Video-Untertitel säubern + "Phrasen wie '#', 'Fundraiser', 'Shop' und 'products' sind vor den Videountertiteln versteckt." + "Ausdrücke wie '#', 'Fundraiser', 'Shop' und 'products' werden aus den Video-Untertiteln gezeigt." Aktionsschaltflächen Aktionsschaltflächen unter Videos ausblenden oder anzeigen. @@ -631,6 +841,18 @@ Einstellungen → Autoplay → Nächstes Video automatisch abspielen" \"Danke\" Schaltfläche wird versteckt. \"Danke\" Schaltfläche wird angezeigt. + Aktionsbutton nach Index ausblenden + "Aktionstasten werden durch den Index versteckt. + +Info: +• Falsche Aktionstasten können ausgeblendet werden oder Aktionstasten können nicht ausgeblendet werden. +• Verstecke Aktionstasten lassen keinen leeren Platz." + "Aktionstasten werden durch Bezeichnerfilter ausgeblendet. + +Info: +• Knöpfe mit rechter Aktion werden ausgeblendet. +• Verstecke Aktionstasten lassen Leerzeichen." + Remix-Schaltflächenindex Ambient-Modus Ambient-Modus deaktivieren oder Einschränkungen des Ambient-Modus umgehen. @@ -670,12 +892,24 @@ Einstellungen → Autoplay → Nächstes Video automatisch abspielen" Verstecke das Erstellen der Short-Schaltfläche Erstelle Short-Schaltfläche ist ausgeblendet. Erstelle Short-Schaltfläche wird angezeigt. + Emoji und Zeitstempel ausblenden + Emoji und Zeitstempeltasten sind ausgeblendet. + Emoji und Zeitstempeltasten werden angezeigt. + Markierte Suchlinks ausblenden + Hervorgehobene Suchlinks sind versteckt. + Hervorgehobene Suchlinks werden angezeigt. Live-Chat-Nachrichten verbergen Live-Chat-Nachrichten sind ausgeblendet.\n\nDiese Einstellung gilt auch für Shorts Live-Videos. Live-Chat-Nachrichten werden angezeigt.\n\nDiese Einstellung gilt auch für Shorts Live-Videos. + Live-Chat-Zusammenfassung ausblenden + Zusammenfassung des Live-Chats ist ausgeblendet. + Zusammenfassung des Live-Chats wird angezeigt. Verstecke Vorschau-Kommentar Vorschau-Kommentar ist versteckt Vorschau-Kommentar wird angezeigt + Vorschaukommentyp ausblenden + Dies ändert nicht die Größe des Abschnitts der Kommentare, daher ist es möglich, die Live-Chat-Wiederholung im Kommentarbereich zu öffnen. + Dies ändert die Größe des Kommentarbereichs, so dass es unmöglich ist, eine Live-Chat-Wiederholung im Kommentarbereich zu öffnen. Verstecke \"Danke\" Schaltfläche \"Danke\" Schaltfläche wird versteckt. \"Danke\" Schaltfläche wird angezeigt. @@ -683,6 +917,8 @@ Einstellungen → Autoplay → Nächstes Video automatisch abspielen" Flyout Menü Komponenten des Flyout-Menüs im Feed verstecken oder anzeigen. Toggle Typ ändern + Textschalter werden verwendet. + Schalter werden verwendet. 1080p Premium ausblenden 1080p Premium wird ausgeblendet. 1080p Premium wird angezeigt. @@ -692,6 +928,7 @@ Einstellungen → Autoplay → Nächstes Video automatisch abspielen" Untertitel-Menü verstecken Untertitel-Menü ist versteckt. Untertitel-Menü wird angezeigt. + Untertitel-Menü-Fußzeile ausblenden Captions menu footer is hidden. Captions menu footer is shown. Hide lock screen menu @@ -732,16 +969,34 @@ Einstellungen → Autoplay → Nächstes Video automatisch abspielen" Hide premium controls menu Premium controls menu is hidden. Premium controls menu is shown. + Schlaf-Timer-Menü ausblenden + Schlaf-Timer-Menü ist ausgeblendet. + Schlaf-Timer-Menü wird angezeigt. Hide stable volume menu Stable volume menu is shown. Stable volume menu is hidden. Hide stats for nerds menu Stats for nerds menu is hidden. + Statistiken für Nerds werden angezeigt. + Verstecke in VR anschauen \'In VR ansehen\'-Menü wird ausgeblendet. \'In VR ansehen\'-Menü wird angezeigt. Vollbild Komponenten im Zusammenhang mit Vollbild ausblenden oder ändern. + Engagement-Panel deaktivieren + Engagement Panel ist deaktiviert. + Engagement Panel ist aktiviert. + Vollbildmodus aktivieren, wenn das Video startet + "Vollbildmodus aktivieren, wenn das Video gestartet wird. + +Einschränkung: Funktioniert nicht, wenn der Player minimiert ist, im PiP Modus oder im Hintergrund." + Vollbildmodus aktivieren, wenn das Video startet. + Vollbildmodus am Ende des Videos beenden + Deaktiviert + Hochformat + Querformat + Hoch- und Querformat Video-Titelbereich anzeigen "Zeigt den Video-Titelbereich im Vollbild. @@ -752,6 +1007,7 @@ Einschränkung: Videotitel verschwindet beim Klicken auf den Bildschirm."Live-Chat Replay-Button ausblenden Der Live-Chat-Replay-Button ist verborgen.\n\nEr erscheint im Vollbild, wenn Live-Chat geschlossen wird. Der Live-Chat-Replay-Button ist verborgen.\n\nEr erscheint im Vollbild, wenn Live-Chat geschlossen wird. + Verwandtes Video-Overlay ausblenden Im Schnellaktionscontainer werden weitere Videos sowie die zugehörige Videoüberlagerung ausgeblendet. Im Schnellaktionscontainer werden weitere Videos angezeigt sowie die zugehörige Videoüberlagerung. @@ -797,6 +1053,8 @@ Einschränkung: Videotitel verschwindet beim Klicken auf den Bildschirm."Kompaktsteuerungs-Overlay ist aktiviert Kompaktsteuerungs-Overlay ist deaktiviert Querformat behalten + Videos werden nach dem Ausschalten des Bildschirms und dem Ein- und Ausschalten im Querformat weiterhin abgespielt. + Videos werden im Hochformat abgespielt, nachdem der Bildschirm deaktiviert und eingeschaltet wurde. Timeout für den Wechsel zum Querformat Die Anzahl der Millisekunden nach dem Einschalten des Bildschirms, nachdem Querformat erzwungen wird. @@ -820,6 +1078,9 @@ Einschränkung: Videotitel verschwindet beim Klicken auf den Bildschirm." Miniplayer Ändern Sie den Stil des in App minimierten Players. + Miniplayer fortsetzen deaktivieren + <b>Weiter beobachten</b> wird beim Start der App nicht fortgesetzt. + <b>Weiter beobachten</b> wird beim Start der App fortgesetzt.<br><br>Info:<br>• <b>Weiter</b> ist die YouTube Premium-Funktion.<br>• Diese Einstellung erzwingt <b>Weiter</b> nicht zum Aktivieren. Miniplayer Typ Deaktiviert Original @@ -828,12 +1089,34 @@ Einschränkung: Videotitel verschwindet beim Klicken auf den Bildschirm."Modern 1 Modern 2 Modern 3 + Modern 4 Abgerundete Ecken aktivieren Ecken sind abgerundet. Ecken sind nicht abgerundet. Aktiviere doppeltes Tippen und Pinchen um die Größe zu ändern + "Doppeltippen und mit zwei Fingern vergrößern/verkleinern ist aktiviert + +• Doppeltippen, um die Größe des Mini-Players zu vergrößern +• Nochmals doppeltippen, um die ursprüngliche Größe wiederherzustellen." + Doppel-Tipp-Aktion und Pinch um die Größe zu verändern, ist deaktiviert. Drag and Drop aktivieren + "Drag-and-Drop ist aktiviert + +Der Mini-Player kann in jede Ecke des Bildschirms gezogen werden." Drag and Drop ist deaktiviert. + Horizontales Ziehen aktivieren. + "Horizontale Ziehgeste aktiviert + +Der Mini-Player kann mit einer Wischgeste vom Bildschirm nach links oder rechts gezogen werden." + Horizontale Drag Geste deaktiviert. + Overlay-Tasten verbergen + Overlay-Tasten sind ausgeblendet. + Overlay-Schaltflächen werden angezeigt. + Ausklappen und Schließen der Tasten ausblenden + "Tasten sind ausgeblendet. + +Wischen zum erweitern oder schließen." + Erweitern und Schließen Schaltflächen werden angezeigt. Vorwärts- und Rückwärts-Buttons ausblenden Vorwärts springen und zurück sind versteckt. Vor- und zurückspringen wird angezeigt. @@ -885,6 +1168,8 @@ Tap and hold to copy video timestamp." Tippen um Lautstärke des aktuellen Videos stumm zu schalten. Zum laut schalten erneut tippen. Show external download button Tap to launch external downloader. + Warteschlangen Manager + Statt einen externen Downloader zu starten, öffnen Sie den Warteschlangen-Manager. Zeige Geschwindigkeitsdialog Taste "Tippen um den Geschwindigkeitsdialog zu öffnen. Tippen und halten um die Wiedergabegeschwindigkeit auf 1.0x zurückzusetzen. Halten Sie erneut gedrückt, um die Standardgeschwindigkeit wiederherzustellen." @@ -892,6 +1177,26 @@ Tippen und halten um die Wiedergabegeschwindigkeit auf 1.0x zurückzusetzen. Hal \"Tippen, um den Whitelist-Dialog zu öffnen. Tippen und halten Sie, um den Einstellungsdialog für die Whitelist anzuzeigen. \"Alle abspielen\"-Button anzeigen + "Tippen, um eine Wiedergabeliste aller Videos aus dem Kanal zu generieren. +Tippen und halten um rückgängig zu machen. + +Info: +• Funktioniert möglicherweise nicht auf Live-Streams." + Wiedergabelistenmodus erzeugen + Alle Inhalte (Sortieren nach Uhrzeit, aufsteigend) + Alle Inhalte (sortieren nach Zeit) + Alle Inhalte (nach Beliebt) + Nur Videos (Nach Zeit sortieren) + Nur Videos (nach Beliebt) + Nur Shorts (sortieren nach Zeit) + Nur Shorts (nach Beliebt) + Nur gestreamte Videos (sortieren nach Zeit) + Nur gestreamte Videos (nach Beliebt) + Alle Mitglieder nur Inhalte + Nur Mitglieder Videos + Nur Mitglieder Shorts + Nur Mitglieder Live-Streams + Wiedergabeliste kann aufgrund von Kanal-Id nicht generiert werden. Kanal Whitelist Überprüfen oder die Liste der Kanäle entfernen, die zur Whitelist hinzugefügt wurden. Der Kanal \'%1$s\' wurde auf die Whitelist für %2$s gesetzt. @@ -922,12 +1227,23 @@ Tippen und halten Sie, um den Einstellungsdialog für die Whitelist anzuzeigen.< Replace time stamp action Tap to open playback speed or video quality flyout menu. Tap to show the remaining time. + Suchleisten-Kapitel deaktivieren + Kapitel sind in der Suchleiste deaktiviert. + Kapitel sind in der Suchleiste aktiviert. Eigene Suchleistenfarbe aktivieren Die benutzerdefinierte Farbe der Suchleiste ist aktiviert Die benutzerdefinierte Farbe der Suchleiste ist deaktiviert + Benutzerdefinierte Suchleisten-Primärfarbe + Geben Sie den Hex-Code der Suchbar-Primärfarbe ein. + Farbliche Anpassung des Fortschrittsanzeige-Akzents + Geben Sie den Hex-Code der Suchleisten-Akzentfarbe ein. + Ungültige Suchleistenfarbe. Aktiviere Suchleisten-Tippen (Video Fortschrittsbalken) Tippen der Suchleiste ist aktiviert Tippen der Suchleiste ist deaktiviert + Suchleisten-Kapitelbezeichnungen ausblenden + Kapitelbezeichnungen neben der Suchleiste werden ausgeblendet. + Die Kapitelbezeichnungen neben der Suchleiste werden angezeigt. Verstecke Video-Player-Suchleiste Suchleiste für Video-Player wird versteckt Suchleiste für Video-Player wird angezeigt @@ -940,18 +1256,32 @@ Tippen und halten Sie, um den Einstellungsdialog für die Whitelist anzuzeigen.< Alte Suchleiste Thumbnails wiederherstellen Suchleiste Miniaturansichten werden über der Suchleiste angezeigt. Thumbnails der Suchleiste werden im Vollbild angezeigt. + Aktiviere hochwertige Vorschaubilder + Thumbnails der Suchleiste sind hohe Qualität. + Thumbnails der Suchleiste sind mittlere Qualität. + "Damit werden Thumbnails auf Live-Streams wiederhergestellt, die keine Suchleisten-Thumbnails haben. + +Internet-Datennutzung kann höher sein, und Suchleisten-Thumbnails werden eine leichte Verzögerung haben, bevor sie angezeigt werden. + +Diese Funktion funktioniert am besten mit einer sehr schnellen Internetverbindung." Videobeschreibung Komponenten der Videobeschreibung ausblenden oder anzeigen. Disable rolling number animations Rolling numbers are not animated. Rolling numbers are animated. + AI-generierte Video-Zusammenfassung ausblenden + AI generierte Video-Zusammenfassung-Abschnitt ist versteckt. + AI generierte Video-Zusammenfassung-Abschnitt wird angezeigt. Attributbereich ausblenden Ausgewählte Orte, Spiele und Musiksektionen sind versteckt. Vorgestellte Orte, Spiele und Musikbereiche werden angezeigt. Hide chapters sections Chapters sections are hidden. Chapters sections are shown. + Inhaltsbereich ausblenden + Wie dieser Inhalt erstellt wurde, wird ausgeblendet. + Wie dieser Inhalt erstellt wurde Abschnitt angezeigt. Podcast-Abschnitte ausblenden Podcast-Abschnitte sind ausgeblendet. Podcast-Abschnitte werden angezeigt. @@ -961,6 +1291,11 @@ Tippen und halten Sie, um den Einstellungsdialog für die Whitelist anzuzeigen.< Schlüsselkonzeptsektion ausblenden Schlüsselkonzepte sind ausgeblendet. Schlüsselkonzepte werden angezeigt. + Shopping-Links ausblenden + Einkaufslinks sind ausgeblendet. + Einkaufslinks werden angezeigt. + Transkript-Abschnitt ausblenden + Transkript-Abschnitt ist ausgeblendet. Transkriptabschnitte werden angezeigt Videobeschreibungsinteraktion deaktivieren @@ -973,14 +1308,27 @@ Tippen und halten Sie, um den Einstellungsdialog für die Whitelist anzuzeigen.< Videobeschreibungen werden nicht automatisch erweitert. Shorts + Shorts-Hintergrundwiedergabe deaktivieren + Shorts-Hintergrundwiedergabe ist deaktiviert. + Shorts-Hintergrundwiedergabe ist aktiviert. Shorts-Player beim App-Start ausblenden Shorts-Player aktiv beim Start der Anwendung. Shorts-Player aktiv beim Start der Anwendung + Schwebende Taste ausblenden + "Schwebende Schaltflächen wie 'Diesen Ton verwenden' werden im Tab Kurzkanal ausgeblendet." + "Schwebende Schaltflächen wie 'Diesen Ton verwenden' werden im Tab 'Kurzkanal' angezeigt." + Shorts Regale Verstecke Ausschnitte aus Shorts in Kanälen "Verstecke Shorts Regale. Nebeneffekt: Offizielle Kopfzeilen in Suchergebnissen werden ausgeblendet." + Verstecke im Kanal + "Versteckt im Kanal. + +Info: +• Nur Regale mit der Kopfzeile auf der Startseite werden versteckt." + Im Kanal anzeigen. Verstecke im Home Feed und verwandten Videos Versteckt in Home Feed und verwandten Videos. Im Home Feed und verwandte Videos anzeigen. @@ -991,12 +1339,20 @@ Nebeneffekt: Offizielle Kopfzeilen in Suchergebnissen werden ausgeblendet."Community-Beiträge im Abonnement-Feed sind versteckt. Community-Beiträge im Abonnement-Feed werden angezeigt. Verstecke im Beobachtungsverlauf + Versteckt im Verlauf. + Im Verlauf angezeigt. + Shorts-Hintergrundwiederholungsstatus ändern + Shorts Wiederholungsstatus ändern Autoplay Standard Pause Wiederholen + Öffne Shorts im regulären Spieler + Öffne Shorts im regulären Spieler. + Öffne keine Shorts im regulären Spieler. + Shorts Player Elemente im YouTube-Einstellungsmenü verstecken Kanalleiste ausblenden Kanalleiste ist ausgeblendet. @@ -1008,13 +1364,20 @@ Nebeneffekt: Offizielle Kopfzeilen in Suchergebnissen werden ausgeblendet."Infokarten werden ausgeblendet. Infokarten werden angezeigt. Teilnehmen-Schaltfläche verstecken + Teilnehmen Button ist versteckt. + Teilnehmen Button wird angezeigt. Live-Chat-Kopfzeile ausblenden Live-Chat-Kopfzeile wird ausgeblendet.\n\nZurück Button wird nicht ausgeblendet. Live-Chat-Kopfzeile wird angezeigt.\n\nZurück Button wird nicht ausgeblendet. Verstecke Label für bezahlte Promotion Label für bezahlte Promotion wird versteckt. Label für bezahlte Promotion wird angezeigt. + Pausierter Header ausblenden + Pausierter Header ist ausgeblendet. + Pausierter Header wird angezeigt. Pausierte Overlay-Tasten ausblenden + Pausierte Overlay-Tasten sind ausgeblendet. + Angehaltene Overlay-Schaltflächen werden angezeigt. Shop-Schaltfläche verstecken Shop-Schaltfläche wird versteckt. Shop-Schaltfläche wird angezeigt. @@ -1025,6 +1388,8 @@ Nebeneffekt: Offizielle Kopfzeilen in Suchergebnissen werden ausgeblendet."Sticker sind versteckt. Sticker werden angezeigt. Abonnement-Button ausblenden + Abonnieren Button ist versteckt. + Abonnieren Button wird angezeigt. Verstecke \"Trends\" Schaltfläche \"Trends\" Schaltfläche wird versteckt. \"Trends\" Schaltfläche wird angezeigt. @@ -1032,25 +1397,59 @@ Nebeneffekt: Offizielle Kopfzeilen in Suchergebnissen werden ausgeblendet."Titel ist ausgeblendet. Titel wird angezeigt. + Vorgeschlagene Maßnahmen + Green-Screen-Button ausblenden + Green-Screen-Button ist ausgeblendet. + Green-Screen-Button wird angezeigt. + Standortschaltfläche ausblenden + Standortbutton ist ausgeblendet. + Standortbutton wird angezeigt. + \'Musik speichern\'-Button ausblenden + Musik-Button speichern ist ausgeblendet. + Musikspeicher Button wird angezeigt. + Suchvorschläge ausblenden + Suchvorschläge Button ist ausgeblendet. + Suchvorschläge Button wird angezeigt. Shop-Schaltfläche verstecken + Shop-Schaltfläche wird versteckt. + Shop-Schaltfläche wird angezeigt. Super Dankeschön ausblenden \"Super Thanks\" Button wird ausgeblendet. \"Super Thanks\" Button wird angezeigt. Markierte Produkte ausblenden Markierte Produkte sind ausgeblendet. Markierte Produkte werden angezeigt. + \'Vorlage verwenden\'-Button ausblenden + Template-Schaltfläche verwenden ist ausgeblendet. + Template-Schaltfläche verwenden. + Diese Tonschaltfläche ausblenden + Verwenden Sie diese Sound-Taste ist ausgeblendet. + Verwenden Sie diesen Sound-Button wird angezeigt. Aktionsschaltflächen Verstecke \"Gefällt mir\" Button + \"Gefällt mir\" Schaltfläche ist versteckt. + Schaltfläche \"Gefällt mir\" wird angezeigt. Verstecke den Dislike-Button Dislike-Button ist versteckt. Dislike-Button wird angezeigt. Verstecke Kommentar Button + Kommentar Button wird versteckt. + Kommentar Button wird angezeigt. Verstecke Remix Button + Remix button is hidden. + Remix-Schaltfläche wird angezeigt. Verstecke \"Teilen\" Schaltfläche - Sound button is hidden. + Teilen-Schaltfläche ist versteckt. + Teilen-Schaltfläche wird angezeigt. + Tonschaltfläche ausblenden + Sound-Taste ist ausgeblendet. + Tonschaltfläche wird angezeigt. Animation / Feedback + Fontänenanimation \"Gefällt mir\" deaktivieren + Fountain Animation über der Schaltfläche \"Gefällt mir\" ist deaktiviert. + Fountain Animation über der Schaltfläche \"Gefällt mir\" ist aktiviert. Doppeltipp-Animation Original Mag ich @@ -1064,9 +1463,41 @@ Nebeneffekt: Offizielle Kopfzeilen in Suchergebnissen werden ausgeblendet." Benutzerdefinierte Aktionen Benutzerdefinierte Aktionen im Flyout-Menü aktivieren + "Benutzerdefinierte Aktionen sind im Flyout-Menü aktiviert. + +Einschränkungen: +• Funktioniert nicht mit Live-Stream." + Benutzerdefinierte Aktionen sind im Flyout-Menü deaktiviert. + Eigene Aktionen in der Symbolleiste aktivieren + "Benutzerdefinierte Aktionen sind in der Werkzeugleiste aktiviert. + +Drücken und halten Sie die Schaltfläche Mehr, um den Dialog benutzerdefinierte Aktionen anzuzeigen." + Benutzerdefinierte Aktionen sind in der Symbolleiste deaktiviert. Benutzerdefinierte Aktionen + Kopiere Zeitstempel URL + Kopierzeitstempel URL-Menü anzeigen + Kopiere Zeitstempel-URL-Menü wird angezeigt. + Kopiere Zeitstempel-URL-Menü ist ausgeblendet. Video-URL kopieren + Kopiere Video-URL-Menü anzeigen + Video-URL-Menü kopieren. + Video-URL-Menü kopieren ist ausgeblendet. + Externer Downloader + Externes Downloadmenü anzeigen + Externes Download-Menü wird angezeigt. + Externes Download-Menü ist ausgeblendet. Video öffnen + Zeige geöffnetes Video-Menü + Video-Menü wird angezeigt. + Video-Menü öffnen ist ausgeblendet. + Status wiederholen + Zeige Wiederholungsstatus Menü + Das Statusmenü wird angezeigt. + Statusmenü wiederholen ist ausgeblendet. + Über Eigene Aktionen + "Diese Funktion ist noch experimentell, so dass es keine Garantie dafür gibt, dass es perfekt funktioniert. + +Die meisten Fehler können aufgrund von Client-seitigen Einschränkungen nicht behoben werden. Verwenden Sie sie daher nur für Testzwecke." Zeitstempel aktivieren "Zeitstempel ist aktiviert. @@ -1081,34 +1512,56 @@ Bekannte Probleme: Da dies eine Funktion in der Entwicklungsphase von Google ist Navigationsleiste verstecken Navigationsleiste ist versteckt. Navigation bar is shown. + Prozentsatz des leeren Speichers + Konfigurieren Sie den Prozentsatz der Höhe des leeren Leerraums, wenn die Navigationsleiste ausgeblendet ist, zwischen 0 und 100 (%). + Die Höhe muss zwischen 0-100 (%) liegen. Toolbar verstecken Symbolleiste ist versteckt. Symbolleiste wird angezeigt. Kanalhandle ersetzen Kanalname wird verwendet. Kanalhandle wird verwendet. + Altes Player-Layout wiederherstellen + "Altes Spielerlayout wird verwendet. +Keine Ränder oben und unten des Spielers." + Altes Player-Layout wird nicht verwendet. Wischgesten Aktiviere Auto-Helligkeit durch Wischen Wenn die Helligkeit durch Wischen 0 erreicht wird, wird die automatische Helligkeit aktiviert. Auch wenn die Helligkeit durch Wischen auf 0 gesetzt wird, ist die automatische Helligkeit nicht aktiviert. Aktivierung der Helligkeitsgesten - Helligkeit Wischen ist aktiviert + "Helligkeit Wischen ist aktiviert" Helligkeit Wischen ist deaktiviert Aktiviere Lautstärkegesten - Lautstärkegeste ist aktiviert + "Lautstärkegeste ist aktiviert" Lautstärkegeste ist deaktiviert + Helligkeit speichern und wiederherstellen aktivieren + Speichern und die Helligkeit wiederherstellen, wenn Sie den Vollbild verlassen oder betreten. + Helligkeit beim Beenden oder Vollbild nicht speichern und wiederherstellen. Aktiviere Drücken-zu-Wischgeste Berühren und halten, um die Wischgeste zu aktivieren. Berühren, um die Wischgeste zu aktivieren. Haptisches Feedback aktivieren Haptisches Feedback ist aktiviert. Haptisches Feedback ist deaktiviert. + Wischgesten im Sperrbildschirm-Modus + Wischgesten sind im Sperrbildschirm-Modus aktiviert. Wischgesten sind im Sperrbildschirmmodus deaktiviert. - Wischen Hintergrund Sichtbarkeit - Die Sichtbarkeit des Wischen Overlay-Hintergrunds Wischgrößen-Schwellenwert Der Schwellenwert für das Wischen + Alternative UI Wischen überlagern + Alternatives UI wird verwendet. + Legacy-UI wird verwendet. + Aktivieren Sie den minimalistischen Stil + Minimaler Overlay-Stil ist aktiviert. + Minimaler Overlay-Stil ist deaktiviert. + Kreisförmige Überlagerung anzeigen + Kreisförmige Überlagerung wird angezeigt. + Horizontales Overlay wird angezeigt. + Wische Overlay-Hintergrund Deckkraft + Deckkraft Wert zwischen 0-100. + Wischtransparenz muss zwischen 0-100 liegen. Wischüberlagerung Textgröße Die Textgröße für Wischüberlagerung Wischüberlagerungsgröße @@ -1116,53 +1569,34 @@ Bekannte Probleme: Da dies eine Funktion in der Entwicklungsphase von Google ist Die Größe des Wischbereichs darf nicht mehr als 50 betragen. Zurückgesetzt auf Standardwert. Swipe Overlay-Zeitüberschreitung Die Anzahl der Millisekunden, die das Overlay sichtbar ist + Helligkeit wischen Empfindlichkeit + Konfiguriere den Mindestabstand für Helligkeitswischen zwischen 1 und 1000 (%).\nJe kürzer der Mindestabstand, desto schneller ändert sich die Helligkeitsstufe. + Die Helligkeitswischempfindlichkeit muss zwischen 1-1000 liegen (%). + Lautstärke-Wischen Empfindlichkeit + Konfigurieren Sie den Mindestabstand für das Wischen von Lautstärke zwischen 1 und 1000 (%).\n\nJe kürzer der Mindestabstand, desto schneller ändert sich die Lautstärke.\n\nEmpfohlene Lautstärke-Wischempfindlichkeit beträgt 100% bei 15-Volumen-Schritten und 10% bei 150-Volumen-Schritten. + Lautstärke-Wischempfindlichkeit muss zwischen 1-1000 (%) liegen. Deaktiviere automatische HDR-Helligkeit Automatische HDR-Helligkeit ist deaktiviert Automatische HDR-Helligkeit ist aktiviert Geste zum Wechseln des Videos deaktiviern Wischen nach oben / nach unten wird nicht das nächste / vorherige Video abspielen. Wischen nach oben / unten wird das nächste / vorherige Video abspielen. + Wischen deaktivieren, um in den Vollbildmodus zu gelangen (unter dem Spieler) + Unten unter dem Spieler wischen wird der Vollbildmodus nicht betreten. + Wenn du unter dem Spieler wischst, wird der Vollbildmodus geöffnet. + Wischen deaktivieren, um in den Vollbildmodus zu gelangen (im Player) + Beim Hochwischen im Player wird der Vollbildmodus nicht betreten. + Beim Hochwischen im Player wird der Vollbildmodus gewechselt. + Wischen zum Beenden des Vollbildmodus deaktivieren + Beim Wischen nach unten im Vollbildmodus wird der Vollbildmodus nicht beendet. + Beim Wischen nach unten im Vollbildmodus wird der Vollbildmodus beendet. Auto Video - Standard Wiedergabegeschwindigkeit - Standard Videoqualität im Mobilfunk - Standard-Videoqualität im Wlan + HDR-Video deaktivieren HDR-Video ist deaktiviert HDR-Video ist aktiviert - Benutzerdefinierte Wiedergabegeschwindigkeit aktivieren - Benutzerdefinierte Wiedergabegeschwindigkeit ist aktiviert - Benutzerdefinierte Wiedergabegeschwindigkeit ist deaktiviert - Menü-Typ für benutzerdefinierte Wiedergabegeschwindigkeiten - Benutzerdefinierter Dialog wird verwendet. - Altes Flyout Menü wird verwendet. - Benutzerdefinierte Wiedergabegeschwindigkeiten bearbeiten - Verfügbare Wiedergabegeschwindigkeiten hinzufügen oder ändern - Remember playback speed changes - Playback speed changes apply to all videos. - Playback speed changes only apply to the current video. - Qualitätseinstellungen merken - Qualitätseinstellungen werden für alle Videos angewendet - Qualitätseinstellungen werden nur für das aktuelle Video angewendet - Altes Qualitätsmenü wiederherstellen - Altes Qualitätsmenü wird angezeigt - Altes Qualitätsmenü wird nicht angezeigt - Standard Wiedergabegeschwindigkeit für Shorts aktivieren - Die Standard-Wiedergabegeschwindigkeit gilt für Shorts. - Die Standard-Wiedergabegeschwindigkeit gilt nicht für Shorts. - Skipped preloaded buffer. - Skip preloaded buffer - "Skips the preloaded buffer at the start of videos to immediately apply the default video quality. - -Info: -• When the video starts, there is a delay of approximately 0.3 seconds. -• Does not apply to HDR videos, live stream videos, or videos shorter than 15 seconds." - Zeige eine Benachrichtigung beim Überspringen an - Benachrichtigung wird angezeigt. - Benachrichtigung wird nicht angezeigt. - Spoof device dimensions - "Spoofs the device dimensions in order to unlock higher video qualities that may not be available on your device." VP9 Codec deaktivieren "VP9-Codec ist deaktiviert. @@ -1172,16 +1606,55 @@ Info: VP9-Codec ist aktiviert. Replace software AV1 codec Replaces the software AV1 codec with the VP9 codec. - Reject software AV1 codec response - "Forcefully rejects the software AV1 codec response. -A different codec will be applied after about 20 seconds of buffering." - Das Fallback-Verfahren führt zu etwa 20 Sekunden Pufferung. - %s ändert Standardgeschwindigkeit. - Changing default mobile data quality to %s. - Failed to set video quality. - Changing default Wi-Fi quality to %s. + + Standard Wiedergabegeschwindigkeit + Remember playback speed changes + Playback speed changes apply to all videos. + Playback speed changes only apply to the current video. + Toast anzeigen + Beim Ändern der Standard-Wiedergabegeschwindigkeit wird ein Toast angezeigt. + Beim Ändern der Standard-Wiedergabegeschwindigkeit wird kein Toast angezeigt. + Benutzerdefinierte Wiedergabegeschwindigkeit aktivieren + Benutzerdefinierte Wiedergabegeschwindigkeit ist aktiviert + Benutzerdefinierte Wiedergabegeschwindigkeit ist deaktiviert + Menü-Typ für benutzerdefinierte Wiedergabegeschwindigkeiten + Benutzerdefinierter Dialog wird verwendet. + Altes Flyout Menü wird verwendet. + Benutzerdefinierte Wiedergabegeschwindigkeiten bearbeiten + Verfügbare Wiedergabegeschwindigkeiten hinzufügen oder ändern Ungültige benutzerdefinierte Wiedergabegeschwindigkeiten. Auf Standardwerte zurücksetzen. Ungültige benutzerdefinierte Wiedergabegeschwindigkeiten. Auf Standardwerte zurücksetzen. + + Standard Videoqualität im Mobilfunk + Standard-Videoqualität im Wlan + Qualitätseinstellungen merken + Qualitätseinstellungen werden für alle Videos angewendet + Qualitätseinstellungen werden nur für das aktuelle Video angewendet + Toast anzeigen + Beim Ändern der Standard-Videoqualität wird ein Toast angezeigt. + Beim Ändern der Standard-Videoqualität wird kein Toast angezeigt. + Altes Qualitätsmenü wiederherstellen + Altes Qualitätsmenü wird angezeigt + Altes Qualitätsmenü wird nicht angezeigt + Skipped preloaded buffer. + Skip preloaded buffer + "Skips the preloaded buffer at the start of videos to immediately apply the default video quality. + +Info: +• When the video starts, there is a delay of approximately 0.3 seconds. +• Does not apply to HDR videos, live stream videos, or videos shorter than 15 seconds." + Das Einschalten dieser Einstellung kann zu Videowiedergabeproblemen führen. + Zeige eine Benachrichtigung beim Überspringen an + Benachrichtigung wird angezeigt. + Benachrichtigung wird nicht angezeigt. + Gerätemaße fälschen + + Wiedergabegeschwindigkeit für Musik deaktivieren + Die Standardgeschwindigkeit der Wiedergabe ist für Musik deaktiviert. + Die Standardgeschwindigkeit der Wiedergabe ist für Musik aktiviert. + Mit Kategorien validieren + Standardmäßig ist die Wiedergabegeschwindigkeit deaktiviert, wenn die Videokategorie Musik ist. + Die Standardgeschwindigkeit der Wiedergabe ist für Videos deaktiviert, die auf YouTube Musik abgespielt werden können. Return YouTube Dislike Return YouTube Dislike aktivieren @@ -1216,6 +1689,24 @@ Einschränkung: Dislikes werden im Inkognito Modus nicht angezeigt." Video neu laden, um mit Return YouTube Dislike abzustimmen Versteckt + YouTube-Benutzername zurückgeben + Retour-YouTube-Benutzername aktivieren + Benutzername wird verwendet. + Handle wird verwendet. + Stil anzeigen + Benutzername + Benutzername (@handle) + \@handle (Benutzername) + YouTube Data API Schlüssel + Der Entwicklerschlüssel für die Verwendung der YouTube Data API v3. + Über YouTube Data API Schlüssel + "Ein YouTube Data API v3 Developer Key wird benötigt, um Handles durch Benutzernamen zu ersetzen. + +Das tägliche Kontingent für API-Schlüssel auf dem kostenlosen Paket ist 10.000, und 1 Quota wird verwendet, um ein Handle durch einen Benutzernamen für 1 Kommentar zu ersetzen. + +Klicken Sie hier, um zu sehen, wie Sie einen API-Schlüssel ausgeben." + YouTube Data API v3 Entwicklerschlüssel ausgeben + 1. Gehen Sie zu <a href=%1$s>Erstellen Sie ein neues Projekt</a>.<br>. Klicken Sie auf die <b>Erstelle</b> Schaltfläche.<br>3. Gehen Sie zu <a href=%2$s>YouTube Data API v3</a>.<br>4. Klicken Sie auf <b></b> Knopf.<br>5. Klicken Sie auf <b>CREDENTIALS</b> Schaltfläche.<br>6. Wählen Sie die Option <b>Öffentliche Daten</b> aus.<br>7. Klicken Sie auf die <b>NEXT</b> Schaltfläche.<br>8. API-Schlüssel kopieren.<br><br> ※ API-Schlüssel sollte niemals mit anderen geteilt werden, daher ist er nicht in den Einstellungen Import / Export enthalten. SponsorBlock SponsorBlock aktivieren @@ -1293,6 +1784,7 @@ Einschränkung: Dislikes werden im Inkognito Modus nicht angezeigt." Überspringen Schaltfläche anzeigen In der Suchleiste anzeigen Deaktivieren + Deckfähigkeit: Farbe: Farbe geändert Farben zurücksetzen @@ -1305,6 +1797,8 @@ Einschränkung: Dislikes werden im Inkognito Modus nicht angezeigt." Button für neues Segment anzeigen Button für neues Segment wird angezeigt Button für neues Segment wird nicht angezeigt + Neuen Segmentschritt anpassen + Anzahl der Millisekunden, die sich die Zeitanpassungsschaltflächen beim Erstellen neuer Segmente bewegen. Wert muss eine positive Zahl sein Richtlinien anzeigen Richtlinien enthalten Tipps und Regeln zum Einreichen von Segmenten @@ -1317,12 +1811,16 @@ Einschränkung: Dislikes werden im Inkognito Modus nicht angezeigt." Zeige eine Benachrichtigung an, wenn die API nicht verfügbar ist Benachrichtigung wird angezeigt, wenn „SponsorBlock“ nicht verfügbar ist. Benachrichtigung wird nicht angezeigt, wenn „SponsorBlock“ nicht verfügbar ist. + Aktiviere Sprungzähler-Tracking + Lassen Sie die SponsorBlock Rangliste wissen, wie viel Zeit gespeichert wird. Jedes Mal, wenn ein Segment übersprungen wird, wird eine Nachricht an die Rangliste gesendet. Skip count tracking is not enabled. Minimale Segment-Dauer Segment kürzer als dieser Werte (in Sekunden) werden nicht angezeigt oder übersprungen. + Ungültige Zeitdauer. Ihre private Benutzer-ID Dies sollte privat gehalten werden. Es ist wie ein Passwort und sollte nicht an andere weitergegeben werden. Wenn jemand es hat, kann er sich für Sie ausgeben Benutzer-ID darf nicht leer sein + API URL ändern Die Addresse zum API-Server von SponsorBlock API-URL zurücksetzen. API-URL ist ungültig @@ -1334,6 +1832,7 @@ Einschränkung: Dislikes werden im Inkognito Modus nicht angezeigt." Einstellungen erfolgreich importiert. Importieren fehlgeschlagen: %s. Export fehlgeschlagen: %s. + Ihre Einstellungen enthalten eine private SponsorBlock Benutzeride.\n\nIhre Benutzer-Id ist wie ein Passwort und sollte nie geteilt werden.\n Do not show again SponsorBlock temporarily unavailable. SponsorBlock temporarily unavailable (status %d). @@ -1352,9 +1851,12 @@ Einschränkung: Dislikes werden im Inkognito Modus nicht angezeigt." Negativ bewerten Kategorie ändern Es gibt keine Segmente zur Abstimmung + + %1$s bis %2$s Wähle eine Segmentkategorie aus Category is disabled in settings. Enable category to submit. Neues SponsorBlock Segment + %s als Start oder Ende eines neuen Segments festlegen? Start Ende Jetzt @@ -1379,6 +1881,7 @@ Einschränkung: Dislikes werden im Inkognito Modus nicht angezeigt." Benutzername wurde erfolgreich geändert Your reputation is <b>%.2f</b> You\'ve created <b>%s</b> segments + Tippen Sie hier, um Ihre Segmente anzuzeigen. SponsorBlock Rangliste You\'ve saved people from <b>%s</b> segments Hier tippen, um die globalen Statistiken und Top-Mitwirkende zu sehen @@ -1394,7 +1897,10 @@ Einschränkung: Dislikes werden im Inkognito Modus nicht angezeigt." sponsor.ajay.app Die Daten werden von der SponsorBlock API bereitgestellt. Tippen Sie hier, um mehr zu erfahren und Downloads für andere Plattformen zu sehen - Sonstiges + PreferenceScreen: Sonstiges + URL-Weiterleitungen umgehen + URL-Weiterleitungen werden umgangen. + URL-Umleitungen werden nicht umgangen. QUIC-Protokoll deaktivieren "CronetEngine's QUIC-Protokoll deaktivieren" Debug-Protokollierung aktivieren @@ -1403,11 +1909,17 @@ Einschränkung: Dislikes werden im Inkognito Modus nicht angezeigt." Debug-Pufferprotokollierung aktivieren Debug-Protokolle enthalten Puffer. Debug-Protokolle enthalten keinen Puffer. + Öffne Links extern + Öffnet Links im externen Browser. + Öffnet Links im In-App-Browser. + Freigabe-Links säubern + Entfernt das Teilen von Links durch das Entfernen von Tracking-Abfrageparametern. Standard-App-Einstellungen öffnen Um RVX in einem externen Browser zu öffnen, aktivieren Sie \'Unterstützte Links öffnen\' und aktivieren Sie die unterstützen Web-Adressen GmsCore öffnen Cloud-Nachrichteneinstellungen aktivieren, um Benachrichtigungen zu erhalten GmsCore ist nicht installiert. Bitte installiere es. + Aktion erforderlich "GmsCore hat keine Berechtigung um im Hintergrund zu laufen. Folge der 'Don't kill my app!' Anleitung für dein Gerät and wende die Anweisungen auf deine GmsCore Installation an. @@ -1418,6 +1930,9 @@ Dies wird zum Funktionieren der App benötigt." Drücke Weiter und deaktiviere Akku-Optimierungen." Fortsetzen + Freigabeblatt ändern + System Share Sheet wird verwendet. + App-Freigabeblatt wird verwendet. OPUS Codec aktivieren Aktiviere den OPUS-Codec, wenn die Antwort des Players den OPUS-Codec enthält. @@ -1441,21 +1956,106 @@ Drücke Weiter und deaktiviere Akku-Optimierungen." Einstellungen zurücksetzen Einstellungen wurden erfolgreich importiert. Zurücksetzen + Einstellungen in Zwischenablage kopiert. + Spoof Streamingdaten + Spoof die Streaming-Daten, um Wiedergabeprobleme zu vermeiden. + Spoof Streamingdaten + Streaming-Daten sind gefälscht. + "Streaming-Daten sind nicht gefälscht. Videowiedergabe funktioniert möglicherweise nicht." + Das Deaktivieren dieser Einstellung kann zu Videowiedergabeproblemen führen. + Standard-Client + "Android TV +(Anmeldung erforderlich)" + Android VR + "Android VR +(Keine Authentizität)" + "iOS +(veraltet)" + "iOS TV +(Login erforderlich)" + Nebenwirkungen des Spoofings + "• Audiospurmenü fehlt. +• Stabile Lautstärke ist nicht verfügbar. +• Erzwungene automatische Audiospuren deaktivieren ist nicht verfügbar. +• Kindervideos dürfen nicht abgespielt werden, wenn sie ausgeloggt oder im Inkognito-Modus sind." + • Ganze ASNs/IP-Bereiche können vom Server blockiert werden. + "• Stabile Lautstärke ist nicht verfügbar. +• Filme oder bezahlte Videos können nicht abgespielt werden. +• Kindervideos können nicht abgespielt werden, wenn sie ausgeloggt oder im Inkognito-Modus sind." + Verwende iOS-Client + "iOS-Client zu verfügbaren Clients hinzugefügt. + +WARNUNG: iOS-Client ist veraltet. Alle Probleme, die während der Nutzung auftreten, gehen auf Ihr eigenes Risiko." + iOS-Client wurde nicht zu verfügbaren Clients hinzugefügt. + "Wenn Sie YouTube-API-Endpunkte mit iOS anfordern, benötigen Sie die Auth Token des iOS-Geräts und die von iOSGuard herausgegebenen PoToken. + +Das bedeutet, dass Streaming-Anfragen über iOS sowohl die Auth Token als auch die PoTokens fehlen und der Server kann den Benutzer als Bot betrachten und den gesamten ASN/IP-Bereich blockieren. + +BENUTZEN AUF IHREM EIGENEN RISK!" + iOS AVC (H.264) erzwingen + Video Codec ist zum AVC (H.264) gezwungen. + Video Codec wird automatisch ermittelt. + "Aktivieren Sie dies, um die Akkulaufzeit zu verbessern und Ruckeln bei der Wiedergabe zu beheben. + +AVC hat eine maximale Auflösung von 1080p, Opus-Audiocodec ist nicht verfügbar und die Videowiedergabe verbraucht mehr Internetdaten als VP9 oder AV1." + Onesie-Antwort-Verschlüsselung überspringen + "Onesie Antwortverschlüsselung überspringen. + +• Behebt einen neuen Typ von Wiedergabefehler, mit dem einige Benutzer konfrontiert sind. +• AV1 Codec ist möglicherweise nicht verfügbar." + "Onesie Antwortverschlüsselung nicht überspringen. + +• Einige Benutzer können ein neues Wiedergabeproblem erleben." + Zeigt Statistiken für Nerds + Der Client zum Abrufen von Streaming-Daten wird in Statistiken für Nerds angezeigt. + Client zum Abrufen von Streaming-Daten wird in Statistiken für Nerds versteckt. + Standard-Audiostreamsprache für VR + PoToken / VisitorData + PoToken zu verwenden + PoToken von BotGuard in einem vertrauenswürdigen Browser herausgegeben. + Zu verwendende Besucherdaten + Besucherdaten, die von BotGuard in einem vertrauenswürdigen Browser ausgegeben werden. + Über PoToken / Besucherdaten + "Einige Clients benötigen PoToken und Besucherdaten, um eine gültige Antwort auf Streaming-Daten zu erhalten. + +Wenn Sie versuchen, iOS als Standard-Client zu verwenden, benötigen Sie diese Werte. + +Klicken Sie hier, um weitere Informationen zu sehen." + Historie ansehen + Einstellungen im Zusammenhang mit dem Beobachtungsverlauf ändern. + Alle Chronik verwalten + Klicken Sie hier, um die Verwaltung der YouTube Beobachtungsverlauf zu öffnen. + Überwachungsverlaufstyp + Original + Domain ersetzen + Blocküberwachungsverlauf + Status des Überwachungsverlaufs + • Überwachungsverlauf blockiert. + • Folgen Sie den Einstellungen für den Verlauf von Google-Konto. + "• Folgt den Einstellungen des Beobachtungsverlaufs von Google-Konto. +• Überwachungsverlauf kann aufgrund von DNS oder VPN nicht funktionieren." Patch-Informationen Patch-Informationen Informationen über angewandte Patches + Werkzeug verwendet + Andere + Benutzerdefiniert Stock Afn Blau Afn Rot MMT + Revancify Blau + Revancify Rot YouTube Stock + ausgeschlossen + Enthalten Stock 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 4e88fed07..335baf89e 100644 --- a/patches/src/main/resources/youtube/translations/el-rGR/strings.xml +++ b/patches/src/main/resources/youtube/translations/el-rGR/strings.xml @@ -9,7 +9,7 @@ Έγινε επαναφορά στις προεπιλεγμένες τιμές. Πειραματικές λειτουργίες Θέλετε να συνεχίσετε; - Επανεκκίνηση ώστε να φορτωθεί σωστά η διεπαφή + Επανεκκίνηση ώστε να φορτωθεί σωστά η διάταξη Ανανέωση και επανεκκίνηση Κανονική Όνομα πακέτου προγράμματος λήψης βίντεο @@ -19,59 +19,150 @@ "Το %1$s δεν είναι εγκατεστημένο. Παρακαλούμε εγκαταστήστε το %2$s από την ιστοσελίδα." %s δεν έχει εγκατασταθεί. Παρακαλούμε εγκαταστήστε το. + Προσθήκη στην ουρά + Προσθήκη στην ουρά και άνοιγμα ουράς + Προσθήκη στην ουρά και αναπαραγωγή βίντεο + Εξωτερικό πρόγραμμα λήψης + Άνοιγμα ουράς + Ουρά + Αφαίρεση από την ουρά + Αφαίρεση από την ουρά και άνοιγμα ουράς + Κατάργηση ουράς + Αποθήκευση ουράς + "Αντί να ανοίξετε ένα εξωτερικό πρόγραμμα λήψης, ανοίξτε το παράθυρο διαχείρισης ουράς. + +Μπορείτε επίσης να ανοίξετε τον διαχειριστή ουράς πατώντας παρατεταμένα το κουμπί επιστροφής στη γραμμή πλοήγησης. + +Αυτή η λειτουργία είναι ακόμη υπό ανάπτυξη, οπότε οι περισσότερες λειτουργίες μπορεί να μη λειτουργούν. + +Παρακαλούμε χρησιμοποιήστε την για σκοπούς εντοπισμού σφαλμάτων μόνο." + Απαιτείται σύνδεση + Διαχειριστής ουράς μη διαθέσιμος (%s). + Αδυναμία αναγνώρισης λίστας αναπαραγωγής + Η ουρά είναι κενή + Αδυναμία αναγνώρισης βίντεο + Αποτυχία προσθήκης βίντεο. + Αποτυχία δημιουργίας ουράς. + Αποτυχία διαγραφής ουράς. + Αποτυχία αφαίρεσης βίντεο. + Αποτυχία αποθήκευσης της ουράς. + Το βίντεο προστέθηκε επιτυχώς. + Η ουρά δημιουργήθηκε επιτυχώς. + Η ουρά διαγράφηκε επιτυχώς. + Το βίντεο αφαιρέθηκε επιτυχώς. + Η ουρά αποθηκεύτηκε επιτυχώς στο \'%s\'. Γλώσσα ρυθμίσεων RVX Γλώσσα εφαρμογής - Αραβικά - Αζερική - Βουλγαρικά - Βεγγαλικά - Καταλανικά - Τσέχικα - Δανικά - Γερμανικά - Ελληνικά - Αγγλικά - Ισπανικά - Εσθονικά - Περσικά - Φινλανδικά - Γαλλικά - Γκουτζαρατικά - Χίντι - Κροατικά - Ουγγρικά - Ινδονησιακά - Ιταλικά - Ιαπωνικά - Καζακικά - Κορεάτικα - Λιθουανικά - Λετονικά - Σλαβομακεδονικά - Μογγολικά - Μαράτι - Μαλαισιανά - Βιρμανικά - Ολλανδικά - Οντία - Παντζάμπι - Πολωνικά - Πορτογαλικά - Ρουμανικά - Ρώσικα - Σλοβακικά - Σλοβενικά - Σέρβικα - Σουηδικά - Σουαχίλι - Ταμίλ - Τελούγκου - Ταϊλανδικά - Τούρκικα - Ουκρανικά - Ουρντού - Βιετναμέζικα - Κινέζικα + "Αμχαρικά +አማርኛ" + "Αραβικά +العربية" + "Αζερικά +Azərbaycan" + "Λευκορωσική +беларуская" + "Βουλγαρικά +Български" + "Βεγγαλικά +বাংলা" + "Καταλανικά +Català" + "Τσέχικα +Čeština" + "Δανέζικα +Dansk" + "Γερμανικά +Deutsch" + "Ελληνικά +Ελληνικά" + "Αγγλικά +English" + "Ισπανικά +Español" + "Εσθονικά +Eesti" + "Περσικά +فارسی" + "Φινλανδικά +Suomi" + "Γαλλικά +Français" + "Γκουτζαρατί +ગુજરાતી" + "Εβραϊκά +עברי" + "Ινδικά +हिन्दी" + "Κροατικά +Hrvatski" + "Ουγγρικά +Magyar" + "Ινδονησιακά +Indonesia" + "Ιταλικά +Italiano" + "Ιαπωνικά +日本語" + "Καζακική +Қазақ тілі" + "Κορεατική +한국어" + "Λιθουανικά +Lietuvių" + "Λετονικά +Latviešu" + "Βορειομακεδονικά +Македонски" + "Μογγολικά +Монгол" + "Μαράτι +मराठी" + "Μαλαϊκά +Melayu" + "Βιρμανικά +ဗမာ" + "Ολλανδικά +Nederlands" + "Οντία +ଓଡ଼ିଆ" + "Παντζαμικά +ਪੰਜਾਬੀ" + "Πολωνικά +Polski" + "Πορτογαλικά +Português" + "Ρουμανικά +Română" + "Ρωσικά +Русский" + "Σλοβακικά +Slovenčina" + "Αλβανικά +Shqip" + "Σλοβενικά +Slovenščina" + "Σερβικά +Српски" + "Σουηδικά +Svenska" + "Σουαχίλι +Kiswahili" + "Ταμίλ +தமிழ்" + "Τελούγκου +తెలుగు" + "Ταϊλανδέζικα +ไทย" + "Τουρκικά +Türkçe" + "Ουκρανικά +Українська" + "Ουρντού +اردو" + "Βιετναμέζικα +Tiếng Việt" + "Κινέζικα +中文" Διαφημίσεις Ετικέτα καταστήματος στην τελική οθόνη @@ -390,9 +481,6 @@ Playlists Απενεργοποίηση εφέ εκκίνησης εφαρμογής Το εφέ εκκίνησης της εφαρμογής είναι απενεργοποιημένο. Το εφέ εκκίνησης της εφαρμογής είναι ενεργοποιημένο. - Απενεργοποίηση διαφανούς γραμμής κατάστασης - Η γραμμή κατάστασης δεν είναι διαφανής. - Η διαφάνεια της γραμμής κατάστασης ορίζεται αυτόματα. Διαβαθμισμένη οθόνη φόρτωσης Η οθόνη φόρτωσης θα έχει σταδιακές αποχρώσεις φόντο. Η οθόνη φόρτωσης θα έχει στατική απόχρωση φόντο. @@ -406,10 +494,10 @@ Playlists "Αφαίρεση του παραθύρου προειδοποίησης ηλικιακού περιορισμού. Αυτό δεν παρακάμπτει τον ηλικιακό περιορισμό, απλά τον αποδέχεται αυτόματα." Αλλαγή ενέργειας πατήματος δακτυλίου ζωντανής μετάδοσης - "Το κανάλι ανοίγει όταν πατιέται ο δακτύλιος ζωντανής μετάδοσης. + "Ανοίγει το κανάλι όταν πατιέται ο δακτύλιος ζωντανής μετάδοσης. Περιορισμός: Όταν πρόκειται για ζωντανή μετάδοση Shorts και η λειτουργία «Άνοιγμα των Shorts στην κανονική οθόνη αναπαραγωγής» είναι ενεργοποιημένη, δεν ανοίγει το κανάλι." - Η ζωντανή μετάδοση ανοίγει όταν πατιέται ο δακτύλιος ζωντανής μετάδοσης. + Ανοίγει η ζωντανή μετάδοση όταν πατιέται ο δακτύλιος ζωντανής μετάδοσης. Αλλαγή μορφής διάταξης Προεπιλογή Τηλέφωνο @@ -426,6 +514,22 @@ Playlists • Τα Shorts ανοίγουν στην κανονική οθόνη αναπαραγωγής. • Η ροή οργανώνεται ανά θέματα και κανάλια. • Η περιγραφή βίντεο δεν γίνεται να ανοιχτεί όταν η λειτουργία «Παραποίηση ροών βίντεο» είναι ενεργοποιημένη." + Απενεργοποίηση ενημερώσεων διάταξης + Η διάταξη δε θα ενημερώνεται από τον διακομιστή. + Η διάταξη θα ενημερώνεται από τον διακομιστή. + "Η διάταξη της εφαρμογής θα επαναφερθεί στη διάταξη που χρησιμοποιούσε όταν εγκαταστάθηκε για πρώτη φορά. + +Κάποιες διατάξεις από την πλευρά του διακομιστή ενδέχεται να μην αναιρεθούν. + +Οι αλλαγές περιλαμβάνουν: +• Τα στοιχεία στο αναδυόμενο μενού της οθόνης αναπαραγωγής ή οι σχετικές ρυθμίσεις τους μπορεί να μη λειτουργούν. +• Οι αριθμοί προβολών & «Μου αρέσει» δεν κινούνται αυξανόμενοι εκθετικά. +• Η καρτέλα «Βιβλιοθήκη» θα επαναφερθεί. +• Το κουμπί εναλλαγής λογαριασμού μπορεί να μην εμφανίζεται στην καρτέλα «Βιβλιοθήκη». Χρησιμοποιήστε την ρύθμιση «Ευρεία γραμμή αναζήτησης στο «Εσείς»»." + Απενεργοποίηση διαφανούς γραμμής κατάστασης + Η γραμμή κατάστασης δεν είναι διαφανής. + Η διαφάνεια της γραμμής κατάστασης ορίζεται αυτόματα. + Σε ορισμένες εργοστασιακές ROM που τρέχουν Android 12+, η ενεργοποίηση αυτής της λειτουργίας μπορεί να κάνει τη γραμμή πλοήγησης του συστήματος διαφανή. Παραποίηση έκδοσης εφαρμογής Η έκδοση παραποιείται. Η έκδοση δεν παραποιείται. @@ -443,8 +547,10 @@ Playlists 18.33.40 - Επαναφορά γραμμής ενεργειών Shorts στο παλιό στυλ 18.38.45 - Επαναφορά της παλιάς συμπεριφοράς προεπιλεγμένης ποιότητας βίντεο 18.48.39 - Απενεργοποίηση ενημέρωσης των προβολών & αριθμού των «Μου αρέσει» σε πραγματικό χρόνο + 19.01.34 - Απενεργοποίηση αλληλεπίδρασης περιγραφής βίντεο 19.26.42 - Απενεργοποίηση εικονιδίων θέματος Cairo στις γραμμές πλοήγησης και εργαλείων 19.33.37 - Επαναφορά αναδυόμενου πίνακα ταχύτητας αναπαραγωγής παλιού στυλ + Μη έγκυρη έκδοση για παραποίηση: %s. Μενού λογαριασμού Απόκρυψη ή εμφάνιση στοιχείων στο μενού λογαριασμού και στην καρτέλα «Εσείς». @@ -480,6 +586,9 @@ Playlists Μετατροπή κουμπιού λήψης βίντεο Το κουμπί λήψης του YouTube ανοίγει το εξωτερικό πρόγραμμα λήψης σας. Το κουμπί λήψης του YouTube ανοίγει το εγγενές πρόγραμμα λήψης της εφαρμογής. + Διαχειριστής ουράς + Το κουμπί λήψης βίντεο του YouTube ανοίγει τον διαχειριστή ουράς. + Το κουμπί λήψης βίντεο του YouTube ανοίγει το εξωτερικό πρόγραμμα λήψης σας. Όνομα πακέτου προγράμματος λήψης λίστας αναπαραγωγής Όνομα πακέτου της εγκατεστημένης σας εξωτερικής εφαρμογής λήψης (π.χ YTLDnis). @@ -533,7 +642,6 @@ Playlists Διαφανή γραμμή πλοήγησης Η γραμμή πλοήγησης είναι διαφανής. Η γραμμή πλοήγησης δεν είναι διαφανής. - Σε ορισμένες εκδόσεις YouTube, αυτή η ρύθμιση μπορεί να κάνει τη γραμμή πλοήγησης του συστήματος διάφανη ή να χαλάσει τη διάταξη σε λειτουργία PIP. Γραμμή πλοήγησης Κρυμμένη. Εμφανίζεται. @@ -1118,6 +1226,8 @@ Playlists Εξωτερική λήψη του βίντεο Κουμπί εξωτερικής λήψης του βίντεο. Πατήστε για εκκίνηση του προεπιλεγμένου σας εξωτερικού προγράμματος λήψης. + Διαχειριστής ουράς + Αντί να ανοίξετε ένα εξωτερικό πρόγραμμα λήψης, ανοίξτε τον διαχειριστή ουράς. Αλλαγή ταχύτητας αναπαραγωγής "Κουμπί ρύθμισης ταχύτητας αναπαραγωγής. Πατήστε για να ανοίξετε το παράθυρο αλλαγής ταχύτητας αναπαραγωγής. @@ -1224,7 +1334,7 @@ Playlists Απενεργοποίηση κινήσεων αριθμών Οι αριθμοί δεν κινούνται αυξανόμενοι εκθετικά. Οι αριθμοί κινούνται αυξανόμενοι εκθετικά. - Σύνοψη βίντεο παραγμένου με τεχνητή νοημοσύνη + Σύνοψη βίντεο που δημιουργήθηκε από AI Κρυμμένη. Εμφανίζεται. Ενότητα ιδιοτήτων @@ -1428,8 +1538,7 @@ Playlists "Οι προσαρμοσμένες ενέργειες είναι ενεργοποιημένες στο αναδυόμενο μενού. Περιορισμοί: -• Δεν λειτουργεί εάν η έκδοση της εφαρμογής παραποιείται σε 18.49.37 ή παλιότερη. -• Δεν λειτουργεί σε ζωντανές μεταδόσεις." +• Δε λειτουργεί σε ζωντανές μεταδόσεις." Οι προσαρμοσμένες ενέργειες είναι απενεργοποιημένες στο αναδυόμενο μενού. Προσαρμοσμένες ενέργειες στη γραμμή εργαλείων "Οι προσαρμοσμένες ενέργειες είναι ενεργοποιημένες στη γραμμή εργαλείων. @@ -1453,6 +1562,10 @@ Playlists Εμφάνιση μενού ανοίγματος του βίντεο Το μενού ανοίγματος του βίντεο εμφανίζεται. Το μενού ανοίγματος του βίντεο δεν εμφανίζεται. + Παράθυρο αλλαγής ταχύτητας + Εμφάνιση μενού αλλαγής ταχύτητας + Εμφανίζεται. + Δεν εμφανίζεται. Κατάσταση επανάληψης Εμφάνιση μενού κατάστασης επανάληψης Το μενού κατάστασης επανάληψης εμφανίζεται. @@ -1467,7 +1580,7 @@ Playlists Περιορισμοί: • Αυτή η ρύθμιση ενεργοποιεί όχι μόνο τις χρονοσφραγίδες, αλλά επιτρέπει την απόκρυψη των στοιχείων UI πατώντας στο φόντο της οθόνης αναπαραγωγής. -• Δεδομένου ότι αυτή είναι μια λειτουργία της Google που βρίσκεται ακόμη στο στάδιο ανάπτυξης, η διεπαφή μπορεί να χαλάσει." +• Δεδομένου ότι αυτή είναι μια λειτουργία της Google που βρίσκεται ακόμη στο στάδιο ανάπτυξης, η διάταξη μπορεί να χαλάσει." Οι χρονοσφραγίδες είναι απενεργοποιημένες. Ενέργεια πατήματος χρονοσφραγίδας Πατήστε παρατεταμένα την χρονοσφραγίδα για να αλλάξει η κατάσταση επανάληψης των Shorts. @@ -1496,10 +1609,14 @@ Playlists Η χαμηλότερη τιμή της χειρονομίας φωτεινότητας ενεργοποιεί την αυτόματη φωτεινότητα. Η χαμηλότερη τιμή της χειρονομίας φωτεινότητας δεν ενεργοποιεί την αυτόματη φωτεινότητα. Έλεγχος σάρωσης για Φωτεινότητα - Η αλλαγή φωτεινότητας με χειρονομία σάρωσης στην αριστερή πλευρά της οθόνης είναι ενεργοποιημένη. + "Η χειρονομία σάρωσης για αλλαγή φωτεινότητας στην πλήρη οθόνη είναι ενεργοποιημένη. + +Προσαρμόστε τη φωτεινότητα σαρώνοντας κάθετα στην αριστερή πλευρά της οθόνης." Η αλλαγή φωτεινότητας με χειρονομία σάρωσης στην αριστερή πλευρά της οθόνης είναι απενεργοποιημένη. Έλεγχος σάρωσης για Ένταση Ήχου - Η αλλαγή έντασης ήχου με χειρονομία σάρωσης στη δεξιά πλευρά της οθόνης είναι ενεργοποιημένη. + "Η χειρονομία σάρωσης για αλλαγή έντασης ήχου στην πλήρη οθόνη είναι ενεργοποιημένη. + +Προσαρμόστε την ένταση ήχου σαρώνοντας κάθετα στη δεξιά πλευρά της οθόνης." Η αλλαγή έντασης ήχου με χειρονομία σάρωσης στη δεξιά πλευρά της οθόνης είναι απενεργοποιημένη. Αποθήκευση και επαναφορά φωτεινότητας Η φωτεινότητα αποθηκεύεται και επαναφέρεται κατά την έξοδο ή την είσοδο σε πλήρη οθόνη. @@ -1513,10 +1630,20 @@ Playlists Χρήση στη λειτουργία «Οθόνη κλειδώματος» Οι χειρονομίες σάρωσης είναι ενεργοποιημένες στη λειτουργία «Οθόνη κλειδώματος». Οι χειρονομίες σάρωσης είναι απενεργοποιημένες στη λειτουργία «Οθόνη κλειδώματος». - Ορατότητα φόντου σάρωσης - Η ορατότητα του φόντου σάρωσης στο παρασκήνιο. Κατώτατο όριο μεγέθους σάρωσης Ελάχιστο πλάτος κίνησης αναγνωρίσιμο ως χειρονομία σάρωσης. + Εναλλακτική διεπαφή χρήστη για σάρωση + Χρησιμοποιείται η εναλλακτική διεπαφή χρήστη. + Χρησιμοποιείται η παλιά διεπαφή χρήστη. + Διάταξη μινιμαλιστικού στυλ + Το μινιμαλιστικό στυλ διάταξης των ελέγχων σάρωσης είναι ενεργοποιημένο. + Το μινιμαλιστικό στυλ διάταξης των ελέγχων σάρωσης είναι απενεργοποιημένο. + Εμφάνιση κυκλικής διάταξης + Η διάταξη των ελέγχων σάρωσης είναι κυκλική. + Η διάταξη των ελέγχων σάρωσης είναι οριζόντια. + Αδιαφάνεια φόντου σάρωσης + Τιμή αδιαφάνειας μεταξύ 0-100. + Η αδιαφάνεια σάρωσης πρέπει να είναι μεταξύ 0-100. Μέγεθος κειμένου φόντου σάρωσης Το μέγεθος κειμένου στοιχείων ελέγχου του φόντου σάρωσης. Μέγεθος περιοχής οθόνης σάρωσης @@ -1548,57 +1675,11 @@ Playlists Αυτόματη Βίντεο - Προεπιλεγμένη ταχύτητα αναπαραγωγής - Προεπιλεγμένη ποιότητα βίντεο με δεδομένα κινητής τηλεφωνίας - Προεπιλεγμένη ποιότητα βίντεο με Wi-Fi + + Κωδικοποιητής Απενεργοποίηση βίντεο HDR Τα βίντεο που υποστηρίζουν HDR δεν αναπαράγονται σε HDR ποιότητα. Τα βίντεο που υποστηρίζουν HDR αναπαράγονται σε HDR ποιότητα. - Προσαρμοσμένη ταχύτητα αναπαραγωγής - Η προσαρμοσμένη ταχύτητα αναπαραγωγής είναι ενεργοποιημένη. - Η προσαρμοσμένη ταχύτητα αναπαραγωγής είναι απενεργοποιημένη. - Τύπος μενού ταχύτητας αναπαραγωγής - Εμφανίζεται προσαρμοσμένο παράθυρο. - Εμφανίζεται το αναδυόμενο μενού παλιού στυλ. - Επεξεργασία ταχυτήτων αναπαραγωγής - Προσθέστε ή αλλάξτε τις διαθέσιμες ταχύτητες αναπαραγωγής. - Απομνημόνευση αλλαγών ταχύτητας αναπαραγωγής - Οι αλλαγές ταχύτητας αναπαραγωγής ισχύουν για όλα τα βίντεο. - Οι αλλαγές ταχύτητας αναπαραγωγής ισχύουν μόνο για το τρέχον βίντεο. - Εμφάνιση μηνύματος - Εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης κατά την αλλαγή προεπιλεγμένης ταχύτητας αναπαραγωγής. - Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης κατά την αλλαγή προεπιλεγμένης ταχύτητας αναπαραγωγής. - Απομνημόνευση αλλαγών ποιότητας βίντεο - Οι αλλαγές ποιότητας ισχύουν για όλα τα βίντεο. - Οι αλλαγές ποιότητας ισχύουν μόνο για το τρέχον βίντεο. - Εμφάνιση μηνύματος - Εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης κατά την αλλαγή προεπιλεγμένης ποιότητας βίντεο. - Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης κατά την αλλαγή προεπιλεγμένης ποιότητας βίντεο. - Μενού ποιότητας βίντεο παλιού στυλ - Εμφανίζεται το μενού αλλαγής ποιότητας βίντεο παλιού στυλ. - Εμφανίζεται το μενού αλλαγής ποιότητας βίντεο νέου στυλ. - Απενεργοποίηση αλλαγής ταχύτητας σε βίντεο μουσικής - Η προεπιλεγμένη ταχύτητα δεν εφαρμόζεται σε βίντεο μουσικής. - Η προεπιλεγμένη ταχύτητα εφαρμόζεται σε βίντεο μουσικής. - Επικύρωση με βάση τις κατηγορίες - Η προεπιλεγμένη ταχύτητα αναπαραγωγής είναι απενεργοποιημένη αν η κατηγορία βίντεο είναι μουσική. - Η προεπιλεγμένη ταχύτητα αναπαραγωγής είναι απενεργοποιημένη για βίντεο που μπορούν να αναπαραχθούν στο YouTube Music. - Αλλαγή προεπιλεγμένης ταχύτητας Shorts - Η προεπιλεγμένη ταχύτητα αναπαραγωγής εφαρμόζεται στα Shorts. - Η προεπιλεγμένη ταχύτητα αναπαραγωγής δεν εφαρμόζεται στα Shorts. - Η προφόρτωση βίντεο παραλείφθηκε. - Παράλειψη προφόρτωσης βίντεο - "Παράλειψη προφόρτωσης στην αρχή του βίντεο, ώστε να γίνει άμεση εφαρμογή της προεπιλεγμένης ποιότητας. - -• Κατά την έναρξη του βίντεο, υπάρχει μια καθυστέρηση περίπου 0.3 δευτερολέπτων. -• Δεν εφαρμόζεται σε βίντεο HDR, ζωντανές μεταδόσεις ή βίντεο μικρότερα από 15 δευτερόλεπτα." - Η ενεργοποίηση αυτής της ρύθμισης μπορεί να προκαλέσει προβλήματα αναπαραγωγής βίντεο. - Εμφάνιση μηνύματος κατά την παράλειψη - Εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης. - Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης. - Παραποίηση διαστάσεων συσκευής - "Παραποίηση διαστάσεων συσκευής στη μέγιστη τιμή. -Ενδέχεται να ξεκλειδωθούν υψηλότερες ποιότητες σε κάποια βίντεο που απαιτούν υψηλές διαστάσεις συσκευής, αλλά όχι σε όλα τα βίντεο." Απενεργοποίηση κωδικοποιητή VP9 "Ο κωδικοποιητής VP9 είναι απενεργοποιημένος. @@ -1608,16 +1689,86 @@ Playlists Ο κωδικοποιητής VP9 είναι ενεργοποιημένος. Αντικατάσταση κωδικοποιητή AV1 Αντικατάσταση του κωδικοποιητή λογισμικού AV1 με τον κωδικοποιητή VP9. - Απόρριψη απόκρισης κωδικοποιητή AV1 - "Εξαναγκαστική απόρριψη της απόκρισης του κωδικοποιητή λογισμικού AV1. -Μετά από περίπου 20 δευτερόλεπτα φόρτωσης, θα γίνεται αλλαγή σε διαφορετικό κωδικοποιητή." - Η διαδικασία προκαλεί περίπου 20 δευτερόλεπτα φόρτωσης. - Η προεπιλεγμένη ταχύτητα άλλαξε σε %s. - Η προεπιλεγμένη ποιότητα δεδομένων άλλαξε σε %s. - Αποτυχία ορισμού ποιότητας βίντεο. - Η προεπιλεγμένη ποιότητα με Wi-Fi άλλαξε σε %s. + + Ταχύτητα αναπαραγωγής + Προεπιλεγμένη ταχύτητα αναπαραγωγής + Απομνημόνευση αλλαγών ταχύτητας αναπαραγωγής + Οι αλλαγές ταχύτητας αναπαραγωγής ισχύουν για όλα τα βίντεο. + Οι αλλαγές ταχύτητας αναπαραγωγής ισχύουν μόνο για το τρέχον βίντεο. + Εμφάνιση μηνύματος + Εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης κατά την αλλαγή προεπιλεγμένης ταχύτητας αναπαραγωγής. + Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης κατά την αλλαγή προεπιλεγμένης ταχύτητας αναπαραγωγής. + Προεπιλεγμένη ταχύτητα αναπαραγωγής στα Shorts + Απομνημόνευση αλλαγών ταχύτητας αναπαραγωγής στα Shorts + "Οι αλλαγές ταχύτητας αναπαραγωγής ισχύουν για όλα τα Shorts. + +Πληροφορίες: +Ο μόνος τρόπος για αλλαγή της ταχύτητας αναπαραγωγής στα Shorts είναι η χρήση της λειτουργίας «Παράθυρο αλλαγής ταχύτητας» στις «Προσαρμοσμένες ενέργειες». Αν δεν συμπεριλάβατε την τροποποίηση «Shorts components», δε θα είναι διαθέσιμα." + Οι αλλαγές ταχύτητας αναπαραγωγής ισχύουν μόνο για το τρέχον Short. + Εμφάνιση μηνύματος + Εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης κατά την αλλαγή της προεπιλεγμένης ταχύτητας αναπαραγωγής στα Shorts. + Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης κατά την αλλαγή της προεπιλεγμένης ταχύτητας αναπαραγωγής στα Shorts. + Η προεπιλεγμένη ταχύτητα άλλαξε σε: %s. + Η προεπιλεγμένη ταχύτητα Shorts άλλαξε σε: %s. + Προσαρμοσμένη ταχύτητα αναπαραγωγής + Η προσαρμοσμένη ταχύτητα αναπαραγωγής είναι ενεργοποιημένη. + Η προσαρμοσμένη ταχύτητα αναπαραγωγής είναι απενεργοποιημένη. + Τύπος μενού ταχύτητας αναπαραγωγής + Εμφανίζεται προσαρμοσμένο παράθυρο. + Εμφανίζεται το αναδυόμενο μενού παλιού στυλ. + Επεξεργασία ταχυτήτων αναπαραγωγής + Προσθέστε ή αλλάξτε τις διαθέσιμες ταχύτητες αναπαραγωγής. Οι ταχύτητες πρέπει να είναι μικρότερες από %sx. Μη έγκυρες ταχύτητες αναπαραγωγής. + + Ποιότητα βίντεο + Προεπιλεγμένη ποιότητα βίντεο σε δίκτυο κινητής τηλεφωνίας + Προεπιλεγμένη ποιότητα βίντεο σε δίκτυο Wi-Fi + Απομνημόνευση αλλαγών ποιότητας βίντεο + Οι αλλαγές ποιότητας ισχύουν για όλα τα βίντεο. + Οι αλλαγές ποιότητας ισχύουν μόνο για το τρέχον βίντεο. + Εμφάνιση μηνύματος + Εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης κατά την αλλαγή προεπιλεγμένης ποιότητας βίντεο. + Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης κατά την αλλαγή προεπιλεγμένης ποιότητας βίντεο. + Προεπιλεγμένη ποιότητα Shorts σε δίκτυο κινητής τηλεφωνίας + Προεπιλεγμένη ποιότητα Shorts σε δίκτυο Wi-Fi + Απομνημόνευση αλλαγών ποιότητας στα Shorts + Οι αλλαγές ποιότητας ισχύουν για όλα τα Shorts. + Οι αλλαγές ποιότητας ισχύουν μόνο για το τρέχον Short. + Εμφάνιση μηνύματος + Εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης κατά την αλλαγή της προεπιλεγμένης ποιότητας στα Shorts. + Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης κατά την αλλαγή της προεπιλεγμένης ποιότητας στα Shorts. + δεδομένων + Wi-Fi + Η προεπιλεγμένη ποιότητα %1$s άλλαξε σε: %2$s. + Η προεπιλεγμένη ποιότητα Shorts με %1$s άλλαξε σε %2$s. + Μενού ποιότητας βίντεο παλιού στυλ + Εμφανίζεται το μενού αλλαγής ποιότητας βίντεο παλιού στυλ. + Εμφανίζεται το μενού αλλαγής ποιότητας βίντεο νέου στυλ. + Η προφόρτωση βίντεο παραλείφθηκε. + Παράλειψη προφόρτωσης βίντεο + "Παράλειψη προφόρτωσης στην αρχή του βίντεο, ώστε να γίνει άμεση εφαρμογή της προεπιλεγμένης ποιότητας. + +• Κατά την έναρξη του βίντεο, υπάρχει μια καθυστέρηση περίπου 0.3 δευτερολέπτων. +• Δεν εφαρμόζεται σε βίντεο HDR, ζωντανές μεταδόσεις ή βίντεο μικρότερα από 15 δευτερόλεπτα. +• Η λειτουργία «Παραποίηση δεδομένων ροής» παραλείπει επίσης από μόνη της την προφόρτωση, οπότε αν είναι ενεργοποιημένη τότε αυτή η λειτουργία δε χρειάζεται." + Η ενεργοποίηση αυτής της ρύθμισης μπορεί να προκαλέσει προβλήματα αναπαραγωγής βίντεο. + Εμφάνιση μηνύματος κατά την παράλειψη + Εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης. + Δεν εμφανίζεται μήνυμα στο κάτω μέρος της οθόνης. + Παραποίηση διαστάσεων συσκευής + "Παραποίηση των διαστάσεων συσκευής στη μέγιστη τιμή. + +Πληροφορίες: +• Ενδέχεται να ξεκλειδωθούν υψηλότερες ποιότητες βίντεο σε ορισμένα βίντεο που απαιτούν μεγάλες διαστάσεις συσκευής, αλλά όχι σε όλα. +• Αυτή η λειτουργία δεν είναι διαθέσιμη αν η λειτουργία «Παραποίηση ροών βίντεο» είναι ενεργοποιημένη." + + Απενεργοποίηση αλλαγής ταχύτητας σε βίντεο μουσικής + Η προεπιλεγμένη ταχύτητα δεν εφαρμόζεται σε βίντεο μουσικής. + Η προεπιλεγμένη ταχύτητα εφαρμόζεται σε βίντεο μουσικής. + Επικύρωση με βάση τις κατηγορίες + Η προεπιλεγμένη ταχύτητα αναπαραγωγής είναι απενεργοποιημένη αν η κατηγορία βίντεο είναι μουσική. + Η προεπιλεγμένη ταχύτητα αναπαραγωγής είναι απενεργοποιημένη για βίντεο που μπορούν να αναπαραχθούν στο YouTube Music. Return YouΤube Dislike Επιστροφή του «Δεν μου αρέσει» στο YouTube @@ -1747,6 +1898,7 @@ Playlists Εμφάνιση κουμπιού παράλειψης Εμφάνιση στη γραμμή προόδου Απενεργοποίηση + Αδιαφάνεια: Χρώμα: Το χρώμα άλλαξε. Το χρώμα επαναφέρθηκε. @@ -1813,6 +1965,8 @@ Playlists Αρνητική ψήφος Αλλαγή κατηγορίας Δεν υπάρχουν τμήματα προς ψήφιση. + + %1$s έως %2$s Επιλέξτε την κατηγορία τμήματος Η κατηγορία είναι απενεργοποιημένη στις ρυθμίσεις. Ενεργοποιήστε την κατηγορία για υποβολή. Νέο τμήμα SponsorBlock @@ -1934,8 +2088,8 @@ Playlists Android VR "Android VR (χωρίς auth)" - "iOS -(Απαιτείται αναγνωριστικό PoToken)" + "iOS +(Ξεπερασμένο)" "iOS TV (Απαιτείται σύνδεση)" Παρενέργειες παραποίησης @@ -1943,9 +2097,20 @@ Playlists • Η λειτουργία «Σταθερή ένταση» δεν είναι διαθέσιμη. • Η λειτουργία «Απενεργοποίηση υποχρεωτικών κομματιών ήχου» δεν είναι διαθέσιμη. • Τα βίντεο για παιδιά ενδέχεται να μην αναπαράγονται αν είστε αποσυνδεμένοι ή σε λειτουργία ανώνυμης περιήγησης." - • Ενδέχεται να υπάρχουν προβλήματα αναπαραγωγής (Απαιτείται αναγνωριστικό PoToken). - "• Οι ταινίες ή τα επί πληρωμή βίντεο ενδέχεται να μην αναπαράγονται. + • Ολόκληρα αυτόνομα συστήματα (ASN) ή εύρη διευθύνσεων IP ενδέχεται να αποκλειστούν από τον διακομιστή. + "• Η λειτουργία «Σταθερή ένταση» δεν είναι διαθέσιμη. +• Οι ταινίες ή τα επί πληρωμή βίντεο ενδέχεται να μην αναπαράγονται. • Τα βίντεο για παιδιά ενδέχεται να μην αναπαράγονται αν είστε αποσυνδεμένοι ή σε λειτουργία ανώνυμης περιήγησης." + Χρήση του προγράμματος πελάτη iOS + "Το iOS προστέθηκε στα διαθέσιμα προγράμματα πελάτη. + +ΠΡΟΕΙΔΟΠΟΙΗΣΗ: Το πρόγραμμα πελάτη iOS είναι ξεπερασμένο. Οποιοδήποτε θέμα προκύψει κατά τη χρήση του είναι με δική σας ευθύνη." + Το iOS δεν προστέθηκε στα διαθέσιμα προγράμματα πελάτη. + "Κατά την αποστολή αιτημάτων προς το YouTube API endpoint μέσω iOS, απαιτούνται τα Auth token που εκδίδονται από τη συσκευή iOS και τα PoToken που εκδίδονται από το iOSGuard. + +Αυτό σημαίνει ότι τα αιτήματα ροής μέσω iOS δεν θα περιλαμβάνουν ούτε τα Auth token ούτε τα PoToken, γεγονός που μπορεί να οδηγήσει τον διακομιστή στο να θεωρήσει τον χρήστη ως bot και να αποκλείσει ολόκληρο το ASN ή τα εύρη διευθύνσεων IP. + +ΧΡΗΣΙΜΟΠΟΙΗΣΤΕ ΤΟ ΜΕ ΔΙΚΗ ΣΑΣ ΕΥΘΥΝΗ!" Εξαναγκασμός iOS AVC (H.264) Ο κωδικοποιητής βίντεο έχει οριστεί υποχρεωτικά σε AVC (H.264). Ο κωδικοποιητής βίντεο ορίζεται αυτόματα. @@ -1964,6 +2129,8 @@ Playlists Το πρόγραμμα πελάτη που χρησιμοποιείται για τη λήψη δεδομένων ροής εμφανίζεται στο μενού «Στατιστικά για σπασίκλες». Το πρόγραμμα πελάτη που χρησιμοποιείται για τη λήψη δεδομένων ροής δεν εμφανίζεται στο μενού «Στατιστικά για σπασίκλες». Προεπιλεγμένη γλώσσα ροής ήχου VR + Αδυναμία λήψης ροής του προγράμματος πελάτη. + Μπορεί να μην έχετε συνδεθεί. PoToken / VisitorData PoToken προς χρήση 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 059f0331f..6ccc07135 100644 --- a/patches/src/main/resources/youtube/translations/es-rES/strings.xml +++ b/patches/src/main/resources/youtube/translations/es-rES/strings.xml @@ -19,59 +19,150 @@ "%1$s no está instalado. Por favor, descarga %2$s desde el sitio web." %s no está instalado. Por favor, instálalo. + Añadir a la cola + Añadir a la cola y abrir la cola + Añadir a la cola y reproducir vídeo + Descargador externo + Abrir cola + Cola + Eliminar de la cola + Eliminar de la cola y abrir la cola + Eliminar cola + Guardar cola + "En lugar de abrir un descargador externo, abre el diálogo del gestor de colas. + +También puedes abrir el gestor de colas manteniendo presionado el botón Atrás en la barra de navegación. + +Esta función aún está en desarrollo, por lo que es posible que la mayoría de las funciones no funcionen. + +Por favor, utilízala solo con fines de depuración." + Es necesario iniciar sesión + Gestor de colas no disponible (%s). + No se pudo identificar la lista + La cola está vacía + No se pudo identificar el vídeo + Error al añadir el vídeo. + Error al crear la cola. + Error al eliminar la cola. + Error al eliminar el vídeo. + Error al guardar la cola. + Vídeo añadido correctamente. + Cola creada correctamente. + Cola eliminada correctamente. + Vídeo eliminado correctamente. + Cola guardada correctamente en \'%s\'. Idioma de RVX Idioma de la app - Árabe - Azerbaiyano - Búlgaro - Bengalí - Catalán - Checo - Danés - Alemán - Griego - Inglés - Español - Estoniano - Persa - Finlandés - Francés - Guyaratí - Hindi - Croata - Húngaro - Indonesio - Italiano - Japonés - Kazajo - Coreano - Lituano - Letón - Macedonio - Mongol - Maratí - Malayo - Birmano - Holandés - Odia - Punyabí - Polaco - Portugués - Rumano - Ruso - Eslovaco - Esloveno - Serbio - Sueco - Suajili - Tamil - Télugu - Tailandés - Turco - Ucraniano - Urdu - Vietnamita - Chino + "Amárico +አማርኛ" + "Árabe +العربية" + "Azerbaiyano +Azərbaycan" + "Bielorruso +беларуская" + "Búlgaro +Български" + "Bengalí +বাংলা" + "Catalán +Català" + "Checo +Čeština" + "Danés +Dansk" + "Alemán +Deutsch" + "Griego +Ελληνικά" + "Inglés +English" + "Español +Español" + "Estonio +Eesti" + "Persa +فارسی" + "Finlandés +Suomi" + "Francés +Français" + "Gujarati +ગુજરાતી" + "Hebreo +עברי" + "Hindi +हिन्दी" + "Croata +Hrvatski" + "Húngaro +Magyar" + "Indonesio +Indonesia" + "Italiano +Italiano" + "Japonés +日本語" + "Kazajo +Қазақ тілі" + "Coreano +한국어" + "Lituano +Lietuvių" + "Letón +Latviešu" + "Macedonio +Македонски" + "Mongol +Монгол" + "Maratí +मराठी" + "Malayo +Melayu" + "Birmano +ဗမာ" + "Neerlandés +Nederlands" + "Odia +ଓଡ଼ିଆ" + "Punjabi +ਪੰਜਾਬੀ" + "Polaco +Polski" + "Portugués +Português" + "Rumano +Română" + "Ruso +Русский" + "Eslovaco +Slovenčina" + "Albanés +Shqip" + "Esloveno +Slovenščina" + "Serbio +Српски" + "Sueco +Svenska" + "Suajili +Kiswahili" + "Tamil +தமிழ்" + "Télugu +తెలుగు" + "Tailandés +ไทย" + "Turco +Türkçe" + "Ucraniano +Українська" + "Urdu +اردو" + "Vietnamita +Tiếng Việt" + "Chino +中文" Anuncios Ocultar banner de tienda de pantalla final @@ -391,9 +482,6 @@ Limitación: Es posible que el botón Atrás de la barra de herramientas no func Desactivar animación de bienvenida La animación de bienvenida está desactivada. La animación de bienvenida está activada. - Desactivar barra de estado translúcida - La barra de estado es opaca. - La barra de estado es opaca o translúcida. Activar pantalla de carga de degradado La pantalla de carga de degradado está activada. La pantalla de carga de degradado está desactivada. @@ -425,6 +513,23 @@ Diseño para automóvil • Los Shorts se abren en el reproductor normal. • El feed está organizado por temas y canales. • La descripción del vídeo no se puede abrir cuando está desactivado \"Falsificar datos de transmisión\"." + Desactivar actualizaciones de diseño + El servidor no actualizará el diseño. + El servidor actualizará el diseño. + "El diseño de la aplicación vuelve al diseño que estaba usando cuando se instaló por primera vez. + +Es posible que algunos diseños del lado del servidor no se reviertan. + +Los cambios incluyen: +• Los componentes del menú desplegable del reproductor (o la configuración relacionada) pueden no funcionar. +• Los números rodantes no están animados. +• Se utiliza la pestaña Biblioteca. +• La sección Música de la descripción del vídeo puede no funcionar. +• El botón de cambio de cuenta puede no aparecer en la pestaña Biblioteca. Utilice la función 'Activar barra de búsqueda ancha en la pestaña Tú'." + Desactivar barra de estado translúcida + La barra de estado es opaca. + La barra de estado es opaca o translúcida. + Para algunas ROM de fabricantes que ejecutan Android 12+, activar esta función puede hacer que la barra de navegación del sistema sea transparente. Falsificar versión de la app Versión falsificada Versión no falsificada @@ -442,8 +547,10 @@ Si se desactiva más tarde, se recomienda borrar los datos de la aplicación par 18.33.40 - Restaura la antigua barra de acción de Shorts 18.38.45 - Restaura el antiguo comportamiento de la calidad predeterminada de vídeo 18.48.39 - Desactiva la actualización en tiempo real de las visualizaciones y los me gusta + 19.01.34 - Desactivar la interacción con la descripción del vídeo 19.26.42 - Desactivar icono de Cairo en la barra de navegación y herramientas 19.33.37 - Restaurar el antiguo panel desplegable de velocidad de reproducción + Versión de aplicación falsa no válida: %s. Menú de cuenta Ocultar o mostrar elementos en el menú de la cuenta y la pestaña Tú. @@ -479,6 +586,9 @@ Algunos componentes pueden no estar ocultos." Reemplazar botón de descarga de vídeo El botón nativo de descarga de vídeo abre tu descargador externo. El botón nativo de descarga de vídeo abre el descargador nativo de la aplicación. + Gestor de colas + El botón nativo de descarga de vídeo abre el gestor de colas. + El botón nativo de descarga de vídeo abre tu descargador externo. Nombre del paquete del descargador de listas de reproducción Nombre del paquete de tu aplicación de descargas externas instalada, como YTDLnis. @@ -532,7 +642,6 @@ 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. @@ -1111,6 +1220,8 @@ Mantén pulsado para copiar la marca de tiempo del vídeo." Pulsa para silenciar el volumen del vídeo actual. Pulsa de nuevo para reactivar el sonido. Mostrar botón de descarga externa Pulsa para iniciar el descargador externo. + Gestor de colas + En lugar de iniciar un descargador externo, abre el gestor de colas. Mostrar botón de diálogo de velocidad "Pulsa para abrir el diálogo de velocidad. Mantén pulsado para establecer la velocidad de reproducción en 1.0x." @@ -1432,6 +1543,10 @@ Mantén pulsado el botón Más para mostrar el cuadro de diálogo Acciones perso Mostrar menú de abrir vídeo El menú de abrir vídeo está visible. El menú de abrir vídeo está oculto. + Diálogo de velocidad + Mostrar menú de diálogo de velocidad + El menú de diálogo de velocidad está visible. + El menú de diálogo de velocidad está oculto. Estado de repetición Mostrar menú de estado de repetición El menú del estado de repetición está visible. @@ -1473,10 +1588,10 @@ No hay márgenes en la parte superior e inferior del reproductor." El valor más bajo del gesto de brillo activa el brillo automático. El valor más bajo del gesto de brillo no activa el brillo automático. Activar gesto de brillo - El control del brillo al deslizar está activado. + "El control del brillo al deslizar está activado." El control del brillo al deslizar está desactivado. Activar gesto de volumen - El control del volumen al deslizar está activado. + "El control del volumen al deslizar está activado." El control del volumen al deslizar está desactivado. Activar guardar y restaurar brillo Guarda y restaura el brillo al salir o entrar en pantalla completa. @@ -1490,10 +1605,20 @@ No hay márgenes en la parte superior e inferior del reproductor." Gestos deslizantes en modo \"Bloquear pantalla\" Los gestos deslizantes están activados en el modo \"Bloquear pantalla\". Los gestos deslizantes están desactivados en el modo \"Bloquear pantalla\". - Visibilidad de fondo de deslizamiento - La visibilidad del fondo de superposición de deslizamiento. Umbral de magnitud de deslizamiento La cantidad de umbral para que ocurra el deslizamiento. + Interfaz de usuario alternativa de superposición de deslizamiento + Se utiliza una interfaz de usuario alternativa. + Se utiliza la interfaz de usuario heredada. + Activar estilo minimalista + El estilo de superposición mínimo está activado. + El estilo de superposición mínimo está desactivado. + Mostrar superposición circular + La superposición circular está visible. + La superposición horizontal está visible. + Opacidad de fondo de superposición de deslizamiento + Valor de opacidad entre 0-100. + La opacidad de deslizamiento debe estar entre 0-100. Tamaño del texto de superposición de deslizamiento El tamaño del texto para la superposición de deslizamiento. Tamaño de pantalla de superposición deslizante @@ -1525,12 +1650,42 @@ No hay márgenes en la parte superior e inferior del reproductor." Automático Vídeo - Velocidad predeterminada de reproducción - Calidad predeterminada de vídeo en red móvil - Calidad predeterminada de vídeo en red Wi-Fi + + Códec Desactivar vídeo HDR El vídeo HDR está desactivado. El vídeo HDR está activado. + Desactivar códec VP9 + "El códec VP9 está desactivado. + +• La resolución máxima es 1080p. +• La reproducción de vídeo utilizará más datos de Internet que VP9. +• Para obtener reproducción HDR, el vídeo HDR sigue utilizando el códec VP9." + El códec VP9 está activado. + Reemplazar códec AV1 del software + Reemplaza el códec AV1 del software con el códec VP9. + + Velocidad de reproducción + Velocidad predeterminada de reproducción + Recordar cambios de velocidad de reproducción + Los cambios de velocidad de reproducción se aplican a todos los vídeos. + Los cambios de velocidad de reproducción solo se aplican al vídeo actual. + Mostrar un mensaje + Se mostrará un mensaje al cambiar la velocidad de reproducción predeterminada. + No se mostrará un mensaje al cambiar la velocidad de reproducción predeterminada. + Velocidad predeterminada de reproducción en Shorts + Recordar cambios de velocidad de reproducción en Shorts + "Los cambios en la velocidad de reproducción se aplican a todos los Shorts. + +Información: +• La única forma de cambiar la velocidad de reproducción en el reproductor de Shorts es usar el 'Diálogo de velocidad' en 'Acciones personalizadas'. +• Si no incluyó el parche 'Componentes de Shorts', esto no estaría disponible." + Los cambios de velocidad de reproducción solo se aplican al Short actual. + Mostrar un mensaje + Se mostrará un mensaje al cambiar la velocidad predeterminada de reproducción en los Shorts. + No se mostrará un mensaje al cambiar la velocidad predeterminada de reproducción en los Shorts. + Se ha cambiado la velocidad predeterminada de reproducción a: %s. + Se ha cambiado la velocidad predeterminada de reproducción de Shorts a: %s. Activar velocidad personalizada de reproducción La velocidad personalizada de reproducción está activada. La velocidad personalizada de reproducción está desactivada. @@ -1539,30 +1694,33 @@ No hay márgenes en la parte superior e inferior del reproductor." Se utiliza el antiguo estilo del panel desplegable. Editar velocidades personalizadas de reproducción Añadir o cambiar las velocidades de reproducción disponibles. - Recordar cambios de velocidad de reproducción - Los cambios de velocidad de reproducción se aplican a todos los vídeos. - Los cambios de velocidad de reproducción solo se aplican al vídeo actual. - Mostrar un mensaje - Se mostrará un mensaje al cambiar la velocidad de reproducción predeterminada. - No se mostrará un mensaje al cambiar la velocidad de reproducción predeterminada. + Las velocidades personalizadas deben ser inferiores a %sx. Utilizando valores predeterminados. + Velocidades personalizadas de reproducción no válidas. Utilizando valores predeterminados. + + Calidad de vídeo + Calidad predeterminada de vídeo en red móvil + Calidad predeterminada de vídeo en red Wi-Fi Recordar cambios de calidad de vídeo Los cambios de calidad se aplican a todos los vídeos. Los cambios de calidad solo se aplican al vídeo actual. Mostrar un mensaje Se mostrará un mensaje al cambiar la calidad de vídeo predeterminada. No se mostrará un mensaje al cambiar la calidad de vídeo predeterminada. + Calidad predeterminada de Shorts en red móvil + Calidad predeterminada de Shorts en red Wi-Fi + Recordar cambios de calidad de Shorts + Los cambios de calidad se aplican a todos los Shorts. + Los cambios de calidad solo se aplican al Short actual. + Mostrar un mensaje + Se mostrará un mensaje al cambiar la calidad predeterminada de los Shorts. + No se mostrará un mensaje al cambiar la calidad predeterminada de los Shorts. + móvil + Wi-Fi + Se ha cambiado la calidad %1$s predeterminada a: %2$s. + Se ha cambiado la calidad %1$s de los Shorts a: %2$s. Restaurar antiguo menú de calidad de vídeo El antiguo menú de calidad de vídeo está visible. El antiguo menú de calidad de vídeo está oculto. - Desactivar velocidad de reproducción para música - La velocidad predeterminada de reproducción está desactivada para la música. - La velocidad predeterminada de reproducción está activada para la música. - Validar mediante categorías - La velocidad predeterminada de reproducción se desactiva si la categoría de vídeo es Música. - La velocidad predeterminada de reproducción está desactivada para los vídeos reproducibles en YouTube Music. - Activar velocidad predeterminada de reproducción de Shorts - La velocidad predeterminada de reproducción se aplica a los Shorts. - La velocidad predeterminada de reproducción no se aplica a los Shorts. Búfer precargado omitido. Omitir búfer precargado "Omite el búfer precargado al iniciar el vídeo para evitar la demora en la aplicación de la calidad predeterminada del video. @@ -1574,26 +1732,18 @@ No hay márgenes en la parte superior e inferior del reproductor." El mensaje está visible. El mensaje está oculto. Falsificar dimensiones del dispositivo - "Falsifica las dimensiones del dispositivo para desbloquear calidades de vídeo superiores que pueden no estar disponibles en tu dispositivo." - Desactivar códec VP9 - "El códec VP9 está desactivado. + "Falsifica las dimensiones del dispositivo al valor máximo. -• La resolución máxima es 1080p. -• La reproducción de vídeo utilizará más datos de Internet que VP9. -• Para obtener reproducción HDR, el vídeo HDR sigue utilizando el códec VP9." - El códec VP9 está activado. - Reemplazar códec AV1 del software - Reemplaza el códec AV1 del software con el códec VP9. - Rechazar respuesta del códec AV1 del software - "Rechaza fuertemente la respuesta del códec AV1 del software. -Después de unos 20 segundos de búfer, cambia a un códec diferente." - El proceso de Fallback causa unos 20 segundos de búfer. - Cambiando la velocidad predeterminada a %s. - Cambiando la calidad predeterminada con datos móviles a %s. - Error al establecer la calidad de vídeo. - Cambiando la calidad predeterminada con Wi-Fi a %s. - Las velocidades personalizadas deben ser inferiores a %sx. Utilizando valores predeterminados. - Velocidades personalizadas de reproducción no válidas. Utilizando valores predeterminados. +Información: +• La alta calidad puede ser desbloqueada en algunos vídeos que requieren altas dimensiones del dispositivo, pero no en todos los vídeos. +• Este ajuste no está disponible si está activada la opción 'Falsificar datos de streaming'." + + Desactivar velocidad de reproducción para música + La velocidad predeterminada de reproducción está desactivada para la música. + La velocidad predeterminada de reproducción está activada para la música. + Validar mediante categorías + La velocidad predeterminada de reproducción se desactiva si la categoría de vídeo es Música. + La velocidad predeterminada de reproducción está desactivada para los vídeos reproducibles en YouTube Music. Return YouTube Dislike Activar Return YouTube Dislike @@ -1724,6 +1874,7 @@ Toca para ver cómo crear una clave de API." Mostrar botón de omitir Mostrar en barra de progreso Desactivado + Opacidad: Color: Color cambiado. Color restablecido. @@ -1790,6 +1941,8 @@ Toca para ver cómo crear una clave de API." Voto negativo Cambiar categoría No hay segmentos por los cuales votar. + + De %1$s a %2$s Selecciona la categoría del segmento La categoría está desactivada en los ajustes. Activa la categoría para enviar. Nuevo segmento de SponsorBlock @@ -1907,8 +2060,8 @@ Pulsa el botón de continuar y desactiva las optimizaciones de la batería."Android VR "Android VR (Sin autentificación)" - "iOS -(PoToken requerido)" + "iOS +(Obsoleto)" "iOS TV (Inicio de sesión requerido)" Efectos secundarios de falsificación @@ -1916,9 +2069,19 @@ Pulsa el botón de continuar y desactiva las optimizaciones de la batería." - • Puede haber problemas de reproducción (PoToken requerido). + • El servidor puede bloquear ASN/rangos de IP completos. "• Es posible que las películas o los vídeos de pago no se reproduzcan. • Es posible que los vídeos infantiles no se reproduzcan al cerrar la sesión o en modo incógnito." + Utilizar cliente iOS + "El cliente iOS se ha añadido a los clientes disponibles. + +ADVERTENCIA: El cliente iOS está obsoleto. Cualquier problema que surja al utilizarlo es bajo tu propia responsabilidad." + El cliente iOS no se ha añadido a los clientes disponibles. + "Al solicitar puntos finales de la API de YouTube mediante iOS, necesitarás los tokens de autenticación emitidos por el dispositivo iOS y los PoTokens emitidos por iOSGuard. + +Esto significa que las solicitudes de streaming a través de iOS carecerán tanto de los tokens de autenticación como de los PoTokens, y el servidor puede considerar al usuario como un bot y bloquear todo el rango ASN/IP. + +¡UTILÍZALO BAJO TU PROPIO RIESGO!" Forzar iOS AVC (H.264) El códec de vídeo es AVC (H.264). El códec de vídeo se determina automáticamente. @@ -1937,6 +2100,8 @@ AVC tiene una resolución máxima de 1080p, el códec de audio Opus no está dis El cliente utilizado para obtener datos de transmisión se muestra en estadísticas para nerds. El cliente utilizado para obtener datos de transmisión no se muestra en estadísticas para nerds. Idioma de transmisión de audio por defecto VR + No se han podido obtener las transmisiones de ningún cliente. + Puede que no hayas iniciado sesión. PoToken / VisitorData PoToken a utilizar 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 ffd512f00..810163c3d 100644 --- a/patches/src/main/resources/youtube/translations/fr-rFR/strings.xml +++ b/patches/src/main/resources/youtube/translations/fr-rFR/strings.xml @@ -19,59 +19,150 @@ "%1$s n'est pas installé. Veuillez télécharger %2$s à partir du site web." %s n\'est pas installé. Veuillez l’installer. + Ajouter à la file d\'attente + Ajouter à la file d\'attente et ouvrir la file d\'attente + Ajouter à la file d\'attente et lire la vidéo + Téléchargeur externe + Ouvrir la file d\'attente + File d\'attente + Retirer de la file d\'attente + Retirer de la file d\'attente et ouvrir la file d\'attente + Retirer de la file d\'attente + Enregistrer la file d’attente + "Au lieu d’ouvrir un téléchargeur externe, ouvrez la boîte de dialogue du gestionnaire de file d’attente. + +Vous pouvez également accéder au gestionnaire de file d’attente en maintenant appuyé le bouton de retour situé sur la barre de navigation. + +Cette fonctionnalité est encore en cours de développement, donc la plupart des options risquent de ne pas fonctionner. + +Veuillez l’utiliser exclusivement à des fins de débogage." + Connexion requise + Gestionnaire de file d\'attente indisponible (%s). + Impossible d\'identifier la playlist + La file d\'attente est vide + Impossible d\'identifier la vidéo + Échec de l\'ajout de la vidéo. + Échec de la création de la file d\'attente. + Échec de la suppression de la file d\'attente. + Échec de la suppression de la vidéo. + Échec de l\'enregistrement de la file d\'attente. + Vidéo ajoutée avec succès. + File d\'attente créée avec succès. + File d\'attente supprimée avec succès. + Vidéo supprimée avec succès. + File d\'attente enregistrée avec succès dans \'%s\'. Langue RVX Langue de l\'application - Arabe - Azerbaïdjan - Bulgare - Bengali - Catalan - Tchèque - Danois - Allemand - Grec - Anglais - Espagnol - Estonien - Persan - Finlandais - Français - Gujarati - Hindou - Croate - Hongrois - Indonésien - Italien - Japonais - Kazakh - Coréen - Lithuanien - Letton - Macédonien - Mongol - Marathi - Malaisien - Birman - Néerlandais - Odia - Pendjabi - Polonais - Portugais - Roumain - Russe - Slovaque - Slovène - Serbe - Suédois - Swahili - Tamoul - Télougou - Thaïlandais - Turc - Ukrainien - Ourdou - Vietnamien - Chinois + "Amharique +አማርኛ" + "Arabe +العربية" + "Azerbaïdjanais +Azərbaycan" + "Biélorusse +беларуская" + "Bulgare +Български" + "Bengali +বাংলা" + "Catalan +Català" + "Tchèque +Čeština" + "Danois +Dansk" + "Allemand +Deutsch" + "Grec +Ελληνικά" + "Anglais +English" + "Espagnol +Español" + "Estonien +Eesti" + "Persan +فارسی" + "Finnois +Suomi" + "Français +Français" + "Gujarati +ગુજરાતી" + "Hébreu +עברי" + "Hindi +हिन्दी" + "Croate +Hrvatski" + "Hongrois +Magyar" + "Indonésien +Indonesia" + "Italien +Italiano" + "Japonais +日本語" + "Kazakh +Қазақ тілі" + "Coréen +한국어" + "Lituanien +Lietuvių" + "Letton +Latviešu" + "Macédonien +Македонски" + "Mongol +Монгол" + "Marathi +मराठी" + "Malais +Melayu" + "Birman +ဗမာ" + "Néerlandais +Nederlands" + "Odia +ଓଡ଼ିଆ" + "Panjabi +ਪੰਜਾਬੀ" + "Polonais +Polski" + "Portugais +Português" + "Roumain +Română" + "Russe +Română" + "Slovaque +Slovenčina" + "Albanais +Shqip" + "Slovène +Slovenščina" + "Serbe +Српски" + "Suédois +Svenska" + "Swahili +Kiswahili" + "Tamoul +தமிழ்" + "Télougou +తెలుగు" + "Siamois +ไทย" + "Turc +Türkçe" + "Ukrainien +Українська" + "Ourdou +اردو" + "Vietnamien +Tiếng Việt" + "Chinois +中文" Publicités Masquer la bannière de boutique de l\'écran de fin @@ -394,9 +485,6 @@ Limitation : Le bouton Retour de la barre d'outils peut ne pas fonctionner."Désact. l\'animation de démarrage L\'animation de démarrage est désactivé. L\'animation de démarrage est activé. - Désactiver la barre de notification translucide - La barre de notification est opaque. - La barre de notification est opaque ou translucide. Activer dégradé pendant le chargement Le dégradé pendant l\'écran de chargement est activé. Le dégradé pendant l\'écran de chargement est désactivé. @@ -414,6 +502,39 @@ Cela ne contourne pas la restriction d'âge, mais le confirme automatiquement."< 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\'. + Format de mise en page + Par défaut + Téléphone + Téléphone (Max 480 dpi) + Tablette + Tablette (Min 600 dpi) + Automobile + "Les changements sont les suivants : + +Mise en page tablettes +- Les messages de la communauté sont masqués. + +Mise en page automobile +- Les Shorts s'ouvrent dans le lecteur normal. +- Le flux est organisé par thèmes et par chaînes. +- La description de la vidéo ne peut pas être ouverte lorsque l'option « Falsifier les données de diffusion en direct » est désactivée." + Désactiver les mises à jour de la mise en page + La mise en page ne sera pas mise à jour par le serveur. + La mise en page sera mise à jour par le serveur. + "La mise en page de l'application revient à celle utilisée lors de sa première installation. + +Certaines mises en page côté serveur pourraient ne pas être rétablies. + +Les modifications incluent : +• Les composants du menu déroulant du lecteur (ou les paramètres associés) pourraient ne pas fonctionner. +• L'animation en temps réel des nombres ne sont pas animés. +• L'onglet 'Bibliothèque' est utilisé. +• La section 'Musique' de la description des vidéos pourrait ne pas fonctionner. +• Le bouton 'Changer de compte' pourrait ne pas apparaître dans l'onglet 'Bibliothèque'. Veuillez utiliser le paramètre 'Activer la barre de recherche large dans l'onglet Vous'." + Désactiver la barre de notification translucide + La barre de notification est opaque. + La barre de notification est opaque ou translucide. + Pour certaines ROMs de fabricants fonctionnant sous Android 12 ou supérieur, l\'activation de cette fonctionnalité peut rendre la barre de navigation du système transparente. Falsifier la version de l\'app Version falsifiée Version non falsifiée @@ -431,8 +552,10 @@ Si désactivé ultérieurement, il est recommandé d'effacer les données de l'a 18.33.40 - Restaure l\'ancienne barre d\'action Shorts 18.38.45 - Restaure l\'ancien menu de qualité vidéo 18.48.39 - Désactive les \'vues\' et \'j\'aime\' en temps réel + 19.01.34 - Désactiver l\'interaction des descriptions vidéo 19.26.42 - Désactiver l\'icône Cairo de la barre de navigation et d\'outils 19.33.37 - Restaure l\'ancien menu déroulant de vitesse de lecture du lecteur + Version de falsification de l\'app invalide : %s. Menu du compte Masque ou affiche des éléments dans le menu du compte et dans l\'onglet \'Vous\'. @@ -468,6 +591,9 @@ Certains composants peuvent ne pas être masqués." Remplacer le bouton de téléchargement de la vidéo Le bouton \'Télécharger\' natif ouvre votre téléchargeur externe. Le bouton \'Télécharger\' natif ouvre le téléchargeur de l\'appli. + Gestionnaire de file d’attente + Le bouton \'Télécharger\' natif ouvre le gestionnaire de file d\'attente. + Le bouton \'Télécharger\' natif ouvre votre téléchargeur externe. Nom du paquet du téléchargeur de la playlist Nom de package du téléchargeur externe installé, telle que YTDLnis. @@ -521,7 +647,6 @@ 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. @@ -1100,6 +1225,8 @@ Appuyez longuement pour copier l'horodatage de la vidéo." Appuyez pour couper le son de la vidéo en cours. Appuyez à nouveau pour rétablir le son. Aff. bouton téléchargement externe Appuyez pour lancer le téléchargeur externe. + Gestionnaire de file d’attente + Au lieu de lancer un téléchargeur externe, ouvrez le gestionnaire de file d\'attente. Afficher bouton \'Vitesse de lecture\' "Appuyez pour ouvrir des paramètres de vitesse Appuyez longuement pour revenir à la vitesse de lecture à 1.0x. Appuyez longuement à nouveau rétablir les vitesses par défaut." @@ -1424,6 +1551,10 @@ Appuyez longuement sur le bouton \"Plus\" pour afficher les actions personnalis Afficher le menu \'Ouvrir la vidéo\' Le menu \'Ouvrir la vidéo\' est affiché. Le menu \'Ouvrir la vidéo\' est masqué. + Dialogue de vitesse + Afficher menu \'Vitesse de lecture\' + Le menu \'Vitesse de lecture\' est affiché. + Le menu \'Vitesse de lecture\' est masqué. État de répétition Afficher le menu \'État de répétition\' Le menu \'État de répétition\' est affiché. @@ -1467,10 +1598,10 @@ Pas de marges en haut et en bas du lecteur." La valeur la plus basse du geste de luminosité active la luminosité automatique. La valeur la plus basse du geste de luminosité désactive la luminosité automatique. Activer les gestes de luminosité - Les gestes de luminosité sont activé. + "Les gestes de luminosité sont activé." Les gestes de luminosité sont désactivé. Activer les gestes de volume - Les gestes de volume sont activé. + "Les gestes de volume sont activé." Les gestes de volume sont désactivé. Activer enregistr. et restaur. de la luminosité Enregistrer et restaurer la luminosité en quittant ou en entrant en plein écran. @@ -1484,10 +1615,20 @@ Pas de marges en haut et en bas du lecteur." Gestes en mode \'Écran verrouillé\' Les contrôles par gestes sont activés en mode \'Écran verrouillé\'. Les contrôles par gestes sont désactivés en mode \'Écran verrouillé\'. - Visibilité du voile lors des gestes - La visibilité de l\'opacité du voile lors des gestes. Intensité des gestes Seuil de déclenchement pour que le geste de balayage se produise. + Interface alternative à l\'overlay par geste + L\'interface alternative est utilisée. + L\'ancienne interface est utilisée. + Activer le style minimaliste + Le style minimaliste d\'overlay est activé. + Le style minimaliste d\'overlay est désactivé. + Afficher l\'overlay circulaire + L\'overlay circulaire est affiché. + L\'overlay horizontal est affiché. + Opacité de l\'arrière-plan de l\'overlay par geste + Valeur d\'opacité entre 0-100. + L\'opacité doit être comprise entre 0 et 100 pour les gestes. Taille du texte superposé La taille du texte pendant le voile lors du geste. Taille de la zone de gestes @@ -1519,58 +1660,11 @@ Pas de marges en haut et en bas du lecteur." Auto Vitesses et qualités vidéo - Vitesse de lecture par défaut - Qualité vidéo par défaut sur les données mobiles - Qualité vidéo par défaut sur le réseau Wi-Fi + + Codec Déaactiver les vidéos HDR Les vidéos en HDR sont désactivés. Les vidéos en HDR sont activés. - Activer vitesses de lecture perso. - Les vitesses de lecture personnalisées sont activées. - Les vitesses de lecture personnalisées sont désactivées. - Personnaliser le menu \'Vitesse de lecture\' - Affichage de sélection de vitesse personnalisé. - L\'ancien style du menu déroulant est utilisé. - Saisir des vitesses de lecture personnalisées - Ajouter ou modifier les vitesses de lecture disponibles. - Enreg. modif. de la vitesse de lecture - La modification de vitesse de lecture est appliqué pour toutes les vidéos. - La modification de la vitesse de lecture est appliqué pour la vidéo en cours. - Afficher un message - Un message sera affiché lorsque vous modifiez la vitesse de lecture par défaut. - Un message ne sera pas affiché lorsque vous modifiez la vitesse de lecture par défaut. - Enreg. modification de la qualité - La modification de la résolution est appliqué pour toutes les vidéos. - La modification de la résolution est appliqué pour la vidéo en cours. - Afficher un message - Un message sera affiché lorsque vous modifiez la qualité vidéo par défaut. - Un message ne sera pas affiché lorsque vous modifiez la qualité vidéo par défaut. - Restaur. ancien. interface de qualité vidéo - Affiche l\'ancienne interface de qualité vidéo. - Masque la nouvelle interface de qualité vidéo. - Désactiver la vitesse de lecture pour la musique - La vitesse de lecture par défaut est activée pour la musique. - La vitesse de lecture par défaut est activée pour la musique. - Valider en utilisant les catégories - La vitesse de lecture par défaut est désactivée si la catégorie de la vidéo est Musique. - La vitesse de lecture par défaut est désactivée pour les vidéos lues sur YouTube Music. - Activ. vitesses de lecture shorts par défaut - La vitesse de lecture par défaut s\'applique aux Shorts. - La vitesse de lecture par défaut ne s\'applique pas aux Shorts. - Tampon préchargé passé. - Tampon préchargé - "Passe le tampon préchargé au début de la vidéo pour appliquer immédiatement la qualité vidéo. - -Info : -• Au démarrage de la vidéo, il y a un délai d'environ 0.3 seconde. -• Ne s'applique pas aux vidéos HDR, aux diffusions en direct et aux vidéos de moins de 15 secondes." - Activer ce paramètre peut entraîner des problèmes de lecture vidéo. - Affic. message lors du passage - Le message est affiché. - Le message est masqué. - Falsifier les dimensions de l\'appareil - "Falsifie les dimensions de l'appareil a la valeur maximale. -Les hautes qualités peuvent être débloquées sur certaines vidéos qui requièrent des appareils ayant des dimensions élevées, mais pas sur toutes les vidéos." Désactiver le codec VP9 "Le codec VP9 est désactivé. @@ -1580,16 +1674,89 @@ Les hautes qualités peuvent être débloquées sur certaines vidéos qui requi Le codec VP9 est activé. Remplacer le codec AV1 Remplacer le codec AV1 par le codec VP9. - Rejeter la réponse du codec AV1 - "Force le rejet de la réponse du codec AV1. -Un codec différent sera appliqué après environ 20 secondes de mise en mémoire tampon." - Le processus de rejet entraîne une mise en mémoire tampon d\'environ 20 secondes. - Vitesse de lecture modifiée par %s. - La résolution sur les données mobiles a été modifiée par %s. - Impossible de définir la qualité vidéo. - La résolution sur le Wi-Fi a été modifiée par %s. + + Vitesse de lecture + Vitesse de lecture par défaut + Enreg. modif. de la vitesse de lecture + La modification de vitesse de lecture est appliqué pour toutes les vidéos. + La modification de la vitesse de lecture est appliqué pour la vidéo en cours. + Afficher un message + Un message sera affiché lorsque vous modifiez la vitesse de lecture par défaut. + Un message ne sera pas affiché lorsque vous modifiez la vitesse de lecture par défaut. + Vitesse de lecture par défaut sur les Shorts + Enreg. modif. de la vitesse de lecture sur les Shorts + "Les changements de vitesse de lecture s'appliquent à tous les Shorts. + +Info : +• La seule façon de modifier la vitesse de lecture dans le lecteur de Shorts est d'utiliser le 'Dialogue de vitesse' dans 'Actions personnalisées'. + +• Si vous n'avez pas inclus le patch 'Composants pour Shorts' , cette option ne serait pas disponible." + La modification de vitesse de lecture est appliqué pour ce Short. + Afficher un message + Un message sera affiché lorsque vous modifiez la vitesse de lecture par défaut sur les Shorts. + Un message ne sera pas affiché lorsque vous modifiez la vitesse de lecture par défaut sur les Shorts. + Vitesse de lecture par défaut modifiée en : %s. + Vitesse de lecture par défaut des Shorts modifiée en : %s. + Activer vitesses de lecture perso. + Les vitesses de lecture personnalisées sont activées. + Les vitesses de lecture personnalisées sont désactivées. + Personnaliser le menu \'Vitesse de lecture\' + Affichage de sélection de vitesse personnalisé. + L\'ancien style du menu déroulant est utilisé. + Saisir des vitesses de lecture personnalisées + Ajouter ou modifier les vitesses de lecture disponibles. Les vitesses personnalisées doivent être inférieures à %sx. Valeur des vitesses de lecture invalide. + + Qualité vidéo + Qualité vidéo par défaut sur les données mobiles + Qualité vidéo par défaut sur le réseau Wi-Fi + Enreg. modification de la qualité + La modification de la résolution est appliqué pour toutes les vidéos. + La modification de la résolution est appliqué pour la vidéo en cours. + Afficher un message + Un message sera affiché lorsque vous modifiez la qualité vidéo par défaut. + Un message ne sera pas affiché lorsque vous modifiez la qualité vidéo par défaut. + Qualité par défaut des Shorts sur le réseau mobile + Qualité par défaut des Shorts sur le réseau Wi-Fi + Enreg. modification du changement de qualité des Shorts + La modification de la résolution est appliqué pour tous les Shorts. + La modification de la résolution est appliqué pour ce Short. + Afficher un message + Un message sera affiché lorsque vous modifiez la qualité vidéo des Shorts par défaut. + Un message ne sera pas affiché lorsque vous modifiez la qualité vidéo des Shorts par défaut. + mobile + Wi-Fi + Qualité par défaut %1$s modifiée en : %2$s. + Qualité des Shorts %1$s modifiée en : %2$s. + Restaur. ancien. interface de qualité vidéo + Affiche l\'ancienne interface de qualité vidéo. + Masque la nouvelle interface de qualité vidéo. + Tampon préchargé passé. + Tampon préchargé + "Passe le tampon préchargé au début de la vidéo pour appliquer immédiatement la qualité vidéo. + +Info : +• Au démarrage de la vidéo, il y a un délai d'environ 0.3 secondes. +• Ne s'applique pas aux vidéos HDR, aux diffusions en direct et aux vidéos de moins de 15 secondes. +• Le paramètre 'Falsifier les données de lecture en direct' supprime également les tampons préchargés, ce paramètre n'est donc pas nécessaire si vous l'utilisez." + Activer ce paramètre peut entraîner des problèmes de lecture vidéo. + Affic. message lors du passage + Le message est affiché. + Le message est masqué. + Falsifier les dimensions de l\'appareil + "Falsifie les dimensions de l'appareil à la valeur maximale. + +Info : +• La haute qualité peut être débloquée sur certaines vidéos qui nécessitent des dimensions d'appareil élevées, mais pas sur toutes les vidéos. +• Ce paramètre n'est pas disponible si l'option 'Falsifier les données de lecture en direct' est activée." + + Désactiver la vitesse de lecture pour la musique + La vitesse de lecture par défaut est activée pour la musique. + La vitesse de lecture par défaut est activée pour la musique. + Valider en utilisant les catégories + La vitesse de lecture par défaut est désactivée si la catégorie de la vidéo est Musique. + La vitesse de lecture par défaut est désactivée pour les vidéos lues sur YouTube Music. Return YouTube Dislike Activer Return YouTube Dislike @@ -1719,6 +1886,7 @@ Cliquez ici pour découvrir comment créer une clé API." Afficher un bouton \'Passer\' Afficher dans la barre de progression Désactiver + Opacité : Couleur : Couleur modifiée. Couleur réinitialisée. @@ -1785,6 +1953,8 @@ Cliquez ici pour découvrir comment créer une clé API." Voter contre Changer de catégorie Il n\'y a aucun segments pour lesquels voter. + + %1$s vers %2$s Choisissez la catégorie du segment La catégorie est désactivée dans les paramètres. Activez la catégorie pour soumettre. Nouveau segment SponsorBlock @@ -1904,8 +2074,8 @@ Cliquez sur le bouton Continuer et autorisez les modifications d'optimisations." Android VR "Android VR (sans authentification)" - "iOS -(PoToken requis)" + "iOS +(Obsolète)" "iOS TV (Connexion requise)" Effets inconnus de la falsification @@ -1913,19 +2083,40 @@ Cliquez sur le bouton Continuer et autorisez les modifications d'optimisations." • Le volume stable n'est pas disponible. • La désactivation forcée des pistes audio automatiques n'est pas disponible. • Les vidéos pour enfants peuvent ne pas être lues en cas de déconnexion ou en mode incognito." - • Il peut y avoir des problèmes de lecture (PoToken required). - "• Les films ou vidéos payantes peut ne pas être lus. + • Des plages entières d\'ASN/adresses IP peuvent être bloquées par le serveur. + "• Le volume stable n'es pas disponible. +• Les films ou vidéos payantes peut ne pas être lus. • Les vidéos pour enfants peuvent ne pas être lues en cas de déconnexion ou en mode incognito." + Utiliser le client iOS + "Le client iOS a été ajouté aux clients disponibles. + +AVERTISSEMENT : Le client iOS est obsolète. Tout problème survenant lors de son utilisation est à vos propres risques." + Le client iOS n\'a pas été ajouté aux clients disponibles. + "Lors de la requête des points de terminaison de l'API YouTube en utilisant iOS, vous aurez besoin des jetons d'authentification (Auth tokens) émis par l'appareil iOS et des PoTokens émis par iOSGuard. + +Cela signifie que les requêtes de streaming via iOS manqueront à la fois des jetons d'authentification (Auth tokens) et des PoTokens, et le serveur pourrait considérer l'utilisateur comme un bot et bloquer l'ensemble de la plage ASN/IP. + +UTILISEZ À VOS PROPRES RISQUES !" Forcer le codec AVC de iOS (H.264) Le codec vidéo est forcé sur AVC (H.264). Le codec vidéo est déterminé automatiquement. "Activer ceci peut améliorer la durée de vie de la batterie et résoudre les problèmes de lecture saccadée. AVC a une résolution maximale de 1080p, le codec audio Opus n'est pas disponible et la lecture vidéo utilisera plus de données internet que VP9 ou AV1." + Ignorer le chiffrement de réponse Onesie + "Ignorer le chiffrement de réponse Onesie + +• Corrige un nouveau type de problème de lecture rencontré par certains utilisateurs. +• Le codec AV1 peut ne pas être disponible." + "Ne pas ignorer le cryptage de la réponse d'Onesie. + +- Certains utilisateurs peuvent rencontrer un nouveau type de problème de lecture." Afficher dans \'Statistiques pour les nerds\' Le client utilisé pour récupérer les données de lecture en direct est affiché dans \'Statistiques pour les nerds\'. Le client utilisé pour récupérer les données de lecture en direct est masqué dans \'Statistiques pour les nerds\'. Langue de la piste audio par défaut pour VR + Impossible de récupérer les flux du client. + Vous n\'êtes peut-être pas connecté. PoToken / VisitorData PoToken à utiliser 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 e7c37a19e..61f16bae5 100644 --- a/patches/src/main/resources/youtube/translations/hu-rHU/strings.xml +++ b/patches/src/main/resources/youtube/translations/hu-rHU/strings.xml @@ -21,57 +21,6 @@ Töltsd le a(z) %2$s weboldalról." %s nincs telepítve. Kérlek telepítsd. RVX nyelve Alkalmazás nyelve - Arab - Azerbajdzsáni - Bolgár - Bengáli - Katalán - Cseh - Dán - Német - Görög - Angol - Spanyol - Észt - Perzsa - Finn - Francia - Gudzsaráti - Hindi - Horvát - Magyar - Indonéz - Olasz - Japán - Kazah - Koreai - Litván - Lett - Macedón - Mongol - Marathi - Maláj - Burmai - Holland - Odia - Pandzsábi - Lengyel - Portugál - Román - Orosz - Szlovák - Szlovén - Szerb - Svéd - Szuahéli - Tamil - Telugu - Thai - Török - Ukrán - Urdu - Vietnámi - Kínai Hirdetések Záróképernyő üzlet banner elrejtése @@ -394,9 +343,6 @@ Korlátozás: Előfordulhat, hogy az eszköztár Vissza gombja nem működik."Indító animáció letiltása Az indító animáció le van tiltva. Az indító animáció engedélyezett. - Átlátszó állapotsor letiltása - 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élyezett. A színátmenetes betöltési képernyő le van tiltva. @@ -412,7 +358,25 @@ Ez nem kerüli meg a korhatárt. Csak automatikusan fogadja el." Élő közvetítés ikon kattintás műveletének módosítása "A csatorna nyílik meg az ikonra kattintva." Az élő közvetítés nyílik meg az ikonra kattintva. + Elrendezési alaktényező Alapértelmezett + Telefon + Telefon (max 480 dpi) + Tablet + Tablet (min 600 dpi) + Autóipari + "A változások a következők: + +Táblagép elrendezés +• A közösségi bejegyzések el vannak rejtve. + +Autóipari elrendezés +• A shortok a normál lejátszóban nyílnak meg. +• A hírfolyam témák és csatornák szerint van rendezve. +• A videó leírása nem nyitható meg, ha az 'Adatfolyam hamisítása' ki van kapcsolva." + Átlátszó állapotsor letiltása + Az állapotsor nem átlátszó. + Az állapotsor nem átlátszó vagy átlátszó. Alkalmazásverzió hamisítása Verzió hamisítás Verzió nincs hamisítva @@ -520,7 +484,6 @@ Ha ez a beállítás nem működik, váltson inkognító módra." Á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ó. @@ -1464,10 +1427,10 @@ Nincs margó a lejátszó tetején és alján." A gesztus vezérlés legkisebb értékénél az automatikus fényerő bekapcsol. A gesztus vezérlés legkisebb értékénél az automatikus fényerő nem kapcsol be. Fényerő gesztus vezérlés engedélyezése - A fényerő gesztus vezérlés engedélyezett. + "A fényerő gesztus vezérlés engedélyezett." A fényerő gesztus vezérlés le van tiltva. Hangerő gesztus vezérlés engedélyezése - A hangerő gesztus vezérlés engedélyezett. + "A hangerő gesztus vezérlés engedélyezett." A hangerő gesztus vezérlés le van tiltva. A fényerő mentésének és visszaállításának engedélyezése A fényerő mentése és visszaállítása teljes képernyőből való kilépéskor vagy belépéskor. @@ -1481,8 +1444,6 @@ Nincs margó a lejátszó tetején és alján." Gesztus vezérlések a képernyő lezárása módban A gesztus vezérlések engedélyezettek a képernyő lezárása módban. A gesztus vezérlések le vannak tiltva a képernyő lezárása módban. - Gesztus vezérlés átfedésének láthatósága - A gesztus vezérlés átfedésének láthatósága. Gesztus vezérlés küszöbértéke A gesztus vezérléshez szükséges küszöbérték. Gesztus vezérlés átfedésének szövegmérete @@ -1517,12 +1478,27 @@ Megjegyzés: Ezzel a képernyőterület méretét is megváltoztatja, ahol érz Automatikus Videó - Alapértelmezett lejátszási sebesség - Alapértelmezett videó minőség mobilhálózaton - Alapértelmezett videó minőség Wi-Fi hálózaton + HDR videó letiltása A HDR videó le van tiltva. A HDR videó engedélyezett. + VP9 kodek letiltása + "A VP9 kodek le van tiltva. + +• A maximális felbontás 1080p. +• A videolejátszás több internetes adatot használ, mint a VP9. +• A HDR lejátszáshoz a HDR videó továbbra is a VP9 kodeket használja." + VP9 kodek engedélyezve van. + Szoftver AV1 kodek cseréje + A szoftver AV1 kodeket a VP9 kodekkel helyettesíti. + + Alapértelmezett lejátszási sebesség + Lejátszási sebesség módosításainak megjegyzése + A lejátszási sebesség módosítása minden videóra érvényes. + A lejátszási sebesség módosítása csak a jelenlegi videóra érvényes. + Mutass egy felugró értesítést + Egy felugró értesítés fog látszani, amikor megváltozik az alapértelmezett lejátszási sebesség. + Nem fog felugró értesítés látszani, amikor megváltozik az alapértelmezett lejátszási sebesség. Egyéni lejátszási sebesség engedélyezése Az egyéni lejátszási sebesség engedélyezett. Az egyéni lejátszási sebesség le van tiltva. @@ -1531,12 +1507,11 @@ Megjegyzés: Ezzel a képernyőterület méretét is megváltoztatja, ahol érz A régi stílusú előugró menüt használja. Egyéni lejátszási sebességek szerkesztése Az elérhető lejátszási sebességek hozzáadás vagy módosítása. - Lejátszási sebesség módosításainak megjegyzése - A lejátszási sebesség módosítása minden videóra érvényes. - A lejátszási sebesség módosítása csak a jelenlegi videóra érvényes. - Mutass egy felugró értesítést - Egy felugró értesítés fog látszani, amikor megváltozik az alapértelmezett lejátszási sebesség. - Nem fog felugró értesítés látszani, amikor megváltozik az alapértelmezett lejátszási sebesség. + Az egyéni sebességnek kisebbnek kell lennie, mint %sx. + Érvénytelen egyéni lejátszási sebesség. + + Alapértelmezett videó minőség mobilhálózaton + Alapértelmezett videó minőség Wi-Fi hálózaton Videó minőség módosításainak megjegyzése A minőség változások alkalmazása az összes videóra. A minőség változások alkalmazása csak a jelenlegi videóra. @@ -1546,15 +1521,6 @@ Megjegyzés: Ezzel a képernyőterület méretét is megváltoztatja, ahol érz Régi videóminőség menü visszaállítása A régi videóminőség menü látható. A régi videóminőség menü nem látható. - Lejátszási sebesség zenéhez kiválasztás elrejtése - Az alapértelmezett lejátszási sebesség le van tiltva zene lejátszásnál. - Az alapértelmezett lejátszási sebesség engedélyezett zene lejátszásnál. - Érvényesítés kategóriák használatával - Az alapértelmezett lejátszási sebesség le van tiltva, ha a videó kategóriája zene. - Az alapértelmezett lejátszási sebesség le van tiltva a YouTube Music szolgáltatásban lejátszható videóknál. - Shortok alapértelmezett lejátszási sebességének engedélyezése - Az alapértelmezett lejátszási sebesség vonatkozik a Shortokra. - Az alapértelmezett lejátszási sebesség nem vonatkozik a Shortokra. Kihagyott előtöltött puffer. Előtöltött puffer kihagyása "Átugorja a videók kezdetén az előzetesen betöltött puffert, hogy azonnal alkalmazza az alapértelmezett videóminőséget. @@ -1567,27 +1533,13 @@ Info: A felugró üzenet látható. A felugró üzenet nem látható. Eszközméret hamisítása - "A maximális értékre hamisítja az eszköz méreteit. -Előfordulhat, hogy a jó minőség feloldódik bizonyos videóknál, amelyek nagy eszközméretet igényelnek, de nem minden videónál." - VP9 kodek letiltása - "A VP9 kodek le van tiltva. - -• A maximális felbontás 1080p. -• A videolejátszás több internetes adatot használ, mint a VP9. -• A HDR lejátszáshoz a HDR videó továbbra is a VP9 kodeket használja." - VP9 kodek engedélyezve van. - Szoftver AV1 kodek cseréje - A szoftver AV1 kodeket a VP9 kodekkel helyettesíti. - Elutasítja a szoftver AV1 kodek választ - "Erőteljesen elutasítja a szoftveres AV1 codec válaszát. -Egy másik kodek kerül alkalmazásra kb. 20 másodperc pufferezés után." - Az alapfolyamat kb. 20 másodpercig pufferez. - Az alapértelmezett sebesség módosítva: %s. - Az alapértelmezett videó minőség mobil hálózaton: %s. - Nem sikerült beállítani a videóminőséget. - Az alapértelmezett videó minőség Wi-Fi hálózaton: %s. - Az egyéni sebességnek kisebbnek kell lennie, mint %sx. - Érvénytelen egyéni lejátszási sebesség. + + Lejátszási sebesség zenéhez kiválasztás elrejtése + Az alapértelmezett lejátszási sebesség le van tiltva zene lejátszásnál. + Az alapértelmezett lejátszási sebesség engedélyezett zene lejátszásnál. + Érvényesítés kategóriák használatával + Az alapértelmezett lejátszási sebesség le van tiltva, ha a videó kategóriája zene. + Az alapértelmezett lejátszási sebesség le van tiltva a YouTube Music szolgáltatásban lejátszható videóknál. YouTube nem tetszések visszaállítása YouTube nem tetszések visszaállításának engedélyezése @@ -1783,6 +1735,7 @@ Kattintson az API-kulcs kiadás folyamatának megtekintéséhez." Leszavazás Kategória módosítása Nincsen szakasz, amire szavazni lehet. + Válassza ki a szakasz kategóriáját A kategória letiltva a beállításokban. Engedélyezze a beküldéshez. Új SponsorBlock szakasz @@ -1902,8 +1855,6 @@ Kattintson a folytatás gombra és kapcsolja ki az akkumulátor optimalizáláso Android VR "Android VR (Nincs hitelesítés)" - "iOS -(PoToken szükséges)" "iOS TV (Belépés szükséges)" Hamisítás mellékhatásai @@ -1911,8 +1862,8 @@ Kattintson a folytatás gombra és kapcsolja ki az akkumulátor optimalizáláso • A stabil hangerő nem áll rendelkezésre. • A kényszerített automatikus hangsávok letiltása nem elérhető. • Előfordulhat, hogy a gyerekeknek szánt tartalmat nem lehet lejátszani, ha ki van jelentkezve vagy inkognitó módban van." - • Lejátszási problémák lehetnek (PoToken szükséges). - "• Előfordulhat, hogy a filmeket vagy a fizetős videókat nem lehet lejátszani. + "• A stabil hangerő nem érhető el. +• Előfordulhat, hogy a filmeket vagy a fizetős videókat nem lehet lejátszani. • Előfordulhat, hogy a gyerekeknek szánt tartalmat nem lehet lejátszani, ha ki van jelentkezve vagy inkognitó módban van." Kényszerített iOS AVC (H.264) A kényszerített videó kodek az AVC (H.264). @@ -1920,6 +1871,14 @@ Kattintson a folytatás gombra és kapcsolja ki az akkumulátor optimalizáláso "Ennek engedélyezése javíthatja az akkumulátor élettartamát és javíthatja a lejátszás akadozását. Az AVC maximális felbontása 1080p, az Opus hangkodek nem érhető el, és a videolejátszás több adatforgalmat igényel, mint a VP9 vagy az AV1." + Onesie válasz titkosításának kihagyása + "A Onesie válasz titkosításának kihagyása. + +• Javítja az egyes felhasználók által tapasztalt új típusú lejátszási problémát. +• Előfordulhat, hogy az AV1 kodek nem elérhető." + "Ne hagyja ki a Onesie válasz titkosítását. + +• Egyes felhasználók új típusú lejátszási problémákat tapasztalhatnak." Megjelenítés a statisztikában kockáknak Az adatfolyam lekérésére használt kliens a statisztikában kockáknak látható. Az adatfolyam lekérésére használt kliens a statisztikában kockáknak nem látható. diff --git a/patches/src/main/resources/youtube/translations/id-rID/strings.xml b/patches/src/main/resources/youtube/translations/id-rID/strings.xml new file mode 100644 index 000000000..8c9ecd0d9 --- /dev/null +++ b/patches/src/main/resources/youtube/translations/id-rID/strings.xml @@ -0,0 +1,405 @@ + + + + Nyalakan kontrol aksesibilitas untuk pemutar video? + Kontrol anda telah dimodifikasi karena layanan aksesibilitas menyala. + + RVX + Cari %s + Atur ulang ke nilai default. + Fitur Experimental + Apakah Anda ingin melanjutkan? + Mulai ulang aplikasi untuk memuat tata ruang dengan normal + Perbarui tampilan dan mulai ulang + Biasa + Nama paket pengunduh video + Nama paket aplikasi pengunduh eksternal yang dipasang, misalnya NewPipe atau YTDLnis. + Pengunduh eksternal + Peringatan + "%1$s tidak terpasang +Silahkan unduh %2$s dari situs web." + %s tidak terpasang. Silakan pasang. + Bahasa RVX + Bahasa Aplikasi + + Iklan + Sembunyikan spanduk toko di layar akhir + Spanduk toko disembunyikan. + Spanduk toko ditampilkan. + Sembunyikan iklan yang memenuhi layar + Iklan ysng memenuhi layar disembunyikan. + Iklan yang memenuhi layar ditampilkan. + Iklan layar penuh ditutup. + Sembunyikan iklan umum + Iklan umum disembunyikan. + Iklan umum ditunjukkan. + Sembunyikan rak dagangan + Rak dagangan disembunyikan. + Rak dagangan ditunjukkan. + Sembunyikan label promosi berbayar + Label promosi berbayar disembunyikan. + Label promosi berbayar dotunjukkan. + Sembunyikan rak belanja pemutar + Rak belanja disembunyikan. + Rak belanja ditampilkan. + Sembunyikan spanduk peringatan promosi + Spanduk peringatan promosi disembunyikan. + Spanduk peringatan promosi ditampilkan. + Sembunyikan kartu yang disponsori sendiri + Kartu yang disponsori sendiri disembunyikan. + Kartu yang disponsori sendiri ditunjukkan. + Sembunyikan iklan video + Iklan video disembunyikan. + Iklan video ditunjukkan. + Sembunyikan tampilan spanduk produk + Tampilan spanduk produk disembunyikan. + Tampilan spanduk produk ditunjukkan. + Sembunyikan hasil pencarian web + Hasil pencarian web ditunjukkan. + Hasil pencarian web ditunjukkan. + Sembunyikan promosi YouTube Premium + Promosi YouTube Premium disembunyikan. + Promosi YouTube Premium ditunjukkan. + + Thumbnail alternatif + Tab beranda + Daftar putar pemutar, rekomendasi + Hasil pencarian + Tab langganan + Tab anda + Thumbnail asli + DeArrow & Thumbnail asli + DeArrow & Tangkapan diam + Tangkapan diam + DeArrow + "DeArrow menyediakan gambar mini yang bersumber dari banyak orang untuk video YouTube. Thumbnail ini sering kali lebih relevan dibandingkan thumbnail yang disediakan oleh YouTube. + +Jika diaktifkan, URL video akan dikirim ke server API dan tidak ada data lain yang dikirim. Jika video tidak memiliki thumbnail DeArrow, maka gambar asli atau gambar diam akan ditampilkan. + +Ketuk di sini untuk mempelajari lebih lanjut tentang DeArrow." + Tunjukkan peringatan jika API tidak tersedia + Peringatan ditunjukkan bila DeArrow tidak tersedia. + Peringatan tidak ditunjukkan bila DeArrow tidak tersedia. + Titik akhir API DeArrow + URL titik akhir cache thumbnail DeArrow. + URL APIK DeArrow tidak valid. + Tangkapan video diam + Tangkapan diam diambil dari awal, tengah, akhir setiap video. Gambar ini dibuat di YouTube dan tidak ada API eksternal yang digunakan. + Gunakan tangkapan diam cepat + Menggunakan tangkapan diam kualitas sedang. Thumbnail akan dimuat lebih cepat, tetapi siaran langsung, video yang belum dirilis, atau video yang sangat lama mungkin menampilkan thumbnail kosong. + Menggunakan tangkapan diam berkualitas tinggi. + Waktu video untuk mengambil gambar diam + Awal video + Tengah video + Akhir video + DeArrow sementara ini tidak tersedia (kode status: %s) + DeArrow sementara tidak tersedia. + + Pembatasan wilayah gambar + Abaikan pembatasan wilayah gambar + Menggunakan sumber gambar yt4.ggpht.com. + Menggunakan sumber gambar asli\n\nMengaktifkan ini akan memperbaiki gambar hilang yang diblokir di daerah tertentu. + + Feed + Sembunyikan kartu album + Kartu album disembunyikan. + Kartu album ditampilkan. + Sembunyikan tombol Teks + Tombol teks disembunyikan. + Tombol teks ditampilkan. + Sembunyikan rak carousel + "Rak carousel disembunyikan, seperti: +• Berita terbaru +• Lanjutkan menonton +• Jelajahi lebih banyak saluran +• Dengarkan lagi +• Belanja +• Tonton lagi" + Rak carousel ditampilkan. + Sembunyikan kepingan rak + Kepingan rak disembunyikan. + Kepingan rak ditampilkan. + Sembunyikan kepingan yang dapat diperluas di bawah video + Kepingan yang dapat diperluas disembunyikan. + Kepingan yang dapat diperluas ditampilkan. + Sembunyikan rak yang dapat diperluas + Rak yang dapat diperluas disembunyikan. + Rak yang dapat diperluas ditampilkan. + Sembunyikan tombol mengambang + Tombol mengambang disembunyikan. + Tombol mengambang ditampilkan. + Sembunyikan rak gambar + Rak gambar disembunyikan. + Rak gambar ditamoilkan. + Sembunyikan postingan terbaru + Postingan terbaru disembunyikan. + Postingan terbaru ditampilkan. + Sembunyikan tombol video terbaru + Tombol video terbaru disembunyikan. + Tombol video terbaru ditampilkan. + Sembunyikan daftar putar campuran + Daftar putar campuran disembunyikan. + Daftar putar campuran ditampilkan. + Sembunyikan rak film + Rak film disembunyikan. + Rak film ditampilkan. + Sembunyikan tombol Beri tahu saya + Tombol \'Ingatkan saya\' disembunyikan. + Tombol \'Ingatkan saya\' ditunjukkan. + Sembunyikan yang dapat diputar + Yang dapat diputar disembunyikan. + Yang dapat diputar ditunjukkan. + Sembunyikan kolom pencarian + Kolom pencarian disembunyikan. + Kolom pencarian disembunyikan. + Sembunyikan tombol \'Tunjukkan lebih banyak\' + Tombol \'Tunjukkan lebih banyak disembunyikan. + Tombol \'Tunjukkan lebih banyak\' ditunjukkan. + Sembunyikan carousel langganan + Carousel langganan disembunyikan. + Carousel langganan ditampilkan. + Sembunyikan survei + Survey disembunyikan. + Survey ditampilkan. + Sembunyikan rak tiket + Rak tiket disembunyikan. + Rak tiket ditunjukkan. + + Bilah kategori + Sembunyikan atau tampilkan bilah kategori di feed, pencarian, dan video terkait. + Sembunyikan di halaman utama + Disembunyikan di halaman utama. + Ditunjukkan di halaman utama. + Sembunyikan di video terkait + Disembunyikan di video terkait. + Ditampilkan di video terkait. + Sembunyikan di hasil pencarian + Disembunyikan di hasil pencarian. + Ditampilkan di hasil pencarian. + + Profil saluran + Sembunyikan atau tampilkan komponen dalam profil saluran. + Aktifkan bilah saring saluran + Bilah saring saluran diaktikan. + Bilah saring saluran dinonaktifkan. + Bilah saring saluran + Daftar nama bilah saluran yang akan disaring, dipisahkan oleh baris baru. + "Shorts +Daftar putar +Toko" + Sembunyikan rak anggota channel + Rak anggota channel disembunyikan. + Rak anggota channel ditampilkan. + Sembunyikan tautan profil saluran + Tautan di bagian atas profil saluran disembunyikan. + Tautan di bagian atas profil saluran ditampilkan. + Sembunyikan rak Untuk Anda + Rak Untuk Anda disembunyikan. + Rak Untuk Anda ditampilkan. + Sembunyikan tombol Kunjungi toko + Tombol kunjungi toko disembunyikan. + Tombol kunjungi toko ditampilkan. + + Postingan Komunitas + Sembunyikan atau tampilkan postingan komunitas di feed dan saluran. + Sembunyikan di saluran + Disembunyikan di saluran. + Ditampilkan di saluran. + Sembunyikan di feed beranda dan video terkait + Disembunyikan di feed beranda dan video terkait. + Ditampilkan di feed beranda dan video terkait. + Sembunyikan di feed langganan + Disembunyikan di feed langganan. + Ditampilkan di feed langganan. + + Menu flyout + Sembunyikan atau tampilkan komponen menu flyout di feed. + Aktifkan penyaring menu feed flyout + Filter menu feed flyout diaktifkan. + Filter menu feed flyout dinonaktifkan. + Jenis penyaring menu feed flyout + Saring jika berisi.<br><br>Untuk menyembunyikan menu <b>\'Putar berikutnya dalam antrean\'</b> Anda dapat menggunakan <b>\'Putar berikutnya\'</b> atau <b>\'dalam antrean\'</b> sebagai kata kunci. + Saring jika cocok.<br><br>Untuk menyembunyikan menu <b>\'Putar berikutnya dalam antrean\'</b> Anda hanya dapat menggunakan <b>\'Putar berikutnya dalam antrean\'</b> sebagai kata kunci. + Menu penyaring feed flyout + Daftar nama menu flyout yang disaring, dipisahkan dengan baris baru. + + Penyaring video + Sembunyikan video berdasarkan kata kunci atau penayangan. + + Penyaring kata kunci + Sembunyikan komentar berdasarkan kata kunci + Komentar disaring. + Komentar tidak disaring. + Sembunyikan video di beranda berdasarkan kata kunci + Video di feed beranda disaring. + Video di feed beranda tidak disaring. + Sembunyikan hasil pencarian berdasarkan kata kunci + Hasil pencarian disaring. + Hasil pencarian tidak disaring. + Sembunyikan video langganan berdasarkan kata kunci + Video di feed langganan disaring. + Video dalam feed langganan tidak difilter. + Kata kunci yang ingin disembunyikan + "Kata kunci dan frasa yang akan disembunyikan, dipisahkan dengan baris baru. + +Kata kunci dapat berupa nama saluran atau teks apa pun yang ditampilkan dalam judul video. + +Kata dengan huruf besar di tengah harus dimasukkan dengan huruf kecil (misalnya: iPhone, TikTok, LeBlanc)." + Tentang penyaringan kata kunci + "Beranda / Langganan / Hasil pencarian disaring untuk menyembunyikan konten yang cocok dengan frasa kata kunci. + +Keterbatasan: +• Shorts tidak dapat disembunyikan berdasarkan nama saluran. +• Beberapa komponen UI mungkin tidak dapat disembunyikan. +• Pencarian kata kunci mungkin tidak menampilkan hasil." + Cocokkan seluruh kata + Melingkupi kata kunci/frasa dengan tanda kutip ganda akan mencegah pencocokan sebagian judul video dan nama saluran.<br><br>Sebagai contoh,<br><b>\"ai\"</b> akan menyembunyikan video: <b>Bagaimana cara kerja AI?</b><br>namun tidak akan menyembunyikan: <b>Apa yang dimaksud Jakarta Fair?</b> + Tidak dapat menggunakan kata kunci: %s. + Tambahkan tanda kutip untuk menggunakan kata kunci: %s. + Kata kunci punya keterangan yang bertentangan: %s. + Kata kunci terlalu pendek & butuh tanda kutip: %s. + Kata kunci akan menyembunyikan semua video: %s. + + Video yang direkomendasikan + Sembunyikan video dengan penayangan rendah + "Sembunyikan video dengan kurang dari 1.000 penayangan dari feed beranda yang telah diunggah dari saluran yang tidak berlangganan. + +Filter ini mungkin tidak lagi berfungsi, gunakan 'Filter jumlah penayangan' sebagai gantinya." + Sembunyikan video yang direkomendasikan + "Menyembunyikan video yang direkomendasikan berikut ini: + +• Video dengan frasa seperti 'Orang-orang juga menonton' di bawahnya." + + Filter jumlah penayangan + Sembunyikan video beranda berdasarkan penayangan + Video di feed beranda difilter. + Video di feed beranda tidak difilter. + Sembunyikan hasil pencarian berdasarkan penayangan + Hasil pencarian difilter. + Hasil pencarian tidak difilter. + Sembunyikan video langganan berdasarkan penayangan + Video di feed langganan disaring. + Video dalam feed langganan tidak disaring. + Lebih besar dari penayangan + Video dengan jumlah penayangan yang lebih besar dari angka ini akan disembunyikan. + Kurang dari penayangan + Video dengan jumlah penayangan yang lebih kurang dari angka ini akan disembunyikan. + Lihat kunci + Tentukan templat bahasa Anda untuk jumlah tampilan yang ditampilkan di bawah setiap video di antarmuka pengguna. Setiap kunci (huruf/kata dalam bahasa Anda) -> nilai (yang berarti kunci) harus berada di baris baru. Kunci diletakkan sebelum tanda \"->\". Jika Anda mengubah bahasa aplikasi atau sistem, Anda perlu mengatur ulang pengaturan ini.\n\nContoh:\nInggris: 10K tayangan = K -> 1000, tayangan -> tayangan\nSpanyol: 10 K tayangan = K -> 1000, tayangan -> tayangan + K -> 1 000\nM -> 1 000 000\nB -> 1 000 000 000\ntayangan -> tayangan + Tentang penyaringan jumlah tayangan + "Beranda / Langganan / Hasil pencarian disaring untuk menyembunyikan video dengan jumlah penayangan kurang atau lebih besar dari jumlah yang ditentukan. + +Keterbatasan: +• Shorts tidak dapat disembunyikan. +• Video dengan 0 penayangan tidak disaring." + Sembunyikan video terkait + Video terkait disembunyikan. + Video terkait ditampilkan. + "Pengaturan ini membatasi jumlah maksimum tata letak yang dapat dimuat pada layar pemutar. + +Jika tata letak layar pemutar berubah karena perubahan di sisi server, tata letak yang tidak diinginkan mungkin disembunyikan di layar pemutar." + Offset + + Umum + Ganti halaman awal + Default + Semua langganan + Jelajahi saluran + Kursus / Pembelajaran + Jelajahi + Busana & Kecantikan + Gaming + Riwayat + Pustaka + Video disukai + Siaran Langsung + Film + Musik + Berita + Pemberitahuan + Daftar Putar + Podcast + Cari + Belanja + Shorts + Olahraga + Langganan + Sedang Populer + Realitas Virtual + Tonton nanti + Klip Anda + Ganti jenis halaman awal + "Halaman awal selalu berubah. + +Keterbatasan: Tombol Kembali pada bilah alat mungkin tidak berfungsi." + Halaman awal berubah hanya sekali. + Nonaktifkan trek audio otomatis yang dipaksakan + Trek audio otomatis yang dipaksakan dinonaktifkan. + Trek audio otomatis yang dipaksakan diaktifkan. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/patches/src/main/resources/youtube/translations/in/strings.xml b/patches/src/main/resources/youtube/translations/in/strings.xml new file mode 100644 index 000000000..8c9ecd0d9 --- /dev/null +++ b/patches/src/main/resources/youtube/translations/in/strings.xml @@ -0,0 +1,405 @@ + + + + Nyalakan kontrol aksesibilitas untuk pemutar video? + Kontrol anda telah dimodifikasi karena layanan aksesibilitas menyala. + + RVX + Cari %s + Atur ulang ke nilai default. + Fitur Experimental + Apakah Anda ingin melanjutkan? + Mulai ulang aplikasi untuk memuat tata ruang dengan normal + Perbarui tampilan dan mulai ulang + Biasa + Nama paket pengunduh video + Nama paket aplikasi pengunduh eksternal yang dipasang, misalnya NewPipe atau YTDLnis. + Pengunduh eksternal + Peringatan + "%1$s tidak terpasang +Silahkan unduh %2$s dari situs web." + %s tidak terpasang. Silakan pasang. + Bahasa RVX + Bahasa Aplikasi + + Iklan + Sembunyikan spanduk toko di layar akhir + Spanduk toko disembunyikan. + Spanduk toko ditampilkan. + Sembunyikan iklan yang memenuhi layar + Iklan ysng memenuhi layar disembunyikan. + Iklan yang memenuhi layar ditampilkan. + Iklan layar penuh ditutup. + Sembunyikan iklan umum + Iklan umum disembunyikan. + Iklan umum ditunjukkan. + Sembunyikan rak dagangan + Rak dagangan disembunyikan. + Rak dagangan ditunjukkan. + Sembunyikan label promosi berbayar + Label promosi berbayar disembunyikan. + Label promosi berbayar dotunjukkan. + Sembunyikan rak belanja pemutar + Rak belanja disembunyikan. + Rak belanja ditampilkan. + Sembunyikan spanduk peringatan promosi + Spanduk peringatan promosi disembunyikan. + Spanduk peringatan promosi ditampilkan. + Sembunyikan kartu yang disponsori sendiri + Kartu yang disponsori sendiri disembunyikan. + Kartu yang disponsori sendiri ditunjukkan. + Sembunyikan iklan video + Iklan video disembunyikan. + Iklan video ditunjukkan. + Sembunyikan tampilan spanduk produk + Tampilan spanduk produk disembunyikan. + Tampilan spanduk produk ditunjukkan. + Sembunyikan hasil pencarian web + Hasil pencarian web ditunjukkan. + Hasil pencarian web ditunjukkan. + Sembunyikan promosi YouTube Premium + Promosi YouTube Premium disembunyikan. + Promosi YouTube Premium ditunjukkan. + + Thumbnail alternatif + Tab beranda + Daftar putar pemutar, rekomendasi + Hasil pencarian + Tab langganan + Tab anda + Thumbnail asli + DeArrow & Thumbnail asli + DeArrow & Tangkapan diam + Tangkapan diam + DeArrow + "DeArrow menyediakan gambar mini yang bersumber dari banyak orang untuk video YouTube. Thumbnail ini sering kali lebih relevan dibandingkan thumbnail yang disediakan oleh YouTube. + +Jika diaktifkan, URL video akan dikirim ke server API dan tidak ada data lain yang dikirim. Jika video tidak memiliki thumbnail DeArrow, maka gambar asli atau gambar diam akan ditampilkan. + +Ketuk di sini untuk mempelajari lebih lanjut tentang DeArrow." + Tunjukkan peringatan jika API tidak tersedia + Peringatan ditunjukkan bila DeArrow tidak tersedia. + Peringatan tidak ditunjukkan bila DeArrow tidak tersedia. + Titik akhir API DeArrow + URL titik akhir cache thumbnail DeArrow. + URL APIK DeArrow tidak valid. + Tangkapan video diam + Tangkapan diam diambil dari awal, tengah, akhir setiap video. Gambar ini dibuat di YouTube dan tidak ada API eksternal yang digunakan. + Gunakan tangkapan diam cepat + Menggunakan tangkapan diam kualitas sedang. Thumbnail akan dimuat lebih cepat, tetapi siaran langsung, video yang belum dirilis, atau video yang sangat lama mungkin menampilkan thumbnail kosong. + Menggunakan tangkapan diam berkualitas tinggi. + Waktu video untuk mengambil gambar diam + Awal video + Tengah video + Akhir video + DeArrow sementara ini tidak tersedia (kode status: %s) + DeArrow sementara tidak tersedia. + + Pembatasan wilayah gambar + Abaikan pembatasan wilayah gambar + Menggunakan sumber gambar yt4.ggpht.com. + Menggunakan sumber gambar asli\n\nMengaktifkan ini akan memperbaiki gambar hilang yang diblokir di daerah tertentu. + + Feed + Sembunyikan kartu album + Kartu album disembunyikan. + Kartu album ditampilkan. + Sembunyikan tombol Teks + Tombol teks disembunyikan. + Tombol teks ditampilkan. + Sembunyikan rak carousel + "Rak carousel disembunyikan, seperti: +• Berita terbaru +• Lanjutkan menonton +• Jelajahi lebih banyak saluran +• Dengarkan lagi +• Belanja +• Tonton lagi" + Rak carousel ditampilkan. + Sembunyikan kepingan rak + Kepingan rak disembunyikan. + Kepingan rak ditampilkan. + Sembunyikan kepingan yang dapat diperluas di bawah video + Kepingan yang dapat diperluas disembunyikan. + Kepingan yang dapat diperluas ditampilkan. + Sembunyikan rak yang dapat diperluas + Rak yang dapat diperluas disembunyikan. + Rak yang dapat diperluas ditampilkan. + Sembunyikan tombol mengambang + Tombol mengambang disembunyikan. + Tombol mengambang ditampilkan. + Sembunyikan rak gambar + Rak gambar disembunyikan. + Rak gambar ditamoilkan. + Sembunyikan postingan terbaru + Postingan terbaru disembunyikan. + Postingan terbaru ditampilkan. + Sembunyikan tombol video terbaru + Tombol video terbaru disembunyikan. + Tombol video terbaru ditampilkan. + Sembunyikan daftar putar campuran + Daftar putar campuran disembunyikan. + Daftar putar campuran ditampilkan. + Sembunyikan rak film + Rak film disembunyikan. + Rak film ditampilkan. + Sembunyikan tombol Beri tahu saya + Tombol \'Ingatkan saya\' disembunyikan. + Tombol \'Ingatkan saya\' ditunjukkan. + Sembunyikan yang dapat diputar + Yang dapat diputar disembunyikan. + Yang dapat diputar ditunjukkan. + Sembunyikan kolom pencarian + Kolom pencarian disembunyikan. + Kolom pencarian disembunyikan. + Sembunyikan tombol \'Tunjukkan lebih banyak\' + Tombol \'Tunjukkan lebih banyak disembunyikan. + Tombol \'Tunjukkan lebih banyak\' ditunjukkan. + Sembunyikan carousel langganan + Carousel langganan disembunyikan. + Carousel langganan ditampilkan. + Sembunyikan survei + Survey disembunyikan. + Survey ditampilkan. + Sembunyikan rak tiket + Rak tiket disembunyikan. + Rak tiket ditunjukkan. + + Bilah kategori + Sembunyikan atau tampilkan bilah kategori di feed, pencarian, dan video terkait. + Sembunyikan di halaman utama + Disembunyikan di halaman utama. + Ditunjukkan di halaman utama. + Sembunyikan di video terkait + Disembunyikan di video terkait. + Ditampilkan di video terkait. + Sembunyikan di hasil pencarian + Disembunyikan di hasil pencarian. + Ditampilkan di hasil pencarian. + + Profil saluran + Sembunyikan atau tampilkan komponen dalam profil saluran. + Aktifkan bilah saring saluran + Bilah saring saluran diaktikan. + Bilah saring saluran dinonaktifkan. + Bilah saring saluran + Daftar nama bilah saluran yang akan disaring, dipisahkan oleh baris baru. + "Shorts +Daftar putar +Toko" + Sembunyikan rak anggota channel + Rak anggota channel disembunyikan. + Rak anggota channel ditampilkan. + Sembunyikan tautan profil saluran + Tautan di bagian atas profil saluran disembunyikan. + Tautan di bagian atas profil saluran ditampilkan. + Sembunyikan rak Untuk Anda + Rak Untuk Anda disembunyikan. + Rak Untuk Anda ditampilkan. + Sembunyikan tombol Kunjungi toko + Tombol kunjungi toko disembunyikan. + Tombol kunjungi toko ditampilkan. + + Postingan Komunitas + Sembunyikan atau tampilkan postingan komunitas di feed dan saluran. + Sembunyikan di saluran + Disembunyikan di saluran. + Ditampilkan di saluran. + Sembunyikan di feed beranda dan video terkait + Disembunyikan di feed beranda dan video terkait. + Ditampilkan di feed beranda dan video terkait. + Sembunyikan di feed langganan + Disembunyikan di feed langganan. + Ditampilkan di feed langganan. + + Menu flyout + Sembunyikan atau tampilkan komponen menu flyout di feed. + Aktifkan penyaring menu feed flyout + Filter menu feed flyout diaktifkan. + Filter menu feed flyout dinonaktifkan. + Jenis penyaring menu feed flyout + Saring jika berisi.<br><br>Untuk menyembunyikan menu <b>\'Putar berikutnya dalam antrean\'</b> Anda dapat menggunakan <b>\'Putar berikutnya\'</b> atau <b>\'dalam antrean\'</b> sebagai kata kunci. + Saring jika cocok.<br><br>Untuk menyembunyikan menu <b>\'Putar berikutnya dalam antrean\'</b> Anda hanya dapat menggunakan <b>\'Putar berikutnya dalam antrean\'</b> sebagai kata kunci. + Menu penyaring feed flyout + Daftar nama menu flyout yang disaring, dipisahkan dengan baris baru. + + Penyaring video + Sembunyikan video berdasarkan kata kunci atau penayangan. + + Penyaring kata kunci + Sembunyikan komentar berdasarkan kata kunci + Komentar disaring. + Komentar tidak disaring. + Sembunyikan video di beranda berdasarkan kata kunci + Video di feed beranda disaring. + Video di feed beranda tidak disaring. + Sembunyikan hasil pencarian berdasarkan kata kunci + Hasil pencarian disaring. + Hasil pencarian tidak disaring. + Sembunyikan video langganan berdasarkan kata kunci + Video di feed langganan disaring. + Video dalam feed langganan tidak difilter. + Kata kunci yang ingin disembunyikan + "Kata kunci dan frasa yang akan disembunyikan, dipisahkan dengan baris baru. + +Kata kunci dapat berupa nama saluran atau teks apa pun yang ditampilkan dalam judul video. + +Kata dengan huruf besar di tengah harus dimasukkan dengan huruf kecil (misalnya: iPhone, TikTok, LeBlanc)." + Tentang penyaringan kata kunci + "Beranda / Langganan / Hasil pencarian disaring untuk menyembunyikan konten yang cocok dengan frasa kata kunci. + +Keterbatasan: +• Shorts tidak dapat disembunyikan berdasarkan nama saluran. +• Beberapa komponen UI mungkin tidak dapat disembunyikan. +• Pencarian kata kunci mungkin tidak menampilkan hasil." + Cocokkan seluruh kata + Melingkupi kata kunci/frasa dengan tanda kutip ganda akan mencegah pencocokan sebagian judul video dan nama saluran.<br><br>Sebagai contoh,<br><b>\"ai\"</b> akan menyembunyikan video: <b>Bagaimana cara kerja AI?</b><br>namun tidak akan menyembunyikan: <b>Apa yang dimaksud Jakarta Fair?</b> + Tidak dapat menggunakan kata kunci: %s. + Tambahkan tanda kutip untuk menggunakan kata kunci: %s. + Kata kunci punya keterangan yang bertentangan: %s. + Kata kunci terlalu pendek & butuh tanda kutip: %s. + Kata kunci akan menyembunyikan semua video: %s. + + Video yang direkomendasikan + Sembunyikan video dengan penayangan rendah + "Sembunyikan video dengan kurang dari 1.000 penayangan dari feed beranda yang telah diunggah dari saluran yang tidak berlangganan. + +Filter ini mungkin tidak lagi berfungsi, gunakan 'Filter jumlah penayangan' sebagai gantinya." + Sembunyikan video yang direkomendasikan + "Menyembunyikan video yang direkomendasikan berikut ini: + +• Video dengan frasa seperti 'Orang-orang juga menonton' di bawahnya." + + Filter jumlah penayangan + Sembunyikan video beranda berdasarkan penayangan + Video di feed beranda difilter. + Video di feed beranda tidak difilter. + Sembunyikan hasil pencarian berdasarkan penayangan + Hasil pencarian difilter. + Hasil pencarian tidak difilter. + Sembunyikan video langganan berdasarkan penayangan + Video di feed langganan disaring. + Video dalam feed langganan tidak disaring. + Lebih besar dari penayangan + Video dengan jumlah penayangan yang lebih besar dari angka ini akan disembunyikan. + Kurang dari penayangan + Video dengan jumlah penayangan yang lebih kurang dari angka ini akan disembunyikan. + Lihat kunci + Tentukan templat bahasa Anda untuk jumlah tampilan yang ditampilkan di bawah setiap video di antarmuka pengguna. Setiap kunci (huruf/kata dalam bahasa Anda) -> nilai (yang berarti kunci) harus berada di baris baru. Kunci diletakkan sebelum tanda \"->\". Jika Anda mengubah bahasa aplikasi atau sistem, Anda perlu mengatur ulang pengaturan ini.\n\nContoh:\nInggris: 10K tayangan = K -> 1000, tayangan -> tayangan\nSpanyol: 10 K tayangan = K -> 1000, tayangan -> tayangan + K -> 1 000\nM -> 1 000 000\nB -> 1 000 000 000\ntayangan -> tayangan + Tentang penyaringan jumlah tayangan + "Beranda / Langganan / Hasil pencarian disaring untuk menyembunyikan video dengan jumlah penayangan kurang atau lebih besar dari jumlah yang ditentukan. + +Keterbatasan: +• Shorts tidak dapat disembunyikan. +• Video dengan 0 penayangan tidak disaring." + Sembunyikan video terkait + Video terkait disembunyikan. + Video terkait ditampilkan. + "Pengaturan ini membatasi jumlah maksimum tata letak yang dapat dimuat pada layar pemutar. + +Jika tata letak layar pemutar berubah karena perubahan di sisi server, tata letak yang tidak diinginkan mungkin disembunyikan di layar pemutar." + Offset + + Umum + Ganti halaman awal + Default + Semua langganan + Jelajahi saluran + Kursus / Pembelajaran + Jelajahi + Busana & Kecantikan + Gaming + Riwayat + Pustaka + Video disukai + Siaran Langsung + Film + Musik + Berita + Pemberitahuan + Daftar Putar + Podcast + Cari + Belanja + Shorts + Olahraga + Langganan + Sedang Populer + Realitas Virtual + Tonton nanti + Klip Anda + Ganti jenis halaman awal + "Halaman awal selalu berubah. + +Keterbatasan: Tombol Kembali pada bilah alat mungkin tidak berfungsi." + Halaman awal berubah hanya sekali. + Nonaktifkan trek audio otomatis yang dipaksakan + Trek audio otomatis yang dipaksakan dinonaktifkan. + Trek audio otomatis yang dipaksakan diaktifkan. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 65d79b779..3c5133350 100644 --- a/patches/src/main/resources/youtube/translations/it-rIT/strings.xml +++ b/patches/src/main/resources/youtube/translations/it-rIT/strings.xml @@ -19,59 +19,150 @@ "%1$s non è installato. Si prega di scaricare %2$s dal sito web." %s non è installato. Per favore installalo. + Aggiungi alla coda + Aggiungi alla coda e aprila + Aggiungi alla coda e riproduci il video + Downloader esterno + Apri la coda + Coda + Rimuovi dalla coda + Rimuovi dalla coda e aprila + Rimuovi la coda + Salva la coda + "Invece di aprire un downloader esterno, apri la finestra di dialogo del gestore delle code. + +Puoi anche aprire il gestore delle code tenendo premuto il pulsante Indietro sulla barra di navigazione. + +Questa impostazione è ancora in fase di sviluppo, pertanto la maggior parte delle funzionalità potrebbe non funzionare. + +Usala solo per scopi di debug." + Accesso richiesto + Gestore delle code non disponibile (%s) + Impossibile identificare la playlist + La coda è vuota + Identificazione dell video non riuscito + Aggiunta del video non riuscita + Creazione della coda non riuscita + Eliminazione della coda non riuscita + Rimozione del video non riuscita + Salvataggio della coda non riuscito + Video aggiunto con successo + Coda creata con successo + Coda eliminata con successo + Video rimosso con successo + Coda salvata con successo in \'%s\' Lingua di RVX Lingua dell\'app - Arabo - Azero - Bulgaro - Bengalese - Catalano - Ceco - Danese - Tedesco - Greco - Inglese - Spagnolo - Estone - Persiano - Finlandese - Francese - Gujarati - Hindi - Croato - Ungherese - Indonesiano - Italiano - Giapponese - Kazako - Coreano - Lituano - Lettone - Macedone - Mongolo - Marathi - Malese - Birmano - Olandese - Odia - Punjabi - Polacco - Portoghese - Rumeno - Russo - Slovacco - Sloveno - Serbo - Svevo - Swahili - Tamil - Telugu - Tailandese - Turco - Ucraino - Urdu - Vietnamita - Cinese + "Amarico +አማርኛ" + "Arabo +العربية" + "Azero +Azərbaycan" + "Bielorusso +беларуская" + "Bulgaro +Български" + "Bengalese +বাংলা" + "Catalano +Català" + "Ceco +Čeština" + "Danese +Dansk" + "Tedesco +Deutsch" + "Greco +Ελληνικά" + "Inglese +English" + "Spagnolo +Español" + "Estone +Eesti" + "Persiano +فارسی" + "Finlandese +Suomi" + "Francese +Français" + "Gugiarati +ગુજરાતી" + "Ebraico +עברי" + "Hindi +हिन्दी" + "Croato +Hrvatski" + "Ungherese +Magyar" + "Indonesiano +Indonesia" + "Italiano +Italiano" + "Giapponese +日本語" + "Cazaco +Қазақ тілі" + "Coreano +한국어" + "Lituano +Lietuvių" + "Lettone +Latviešu" + "Macedone +Македонски" + "Mongolo +Монгол" + "Maratti +मराठी" + "Malese +Melayu" + "Birmano +ဗမာ" + "Olandese +Nederlands" + "Oriya +ଓଡ଼ିଆ" + "Pangiabi +ਪੰਜਾਬੀ" + "Polacco +Polski" + "Portoghese +Português" + "Rumeno +Română" + "Russo +Русский" + "Slovacco +Slovenčina" + "Albanese +Shqip" + "Sloveno +Slovenščina" + "Serbo +Српски" + "Svedese +Svenska" + "Suahili +Kiswahili" + "Tàmil +தமிழ்" + "Tèlugu +తెలుగు" + "Tailandese +ไทย" + "Turco +Türkçe" + "Ucraino +Українська" + "Urdù +اردو" + "Vietnamese +Tiếng Việt" + "Cinese +中文" Annunci Nascondi il banner del negozio alla fine dei video @@ -392,9 +483,6 @@ Nota: il pulsante Indietro della barra degli strumenti potrebbe non funzionare." Disattiva l\'animazione di avvio L\'animazione di avvio è disattivata. L\'animazione di avvio è attivata. - Disattiva la barra di stato traslucida - La barra di stato è opaca. - La barra di stato è opaca o traslucida. Attiva la schermata di caricamento gradiente La schermata di caricamento gradiente è attivata. La schermata di caricamento gradiente è disattivata. @@ -419,13 +507,30 @@ Nota: i canali non si aprono se gli Shorts live sono aperti nel riproduttore nor Tablet Tablet (minimo 600 dp) Semovente - "Le modifiche dell'interfaccia: + "Le modifiche includono: Tablet: i post della community sono nascosti. Semovente: • Gli Shorts verranno aperti nel riproduttore normale. • Il feed è organizzato per argomenti e canali. • La descrizione dei video non può essere aperta quando \"Camuffa i dati in streaming\" è disattivato." + Disattiva gli aggiornamenti dell\'interfaccia + L\'interfaccia non verrà aggiornata dal server. + L\'interfaccia verrà aggiornata dal server. + "L'interfaccia dell'app viene ripristinata all'interfaccia usata al momento della prima installazione. + +Nota: alcune interfacce lato server potrebbero non essere ripristinate. + +Le modifiche includono: +• I componenti nel menù a comparsa (o le impostazioni correlate) potrebbero non funzionare. +• Le animazioni dei contatori numerici sono disattivate. +• Viene usata la scheda Raccolta. +• La sezione Musica nella descrizione dei video potrebbe non funzionare. +• Il pulsante Cambia Account potrebbe essere nascosto nella scheda Raccolta. Usa l'impostazione \"Attiva la barra di ricerca estesa nella scheda Tu\"." + Disattiva la barra di stato traslucida + La barra di stato è opaca. + La barra di stato è opaca o traslucida. + Per alcune ROM con Android 12+, l\'attivazione di questa impostazione potrebbe rendere trasparente la barra di navigazione del sistema. Attiva il camuffamento della versione dell\'app Il camuffamento della versione dell\'app è attivato. Il camuffamento della versione dell\'app è disattivato. @@ -433,7 +538,7 @@ Semovente: Questo cambierà l'aspetto e le caratteristiche dell'app, ma potrebbero verificarsi effetti collaterali sconosciuti. -Se in seguito verrà disattivato, si consiglia di cancellare i dati dell'app per evitare bug dell'interfaccia." +Se in seguito verrà disattivata, si consiglia di cancellare i dati dell'app per evitare bug dell'interfaccia." Modifica la versione dell\'app da camuffare Digita la versione dell\'app da camuffare. Versione dell\'app da camuffare @@ -443,8 +548,10 @@ Se in seguito verrà disattivato, si consiglia di cancellare i dati dell'app per 18.33.40 - Ripristina la vecchia barra delle azioni degli Shorts 18.38.45 - Ripristina il comportamento della vecchia qualità video predefinita 18.48.39 - Disattiva l\'aggiornamento in tempo reale delle visualizzazioni e dei mi piace + 19.01.34 - Disattiva l\'interazione con la descrizione dei video 19.26.42 - Disattiva l\'icona Cairo nella navigazione e nella barra degli strumenti 19.33.37 - Ripristina il vecchio pannello della velocità di riproduzione + Versione dell\'app da camuffare non valida: %s Menù dell\'account Personalizza i componenti dei menù dell\'account e della scheda Tu. @@ -480,6 +587,9 @@ Nota: alcuni componenti potrebbero non essere nascosti." Sovrascrivi il pulsante Scarica Video Il pulsante Scarica Video nativo apre il downloader esterno. Il pulsante Scarica Video nativo apre il downloader nativo. + Gestore delle code + Il pulsante Scarica Video nativo apre il gestore delle code. + Il pulsante Scarica Video nativo apre il downloader esterno. Nome del pacchetto del downloader esterno delle playlist Il nome del pacchetto dell\'app di download esterna installata, ad esempio YTDLnis. @@ -533,7 +643,6 @@ 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. @@ -697,7 +806,7 @@ Note: • La disattivazione della sovrapposizione della velocità ripristinerà il gesto Scorri la Barra di Avanzamento della vecchia interfaccia. • La disattivazione di questa impostazione non attiva forzatamente la sovrapposizione della velocità." Valore di sovrapposizione della velocità di riproduzione - Valore di sovrapposizione della velocità di riproduzione tra 0 e 8. + Il valore di sovrapposizione della velocità di riproduzione tra 0 e 8. Il valore di sovrapposizione della velocità di riproduzione deve essere tra 0 e 8 Nascondi il watermark nei video Il watermark nei video è nascosto. @@ -1116,6 +1225,8 @@ Tocca e tieni premuto per copiare il timestamp del video." Tocca di nuovo per riattivare l\'audio. Mostra il pulsante Downloader Esterno Tocca per avviare il downloader esterno. + Gestore delle code + Invece di aprire un downloader esterno, apri la finestra di dialogo del gestore delle code. Mostra il pulsante Velocità di Riproduzione "Tocca per aprire la finestra della velocità di riproduzione Tocca e tieni premuto per impostare la velocità di riproduzione a 1x. @@ -1215,9 +1326,9 @@ Questa impostazione funziona meglio con una connessione internet molto veloce."< Descrizione dei video Personalizza i componenti della descrizione dei video. - Disattiva l\'effetto contatore dei numeri - L\'effetto contatore dei numeri è disattivato. - L\'effetto contatore dei numeri è attivato. + Disattiva le animazioni dei contatori numerici + Le animazioni dei contatori numerici sono disattivate. + Le animazioni dei contatori numerici sono attivate. Nascondi la sezione Riepilogo Video Generato dall\'IA La sezione Riepilogo Video Generato dall\'IA è nascosta. La sezione Riepilogo Video Generato dall\'IA è visibile. @@ -1409,9 +1520,7 @@ Nota: solo gli scaffali con l'intestazione Shorts nella scheda Home sono nascost Attiva le azioni personalizzate nei menù a comparsa "Le azioni personalizzate nei menù a comparsa sono attivate. -Note: -• Non funziona se la versione dell'app è camuffata alla versione 18.49.37 o precedente. -• Non funziona con i video live." +Nota: non funziona con i video live." Le azioni personalizzate nei menù a comparsa sono disattivate. Attiva le azioni personalizzate nella barra degli strumenti "Le azioni personalizzate nella barra degli strumenti sono attivate. @@ -1435,6 +1544,10 @@ Tocca e tieni premuto il pulsante Altro per visualizzare la finestra delle azion Mostra il menù Apri il Video Il menù Apri il Video è visibile. Il menù Apri il Video è nascosto. + Finestra della velocità + Mostra la finestra della velocità + La finestra della velocità è visibile. + La finestra della velocità è nascosta. Stato di ripetizione Mostra il menù Stato di Ripetizione Il menù Stato di Ripetizione è visibile. @@ -1478,10 +1591,14 @@ Note: Il valore più basso del gesto della luminosità attiva la luminosità automatica. Il valore più basso del gesto della luminosità non attiva la luminosità automatica. Attiva il gesto della luminosità - Il gesto della luminosità è attivato. + "Il gesto della luminosità è attivato. + +Regola la luminosità scorrendo verticalmente sul lato sinistro dello schermo." Il gesto della luminosità è disattivato. Attiva il gesto del volume - Il gesto del volume è attivato. + "Il gesto del volume è attivato. + +Regola il volume scorrendo verticalmente sul lato destro dello schermo." Il gesto del volume è disattivato. Attiva il salvataggio e il ripristino della luminosità Salva e ripristina la luminosità uscendo ed entrando a schermo intero. @@ -1495,10 +1612,20 @@ Note: Attiva i gesti di trascinamento in modalità Blocca Schermo I gesti di trascinamento in modalità Blocca Schermo sono attivati. I gesti di trascinamento in modalità Blocca Schermo sono disattivati. - La visibilità dello sfondo del trascinamento - La visibilità dello sfondo in sovrapposizione durante il trascinamento. Il limite di ampiezza del trascinamento Il limite di ampiezza entro cui deve avvenire il trascinamento. + Interfaccia alternativa con sovrapposizione di scorrimento + È in uso l\'interfaccia alternativa. + È in uso l\'interfaccia originale. + Attiva lo stile di sovrapposizione minimale + Lo stile di sovrapposizione minimale è attivato. + Lo stile di sovrapposizione minimale è disattivato. + Mostra la sovrapposizione circolare + È mostrata la sovrapposizione circolare. + È mostrata la sovrapposizione orizzontale. + Opacità dello sfondo della sovrapposizione di scorrimento + Il valore dell\'opacità tra 0 e 100. + L\'opacità dello scorrimento deve essere tra 0 e 100 La dimensione del testo del trascinamento La dimensione del testo in sovrapposizione durante il trascinamento. La dimensione dello schermo in sovrapposizione del trascinamento @@ -1530,12 +1657,43 @@ Note: Automatico Video - Velocità di riproduzione predefinita - Qualità predefinita con connessione dati - Qualità predefinita con Wi-Fi + + Codec Disattiva l\'HDR dei video L\'HDR dei video è disattivato. L\'HDR dei video è attivato. + Disattiva il codec VP9 + "Il codec VP9 è disattivato. + +Note: +• La risoluzione massima è 1080p. +• La riproduzione userà più dati internet di VP9. +• VP9 è comunque usato per la riproduzione HDR." + Il codec VP9 è attivato. + Sostituisci il codec AV1 + Sostituisce il codec AV1 con il codec VP9. + + Velocità di riproduzione + Velocità di riproduzione predefinita dei video + Ricorda i cambiamenti della velocità di riproduzione dei video + I cambiamenti alla velocità di riproduzione si applicano a tutti i video. + I cambiamenti alla velocità di riproduzione si applicano solo al video corrente. + Mostra una notifica toast al cambio della velocità di riproduzione predefinita dei video + Una notifica toast verrà mostrata al cambio della velocità di riproduzione predefinita dei video. + Una notifica toast non verrà mostrata al cambio della velocità di riproduzione predefinita dei video. + Velocità di riproduzione predefinita degli Shorts + Ricorda i cambiamenti della velocità di riproduzione degli Shorts + "I cambiamenti alla velocità di riproduzione si applicano a tutti gli Shorts. + +Note: +• L'unico modo per modificare la velocità di riproduzione nel riproduttore degli Shorts è usare la \"finestra della velocità\" in \"Azioni personalizzate\". +• Se non hai incluso la patch \"Componenti Shorts\", questa impostazione non sarà disponibile." + I cambiamenti alla velocità di riproduzione si applicano solo allo Short corrente. + Mostra una notifica toast al cambio della velocità di riproduzione predefinita degli Shorts + Una notifica toast verrà mostrata al cambio della velocità di riproduzione predefinita degli Shorts. + Una notifica toast non verrà mostrata al cambio della velocità di riproduzione predefinita degli Shorts. + La velocità di riproduzione predefinita dei video è stata cambiata a %s + La velocità di riproduzione predefinita degli Shorts è stata cambiata a %s Attiva la velocità di riproduzione personalizzata La velocità di riproduzione personalizzata è attivata. La velocità di riproduzione personalizzata è disattivata. @@ -1544,63 +1702,58 @@ Note: Vecchio menù a comparsa Modifica le velocità di riproduzione personalizzate Aggiungi, rimuovi o modifica le velocità di riproduzione. - Ricorda i cambiamenti della velocità di riproduzione - I cambiamenti alla velocità di riproduzione si applicano a tutti i video. - I cambiamenti alla velocità di riproduzione si applicano solo al video corrente. - Mostra una notifica toast al cambio della velocità di riproduzione - Una notifica toast verrà mostrata al cambio della velocità di riproduzione predefinita. - Una notifica toast non verrà mostrata al cambio della velocità di riproduzione predefinita. - Ricorda i cambiamenti della qualità + Le velocità personalizzate devono essere inferiori a %sx + Velocità di riproduzione personalizzate non valide + + Qualità video + Qualità predefinita dei video con connessione dati + Qualità predefinita dei video con Wi-Fi + Ricorda i cambiamenti della qualità dei video I cambiamenti alla qualità si applicano a tutti i video. I cambiamenti alla qualità si applicano solo al video corrente. - Mostra una notifica toast al cambio della qualità - Una notifica toast verrà mostrata quando al cambio della qualità predefinita. - Una notifica toast non verrà mostrata quando al cambio della qualità predefinita. + Mostra una notifica toast al cambio della qualità predefinita dei video + Una notifica toast verrà mostrata al cambio della qualità predefinita dei video. + Una notifica toast non verrà mostrata al cambio della qualità predefinita dei video. + Qualità predefinita degli Shorts con connessione dati + Qualità predefinita degli Shorts con Wi-Fi + Ricorda i cambiamenti della qualità degli Shorts + I cambiamenti alla qualità si applicano a tutti gli Shorts. + I cambiamenti alla qualità si applicano solo allo Short corrente. + Mostra una notifica toast al cambio della qualità predefinita degli Shorts + Una notifica toast verrà mostrata al cambio della qualità predefinita degli Shorts. + Una notifica toast non verrà mostrata al cambio della qualità predefinita degli Shorts. + Cellulare + Wi-Fi + La qualità predefinita dei video è stata cambiata da %1$s a %2$s + La qualità predefinita degli Shorts è stata cambiata da %1$s a %2$s Attiva il vecchio menù Qualità Il vecchio menù Qualità è attivato. Il vecchio menù Qualità è disattivato. + Buffer precaricato saltato + Salta il buffer precaricato + "Salta il buffer precaricato all'avvio dei video per applicare immediatamente la qualità video predefinita. + +Note: +• Quando il video inizia, c'è un ritardo di circa 0.3 secondi. +• Non si applica agli Shorts, ai video HDR, live e più brevi di 15 secondi. +• L'impostazione \"Camuffa i dati in streaming\" rimuove anche i buffer precaricati, pertanto questa impostazione non è necessaria se si usa tale impostazione." + L\'attivazione di questa impostazione potrebbe causare problemi di riproduzione. + Mostra una notifica toast quando un segmento è saltato + Una notifica toast verrà mostrata quando un segmento è saltato. + Una notifica toast non verrà mostrata quando un segmento è saltato. + Camuffa le dimensioni del dispositivo + "Camuffa le dimensioni del dispositivo al valore massimo. + +Note: +• L'alta qualità potrebbe essere sbloccata su alcuni video che richiedono dimensioni di dispositivo elevate, ma non su tutti i video. +• Questa impostazione non è disponibile se è stato attivato \"Camuffa i dati in streaming\"." + Disattiva la velocità di riproduzione predefinita per la musica La velocità di riproduzione predefinita per la musica è disattivata. La velocità di riproduzione predefinita per la musica è attivata. Convalida usando le categorie La velocità di riproduzione predefinita è disattivata se la categoria video è Musica. La velocità di riproduzione predefinita è attivata se la categoria video è Musica. - Attiva la velocità di riproduzione predefinita negli Shorts - La velocità di riproduzione predefinita negli Shorts è attivata. - La velocità di riproduzione predefinita negli Shorts è disattivata. - Buffer precaricato saltato - Salta il buffer precaricato - "Salta il buffer precaricato all'avvio dei video per bypassare il ritardo della qualità video predefinita forzata. - -Note: -• Quando il video inizia, c'è un ritardo di circa 0.7 secondi, ma la qualità video predefinita viene applicata immediatamente. -• Non si applica ai video HDR, live e più brevi di 10 secondi." - L\'attivazione di questa impostazione potrebbe causare problemi di riproduzione. - Mostra una notifica toast quando un segmento è saltato - Una notifica toast verrà mostrata quando un segmento è saltato. - Una notifica toast non verrà mostrata quando un segmento è saltato. - Camuffa le dimensioni del dispositivo - "Camuffa le dimensioni del dispositivo portandole al valore massimo. -Nota: la qualità alta potrebbe essere sbloccata per alcuni video, ma non per tutti, che richiedono dimensioni del dispositivo elevate." - Disattiva il codec VP9 - "Il codec VP9 è disattivato. - -• La risoluzione massima è 1080p. -• La riproduzione userà più dati internet di VP9. -• VP9 è usato per la riproduzione HDR." - Il codec VP9 è attivato. - Sostituisci il codec AV1 - Sostituisce il codec AV1 con il codec VP9. - Rifiuta la risposta del codec AV1 - "Rifiuta forzatamente la risposta del codec AV1. -Verrà applicato un codec diverso dopo circa 20 secondi di buffering." - Il processo di fallback provoca circa 20 secondi di buffering - Cambiando la velocità di riproduzione predefinita a %s - Cambiando la qualità video predefinita con connessione dati a %s - Impossibile impostare la qualità video - Cambiando la qualità video predefinita con Wi-Fi a %s - Le velocità personalizzate devono essere inferiori a %sx - Velocità di riproduzione personalizzate non valide Return YouTube Dislike Attiva Return YouTube Dislike @@ -1730,6 +1883,7 @@ Tocca qui per vedere come emettere una chiave API." Mostra un pulsante salta Mostra nella barra di avanzamento Disabilita + Opacità: Colore: Colore cambiato Colore ripristinato @@ -1796,6 +1950,8 @@ Tocca qui per vedere come emettere una chiave API." Voto negativo Cambia la categoria Non ci sono segmenti per cui votare. + + %1$s a %2$s Scegli la categoria del segmento La categoria è disattivata nelle impostazioni. Attiva la categoria da inviare. Nuovo segmento SponsorBlock @@ -1913,8 +2069,8 @@ Tocca il pulsante Continua e consenti le modifiche di ottimizzazione." Android VR "Android VR (Nessun accesso richiesto)" - "iOS -(PoToken richiesto)" + "iOS +(Obsoleto)" "iOS TV (Accesso richiesto)" Effetti collaterali del camuffamento @@ -1922,10 +2078,20 @@ Tocca il pulsante Continua e consenti le modifiche di ottimizzazione." • Il volume stabile non funziona. • La disattivazione delle tracce audio automatiche forzate non funziona. • I video per bambini potrebbero non essere riprodotti se si è disconnessi o navigando in incognito." - • Ci potrebbero essere problemi di riproduzione (PoToken richiesto). + • Interi intervalli di ASN/IP potrebbero essere bloccati dal server. "• Il volume stabile non funziona. • I film o i video a pagamento potrebbero non essere riproducibili. • I video per bambini potrebbero non essere riprodotti se si è disconnessi o navigando in incognito." + Usa il client iOS + "Il client iOS è stato aggiunto ai client disponibili. + +Nota: il client iOS è obsoleto, pertanto eventuali problemi riscontrati durante l'uso sono a tuo rischio." + Il client iOS non è stato aggiunto ai client disponibili. + "Quando richiedi gli endpoint API di YouTube tramite iOS, avrai bisogno dei token Auth emessi dal dispositivo iOS e dei PoToken emessi da iOSGuard. + +Ciò significa che alle richieste di streaming tramite iOS mancheranno sia i token Auth che i PoToken, e il server potrebbe considerare l'utente come un bot e bloccare l'intero intervallo ASN/IP. + +Usalo a tuo rischio!" Forza iOS AVC (H.264) Il codec video è forzato ad AVC (H.264). Il codec video è determinato automaticamente. @@ -1943,6 +2109,8 @@ Note: Il client usato per recuperare i dati in streaming è visibile nelle statistiche per nerd. Il client usato per recuperare i dati in streaming è nascosto nelle statistiche per nerd. Lingua predefinita dello stream audio VR + Non è stato possibile recuperare alcun flusso client + Potresti non essere connesso PoToken / VisitorData PoToken da usare 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 f25311f54..8bd5b2d92 100644 --- a/patches/src/main/resources/youtube/translations/ja-rJP/strings.xml +++ b/patches/src/main/resources/youtube/translations/ja-rJP/strings.xml @@ -19,59 +19,95 @@ "%1$s はインストールされていません。 ウェブサイトから %2$s をダウンロードしてください。" %s はインストールされていません。インストールしてください。 + キューに追加 + キューに追加してキューを開く + キューに追加して動画を再生 + 外部ダウンローダー + キューを開く + キュー + キューから削除 + キューから削除してキューを開く + キューを削除 + キューを保存 + "外部ダウンローダーを開く代わりに、キューマネージャーダイアログを開きます。 + +ナビゲーションバーの戻るボタンを長押ししてキューマネージャーを開くこともできます。 + +この機能はまだ開発中であるため、ほとんどの機能が動作しない可能性があります。 + +デバッグ目的でのみ使用してください。" + ログインが必要です。 + キューマネージャーは利用できません (%s)。 + プレイリストを識別できませんでした。 + キューは空です + 動画を識別できませんでした + 動画を追加できませんでした。 + キューを作成できませんでした。 + キューを削除できませんでした。 + 動画を削除できませんでした。 + キューを保存できませんでした。 + 動画を追加しました。 + キューを作成しました。 + キューを削除しました。 + 動画を削除しました。 + キューは「%s」に保存されました。 YouTube と Revanced Extended 設定の言語 アプリの設定言語に従う - アラビア語 - アゼルバイジャン語 - ブルガリア語 - ベンガル語 - カタロニア語 - チェコ語 - デンマーク語 - ドイツ語 - ギリシャ語 - 英語 - スペイン語 - エストニア語 - ペルシャ語 - フィンランド語 - フランス語 - グジャラート語 - ヒンディー語 - クロアチア語 - ハンガリー語 - インドネシア語 - イタリア語 - 日本語 - カザフ語 - 韓国語 - リトアニア語 - ラトビア語 - マケドニア語 - モンゴル語 - マラーティー語 - マレー語 - ビルマ語 (ミャンマー語) - オランダ語 - オディア語 - パンジャーブ語 - ポーランド語 - ポルトガル語 - ルーマニア語 - ロシア語 - スロバキア語 - スロベニア語 - セルビア語 - スウェーデン語 - スワヒリ語 - タミル語 - テルグ語 - タイ語 - トルコ語 - ウクライナ語 - ウルドゥー語 - ベトナム語 - 中国語 + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" 広告 終了画面の商品バナーを非表示 @@ -396,9 +432,6 @@ DeArrow の詳細については、ここをタップしてください。"スプラッシュアニメーションを無効化 YouTube 起動時のスプラッシュアニメーションを無効にします。 YouTube 起動時のスプラッシュアニメーションを無効にします。 - 半透明なステータスバーを無効化 - ステータスバーを不透明にします。 - ステータスバーを不透明にします。 グラデーションの読み込み画面を有効化 アプリ起動時や動画の読み込み画面などでグラデーションを有効化します。 アプリ起動時や動画の読み込み画面などでグラデーションを有効化します。 @@ -433,6 +466,24 @@ DeArrow の詳細については、ここをタップしてください。" + レイアウトの更新を無効化 + サーバー側によるレイアウトの更新を無効化します。 + サーバー側によるレイアウトの更新を無効化します。 + "アプリのレイアウトは初回インストール時の状態に戻ります。 + +一部のサーバー側のレイアウトは初回インストール時の状態に戻らない可能性があります。 + +主な変更点: +・プレーヤー‎内のフライアウトメニュー(及び/または関連設定)のコンポーネントが動作しない可能性があります。 +・数字の回転アニメーションが無効になります。 +・[マイページ]タブではなく[ライブラリ]タブが使用されます。 +・概要欄の音楽セクションが動作しない可能性があります。 +・[ライブラリ]タブでアカウント切り替えボタンが表示されない可能性があります。 +→「[マイページ]タブで幅の広い検索バーを有効化」設定を使用してください。" + 半透明なステータスバーを無効化 + ステータスバーを不透明にします。 + ステータスバーを不透明にします。 + Android 12 以降を実行している一部のメーカーの ROM では、この機能を有効にすると、システムナビゲーションバーが透明になる可能性があります。 アプリのバージョンを偽装 アプリのバージョンを偽装できます。 アプリのバージョンを偽装できます。 @@ -450,8 +501,10 @@ DeArrow の詳細については、ここをタップしてください。"18.33.40 - ショートの古いアクションバーを復元 18.38.45 - 以前のデフォルトの画質の動作を復元 18.48.39 - リアルタイムで更新される再生回数と高評価数を無効化 + 19.01.34 - 概要欄のインタラクションを無効化 19.26.42 - ナビゲーションとツールバーで Cairo アイコンを無効化 19.33.37 - 古い再生速度のフライアウトパネルを復元 + 無効な偽装アプリバージョンです: %s 「アカウント」メニュー 「アカウント」メニューと「マイページ」タブで要素を非表示または表示します。 @@ -487,6 +540,9 @@ DeArrow の詳細については、ここをタップしてください。"「オフライン」ボタンを置換 「オフライン」ボタンで外部ダウンローダーを開きます。 「オフライン」ボタンで外部ダウンローダーを開きます。 + 「オフライン」ボタンでキューマネージャーを開く + 「オフライン」ボタンで外部キューマネージャーを開きます。 + 「オフライン」ボタンで外部キューマネージャーを開きます。 プレイリストの外部ダウンローダーのパッケージ名 NewPipe や YTDLnis などの、インストールされている外部ダウンローダーアプリのパッケージ名です。 @@ -540,7 +596,6 @@ DeArrow の詳細については、ここをタップしてください。"半透明のナビゲーションバーを有効化 ナビゲーションバー (ホーム、登録チャンネルなどのボタン) を半透明にします。 ナビゲーションバー (ホーム、登録チャンネルなどのボタン) を半透明にします。 - 特定の YouTube バージョンでは、この設定によりシステムナビゲーションバーが透明になるか、ピクチャーインピクチャーモードでレイアウトが崩れることがあります。 ナビゲーションバーを非表示 ナビゲーションバー(ホーム、登録チャンネルなどのボタン)を非表示にします。 ナビゲーションバー(ホーム、登録チャンネルなどのボタン)を非表示にします。 @@ -639,7 +694,7 @@ DeArrow の詳細については、ここをタップしてください。"ヘッダー付きの幅広い検索バーを有効化 ヘッダー付きの幅広い検索バーを有効化します。 ヘッダー付きの幅広い検索バーを有効化します。 - マイページ タブで幅広い検索バーを有効化 + [マイページ] タブで幅の広い検索バーを有効化 "[マイページ] タブで幅広い検索バーを有効化します。 注意: @@ -1117,6 +1172,8 @@ DeArrow の詳細については、ここをタップしてください。"タップすると現在の動画の音声をミュートにします。もう一度タップするとミュートを解除します。 外部ダウンローダーボタンを表示 タップすると外部ダウンローダーが起動します。 + 「オフライン」ボタンでキューマネージャーを開く + 外部ダウンローダーを起動する代わりに、キューマネージャーを開きます。 再生速度のダイアログボタンを表示 "タップすると再生速度のダイアログが開きます。 長押しすると再生速度を 1.0 倍にリセットします。もう一度長押しするとデフォルトの再生速度に戻ります。" @@ -1155,7 +1212,7 @@ DeArrow の詳細については、ここをタップしてください。"ホワイトリストに登録されています。 再生速度 SponsorBlock - チャンネル情報の読み込みに失敗しました。 + チャンネル情報を読み込めませんでした。 再生速度を %s 倍速にリセットしました。 ボタンの状態を変更するには、長押ししてください。 タイムスタンプをクリップボードにコピーしました。 (%s) @@ -1412,10 +1469,8 @@ DeArrow の詳細については、ここをタップしてください。"フライアウトメニューのカスタムアクションを有効化 "フライアウトメニューでカスタムアクションを使用できるようにします。 -注意: -・YouTube のバージョンが 18.49.37 以前に偽装されている場合は機能しません。 -・ライブ配信では機能しません。" - フライアウトメニューでカスタムアクションを使用できるようにします。\n\n注意: \n・YouTube のバージョンが 18.49.37 以前に偽装されている場合は機能しません。\n・ライブ配信では機能しません。 +注意: ライブ配信では機能しません。" + フライアウトメニューでカスタムアクションを使用できるようにします。\n\n注意: ライブ配信では機能しません。 ツールバーのカスタムアクションを有効化 "ツールバーでカスタムアクションを使用できるようにします。 @@ -1438,6 +1493,10 @@ DeArrow の詳細については、ここをタップしてください。"動画を開くメニューを表示 動画を開くメニューを表示します。 動画を開くメニューを表示します。 + 再生速度ダイアログ + 再生速度のダイアログメニューを表示 + 再生速度のダイアログメニューを表示します。 + 再生速度のダイアログメニューを表示します。 リピート状態 リピート状態のメニューを表示 リピート状態のメニューを表示します。 @@ -1482,10 +1541,14 @@ DeArrow の詳細については、ここをタップしてください。"スワイプして明るさを 0 にして、明るさの自動調節を有効化します。 スワイプして明るさを 0 にして、明るさの自動調節を有効化します。 明るさのジェスチャーを有効化 - 明るさのスワイプコントロールを有効化します。 + "明るさのスワイプコントロールを有効化します。 + +画面左側を垂直にスワイプして明るさを調整できます。" 明るさのスワイプコントロールを有効化します。 音量ジェスチャーを有効化 - 音量のスワイプコントロールを有効化します。 + "音量のスワイプコントロールを有効化します。 + +画面左側を垂直にスワイプして音量を調整できます。" 音量のスワイプコントロールを有効化します。 明るさの保存と復元を有効化 全画面表示を終了 / 開始した際に明るさを保存/復元します。 @@ -1499,10 +1562,20 @@ DeArrow の詳細については、ここをタップしてください。"「画面のロック」時のスワイプジェスチャーを有効化 スワイプジェスチャーを「画面のロック」モードで有効化します。 スワイプジェスチャーを「画面のロック」モードで有効化します。 - スワイプオーバーレイの背景の透明度 - スワイプオーバーレイの背景の透明度です。 スワイプ可能な領域のしきい値 スワイプとして検出する量のしきい値です。 + 代替の UI でスワイプオーバーレイを表示 + スワイプオーバーレイを代替の UI で表示します。 + スワイプオーバーレイを代替の UI で表示します。 + オーバーレイを最小化 + オーバーレイのスタイルを最小化します。 + オーバーレイのスタイルを最小化します。 + 円形のオーバーレイを表示 + 円形のオーバーレイを表示します。 + 円形のオーバーレイを表示します。 + スワイプオーバーレイの背景の透明度 + 不透明度は 0~100 の間の値です。 + 透明度の値は 0~100 の間でなければなりません。 スワイプオーバーレイのテキストサイズ スワイプオーバーレイのテキストサイズです。 スワイプオーバーレイの画面サイズ @@ -1534,12 +1607,43 @@ DeArrow の詳細については、ここをタップしてください。"自動 動画 - デフォルトの再生速度 - モバイルネットワーク使用時のデフォルト画質 - Wi-Fi 使用時のデフォルト画質 + + コー​​デック HDR 動画を無効化 HDR 動画を無効化します。 HDR 動画を無効化します。 + VP9 コーデックを無効化 + "VP9 コーデックを無効化します。 + +注意: +・最大解像度は 1080p です。 +・動画を再生する際には VP9 コーデックよりも多くの通信量を消費します。 +・HDR 動画では引き続き VP9 コーデックが使用されます。" + VP9 コーデックを無効化します。\n\n注意: \n・最大解像度は 1080p です。\n・動画を再生する際には VP9 コーデックよりも多くの通信量を消費します。\n・HDR 動画では引き続き VP9 コーデックが使用されます。 + AV1 コーデックを置換 + AV1 コーデックを VP9 コーデックに置き換えます。 + + 再生速度 + デフォルトの再生速度 + 再生速度の変更を保存 + 現在の設定: 再生速度の変更はすべての動画に適用されます。 + 現在の設定: 再生速度の変更は現在の動画にのみ適用されます。 + トーストを表示 + デフォルトの再生速度を変更した際にトーストが表示されるようにします。 + デフォルトの再生速度を変更した際にトーストが表示されるようにします。 + ショートのデフォルトの再生速度 + ショートの再生速度の変更を記憶 + "現在の設定: 再生速度の変更はすべてのショートに適用されます。 + +注意: +・ショートのプレーヤーで再生速度を変更する唯一の方法は、「カスタムアクション」の「再生速度ダイアログ」を使用することです。 +・「Shorts components」パッチを含めていない場合、この機能は利用できません。" + 現在の設定: 再生速度の変更は現在のショートにのみ適用されます。 + トーストを表示 + ショートでデフォルトの再生速度を変更した際にトーストを表示します。 + ショートでデフォルトの再生速度を変更した際にトーストを表示します。 + デフォルトの再生速度を %s に変更しました。 + デフォルトのショートの再生速度を %s に変更しました。 カスタム再生速度を有効化 再生速度のカスタムを有効化します。 再生速度のカスタムを有効化します。 @@ -1548,64 +1652,58 @@ DeArrow の詳細については、ここをタップしてください。"現在の設定: 古いスタイルのフライアウトパネルが使用されます。 カスタム再生速度の編集 利用可能な再生速度を追加または変更します。 - 再生速度の変更を保存 - 現在の設定: 再生速度の変更はすべての動画に適用されます。 - 現在の設定: 再生速度の変更は現在の動画にのみ適用されます。 - トーストを表示 - デフォルトの再生速度を変更した際にトーストが表示されるようにします。 - デフォルトの再生速度を変更した際にトーストが表示されるようにします。 + カスタム再生速度は %s 倍速未満である必要があります。 + 無効なカスタム再生速度です。デフォルトの値を使用します。 + + 画質 + モバイルデータ通信使用時のデフォルト画質 + Wi-Fi 使用時のデフォルト画質 画質の変更を保存 現在の設定: 画質の変更はすべての動画に適用されます。 現在の設定: 画質の変更は現在の動画にのみ適用されます。 トーストを表示 デフォルトの画質を変更した際にトーストが表示されるようにします。 デフォルトの画質を変更した際にトーストが表示されるようにします。 + モバイルデータ通信使用時のデフォルト画質 + Wi-Fi 使用時のデフォルト画質 + ショートの画質変更を記憶 + 現在の設定: 画質の変更はすべてのショートに適用されます。 + 現在の設定: 画質の変更は現在のショートにのみ適用されます。 + トーストを表示 + ショートの画質を変更した際にトーストを表示します。 + ショートの画質を変更した際にトーストを表示します。 + モバイルデータ通信 + Wi-Fi + デフォルトの画質 (%1$s) を %2$s に変更しました。 + ショートの画質 (%1$s) を %2$s に変更しました。 古いスタイルの画質メニューを復元 古いスタイルの画質設定メニューを復活させます。 古いスタイルの画質設定メニューを復活させます。 - 音楽再生時にデフォルトの再生速度を無効化 - 音楽を再生する際に「デフォルトの再生速度」で設定した再生速度を無効化します。 - 音楽を再生する際に、「デフォルトの再生速度」で設定した再生速度を無効化します。\n\n注意: この設定は、「YouTube Music で聴く」バナーが表示されている動画にのみ適用されます。 - 自動的にデフォルトの再生速度を無効化 - 動画のカテゴリが「音楽」の場合、デフォルトの再生速度を無効にします。 - 動画のカテゴリが「音楽」の場合、デフォルトの再生速度を無効にします。 - ショートのデフォルト再生速度を有効化 - デフォルトの再生速度をショートに適用します。 - デフォルトの再生速度をショートに適用します。 事前に読み込まれたバッファをスキップしました。 事前に読み込まれたバッファをスキップ "動画開始時に予め読み込まれたバッファをスキップして、デフォルトの画質を即座に適用します。 注意: ・動画開始時に約 0.3 秒の遅延が発生します。 -・HDR 動画、ライブ配信、15 秒未満の動画には適用されません。" +・ショート、HDR 動画、ライブ配信、15 秒未満の動画には適用されません。 +・「ストリーミングデータを偽装」設定を既に有効化している場合、予め読み込まれたバッファはスキップされるため、この設定は必要ありません。" この設定をオンにした場合、動画が再生できない問題が発生する可能性があります。 スキップ時にトーストを表示 スキップ時にトーストを表示します。 スキップ時にトーストを表示します。 デバイスの解像度を偽装 "デバイスの解像度を最大値に偽装します。 -高画質は、高いデバイスの解像度を必要とする一部の動画でアンロックされる可能性がありますが、すべての動画でアンロックされるわけではありません。" - VP9 コーデックを無効化 - "VP9 コーデックを無効化します。 -注意: -・最大解像度は 1080p です。 -・動画を再生する際には VP9 コーデックよりも多くの通信量を消費します。 -・HDR 再生を可能にするために、HDR 動画では引き続き VP9 コーデックが使用されます。" - VP9 コーデックを無効化します。\n\n注意: \n・最大解像度は 1080p です。\n・動画を再生する際には VP9 コーデックよりも多くの通信量を消費します。\n・HDR 再生を可能にするために、HDR 動画では引き続き VP9 コーデックが使用されます。 - AV1 コーデックを置換 - AV1 コーデックを VP9 コーデックに置き換えます。 - AV1 コーデックの応答を拒否 - "AV1 コーデック応答を強制的に拒否します。 -約 20 秒間のバッファリングの後、異なるコーデックに切り替わります。" - フォールバック処理で約20秒のバッファリングが発生します。 - デフォルトの再生速度を %s に変更しました。 - モバイルネットワーク使用時のデフォルト画質を %s に変更しました。 - 画質を設定できませんでした。 - Wi-Fi 使用時のデフォルト画質を %s に変更しました。 - カスタム再生速度は %s 倍速未満である必要があります。デフォルト値にリセットします。 - 無効なカスタム再生速度です。デフォルトの値を使用します。 +注意: +・高いデバイス解像度を必要とする一部の動画では高画質が利用可能になる可能性がありますが、すべての動画で利用可能になるわけではありません。 +・「ストリーミングデータを偽装」が有効化されている場合、この機能は利用できません。" + + 音楽再生時にデフォルトの再生速度を無効化 + 音楽を再生する際に「デフォルトの再生速度」で設定した再生速度を無効化します。 + 音楽を再生する際に、「デフォルトの再生速度」で設定した再生速度を無効化します。\n\n注意: この設定は、「YouTube Music で聴く」バナーが表示されている動画にのみ適用されます。 + 自動的にデフォルトの再生速度を無効化 + 動画のカテゴリが「音楽」の場合、デフォルトの再生速度を無効にします。 + 動画のカテゴリが「音楽」の場合、デフォルトの再生速度を無効にします。 Return YouTube Dislike Return YouTube Dislike を有効化 @@ -1735,6 +1833,7 @@ API キーの発行方法については、ここをタップしてください スキップボタンを表示 シークバーに表示 無効 + 不透明度: 色: 色を変更しました。 色をリセットしました。 @@ -1776,31 +1875,33 @@ API キーの発行方法については、ここをタップしてください API の URl が無効です。 API の URL を変更しました。 コピー - 設定のインポート/エクスポート + 設定のインポート / エクスポート SponsorBlock 設定の JSON は、ReVanced Extended やその他の SponsorBlock プラットフォームにインポート/エクスポートできます。 ReVanced Extended や他のプラットフォームの SponsorBlock にインポート/エクスポート可能な SponsorBlock 設定の JSON です。これにはプライベートユーザー ID が含まれています。共有する際は十分注意してください。 設定は正常にインポートされました。 - インポート失敗: %s - エクスポート失敗: %s + インポートできませんでした: %s + エクスポートできませんでした: %s この設定には SponsorBlock のプライベートユーザー ID が含まれています。\n\nユーザー ID はパスワードのようなものであるため、誰とも共有しないようにしてください。\n 今後表示しない SponsorBlock は一時的に利用できません。 SponsorBlock は一時的に利用できません。(ステータス %d) SponsorBlock は一時的に利用できません。(API がタイムアウトしました) - セグメントを送信できません: %s + セグメントを送信できませんでした: %s SponsorBlock は一時的に停止しています。 - セグメントを送信できません (ステータス: %1$d %2$s) - セグメントを送信できません\nレート制限 (同じユーザー / IP からの送信が多すぎます) + セグメントを送信できませんでした (ステータス: %1$d %2$s) + セグメントを送信できませんでした。\nレート制限 (同じユーザー / IP からの送信が多すぎます) セグメントを送信できません: %s セグメントを送信できません。\n既に存在します。 セグメントは正常に送信されました - セグメントを評価できません (API がタイムアウトしました) - セグメントの評価を送信できません (ステータス: %1$d %2$s) - セグメントの評価を送信できません: %s + セグメントの評価を送信できませんでした (API がタイムアウトしました) + セグメントの評価を送信できませんでした (ステータス: %1$d %2$s) + セグメントに投票できません: %s 賛成 反対 カテゴリーを変更 評価できるセグメントがありません + + %1$s から %2$s セグメントのカテゴリを選択してください カテゴリーは設定で無効になっています。送信するにはカテゴリーを有効にしてください。 新しい SponsorBlock セグメント @@ -1898,11 +1999,11 @@ GmsCore の電池の最適化を無効にしても、バッテリーの使用に テキストとしてインポート/エクスポート テキストとしてインポート/エクスポート 設定をテキストとしてインポートまたはエクスポートします。 - 設定のエクスポートに失敗しました。 + 設定をエクスポートできませんでした。 設定は正常にエクスポートされました。 インポート コピー - 設定のインポートに失敗しました。 + 設定をインポートできませんでした。 設定をデフォルトにリセットしました。 設定は正常にインポートされました。 リセット @@ -1914,14 +2015,14 @@ GmsCore の電池の最適化を無効にしても、バッテリーの使用に ストリーミングデータを偽装していない場合、動画の再生ができない可能性があります。 "ストリーミングデータを偽装していない場合、動画の再生ができない可能性があります。" この設定をオフにした場合、動画が再生できない問題が発生する可能性があります。 - 偽装するクライアントの種類 + 偽装するクライアントを選択 "Android TV (Google アカウントへのログインが必要)" Android VR "Android VR (認証なし)" - "iOS -(PoToken が必要)" + "iOS +(非推奨)" "iOS TV (Google アカウントへのログインが必要)" ストリーミングデータを偽装することによる副作用 @@ -1929,10 +2030,20 @@ GmsCore の電池の最適化を無効にしても、バッテリーの使用に ・「一定音量」は利用できません。 ・「音声トラックの強制を無効化」は利用できません。 ・ログインをしていない場合やシークレットモードでは、子供向け動画は再生できない可能性があります。" - ・動画が再生できない可能性があります(PoToken が必要です)。 + ・ASN/IP 範囲全体がサーバーによってブロックされる可能性があります。 "・「一定音量」は利用できません。 ・映画や有料動画は再生できない可能性があります。 ・ログインをしていない場合やシークレットモードでは、子供向け動画は再生できない可能性があります。" + iOS クライアントを使用 + "偽装するクライアントの一覧に iOS クライアントを追加します。 + +警告: iOS クライアントは非推奨です。使用中に問題が発生した場合は、自己責任で対処してください。" + 偽装するクライアントの一覧に iOS クライアントを追加します。\n\n警告: iOS クライアントは非推奨です。使用中に問題が発生した場合は、自己責任で対処してください。 + "iOS を使用して YouTube API エンドポイントをリクエストする場合、iOS デバイスによって発行された Auth トークンと iOSGuard によって発行された PoToken が必要になります。 + +つまり、iOS 経由のストリーミングリクエストでは Auth トークンと PoToken の両方が欠落し、サーバーがユーザーをボットと見なして ASN/IP 範囲全体をブロックする可能性があります。 + +自己責任で使用してください。" iOS クライアントで AVC (H.264) を強制 iOS クライアントで AVC (H.264) を強制します。 iOS クライアントで AVC (H.264) を強制します。 @@ -1951,6 +2062,8 @@ GmsCore の電池の最適化を無効にしても、バッテリーの使用に 統計情報に偽装したクライアントを表示します。 統計情報に偽装したクライアントを表示します。 偽装するクライアントを Android VR に設定した際にデフォルトで使用する音声トラックの言語 + クライアントストリームを取得できませんでした。 + ログインをしていない可能性があります。 PoToken / VisitorData 使用する PoToken を入力 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 8818e060a..782ea8c39 100644 --- a/patches/src/main/resources/youtube/translations/ko-rKR/strings.xml +++ b/patches/src/main/resources/youtube/translations/ko-rKR/strings.xml @@ -6,7 +6,7 @@ RVX 설정 %s 검색 - 기본값으로 초기화합니다. + 기본값으로 초기화하였습니다. 실험 기능 계속하시겠습니까? 레이아웃을 정상적으로 불러오기 위해 다시 시작합니다. @@ -17,61 +17,152 @@ 외부 다운로더 앱 경고 "%1$s 가 설치되어 있지 않습니다. -웹사이트에서 %2$s 를 다운로드하세요." +웹사이트에서 %2$s 를 다운로드하고 설치하세요." %s가 설치되지 않았습니다. 설치하세요. + 현재 재생목록에 추가 + 현재 재생목록에 추가하고 현재 재생목록 열기 + 현재 재생목록에 추가하고 동영상 재생 + 외부 다운로더 + 현재 재생목록 열기 + 현재 재생목록 + 현재 재생목록에서 제거 + 현재 재생목록에서 제거하고 현재 재생목록 열기 + 현재 재생목록 제거 + 현재 재생목록 저장 + "외부 다운로더가 아닌 현재 재생목록 관리자 다이얼로그를 실행할 수 있습니다. + +시스템 네비게이션 바에서 '뒤로 가기' 버튼을 길게 눌러서 현재 재생목록 관리자를 실행할 수도 있습니다. + +이 기능은 아직 개발 중이므로 대부분의 기능이 작동하지 않을 수 있습니다. + +디버깅 목적으로만 사용하세요." + 로그인이 요구됨 + 현재 재생목록 관리자를 실행할 수 없습니다. (%s) + 재생목록을 식별할 수 없음 + 현재 재생목록이 비어져 있음 + 동영상을 식별할 수 없음 + 동영상을 추가할 수 없습니다. + 현재 재생목록을 만들 수 없습니다. + 현재 재생목록을 제거할 수 없습니다. + 동영상을 제거할 수 없습니다. + 현재 재생목록을 저장할 수 없습니다. + 동영상을 성공적으로 추가하였습니다. + 현재 재생목록을 성공적으로 만들었습니다. + 현재 재생목록을 성공적으로 제거하였습니다. + 동영상을 성공적으로 제거하였습니다. + 현재 재생목록을 \'%s\'에 성공적으로 저장하였습니다. RVX 언어 앱 언어 - 아랍어 - 아제르바이잔어 - 불가리아어 - 뱅골어 - 카탈루냐어 - 체코어 - 덴마크어 - 독일어 - 그리스어 - 영어 - 스페인어 - 에스토니아어 - 페르시아어 - 핀란드어 - 프랑스어 - 구자라트어 - 힌디어 - 크로아티아어 - 헝가리어 - 인도네시아어 - 이탈리아어 - 일본어 - 카자흐어 - 한국어 - 리투아니아어 - 라트비아어 - 마케도니아어 - 몽골어 - 마라티어 - 말레이어 - 버마어 - 네덜란드어 - 오리야어 - 펀자브어 - 폴란드어 - 포르투갈어 - 루마니아어 - 러시아어 - 슬로바키아어 - 슬로베니아어 - 세르비아어 - 스웨덴어 - 스와힐리어 - 타밀어 - 텔루구어 - 태국어 - 터키어 - 우크라이나어 - 우르두어 - 베트남어 - 중국어 + "암하라어 +አማርኛ" + "아랍어 +العربية" + "아제르바이잔어 +Azərbaycan" + "벨라루스어 +беларуская" + "불가리아어 +Български" + "뱅골어 +বাংলা" + "카탈로니아어 +Català" + "체코어 +Čeština" + "덴마크어 +Dansk" + "독일어 +Deutsch" + "그리스어 +Ελληνικά" + "영어 +English" + "스페인어 +Español" + "에스토니아어 +Eesti" + "페르시아어 +فارسی" + "핀란드어 +Suomi" + "프랑스어 +Français" + "구자라트어 +ગુજરાતી" + "히브리어 +עברי" + "힌디어 +हिन्दी" + "크로아티아어 +Hrvatski" + "헝가리어 +Magyar" + "인도네시아어 +Indonesia" + "이탈리아어 +Italiano" + "일본어 +日本語" + "카자흐어 +Қазақ тілі" + "한국어 +한국어" + "리투아니아어 +Lietuvių" + "라트비아어 +Latviešu" + "마케도니아어 +Македонски" + "몽골어 +Монгол" + "마라티어 +मराठी" + "말레이어 +Melayu" + "버마어 +ဗမာ" + "네덜란드어 +Nederlands" + "오리야어 +ଓଡ଼ିଆ" + "펀잡어 +ਪੰਜਾਬੀ" + "폴란드어 +Polski" + "포르투갈어 +Português" + "루마니아어 +Română" + "러시아어 +Русский" + "슬로바키아어 +Slovenčina" + "알바니아어 +Shqip" + "슬로베니아어 +Slovenščina" + "세르비아어 +Српски" + "스웨덴어 +Svenska" + "스와힐리어 +Kiswahili" + "타밀어 +தமிழ்" + "텔루구어 +తెలుగు" + "태국어 +ไทย" + "터키어 +Türkçe" + "우크라이나어 +Українська" + "우르두어 +اردو" + "베트남어 +Tiếng Việt" + "중국어 +中文" 광고 최종 화면에서 스토어 배너 숨기기 @@ -80,7 +171,7 @@ 전체 화면 광고 숨기기 전체 화면 광고가 숨겨집니다. 전체 화면 광고가 표시됩니다. - 전체 화면 광고가 닫아집니다. + 전체 화면 광고가 닫아졌습니다. 일반 레이아웃 광고 숨기기 일반 레이아웃 광고가 숨겨집니다. 일반 레이아웃 광고가 표시됩니다. @@ -114,7 +205,7 @@ 대체 썸네일 홈 탭 - 플레이어: 재생목록, 관련 동영상 ... + 플레이어: 재생목록, 관련 동영상, etc. 검색 결과 구독 탭 내 페이지 탭 @@ -149,7 +240,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 이미지 표시 제한 국가 이미지 표시 제한 국가 우회하기 대체 이미지 도메인을 사용합니다.\n\n대체 이미지 도메인 기본값: yt4.ggpht.com - 기본 이미지 도메인을 사용합니다.\n\n이 설정을 활성화하면 일부 국가에서 차단된 이미지를 수신할 수 있습니다. (채널 프로필 사진, 커뮤니티 게시물 이미지 ...) + 기본 이미지 도메인을 사용합니다.\n\n이 설정을 활성화하면 일부 국가에서 차단된 이미지를 수신할 수 있습니다. (채널 프로필 사진, 커뮤니티 게시물 이미지, etc.) 피드 음악 앨범 카드 숨기기 @@ -168,14 +259,14 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." • 주요 뉴스, 뉴스 속보 • 맞춤 실시간 스트림 • 라이브 쇼핑 -• 보건 정보 출처 ..." - 다음 선반이 표시됩니다:\n• 다시 듣기\n• 다시 시청하기\n• 이어서 시청하기\n• 채널 더보기\n• 이 게임 더보기\n• 주요 뉴스, 뉴스 속보\n• 맞춤 실시간 스트림\n• 라이브 쇼핑\n• 보건 정보 출처 ... +• 보건 정보 출처, etc." + 다음 선반이 표시됩니다:\n• 다시 듣기\n• 다시 시청하기\n• 이어서 시청하기\n• 채널 더보기\n• 이 게임 더보기\n• 주요 뉴스, 뉴스 속보\n• 맞춤 실시간 스트림\n• 라이브 쇼핑\n• 보건 정보 출처, etc. 더 많은 주제 탐색 선반 숨기기 더 많은 주제 탐색 선반이 숨겨집니다. 더 많은 주제 탐색 선반이 표시됩니다. 펼쳐볼 수 있는 정보 숨기기 - 썸네일 하단에서 다음 정보들이 숨겨집니다:\n동영상 설명, 챕터, 주요 순간, 스크립트,\n재생목록의 동영상, 이 동영상에 나온 제품 ... - 썸네일 하단에서 다음 정보들이 표시됩니다:\n동영상 설명, 챕터, 주요 순간, 스크립트,\n재생목록의 동영상, 이 동영상에 나온 제품 ... + 썸네일 하단에서 다음 정보들이 숨겨집니다:\n동영상 설명, 챕터, 주요 순간, 스크립트,\n재생목록의 동영상, 이 동영상에 나온 제품, etc. + 썸네일 하단에서 다음 정보들이 표시됩니다:\n동영상 설명, 챕터, 주요 순간, 스크립트,\n재생목록의 동영상, 이 동영상에 나온 제품, etc. 펼쳐볼 수 있는 선반 숨기기 다음 선반이 숨겨집니다:\n좋아하는 장르 선택 선반 다음 선반이 표시됩니다:\n좋아하는 장르 선택 선반 @@ -308,10 +399,10 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 전체 단어 일치시키기 필터링할 키워드 및 구문을 큰따옴표로 묶으면 동영상 제목과 채널 이름이 부분적으로 일치하지 않도록 방지할 수 있습니다.<br><br>• 예를 들어, <b>\"ai\"</b>라는 키워드로 <b>AI 커리어 완벽 가이드</b>라는 동영상을 숨길 수 있지만, <b>생성형AI가 바꿔놓은 세계</b> 또는 <b>What does fair use mean?</b>라는 동영상은 숨길 수 없습니다.<br>• 그리고 구두점을 단어의 경계로 간주하기 때문에 <b>인공지능(AI)의 원리</b>라는 동영상은 숨길 수 있습니다. 큰따옴표는 다른 단어 내부의 하위 문자열만 무시합니다 (예: <b>fair</b>는 숨길 수 없지만, <b>f(ai)r</b>는 숨김). 키워드를 사용할 수 없습니다: %s - 따옴표를 추가하여 키워드를 사용합니다: %s + 키워드를 사용하려면 따옴표를 추가하세요: %s 키워드에 충돌하는 선언이 있습니다: %s 키워드가 너무 짧아서 따옴표가 필요합니다: %s - 키워드가 모든 동영상을 숨깁니다: %s + 키워드가 모든 동영상을 숨길 것입니다: %s 추천 동영상 조회수가 낮은 추천 동영상 숨기기 @@ -397,9 +488,6 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 스플래시 애니메이션 비활성화하기 앱을 시작할 때, 스플래시 애니메이션을 비활성화합니다. 앱을 시작할 때, 스플래시 애니메이션을 활성화합니다. - 반투명 상태바 비활성화하기 - 상태바가 불투명합니다. - 상태바가 불투명하거나 반투명합니다. 그라데이션 색상 로딩 화면 활성화하기 그라데이션 색상 로딩 화면을 활성화합니다. 그라데이션 색상 로딩 화면을 비활성화합니다. @@ -410,7 +498,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 동영상들 사이에서 회색 구분선이 숨겨집니다. 동영상들 사이에서 회색 구분선이 표시됩니다. 시청 경고 다이얼로그 제거하기 - "다음 동영상을 시청하기 전에 표시되는 시청 경고 다이얼로그를 제거합니다:\n• 연령 제한 동영상\n• 혐오감을 주는 동영상\n• 자살 또는 자해와 관련된 동영상 ...\n\n이 설정은 다이얼로그를 자동으로 허용하기만 하며 연령 제한(성인인증 절차)을 우회할 수 없습니다." + "다음 동영상을 시청하기 전에 표시되는 시청 경고 다이얼로그를 제거합니다:\n• 연령 제한 동영상\n• 혐오감을 주는 동영상\n• 자살 또는 자해와 관련된 동영상, etc.\n\n이 설정은 다이얼로그를 자동으로 허용하기만 하며 연령 제한(성인인증 절차)을 우회할 수 없습니다." 라이브 링 누르기 동작 변경하기 "라이브 링이 표시된 채널 아이콘을 누르면 채널 프로필으로 연결됩니다. @@ -433,6 +521,23 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." • Shorts가 일반 플레이어에서 재생됩니다. • 피드가 주제와 채널별로 구성됩니다. • '스트리밍 데이터 변경하기'가 비활성화되어있으면 동영상 설명을 열 수 없습니다." + 레이아웃 업데이트 비활성화하기 + 레이아웃이 서버에 의해 업데이트되지 않습니다. + 레이아웃이 서버에 의해 업데이트됩니다. + "앱 레이아웃이 처음 설치할 때 사용되는 레이아웃으로 되돌아갑니다. + +일부 서버 측 레이아웃은 되돌려지지 않을 수 있습니다. + +변경 사항 +• 플레이어 메뉴 구성 요소(또는 관련 설정)가 작동되지 않을 수 있습니다. +• 롤링 넘버 애니메이션이 적용되지 않습니다. +• 보관함 탭이 사용됩니다. +• 동영상 설명에서 음악 섹션이 작동되지 않을 수 있습니다. +• 보관함 탭에서 계정 전환 버튼이 표시되지 않을 수 있습니다. '내 페이지에서 넓은 검색창 활성화하기' 설정을 사용하세요." + 반투명 상태바 비활성화하기 + 상태바가 불투명합니다. + 상태바가 불투명하거나 반투명합니다. + Android 12+로 실행되는 일부 제조사 ROM의 경우에는 이 기능을 활성화하면 시스템 네비게이션 바가 투명해질 수 있습니다. 앱 버전 변경하기 앱 버전을 변경합니다. 앱 버전을 변경하지 않습니다. @@ -446,8 +551,10 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 18.33.40 - 이전 Shorts 액션바로 복원합니다. 18.38.45 - 이전 기본 동영상 화질 적용 방식으로 복원합니다. 18.48.39 - \'조회수\' & \'좋아요\'의 실시간 업데이트를 비활성화합니다. + 19.01.34 - 동영상 설명 상호작용을 비활성화합니다. 19.26.42 - 하단바와 툴바에서 Cairo 아이콘을 비활성화합니다. 19.33.37 - 이전 동영상 재생 속도 구성요소 패널을 복원합니다. + 변경할 앱 버전이 잘못되었습니다: %s 계정 메뉴 계정 메뉴 및 내 페이지에서 구성요소를 숨기거나 표시할 수 있습니다. @@ -481,8 +588,11 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 기본 재생목록 오프라인 저장 버튼이 항상 표시되어 있으며, 공개 재생목록에서는 그 버튼으로 외부 다운로더를 실행할 수 있습니다. 기본 재생목록 오프라인 저장 버튼이 표시되어 있으면, 그 버튼으로 기본 다운로더를 실행할 수 있습니다. (YouTube Premium 기능) 동영상 오프라인 저장 버튼 재정의하기 - 동영상 오프라인 저장 버튼으로 외부 다운로더를 실행할 수 있습니다. - 동영상 오프라인 저장 버튼으로 기본 다운로더를 실행할 수 있습니다. (YouTube Premium 기능) + 기본 동영상 오프라인 저장 버튼으로 외부 다운로더를 실행할 수 있습니다. + 기본 동영상 오프라인 저장 버튼으로 기본 다운로더를 실행할 수 있습니다. (YouTube Premium 기능) + 현재 재생목록 관리자 + 기본 동영상 오프라인 저장 버튼으로 현재 재생목록 관리자를 실행할 수 있습니다. + 기본 동영상 오프라인 저장 버튼으로 외부 다운로더를 실행할 수 있습니다. 재생목록 외부 다운로더 앱 패키지명 YTDLnis와 같은 설치된 외부 다운로더 앱 패키지명을 설정하세요. @@ -536,7 +646,6 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 반투명 하단바 활성화하기 하단바가 반투명합니다. 하단바가 불투명합니다. - 특정 YouTube 앱 버전에서는 이 설정으로 인하여 시스템 네비게이션 바가 투명해지거나 PIP 모드에서 레이아웃이 깨질 수 있습니다. 하단바 숨기기 하단바가 숨겨집니다. 하단바가 표시됩니다. @@ -689,8 +798,8 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 설정 → 자동재생 / 재생 → 다음 동영상 자동재생" 이 설정을 활성화하면 자동재생이 켜져 있는 동안에 음악 동영상을 재생하면 YouTube 믹스 재생목록으로 자동전환되지 않습니다. 플레이어 팝업 패널 비활성화하기 - 자동 플레이어 팝업 패널을 비활성화합니다.\n• 재생목록, 실시간 채팅, 제품 패널 ... - 자동 플레이어 팝업 패널을 활성화합니다.\n• 재생목록, 실시간 채팅, 제품 패널 ... + 자동 플레이어 팝업 패널을 비활성화합니다.\n• 재생목록, 실시간 채팅, 제품 패널, etc. + 자동 플레이어 팝업 패널을 활성화합니다.\n• 재생목록, 실시간 채팅, 제품 패널, etc. 동영상 재생 속도 오버레이 비활성화하기 "화면을 길게 눌러서 '2배속 >>'을 비활성화합니다. @@ -750,8 +859,8 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 줌 오버레이가 숨겨집니다. 줌 오버레이가 표시됩니다. 동영상 제목 하단 정리하기 - "제목 하단에서 다음 문구들이 숨겨집니다:\n#(해시태그), 모금 행사, 쇼핑, n개 제품, 더빙,\n인기 급상승 동영상 #순위, 회원 전용 ..." - "제목 하단에서 다음 문구들이 표시됩니다:\n#(해시태그), 모금 행사, 쇼핑, n개 제품, 더빙,\n인기 급상승 동영상 #순위, 회원 전용 ..." + "제목 하단에서 다음 문구들이 숨겨집니다:\n#(해시태그), 모금 행사, 쇼핑, n개 제품, 더빙,\n인기 급상승 동영상 #순위, 회원 전용, etc." + "제목 하단에서 다음 문구들이 표시됩니다:\n#(해시태그), 모금 행사, 쇼핑, n개 제품, 더빙,\n인기 급상승 동영상 #순위, 회원 전용, etc." 액션 버튼 플레이어 하단에 있는 액션 버튼을 숨기거나 표시할 수 있습니다. @@ -1117,6 +1226,8 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 버튼을 눌러서 재생 중인 동영상을 음소거할 수 있습니다.\n다시 누르면 음소거가 해제됩니다. 외부 다운로더 버튼 표시하기 버튼을 눌러서 외부 다운로더를 실행할 수 있습니다. + 현재 재생목록 관리자 + 외부 다운로더가 아닌 현재 재생목록 관리자를 실행할 수 있습니다. 동영상 재생 속도 다이얼로그 버튼 표시하기 "버튼을 눌러서 동영상 재생 속도 다이얼로그를 열 수 있습니다. 길게 누르면 동영상 재생 속도 값이 1.0배속으로 설정되고, 다시 길게 누르면 기본 동영상 재생 속도 값으로 설정됩니다." @@ -1255,7 +1366,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." • 길게 눌러서 텍스트 선택하기" 동영상 설명 펼치기 동영상 설명이 자동으로 펼쳐집니다. - 동영상 설명이 수동으로 펼쳐집니다. + 동영상 설명이 자동으로 펼쳐지지 않습니다. Shorts Shorts 백그라운드 재생 비활성화하기 @@ -1277,7 +1388,7 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." • 아티스트 또는 크리에이터의 최신 동영상 • 이전에 시청한 동영상 • 관련 검색어의 검색결과 -• 새로운 맞춤 채널 ..." +• 새로운 맞춤 채널, etc." 채널에서 Shorts 선반 숨기기 "채널에서 Shorts 선반이 숨겨집니다. @@ -1421,7 +1532,6 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." "메뉴 구성요소에서 사용자 정의 동작을 활성화합니다. 알려진 문제점: -• 앱 버전이 18.49.37 이하로 변경된 경우에는 작동되지 않습니다. • 실시간 스트림에서는 작동되지 않습니다." 메뉴 구성요소에서 사용자 정의 동작을 비활성화합니다. 툴바에서 사용자 정의 동작 활성화하기 @@ -1446,6 +1556,10 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 일반 플레이어에서 재생 메뉴 표시하기 일반 플레이어에서 재생 메뉴가 표시됩니다. 일반 플레이어에서 재생 메뉴가 숨겨집니다. + 재생 속도 + 재생 속도 메뉴 표시하기 + 재생 속도 메뉴가 표시됩니다. + 재생 속도 메뉴가 숨겨집니다. 반복 상태 변경 반복 상태 변경 메뉴 표시하기 반복 상태 변경 메뉴가 표시됩니다. @@ -1489,10 +1603,10 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 전체 화면에서 스와이프하여 밝기가 0이 되면 자동 밝기를 활성화합니다. 전체 화면에서 스와이프하여 밝기가 0이 되더라도 자동 밝기를 활성화하지 않습니다. 스와이프 제스처로 밝기 조절 활성화하기 - 전체 화면 왼쪽에서 위로/아래로 스와이프하여 밝기 조절합니다. + "전체 화면 왼쪽에서 위로/아래로 스와이프하여 밝기 조절합니다." 전체 화면 왼쪽에서 위로/아래로 스와이프하여 밝기 조절하지 않습니다. 스와이프 제스처로 볼륨 조절 활성화하기 - 전체 화면 오른쪽에서 위로/아래로 스와이프하여 볼륨 조절합니다. + "전체 화면 오른쪽에서 위로/아래로 스와이프하여 볼륨 조절합니다." 전체 화면 오른쪽에서 위로/아래로 스와이프하여 볼륨 조절하지 않습니다. 화면 밝기 값 저장 및 복원 활성화하기 전체 화면에서 나가거나 들어갈 때마다 화면 밝기 값을 저장 및 복원합니다. @@ -1506,10 +1620,20 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 잠금 화면에서 스와이프 제스처 활성화하기 잠금 화면에서 스와이프 제스처를 활성화합니다. 잠금 화면에서 스와이프 제스처를 비활성화합니다. - 스와이프 오버레이 배경 투명도 - 스와이프 오버레이 배경 투명도 값을 지정할 수 있습니다. 스와이프 한계치 제스처 인식을 위해 얼마나 스와이프를 해야 할지를 지정할 수 있습니다. + 스와이프 오버레이 대체 UI + 대체 UI를 사용합니다. + 기존 UI를 사용합니다. + 최소화된 스타일 활성화하기 + 최소화된 오버레이 스타일을 활성화합니다. + 최소화된 오버레이 스타일을 비활성화합니다. + 원형 오버레이 표시하기 + 원형 오버레이를 표시합니다. + 바형 오버레이를 표시합니다. + 스와이프 오버레이 배경 불투명도 + 스와이프 불투명도 값은 0-100 사이여야 합니다. + 스와이프 불투명도 값은 0-100 사이여야 합니다. 스와이프 오버레이 텍스트 크기 스와이프 오버레이 텍스트 크기를 지정할 수 있습니다. 스와이프 오버레이 화면 크기 @@ -1541,12 +1665,41 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 자동 동영상 - 기본 동영상 재생 속도 - 모바일 네트워크 이용 시 기본 동영상 화질 - Wi-Fi 이용 시 기본 동영상 화질 + + 코덱 HDR 동영상 비활성화하기 HDR 동영상을 비활성화합니다. HDR 동영상을 활성화합니다. + VP9 코덱 비활성화하기 + "VP9 코덱을 비활성화합니다. +• 재생 문제가 없는 계정이거나 VR 또는 iOS 클라이언트는 AV1 코덱까지 지원되기 때문에 2160p까지 재생될 수 있고, 나머지 클라이언트는 VP9 코덱까지만 지원되기 때문에 1080p까지 재생될 수 있습니다. +• AVC 코덱 동영상을 재생했을 경우에는 VP9보다 더 많은 데이터가 사용되오니 주의하세요. +• HDR 동영상을 재생하기 위해 HDR 동영상에서는 VP9 또는 AV1 코덱이 사용됩니다." + VP9 코덱을 활성화합니다.\n• 예전에 업로드된 동영상에서 일부 화질 값들이 제거되어 360p와 1080p(Premium 기능)만 선택할 수 있거나 화질 메뉴를 선택하지 못할 수 있습니다. + AV1 코덱 변경하기 + AV1 코덱을 VP9 코덱으로 변경합니다. + + 재생 속도 + 기본 동영상 재생 속도 + 동영상 재생 속도 저장 활성화하기 + 동영상 재생 속도 값을 변경할 때마다 기본 동영상 재생 속도로 저장합니다. + 동영상 재생 속도 값을 변경할 때마다 기본 동영상 재생 속도로 저장하지 않습니다. + 팝업 메시지 표시하기 + 기본 동영상 재생 속도 값을 변경하였을 때, 팝업 메시지를 표시합니다. + 기본 동영상 재생 속도 값을 변경하였을 때, 팝업 메시지를 표시하지 않습니다. + 기본 Shorts 재생 속도 + Shorts 재생 속도 저장 활성화하기 + "Shorts 재생 속도 값을 변경할 때마다 기본 Shorts 재생 속도로 저장합니다. + +알림: +• Shorts 플레이어에서 재생 속도를 변경하는 유일한 방법은 '사용자 정의 동작'에서 '재생 속도' 메뉴를 사용하는 것입니다. +• ‘Shorts components’ 패치를 포함되지 않은 앱일 경우에는 이 기능을 사용할 수 없습니다." + Shorts 재생 속도 값을 변경할 때마다 기본 Shorts 재생 속도로 저장하지 않습니다. + 팝업 메시지 표시하기 + 기본 Shorts 재생 속도를 변경하였을 때, 팝업 메시지를 표시합니다. + 기본 Shorts 재생 속도를 변경하였을 때, 팝업 메시지를 표시하지 않습니다. + 기본 동영상 재생 속도 값을 %s 로 변경하였습니다. + 기본 Shorts 재생 속도 값을 %s 로 변경하였습니다. 사용자 정의 동영상 재생 속도 활성화하기 사용자 정의 동영상 재생 속도를 활성화합니다. 사용자 정의 동영상 재생 속도를 비활성화합니다. @@ -1555,61 +1708,58 @@ DeArrow에 대해 자세히 알아보려면 여기를 누르세요." 이전 메뉴 구성요소를 활성화합니다. 사용자 정의 동영상 재생 속도 편집하기 사용하고 싶은 동영상 재생 속도 값을 추가 또는 변경할 수 있습니다. - 동영상 재생 속도 저장 활성화하기 - 동영상 재생 속도 값을 변경할 때마다 기본 동영상 재생 속도로 저장합니다. - 동영상 재생 속도 값을 변경할 때마다 기본 동영상 재생 속도로 저장하지 않습니다. - 팝업 메시지 표시하기 - 기본 동영상 재생 속도 값을 변경하였을 때, 팝업 메시지를 표시합니다. - 기본 동영상 재생 속도 값을 변경하였을 때, 팝업 메시지를 표시하지 않습니다. + 사용자 정의 재생 속도는 %s 배속보다 작아야 합니다. + 잘못된 재생 속도 값입니다. + + 동영상 화질 + 모바일 네트워크 이용 시 기본 동영상 화질 + Wi-Fi 이용 시 기본 동영상 화질 동영상 화질 저장 활성화하기 동영상 화질 값을 변경할 때마다 기본 동영상 화질으로 저장합니다. 동영상 화질 값을 변경할 때마다 기본 동영상 화질으로 저장하지 않습니다. 팝업 메시지 표시하기 기본 동영상 화질 값을 변경하였을 때, 팝업 메시지를 표시합니다. 기본 동영상 화질 값을 변경하였을 때, 팝업 메시지를 표시하지 않습니다. + 모바일 네트워크 이용 시 기본 Shorts 화질 + Wi-Fi 이용 시 기본 Shorts 화질 + Shorts 화질 저장 활성화하기 + Shorts 화질 값을 변경할 때마다 기본 Shorts 화질으로 저장합니다. + Shorts 화질 값을 변경할 때마다 기본 Shorts 화질으로 저장하지 않습니다. + 팝업 메시지 표시하기 + 기본 Shorts 화질을 변경하였을 때, 팝업 메시지를 표시합니다. + 기본 Shorts 화질을 변경하였을 때, 팝업 메시지를 표시하지 않습니다. + 모바일 네트워크 + Wi-Fi + %1$s 이용 시 기본 동영상 화질 값을 %2$s 로 변경하였습니다. + %1$s 이용 시 기본 Shorts 화질 값을 %2$s 로 변경하였습니다. 이전 동영상 화질 설정 메뉴 활성화하기 이전 동영상 화질 설정 메뉴를 활성화합니다. 이전 동영상 화질 설정을 비활성화합니다. + 미리 로드된 버퍼를 건너뛰었습니다. + 미리 로드된 버퍼 건너뛰기 + "동영상 시작 시 미리 로드된 버퍼를 건너뛰고 기본 동영상 화질을 즉시 적용합니다. + +알림: +• 동영상이 시작되면 약 0.3초의 지연이 발생합니다. +• Shorts 동영상, HDR 동영상, 실시간 스트리밍 동영상 또는 15초 미만의 동영상에는 적용되지 않습니다. +• ‘스트리밍 데이터 변경하기’ 기능은 미리 로드된 버퍼도 제거하므로 사용하는 경우에는 이 기능이 필요하지 않습니다." + 이 설정을 활성화하면 동영상 재생 문제가 발생할 수 있습니다. + 건너뛸 때, 팝업 메시지 표시하기 + 팝업 메시지를 표시합니다. + 팝업 메시지를 표시하지 않습니다. + 기기 크기 정보 변경하기 + "기기 크기 정보를 최대 값으로 변경합니다. + +알림: +• 높은 기기 크기 정보가 필요한 동영상에서는 고화질 동영상 값이 해제될 수 있지만 모든 동영상에는 적용되지 않습니다. +• '스트리밍 데이터 변경하기'가 활성화되어 있으면, 이 설정을 사용할 수 없습니다." + 음악에서 기본 동영상 재생 속도 비활성화하기 음악 동영상에서 기본 동영상 재생 속도를 비활성화합니다. 음악 동영상에서 기본 동영상 재생 속도를 활성화합니다. 카테고리를 사용하여 유효성 검사하기 동영상 카테고리가 음악인 경우에는 기본 동영상 재생 속도를 비활성화합니다. YouTube Music에서 재생할 수 있는 동영상인 경우에는 기본 동영상 재생 속도를 비활성화합니다. - Shorts에서 기본 동영상 재생 속도 활성화하기 - Shorts에서 기본 동영상 재생 속도를 활성화합니다. - Shorts에서 기본 동영상 재생 속도를 비활성화합니다. - 미리 로드된 버퍼를 건너뛰었습니다. - 미리 로드된 버퍼 건너뛰기 - "동영상을 시작할 때, 미리 로드된 버퍼를 건너뛰어 기본 동영상 화질 적용 지연을 우회합니다. -• 동영상이 시작되면 약 0.3초정도의 지연이 발생하지만 기본 동영상 화질이 즉시 적용됩니다. -• HDR 동영상, 실시간 스트림, 15초 미만의 동영상에는 적용되지 않습니다." - 이 설정을 활성화하면 동영상 재생 문제가 발생할 수 있습니다. - 건너뛸 때, 팝업 메시지 표시하기 - 팝업 메시지를 표시합니다. - 팝업 메시지를 표시하지 않습니다. - 기기 크기 정보 변경하기 - "기기 크기 정보를 최대값으로 변경합니다. -• 높은 기기 크기 정보가 필요한 일부 동영상에서는 고화질 동영상 값이 잠금 해제될 수 있지만 모든 동영상에는 적용되지 않습니다. -• 일부 스마트폰에서 이 설정을 활성화하면, 동영상 재생이 끊기거나 배터리 수명이 단축될 수 있으며, 알려지지 않은 문제점도 발생할 수 있습니다." - VP9 코덱 비활성화하기 - "VP9 코덱을 비활성화합니다. -• 재생 문제가 없는 계정이거나 iOS 클라이언트는 AV1 코덱까지 지원되기 때문에 2160p까지 재생될 수 있고, 나머지 클라이언트는 VP9 코덱까지만 지원되기 때문에 1080p까지 재생될 수 있습니다. -• AVC 코덱 동영상을 재생했을 경우에는 VP9보다 더 많은 데이터가 사용되오니 주의하세요. -• HDR 동영상을 재생하기 위해 HDR 동영상에서는 VP9 또는 AV1 코덱이 사용됩니다." - VP9 코덱을 활성화합니다.\n• 예전에 업로드된 동영상에서 일부 화질 값들이 제거되어 360p와 1080p(Premium 기능)만 선택할 수 있거나 화질 메뉴를 선택할 수 없을 수 있습니다. - AV1 코덱 변경하기 - AV1 코덱을 VP9 코덱으로 변경합니다. - AV1 코덱 응답 거부하기 - "AV1 코덱 응답을 강제로 거부합니다. -• 약 20초정도의 버퍼링 후에 다른 코덱으로 전환됩니다." - Fallback 프로세스로 인하여 약 20초정도의 버퍼링이 발생합니다. - 기본 동영상 재생 속도 값을 %s 로 변경합니다. - 모바일 네트워크 이용 시 기본 동영상 화질 값을 %s 로 변경합니다. - 동영상 화질을 설정할 수 없습니다. - Wi-Fi 이용 시 기본 동영상 화질 값을 %s 로 변경합니다. - 사용자 정의 재생 속도는 %s 배속보다 작아야 합니다. - 잘못된 재생 속도 값입니다. Return YouTube Dislike Return YouTube Dislike 활성화하기 @@ -1740,6 +1890,7 @@ API Key를 발급받는 방법을 보려면 여기를 누르세요." 건너뛰기 버튼 표시하기 재생바에만 표시하기 사용 안 함 + 불투명도: 색상: 설정한 색상을 적용하였습니다. 색상을 초기화하였습니다. @@ -1806,6 +1957,8 @@ API Key를 발급받는 방법을 보려면 여기를 누르세요." 싫어요 카테고리 변경 투표할 구간이 없습니다. + + %1$s ~ %2$s 구간 카테고리를 선택하세요. 이 카테고리는 비활성화되어 있습니다. 제출하려면 설정에서 활성화해야 합니다. 새로운 SponsorBlock 구간 @@ -1925,24 +2078,35 @@ GmsCore 앱 배터리 최적화를 비활성화(제한 없음)하더라도, 배 Android VR "Android VR (인증 없음)" - "iOS -(PoToken이 요구됨)" + "iOS +(폐기 예정)" "iOS TV (로그인이 요구됨)" 알려진 문제점 "• 오디오 트랙 메뉴가 표시되지 않습니다. • 안정적인 볼륨을 사용할 수 없습니다. • 자동 오디오 트랙을 비활성화할 수 없습니다. -• 사용자가 로그인을 하지 않았거나 시크릿 모드에서는 Kids 동영상이 재생되지 않을 수 있습니다. -• VR은 Kids // TV는 재생목록과 음악 동영상에서 다른 클라이언트가 사용될 수 있습니다. +• 로그인을 하지 않았거나 시크릿 모드에서는 Kids 동영상이 재생되지 않을 수 있습니다. (로그인 정보가 필요함) +• VR은 Kids // TV는 재생목록, 음악 동영상 또는 로그인 정보가 없을 경우에는 다른 클라이언트가 사용될 수 있습니다. • TV는 AV1 코덱이 지원되지 않습니다. -• 인증 없음: 사용자 로그인 정보를 사용하지 않음" - • 동영상 재생 문제가 발생할 수 있습니다 (PoToken이 요구됨).\n• 영화, 유료, 비공개 그리고 연령 제한 동영상에서 다른 클라이언트가 사용될 수 있습니다. +• 인증 없음: 로그인 정보를 사용하지 않음" + • 서버에서 전체 ASNs/IP 범위가 차단될 수 있습니다. "• 안정적인 볼륨을 사용할 수 없습니다. • 영화 또는 유료 동영상이 재생되지 않을 수 있습니다. -• 사용자가 로그인을 하지 않았거나 시크릿 모드에서는 Kids 동영상이 재생되지 않을 수 있습니다. -• 음악 동영상에서 다른 클라이언트가 사용될 수 있습니다. +• 로그인을 하지 않았거나 시크릿 모드에서는 Kids 동영상이 재생되지 않을 수 있습니다. (로그인 정보가 필요함) +• 음악 동영상 또는 로그인 정보가 없을 경우에는 다른 클라이언트가 사용될 수 있습니다. • AV1 코덱이 지원되지 않습니다." + iOS 클라이언트 사용하기 + "사용 가능한 클라이언트에 iOS 클라이언트가 추가되었습니다. + +경고: +• iOS 클라이언트는 폐기될 예정입니다. 사용 중 발생하는 모든 문제는 사용자의 책임입니다." + 사용 가능한 클라이언트에 iOS 클라이언트가 추가되지 않습니다. + "iOS를 사용하여 YouTube API 엔드포인트를 요청할 때는 iOS 기기에서 발급된 Auth Token과 iOSGuard에 의해 발급된 PoTokens가 필요합니다. + +즉, iOS를 통한 스트리밍 요청에는 Auth Token과 PoTokens가 모두 누락되며, 서버는 사용자를 봇으로 간주하여 전체 ASN/IP 범위를 차단할 수 있습니다. + +당신의 위험을 감수하고 사용하세요!" iOS AVC (H.264) 강제로 활성화하기 동영상 코덱을 AVC (H.264)로 강제로 활성화합니다.\n\n• 일부 VP9 코덱 동영상에서 제거되었던 화질 값이 표시될 수 있습니다.\n• 최대 화질 값이 1080p이므로, 초고화질 동영상을 재생할 수 없습니다.\n• HDR 동영상을 재생할 수 없습니다. 동영상 코덱을 자동으로 결정합니다.\n\n• 예전에 업로드된 동영상을 재생했는데 VP9 코덱 응답을 받았을 경우, 일부 화질값이 제거되어 360p와 1080p(Premium 기능)만 선택가능할 수 있거나 화질 메뉴를 선택불가능할 수 있습니다. @@ -1960,7 +2124,9 @@ AVC의 최대 화질 값은 1080p이고, OPUS 코덱을 사용불가 및 HDR 동 전문 통계에서 표시하기 스트리밍 데이터를 가져오는 데 사용되는 클라이언트가 전문 통계에서 표시됩니다.\n\n• 만약 선택한 기본 클라이언트가 재생할 수 없는 동영상을 재생하려하거나 클라이언트 재생 문제가 발생하면, 그 문제를 해결하기 위해 다른 클라이언트가 사용되는 경우도 있기 때문에 전문 통계에서 다르게 표시될 수 있습니다. 스트리밍 데이터를 가져오는 데 사용되는 클라이언트가 전문 통계에서 숨겨집니다.\n\n• 만약 선택한 기본 클라이언트가 재생할 수 없는 동영상을 재생하려하거나 클라이언트 재생 문제가 발생하면, 그 문제를 해결하기 위해 다른 클라이언트가 사용되는 경우도 있기 때문에 전문 통계에서 다르게 표시될 수 있습니다. - Android VR 기본 오디오 스트림 언어 + VR 기본 오디오 스트림 언어 + 클라이언트 스트림을 가져올 수 없습니다. + 로그인하지 않았을 수 있습니다. PoToken & VisitorData PoToken 등록하기 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 2a409d692..43310e109 100644 --- a/patches/src/main/resources/youtube/translations/pl-rPL/strings.xml +++ b/patches/src/main/resources/youtube/translations/pl-rPL/strings.xml @@ -19,59 +19,150 @@ "%1$s nie jest zainstalowany. Pobierz %2$s ze strony internetowej." %s nie jest zainstalowany. Proszę go zainstalować. + Dodaj do kolejki + Dodaj do kolejki i otwórz kolejkę + Dodaj do kolejki i odtwórz film + Zewnętrzna aplikacja od pobierania + Otwórz kolejkę + Kolejka + Usuń z kolejki + Usuń z kolejki i otwórz kolejkę + Usuń kolejkę + Zapisz kolejkę + "Zamiast otwierać zewnętrzną aplikację od pobierania, otwiera okno menedżera kolejki. + +Możesz również otworzyć menedżer kolejek poprzez kliknięcie i przytrzymanie przycisku cofania na pasku nawigacyjnym. + +Funkcja jest nadal rozwijana, więc większość funkcji może nie działać. + +Proszę używać tej funkcji wyłącznie w celach debugowania." + Wymagane logowanie + Menedżer kolejki jest niedostępny (%s). + Nie można zidentyfikować playlisty + Kolejka jest pusta + Nie można zidentyfikować filmu + Nie udało się dodać filmu. + Nie udało się utworzyć kolejki. + Nie udało się usunąć kolejki. + Nie udało się usunąć filmu. + Nie udało się zapisać kolejki. + Pomyślnie dodano film. + Pomyślnie utworzono kolejkę. + Pomyślnie usunięto kolejkę. + Pomyślnie usunięto film. + Pomyślnie zapisano kolejkę do \'%s\'. Język RVX Język aplikacji - Arabski - Azerbejdżański - Bułgarski - Bengalski - Kataloński - Czeski - Duński - Niemiecki - Grecki - Angielski - Hiszpański - Estoński - Perski - Fiński - Francuski - Gudżaracki - Hindi - Chorwacki - Węgierski - Indonezyjski - Włoski - Japoński - Kazachski - Koreański - Litewski - Łotewski - Macedoński - Mongolski - Maratyjski - Malajski - Birmański - Niderlandzki - Orija - Pendżabski - Polski - Portugalski - Rumuński - Rosyjski - Słowacki - Słoweński - Serbski - Szwedzki - Suahili - Tamilski - Telugu - Tajski - Turecki - Ukraiński - Urdu - Wietnamski - Chiński + "Amharski +አማርኛ" + "Arabski +العربية" + "Azerbejdżański +Azərbaycan" + "Białoruski +беларуская" + "Bułgarski +Български" + "Bengalski +বাংলা" + "Kataloński +Català" + "Czeski +Čeština" + "Duński +Dansk" + "Niemiecki +Deutsch" + "Grecki +Ελληνικά" + "Angielski +English" + "Hiszpański +Español" + "Estoński +Eesti" + "Perski +فارسی" + "Fiński +Suomi" + "Francuski +Français" + "Gudżarati +ગુજરાતી" + "Hebrajski +עברי" + "Hindi +हिन्दी" + "Chorwacki +Hrvatski" + "Węgierski +Magyar" + "Indonezyjski +Indonesia" + "Włoski +Italiano" + "Japoński +日本語" + "Kazachski +Қазақ тілі" + "Koreański +한국어" + "Litewski +Lietuvių" + "Łotewski +Latviešu" + "Macedoński +Македонски" + "Mongolski +Монгол" + "Marathi +मराठी" + "Malajski +Melayu" + "Birmański +ဗမာ" + "Niderlandzki +Nederlands" + "Orija +ଓଡ଼ିଆ" + "Pendżabski +ਪੰਜਾਬੀ" + "Polski +Polski" + "Portugalski +Português" + "Rumuński +Română" + "Rosyjski +Русский" + "Słowacki +Slovenčina" + "Albański +Shqip" + "Słoweński +Slovenščina" + "Serbski +Српски" + "Szwedzki +Svenska" + "Suahili +Kiswahili" + "Tamilski +தமிழ்" + "Telugu +తెలుగు" + "Tajski +ไทย" + "Turecki +Türkçe" + "Ukraiński +Українська" + "Urdu +اردو" + "Wietnamski +Tiếng Việt" + "Chiński +中文" Reklamy Baner sklepowy po zakończeniu filmu @@ -394,9 +485,6 @@ Ograniczenie: Przycisk wstecz na pasku narzędzi może nie działać." Animacja uruchamiania aplikacji Wyłączona Włączona - Półprzezroczystość paska statusu - Wyłączona - Włączona Kolorowy ekran ładowania Włączony Wyłączony @@ -430,6 +518,23 @@ Układ samochodowy: • Shortsy otwierają się w zwykłym odtwarzaczu • Strona główna jest sortowana po tematach i kanałach • Opis filmu nie może być otwarty po wyłączeniu 'Oszukuj strumień danych'" + Aktualizacje układu aplikacji przez serwer + Wyłączone + Włączone + "Układ aplikacji wraca do układu używanego przy pierwszej instalacji. + +Większość powiązanych serwerowo układów aplikacji może się nie przywrócić. + +Zmiany zawierają: +• Komponenty menu ustawień filmu (i powiązanych ustawień) mogą nie działać +• Animacje liczb nie działają +• Zakładka biblioteki jest używana +• Sekcja muzyki w opisie filmu może nie działać +• Przycisk od przełączania konta może się nie pojawiać w zakładce biblioteki. Użyj ustawienia 'Szeroki pasek wyszukiwania w zakładce Ty'" + Półprzezroczystość paska statusu + Wyłączona + Włączona + Dla większości producentów ROM-ów bazujących na Androidzie 12 i wzwyż, włączenie tej opcji spowoduje stanie się systemowego paska nawigacji przezroczystym. Oszukiwanie wersji aplikacji Włączone Wyłączone @@ -447,8 +552,10 @@ Jeśli później zostanie to wyłączone, rekomendowane jest usunięcie danych a 18.33.40 - Przywraca stary pasek akcji Shortsów 18.38.45 - Przywraca stare zachowanie domyślnej jakości filmu 18.48.39 - Wyłącza aktualizowanie wyświetleń i łapek w górę w czasie rzeczywistym + 19.01.34 - Wyłącza interakcje z opisami filmów 19.26.42 - Wyłącza ikony Cairo w pasku nawigacji oraz narzędzi 19.33.37 - Przywraca stare menu szybkości odtwarzania + Nieprawidłowa oszukiwana wersja aplikacji: %s. Menu konta Ukryj bądź pokaż elementy w menu konta i zakładki Ty @@ -484,6 +591,9 @@ Niektóre komponenty mogą nie być ukryte." Metoda pobierania filmów Zewnętrzna aplikacja Natywne pobieranie + Menedżer kolejki + Natywny przycisk od pobierania otworzy menedżer kolejki + Natywny przycisk od pobierania otworzy zewnętrzną aplikację od pobierania Nazwa pakietu aplikacji od pobierania (playlisty) Nazwa pakietu zainstalowanej zewnętrznej aplikacji od pobierania, takiej jak YTDLnis. @@ -538,7 +648,6 @@ 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 @@ -637,7 +746,7 @@ Jeśli zajdą zmiany po stronie serwera, kolor tła tych komunikatów może się Szeroki pasek wyszukiwania z nagłówkiem YouTube Włączony Wyłączony - Szeroki pasek wyszukiwania na stronie Ty + Szeroki pasek wyszukiwania w zakładce Ty "Włączony By dostać się do ustawień, użyj następującej ścieżki: @@ -1118,6 +1227,8 @@ Stuknij i przytrzymaj, by skopiować czas filmu." Stuknij, by wyciszyć bieżący film. Stuknij ponownie, by odciszyć film. Przycisk do pobierania Stuknij, by otworzyć aplikacje od pobierania. + Menedżer kolejki + Zamiast otwierać zewnętrzną aplikację od pobierania, otwiera menedżera kolejki. Przycisk od prędkości "Stuknij, by zmienić prędkość odtwarzania. Stuknij i przytrzymaj, by zmienić prędkość odtwarzania na 1.0x. Stuknij i przytrzymaj ponownie, by zresetować do domyślnej prędkości." @@ -1416,8 +1527,7 @@ Informacja: Własne akcje w menu ustawień Shortsa "Włączone -Ograniczenia: -• Nie działają, jeśli wersja aplikacji jest oszukana do 18.49.37, bądź wcześniejszej +Ograniczenie: • Nie działają na transmisjach na żywo" Wyłączone Własne akcje w pasku narzędzi @@ -1442,6 +1552,10 @@ Kliknij i przytrzymaj przycisk 'Więcej', by wyświetlić okno z własnymi akcja Menu od otwierania filmu Widoczne Ukryte + Prędkość odtwarzania + Menu od prędkości odtwarzania + Widoczne + Ukryte Stan powtarzania Shortsów Menu od stanu powtarzania Shortsów Widoczne @@ -1484,10 +1598,14 @@ Ograniczenia: Najniższa wartość jasności aktywowana przesuwaniem włącza automatyczną jasność Najniższa wartość jasności aktywowana przesuwaniem nie włącza automatycznej jasności Zmienianie jasności przesuwaniem - Włączone + "Włączone + +Dostosuj jasność, przesuwając pionowo po lewej stronie ekranu." Wyłączone Zmienianie głośności przesuwaniem - Włączone + "Włączone + +Dostosuj głośność, przesuwając pionowo po prawej stronie ekranu." Wyłączone Zapisuj i przywracaj jasność podczas zamykania lub wchodzenia w tryb pełnoekranowy Tak @@ -1501,10 +1619,20 @@ Ograniczenia: Przesuwanie podczas trybu blokady ekranu Włączone Wyłączone - Widoczność tła przesuwania - Widoczność tła nakładki przesuwania Minimalna długość przesunięcia Minimalna długość przesunięcia + Wygląd nakładki przesuwania + Alternatywny + Starszy + Minimalistyczny styl nakładki przesuwania + Włączony + Wyłączony + Okrągła nakładka + Włączona + Wyłączona + Przezroczystość tła nakładki przesuwania + Wartość musi być pomiędzy 0 a 100 + Wartość musi być pomiędzy 0 a 100. Rozmiar tekstu nakładki przesuwania Rozmiar tekstu nakładki przesuwania Rozmiar obszaru przesuwania @@ -1536,58 +1664,11 @@ Ograniczenia: Automatyczna Filmy - Domyślna prędkość odtwarzania - Domyślna jakość filmu podczas używania sieci mobilnej - Domyślna jakość filmu podczas używania Wi-Fi + + Kodek Filmy z HDR Wyłączone Włączone - Niestandardowa prędkość odtwarzania - Włączona - Wyłączona - Wygląd menu od prędkości odtwarzania - Własny - Stary - Edytuj niestandardowe prędkości odtwarzania - Dodaj lub zmień dostępne prędkości odtwarzania - Zapamiętuj zmiany prędkości odtwarzania - Zmiany prędkości odtwarzania dotyczą wszystkich filmów - Zmiany prędkości odtwarzania dotyczą tylko bieżącego filmu - Komunikaty o zmianie domyślnej prędkości odtwarzania - Włączone - Wyłączone - Zapamiętuj zmiany jakości filmu - Zmiany jakości dotyczą wszystkich filmów - Zmiany jakości dotyczą tylko bieżącego filmu - Komunikaty o zmianie domyślnej jakości filmów - Włączone - Wyłączone - Stare menu od jakości filmu - Widoczne - Niewidoczne - Domyślna prędkość odtwarzania dla muzyki - Wyłączona - Włączona - Sprawdzaj przy użyciu kategorii - Wyłączone dla filmów z kategorią muzyki - Wyłączone dla filmów odtwarzalnych w YouTube Music - Domyślna prędkość odtwarzania w Shortsach - Włączona - Wyłączona - Pominięto wstępnie załadowany bufor. - Wstępnie załadowany bufor - "Pomija wstępnie załadowany bufor na początku filmu, aby natychmiastowo ustawić domyślną jakość filmu. - -Informacje: -• Po uruchomieniu filmu występuje opóźnienie około 0.3 sekundy -• Nie działa w przypadku filmów z HDR, transmisji na żywo i filmów krótszych niż 15 sekund." - Włączenie tej opcji może spowodować problemy z odtwarzaniem filmów. - Komunikaty o pominięciu - Widoczne - Ukryte - Oszukaj rozdzielczość urządzenia - "Oszukuje rozdzielczość urządzenia do maksymalnej wartości. -Wysoka jakość może być odblokowana na niektórych filmach, które wymagają wysokiej rozdzielczości urządzenia, lecz nie na wszystkich." Wyłącz kodek VP9 "Wyłączone @@ -1597,16 +1678,88 @@ Wysoka jakość może być odblokowana na niektórych filmach, które wymagają Włączone Zmień kodek AV1 programu Zastąp kodek AV1 programu kodekiem VP9 - Odrzucaj kodek AV1 programu - "Odrzucaj kodek AV1 programu. -Po około 20 sekundach ładowania kodek zostanie zmieniony." - Ten sposób powoduje około 20 sekund ładowania. - Zmieniono domyślną prędkość odtwarzania na %s. - Zmieniono domyślną jakość podczas używania sieci mobilnej na %s. - Nie udało się zmienić jakości obrazu. - Zmieniono domyślną jakość podczas używania Wi-Fi na %s. + + Prędkość odtwarzania + Domyślna prędkość odtwarzania + Zapamiętuj zmiany prędkości odtwarzania + Zmiany prędkości odtwarzania dotyczą wszystkich filmów + Zmiany prędkości odtwarzania dotyczą tylko bieżącego filmu + Komunikaty o zmianie domyślnej prędkości odtwarzania + Włączone + Wyłączone + Domyślna prędkość odtwarzania w Shortsach + Zapamiętuj zmiany prędkości odtwarzania w Shortsach + "Dla wszystkich Shortsów + +Informacje: +• jedyną drogą zmiany prędkości odtwarzania w odtwarzaczu Shortsów jest użycie 'Prędkość odtwarzania' w 'Własne akcje' +• jeśli nie zawarłeś łatki związanej z komponentami Shortsów, nie będzie to możliwe" + Dla bieżącego Shortsa + Komunikat o zmianie domyślnej prędkości odtwarzania dla Shortsów + Włączony + Wyłączony + Zmieniono domyślną prędkość odtwarzania na: %s. + Zmieniono domyślną prędkość odtwarzania w Shortsach na: %s. + Niestandardowa prędkość odtwarzania + Włączona + Wyłączona + Wygląd menu od prędkości odtwarzania + Własny + Stary + Edytuj niestandardowe prędkości odtwarzania + Dodaj lub zmień dostępne prędkości odtwarzania Niestandardowe prędkości muszą by mniejsze niż %sx. Nieprawidłowe niestandardowe prędkości odtwarzania. + + Jakość filmu + Domyślna jakość filmu podczas używania sieci mobilnej + Domyślna jakość filmu podczas używania Wi-Fi + Zapamiętuj zmiany jakości filmu + Zmiany jakości dotyczą wszystkich filmów + Zmiany jakości dotyczą tylko bieżącego filmu + Komunikaty o zmianie domyślnej jakości filmów + Włączone + Wyłączone + Domyślna jakość Shortsów podczas używania sieci mobilnej + Domyślna jakość Shortsów podczas używania Wi-Fi + Zapamiętuj zmiany jakości Shortsów + Dla wszystkich Shortsów + Dla bieżącego Shortsa + Komunikat o zmianie domyślnej jakości Shortsa + Włączony + Wyłączony + Sieć mobilna + Wi-Fi + Zmieniono domyślną jakość filmu z %1$s na: %2$s. + Zmieniono domyślną jakość Shortsa z %1$s na: %2$s. + Stare menu od jakości filmu + Widoczne + Niewidoczne + Pominięto wstępnie załadowany bufor. + Wstępnie załadowany bufor + "Pomija wstępnie załadowany bufor na początku filmu, aby natychmiastowo ustawić domyślną jakość filmu. + +Informacje: +• po uruchomieniu filmu występuje opóźnienie około 0.3 sekundy +• nie działa w przypadku filmów z HDR, transmisji na żywo i filmów krótszych niż 15 sekund. +• opcja 'Oszukuj strumień danych' również pomija wstępnie załadowany bufor, więc ta opcja nie jest potrzebna, jeśli używasz tego ustawienia" + Włączenie tej opcji może spowodować problemy z odtwarzaniem filmów. + Komunikaty o pominięciu + Widoczne + Ukryte + Oszukaj rozdzielczość urządzenia + "Oszukuje rozdzielczość urządzenia do maksymalnej wartości. + +Informacje: +• wysoka jakość może być odblokowana na niektórych filmach, które wymagają wysokiej rozdzielczości urządzenia, lecz nie na wszystkich +• ustawienie nie będzie dostępne, jeśli opcja 'Oszukuj strumień danych' jest włączona" + + Domyślna prędkość odtwarzania dla muzyki + Wyłączona + Włączona + Sprawdzaj przy użyciu kategorii + Wyłączone dla filmów z kategorią muzyki + Wyłączone dla filmów odtwarzalnych w YouTube Music Return YouTube Dislike Return YouTube Dislike @@ -1736,6 +1889,7 @@ Kliknij, by zobaczyć, jak zgłosić klucz API." Pokaż przycisk od pomijania Pokazuj w pasku postępu filmu Wyłącz + Przezroczystość: Kolor: Kolor został zmieniony. Kolor został zresetowany. @@ -1802,6 +1956,8 @@ Kliknij, by zobaczyć, jak zgłosić klucz API." Głos przeciw Zmień kategorię Brak segmentów, na które można zagłosować. + + %1$s do %2$s Wybierz kategorię segmentu Ta kategoria jest wyłączona w ustawieniach. Włącz kategorię, aby móc wysłać ten segment. Nowy segment SponsorBlock @@ -1921,8 +2077,8 @@ Stuknij przycisk kontynuacji i zezwól na zmiany w optymalizacji." Android VR "Android VR (Bez autoryzacji)" - "iOS -(Wymagany PoToken)" + "iOS +(Porzucone)" "iOS TV (Wymagane zalogowanie)" Efekty uboczne oszukiwania @@ -1930,10 +2086,22 @@ Stuknij przycisk kontynuacji i zezwól na zmiany w optymalizacji." • Stabilna głośność nie jest dostępna • Automatyczne wymuszenie ścieżki dźwiękowej nie jest dostępne • Filmy dla dzieci mogą się nie odtwarzać będąc zalogowanym lub w trybie incognito" - • Mogą wystąpić problemy z odtwarzaniem (wymagany PoToken) + • Cały zakres ASNów/IP może być zablokowany przez serwer "• Stabilna głośność nie jest dostępna • Filmy kinowe lub płatne mogą się nie odtwarzać • Filmy dla dzieci mogą się nie odtwarzać będąc zalogowanym lub w trybie incognito" + Włącz klienta iOS + "Tak + +OSTRZEŻENIE: Klient iOS jest porzucony. Wszelkie problemy pojawiające się podczas korzystania, są na własne ryzyko." + Nie + "Podczas żądania punktów końcowych interfejsu API YouTube za pomocą systemu iOS potrzebne będą tokeny Auth wydane przez urządzenie z systemem iOS oraz PoTokeny wydane przez iOSGuard. + +Oznacza to, że żądania przesyłania strumieniowego za pośrednictwem systemu iOS będą pozbawione zarówno tokenów Auth, jak i PoTokenów, a serwer może uznać użytkownika za bota i zablokować cały zakres ASN/IP. + +UŻYWAJ NA WŁASNE RYZYKO! + +Przetłumaczono z DeepL.com (wersja darmowa)" Wymuś kodek iOS AVC (H.264) Wymuszony Określany automatycznie @@ -1952,6 +2120,8 @@ AVC obsługuje maksymalnie rozdzielczość 1080p, kodek audio OPUS nie jest dost Widoczna Ukryta Domyślny język dla transmisji na żywo dla VR + Nie można przechwycić żadnego ze strumieni klienta. + Możesz nie być zalogowany. PoToken / VisitorData PoToken do użycia 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 a3668074b..08b66fcf1 100644 --- a/patches/src/main/resources/youtube/translations/pt-rBR/strings.xml +++ b/patches/src/main/resources/youtube/translations/pt-rBR/strings.xml @@ -19,59 +19,48 @@ "%1$s não está instalado. Por favor, baixe %2$s do site." %s não está instalado. Por favor, instale-o. + Adicionar à fila + Adicionar à fila e abrir a fila + Adicionar à fila e reproduzir o vídeo + Download externo + Abrir fila + Fila + Remover da lista + Remover da fila e abrir a fila + Remover fila + Salvar fila + "Em vez de abrir um aplicativo de download externo, abra a caixa de diálogo do gerenciador de filas. + +Você também pode abrir o gerenciador de filas pressionando e segurando o botão Voltar na barra de navegação. + +Este recurso ainda está em andamento, então a maioria dos recursos pode não funcionar. + +Use-o apenas para fins de depuração." + Login obrigatório + Gerenciador de filas indisponível (%s). + Não foi possível identificar a playlist + A fila está vazia + Não foi possível identificar vídeo + Falha ao adicionar vídeo. + Falha ao criar fila. + Falha ao excluir fila. + Falha ao remover o vídeo. + Falha ao salvar fila. + Vídeo adicionado com sucesso. + Fila criada com sucesso. + Fila excluída com sucesso. + Vídeo removido com sucesso. + Fila salva com sucesso em \'%s\'. Idioma do RVX Idioma do app - Árabe - Azerbaijano - Búlgaro - Bengalês - Catalão - Tcheco - Dinamarquês - Alemão - Grego - Inglês - Espanhol - Estoniano - Persa - Finlandês - Francês - Guzerate - Hindu - Croata - Húngaro - Indonésio - Italiano - Japonês - Cazaque - Coreano - Lituano - Letão - Macedônio - Mongol - Marata - Malaio - Birmanês - Holandês - Odia - Punjabi - Polonês - Português - Romeno - Russo - Eslovaco - Esloveno - Sérvio - Sueco - Suaíli - Tâmil - Telugu - Tailandês - Turco - Ucraniano - Urdu - Vietnamita - Chinês + "Francês +Français" + "Português +Português" + "Romeno +Română" + "Russo +Русский" Anúncios Ocultar banner da loja na tela final @@ -389,9 +378,6 @@ Limitação: O botão voltar na barra de ferramentas pode não funcionar."Desativar animação inicial A animação inicial está desativada. A animação do inicial está ativada. - Desativar barra de status transparente - A barra de status está opaca. - A barra de status está opaca ou transparente. Ativar tela de carregamento gradiente A tela de carregamento gradiente está ativada. A tela de carregamento gradiente está desativada. @@ -407,6 +393,28 @@ Isso não ignora a restrição de idade, apenas aceita isso automaticamente."Alterar ação de clique do anel ao vivo "O canal é aberto quando o anel ao vivo é clicado." A transmissão ao vivo é aberta quando o anel ao vivo é clicado. + Fator de forma de layout + Padrão + Telefone + Telefone (Máximo 480 dp) + Tablet + Tablet (Min 600 dp) + Automotivo + "As alterações incluem: + +Tablet layout +• Os posts da comunidade estão ocultos. + +Automotivo layout +• Shorts abertos no reprodutor regular. +• O feed é organizado por tópicos e canais. +• A descrição do vídeo não pode ser aberta quando o 'Falsificar dados de reprodução' está desligado." + Desativar atualizações de layout + O layout não será atualizado pelo servidor. + O layout será atualizado pelo servidor. + Desativar barra de status transparente + A barra de status está opaca. + A barra de status está opaca ou transparente. Falsificar versão do aplicativo Versão falsificada Versão não falsificada @@ -424,8 +432,10 @@ Se for desativado posteriormente, é recomendável limpar os dados do aplicativo 18.33.40 - Restaurar barra de ação antiga do shorts 18.38.45 - Restaurar antigo comportamento padrão de qualidade de vídeo 18.48.39 - Desativa a atualização em tempo real de \'visualizações\' e \'curtidas\' + 19.01.34 - Desativar interação com descrição do vídeo 19.26.42 - Desativar ícone do Cairo na barra de navegação e de ferramentas 19.33.37 - Restaurar painel flutuante antigo de velocidade de reprodução + Versão da falsificação do app inválida: %s. Menu da Conta Ocultar ou mostrar elementos no menu de contas e na aba Você. @@ -459,6 +469,9 @@ Alguns componentes podem não ser ocultos" Substituir o botão de download de vídeo O botão de download de vídeo nativo abre seu aplicativo de download externo. O botão de download de vídeo nativo abre o download nativo do aplicativo. + Gerenciador de Filas + O botão nativo de download de vídeo abre o gerenciador de filas. + O botão de download de vídeo nativo abre seu aplicativo de download externo. Nome do pacote do aplicativo de download de playlist Nome do pacote do seu aplicativo de download externo instalado, como YTDLnis. @@ -512,7 +525,6 @@ 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. @@ -1001,6 +1013,7 @@ Limitação: Título do vídeo desaparece quando clicado." Mini reprodutor Alterar o estilo do reprodutor minimizado no aplicativo. + Desativar retomada do mini reprodutor Tipo de mini reprodutor Desativado Original @@ -1451,10 +1464,10 @@ Sem margens na parte superior e inferior do reprodutor." O menor valor do gesto de brilho ativa o brilho automático. O menor valor do gesto de brilho não ativa o brilho automático. Ativar gesto de brilho - O gesto de brilho está ativado. + "O gesto de brilho está ativado." O gesto de brilho está desativado. Ativar gesto de volume - O gesto de volume está ativado. + "O gesto de volume está ativado." O gesto de volume está desativado. Ativar salvar e restaurar brilho Salvar e restaurar o brilho ao sair ou entrar em tela cheia. @@ -1468,10 +1481,10 @@ Sem margens na parte superior e inferior do reprodutor." Gestos de deslize no modo \'Tela de bloqueio\' Os gestos de deslize estão ativados no modo \'Tela de bloqueio\'. Os gestos de deslize estão desativados no modo \'Tela de bloqueio\'. - Visibilidade do fundo de gestos - A visibilidade do fundo da sobreposição de gestos. Limite de magnitude de deslize A quantidade de limite para que o deslize ocorra. + Interface alternativa é utilizada. + Interface antiga é utilizada. Tamanho do texto da sobreposição de gestos O tamanho do texto para a sobreposição de gestos Tamanho da área deslizável da tela de sobreposição @@ -1503,12 +1516,29 @@ Sem margens na parte superior e inferior do reprodutor." Automático Vídeo - Velocidade de reprodução padrão - Qualidade de vídeo padrão nos dados móveis - Qualidade de vídeo padrão no Wi-Fi + + Codec Desativar vídeo HDR O vídeo HDR está desativado. O vídeo HDR está ativado. + Desativar codec VP9 + "O codec VP9 está desativado. + +• A resolução máxima é 1080p. +• A reprodução de vídeos usará mais dados na internet do que VP9. +• Para fazer com que o HDR reproduza, o vídeo HDR ainda usa o codec VP9." + O codec VP9 está ativado. + Substituir codec AV1 do software + Substitua o codec AV1 do software com o codec VP9. + + Velocidade de reprodução + Velocidade de reprodução padrão + Lembrar alterações na velocidade de reprodução + As alterações de velocidade de reprodução aplicam-se a todos os vídeos. + As alterações de velocidade de reprodução só se aplicam ao vídeo atual. + Mostrar uma notificação flutuante + Uma notificação flutuante será exibida quando mudar a velocidade padrão de reprodução. + Uma notificação flutuante não será exibida quando mudar a velocidade padrão de reprodução. Ativar velocidade de reprodução personalizada A velocidade de reprodução personalizada está ativada. A velocidade de reprodução personalizada está desativada. @@ -1517,30 +1547,22 @@ Sem margens na parte superior e inferior do reprodutor." O menu flutuante antigo é usado. Editar velocidades de reprodução personalizadas Adicionar ou alterar as velocidades de reprodução disponíveis - Lembrar alterações na velocidade de reprodução - As alterações de velocidade de reprodução aplicam-se a todos os vídeos. - As alterações de velocidade de reprodução só se aplicam ao vídeo atual. - Mostrar uma notificação flutuante - Uma notificação flutuante será exibida quando mudar a velocidade padrão de reprodução. - Uma notificação flutuante não será exibida quando mudar a velocidade padrão de reprodução. + As velocidades personalizadas devem ser inferiores a %sx. Usando valores padrão. + Velocidade personalizada de reprodução inválida. Usando valores padrão. + + Qualidade do vídeo + Qualidade de vídeo padrão nos dados móveis + Qualidade de vídeo padrão no Wi-Fi Lembrar alterações na qualidade do vídeo As alterações de qualidade aplicam-se a todos os vídeos. As alterações de qualidade só se aplicam ao vídeo atual. Mostrar uma notificação flutuante Uma notificação flutuante será exibida quando mudar a qualidade padrão de vídeo. Uma notificação flutuante não será exibida quando mudar a qualidade padrão de vídeo. + móvel Restaurar menu antigo de qualidade de vídeo O menu antigo de qualidade de vídeo está sendo exibido. O menu antigo de qualidade de vídeo não está sendo exibido. - Desativar a velocidade de reprodução para música - A velocidade de reprodução padrão está desativada para música. - A velocidade de reprodução padrão está ativada para música. - Validar usando categorias - A velocidade de reprodução padrão está desativada se a categoria de vídeo for Música. - A velocidade padrão de reprodução está desativada para vídeos reproduzíveis no YouTube. - Ativar velocidade de reprodução padrão no Shorts - A velocidade de reprodução padrão se aplica ao Shorts. - A velocidade de reprodução padrão não se aplica ao Shorts. O buffer pré-carregado é ignorado. Ignorar buffer pré-carregado "Ignore o buffer pré-carregado no início do vídeo para ignorar o atraso padrão na aplicação da qualidade do vídeo. @@ -1552,27 +1574,13 @@ Sem margens na parte superior e inferior do reprodutor." Uma notificação flutuante será exibida. Uma notificação flutuante não será exibida. Falsificar dimensões do dispositivo - "Falsifica as dimensões do dispositivo para o valor máximo. -A alta qualidade pode ser desbloqueada em alguns vídeos que exigem dimensões elevadas do dispositivo, mas não em todos os vídeos." - Desativar codec VP9 - "O codec VP9 está desativado. - -• A resolução máxima é 1080p. -• A reprodução de vídeos usará mais dados na internet do que VP9. -• Para fazer com que o HDR reproduza, o vídeo HDR ainda usa o codec VP9." - O codec VP9 está ativado. - Substituir codec AV1 do software - Substitua o codec AV1 do software com o codec VP9. - Rejeitar resposta do codec AV1 do software - "Rejeita à força a resposta do codec AV1 do software. -Após cerca de 20 segundos de buffer, muda para um codec diferente." - O processo de fallback causa cerca de 20 segundos de buffer. - Alterando a velocidade padrão para %s. - Alterando a qualidade padrão de dados móveis para %s. - Falha ao definir a qualidade de vídeo. - Alterando a qualidade padrão do Wi-Fi para %s. - As velocidades personalizadas devem ser inferiores a %sx. Usando valores padrão. - Velocidade personalizada de reprodução inválida. Usando valores padrão. + + Desativar a velocidade de reprodução para música + A velocidade de reprodução padrão está desativada para música. + A velocidade de reprodução padrão está ativada para música. + Validar usando categorias + A velocidade de reprodução padrão está desativada se a categoria de vídeo for Música. + A velocidade padrão de reprodução está desativada para vídeos reproduzíveis no YouTube. Return YouTube Dislike Ativar Return YouTube Dislike @@ -1768,6 +1776,7 @@ Clique para ver como emitir uma chave de API." Voto negativo Alterar categoria Não há segmentos para votar. + Escolha a categoria do segmento A categoria está desativada nas configurações. Ative a categoria para enviar. Novo segmento SponsorBlock @@ -1885,8 +1894,6 @@ Toque no botão continuar e desative as otimizações da bateria." Android VR "Android VR (Sem autenticação)" - "iOS -(requer PoToken)" "iOS TV (é necessário logar)" Efeitos colaterais da falsificação @@ -1894,15 +1901,27 @@ Toque no botão continuar e desative as otimizações da bateria." • Volume estável não está disponível. • Desativar faixas de áudio automático forçadas não estão disponíveis. • Os vídeos para crianças podem não reproduzir quando desconectado ou em modo incógnito." - • Pode haver problemas de reprodução (PoToken necessário). "• Filmes ou vídeos pagos podem não reproduzir. • Os vídeos para crianças podem não ser reproduzidos quando desconectado ou em modo incógnito." + Usar cliente iOS + "Cliente iOS adicionado aos clientes disponíveis. + +AVISO: cliente iOS está obsoleto. Quaisquer problemas que surjam ao usá-lo estão por sua conta e risco." + Cliente iOS não adicionado aos clientes disponíveis. Forçar iOS AVC (H.264) O codec de vídeo é forçado para AVC (H.264). O codec de vídeo é determinado automaticamente. "Ativar isso pode melhorar a duração da bateria e corrigir travamentos na reprodução. O AVC tem uma resolução máxima de 1080p, o codec de áudio Opus não está disponível e a reprodução de vídeo usará mais dados da internet do que VP9 ou AV1." + Pular criptografia de resposta Onesie + "Pular criptografia de resposta Onesie. + +• Corrige um novo tipo de problema de reprodução que alguns usuários estão enfrentando. +• O codec AV1 pode não estar disponível." + "Não pule a criptografia de resposta Onesie. + +• Alguns usuários podem experimentar um novo tipo de problema de reprodução." Exibir em Estatísticas para nerds O cliente usado para buscar dados de streaming é mostrado em Estatísticas para nerds. O cliente usado para buscar dados de streaming está oculto em Estatísticas para nerds. 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 c1e134bcd..e87a1c83a 100644 --- a/patches/src/main/resources/youtube/translations/ru-rRU/strings.xml +++ b/patches/src/main/resources/youtube/translations/ru-rRU/strings.xml @@ -19,59 +19,150 @@ "%1$s не установлен. Пожалуйста, скачайте %2$s с сайта." %s не установлен. Установите его. + Добавить в очередь + Добавить в очередь и открыть очередь + Добавить в очередь и посмотреть + Внешний загрузчик + Открыть очередь + Очередь + Удалить из очереди + Удалить из очереди и открыть очередь + Удалить очередь + Сохранить очередь + "Вместо открытия внешнего загрузчика открывается диалог управления очередью. + +Также можно открыть управление нажатием и удерживанием кнопки назад на панели навигации. + +Эта функция все ещё находится в процессе разработки, поэтому большинство функций не работает. + +Пожалуйста, используйте её только для отладки." + Требуется авторизация + Управление очередью недоступно (%s). + Не определился список просмотра + Очередь пустая + Не удалось идентифицировать видео + Не удалось добавить видео. + Не удалось создать очередь. + Не удалось удалить очередь. + Не удалось удалить видео. + Не удалось сохранить очередь. + Видео добавлено успешно. + Очередь создана успешно. + Очередь удалена успешно. + Видео удалено успешно. + Очередь была успешно сохранена до: \'%s\'. Язык RVX Язык приложения - عَرَبِيّ - Azərbaycan - Български - বাংলা - Català - Čeština - Dansk - Deutsch - Ελληνικά - English - Español - Eesti keel - فارسی - Suomalainen - Le français - ગુજરાતી - हिन्दी - Hrvatski - Magyar - Indonesia - Italiano - 日本語 - Қазақ - 한국어 - Lietùvių - Latviešu - Македонски - Монгол - Marāṭhī / मराठी - Melayu - မြန်မာ - Nederlands - ଓଡ଼ିଆ - ਪੰਜਾਬੀ / پنجابی - Polski - Português - Romanesc - Русский - Slovenský - Slovenski - Српски - Svenska - Kiswahili - தமிழ் - తెలుగు - แบบไทย - Türkçe - Українська - اردو - Tiếng Việt - 中文 + "Амхарский +አማርኛ" + "Арабский +العربية" + "Азербайджанский +Azərbaycan" + "Белорусский +беларуская" + "Болгарский +Български" + "Бенгальский +বাংলা" + "Каталонский +Català" + "Чешский +Čeština" + "Датский +Dansk" + "Немецкий +Deutsch" + "Греческий +Ελληνικά" + "Английский +English" + "Испанский +Español" + "Эстонский +Eesti" + "Персидский +فارسی" + "Финский +Suomi" + "Французский +Français" + "Гуджаратский +ગુજરાતી" + "Ивритский +עברי" + "Хинди +हिन्दी" + "Хорватский +Hrvatski" + "Венгерский +Magyar" + "Индонезийский +Indonesia" + "Итальянский +Italiano" + "Японский +日本語" + "Казахский +Қазақ тілі" + "Корейский +한국어" + "Литовский +Lietuvių" + "Латышский +Latviešu" + "Македонский +Македонски" + "Монгольский +Монгол" + "Маратхский +मराठी" + "Малайский +Melayu" + "Бирманский +ဗမာ" + "Голландский +Nederlands" + "Ория +ଓଡ଼ିଆ" + "Панджабский +ਪੰਜਾਬੀ" + "Польский +Polski" + "Португальский +Português" + "Румынский +Română" + "Русский +Русский" + "Словацкий +Slovenčina" + "Албанский +Shqip" + "Словенский +Slovenščina" + "Сербский +Српски" + "Шведский +Svenska" + "Суахилийский +Kiswahili" + "Тамильский +தமிழ்" + "Телугуский +తెలుగు" + "Тайский +ไทย" + "Турецкий +Türkçe" + "Украинский +Українська" + "Урдуский +اردو" + "Вьетнамский +Tiếng Việt" + "Китайский +中文" Реклама Скрыть баннер магазина на конечном экране @@ -271,8 +362,8 @@ Shorts Фильтр всплывающего меню ленты включен. Фильтр всплывающего меню ленты отключен. Тип фильтра выдвижного меню в ленте - Фильтруется, если содержат.<br><br>Чтобы скрыть меню <b>\'Воспроизвести следующим в очереди\'</b>, можете использовать <b>\'Воспроизвести следующим\'</b> или <b>\'в очереди\'</b> как ключевые слова. - Фильтруется, если совпадают.<br><br>Чтобы скрыть меню <b>\'Воспроизвести следующим в очереди\'</b>, можете использовать только <b>\'Воспроизвести следующим в очереди\'</b> как ключевые слова. + Фильтр, если содержат.<br><br> Чтобы скрыть меню <b>\'Просмотр следующим в очереди\'</b>, можете использовать <b>\'Просмотр следующим\'</b> или <b>\'в очереди\'</b> как ключевые слова. + Фильтр, если совпадают.<br><br> Чтобы скрыть меню <b>\'Просмотр следующим в очереди\'</b>, можете использовать только <b>\'Просмотр следующим в очереди\'</b> как ключевые слова. Фильтр всплывающего меню ленты Список имен всплывающего меню ленты для скрытия.\nРазделять новой строкой. @@ -283,9 +374,9 @@ Shorts Фильтр комментариев Фильтр комментариев включен. Фильтр комментариев отключен. - Фильтр видео в \"Главная\" - Фильтр видео в \"Главная\" включен. - Фильтр видео в \"Главная\" отключен. + Фильтр видео на главной + Фильтр видео на главной включен. + Фильтр видео на главной отключен. Фильтр результатов поиска Фильтр результатов поиска включен. Фильтр результатов поиска отключен. @@ -323,9 +414,9 @@ Shorts • С каналов, на которые вы не подписаны (менее 1000 просмотров)." Фильтр по количеству просмотров - Фильтр видео в \"Главная\" по просмотрам - Фильтр видео в \"Главная\", по просмотрам, включен. - Фильтр видео в \"Главная\", по просмотрам, отключен. + Фильтр видео на главной по просмотрам + Фильтр видео на главной, по просмотрам, включен. + Фильтр видео на главной, по просмотрам, отключен. Фильтр результатов поиска по просмотрам Фильтр результатов поиска, по просмотрам, включен. Фильтр результатов поиска, по просмотрам, отключен. @@ -404,9 +495,6 @@ Shorts Анимированная заставка Анимированная заставка отключена. Анимированная заставка включена. - Полупрозрачность строки состояния - Полупрозрачность включена. - Полупрозрачность отключена. Переливающийся экран загрузки Переливающийся экран загрузки включен. Переливающийся экран загрузки отключен. @@ -418,7 +506,7 @@ Shorts Серые разделители отображены. Скрыть диалог возрастного ограничения "Возрастные ограничения будут приниматься автоматически." - Действие нажатия на точку прямого эфира + Действие нажатия на значок прямого эфира "Открывается канал." Открывается прямой эфир. Макет интерфейса @@ -437,6 +525,23 @@ Shorts • Shorts открываются в обычном плеере. • Лента организована по темам и каналам. • Описание видео нельзя открыть, если «Подмена потоковых данных» отключена." + Отключить обновление макета + Макет не будет обновляться сервером. + Макет будет обновляться сервером. + "Макет программы возвращается к макету, который использовался при первой установке. + +Некоторые серверные макеты могут не вернуться. + +Изменения включают: +• Компоненты в раскрывающемся меню плеера (или связанные настройки) могут не работать. +• Анимация прокручивания чисел. +• Используется вкладка Библиотека. +• Секция Музыка в описании видео может не работать. +• Кнопка переключения аккаунта может не появляться во вкладке Библиотека. Используйте настройки 'Включить широкую панель поиска на вкладке Вы'." + Полупрозрачность строки состояния + Полупрозрачность включена. + Полупрозрачность отключена. + Для прошивок некоторых разработчиков Android 12+, включение этой функции может сделать панель навигации системы прозрачной. Подмена версии приложения Версия приложения подменена Версия приложения не подменена @@ -454,8 +559,10 @@ Shorts 18.33.40 - Восстановление старой панели действий Shorts 18.38.45 - Восстановление старого поведения качества видео по умолчанию 18.48.39 - Отключение обновления \"просмотров\" и \"лайков\" в реальном времени + 19.01.34 - Отключить взаимодействие с описанием видео 19.26.42 - Выключится значок Каир в навигации и панели инструментов 19.33.37 - Восстановит старый стиль выпадающей панели скорости проигрывания + Неверная версия подмены: %s. Меню аккаунта Настройка меню аккаунта и вкладки Вы. @@ -489,8 +596,11 @@ Shorts Кнопка \"Скачать\" для плейлиста, использует внешний загрузчик. Кнопка \"Скачать\" для плейлиста, использует внутренний загрузчик. Действие кнопки \"Скачать\" для видео - Кнопка \"Скачать\" использует внешний загрузчик. + Кнопка \"Скачать\" открывает внешний загрузчик. Кнопка \"Скачать\" использует внутренний загрузчик. + Управление очередью + Кнопка загрузки видео открывает управление очередью. + Кнопка загрузки видео открывает внешний загрузчик. Внешний загрузчик для плейлиста, название пакета Например: NewPipe или YTDLnis, или др. (Для плейлиста). @@ -545,7 +655,6 @@ Shorts Полупрозрачность панели навигации Полупрозрачность включена. Полупрозрачность отключена. - В некоторых версиях YouTube этот параметр может сделать панель навигации прозрачной или нарушить макет в режиме PIP. Панель навигации Панель навигации скрыта. Панель навигации отображена. @@ -564,7 +673,7 @@ Shorts Экономия трафика Экономия трафика скрыта. Экономия трафика отображена. - Меню автовоспроизведения или воспроизведения + Меню авто просмотра или просмотра Скрыто. Отображено. Качество видео @@ -692,11 +801,11 @@ Shorts Значение должно быть в диапазоне от 0 до 100. Сброс по умолчанию. Переключатель списков \"Джем\" Авто-переключатель списков \"Джем\" отключен. - "Авто-переключение списков \"Джем\", при активном автовоспроизведении, включено. + "Авто переключение списков \"Джем\", при активном авто просмотре, включено. -Автовоспроизведение настраивается в: -Настройки YouTube → Автовоспроизведение → Автовоспроизведение следующего видео" - Авто-переключение списков \"Джем\", при активном автовоспроизведении, отключено. +Авто просмотр настраивается в: +Настройки YouTube → Авто просмотр → Авто просмотр следующего видео" + Авто переключение списков \"Джем\", при активном авто просмотре, отключено. Всплывающие панели плеера Всплывающие панели плеера отключены. Всплывающие панели плеера включены. @@ -748,7 +857,7 @@ Shorts Автовоспроизведение можно изменить в настройках YouTube: Настройки -> Автовоспроизведение -> Автовоспроизведение следующего видео" Рекомендуемое видео в конце воспроизведения отображено. - Задержка автовоспроизведения + Задержка авто просмотра Задержка автовоспроизведения следующего видео отключена. Задержка автовоспроизведения следующего видео включена. Реакции по времени @@ -893,9 +1002,9 @@ Shorts Меню \"Дополнительная информация\" Меню \"Дополнительная информация\" скрыто. Меню \"Дополнительная информация\" отображено. - Меню \"Скорость воспроизведения\" - Меню \"Скорость воспроизведения\" скрыто. - Меню \"Скорость воспроизведения\" отображено. + Меню \"Скорость просмотра\" + Меню \"Скорость просмотра\" скрыто. + Меню \"Скорость просмотра\" отображено. Заголовок меню выбора качества Заголовок меню выбора качества скрыт. Заголовок меню выбора качества отображен. @@ -960,9 +1069,9 @@ Shorts "Показывает секцию заголовка видео в полноэкранном режиме. Ограничение: заголовок видео исчезает при нажатии." - Превью автовоспроизведения - Превью автовоспроизведения скрыто. - Превью автовоспроизведения отображено. + Превью авто просмотра + Превью авто просмотра скрыто. + Превью авто просмотра отображено. Кнопка \"Повтор\" в чате трансляции Кнопка \"Повтор\" в онлайн чате скрыта.\n\nПоявляется в полноэкранном режиме при закрытии онлайн чата. Кнопка \"Повтор\" в онлайн чате отображена.\n\nПоявляется в полноэкранном режиме при закрытии онлайн чата. @@ -1091,9 +1200,9 @@ Shorts Кнопки плеера Скрыть или показать кнопки в видео. - Кнопка \"Автовоспроизведение\" - Кнопка \"Автовоспроизведение\" скрыта. - Кнопка \"Автовоспроизведение\" отображена. + Кнопка \"Авто просмотр\" + Кнопка \"Авто просмотр\" скрыта. + Кнопка \"Авто просмотр\" отображена. Кнопка \"Субтитры\" Кнопка \"Субтитры\" скрыта. Кнопка \"Субтитры\" отображена. @@ -1127,14 +1236,16 @@ Shorts Нажатия кнопки, отключает/включает звук текущего видео. Кнопка внешний загрузчик Запустить внешний загрузчик. - Кнопка скорости воспроизведения + Управление очередью + Вместо запуска внешнего загрузчика открывается управление очередью. + Кнопка Скорость просмотра "Нажать - открытие окна скорости. -Нажать и удерживать - скорость воспроизведения в 1.0x." +Нажать и удерживать - скорость просмотра в 1.0x." Кнопка \"Белый список\" Нажать - Открыть \"Белый список\". Нажать и удерживать - Открыть настройки \"Белый список\". Кнопка воспроизвести все - "Коснитесь, чтобы создать список воспроизведения всех видео из канала. + "Коснитесь, чтобы создать список просмотра всех видео из канала. Коснитесь и удерживайте, для отмены. Инфо: @@ -1164,7 +1275,7 @@ Shorts Каналы в белом списке отсутствуют. Не добавлен в белый список. Добавлен в белый список. - Скорость воспроизведения + Скорость просмотра SponsorBlock Ошибка загрузки данных канала. Скорость сброшена на: %sx. @@ -1180,9 +1291,9 @@ Shorts Информация метки времени отключена. Добавить тип информации Добавить качество видео. - Показывать скорость воспроизведения. + Показывать скорость просмотра. Заменить действие метки времени - Нажмите, чтобы открыть меню скорости воспроизведения или качества видео. + Открыть меню скорости просмотра или качества видео. Нажмите, чтобы показать оставшееся время. Главы в прогрессе Главы в прогрессе скрыты. @@ -1255,7 +1366,7 @@ Shorts Секция \"Расшифровка видео\" скрыта. Секция \"Расшифровка видео\" отображена. - Взаимодействие с описанием видео + Отключить взаимодействие с описанием видео "Отключает следующее взаимодействие при расширении описания видео: • Нажать - прокрутка. @@ -1265,9 +1376,9 @@ Shorts Описание видео разворачивается вручную. Shorts - Фоновое воспроизведение Shorts - Фоновое воспроизведение Shorts отключено. - Фоновое воспроизведение Shorts включено. + Фоновый просмотр Shorts + Фоновый просмотр Shorts отключен. + Фоновый просмотр Shorts включен. Возобновление Shorts при запуске приложения Возобновление Shorts отключено. Возобновление Shorts включено. @@ -1419,7 +1530,7 @@ Shorts Сердечко Сердце (Оттенок) Скрыта - Фон кнопки \"Воспроизведение\" - \"Пауза\" + Фон кнопки \"Просмотр\" - \"Пауза\" Фон скрыт. Фон отображен. @@ -1453,6 +1564,10 @@ Shorts Меню открыть видео Меню открыть видео отображено. Меню открыть видео скрыто. + Выбор скорости + Окно выбора скорости + Отображено. + Скрыто. Режим повтора Меню режима повтора Меню режима повтора отображено. @@ -1497,10 +1612,10 @@ Shorts При снижении яркости жестом до минимума активируется автояркость. При снижении яркости жестом до минимума автояркость не активируется. Управление яркостью жестом - Управление яркостью жестом включено. + "Управление яркостью жестом включено." Управление яркостью жестом отключено. Управление громкостью жестом - Громкость жестом включено. + "Громкость жестом включено." Громкость жестом отключено. Сохранение и восстановление яркости Сохранять и восстанавливать яркость при переключениях полного экрана. @@ -1514,10 +1629,20 @@ Shorts Жесты в режиме \"Блокировка экрана\" Жесты в режиме \"Блокировка экрана\" включены. Жесты в режиме \"Блокировка экрана\" отключены. - Видимость фона жестов - Видимость фона наложения при жесте. Порог величины жеста Амплитуда движения распознаваемая как жест. + Альтернативный интерфейс жеста + Используется альтернативный интерфейс. + Используется старый интерфейс. + Включить минималистичный стиль + Минималистичный стиль включен. + Минималистичный стиль отключен. + Показать круговой индикатор + Отображается круговой индикатор. + Отображается линейный индикатор. + Затемнение фона панели во время жеста + Значение затемнения в пределах 0-100. + Затемнение при жесте должно быть в пределах 0-100. Размер текста при жесте Размер текста для наложения жестов. Размер области экрана для жестов @@ -1551,12 +1676,42 @@ Shorts Авто Видео - Скорость по умолчанию - Качество видео для мобильной сети - Качество видео для Wi-Fi сети + + Кодек HDR видео HDR видео отключены. HDR видео включены. + VP9 кодек + "VP9 кодек отключен. + +• Максимальное разрешение 1080p. +• Просмотр будет использовать больше трафика, чем с VP9. +• HDR просмотр недоступен, HDR видео все еще использует VP9 кодек." + VP9 кодек включен. + Заменить программный кодек AV1 + Замена программного кодека AV1 на VP9. + + Скорость просмотра + Скорость по умолчанию + Запомнить скорость воспроизведения + Изменяется скорость воспроизведения у всех видео. + Изменяется скорость воспроизведения у текущего видео. + Уведомление о выбранной скорости + Всплывающее уведомление отображено. + Всплывающее уведомление скрыто. + Скорость просмотра в Shorts по умолчанию + Запомнить скорость просмотра в Shorts + "Скорость просмотра применяется ко всем Shorts. + +Информация: +• Изменить скорость просмотра в плеере Shorts - можно, только, через диалог «Скорость» в разделе «Пользовательские действия». +• При отключенном патче «компоненты Shorts», изменения недоступны." + Скорость просмотра применяются, только, к текущему Short. + Уведомление о выбранной скорости + Уведомление включено. + Уведомление отключено. + Скорость просмотра: %s. + Скорость просмотра Shorts: %s. Пользовательская скорость воспроизведения Пользовательская скорость воспроизведения включена. Пользовательская скорость воспроизведения отключена. @@ -1565,30 +1720,33 @@ Shorts Старый стиль всплывающего меню скорости воспроизведения. Пользовательская скорость воспроизведения Добавить или изменить скорости воспроизведения. - Запомнить скорость воспроизведения - Изменяется скорость воспроизведения у всех видео. - Изменяется скорость воспроизведения у текущего видео. - Уведомление о выбранной скорости - Всплывающее уведомление отображено. - Всплывающее уведомление скрыто. + Значения пользовательской скорости не должны превышать %sx. Сброс к значениям по умолчанию. + Недопустимые значения скорости. Сброс к значениям по умолчанию. + + Качество видео + Качество видео для мобильной сети + Качество видео для Wi-Fi сети Запомнить изменения качества видео Изменяется качество у всех видео. Изменяется качество у текущего видео. Уведомление о выбранном качестве Всплывающее уведомление отображено. Всплывающее уведомление скрыто. + Качество Shorts, мобильной сети + Качество Shorts, Wi-Fi сети + Запомнить изменения качества Shorts + Изменяется качество у всех Shorts. + Изменяется качество у текущего Shorts. + Уведомление о выбранном качестве + Уведомление включено. + Уведомление отключено. + Мобильный + WiFi + Качество %1$s изменено на: %2$s. + Качество Shorts %1$s изменено на: %2$s. Старое меню качества Старое меню качества отображено. Старое меню качества скрыто. - Скорость воспроизведения для музыки - Скорость по умолчанию для категории YouTube Музыка включена. - Скорость воспроизведения по умолчанию для музыки включена. - Выбирать скорость ориентируясь на категории - Скорость по умолчанию для категории YouTube Музыка отключена. - Скорость по умолчанию для категории YouTube Музыка включена. - Скорость воспроизведения по умолчанию в Shorts - Скорость воспроизведения по умолчанию в Shorts включена. - Скорость воспроизведения по умолчанию в Shorts отключена. Пропущен предварительно загруженный буфер. Пропустить предварительно загруженный буфер "Пропустить предварительно загруженный буфер при запуске видео, чтобы избежать задержек обеспечения качества видео. @@ -1602,26 +1760,18 @@ Shorts Уведомление включено. Уведомление отключено. Подмена размеров устройства - "Подменяет размеры устройства, для разблокировки более высокого качества видео, которое может быть недоступно на вашем устройстве." - VP9 кодек - "VP9 кодек отключен. + "Подменяет размеры устройства до максимального значения. -• Максимальное разрешение 1080p. -• Воспроизведение видео будет использовать больше сетевых данных, чем с VP9. -• HDR воспроизведения нет, HDR видео все еще использует VP9 кодек." - VP9 кодек включен. - Заменить программный кодек AV1 - Замена программного кодека AV1 на VP9. - Отказ от программного кодека AV1 - "Принудительный отказ от программного кодека AV1. -После примерно 20 секунд буферизации будет применён другой кодек." - Буферизация из-за программного кодека AV1 (примерно 20 сек.) - Скорость воспроизведения по умолчанию изменена на %s. - Качество видео в моб. сети изменено на %s. - Ошибка установки качества видео. - Качество видео в Wi-Fi сети изменено на %s. - Значения пользовательской скорости не должны превышать %sx. Сброс к значениям по умолчанию. - Недопустимые значения скорости. Сброс к значениям по умолчанию. +Информация: +• Высокое качество может быть разблокировано на некоторых видео, которые это требуют, не все видео. +• Недоступно, если включена опция 'Подмена потоковых данных'." + + Скорость воспроизведения для музыки + Скорость по умолчанию для категории YouTube Музыка включена. + Скорость воспроизведения по умолчанию для музыки включена. + Выбирать скорость ориентируясь на категории + Скорость по умолчанию для категории YouTube Музыка отключена. + Скорость по умолчанию для категории YouTube Музыка включена. Вернуть YouTube Dislike Включить Return YouTube Dislike @@ -1752,6 +1902,7 @@ Shorts Показывать кнопку \"Пропуск\" Показывать в шкале воспроизведения Ничего не делать + Непрозрачность: Цвет: Цвет изменен. Цвет сброшен. @@ -1818,6 +1969,8 @@ Shorts Проголосовать против Изменить категорию Нет сегментов для голосования. + + %1$s до %2$s Выберите категорию сегмента Эта категория отключена в настройках. Включите ее для отправки сегмента. Новый сегмент SponsorBlock @@ -1879,7 +2032,7 @@ Shorts Открывает ссылки в браузере приложения. Очистить ссылки при отправке Убирает параметры отслеживания запросов из URL при отправке ссылки. - Использование по умолчанию + Открыть настройки Чтобы открывать ссылки YouTube с помощью RVX, настройте \"Открытие поддерживаемых ссылок\" и включите нужные поддерживаемые веб-адреса. Открыть GmsCore Включите \"Облачные уведомления\" для получения уведомлений. @@ -1895,9 +2048,9 @@ Shorts Нажмите кнопку \"Продолжить\" и отключите оптимизацию батареи." Продолжить - Список приложений общего доступа - Список приложений общего доступа - системный. - Список приложений общего доступа - встроенный. + Изменить диалог \"Поделиться\" + Используется системный диалог. + Используется встроенный диалог. Включить кодек OPUS Включить кодек OPUS, если содержимое в плеере подходит для кодека. @@ -1924,50 +2077,62 @@ Shorts Настройки скопированы в буфер обмена. Подмена потоковых данных - Подменяет потоковые данные при проблемах с воспроизведением видео. + Подменяет потоковые данные при проблемах с просмотром видео. Подмена потоковых данных Подмена потоковых данных включена. "Подмена потоковых данных отключена. -Воспроизведение видео может не работать." - Отключение этой настройки вызовет проблемы с воспроизведением видео. +Просмотр может быть не доступен." + Отключение этой настройки вызовет проблемы с просмотром. Клиент по умолчанию "Android TV (Требуется вход)" Android VR "Android VR (Без автора)" - "iOS -(Необходим PoToken)" - "iOS + "iOS +(Устаревшая)" + "iOS TV (Необходим вход)" Эффекты от подмены "• Меню аудио дорожки отсутствует. • Стабильная громкость недоступна. • Отключить принудительные авто звуковые дорожки недоступна. -• Детские видео не могут воспроизводиться в режиме инкогнито или выхода." - • Возможно проблемы (требуется PoToken). - "• Фильмы или платные видео могут не воспроизводиться. -• Детские видео могут не воспроизводиться при выходе из системы или в режиме инкогнито." +• Детские видео могут не работать в режиме инкогнито или выхода." + • Целые диапазоны ASN/IP могут блокироваться сервером. + "• Фильмы или платные видео могут не работать. +• Детские видео могут не работать при выходе из системы или в режиме инкогнито." + Использовать клиент iOS + "Клиент iOS добавлен к доступным клиентам. + +ВНИМАНИЕ: Клиент iOS устаревший. Любые проблемы, возникающие при его использовании, на свой страх и риск." + Клиент iOS не добавлен к доступным клиентам. + "При запросе конечных точек YouTube API с помощью iOS требуются токены Auth, выданные устройством iOS, и токены PoToken, выданные iOSGuard. + +Это означает, что в запросах трансляции через iOS не будет ни токенов Auth, ни токенов PoToken, и сервер может считать пользователя ботом и заблокировать целый диапазон ASN/IP. + +ИСПОЛЬЗУЙТЕ НА СВОЙ СТРАХ И РИСК!" Принудительно iOS, AVC (H.264) - Принудительное использование AVC (H.264) включено. - Принудительное использование AVC (H.264) отключено. + Принудительно AVC (H.264) включен. + Принудительно AVC (H.264) отключено. "Включение может: - улучшить время автономной работы - устранить прерывания при воспроизведении. -Максимальное разрешение видео AVC составляет 1080p. Кодек Opus недоступен. Потребление интернет трафика будет больше, чем при VP9 или AV1." - Пропустить шифрование ответа Onesie - "Шифрование ответа Onesie пропускается. +Максимальное разрешение AVC - 1080p. Кодек Opus недоступен. Потребление трафика больше, чем при VP9 или AV1." + Пропуск ответа для шифрования оболочки + "Пропуск включен. -• Исправлен новый тип воспроизведения, с которыми сталкиваются некоторые пользователи. +• Устраняет проблемы просмотра, у некоторых пользователей. • Кодек AV1 может быть недоступен." - "Шифрование ответа Onesie не пропускается. + "Пропуск отключен. -• Некоторые пользователи могут сталкиваться с новым типом проблемы воспроизведения." - Статистике для сисадминов +• Могут быть проблемы просмотра, у некоторых пользователей." + Статистика для сисадминов Клиент потоковых данных отображается в Статистике для сисадминов. Клиент потоковых данных скрыт в Статистике для сисадминов. Язык аудио потока по умолчанию VR + Не получены клиентские потоки. + Возможно, Вы не вошли в систему. PoToken / VisitorData PoToken @@ -1975,11 +2140,11 @@ Shorts VisitorData VisitorData выданный с BotGuard в надежном браузере. О PoToken / VisitorData - "Некоторые клиенты требуют PoToken и VisitorData для получения правильного потокового ответа данных. + "Некоторые клиенты требуют PoToken и VisitorData для получения правильного ответа потоковых данных. -При использовании iOS клиента по умолчанию, эти значения могут потребоваться. +Для iOS клиента по умолчанию, эти значения могут потребоваться. -Нажмите, чтобы увидеть дополнительную информацию." +Нажмите, для дополнительной информации." История просмотра Изменить настройки истории просмотра. 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 23ebdab7c..2ca246e47 100644 --- a/patches/src/main/resources/youtube/translations/tr-rTR/strings.xml +++ b/patches/src/main/resources/youtube/translations/tr-rTR/strings.xml @@ -21,57 +21,6 @@ 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 @@ -385,9 +334,6 @@ 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ışı @@ -404,6 +350,9 @@ Sınırlama: Araç çubuğundaki geri butonu çalışmayabilir." 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. + Yarı saydam durum çubuğunu devre dışı bırak. + Durum çubuğu opak. + Durum çubuğu opak veya yarı saydam. Uygulama Versiyonunu taklit et Sürüm taklit ediliyor Sürüm taklit edilmiyor @@ -508,7 +457,6 @@ Bu ayar etkili olmazsa Gizli moda geçmeyi deneyin." 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. @@ -1237,10 +1185,10 @@ Bilinen sorunlar: Parlaklık hareketinin en düşük değeri otomatik parlaklığı etkinleştirir. Parlaklık hareketinin en düşük değeri otomatik parlaklığı etkinleştirmez. Kaydırarak parlaklığı ayarla - Video tam ekrandayken, ekranın solunu kaydırarak ekran parlaklığını ayarlama açık + "Video tam ekrandayken, ekranın solunu kaydırarak ekran parlaklığını ayarlama açık" Video tam ekrandayken, ekranın solunu kaydırarak ekran parlaklığını ayarlama kapalı Kaydırarak sesi ayarla - Video tam ekrandayken, ekranın sağını kaydırarak sesi ayarlama açık + "Video tam ekrandayken, ekranın sağını kaydırarak sesi ayarlama açık" Video tam ekrandayken, ekranın sağını kaydırarak sesi ayarlama kapalı Kaydetmeyi ve parlaklığı geri yüklemeyi etkinleştir Tam ekrandan çıkarken veya tam ekrana girerken parlaklığı kaydedin ve geri yükleyin. @@ -1254,8 +1202,6 @@ Bilinen sorunlar: \'Kilit ekranı\' modundaki kaydırma hareketleri Kaydırma hareketleri \'Kilit ekranı\' modunda etkindir. Kaydırma hareketleri \'Kilit ekranı\' modunda devre dışıdır. - Kaydırma arka plan şeffaflığı - Kaydırma arka planının şeffaflık değeri Kaydırma büyüklük eşiği Kaydırma işleminin gerçekleşmesi için eşik miktarı Kaydırma metin boyutu @@ -1271,12 +1217,17 @@ Bilinen sorunlar: Otomatik Video - Varsayılan oynatma hızı - Mobil ağda varsayılan video kalitesi - Wi̇-Fi ağında varsayılan video kalitesi + HDR\'lı videolarda, HDR\'ı devre dışı bırak HDR devre dışı HDR etkin + AV1 codec yazılımı bileşenini değiştirin + AV1 codec yazılımı bileşenini VP9 kodeği ile değiştirin. + + Varsayılan oynatma hızı + Oynatma hızı değişimlerini hatırla + Oynatma hızı değişimleri bütün videolara uygulanıyor. + Oynatma hızı değişimleri sadece oynatılan videoya uygulanıyor Özel oynatma hızlarını etkinleştir Özel oynatma hızları etkin Özel oynatma hızları kapalı @@ -1285,18 +1236,17 @@ Bilinen sorunlar: Eski stil açılır menü kullanıldı. Özel oynatma hızlarını düzenle Mevcut oynatma hızlarını değiştirin. - Oynatma hızı değişimlerini hatırla - Oynatma hızı değişimleri bütün videolara uygulanıyor. - Oynatma hızı değişimleri sadece oynatılan videoya uygulanıyor + Geçersiz özel oynatma hızları. Hızlar varsayılana sıfırlandı. + Geçersiz özel oynatma hızları. Varsayılanlar seçildi. + + Mobil ağda varsayılan video kalitesi + Wi̇-Fi ağında varsayılan video kalitesi Video kalitesi değişimlerini hatırla Kalite değişimleri bütün videolara uygulanıyor Kalite değişimleri sadece oynatılan videoya uygulanıyor Eski video kalite menüsünü geri getir Eski video kalite menüsü gösteriliyor. Eski video kalite menüsü gösterilmiyor. - Shorts videolarında varsayılan oynatma hızını etkinleştir - Varsayılan oynatma hızı Shorts videolara uygulanır. - Varsayılan oynatma hızı Shorts videolara uygulanmaz. Önceden yüklenmiş arabellek atlandı. Önceden yüklenmiş arabelleği atla "Varsayılan video kalitesi uygulama gecikmesini atlamak için video başlangıcında önceden yüklenmiş arabelleği atlayın. @@ -1308,19 +1258,7 @@ Bilinen sorunlar: Gizlenmiyor Gizleniyor Farklı cihaz boyutlarını taklit et - "Cihaz boyutlarını maksimum değere kadar taklit eder. Yüksek cihaz boyutları gerektiren bazı videolarda yüksek kalitenin kilidi açılabilir ancak tüm videolarda bu durum geçerli olmayabilir." - AV1 codec yazılımı bileşenini değiştirin - AV1 codec yazılımı bileşenini VP9 kodeği ile değiştirin. - AV1 codec yazılımı yanıtını reddet - "AV1 codec yazılımı yanıtı zorla reddeder. -Yaklaşık 20 saniyelik ara belleğe alma işleminin ardından farklı codec bileşenine geçiş yapılır." - Geri çekilme işlemi yaklaşık 20 saniyelik ara belleğe alma işlemine neden olur. - Varsayılan hız %s olarak değiştiriliyor. - Mobil ağda varsayılan video kalitesi %s olarak değiştiriliyor. - Video kalitesi ayarlanamadı. - Wi-Fi\'da varsayılan video kalitesi %s olarak değiştiriliyor. - Geçersiz özel oynatma hızları. Hızlar varsayılana sıfırlandı. - Geçersiz özel oynatma hızları. Varsayılanlar seçildi. + Return YouTube Dislike Return YouTube Dislike\'ı etkinleştir @@ -1494,6 +1432,7 @@ Bir örnek görmek için buraya dokunun. Olumsuz oy ver Kategoriyi değiştir Oylanabilecek bir kısım yok. + Kategori seçin Ayarlarda kategori devre dışı bırakıldı. Göndermek için kategoriyi etkinleştir. Yeni SponsorBlock segmenti 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 ccf60c3de..4d5174517 100644 --- a/patches/src/main/resources/youtube/translations/uk-rUA/strings.xml +++ b/patches/src/main/resources/youtube/translations/uk-rUA/strings.xml @@ -19,59 +19,150 @@ "%1$s не встановлено. Будь ласка, завантажте %2$s з сайту." %s не встановлено. Будь ласка, встановіть його. + Додати до черги + Додати до черги й відкрити чергу + Додати до черги й відтворити відео + Зовнішній завантажувач + Відкрити чергу + Черга + Видалити з черги + Вилучити з черги й відкрити чергу + Вилучити чергу + Зберегти чергу + "Замість відкривання зовнішнього завантажувача відкриває діалог керування чергою. + +Також можливо відкрити керування чергою натисканням та утриманням кнопки назад на панелі навігації. + +Ця функція все ще перебуває в процесі розробки, тому більшість функцій не працюють. + +Будь ласка, використовуйте її лише для налагодження." + Необхідна авторизація + Керування чергою недоступне (%s). + Не вдалося ідентифікувати список відтворення + Черга порожня + Не вдалося ідентифікувати відео + Не вдалося додати відео. + Не вдалося створити чергу. + Не вдалося видалити чергу. + Не вдалося вилучити відео. + Не вдалося зберегти чергу. + Відео додано успішно. + Чергу створено успішно. + Чергу видалено успішно. + Відео вилучено успішно. + Чергу збережено успішно до: \'%s\'. Мова RVX Мова додатка - Арабська - Азербайджанська - Болгарська - Бенгальська - Каталонська - Чеська - Данська - Німецька - Грецька - Англійська - Іспанська - Естонська - Перська - Фінська - Французька - Гуджаратська - Хінді - Хорватська - Угорська - Індонезійська - Італійська - Японська - Казахська - Корейська - Литовська - Латвійська - Македонська - Монгольська - Маратхі - Малайська - Бірманська - Голландська - Одія - Пенджабі - Польська - Португальська - Румунська - Російська - Словацька - Словенська - Сербська - Шведська - Суахілі - Тамільська - Телуґу - Тайська - Турецька - Українська - Урду - В’єтнамська - Китайська + "Амхарська +አማርኛ" + "Арабська +العربية" + "Азербайджанська +Azərbaycan" + "Білоруська +Беларуская" + "Болгарська +Български" + "Бенгальська +বাংলা" + "Каталонська +Català" + "Чеська +Čeština" + "Данська +Dansk" + "Німецька +Deutsch" + "Грецька +Ελληνικά" + "Англійська +English" + "Іспанська +Español" + "Естонська +Eesti" + "Перська +فارسی" + "Фінська +Suomi" + "Французька +Français" + "Гуджараті +ગુજરાતી" + "Іврит +עברי" + "Хінді +हिन्दी" + "Хорватська +Hrvatski" + "Угорська +Magyar" + "Індонезійська +Indonesia" + "Італійська +Italiano" + "Японська +日本語" + "Казахська +Қазақ тілі" + "Корейська +한국어" + "Литовська +Lietuvių" + "Латвійська +Latviešu" + "Македонська +Македонски" + "Монгольська +Монгол" + "Маратхі +मराठी" + "Малайська +Melayu" + "Бірманська +ဗမာ" + "Нідерландська +Nederlands" + "Одія +ଓଡ଼ିଆ" + "Пенджабі +ਪੰਜਾਬੀ" + "Польська +Polski" + "Португальська +Português" + "Румунська +Română" + "Російська +Русский" + "Словацька +Slovenčina" + "Албанська +Shqip" + "Словенська +Slovenščina" + "Сербська +Српски" + "Шведська +Svenska" + "Суахілі +Kiswahili" + "Тамільська +தமிழ்" + "Телуґу +తెలుగు" + "Тайська +ไทย" + "Турецька +Türkçe" + "Українська +Українська" + "Урду +اردو" + "В'єтнамська +Tiếng Việt" + "Китайська +中文" Реклама Приховати банер магазину на кінцевому екрані @@ -394,9 +485,6 @@ Вимкнути сплеш анімацію Сплеш анімацію вимкнено. Сплеш анімацію увімкнено. - Вимкнути напівпрозорість рядка стану - Рядок стану непрозорий. - Рядок стану непрозорий чи напівпрозорий. Увімкнути градієнт екрану завантаження Градієнт екрану завантаження увімкнено. Градієнт екрану завантаження вимкнено. @@ -430,6 +518,23 @@ • Shorts відкриваються у поточному плеєрі. • Стрічка організована за темами та каналами. • Опис відео не відкривається, якщо вимкнена функція 'Підробити дані трансляції'." + Вимкнути оновлення макета + Макет не оновлюватиметься сервером. + Макет оновлюватиметься сервером. + "Макет програми повертається до макета, який використовувався при першому встановленні. + +Деякі серверні макети можуть не повернутися. + +Зміни включають: +• Компоненти у випадаючому меню плеєра (чи пов'язані налаштування) можуть не працювати. +• Лічильники не анімовані. +• Використовується вкладку Бібліотека. +• Секція Музика опису відео може не працювати. +• Кнопка перемикання облікового запису може не з'являтися у вкладці Бібліотека. Використовуйте налаштування 'Увімкнути широку панель пошуку у вкладці Ви'." + Вимкнути напівпрозорість рядка стану + Рядок стану непрозорий. + Рядок стану непрозорий чи напівпрозорий. + Для прошивок деяких розробників Android 12+, увімкнення цієї функції може зробити панель навігації системи прозорою. Підробити версію програми Версію підроблено Версію не підроблено @@ -447,8 +552,10 @@ 18.33.40 - відновлення старої панелі дій Shorts 18.38.45 - Відновлення старої поведінки типової якості відео 18.48.39 - Виключає \'переглянуті\' та \'вподобані\' з оновлення в режимі реального часу + 19.01.34 - Вимикання взаємодії з описом відео 19.26.42 - Вимикання значка Каїр в панелі навігації та інструментів 19.33.37 - Відновлення старої висувної панелі швидкості відтворення + Недійсне підроблення версії програми: %s. Меню облікового запису Приховувати чи показувати елементи меню облікового запису і вкладки Ви. @@ -484,6 +591,9 @@ Перевизначити кнопку завантаження відео Кнопка завантаження відео відкриває зовнішній завантажувач. Кнопка завантаження відео відкриває вбудований завантажувач. + Керування чергою + Кнопка завантаження відео відкриває керування чергою. + Кнопка завантаження відео відкриває зовнішній завантажувач. Ім\'я пакета завантажувача списку відтворення Ім\'я пакета встановленого зовнішнього завантажувача, наприклад YTDLnis. @@ -537,7 +647,6 @@ Увімкнути напівпрозорість панелі навігації Панель навігації напівпрозора. Панель навігації непрозора. - У певних версіях YouTube цей параметр може зробити панель навігації прозорою або порушити макет у режимі PIP. Приховати панель навігації Панель навігації приховано. Панель навігації показується. @@ -1116,6 +1225,8 @@ Натисніть, щоб вимкнути звук поточного відео. Натисніть знову, щоб увімкнути. Показувати кнопку зовнішнього завантажувача Натисніть для запуску зовнішнього завантажувача + Керування чергою + Замість запуску зовнішнього завантажувача відкриває керування чергою. Показувати кнопку Діалог швидкості "Натисніть, щоб відкрити діалог швидкості. Натисніть і утримуйте, щоб скинути швидкість відтворення до 1.0x. Натисніть і утримуйте ще раз, щоб скинути швидкість назад до типової." @@ -1415,7 +1526,6 @@ "Спеціальні дії увімкнено у висувному меню. Застереження: -• Не працює, якщо версію програми підроблено на 18.49.37 чи нижче. • Не працює з прямими трансляціями." Спеціальні дії вимкнено у висловному меню. Увімкнути спеціальні дії в панелі інструментів @@ -1440,6 +1550,10 @@ Показувати меню Відкрити відео Меню Відкрити відео показується. Меню Відкрити відео приховано. + Діалог швидкості + Показувати меню Діалог швидкості + Меню Діалог швидкості показується. + Меню Діалог швидкості приховано. Стан повторення Показувати меню Стан повторення Меню Стан повторення показується. @@ -1483,10 +1597,14 @@ Нижнє значення жесту яскравості активує автояскравість. Нижнє значення жесту яскравості не активує автояскравість. Увімкнути зміну яскравості жестом - Зміну яскравості жестом увімкнено. + "Зміну яскравості жестом увімкнено. + +Регулюйте яскравість вертикальним проведенням по лівій стороні екрана." Зміну яскравості жестом вимкнено. Увімкнути зміну гучності жестом - Зміну гучності жестом увімкнено. + "Зміну гучності жестом увімкнено. + +Регулюйте гучність вертикальним проведенням по правій стороні екрана." Зміну гучності жестом вимкнено. Увімкнути збереження та відновлення яскравості Зберігається та відновлюється яскравість при переході з/до повноекранного режиму. @@ -1500,10 +1618,20 @@ Жести переміщення в режимі \'Блокування екрана\' Жести переміщення увімкнено в режимі \'Блокування екрана\'. Жести переміщення вимкнено в режимі \'Блокування екрана\'. - Видимість фону при жесті - Видимість фону панелі при жесті Поріг величини жесту Мінімальна амплітуда руху, що розпізнається як жест + Альтернативний інтерфейс жесту + Використовується альтернативний інтерфейс. + Використовується старий інтерфейс. + Увімкнути мінімалістичний стиль + Мінімалістичний стиль увімкнено. + Мінімалістичний стиль вимкнено. + Показувати круговий індикатор + Показується круговий індикатор. + Показується лінійний індикатор. + Затемнення фону панелі під час жесту + Значення затемнення в межах 0-100. + Затемнення при жесті повинно бути в межах 0-100. Розмір шрифту панелі Розмір шрифту в панелі при жесті Розмір екрана накладки проведення @@ -1535,12 +1663,43 @@ Авто Відео - Типова швидкість відтворення - Типова якість відео в мобільній мережі - Типова якість відео в Wi-Fi мережі + + Кодек Вимкнути HDR відео HDR відео вимкнено. HDR відео увімкнено. + Вимкнути кодек VP9 + "Кодек VP9 вимкнено. + +Інформація: +• Максимальна роздільна здатність - 1080p. +• Відтворення відео використовуватиме більше інтернет даних ніж VP9. +• Кодек VP9 все ще використовується для HDR відео." + Кодек VP9 увімкнено. + Замінити програмний кодек AV1 + Замінити програмний кодек AV1 кодеком VP9. + + Швидкість відтворення + Типова швидкість відтворення + Запам\'ятовувати зміни швидкості відтворення + Зміни швидкості відтворення застосовуються до всіх відео. + Зміни швидкості відтворення застосовуються лише до поточного відео. + Показувати тост + Тост показуватиметься при зміні типової швидкості відтворення. + Тост не показуватиметься при зміні типової швидкості відтворення. + Типова швидкість відтворення Shorts + Запам\'ятовувати зміни швидкості відтворення Shorts + "Зміни швидкості відтворення застосовуються до всіх Shorts. + +Інформація: +• Єдиним спосіб змінити швидкість відтворення у плеєрі Shorts є використання 'Діалог швидкості' у 'Спеціальні дії'. +• Якщо не включити патч 'Shorts components', це буде недоступно." + Зміни швидкості відтворення застосовуються лише до поточного Shorts. + Показувати тост + Тост показуватиметься при зміні типової швидкості відтворення Shorts. + Тост не показуватиметься при зміні типової швидкості відтворення Shorts. + Типову швидкість відтворення змінено на: %s. + Типову швидкість відтворення Shorts змінено на: %s. Увімкнути користувацьку швидкість відтворення Користувацьку швидкість відтворення увімкнено. Користувацьку швидкість відтворення вимкнено. @@ -1549,62 +1708,58 @@ Використовується висувне меню старого стилю. Редагувати користувацькі швидкості відтворення Додати або змінити доступні швидкості відтворення - Запам\'ятовувати зміни швидкості відтворення - Зміни швидкості відтворення застосовуються до всіх відео. - Зміни швидкості відтворення застосовуються лише до поточного відео. - Показувати тост - Тост показуватиметься при зміні типової швидкості відтворення. - Тост не показуватиметься при зміні типової швидкості відтворення. + Користувацькі швидкості повинні бути менше ніж %sx. + Неправильні користувацькі швидкості відтворення. + + Якість відео + Типова якість відео в мобільній мережі + Типова якість відео в Wi-Fi мережі Запам\'ятовувати зміни якості відео Зміни якості застосовуються до всіх відео. Зміни якості застосовуються лише до поточного відео. Показувати тост Тост показуватиметься при зміні типової якості відео. Тост не показуватиметься при зміні типової якості відео. + Типова якість Shorts в мобільній мережі + Типова якість Shorts в Wi-Fi мережі + Запам\'ятовувати зміни якості Shorts + Зміни якості застосовуються до всіх Shorts. + Зміни якості застосовуються лише до поточного Shorts. + Показувати тост + Тост показуватиметься при зміні типової якості Shorts. + Тост не показуватиметься при зміні типової якості Shorts. + мобільна + wifi + Зміна типової якості %1$s на: %2$s. + Зміна якості Shorts %1$s на: %2$s. Відновити старе меню якості відео Старе меню якості відео показується. Старе меню якості відео не показується. - Вимкнути швидкість відтворення для музики - Типову швидкість відтворення вимкнено для музики. - Типову швидкість відтворення увімкнено для музики. - Перевіряти за допомогою категорій - Типову швидкість відтворення вимкнено якщо категорія відео Музика. - Типову швидкість відтворення вимкнено для відтворення відео на YouTube Music. - Увімкнути типову швидкість відтворення Shorts - Типову швидкість відтворення застосовується для Shorts. - Типову швидкість відтворення не застосовується для Shorts. Пропущено перед вантажений буфер Пропустити перед вантажений буфер - "Пропуск перед вантаженого буфера під час запуску відео для обходу затримки примусового застосування типової якості відео. + "Пропуск передвантаженого буфера під час запуску відео для обходу затримки примусового застосування типової якості відео. -• Під час запуску відео є затримка приблизно 0.3 секунди, але типова якість відео застосовується відразу. -• Не застосовується на відео HDR, прямі трансляції, менші ніж 15 секунд." +Інформація: +• Під час запуску відео є затримка приблизно 0.3 секунди. +• Не застосовується на Shorts, відео HDR, прямі трансляції, менші ніж 15 секунд. +• Налаштування 'Підробити дані трансляції' також вилучає передвантажений буфер, отже це налаштування не потрібне якщо використовуєте те." Вмикання цього налаштування може спричинити проблеми відтворення відео. Показувати тост, коли пропущено Тост показується. Тост не показується. Підробити розміри пристрою "Підробка розмірів пристрою до максимального значення. -Високу якість може бути розблоковано для деяких відео, які вимагають великих розмірів пристрою, але не для всіх відео." - Вимкнути кодек VP9 - "Кодек VP9 вимкнено. -• Максимальна роздільна здатність - 1080p. -• Відтворення відео використовуватиме більше інтернет даних ніж VP9. -• Кодек VP9 все ще використовується для HDR відео." - Кодек VP9 увімкнено. - Замінити програмний кодек AV1 - Замінити програмний кодек AV1 кодеком VP9. - Відкинути відповідь програмного кодека AV1 - "Примусово відкидається відповідь програмного кодека AV1. -Приблизно через 20 секунд буферизації перемикається на інший кодек." - Процес спричиняє приблизно 20 секунд буферизації. - Зміна типової швидкості на %s. - Зміна типової якості при мобільному з\'єднанні на %s. - Не вдалося встановити якість відео. - Зміна типової якості при Wi-Fi на %s. - Користувацькі швидкості повинні бути менше ніж %sx. - Неправильні користувацькі швидкості відтворення. +Інформація: +• Високі якості можуть розблокуватися на деяких відео, які потребують великих розмірів пристрою, але не на всіх відео. +• Це налаштування недоступне якщо увімкнене 'Підробити дані трансляції'." + + Вимкнути швидкість відтворення для музики + Типову швидкість відтворення вимкнено для музики. + Типову швидкість відтворення увімкнено для музики. + Перевіряти за допомогою категорій + Типову швидкість відтворення вимкнено якщо категорія відео Музика. + Типову швидкість відтворення вимкнено для відтворення відео на YouTube Music. Повернення Дизлайків Ввімкнути повернення дизлайків YouTube @@ -1734,6 +1889,7 @@ Показувати кнопку пропуску Показувати в панелі прогресу Вимкнути + Непрозорість: Колір: Колір змінено Колір скинуто @@ -1800,6 +1956,8 @@ Проголосувати «проти» Змінити категорію Немає сегментів для голосування + + %1$s до %2$s Вибрати категорію сегмента Категорія вимкнена у налаштуваннях. Увімкніть категорію, щоб надіслати. Новий сегмент Спонсорблок @@ -1915,8 +2073,8 @@ Android VR "Android VR (Без авторизації)" - "iOS -(Потрібен PoToken)" + "iOS +(Застаріла)" "iOS TV (Потрібна авторизація)" Побічні ефекти імітування @@ -1924,10 +2082,20 @@ • Стабілізація гучності недоступна. • Вимкнення примусових авто звукових доріжок недоступне. • Відео для дітей можуть не відтворюватися коли вийшли з системи або в анонімному режимі." - • Можуть бути проблеми з відтворенням (Потрібен PoToken). + • Цілі діапазони ASN/IP можуть блокуватися сервером. "• Стабілізація гучності недоступна. • Фільми та платні відео можуть не відтворюватися. • Відео для дітей можуть не відтворюватися коли вийшли з системи або в анонімному режимі." + Використовувати клієнт iOS + "Клієнт iOS доданий до доступних клієнтів. + +ЗАУВАЖЕННЯ: Клієнт iOS застарілий. Будь-які проблеми, які виникають під час його використання, на власний ризик." + Клієнт iOS не доданий до доступних клієнтів. + "Під час запиту кінцевих точок YouTube API за допомогою iOS потрібні токени Auth, видані пристроєм iOS, і токени PoToken, видані iOSGuard. + +Це означає, що в запитах трансляції через iOS не буде ані токенів Auth, ані токенів PoToken, і сервер може вважати користувача ботом і заблокувати цілий діапазон ASN/IP. + +ВИКОРИСТОВУЙТЕ НА ВЛАСНИЙ РИЗИК!" Примусово AVC (H.264) iOS Примусово увімкнено відеокодек AVC (H.264). Відеокодек визначається автоматично. @@ -1946,6 +2114,8 @@ AVC має максимальну роздільну здатність 1080p, Клієнт, що використовується для отримання даних трансляції показується у Статистика для сисадмінів. Клієнт, що використовується для отримання даних трансляції приховано у Статистика для сисадмінів. Мова звукової доріжки для VR + Не вдалося отримати якісь трансляції клієнта. + Не можливо авторизуватися. PoToken / VisitorData PoToken для використання 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 67bdd30be..d272eece7 100644 --- a/patches/src/main/resources/youtube/translations/vi-rVN/strings.xml +++ b/patches/src/main/resources/youtube/translations/vi-rVN/strings.xml @@ -19,59 +19,150 @@ "Có vẻ như %1$s chưa được cài đặt. Vui lòng tải xuống %2$s từ trang web." Hiện %s chưa được cài đặt. Hãy cài đặt và thử lại. + Thêm vào hàng chờ + Thêm vào hàng chờ và xem hàng chờ + Thêm vào hàng chờ và phát video + Trình tải xuống bên ngoài + Xem hàng chờ + Hàng chờ + Xóa khỏi hàng chờ + Xóa khỏi hàng chờ và xem hàng chờ + Xoá hàng chờ + Lưu hàng chờ + "Thay vì mở trình tải xuống bên ngoài, khi tương tác sẽ mở hộp thoại quản lý hàng chờ. + +Ngoài ra, bạn cũng có thể mở hộp thoại quản lý hàng chờ bằng cách nhấn và giữ nút quay lại trên thanh điều hướng. + +Nhưng tính năng này vẫn còn đang trong quá trình phát triển, vì vậy phần lớn các tính năng có thể chưa hoạt động. + +Vui lòng chỉ sử dụng cho mục đích gỡ lỗi." + Yêu cầu đăng nhập + Quản lý hàng chờ không khả dụng (%s). + Không xác định được danh sách phát + Hàng chờ trống + Không xác định được video + Không thể thêm video. + Không thể tạo hàng chờ. + Không thể xoá hàng chờ. + Không thể xoá video. + Không thể lưu hàng chờ. + Thêm video thành công. + Tạo hàng chờ thành công. + Xoá hàng chờ thành công. + Xoá video thành công. + Lưu hàng chờ thành công thành \"%s\". Ngôn ngữ trong cài đặt RVX Theo ứng dụng - Tiếng Ả Rập - Tiếng Azerbaijan - Tiếng Bulgaria - Tiếng Bengal - Tiếng Catalan - Tiếng Séc - Tiếng Đan Mạch - Tiếng Đức - Tiếng Hy Lạp - Tiếng Anh - Tiếng Tây Ban Nha - Tiếng Estonia - Tiếng Ba Tư - Tiếng Phần Lan - Tiếng Pháp - Tiếng Gujarati - Tiếng Hindi - Tiếng Croatia - Tiếng Hungary - Tiếng Indonesia - Tiếng Ý - Tiếng Nhật - Tiếng Kazakh - Tiếng Hàn - Người Lithuania - Tiếng Latvia - Tiếng Macedonia - Tiếng Mông Cổ - Tiếng Marathi - Tiếng Mã Lai - Tiếng Miến Điện - Tiếng Hà Lan - Tiếng Odia - Tiếng Punjab - Tiếng Ba Lan - Tiếng Bồ Đào Nha - Tiếng Romania - Tiếng Nga - Tiếng Slovak - Tiếng Slovenia - Tiếng Serbia - Tiếng Thuỵ Điển - Tiếng Swahili - Tiếng Tamil - Tiếng Telugu - Tiếng Thái - Tiếng Thổ Nhĩ Kỳ - Tiếng Ukraina - Tiếng Urdu - Tiếng Việt - Tiếng Trung + "Amharic +አማርኛ" + "Arabic +العربية" + "Azerbaijani +Azərbaycan" + "Belarusian +беларуская" + "Bulgarian +Български" + "Bengali +বাংলা" + "Catalan +Català" + "Czech +Čeština" + "Danish +Dansk" + "German +Deutsch" + "Greek +Ελληνικά" + "English +English" + "Spanish +Español" + "Estonian +Eesti" + "Persian +فارسی" + "Finnish +Suomi" + "French +Français" + "Gujarati +ગુજરાતી" + "Hebrew +עברי" + "Hindi +हिन्दी" + "Croatian +Hrvatski" + "Hungarian +Magyar" + "Indonesian +Indonesia" + "Italian +Italiano" + "Japanese +日本語" + "Kazakh +Қазақ тілі" + "Korean +한국어" + "Lithuanian +Lietuvių" + "Latvian +Latviešu" + "Macedonian +Македонски" + "Mongolian +Монгол" + "Marathi +मराठी" + "Malay +Melayu" + "Burmese +ဗမာ" + "Dutch +Nederlands" + "Odia +ଓଡ଼ିଆ" + "Punjabi +ਪੰਜਾਬੀ" + "Polish +Polski" + "Portuguese +Português" + "Romanian +Română" + "Russian +Русский" + "Slovak +Slovenčina" + "Albanian +Shqip" + "Slovene +Slovenščina" + "Serbian +Српски" + "Swedish +Svenska" + "Swahili +Kiswahili" + "Tamil +தமிழ்" + "Telugu +తెలుగు" + "Thai +ไทย" + "Turkish +Türkçe" + "Ukrainian +Українська" + "Urdu +اردو" + "Vietnamese +Tiếng Việt" + "Chinese +中文" Quảng cáo Ẩn dải quảng cáo cửa hàng ở cuối video @@ -394,9 +485,6 @@ Hạn chế: Nút Quay lại trên thanh công cụ có thể không hoạt đ Tắt hoạt ảnh khi ứng dụng khởi chạy Hoạt ảnh khi ứng dụng khởi chạy đã tắt. Hoạt ảnh khi ứng dụng khởi chạy được bật. - Vô hiệu hoá thanh trạng thái trong suốt - Thanh trạng thái không còn trong suốt. - Thanh trạng thái có thể trong suốt hoặc không. Màn hình tải hiệu ứng gradient Màn hình tải hiệu ứng gradient đã bật. Màn hình tải hiệu ứng gradient đã tắt. @@ -429,7 +517,24 @@ Bố cục máy tính bảng Bố cục màn hình ô tô • Video ngắn sẽ được mở trong trình phát thông thường. • Bảng tin được sắp xếp theo chủ đề và kênh. -• Không thể mở mô tả video khi cài đặt \"Giả mạo luồng dữ liệu trực tuyến\" đang tắt." +• Không thể mở mô tả video khi cài đặt \"Giả mạo dữ liệu phát trực tuyến\" đang tắt." + Tắt cập nhật bố cục + Bố cục sẽ không được máy chủ cập nhật. + Bố cục sẽ được máy chủ cập nhật. + "Bố cục ứng dụng sẽ trở về như lần đầu bạn mở ứng dụng. + +Một số bố cục do máy chủ kiểm soát có thể không được hoàn nguyên. + +Các thay đổi bao gồm: +• Các mục trong trình đơn tuỳ chọn (hoặc các cài đặt liên quan) có thể không hoạt động. +• Hiệu ứng số cuộn không được hiển thị. +• Thẻ Thư viện được hiển thị thay vì thẻ Bạn. +• Phần Âm nhạc trong mô tả video có thể không hoạt động. +• Nút chuyển đổi tài khoản có thể không xuất hiện trong thẻ Thư viện. Hãy sử dụng tuỳ chọn \"Thanh tìm kiếm rộng trong thẻ Bạn\"." + Vô hiệu hoá thanh trạng thái trong suốt + Thanh trạng thái không còn trong suốt. + Thanh trạng thái có thể trong suốt hoặc không. + Trên một số ROM tùy chỉnh chạy Android 12+, bật tính năng này có thể làm cho thanh điều hướng hệ thống trở nên trong suốt. 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 @@ -447,8 +552,10 @@ Nếu muốn tắt tính năng này sau đó, bạn nên xóa dữ liệu ứng 18.33.40 - Khôi phục thanh thao tác trình Shorts kiểu cũ 18.38.45 - Khôi phục phương thức áp dụng chất lượng video mặc định kiểu cũ 18.48.39 - Vô hiệu hoá cập nhật số \"lượt xem\" và \"lượt thích\" theo thời gian thực + 19.01.34 - Tắt tương tác với mô tả video 19.26.42 - Tắt biểu tượng Cairo trong thanh điều hướng và thanh công cụ 19.33.37 - Khôi phục bảng trình đơn tốc độ phát kiểu cũ + Phiên bản ứng dụng đã chọn không hợp lệ: %s. Trình đơn Tài khoản Ẩn hoặc hiển thị các mục của trình đơn Tài khoản và thẻ Bạn. @@ -484,6 +591,9 @@ Một số mục có thể không bị ẩn." Ghi đè nút tải xuống video Khi thao tác với nút tải xuống video sẽ mở trình tải xuống bên ngoài của bạn. Khi thao tác với nút tải xuống video sẽ mở trình tải xuống được tích hợp sẵn của Youtube. + Quản lý hàng chờ + Khi tương tác với nút tải xuống video sẽ mở hộp thoại quản lý hàng chờ của bạn. + Khi tương tác với nút tải xuống video sẽ mở trình tải xuống bên ngoài của bạn. Tên gói trình tải xuống danh sách phát Chọn hoặc nhập tên gói ứng dụng trình tải xuống đã được cài đặt trên thiết bị của bạn, chẳng hạn như YTDLnis. @@ -537,7 +647,6 @@ 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ị. @@ -634,7 +743,7 @@ Bạn có thể đổi màu nền cho chúng, nhưng nếu phía máy chủ thay Thanh tìm kiếm rộng với tiêu đề YouTube Thanh tìm kiếm rộng được hiển thị cùng với tiêu đề YouTube. Thanh tìm kiếm rộng đã ẩn và làm ẩn tiêu đề YouTube. - Thanh tìm kiếm rộng trên thẻ Bạn + Thanh tìm kiếm rộng trong thẻ Bạn "Thanh tìm kiếm rộng được bật trong thẻ Bạn. Để vào cài đặt, vui lòng thao tác theo đường dẫn sau: @@ -1114,6 +1223,8 @@ Nhấn và giữ để sao chép dấu thời gian." Nhấn để tắt tiếng của video hiện tại. Nhấn lần nữa để bật trở lại. Nút Trình tải xuống bên ngoài Nhấn để khởi chạy trình tải xuống bên ngoài. + Quản lý hàng chờ + Thay vì mở trình tải xuống bên ngoài, khi tương tác sẽ mở hộp thoại quản lý hàng chờ. Nút Tốc độ phát "Nhấn để mở hộp thoại Tốc độ phát. Nhấn và giữ để đặt lại tốc độ phát video (1.0x). Nhấn và giữ lần nữa để đặt lại về tốc độ mặc định đã đặt." @@ -1408,10 +1519,9 @@ Chi tiết: Tác vụ tuỳ chỉnh Tác vụ tuỳ chỉnh trong trình đơn tuỳ chọn - "Tác vụ tuỳ chỉnh được bật trong trình đơn tuỳ chọn. + "Tác vụ tuỳ chỉnh đã được bật trong trình đơn tuỳ chọn. Hạn chế: -• Không hoạt động nếu phiên bản đang được giả mạo sang 18.49.37 hoặc cũ hơn nữa. • Không hoạt động đối với video phát trực tiếp." Tác vụ tuỳ chỉnh đã tắt trong trình đơn tuỳ chọn. Tác vụ tuỳ chỉnh trong thanh công cụ @@ -1436,6 +1546,10 @@ Nhấn và giữ nút Thêm (⋮) để hiển thị hộp thoại Tác vụ tu Mục mở video Mục mở video được hiển thị. Mục mở video đã ẩn. + Hộp thoại tốc độ phát + Mục hộp thoại tốc độ phát + Mục hộp thoại tốc độ phát được hiển thị. + Mục hộp thoại tốc độ phát đã ẩn. Trạng thái lặp lại Mục trạng thái lặp lại Mục trạng thái lặp lại được hiển thị. @@ -1479,10 +1593,14 @@ Không còn lề trên và dưới trong trình phát." Chế độ độ sáng tự động sẽ được bật khi vuốt độ sáng về mức tổi thiểu. Chế độ độ sáng tự động sẽ không được bật khi vuốt độ sáng về mức tổi thiểu. Vuốt điều chỉnh độ sáng - Cử chỉ vuốt điều chỉnh độ sáng đã bật. + "Cử chỉ vuốt điều chỉnh độ sáng ở chế độ Toàn màn hình đã bật. + + Điều chỉnh độ sáng bằng cách vuốt dọc bên cạnh trái màn hình." Cử chỉ vuốt điều chỉnh độ sáng đã tắt. Vuốt điều chỉnh âm lượng - Cử chỉ vuốt điều chỉnh âm lượng đã bật. + "Cử chỉ vuốt điều chỉnh âm lượng ở chế độ Toàn màn hình đã bật. + + Điều chỉnh âm lượng bằng cách vuốt dọc bên cạnh phải màn hình." Cử chỉ vuốt điều chỉnh âm lượng đã tắt. Lưu độ sáng Lưu độ sáng khi thoát ra hoặc vào chế độ toàn màn hình. @@ -1496,10 +1614,20 @@ Không còn lề trên và dưới trong trình phát." Vuốt ở chế độ Khoá màn hình Cử chỉ vuốt đã bật ở chế độ Khoá màn hình. Vuốt ở chế độ Khoá màn hình đã tắt. - Độ trong suốt lớp phủ - Độ trong suốt của nền khi thực hiện cử chỉ vuốt. Độ rộng ngưỡng vuốt Độ rộng của ngưỡng vuốt để thực hiện cử chỉ vuốt. + Giao diện thay thế lớp phủ vuốt + Đang sử dụng giao diện thay thế. + Đang sử dụng giao diện cũ. + Chế độ tối giản + Lớp phủ tối giản đã bật. + Lớp phủ tối giản đã tắt. + Lớp phủ hình tròn + Lớp phủ hình tròn được hiển thị. + Lớp ngủ ngang được hiển thị. + Độ mờ nền của lớp phủ vuốt + Độ mờ có thể điều chỉnh từ 0 đến 100. + Giá trị của độ mờ khi vuốt phải từ 0 đến 100. Kích thước văn bản trên lớp phủ Độ lớn của văn bản được hiển thị trên lớp phủ khi vuốt. Kích thước màn hình lớp phủ @@ -1531,12 +1659,43 @@ Không còn lề trên và dưới trong trình phát." Tự động Video - Tốc độ phát mặc định - Chất lượng video mặc định trên mạng di động - Chất lượng video mặc định trên mạng Wi-Fi + + Codec Tắt video HDR Video HDR đã tắt. Video HDR đã bật. + Vô hiệu hoá codec VP9 + "Codec VP9 đã bị vô hiệu hoá. + +Chi tiết: +• Độ phân giải tối đa lúc này là 1080p. +• Phát video sẽ sử dụng nhiều dữ liệu di động hơn so với VP9. +• Codec VP9 vẫn được sử dụng cho video HDR." + Codec VP9 được kích hoạt. + Thay thế codec AV1 phần mềm + Thay thế codec AV1 phần mềm bằng codec VP9. + + Tốc độ phát + Tốc độ phát mặc định + Lưu lựa chọn tốc độ phát + Lựa chọn tốc độ phát đã chọn sẽ áp dụng cho tất cả video. + Lựa chọn tốc độ phát đã chọn chỉ áp dụng cho video hiện tại. + Thông báo ngắn + Hiển thị một thông báo ngắn khi thay đổi tốc độ phát mặc định. + Không hiện một thông báo ngắn khi thay đổi tốc độ phát mặc định. + Tốc độ phát mặc định trên Shorts + Lưu lựa chọn tốc độ phát trên Shorts + "Lựa chọn tốc độ phát đã chọn sẽ áp dụng cho tất cả video Shorts. + +Chi tiết: +• Cách duy nhất để thay đổi tốc độ phát trong trình phát Shorts là dùng \"Hộp thoại tốc độ phát\" trong \"Tác vụ tùy chỉnh\". +• Nếu bạn không bao gồm bản vá \"Shorts components\", tùy chọn này sẽ không khả dụng." + Lựa chọn tốc độ phát đã chọn chỉ áp dụng cho video Shorts hiện tại. + Thông báo ngắn + Hiển thị một thông báo ngắn khi thay đổi tốc độ phát mặc định trên Shorts. + Không hiện một thông báo ngắn khi thay đổi tốc độ phát mặc định trên Shorts. + Đã thay đổi tốc độ phát mặc định thành: %s. + Đã thay đổi tốc độ phát mặc định trên Shorts thành: %s. Tốc độ phát tuỳ chỉnh Đang áp dụng các giá trị tốc độ phát video tuỳ chỉnh. Đang áp dụng các giá trị tốc độ phát video mặc định. @@ -1545,63 +1704,58 @@ Không còn lề trên và dưới trong trình phát." Mục tốc độ phát kiểu cũ được sử dụng. Chỉnh sửa tốc độ phát Thêm hoặc thay đổi tốc độ phát lại có sẵn. - Lưu lựa chọn tốc độ phát - Lựa chọn tốc độ phát đã chọn sẽ áp dụng cho tất cả video. - Lựa chọn tốc độ phát đã chọn chỉ áp dụng cho video hiện tại. - Thông báo ngắn - Hiển thị một thông báo ngắn khi thay đổi tốc độ phát mặc định. - Không hiện một thông báo ngắn khi thay đổi tốc độ phát mặc định. + Tốc độ tùy chỉnh phải nhỏ hơn %sx. + Tốc độ phát tùy chỉnh không hợp lệ. + + Chất lượng video + Chất lượng video mặc định trên mạng di động + Chất lượng video mặc định trên mạng Wi-Fi Lưu lựa chọn chất lượng video Lựa chọn chất lượng video đã chọn sẽ áp dụng cho tất cả video. Lựa chọn chất lượng video đã chọn chỉ áp dụng cho video hiện tại. Thông báo ngắn Hiển thị một thông báo ngắn khi thay đổi chất lượng video mặc định. Không hiện một thông báo ngắn khi thay đổi chất lượng video mặc định. + Chất lượng video Shorts mặc định trên mạng di động + Chất lượng video Shorts mặc định trên mạng Wi-Fi + Lưu lựa chọn chất lượng video Shorts + Lựa chọn chất lượng video đã chọn sẽ áp dụng cho tất cả video Shorts. + Lựa chọn chất lượng video đã chọn chỉ áp dụng cho video Shorts hiện tại. + Thông báo ngắn + Hiển thị một thông báo ngắn khi thay đổi chất lượng video Shorts mặc định. + Không hiện một thông báo ngắn khi thay đổi chất lượng video Shorts mặc định. + mạng di động + wi-fi + Đã thay đổi chất lượng mặc định %1$s thành: %2$s. + Đã thay đổi chất lượng Shorts mặc định %1$s thành: %2$s. Khôi phục mục chất lượng video kiểu cũ Mục chất lượng video kiểu cũ được hiển thị. Mục chất lượng video kiểu cũ không được hiển thị. - Tắt tùy chọn tốc độ phát khi phát nhạc - Tốc độ phát mặc định đã tắt khi phát nhạc. - Tốc độ phát mặc định được kích hoạt khi phát nhạc. - Dựa vào danh mục - Tốc độ phát mặc định đã tắt nếu video thuộc danh mục Âm nhạc. - Tốc độ phát mặc định đã tắt đối với các video chơi game trên Youtube Music. - Tốc độ phát mặc định cho video ngắn - Tốc độ phát mặc định đã đặt đang được áp dụng khi xem Shorts. - Tốc độ phát mặc định đã đặt không áp dụng cho Shorts. Đã bỏ qua bộ đệm tải trước. Bỏ qua bộ đệm tải trước "Bỏ qua bộ đệm tải trước ở đầu video để áp dụng ngay chất lượng video mặc định. Chi tiết: • Khi video bắt đầu, sẽ có độ trễ khoảng 0,3 giây. -• Không áp dụng cho video HDR, video phát trực tiếp, hoặc video ngắn hơn 15 giây." +• Không áp dụng cho Shorts, video HDR, video phát trực tiếp, hoặc video ngắn hơn 15 giây. +• Cài đặt \"Giả mạo dữ liệu phát trực tuyến\" cũng xoá bộ đệm tải trước, do đó bạn không cần thiết phải bật cả hai." Bật cài đặt này có thể gây ra sự cố phát video. Thông báo ngắn khi bỏ qua Thông báo ngắn được hiển thị. Thông báo ngắn đã ẩn. Giả mạo kích thước thiết bị - "Giả lập kích thước thiết bị đến giá trị tối đa. -Chất lượng cao có thể được mở khóa trên một số video yêu cầu kích thước thiết bị lớn, nhưng không phải tất cả các video." - Vô hiệu hoá codec VP9 - "Codec VP9 đã vô hiệu hoá. + "Giả mạo kích thước thiết bị đến giá trị tối đa. -• Độ phân giải tối đa lúc này là 1080p. -• Phát video sẽ sử dụng nhiều dữ liệu di động hơn so với VP9. -• Codec VP9 vẫn được sử dụng cho video HDR." - Codec VP9 được kích hoạt. - Thay thế codec AV1 phần mềm - Thay thế codec AV1 phần mềm bằng codec VP9. - Từ chối phản hồi codec AV1 phần mềm - "Buộc từ chối phản hồi codec AV1 phần mềm. -Một codec khác sẽ được áp dụng sau khoảng 20 giây tải bộ đệm." - Quá trình dự phòng khiến việc tải bộ đệm mất khoảng 20 giây trước khi bắt đầu. - Đã lưu lựa chọn tốc độ phát mặc định thành %s. - Đã lưu lựa chọn chất lượng video mặc định khi sử dụng dữ liệu di động thành %s. - Thay đổi lựa chọn chất lượng video thất bại. - Đã lưu lựa chọn chất lượng video mặc định khi sử dụng Wi-Fi thành %s. - Tốc độ tùy chỉnh phải nhỏ hơn %sx. - Tốc độ phát tùy chỉnh không hợp lệ. +Chi tiết: +• Có thể mở khóa chất lượng cao cho một số video yêu cầu thiết bị có màn hình lớn, nhưng không phải tất cả. +• Cài đặt này sẽ không khả dụng nếu \"Giả mạo dữ liệu phát trực tuyến\" đang được bật." + + Tắt tùy chọn tốc độ phát khi phát nhạc + Tốc độ phát mặc định đã tắt khi phát nhạc. + Tốc độ phát mặc định được kích hoạt khi phát nhạc. + Dựa vào danh mục + Tốc độ phát mặc định đã tắt nếu video thuộc danh mục Âm nhạc. + Tốc độ phát mặc định đã tắt đối với các video chơi game trên Youtube Music. Return YouTube Dislike Hiện số lượt không thích @@ -1731,6 +1885,7 @@ Nhấp vào đây để xem các bước phát hành khóa API." Hiển thị nút Bỏ qua Hiển thị trong thanh tiến trình Tắt + Độ mờ: Màu: Đã thay đổi màu. Đặt lại màu @@ -1797,6 +1952,8 @@ Nhấp vào đây để xem các bước phát hành khóa API." Phản đối Đổi danh mục Không có phân đoạn nào để bình chọn. + + %1$s đến %2$s Chọn danh mục phân đoạn Danh mục đã tắt trong cài đặt. Bật danh mục để gửi. Đoạn SponsorBlock mới @@ -1904,11 +2061,11 @@ Nhấn vào Tiếp tục và cho phép thay đổi lựa chọn tối ưu hoá p Đặt lại Đã sao chép cài đặt sang bảng nhớ tạm. - Giả mạo luồng dữ liệu trực tuyến - Giả mạo luồng dữ liệu trực tuyến để khắc phục sự cố phát video. - Giả mạo luồng dữ liệu trực tuyến - Luồng dữ liệu trực tuyến được giả mạo. - "Luồng dữ liệu trực tuyến chưa được giả mạo. Khi phát video có thể gặp sự cố đứng hình." + Giả mạo dữ liệu phát trực tuyến + Giả mạo dữ liệu phát trực tuyến để khắc phục sự cố phát video. + Giả mạo dữ liệu phát trực tuyến + Dữ liệu phát trực tuyến được giả mạo. + "Dữ liệu phát trực tuyến chưa được giả mạo. Khi phát video có thể gặp sự cố đứng hình." Tắt cài đặt này có thể gây ra sự cố phát video. Ứng dụng khách mặc định "Android TV @@ -1916,8 +2073,8 @@ Nhấn vào Tiếp tục và cho phép thay đổi lựa chọn tối ưu hoá p Android VR "Android VR (Không xác thực)" - "iOS -(Yêu cầu PoToken)" + "iOS +(Không khuyến khích)" "iOS TV (Yêu cầu Đăng nhập)" Hạn chế @@ -1925,10 +2082,20 @@ 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). + • Máy chủ có thể chặn toàn bộ dải ASN/IP. "• Âm lượng ổn định không khả dụng. • 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." + Ứng dụng khách iOS + "Ứng dụng khách iOS được thêm vào danh sách ứng dụng khách có sẵn. + +CẢNH BÁO: Ứng dụng khách iOS đã không còn được hỗ trợ. Mọi sự cố phát sinh khi sử dụng đều do bạn tự chịu trách nhiệm." + Ứng dụng khách iOS không được thêm vào danh sách ứng dụng khách có sẵn. + "Khi yêu cầu các điểm cuối YouTube API bằng iOS, bạn sẽ cần các mã xác thực do thiết bị iOS và PoTokens do iOSGuard cung cấp. + +Điều này có nghĩa là các yêu cầu phát trực tuyến qua iOS sẽ bị thiếu cả mã xác thực và PoTokens, khiến máy chủ có thể coi người dùng là bot cũng như chặn toàn bộ dải ASN/IP. + +HÃY CÂN NHẮC RỦI RO TRƯỚC KHI SỬ DỤNG!" Bắt buộc iOS sử dụng AVC (H.264) Codec video luôn là AVC (H.264). Codec video được xác định tự động. @@ -1947,9 +2114,11 @@ Hạn chế: • Một số người dùng có thể gặp phải sự cố phát mới." Hiển thị trong Thống kê chi tiết - Ứng dụng khách sử dụng để nạp luồng dữ liệu trực tuyến được hiển thị trong Thống kê chi tiết. - Ứng dụng khách sử dụng để nạp luồng dữ liệu trực tuyến đã ẩn trong Thống kê chi tiết. + Ứng dụng khách sử dụng để nạp dữ liệu phát trực tuyến được hiển thị trong Thống kê chi tiết. + Ứng dụng khách sử dụng để nạp dữ liệu phát trực tuyến đã ẩn trong Thống kê chi tiết. Ngôn ngữ luồng âm thanh mặc định cho ứng dụng khách VR + Không thể nạp dữ liệu luồng từ ứng dụng khách. + Có thể bạn chưa đăng nhập. PoToken/VisitorData PoToken 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 76505ebc0..a77aecfd8 100644 --- a/patches/src/main/resources/youtube/translations/zh-rCN/strings.xml +++ b/patches/src/main/resources/youtube/translations/zh-rCN/strings.xml @@ -20,57 +20,6 @@ %s 未安装,请先安装它。 RVX 语言 应用程序语言 - 阿拉伯语 - 阿塞拜疆语 - 保加利亚语 - 孟加拉语 - 加泰罗尼亚语 - 捷克语 - 丹麦语 - 德语 - 希腊语 - 英语 - 西班牙语 - 爱沙尼亚语 - 波斯语 - 芬兰语 - 法语 - 古吉拉特语 - 印地语 - 克罗地亚语 - 匈牙利语 - 印度尼西亚语 - 意大利语 - 日语 - 哈萨克语 - 韩语 - 立陶宛语 - 拉脱维亚语 - 马其顿语 - 蒙古语 - 马拉提语 - 马来西亚语 - 缅甸语 - 荷兰语 - 奥迪亚语 - 旁遮普语 - 波兰语 - 葡萄牙语 - 罗马尼亚语 - 俄语 - 斯洛伐克文 - 斯洛文尼亚语 - 塞尔维亚语 - 瑞典语 - 斯瓦希里语 - 泰米尔语 - 泰卢固语 - 泰语 - 土耳其语 - 乌克兰语 - 乌尔都语 - 越南语 - 中文 广告 隐藏片尾商店横幅 @@ -1259,10 +1208,10 @@ 亮度手势的最低值会启用自动亮度调节 亮度手势的最低值不会启用自动亮度调节 启用滑动控制亮度 - 滑动控制亮度已启用 + "滑动控制亮度已启用" 滑动控制亮度已禁用 音量手势 - 滑动控制音量已启用 + "滑动控制音量已启用" 滑动控制音量已禁用 启用保存和恢复亮度 退出/进入全屏时保存和恢复亮度 @@ -1276,8 +1225,6 @@ 在“锁定屏幕”模式下滑动手势 在“锁定屏幕”模式下启用滑动手势 在“锁定屏幕”模式下禁用滑动手势 - 滑动背景透明度 - 滑动叠加层背景透明度 滑动幅度阈值 防误触的滑动幅度阈值 滑动叠加层上的文本大小 @@ -1299,12 +1246,27 @@ 自动 视频 - 默认播放速度 - 移动网络的默认视频画质 - WiFi 网络的默认视频画质 + HDR 视频 HDR 视频已禁用 HDR 视频已启用 + 禁用 VP9 编解码器 + "VP9 编解码器已禁用 + +• 最大分辨率是 1080p +• 视频播放将使用比 VP9 更多的网络数据 +要获取 HDR 播放,HDR 视频仍使用VP9 编解码器" + VP9 编解码器已启用 + 替换软件 AV1 编解码器 + 使用 VP9 编解码器替换软件 AV1 编解码器 + + 默认播放速度 + 记住播放速度更改 + 播放速度更改适用于所有视频 + 播放速度更改仅适用于当前视频 + 显示 Toast + 更改默认播放速度时,显示 Toast + 更改默认播放速度时,不显示 Toast 启用自定义播放速度 自定义播放速度已启用 自定义播放速度已禁用 @@ -1313,12 +1275,11 @@ 使用旧版弹出菜单 编辑自定义播放速度 添加或更改播放速度 - 记住播放速度更改 - 播放速度更改适用于所有视频 - 播放速度更改仅适用于当前视频 - 显示 Toast - 更改默认播放速度时,显示 Toast - 更改默认播放速度时,不显示 Toast + 自定义播放速度必须小于 %sx,已重置为默认值 + 无效的自定义播放速度将使用默认值 + + 移动网络的默认视频画质 + WiFi 网络的默认视频画质 记住视频画质更改 画质更改适用于所有视频 画质更改仅适用于当前视频 @@ -1328,11 +1289,6 @@ 恢复旧的视频画质菜单 显示旧的视频画质菜单 不显示旧的视频画质菜单 - 禁用音乐播放速度 - 音乐默认播放速度已启用 - 启用 Shorts 默认播放速度 - 默认播放速度适用于 Shorts - 默认播放速度不适用于 Shorts 跳过预加载缓冲 跳过预加载缓冲 "视频播放开始时,跳过预加载缓冲的默认视频画质 @@ -1344,25 +1300,9 @@ 提示信息已显示 提示信息已隐藏 伪装设备尺寸 - "伪装设备尺寸,以解锁设备可能无法提供的更高视频质量" - 禁用 VP9 编解码器 - "VP9 编解码器已禁用 - -• 最大分辨率是 1080p -• 视频播放将使用比 VP9 更多的网络数据 -要获取 HDR 播放,HDR 视频仍使用VP9 编解码器" - VP9 编解码器已启用 - 替换软件 AV1 编解码器 - 使用 VP9 编解码器替换软件 AV1 编解码器 - 拒绝软件 AV1 编解码器切换 - "强制拒绝软件 AV1 编解码器响应约 20 秒缓冲后,切换到其他编解码器" - 切换过程会导致约 20 秒缓冲 - 将默认速度更改为 %s - 将默认移动数据画质更改为 %s - 无法设置视频画质 - 将默认 WiFi 画质更改为 %s - 自定义播放速度必须小于 %sx,已重置为默认值 - 无效的自定义播放速度将使用默认值 + + 禁用音乐播放速度 + 音乐默认播放速度已启用 恢复 YouTube 点踩 恢复 YouTube 点踩 @@ -1558,6 +1498,7 @@ 反对 更改类别 没有可供投票的片段 + 选择片段类别 类别在设置中被禁用请启用类别以提交 新的 SponsorBlock 片段 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 4c6d7ebc5..290bfe2cc 100644 --- a/patches/src/main/resources/youtube/translations/zh-rTW/strings.xml +++ b/patches/src/main/resources/youtube/translations/zh-rTW/strings.xml @@ -1,11 +1,11 @@ - 是否啟用影片播放器的無障礙控制? + 啟用影片播放器的無障礙控制功能? 由於已啟用無障礙服務,因此你的控制選項已被修改。 RVX - 搜尋設定 + 搜尋 %s 重設為預設值。 實驗性功能 你想繼續嗎? @@ -19,59 +19,150 @@ "未安裝 %1$s。 請從網站下載 %2$s。" 未安裝 %s。請安裝該應用程式。 + 加入佇列清單 + 加到佇列並打開佇列 + 加入佇列並播放影片 + 外部下載器 + 開啟佇列 + 佇列 + 從佇列中移除 + 從佇列中移除並打開佇列 + 移除佇列 + 儲存佇列 + "不要開啟外部下載器,而是開啟佇列管理器對話方塊。 + +您也可以按住導覽列上的返回按鈕來開啟佇列管理器。 + +此功能仍在開發中,因此大多數功能可能無法使用。 + +請僅將其用於調試目的。" + 需要登入 + 佇列管理器不可用 (%s)。 + 無法辨識播放列表 + 佇列為空 + 無法辨識影片 + 新增影片失敗。 + 無法建立佇列。 + 刪除佇列失敗。 + 刪除影片失敗。 + 保存佇列失敗。 + 影片添加成功。 + 佇列已成功建立。 + 佇列已成功刪除。 + 影片已成功移除。 + 佇列已成功儲存至 ‘%s’。 RVX 語言 應用程式語言 - 阿拉伯文 - 亞塞拜然文 - 保加利亞文 - 孟加拉文 - 加泰隆尼亞文 - 捷克文 - 丹麥文 - 德文 - 希臘文 - 英文 - 西班牙文 - 愛沙尼亞文 - 波斯文 - 芬蘭文 - 法文 - 古吉拉特文 - 印地文 - 克羅埃西亞文 - 匈牙利文 - 印尼文 - 義大利文 - 日文 - 哈薩克文 - 韓文 - 立陶宛文 - 拉脫維亞文 - 馬其頓文 - 蒙古文 - 馬拉提文 - 馬來文 - 緬甸文 - 荷蘭文 - 歐迪亞語 - 旁遮普語 - 波蘭語 - 葡萄牙語 - 羅馬尼亞語 - 俄語 - 斯洛伐克語 - 斯洛維尼亞語 - 塞爾維亞語 - 瑞典語 - 史瓦希利語 - 坦米爾語 - 泰盧固語 - 泰語 - 土耳其語 - 烏克蘭語 - 烏爾都語 - 越南語 - 中文 + "阿姆哈拉語 +አማርኛ" + "阿拉伯語 +العربية" + "亞塞拜然語 +Azərbaycan" + "白俄羅斯語 +беларуская" + "保加利亞語 +Български" + "孟加拉语 +বাংলা" + "加泰隆尼亞語 +Català" + "捷克語 +Čeština" + "丹麥語 +Dansk" + "德語 +Deutsch" + "希臘語 +Ελληνικά" + "英語 +English" + "西班牙語 +Español" + "愛沙尼亞語 +Eesti" + "波斯語 +فارسی" + "芬蘭語 +Suomi" + "法語 +Français" + "古吉拉特語 +ગુજરાતી" + "希伯來語 +עברי" + "印地語 +हिन्दी" + "克羅埃西亞語 +Hrvatski" + "匈牙利語 +Magyar" + "印尼語 +Indonesia" + "義大利語 +Italiano" + "日語 +日本語" + "哈薩克語 +Қазақ тілі" + "韓語 +한국어" + "立陶宛語 +Lietuvių" + "拉脫維亞語 +Latviešu" + "馬其頓語 +Македонски" + "蒙語 +Монгол" + "馬拉地語 +मराठी" + "馬來語 +Melayu" + "緬甸語 +ဗမာ" + "荷蘭語 +Nederlands" + "奧迪亞語 +ଓଡ଼ିଆ" + "旁遮普語 +ਪੰਜਾਬੀ" + "波蘭語 +Polski" + "葡萄牙語 +Português" + "羅馬尼亞語 +Română" + "俄語 +Русский" + "斯洛伐克語 +Slovenčina" + "阿爾巴尼亞語 +Shqip" + "斯洛維尼亞語 +Slovenščina" + "塞爾維亞語 +Српски" + "瑞典語 +Svenska" + "斯瓦希里語 +Kiswahili" + "泰米爾語 +தமிழ்" + "泰盧固語 +తెలుగు" + "泰語 +ไทย" + "土耳其語 +Türkçe" + "烏克蘭語 +Українська" + "烏爾都語 +اردو" + "越南語 +Tiếng Việt" + "中文 +中文" 廣告 隱藏片尾商店橫幅 @@ -224,9 +315,9 @@ 隱藏相關影片 在相關影片中隱藏 在相關影片中顯示 - 在搜尋結果中隱藏 - 搜尋結果中已隱藏 - 在搜尋結果中顯示 + 在搜尋結果中隱蔵 + 隱藏在搜尋結果中。 + 顯示在搜尋結果中。 頻道簡介 隱藏或顯示頻道簡介中的元件 @@ -235,7 +326,7 @@ 頻道標籤篩選器已停用 頻道標籤篩選器 要篩選的頻道標籤名稱清單,每行一個名稱 - "Shorts + "短影音 播放清單 商店" 隱藏頻道會員清單 @@ -291,15 +382,18 @@ 訂閱影片的關鍵字篩選器已啟用 訂閱影片的關鍵字篩選器已停用 要隱藏的關鍵詞 - "需要隱藏的關鍵字和詞組,請用新行分隔。 -具有中間大寫字母的單詞必須依照大小寫輸入(例如:iPhone、TikTok、LeBlanc)。" + "要隱藏的關鍵字和短語,用換行符分隔。 + +關鍵字可以是頻道名稱或影片標題中顯示的任何文字。 + +中間有大寫字母的詞必須保持原有大小寫(例如:iPhone, TikTok, LeBlanc)。" 關於關鍵詞篩選 - "搜尋、首頁、訂閱和留言會篩選隱藏包含關鍵字詞的內容 + "搜尋、首頁、訂閱和留言會篩選隱藏包含關鍵字詞的內容。 限制: - • 部分 Shorts 可能不會隱藏 - • 部分介面元件可能不會隱藏 - • 搜尋關鍵字可能不會顯示任何結果" + • 短影音無法透過頻道名稱隱藏。 + • 部分介面元件可能無法隱藏。 + • 搜尋關鍵字可能不會顯示任何結果。" 匹配整個單字 用雙引號將關鍵字/短語括起來將防止影片標題和頻道名稱部分匹配。<br></b>例如,<br><b>\"ai\"</b>將隱藏影片:<b>人工智慧是如何運作的?</b><br> 但不會隱藏: <b>合理使用是什麼意思?</b> 無效的關鍵字。不能使用:「%s」作為篩選器 @@ -335,11 +429,11 @@ 指定你的語言模板來修飾使用者。中每個影片下顯示的播放量。每個關鍵字(在你的語言中的一個字/詞) -> 值(關鍵字的含義)必須單獨一行。在「->」符號之前是關鍵字。如果更改應用程序或系統語言,則需要重置此設定。\n\n示範:\n英語:10K 觀看數 = K -> 1000,觀看數 -> 觀看數\n西班牙文:10 K 觀看數 = K -> 1000,觀看數 -> 觀看數 千 -> 1 000\n百萬 -> 1 000 000\n十億 -> 1 000 000 000\n觀看數 -> 觀看數 關於觀看次數篩選 - "主頁/訂閱/搜尋結果將被過濾以隱藏觀看次數小於或大於指定數量的影片。 + "主頁/訂閱/搜尋結果將被篩選以隱藏觀看次數小於或大於指定數量的影片。 限制: -• 短影片無法隱藏。 -• 觀看次數為0的影片不會被曬選。" +• 短影音無法隱藏。 +• 觀看次數為零的影片不會被篩選。" 隱藏相關影片 相關影片已隱藏。 相關影片已顯示。 @@ -369,7 +463,7 @@ 播客 搜尋 購物 - 短片 + 短影音 運動 訂閱 熱門 @@ -390,9 +484,6 @@ 停用啟動動畫 啟動動畫已停用 啟動動畫已啟用 - 停用半透明狀態列 - 狀態列不透明。 - 狀態列是不透明或半透明的。 啟用漸變載入畫面 漸變載入畫面已啟用 漸變載入畫面已停用 @@ -408,6 +499,39 @@ 變更點擊直播狀態頻道圖示的動作 "點擊直播按鈕 頻道將會開啟。" 點擊直播按鈕 直播將會開啟。 + 版面配置形式 + 預設 + 手機 + 手機 (最大 480 dp) + 平板 + 平板電腦 (最少 600 dp) + 車用 + "改變包括: + +平板佈局 +• 社群貼文被隱藏。 + +車用佈局 +• 常規播放器中的短影音開啟。 +• 資訊流依主題和頻道進行組織。 +• 關閉「偽裝串流數據」時無法開啟影片說明。" + 禁用佈局更新 + 伺服器不會更新佈局。 + 佈局將由伺服器更新。 + "應用程式佈局恢復為首次安裝時使用的佈局。 + +某些伺服器端佈局可能無法復原。 + +變化包括: +• 播放器彈出式選單中的元件(或相關設定)可能無法正常運作。 +• 滾動數字不是動畫。 +• 已使用庫標籤頁。 +• 影片描述的音樂部分可能無法運作。 +• 帳戶切換按鈕可能不會出現在庫標籤頁上。使用「在您的標籤頁中啟用寬搜尋列」設定。" + 停用半透明狀態列 + 狀態列不透明。 + 狀態列是不透明或半透明的。 + 對於一些運行 Android 12 以上版本的製造商 ROM,啟用此功能可能會使系統導覽列變得透明。 偽裝應用程式版本 已偽裝版本 未偽裝版本 @@ -416,17 +540,19 @@ 這將改變應用程式的外觀和功能,但可能會出現未知的副作用。 如果稍後關閉,建議清除應用程式資料以防止 UI 錯誤。" - 編輯欺騙應用程式版本 - 輸入欺騙的應用程式版本目標 - 欺騙應用程式版本目標 + 編輯偽裝應用程式版本 + 輸入偽裝的應用程式版本目標。 + 偽裝應用程式版本目標 17.41.37 - 恢復舊版的播放清單 18.05.40 - 恢復舊版留言輸入方塊 18.17.43 - 恢復舊版播放器彈出式面板 - 18.33.40 - 恢復舊的 Shorts 頁籤 + 18.33.40 - 恢復舊版短影音操作欄 18.38.45 - 恢復舊版預設影片畫質 18.48.39 - 停用即時更新「觀看次數」和「喜歡次數」 + 19.01.34 - 禁用影片描述互動 19.26.42 - 停用導航和工具列中的開羅圖標 19.33.37 - 恢復舊的播放速度彈出面板 + 無效的偽裝應用程式版本: %s。 帳戶選單 隱藏或顯示帳戶選單和你的內容分頁中的元素。 @@ -462,6 +588,9 @@ 覆蓋影片下載按鈕 原生影片下載按鈕可開啟你的外部下載器。 原生影片下載按鈕可開啟本機應用程式內下載器。 + 佇列管理器 + 原生影片下載按鈕開啟佇列管理器。 + 原生影片下載按鈕開啟您的外部下載器。 播放清單下載器套件名稱 你安裝的外部下載器應用程式的套件名稱,例如 YTDLnis。 @@ -493,9 +622,9 @@ 隱藏通知按鈕 通知按鈕已隱藏 通知按鈕已顯示 - 隱藏 Shorts 按鈕 - Shorts 按鈕已隱藏 - Shorts 按鈕已顯示 + 隱藏短影音按鈕 + 短影音按鈕已隱藏 + 短影音按鈕已顯示 隱藏訂閱按鈕 訂閱按鈕已隱藏 訂閱按鈕已顯示 @@ -515,7 +644,6 @@ 啟用半透明導覽列 導覽列是半透明的。 導覽列不透明。 - 在某些 YouTube 版本中,此設定可以使系統導覽列透明,或在 PIP 模式下可以破壞佈局。 隱藏導覽列 導覽列已隱藏。 導覽列已顯示。 @@ -733,18 +861,18 @@ 操作按鈕 隱藏或顯示影片下方的操作按鈕 - 停用讚和反讚按鈕發光 - 讚和反讚按鈕在提及時不會發光。 - 讚和反讚按鈕在提及時會發光。 + 停用讚和不喜歡按鈕高亮顯示 + 讚和不喜歡按鈕在提及時不會高亮顯示。 + 讚和不喜歡按鈕在提及時會高亮顯示。 隱藏剪輯按鈕 剪輯按鈕已隱藏 剪輯按鈕已顯示 隱藏下載按鈕 隱藏下載按鈕 顯示下載按鈕 - 隱藏讚和踩按鈕 - 隱藏讚和踩按鈕 - 顯示讚和踩按鈕 + 隱藏讚和不喜歡按鈕 + 隱藏讚和不喜歡按鈕。 + 顯示讚和不喜歡按鈕。 隱藏混剪按鈕 混剪按鈕已隱藏 混剪按鈕已顯示 @@ -815,9 +943,9 @@ 隱藏首頁動態中的評論部分 首頁動態中評論部分已隱藏 首頁動態中評論部分已顯示 - 隱藏創建短片按鈕 - 創建短片按鈕已隱藏 - 創建短片按鈕已顯示 + 隱藏建立短影音按鈕 + 建立短影音按鈕已隱藏 + 建立短影音按鈕已顯示 隱藏時間戳和表情按鈕 時間戳和表情按鈕已隱藏 時間戳和表情按鈕已顯示 @@ -825,8 +953,8 @@ 被標記的搜尋連結已隱藏 被標記的搜尋連結已顯示 隱藏即時聊天訊息 - 即時聊天訊息已隱藏。\n\n這項設定也適用於 Shorts 直播影片。 - 即時聊天訊息已顯示。\n\n這項設定也適用於 Shorts 直播影片。 + 直播聊天訊息已隱藏。\n\n這項設定也適用於短影音直播影片。 + 直播聊天訊息已顯示。\n\n這項設定也適用於短影音直播影片。 在即時聊天中隱藏聊天摘要 聊天摘要已隱藏。 聊天摘要已顯示。 @@ -944,12 +1072,12 @@ 隱藏評論按鈕 評論按鈕已隱藏 評論按鈕已顯示 - 隱藏倒讚按鈕 - 倒讚按鈕已隱藏 - 倒讚按鈕已顯示 - 隱藏點贊按鈕 - 點讚按鈕已隱藏 - 點讚按鈕已顯示 + 隱藏不喜歡按鈕 + 不喜歡按鈕已隱藏。 + 不喜歡按鈕已顯示。 + 隱藏點讚按鈕 + 點讚按鈕已隱藏。 + 點讚按鈕已顯示。 隱藏實時聊天按鈕 實時聊天按鈕已隱藏 實時聊天按鈕已顯示 @@ -1042,7 +1170,7 @@ "按鈕被隱藏。 滑動即可展開或關閉。" - 顯示展開和關閉按鈕。 + 顯示擴展和關閉按鈕。 隱藏快轉和倒轉按鈕 快轉和倒轉按鈕已隱藏 快轉和倒轉按鈕已顯示 @@ -1094,6 +1222,8 @@ 點選可將目前影片靜音。 再次點擊即可取消靜音。 外部下載按鈕 點擊以打開外部下載器 + 佇列管理器 + 不要啟動外部下載程序,而是開啟佇列管理器。 播放速度按鈕 "點擊選擇影片播放速度 長按設定預設播放速度(1.0x)" @@ -1112,13 +1242,13 @@ 所有內容 (按熱門排序) 僅限影片 (按時間排序) 僅限影片 (按熱門排序) - 僅限短影片 (按時間排序) - 僅限短影片 (按流行排序) + 僅限短影音 (按時間排序) + 僅限短影音 (按流行排序) 僅限串流媒體影片 (按時間排序) 僅限串流媒體影片 (按熱門排序) 所有會員專屬內容 會員專屬影片 - 會員專屬短影片 + 會員專屬短影音 會員專屬直播 由於頻道ID不匹配 因此無法產生播放清單。 頻道白名單 @@ -1231,22 +1361,22 @@ 自動展開影片描述 手動展開影片描述 - 短片 - 停用 短影片 後台播放 - 短影片 後台播放已停用。 - 短影片 後台播放已啟用。 - 停用恢復短片播放器 - 短片播放器在應用程式啟動時不會恢復播放。 - 短片播放器在應用程式啟動時會恢復播放。 + 短影音 + 停用 短影音 後台播放 + 短影音 後台播放已停用。 + 短影音 後台播放已啟用。 + 停用恢復短影音播放器 + 短影音播放器在應用程式啟動時不會恢復播放。 + 短影音播放器在應用程式啟動時會恢復播放。 隱藏浮動按鈕 - "「使用此聲音」等浮動按鈕隱藏在 短片頻道標籤中。" - "「使用此聲音」等浮動按鈕顯示在 短片頻道標籤中。" + "短影音頻道分頁中會顯示「使用此音效」等浮動按鈕。" + "短影音頻道分頁中會顯示「使用此音效」等浮動按鈕。" - 短片欄 - 隱藏短片欄 - "隱藏短片欄 + 短影音專區 + 隱藏短影音專區 + "隱藏短影音專區 -已知問題:搜索結果中的官方標題將被隱藏" +副作用:搜索結果中的官方標題將被隱藏" 隱藏在頻道中 "隱藏在頻道中。 @@ -1256,18 +1386,18 @@ 在首頁和相關影片中隱藏 在首頁和相關影片中隱藏 在首頁和相關影片中顯示 - 隱藏搜尋結果中的短片 - 搜尋結果中的短片已隱藏 - 搜尋結果中的短片已顯示 - 隱藏訂閱中的短片 - 訂閱中的短片已隱藏 - 訂閱中的短片已顯示 + 在搜尋結果中隱藏 + 隱藏在搜尋結果中。 + 顯示在搜尋結果中。 + 在訂閱內容動態中隱藏 + 隱藏在訂閱內容動態中。 + 顯示在訂閱內容動態中。 在觀看歷史中隱藏 在觀看歷史中隱藏 在觀看歷史中顯示 - 變更 短影片背景重複狀態 - 更改短片重複狀態 + 變更 短影音背景重複狀態 + 更改短影音重複播放狀態 自動播放 預設 暫停 @@ -1276,8 +1406,8 @@ 在一般播放器中開啟 Shorts。 不要在普通播放器中開啟 Shorts。 - 短片播放器 - 隱藏或顯示短片播放器中的組件 + 短影音播放器 + 隱藏或顯示短影音播放器中的組件。 隱藏頻道欄 頻道欄已隱藏 頻道欄已顯示 @@ -1351,12 +1481,12 @@ 顯示使用此聲音按鈕。 操作按鈕 - 隱藏點贊按鈕 - 點讚按鈕已隱藏 - 點讚按鈕已顯示 - 隱藏倒讚按鈕 - 倒讚按鈕已隱藏 - 倒讚按鈕已顯示 + 隱藏點讚按鈕 + 點讚按鈕已隱藏。 + 點讚按鈕已顯示。 + 隱藏不喜歡按鈕 + 不喜歡按鈕已隱藏。 + 不喜歡按鈕已顯示。 隱藏評論按鈕 評論按鈕已隱藏 評論按鈕已顯示 @@ -1376,7 +1506,7 @@ 點讚按鈕上方的噴泉動畫已啟用。 雙擊動畫 原始 - 比讚 + 按讚 Cairo 愛心 愛心 (著色) @@ -1390,7 +1520,6 @@ "自訂操作在彈出式選單中啟用。 限制: -• 如果應用程式版本被欺騙為18.49.37 或更早版本,則不起作用。 • 不適用於直播。" 彈出式選單中禁用自訂操作。 在工具列中啟用自訂操作 @@ -1415,6 +1544,10 @@ 顯示開啟影片選單 開啟影片選單已顯示。 開啟影片選單已隱藏。 + 快速對話 + 顯示快速對話選單 + 快速對話選單已顯示。 + 快速對話選單已隱藏。 重複狀態 顯示重複狀態選單 重複狀態選單已顯示。 @@ -1428,7 +1561,7 @@ "已知問題:由於這是 Google 開發階段的功能,因此佈局可能會被破壞。" 時間戳已停用。 長按時間戳記 - 按住時間戳記可變更短片重複狀態。 + 長按時間戳記以變更短影音重複播放狀態。 面板下邊距 配置從搜尋欄到面板的間距,範圍為 0-64。 面板底部邊距必須介於 0-64 之間。 重設為預設值。 @@ -1454,10 +1587,10 @@ 亮度手勢的最低值會啟用自動亮度調節 亮度手勢的最低值會啟動自動亮度。 啟用滑動控制亮度 - 滑動控制亮度已啟用 + "滑動控制亮度已啟用" 滑動控制亮度已停用 音量手勢 - 滑動控制音量已啟用 + "滑動控制音量已啟用" 滑動控制音量已停用 啟用儲存和還原亮度 退出或進入全螢幕時儲存和還原亮度。 @@ -1471,10 +1604,20 @@ 在「鎖定螢幕」模式下滑動手勢 在「鎖定螢幕」模式下啟用滑動手勢 在「鎖定螢幕」模式下停用滑動手勢 - 滑動背景透明度 - 滑動疊加層背景透明度 滑動幅度閾值 防誤觸的滑動幅度閾值 + 滑動覆蓋替代用戶介面 + 使用替代的使用者介面。 + 使用舊版使用者介面。 + 啟用極簡風格 + 已啟用最小覆蓋樣式。 + 最小覆蓋樣式已停用。 + 顯示圓形覆蓋層 + 圓形覆蓋層已顯示。 + 水平覆蓋層已顯示。 + 滑動調節覆蓋層背景不透明度 + 不透明度值介於 0 到 100 之間。 + 滑動透明度必須介於 0 到 100 之間。 滑動疊加層上的檔案大小 滑動疊加層上的檔案大小 滑動覆蓋螢幕尺寸 @@ -1506,12 +1649,42 @@ 自動 影片 - 預設播放速度 - 行動數據的預設影片畫質 - Wi-Fi 預設的影片畫質 + + 編解碼器 HDR 影片 HDR 影片已啟用 HDR 影片已停用 + 停用 VP9 編解碼器 + "VP9 編解碼器已停用。 + +• 最高解析度為 1080p。 +• 影片播放將使用比 VP9 更多的網路數據。 +• 若要獲得 HDR 播放,HDR 影片仍會使用 VP9 編解碼器。" + VP9 編解碼器已啟用。 + 替換軟體 AV1 編解碼器 + 將軟體 AV1 編解碼器替換為 VP9 編解碼器。 + + 播放速度 + 預設播放速度 + 記住播放速度更改 + 播放速度更改適用於所有影片 + 播放速度更改僅適用於當前影片 + 顯示提示訊息 + 變更預設播放速度時會顯示提示訊息。 + 變更預設播放速度時不會顯示提示訊息。 + 短影音的預設播放速度 + 記住短影音的播放速度變更 + "播放速度變更適用於所有短影音。 + +資訊: +• 在短影音播放器中變更播放速度的唯一方法是使用「自訂動作」中的「速度對話框」。 +• 如果您未包含「短影音元件」補丁,則此功能將無法使用。" + 播放速度變更僅適用於目前的短影音。 + 顯示提示訊息 + 變更短影音上的預設播放速度時,會顯示提示訊息。 + 變更短影音上的預設播放速度時,不會顯示提示訊息。 + 將預設播放速度變更為:%s。 + 將預設短影音播放速度變更為:%s。 啟用自定義播放速度 自定義播放速度已啟用 自定義播放速度已停用 @@ -1520,30 +1693,34 @@ 使用舊版彈出選單 編輯自定義播放速度 添加或更改播放速度 - 記住播放速度更改 - 播放速度更改適用於所有影片 - 播放速度更改僅適用於當前影片 - 顯示提示訊息 - 變更預設播放速度時會顯示提示訊息。 - 變更預設播放速度時不會顯示提示訊息。 + 自訂速度必須小於 %sx +使用預設值 + 無效的自定播放速度將使用預設值 + + 影片畫質 + 行動數據的預設影片畫質 + Wi-Fi 預設的影片畫質 記住影片畫質更改 畫質更改適用於所有影片 畫質更改僅適用於當前影片 顯示提示訊息 更改預設影片畫質時將顯示提示訊息。 更改預設影片畫質時不會顯示提示訊息。 + 行動網路上的預設短影音畫質 + 無線網路上的預設短影音畫質 + 記住短影音的畫質變化 + 畫質變更適用於所有短影音。 + 畫質變化僅適用於目前的短影音。 + 顯示提示訊息 + 變更預設短影音畫質時將顯示提示。 + 變更預設短影音畫質時不會顯示提示。 + 行動網路 + 無線上網 + 將預設 %1$s 畫質變更為: %2$s。 + 將短影音 %1$s 的畫質變更為: %2$s。 恢復舊的影片畫質選單 顯示舊的影片畫質選單 不顯示舊的影片畫質選單 - 停用音樂的播放速度 - 音樂的預設播放速度被禁用。 - 音樂的預設播放速度已啟用。 - 使用類別進行驗證 - 如果影片類別為音樂,則預設播放速度將被停用。 - YouTube 音樂上可播放的影片的預設播放速度為停用。 - 啟用短片預設播放速度 - 預設播放速度適用於短片 - 預設播放速度不適用於短片 跳過預載入 跳過預載入 "跳過影片開頭的預先載入緩衝區以立即套用預設影片品質 @@ -1556,60 +1733,50 @@ 提示訊息已顯示 提示訊息已隱藏 偽裝裝置尺寸 - "變更裝置尺寸設定,以便解鎖在您目前的裝置上原本不支援的較高影片品質。" - 停用 VP9 編解碼器 - "VP9 編解碼器已停用。 + "將設備尺寸偽裝為最大值。 -• 最高解析度為 1080p。 -• 影片播放將使用比 VP9 更多的網路數據。 -• 若要獲得 HDR 播放,HDR 影片仍會使用 VP9 編解碼器。" - VP9 編解碼器已啟用。 - 替換軟體 AV1 編解碼器 - 將軟體 AV1 編解碼器替換為 VP9 編解碼器。 - 拒絕軟體 AV1 編解碼器回應 - "強制拒絕軟體 AV1 編解碼器回應。 -約 20 秒的緩衝後會應用不同的編解碼器。" - 切換過程會導致約 20 秒載入 - 將預設速度更改為 %s - 將預設移動數據畫質更改為 %s - 無法設定影片畫質 - 將預設 WiFi 畫質更改為 %s - 自訂速度必須小於 %sx -使用預設值 - 無效的自定播放速度將使用預設值 +資訊: +• 某些需要較高設備尺寸的影片可能會解鎖高畫質,但並非所有影片都適用。 +• 如果開啟“偽裝串流數據”,則此設定不可用。" + + 停用音樂的播放速度 + 音樂的預設播放速度被禁用。 + 音樂的預設播放速度已啟用。 + 使用類別進行驗證 + 如果影片類別為音樂,則預設播放速度將被停用。 + YouTube 音樂上可播放的影片的預設播放速度為停用。 - 恢復 YouTube 倒讚 - 啟用恢復 YouTube 倒讚 - 倒讚數已顯示 - 倒讚數已隱藏 - 倒讚 - 短片中顯示的不喜歡內容 %s - "在短片中顯示不喜歡 + 恢復 YouTube 不喜歡 + 啟用恢復 YouTube 不喜歡功能 + 不喜歡數已顯示。 + 不喜歡數已隱藏。 + 在短影音中顯示不喜歡數 + 短影音顯示不喜歡數。 + "在短影音中顯示不喜歡數 -限制:在無痕模式下,倒讚可能不會顯示" - 倒讚已隱藏 - 倒讚百份比 - 倒讚顯示為百份比 - 倒讚顯示為數字 - 緊湊點讚按鈕 - 點讚按鈕樣式:最小寬度 - 點讚按鈕樣式:最佳顯示 +限制:若使用者未登入或使用無痕模式,可能不會顯示不喜歡數。" + 短影音中的不喜歡數已隱藏。 + 不喜歡數的百分比 + 不喜歡數顯示為百分比。 + 不喜歡數顯示為數字。 + 精簡按讚按鈕 + 按讚按鈕設計為最小寬度。 + 按讚按鈕設計為最佳外觀。 顯示估計喜歡的次數 估計喜歡的次數已顯示。 估計喜歡的次數已隱藏。 如果 API 無法使用,顯示提示訊息 - 如果 恢復 YouTube 倒讚 無法使用,則顯示提示訊息 - 如果 恢復 YouTube 倒讚 無法使用,不顯示訊息 + 如果 Return YouTube Dislike 不可用,將會顯示提示訊息。 + 如果 Return YouTube Dislike 不可用,將不顯示提 +示訊息。 關於 ReturnYouTubeDislike.com - 倒讚資訊由 Return YouTube Dislike API 提供 - -點擊了解更多資訊 - 倒讚數暫時不可用(API 連接超時) - 倒讚數不可用(狀態 %d) - 倒讚數不可用(已達到用戶端 API 限制) - 倒讚數不可用(%s) + 不喜歡數據由 Return YouTube Dislike API 提供。點擊這裡了解更多。 + 不喜歡功能暫時無法使用(API 超時)。 + 不喜歡數不可用 (狀態 %d)。 + 不喜歡數不可用(已達到用戶端 API 限制)。 + 不喜歡數不可用 (%s)。 重新載入影片以使用 恢復 YouTube 倒讚 進行投票 隱藏 @@ -1659,7 +1826,7 @@ 無償廣告/自我推廣 類似於「贊助」,非付費或自我推廣除外。這包括有關商品、捐贈或與他人合作的訊息 交互提醒(訂閱) - 影片中間簡短提醒觀眾來點讚、訂閱或關注。 如果片段較長,或是關於某個具體事物,則應分類為自我推廣 + 影片中間簡短提醒觀眾來點讚、訂閱或關注。 如果片段較長,或是關於某個具體事物,則應分類為自我推廣。 重點 大多數人正在尋找的影片重點位置 過場/開場動畫 @@ -1708,6 +1875,7 @@ 顯示跳過按鈕 僅在進度條中顯示 停用 + 不透明度: 顏色: 顏色已更改 顏色已重置 @@ -1773,10 +1941,12 @@ 無法為片段投票(API 超時) 無法為片段投票(狀態:%1$d %2$s) 無法為片段投票:%s - 贊成 + 按讚 反對 更改類別 沒有可供投票的片段 + + %1$s 到 %2$s 選擇片段類別 類別在設定中被停用請啟用類別以提交 新的 SponsorBlock 片段 @@ -1895,8 +2065,8 @@ Android VR "Android VR (無授權)" - "iOS -(需要 PoToken)" + "iOS +(已棄用)" "iOS TV (需要登入)" 偽裝副作用 @@ -1904,19 +2074,39 @@ • 無法獲得穩定的音量。 • 停用強制自動音軌無法使用。 • 登出或處於隱身模式時可能無法播放兒童影片。" - • 可能有播放問題 (需要PoToken)。 + • 整個 ASN/IP 範圍可能會被伺服器封鎖。 "• 電影或付費影片可能無法播放。 • 登出或處於隱身模式時可能無法播放兒童影片。" + 使用 iOS 用戶端 + " iOS 用戶端已新增到可用用戶端列表中。 + +警告:iOS 用戶端已被棄用。使用過程中出現的任何問題 風險自負。" + iOS 用戶端未新增至可用用戶端列表中。 + "當使用 iOS 請求 YouTube API 端點時,您將需要 iOS 裝置頒發的 Auth 令牌和 iOSGuard 頒發的 PoTokens。 + +這意味著透過 iOS 的串流請求將缺少 Auth 令牌和 PoTokens,而伺服器可能會將使用者視為機器人並阻止整個 ASN/IP 範圍。 + +請自行承擔風險!" 強制 iOS AVC (H.264) 影片編解碼器強制為 AVC (H.264)。 影片編解碼器是自動決定的。 "啟用此功能可能會延長電池壽命並修復播放卡頓問題。 AVC 的最大解析度為 1080p,Opus 音訊編解碼器不可用,影片播放將使用比 VP9 或 AV1 更多的網路資料。" + 跳過單件式回應加密 + "跳過單件式回應加密。 + +• 修正了某些使用者遇到的新型播放問題。 +• AV1 編解碼器可能無法使用。" + "不要跳過單件式回應加密。 + +• 有些使用者可能會遇到新型播放問題。" 顯示統計資料 用於取得串流資料的用戶端顯示在統計資料中。 用於獲取串流資料的用戶端隱藏在統計資料中。 VR預設音訊串流語言 + 無法獲取任何客戶端串流。 + 您可能尚未登入。 Potoken / 訪客數據 使用PoToken diff --git a/settings.gradle.kts b/settings.gradle.kts index bcff5f42d..73bc68f8d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,4 +19,3 @@ pluginManagement { plugins { id("app.revanced.patches") version "1.0.0-dev.6" } -