Compare commits

..

51 Commits

Author SHA1 Message Date
semantic-release-bot
0cfc31c8f7 chore: Release v5.33.0-dev.13 [skip ci]
# [5.33.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.12...v5.33.0-dev.13) (2025-08-05)

### Bug Fixes

* **Messenger - Hide Facebook button:** Support the latest app version ([#5590](https://github.com/ReVanced/revanced-patches/issues/5590)) ([a28891e](a28891e5f3))
2025-08-05 03:23:38 +00:00
Dawid Krajcarz
a28891e5f3 fix(Messenger - Hide Facebook button): Support the latest app version (#5590) 2025-08-04 23:21:10 -04:00
semantic-release-bot
36036b082d chore: Release v5.33.0-dev.12 [skip ci]
# [5.33.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.11...v5.33.0-dev.12) (2025-08-04)

### Bug Fixes

* **YouTube - Video quality:** Fix dialog quality list check mark not always shown ([1bc63e5](1bc63e50a7))
2025-08-04 19:19:31 +00:00
LisoUseInAIKyrios
1bc63e50a7 fix(YouTube - Video quality): Fix dialog quality list check mark not always shown 2025-08-04 15:17:00 -04:00
semantic-release-bot
4b2b5e3029 chore: Release v5.33.0-dev.11 [skip ci]
# [5.33.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.10...v5.33.0-dev.11) (2025-08-04)

### Bug Fixes

* **YouTube - Video quality:** Fix 144p default not always used ([9afa7d2](9afa7d2ac6))
2025-08-04 19:03:03 +00:00
LisoUseInAIKyrios
9afa7d2ac6 fix(YouTube - Video quality): Fix 144p default not always used 2025-08-04 15:00:14 -04:00
semantic-release-bot
1a8146dbc8 chore: Release v5.33.0-dev.10 [skip ci]
# [5.33.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.9...v5.33.0-dev.10) (2025-08-04)

### Bug Fixes

* **YouTube - Video quality:** Fix wrong qualities sometimes shown in player button dialog ([178eed7](178eed7fcd))
2025-08-04 17:23:50 +00:00
LisoUseInAIKyrios
178eed7fcd fix(YouTube - Video quality): Fix wrong qualities sometimes shown in player button dialog 2025-08-04 13:21:02 -04:00
semantic-release-bot
621292644c chore: Release v5.33.0-dev.9 [skip ci]
# [5.33.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.8...v5.33.0-dev.9) (2025-08-04)

### Bug Fixes

* **YouTube - Force original audio:** Disable a/b feature flag that forces localized audio ([#5582](https://github.com/ReVanced/revanced-patches/issues/5582)) ([1dd01cf](1dd01cf54a))
2025-08-04 01:27:12 +00:00
LisoUseInAIKyrios
1dd01cf54a fix(YouTube - Force original audio): Disable a/b feature flag that forces localized audio (#5582) 2025-08-03 21:23:27 -04:00
semantic-release-bot
8c31374c53 chore: Release v5.33.0-dev.8 [skip ci]
# [5.33.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.7...v5.33.0-dev.8) (2025-08-03)

### Bug Fixes

* **NFC Tools:** Remove broken patch that is no longer supported ([#5584](https://github.com/ReVanced/revanced-patches/issues/5584)) ([2e177a8](2e177a8839))
2025-08-03 23:44:42 +00:00
LisoUseInAIKyrios
2e177a8839 fix(NFC Tools): Remove broken patch that is no longer supported (#5584) 2025-08-03 19:41:54 -04:00
github-actions[bot]
cfffd422f8 chore: Sync translations (#5586) 2025-08-03 19:41:07 -04:00
github-actions[bot]
37aab8382e chore: Sync translations (#5585) 2025-08-03 19:38:17 -04:00
semantic-release-bot
f4950ec2ea chore: Release v5.33.0-dev.7 [skip ci]
# [5.33.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.6...v5.33.0-dev.7) (2025-08-03)

### Features

* **YouTube:** Add player button to change video quality ([#5435](https://github.com/ReVanced/revanced-patches/issues/5435)) ([7bdc328](7bdc32867a))
2025-08-03 15:26:39 +00:00
MarcaD
7bdc32867a feat(YouTube): Add player button to change video quality (#5435)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-08-03 11:23:46 -04:00
semantic-release-bot
6e60ac6963 chore: Release v5.33.0-dev.6 [skip ci]
# [5.33.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.5...v5.33.0-dev.6) (2025-07-31)

### Bug Fixes

* **YouTube - Video quality:** Use 1080p enhanced bitrate for Premium users ([#5565](https://github.com/ReVanced/revanced-patches/issues/5565)) ([1adbd56](1adbd563b2))
2025-07-31 18:30:03 +00:00
LisoUseInAIKyrios
1adbd563b2 fix(YouTube - Video quality): Use 1080p enhanced bitrate for Premium users (#5565) 2025-07-31 14:27:17 -04:00
github-actions[bot]
9ccf13b680 chore: Sync translations (#5567) 2025-07-31 14:27:05 -04:00
semantic-release-bot
7b8ca9c018 chore: Release v5.33.0-dev.5 [skip ci]
# [5.33.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.4...v5.33.0-dev.5) (2025-07-31)

### Bug Fixes

* **YouTube - Litho filter:** Correctly filter identifier of older YouTube targets ([b1d164b](b1d164b446))
2025-07-31 10:35:51 +00:00
github-actions[bot]
ae6dd23d08 chore: Sync translations (#5564) 2025-07-31 06:33:31 -04:00
LisoUseInAIKyrios
b1d164b446 fix(YouTube - Litho filter): Correctly filter identifier of older YouTube targets 2025-07-31 06:33:12 -04:00
dependabot[bot]
87c39dd485 chore(deps-dev): Bump semantic-release from 24.2.6 to 24.2.7 (#5545) 2025-07-30 10:31:08 -04:00
semantic-release-bot
1549ac12aa chore: Release v5.33.0-dev.4 [skip ci]
# [5.33.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.3...v5.33.0-dev.4) (2025-07-30)

### Performance Improvements

* **YouTube:** Filter identifier callback only on root component creation ([#5558](https://github.com/ReVanced/revanced-patches/issues/5558)) ([5d08fdd](5d08fdddb8))
2025-07-30 13:21:43 +00:00
LisoUseInAIKyrios
5d08fdddb8 perf(YouTube): Filter identifier callback only on root component creation (#5558) 2025-07-30 09:18:20 -04:00
semantic-release-bot
98114e5bde chore: Release v5.33.0-dev.3 [skip ci]
# [5.33.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.2...v5.33.0-dev.3) (2025-07-30)

### Bug Fixes

* **YouTube - Playback speed:** Use old speed menu for player button if enabled ([a4817df](a4817dfdd0))
2025-07-30 10:06:04 +00:00
LisoUseInAIKyrios
a4817dfdd0 fix(YouTube - Playback speed): Use old speed menu for player button if enabled 2025-07-30 06:03:21 -04:00
semantic-release-bot
d4f05351e1 chore: Release v5.33.0-dev.2 [skip ci]
# [5.33.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.1...v5.33.0-dev.2) (2025-07-29)

### Features

* **ORF ON:** Add `Remove root detection` patch ([#5551](https://github.com/ReVanced/revanced-patches/issues/5551)) ([d92362b](d92362b0d9))
2025-07-29 08:33:08 +00:00
abichinger
d92362b0d9 feat(ORF ON): Add Remove root detection patch (#5551) 2025-07-29 04:30:43 -04:00
github-actions[bot]
afc7c75df1 chore: Sync translations (#5555) 2025-07-29 04:29:42 -04:00
semantic-release-bot
f0d4e9bfb4 chore: Release v5.33.0-dev.1 [skip ci]
# [5.33.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.32.0...v5.33.0-dev.1) (2025-07-28)

### Features

* **YouTube - Playback speed:** Add "Restore old playback speed menu" option ([#5552](https://github.com/ReVanced/revanced-patches/issues/5552)) ([e9e4cf3](e9e4cf39b6))
2025-07-28 18:47:44 +00:00
LisoUseInAIKyrios
e9e4cf39b6 feat(YouTube - Playback speed): Add "Restore old playback speed menu" option (#5552) 2025-07-28 22:44:18 +04:00
semantic-release-bot
0579a9f760 chore: Release v5.32.0 [skip ci]
# [5.32.0](https://github.com/ReVanced/revanced-patches/compare/v5.31.2...v5.32.0) (2025-07-27)

### Bug Fixes

* **Messenger - Hide inbox ads:** Support the latest app version ([8ec857a](8ec857a175))
* **YouTube  - Hide layout components:** Fix "Hide ticket shelf" ([#5516](https://github.com/ReVanced/revanced-patches/issues/5516)) ([9ddb3ac](9ddb3ac39d))
* **YouTube - GmsCore support:** Fix search suggestions when logged out by using correct search provider ([#5483](https://github.com/ReVanced/revanced-patches/issues/5483)) ([e4e81b8](e4e81b89ea))

### Features

* **Prime Video:** Add `Playback speed` patch ([#5444](https://github.com/ReVanced/revanced-patches/issues/5444)) ([f46dbcd](f46dbcd084))
* **YouTube - External downloads:** Improve the selection of the external downloader package ([#5504](https://github.com/ReVanced/revanced-patches/issues/5504)) ([cfd7780](cfd77800d6))
* **YT Music:** Support latest versions ([#5524](https://github.com/ReVanced/revanced-patches/issues/5524)) ([1258555](125855540b))
2025-07-27 13:17:58 +00:00
LisoUseInAIKyrios
1c0acef3f3 chore: Merge branch dev to main (#5479) 2025-07-27 17:14:36 +04:00
github-actions[bot]
2419adb77b chore: Sync translations (#5544) 2025-07-27 17:14:11 +04:00
semantic-release-bot
9e4113555b chore: Release v5.32.0-dev.5 [skip ci]
# [5.32.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.4...v5.32.0-dev.5) (2025-07-26)

### Features

* **YT Music:** Support latest versions ([#5524](https://github.com/ReVanced/revanced-patches/issues/5524)) ([1258555](125855540b))
2025-07-26 06:30:30 +00:00
netceil
125855540b feat(YT Music): Support latest versions (#5524) 2025-07-26 10:27:47 +04:00
github-actions[bot]
a8eee825e6 chore: Sync translations (#5538) 2025-07-26 10:27:17 +04:00
semantic-release-bot
63859f0ef9 chore: Release v5.32.0-dev.4 [skip ci]
# [5.32.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.3...v5.32.0-dev.4) (2025-07-25)

### Bug Fixes

* **Messenger - Hide inbox ads:** Support the latest app version ([8ec857a](8ec857a175))
2025-07-25 06:53:39 +00:00
github-actions[bot]
1c9000dbda chore: Sync translations (#5531) 2025-07-25 10:51:05 +04:00
LisoUseInAIKyrios
8ec857a175 fix(Messenger - Hide inbox ads): Support the latest app version 2025-07-25 10:46:10 +04:00
semantic-release-bot
f56c7868f5 chore: Release v5.32.0-dev.3 [skip ci]
# [5.32.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.2...v5.32.0-dev.3) (2025-07-24)

### Features

* **YouTube - External downloads:** Improve the selection of the external downloader package ([#5504](https://github.com/ReVanced/revanced-patches/issues/5504)) ([cfd7780](cfd77800d6))
2025-07-24 07:31:13 +00:00
MarcaD
cfd77800d6 feat(YouTube - External downloads): Improve the selection of the external downloader package (#5504)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-07-24 11:28:16 +04:00
semantic-release-bot
707deaef0b chore: Release v5.32.0-dev.2 [skip ci]
# [5.32.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.1...v5.32.0-dev.2) (2025-07-23)

### Bug Fixes

* **YouTube  - Hide layout components:** Fix "Hide ticket shelf" ([#5516](https://github.com/ReVanced/revanced-patches/issues/5516)) ([9ddb3ac](9ddb3ac39d))
2025-07-23 12:05:24 +00:00
ILoveOpenSourceApplications
9ddb3ac39d fix(YouTube - Hide layout components): Fix "Hide ticket shelf" (#5516) 2025-07-23 16:02:53 +04:00
github-actions[bot]
a7d3b7c287 chore: Sync translations (#5519) 2025-07-23 16:02:21 +04:00
LisoUseInAIKyrios
30bac0397e chore(YouTube): Fix string typo 2025-07-20 15:38:40 +04:00
semantic-release-bot
c5fc187a35 chore: Release v5.32.0-dev.1 [skip ci]
# [5.32.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.3-dev.1...v5.32.0-dev.1) (2025-07-16)

### Features

* **Prime Video:** Add `Playback speed` patch ([#5444](https://github.com/ReVanced/revanced-patches/issues/5444)) ([f46dbcd](f46dbcd084))
2025-07-16 19:30:50 +00:00
Sujitha Wijewantha
f46dbcd084 feat(Prime Video): Add Playback speed patch (#5444) 2025-07-16 23:27:55 +04:00
github-actions[bot]
2136573cb6 chore: Sync translations (#5484) 2025-07-16 23:27:18 +04:00
MarcaD
86ec08993c refactor(YouTube - Settings): Back button/gesture closes search instead of exiting (#5439) 2025-07-16 23:26:20 +04:00
170 changed files with 4221 additions and 1493 deletions

View File

@@ -1,3 +1,145 @@
# [5.33.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.12...v5.33.0-dev.13) (2025-08-05)
### Bug Fixes
* **Messenger - Hide Facebook button:** Support the latest app version ([#5590](https://github.com/ReVanced/revanced-patches/issues/5590)) ([0cab98d](https://github.com/ReVanced/revanced-patches/commit/0cab98df1689dbf7a042f18f4a961d47da1430ad))
# [5.33.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.11...v5.33.0-dev.12) (2025-08-04)
### Bug Fixes
* **YouTube - Video quality:** Fix dialog quality list check mark not always shown ([295f0f2](https://github.com/ReVanced/revanced-patches/commit/295f0f216b5e8aa9d68457862e73e312b7342703))
# [5.33.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.10...v5.33.0-dev.11) (2025-08-04)
### Bug Fixes
* **YouTube - Video quality:** Fix 144p default not always used ([2f7483a](https://github.com/ReVanced/revanced-patches/commit/2f7483a2d789c28a243b58bb7a252c0d590858ee))
# [5.33.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.9...v5.33.0-dev.10) (2025-08-04)
### Bug Fixes
* **YouTube - Video quality:** Fix wrong qualities sometimes shown in player button dialog ([7378ae3](https://github.com/ReVanced/revanced-patches/commit/7378ae3c5fc88f91bf5cd6db47c6cd170a8c5a4f))
# [5.33.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.8...v5.33.0-dev.9) (2025-08-04)
### Bug Fixes
* **YouTube - Force original audio:** Disable a/b feature flag that forces localized audio ([#5582](https://github.com/ReVanced/revanced-patches/issues/5582)) ([9fe13ee](https://github.com/ReVanced/revanced-patches/commit/9fe13ee1af104c009efd19b826adef375e48e191))
# [5.33.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.7...v5.33.0-dev.8) (2025-08-03)
### Bug Fixes
* **NFC Tools:** Remove broken patch that is no longer supported ([#5584](https://github.com/ReVanced/revanced-patches/issues/5584)) ([cd3a6be](https://github.com/ReVanced/revanced-patches/commit/cd3a6be75c6bd3cc33c0b17a044bd6147f27b5ce))
# [5.33.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.6...v5.33.0-dev.7) (2025-08-03)
### Features
* **YouTube:** Add player button to change video quality ([#5435](https://github.com/ReVanced/revanced-patches/issues/5435)) ([d5f51bf](https://github.com/ReVanced/revanced-patches/commit/d5f51bf400dd22626ff65d7563b6fde70d53fb25))
# [5.33.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.5...v5.33.0-dev.6) (2025-07-31)
### Bug Fixes
* **YouTube - Video quality:** Use 1080p enhanced bitrate for Premium users ([#5565](https://github.com/ReVanced/revanced-patches/issues/5565)) ([bd3ace0](https://github.com/ReVanced/revanced-patches/commit/bd3ace0bd04ccd0369adb49d63aa0cf986402346))
# [5.33.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.4...v5.33.0-dev.5) (2025-07-31)
### Bug Fixes
* **YouTube - Litho filter:** Correctly filter identifier of older YouTube targets ([bf29d69](https://github.com/ReVanced/revanced-patches/commit/bf29d6909e389819bad878ad3b94bbc90d823cc9))
# [5.33.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.3...v5.33.0-dev.4) (2025-07-30)
### Performance Improvements
* **YouTube:** Filter identifier callback only on root component creation ([#5558](https://github.com/ReVanced/revanced-patches/issues/5558)) ([ccac46e](https://github.com/ReVanced/revanced-patches/commit/ccac46eebc2e14b094454e37ef4461d48a62c53f))
# [5.33.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.2...v5.33.0-dev.3) (2025-07-30)
### Bug Fixes
* **YouTube - Playback speed:** Use old speed menu for player button if enabled ([1e8f436](https://github.com/ReVanced/revanced-patches/commit/1e8f4368e117f4b278c24709231cb32546e46dc0))
# [5.33.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.33.0-dev.1...v5.33.0-dev.2) (2025-07-29)
### Features
* **ORF ON:** Add `Remove root detection` patch ([#5551](https://github.com/ReVanced/revanced-patches/issues/5551)) ([6c6aa35](https://github.com/ReVanced/revanced-patches/commit/6c6aa35411a139dddc3a15dd757fbeded5d1a0a3))
# [5.33.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.32.0...v5.33.0-dev.1) (2025-07-28)
### Features
* **YouTube - Playback speed:** Add "Restore old playback speed menu" option ([#5552](https://github.com/ReVanced/revanced-patches/issues/5552)) ([b01f15b](https://github.com/ReVanced/revanced-patches/commit/b01f15b9acb0427aed99b0141ae271831b7936bf))
# [5.32.0](https://github.com/ReVanced/revanced-patches/compare/v5.31.2...v5.32.0) (2025-07-27)
### Bug Fixes
* **Messenger - Hide inbox ads:** Support the latest app version ([2959c02](https://github.com/ReVanced/revanced-patches/commit/2959c0214dfa703ee623ef1f89bded7f78c9d252))
* **YouTube - Hide layout components:** Fix "Hide ticket shelf" ([#5516](https://github.com/ReVanced/revanced-patches/issues/5516)) ([3b85c71](https://github.com/ReVanced/revanced-patches/commit/3b85c71433325fff49e01c77c7b9ff8ddd0a7068))
* **YouTube - GmsCore support:** Fix search suggestions when logged out by using correct search provider ([#5483](https://github.com/ReVanced/revanced-patches/issues/5483)) ([e86fdc8](https://github.com/ReVanced/revanced-patches/commit/e86fdc86b161a6077960b85149e83bacbac664e7))
### Features
* **Prime Video:** Add `Playback speed` patch ([#5444](https://github.com/ReVanced/revanced-patches/issues/5444)) ([22cf313](https://github.com/ReVanced/revanced-patches/commit/22cf313a7b99b69e17b9d488c514802043a5dc10))
* **YouTube - External downloads:** Improve the selection of the external downloader package ([#5504](https://github.com/ReVanced/revanced-patches/issues/5504)) ([5de9aa9](https://github.com/ReVanced/revanced-patches/commit/5de9aa9fad4f24186da045fb188f8718d6f63d7a))
* **YT Music:** Support latest versions ([#5524](https://github.com/ReVanced/revanced-patches/issues/5524)) ([551dcf0](https://github.com/ReVanced/revanced-patches/commit/551dcf01ca9c489a779196b49c8744727d79d6bc))
# [5.32.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.4...v5.32.0-dev.5) (2025-07-26)
### Features
* **YT Music:** Support latest versions ([#5524](https://github.com/ReVanced/revanced-patches/issues/5524)) ([551dcf0](https://github.com/ReVanced/revanced-patches/commit/551dcf01ca9c489a779196b49c8744727d79d6bc))
# [5.32.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.3...v5.32.0-dev.4) (2025-07-25)
### Bug Fixes
* **Messenger - Hide inbox ads:** Support the latest app version ([2959c02](https://github.com/ReVanced/revanced-patches/commit/2959c0214dfa703ee623ef1f89bded7f78c9d252))
# [5.32.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.2...v5.32.0-dev.3) (2025-07-24)
### Features
* **YouTube - External downloads:** Improve the selection of the external downloader package ([#5504](https://github.com/ReVanced/revanced-patches/issues/5504)) ([5de9aa9](https://github.com/ReVanced/revanced-patches/commit/5de9aa9fad4f24186da045fb188f8718d6f63d7a))
# [5.32.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.1...v5.32.0-dev.2) (2025-07-23)
### Bug Fixes
* **YouTube - Hide layout components:** Fix "Hide ticket shelf" ([#5516](https://github.com/ReVanced/revanced-patches/issues/5516)) ([3b85c71](https://github.com/ReVanced/revanced-patches/commit/3b85c71433325fff49e01c77c7b9ff8ddd0a7068))
# [5.32.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.3-dev.1...v5.32.0-dev.1) (2025-07-16)
### Features
* **Prime Video:** Add `Playback speed` patch ([#5444](https://github.com/ReVanced/revanced-patches/issues/5444)) ([22cf313](https://github.com/ReVanced/revanced-patches/commit/22cf313a7b99b69e17b9d488c514802043a5dc10))
## [5.31.3-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.2...v5.31.3-dev.1) (2025-07-16)

View File

@@ -0,0 +1,207 @@
package app.revanced.extension.primevideo.videoplayer;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.RectF;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.ColorFilter;
import android.graphics.PixelFormat;
import java.util.Arrays;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import com.amazon.video.sdk.player.Player;
public class PlaybackSpeedPatch {
private static Player player;
private static final float[] SPEED_VALUES = {0.5f, 0.7f, 0.8f, 0.9f, 0.95f, 1.0f, 1.05f, 1.1f, 1.2f, 1.3f, 1.5f, 2.0f};
private static final String SPEED_BUTTON_TAG = "speed_overlay";
public static void setPlayer(Player playerInstance) {
player = playerInstance;
if (player != null) {
// Reset playback rate when switching between episodes to ensure correct display.
player.setPlaybackRate(1.0f);
}
}
public static void initializeSpeedOverlay(View userControlsView) {
try {
LinearLayout buttonContainer = Utils.getChildViewByResourceName(userControlsView, "ButtonContainerPlayerTop");
// If the speed overlay exists we should return early.
if (Utils.getChildView(buttonContainer, false, child ->
child instanceof ImageView && SPEED_BUTTON_TAG.equals(child.getTag())) != null) {
return;
}
ImageView speedButton = createSpeedButton(userControlsView.getContext());
speedButton.setOnClickListener(v -> changePlaybackSpeed(speedButton));
buttonContainer.addView(speedButton, 0);
} catch (IllegalArgumentException e) {
Logger.printException(() -> "initializeSpeedOverlay, no button container found", e);
} catch (Exception e) {
Logger.printException(() -> "initializeSpeedOverlay failure", e);
}
}
private static ImageView createSpeedButton(Context context) {
ImageView speedButton = new ImageView(context);
speedButton.setContentDescription("Playback Speed");
speedButton.setTag(SPEED_BUTTON_TAG);
speedButton.setClickable(true);
speedButton.setFocusable(true);
speedButton.setScaleType(ImageView.ScaleType.CENTER);
SpeedIconDrawable speedIcon = new SpeedIconDrawable();
speedButton.setImageDrawable(speedIcon);
int buttonSize = Utils.dipToPixels(48);
speedButton.setMinimumWidth(buttonSize);
speedButton.setMinimumHeight(buttonSize);
return speedButton;
}
private static String[] getSpeedOptions() {
String[] options = new String[SPEED_VALUES.length];
for (int i = 0; i < SPEED_VALUES.length; i++) {
options[i] = SPEED_VALUES[i] + "x";
}
return options;
}
private static void changePlaybackSpeed(ImageView imageView) {
if (player == null) {
Logger.printException(() -> "Player not available");
return;
}
try {
player.pause();
AlertDialog dialog = createSpeedPlaybackDialog(imageView);
dialog.setOnDismissListener(dialogInterface -> player.play());
dialog.show();
} catch (Exception e) {
Logger.printException(() -> "changePlaybackSpeed", e);
}
}
private static AlertDialog createSpeedPlaybackDialog(ImageView imageView) {
Context context = imageView.getContext();
int currentSelection = getCurrentSpeedSelection();
return new AlertDialog.Builder(context)
.setTitle("Select Playback Speed")
.setSingleChoiceItems(getSpeedOptions(), currentSelection,
PlaybackSpeedPatch::handleSpeedSelection)
.create();
}
private static int getCurrentSpeedSelection() {
try {
float currentRate = player.getPlaybackRate();
int index = Arrays.binarySearch(SPEED_VALUES, currentRate);
return Math.max(index, 0); // Use slowest speed if not found.
} catch (Exception e) {
Logger.printException(() -> "getCurrentSpeedSelection error getting current playback speed", e);
return 0;
}
}
private static void handleSpeedSelection(android.content.DialogInterface dialog, int selectedIndex) {
try {
float selectedSpeed = SPEED_VALUES[selectedIndex];
player.setPlaybackRate(selectedSpeed);
player.play();
} catch (Exception e) {
Logger.printException(() -> "handleSpeedSelection error setting playback speed", e);
} finally {
dialog.dismiss();
}
}
}
class SpeedIconDrawable extends Drawable {
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
@Override
public void draw(Canvas canvas) {
int w = getBounds().width();
int h = getBounds().height();
float centerX = w / 2f;
// Position gauge in lower portion.
float centerY = h * 0.7f;
float radius = Math.min(w, h) / 2f * 0.8f;
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(radius * 0.1f);
// Draw semicircle.
RectF oval = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
canvas.drawArc(oval, 180, 180, false, paint);
// Draw three tick marks.
paint.setStrokeWidth(radius * 0.06f);
for (int i = 0; i < 3; i++) {
float angle = 180 + (i * 45); // 180°, 225°, 270°.
float angleRad = (float) Math.toRadians(angle);
float startX = centerX + (radius * 0.8f) * (float) Math.cos(angleRad);
float startY = centerY + (radius * 0.8f) * (float) Math.sin(angleRad);
float endX = centerX + radius * (float) Math.cos(angleRad);
float endY = centerY + radius * (float) Math.sin(angleRad);
canvas.drawLine(startX, startY, endX, endY, paint);
}
// Draw needle.
paint.setStrokeWidth(radius * 0.08f);
float needleAngle = 200; // Slightly right of center.
float needleAngleRad = (float) Math.toRadians(needleAngle);
float needleEndX = centerX + (radius * 0.6f) * (float) Math.cos(needleAngleRad);
float needleEndY = centerY + (radius * 0.6f) * (float) Math.sin(needleAngleRad);
canvas.drawLine(centerX, centerY, needleEndX, needleEndY, paint);
// Center dot.
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(centerX, centerY, radius * 0.06f, paint);
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public int getIntrinsicWidth() {
return Utils.dipToPixels(32);
}
@Override
public int getIntrinsicHeight() {
return Utils.dipToPixels(32);
}
}

View File

@@ -4,4 +4,10 @@ public interface VideoPlayer {
long getCurrentPosition();
void seekTo(long positionMs);
void pause();
void play();
boolean isPlaying();
}

View File

@@ -0,0 +1,11 @@
package com.amazon.video.sdk.player;
public interface Player {
float getPlaybackRate();
void setPlaybackRate(float rate);
void play();
void pause();
}

View File

@@ -1438,6 +1438,38 @@ public class Utils {
);
}
/**
* Converts a percentage of the screen height to actual device pixels.
*
* @param percentage The percentage of the screen height (e.g., 30 for 30%).
* @return The device pixel value corresponding to the percentage of screen height.
*/
public static int percentageHeightToPixels(int percentage) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (metrics.heightPixels * (percentage / 100.0f));
}
/**
* Converts a percentage of the screen width to actual device pixels.
*
* @param percentage The percentage of the screen width (e.g., 30 for 30%).
* @return The device pixel value corresponding to the percentage of screen width.
*/
public static int percentageWidthToPixels(int percentage) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (metrics.widthPixels * (percentage / 100.0f));
}
/**
* Uses {@link #adjustColorBrightness(int, float)} depending if light or dark mode is active.
*/
@ColorInt
public static int adjustColorBrightness(@ColorInt int baseColor, float lightThemeFactor, float darkThemeFactor) {
return isDarkModeEnabled()
? adjustColorBrightness(baseColor, darkThemeFactor)
: adjustColorBrightness(baseColor, lightThemeFactor);
}
/**
* Adjusts the brightness of a color by lightening or darkening it based on the given factor.
* <p>

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
@@ -26,7 +24,7 @@ public class CustomDialogListPreference extends ListPreference {
/**
* Custom ArrayAdapter to handle checkmark visibility.
*/
private static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
public static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
private static class SubViewDataContainer {
ImageView checkIcon;
View placeholder;

View File

@@ -1,28 +1,15 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.graphics.drawable.shapes.RoundRectShape;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.Paint.Style;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;

View File

@@ -1,17 +1,15 @@
package app.revanced.extension.youtube.patches;
import static app.revanced.extension.youtube.settings.preference.ExternalDownloaderPreference.showDialogIfAppIsNotInstalled;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import androidx.annotation.NonNull;
import java.lang.ref.WeakReference;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.StringRef;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
@@ -36,7 +34,7 @@ public final class DownloadsPatch {
*
* Appears to always be called from the main thread.
*/
public static boolean inAppDownloadButtonOnClick(@NonNull String videoId) {
public static boolean inAppDownloadButtonOnClick(String videoId) {
try {
if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) {
return false;
@@ -48,6 +46,9 @@ public final class DownloadsPatch {
boolean isActivityContext = true;
if (context == null) {
// Utils context is the application context, and not an activity context.
//
// Edit: This check may no longer be needed since YT can now
// only be launched from the main Activity (embedded usage in other apps no longer works).
context = Utils.getContext();
isActivityContext = false;
}
@@ -64,8 +65,7 @@ public final class DownloadsPatch {
* @param isActivityContext If the context parameter is for an Activity. If this is false, then
* the downloader is opened as a new task (which forces YT to minimize).
*/
public static void launchExternalDownloader(@NonNull String videoId,
@NonNull Context context, boolean isActivityContext) {
public static void launchExternalDownloader(String videoId, Context context, boolean isActivityContext) {
try {
Objects.requireNonNull(videoId);
Logger.printDebug(() -> "Launching external downloader with context: " + context);
@@ -73,16 +73,8 @@ public final class DownloadsPatch {
// Trim string to avoid any accidental whitespace.
var downloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get().trim();
boolean packageEnabled = false;
try {
packageEnabled = context.getPackageManager().getApplicationInfo(downloaderPackageName, 0).enabled;
} catch (PackageManager.NameNotFoundException error) {
Logger.printDebug(() -> "External downloader could not be found: " + error);
}
// If the package is not installed, show the toast
if (!packageEnabled) {
Utils.showToastLong(StringRef.str("revanced_external_downloader_not_installed_warning", downloaderPackageName));
// If the package is not installed, show a dialog.
if (showDialogIfAppIsNotInstalled(context, downloaderPackageName)) {
return;
}

View File

@@ -24,6 +24,16 @@ public class ForceOriginalAudioPatch {
}
}
/**
* Injection point.
*/
public static boolean ignoreDefaultAudioStream(boolean original) {
if (Settings.FORCE_ORIGINAL_AUDIO.get()) {
return false;
}
return original;
}
/**
* Injection point.
*/
@@ -50,7 +60,6 @@ public class ForceOriginalAudioPatch {
return isOriginal;
} catch (Exception ex) {
Logger.printException(() -> "isDefaultAudioStream failure", ex);
return isDefault;
}
}

View File

@@ -16,7 +16,7 @@ import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilterPatch;
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilter;
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
@@ -55,7 +55,7 @@ public class ReturnYouTubeDislikePatch {
private static volatile ReturnYouTubeDislike lastLithoShortsVideoData;
/**
* Because litho Shorts spans are created offscreen after {@link ReturnYouTubeDislikeFilterPatch}
* Because litho Shorts spans are created offscreen after {@link ReturnYouTubeDislikeFilter}
* detects the video ids, but the current Short can arbitrarily reload the same span,
* then use the {@link #lastLithoShortsVideoData} if this value is greater than zero.
*/

View File

@@ -6,8 +6,6 @@ import android.app.Instrumentation;
import android.view.KeyEvent;
import android.view.View;
import androidx.annotation.Nullable;
import java.util.List;
import app.revanced.extension.shared.Logger;
@@ -155,10 +153,10 @@ public final class AdsFilter extends Filter {
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == playerShoppingShelf) {
return contentIndex == 0 && playerShoppingShelfBuffer.check(protobufBufferArray).isFiltered();
return contentIndex == 0 && playerShoppingShelfBuffer.check(buffer).isFiltered();
}
if (exceptions.matches(path)) {

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.youtube.patches.components;
import androidx.annotation.Nullable;
import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQualityMenuPatch;
import app.revanced.extension.youtube.settings.Settings;
@@ -21,7 +19,7 @@ public final class AdvancedVideoQualityMenuFilter extends Filter {
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
isVideoQualityMenuVisible = true;

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.youtube.patches.components;
import androidx.annotation.Nullable;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
@@ -100,7 +98,7 @@ final class ButtonsFilter extends Filter {
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == likeSubscribeGlow) {
return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
@@ -117,7 +115,7 @@ final class ButtonsFilter extends Filter {
// Make sure the current path is the right one
// to avoid false positives.
return path.startsWith(VIDEO_ACTION_BAR_PATH)
&& bufferButtonsGroupList.check(protobufBufferArray).isFiltered();
&& bufferButtonsGroupList.check(buffer).isFiltered();
}
return true;

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.youtube.patches.components;
import androidx.annotation.Nullable;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
@@ -87,12 +85,12 @@ final class CommentsFilter extends Filter {
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == chipBar) {
// Playlist sort button uses same components and must only filter if the player is opened.
return PlayerType.getCurrent().isMaximizedOrFullscreen()
&& aiCommentsSummary.check(protobufBufferArray).isFiltered();
&& aiCommentsSummary.check(buffer).isFiltered();
}
return true;

View File

@@ -3,7 +3,6 @@ package app.revanced.extension.youtube.patches.components;
import static app.revanced.extension.shared.StringRef.str;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Arrays;
import java.util.Collection;
@@ -146,7 +145,7 @@ final class CustomFilter extends Filter {
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// All callbacks are custom filter groups.
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
@@ -158,6 +157,6 @@ final class CustomFilter extends Filter {
return true; // No buffer filter, only path filtering.
}
return custom.bufferSearch.matches(protobufBufferArray);
return custom.bufferSearch.matches(buffer);
}
}

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.youtube.patches.components;
import androidx.annotation.Nullable;
import app.revanced.extension.youtube.StringTrieSearch;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
@@ -105,7 +103,7 @@ final class DescriptionComponentsFilter extends Filter {
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == aiGeneratedVideoSummarySection) {
@@ -116,11 +114,11 @@ final class DescriptionComponentsFilter extends Filter {
if (exceptions.matches(path)) return false;
if (matchedGroup == macroMarkersCarousel) {
return contentIndex == 0 && macroMarkersCarouselGroupList.check(protobufBufferArray).isFiltered();
return contentIndex == 0 && macroMarkersCarouselGroupList.check(buffer).isFiltered();
}
if (matchedGroup == horizontalShelf) {
return cellVideoAttribute.check(protobufBufferArray).isFiltered();
return cellVideoAttribute.check(buffer).isFiltered();
}
return true;

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.youtube.patches.components;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -42,6 +40,7 @@ abstract class Filter {
/**
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
* if any of the groups are found.
* <p>
*/
protected final void addIdentifierCallbacks(StringFilterGroup... groups) {
identifierCallbacks.addAll(Arrays.asList(groups));
@@ -68,7 +67,7 @@ abstract class Filter {
* @param contentIndex Matched index of the identifier or path.
* @return True if the litho component should be filtered out.
*/
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
return true;
}

View File

@@ -3,9 +3,9 @@ package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class HideInfoCardsFilterPatch extends Filter {
public final class HideInfoCardsFilter extends Filter {
public HideInfoCardsFilterPatch() {
public HideInfoCardsFilter() {
addIdentifierCallbacks(
new StringFilterGroup(
Settings.HIDE_INFO_CARDS,

View File

@@ -554,7 +554,7 @@ final class KeywordContentFilter extends Filter {
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (contentIndex != 0 && matchedGroup == startsWithFilter) {
return false;
@@ -574,7 +574,7 @@ final class KeywordContentFilter extends Filter {
}
MutableReference<String> matchRef = new MutableReference<>();
if (bufferSearch.matches(protobufBufferArray, matchRef)) {
if (bufferSearch.matches(buffer, matchRef)) {
updateStats(true, matchRef.value);
return true;
}

View File

@@ -266,7 +266,7 @@ public final class LayoutComponentsFilter extends Filter {
ticketShelf = new ByteArrayFilterGroup(
Settings.HIDE_TICKET_SHELF,
"ticket.eml"
"ticket_item.eml"
);
addPathCallbacks(
@@ -304,7 +304,7 @@ public final class LayoutComponentsFilter extends Filter {
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// This identifier is used not only in players but also in search results:
// https://github.com/ReVanced/revanced-patches/issues/3245
@@ -322,7 +322,7 @@ public final class LayoutComponentsFilter extends Filter {
}
if (matchedGroup == channelProfile) {
return channelProfileBuffer.check(protobufBufferArray).isFiltered();
return channelProfileBuffer.check(buffer).isFiltered();
}
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
@@ -331,11 +331,11 @@ public final class LayoutComponentsFilter extends Filter {
return compactChannelBarInnerButton.check(path).isFiltered()
// The filter may be broad, but in the context of a compactChannelBarInnerButton,
// it's safe to assume that the button is the only thing that should be hidden.
&& joinMembershipButton.check(protobufBufferArray).isFiltered();
&& joinMembershipButton.check(buffer).isFiltered();
}
if (matchedGroup == horizontalShelves) {
return contentIndex == 0 && (hideShelves() || ticketShelf.check(protobufBufferArray).isFiltered());
return contentIndex == 0 && (hideShelves() || ticketShelf.check(buffer).isFiltered());
}
if (matchedGroup == chipBar) {

View File

@@ -17,29 +17,28 @@ public final class LithoFilterPatch {
* Simple wrapper to pass the litho parameters through the prefix search.
*/
private static final class LithoFilterParameters {
@Nullable
final String identifier;
final String path;
final byte[] protoBuffer;
final byte[] buffer;
LithoFilterParameters(@Nullable String lithoIdentifier, String lithoPath, byte[] protoBuffer) {
LithoFilterParameters(String lithoIdentifier, String lithoPath, byte[] buffer) {
this.identifier = lithoIdentifier;
this.path = lithoPath;
this.protoBuffer = protoBuffer;
this.buffer = buffer;
}
@NonNull
@Override
public String toString() {
// Estimate the percentage of the buffer that are Strings.
StringBuilder builder = new StringBuilder(Math.max(100, protoBuffer.length / 2));
StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2));
builder.append( "ID: ");
builder.append(identifier);
builder.append(" Path: ");
builder.append(path);
if (Settings.DEBUG_PROTOBUFFER.get()) {
builder.append(" BufferStrings: ");
findAsciiStrings(builder, protoBuffer);
findAsciiStrings(builder, buffer);
}
return builder.toString();
@@ -128,21 +127,21 @@ public final class LithoFilterPatch {
private static void filterUsingCallbacks(StringTrieSearch pathSearchTree,
Filter filter, List<StringFilterGroup> groups,
Filter.FilterContentType type) {
String filterSimpleName = filter.getClass().getSimpleName();
for (StringFilterGroup group : groups) {
if (!group.includeInSearch()) {
continue;
}
for (String pattern : group.filters) {
String filterSimpleName = filter.getClass().getSimpleName();
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex,
matchedLength, callbackParameter) -> {
if (!group.isEnabled()) return false;
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
final boolean isFiltered = filter.isFiltered(parameters.identifier,
parameters.path, parameters.protoBuffer, group, type, matchedStartIndex);
parameters.path, parameters.buffer, group, type, matchedStartIndex);
if (isFiltered && BaseSettings.DEBUG.get()) {
if (type == Filter.FilterContentType.IDENTIFIER) {
@@ -163,6 +162,7 @@ public final class LithoFilterPatch {
/**
* Injection point. Called off the main thread.
* Targets 20.22+
*/
public static void setProtoBuffer(byte[] buffer) {
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
@@ -193,9 +193,9 @@ public final class LithoFilterPatch {
/**
* Injection point.
*/
public static boolean shouldFilter(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
public static boolean isFiltered(String lithoIdentifier, StringBuilder pathBuilder) {
try {
if (pathBuilder.length() == 0) {
if (lithoIdentifier.isEmpty() && pathBuilder.length() == 0) {
return false;
}
@@ -210,7 +210,7 @@ public final class LithoFilterPatch {
lithoIdentifier, pathBuilder.toString(), buffer);
Logger.printDebug(() -> "Searching " + parameter);
if (parameter.identifier != null && identifierSearchTree.matches(parameter.identifier, parameter)) {
if (identifierSearchTree.matches(parameter.identifier, parameter)) {
return true;
}
@@ -218,7 +218,7 @@ public final class LithoFilterPatch {
return true;
}
} catch (Exception ex) {
Logger.printException(() -> "Litho filter failure", ex);
Logger.printException(() -> "isFiltered failure", ex);
}
return false;

View File

@@ -0,0 +1,49 @@
package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.Settings;
/**
* Abuse LithoFilter for {@link CustomPlaybackSpeedPatch}.
*/
public final class PlaybackSpeedMenuFilter extends Filter {
/**
* Old litho based speed selection menu.
*/
public static volatile boolean isOldPlaybackSpeedMenuVisible;
/**
* 0.05x speed selection menu.
*/
public static volatile boolean isPlaybackRateSelectorMenuVisible;
private final StringFilterGroup oldPlaybackMenuGroup;
public PlaybackSpeedMenuFilter() {
// 0.05x litho speed menu.
var playbackRateSelectorGroup = new StringFilterGroup(
Settings.CUSTOM_SPEED_MENU,
"playback_rate_selector_menu_sheet.eml-js"
);
// Old litho based speed menu.
oldPlaybackMenuGroup = new StringFilterGroup(
Settings.CUSTOM_SPEED_MENU,
"playback_speed_sheet_content.eml-js");
addPathCallbacks(playbackRateSelectorGroup, oldPlaybackMenuGroup);
}
@Override
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == oldPlaybackMenuGroup) {
isOldPlaybackSpeedMenuVisible = true;
} else {
isPlaybackRateSelectorMenuVisible = true;
}
return false;
}
}

View File

@@ -1,35 +0,0 @@
package app.revanced.extension.youtube.patches.components;
import androidx.annotation.Nullable;
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.Settings;
/**
* Abuse LithoFilter for {@link CustomPlaybackSpeedPatch}.
*/
public final class PlaybackSpeedMenuFilterPatch extends Filter {
/**
* 0.05x speed selection menu.
*/
public static volatile boolean isPlaybackRateSelectorMenuVisible;
public PlaybackSpeedMenuFilterPatch() {
// 0.05x litho speed menu.
var playbackRateSelectorGroup = new StringFilterGroup(
Settings.CUSTOM_SPEED_MENU,
"playback_rate_selector_menu_sheet.eml-js"
);
addPathCallbacks(playbackRateSelectorGroup);
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
isPlaybackRateSelectorMenuVisible = true;
return false;
}
}

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.youtube.patches.components;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
import app.revanced.extension.youtube.settings.Settings;
@@ -96,7 +94,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == videoQualityMenuFooter) {
return true;
@@ -107,10 +105,10 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
}
// Shorts also use this player flyout panel
if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(protobufBufferArray).isFiltered()) {
if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(buffer).isFiltered()) {
return false;
}
return flyoutFilterGroupList.check(protobufBufferArray).isFiltered();
return flyoutFilterGroupList.check(buffer).isFiltered();
}
}

View File

@@ -26,7 +26,7 @@ import app.revanced.extension.youtube.TrieSearch;
*
* Once a way to asynchronously update litho text is found, this strategy will no longer be needed.
*/
public final class ReturnYouTubeDislikeFilterPatch extends Filter {
public final class ReturnYouTubeDislikeFilter extends Filter {
/**
* Last unique video id's loaded. Value is ignored and Map is treated as a Set.
@@ -67,7 +67,7 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
private final ByteArrayFilterGroupList videoIdFilterGroup = new ByteArrayFilterGroupList();
public ReturnYouTubeDislikeFilterPatch() {
public ReturnYouTubeDislikeFilter() {
// When a new Short is opened, the like buttons always seem to load before the dislike.
// But if swiping back to a previous video and liking/disliking, then only that single button reloads.
// So must check for both buttons.
@@ -84,15 +84,15 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) {
return false;
}
FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(protobufBufferArray);
FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(buffer);
if (result.isFiltered()) {
String matchedVideoId = findVideoId(protobufBufferArray);
String matchedVideoId = findVideoId(buffer);
// Matched video will be null if in incognito mode.
// Must pass a null id to correctly clear out the current video data.
// Otherwise if a Short is opened in non-incognito, then incognito is enabled and another Short is opened,

View File

@@ -4,8 +4,6 @@ import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButt
import android.view.View;
import androidx.annotation.Nullable;
import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
import java.lang.ref.WeakReference;
@@ -13,7 +11,6 @@ import java.util.Arrays;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.NavigationBar;
import app.revanced.extension.youtube.shared.PlayerType;
@@ -321,7 +318,7 @@ public final class ShortsFilter extends Filter {
}
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (contentType == FilterContentType.PATH) {
if (matchedGroup == subscribeButton || matchedGroup == joinButton || matchedGroup == paidPromotionButton) {
@@ -330,22 +327,22 @@ public final class ShortsFilter extends Filter {
}
if (matchedGroup == useSoundButton) {
return useSoundButtonBuffer.check(protobufBufferArray).isFiltered();
return useSoundButtonBuffer.check(buffer).isFiltered();
}
if (matchedGroup == useTemplateButton) {
return useTemplateButtonBuffer.check(protobufBufferArray).isFiltered();
return useTemplateButtonBuffer.check(buffer).isFiltered();
}
if (matchedGroup == shortsCompactFeedVideo) {
return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered();
return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(buffer).isFiltered();
}
// Video action buttons (comment, share, remix) have the same path.
// Like and dislike are separate path filters and don't require buffer searching.
if (matchedGroup == shortsActionBar) {
return videoActionButton.check(path).isFiltered()
&& videoActionButtonBuffer.check(protobufBufferArray).isFiltered();
&& videoActionButtonBuffer.check(buffer).isFiltered();
}
if (matchedGroup == suggestedAction) {
@@ -356,7 +353,7 @@ public final class ShortsFilter extends Filter {
return true;
}
return suggestedActionsBuffer.check(protobufBufferArray).isFiltered();
return suggestedActionsBuffer.check(buffer).isFiltered();
}
return true;

View File

@@ -18,7 +18,7 @@ import app.revanced.extension.youtube.settings.Settings;
public final class AdvancedVideoQualityMenuPatch {
/**
* Injection point.
* Injection point. Regular videos.
*/
public static void onFlyoutMenuCreate(RecyclerView recyclerView) {
if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return;
@@ -61,22 +61,12 @@ public final class AdvancedVideoQualityMenuPatch {
});
}
/**
* Injection point.
*
* Used to force the creation of the advanced menu item for the Shorts quality flyout.
*/
public static boolean forceAdvancedVideoQualityMenuCreation(boolean original) {
return Settings.ADVANCED_VIDEO_QUALITY_MENU.get() || original;
}
/**
* Injection point.
*
* Shorts video quality flyout.
*/
public static void showAdvancedVideoQualityMenu(ListView listView) {
public static void addVideoQualityListMenuListener(ListView listView) {
if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return;
listView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
@@ -91,7 +81,6 @@ public final class AdvancedVideoQualityMenuPatch {
listView.setSoundEffectsEnabled(false);
final var qualityItemMenuPosition = 4;
listView.performItemClick(null, qualityItemMenuPosition, 0);
} catch (Exception ex) {
Logger.printException(() -> "showAdvancedVideoQualityMenu failure", ex);
}
@@ -102,4 +91,13 @@ public final class AdvancedVideoQualityMenuPatch {
}
});
}
/**
* Injection point.
*
* Used to force the creation of the advanced menu item for the Shorts quality flyout.
*/
public static boolean forceAdvancedVideoQualityMenuCreation(boolean original) {
return Settings.ADVANCED_VIDEO_QUALITY_MENU.get() || original;
}
}

View File

@@ -5,10 +5,9 @@ import static app.revanced.extension.shared.Utils.NetworkType;
import androidx.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.google.android.libraries.youtube.innertube.model.media.VideoQuality;
import java.util.Arrays;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
@@ -17,10 +16,29 @@ import app.revanced.extension.shared.settings.IntegerSetting;
import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
import app.revanced.extension.youtube.videoplayer.VideoQualityDialogButton;
@SuppressWarnings("unused")
public class RememberVideoQualityPatch {
private static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2;
/**
* Interface to use obfuscated methods.
*/
public interface VideoQualityMenuInterface {
void patch_setQuality(VideoQuality quality);
}
/**
* Video resolution of the automatic quality option..
*/
public static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2;
/**
* All quality names are the same for all languages.
* VideoQuality also has a resolution enum that can be used if needed.
*/
public static final String VIDEO_QUALITY_1080P_PREMIUM_NAME = "1080p Premium";
private static final IntegerSetting videoQualityWifi = Settings.VIDEO_QUALITY_DEFAULT_WIFI;
private static final IntegerSetting videoQualityMobile = Settings.VIDEO_QUALITY_DEFAULT_MOBILE;
private static final IntegerSetting shortsQualityWifi = Settings.SHORTS_QUALITY_DEFAULT_WIFI;
@@ -29,46 +47,83 @@ public class RememberVideoQualityPatch {
private static boolean qualityNeedsUpdating;
/**
* If the user selected a new quality from the flyout menu,
* and {@link Settings#REMEMBER_VIDEO_QUALITY_LAST_SELECTED} is enabled.
*/
private static boolean userChangedDefaultQuality;
/**
* Index of the video quality chosen by the user from the flyout menu.
*/
private static int userSelectedQualityIndex;
/**
* The available qualities of the current video in human readable form: [1080, 720, 480]
* The available qualities of the current video.
*/
@Nullable
private static List<Integer> videoQualities;
private static VideoQuality[] currentQualities;
private static boolean shouldRememberVideoQuality() {
BooleanSetting preference = ShortsPlayerState.isOpen() ?
Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED
/**
* The current quality of the video playing.
* This is always the actual quality even if Automatic quality is active.
*/
@Nullable
private static VideoQuality currentQuality;
/**
* The current VideoQualityMenuInterface, set during setVideoQuality.
*/
@Nullable
private static VideoQualityMenuInterface currentMenuInterface;
@Nullable
public static VideoQuality[] getCurrentQualities() {
return currentQualities;
}
@Nullable
public static VideoQuality getCurrentQuality() {
return currentQuality;
}
@Nullable
public static VideoQualityMenuInterface getCurrentMenuInterface() {
return currentMenuInterface;
}
public static boolean shouldRememberVideoQuality() {
BooleanSetting preference = ShortsPlayerState.isOpen()
? Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED
: Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED;
return preference.get();
}
private static void changeDefaultQuality(int defaultQuality) {
public static int getDefaultQualityResolution() {
final boolean isShorts = ShortsPlayerState.isOpen();
IntegerSetting preference = Utils.getNetworkType() == NetworkType.MOBILE
? (isShorts ? shortsQualityMobile : videoQualityMobile)
: (isShorts ? shortsQualityWifi : videoQualityWifi);
return preference.get();
}
public static void saveDefaultQuality(int qualityResolution) {
final boolean shortPlayerOpen = ShortsPlayerState.isOpen();
String networkTypeMessage;
boolean useShortsPreference = ShortsPlayerState.isOpen();
IntegerSetting qualitySetting;
if (Utils.getNetworkType() == NetworkType.MOBILE) {
if (useShortsPreference) shortsQualityMobile.save(defaultQuality);
else videoQualityMobile.save(defaultQuality);
networkTypeMessage = str("revanced_remember_video_quality_mobile");
qualitySetting = shortPlayerOpen ? shortsQualityMobile : videoQualityMobile;
} else {
if (useShortsPreference) shortsQualityWifi.save(defaultQuality);
else videoQualityWifi.save(defaultQuality);
networkTypeMessage = str("revanced_remember_video_quality_wifi");
qualitySetting = shortPlayerOpen ? shortsQualityWifi : videoQualityWifi;
}
if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get())
if (qualitySetting.get() == qualityResolution) {
// User clicked the same video quality as the current video,
// or changed between 1080p Premium and non-Premium.
return;
}
qualitySetting.save(qualityResolution);
if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) {
String qualityLabel = qualityResolution + "p";
Utils.showToastShort(str(
useShortsPreference ? "revanced_remember_video_quality_toast_shorts" : "revanced_remember_video_quality_toast",
networkTypeMessage, (defaultQuality + "p")
));
shortPlayerOpen
? "revanced_remember_video_quality_toast_shorts"
: "revanced_remember_video_quality_toast",
networkTypeMessage,
qualityLabel)
);
}
}
/**
@@ -77,109 +132,132 @@ public class RememberVideoQualityPatch {
* @param qualities Video qualities available, ordered from largest to smallest, with index 0 being the 'automatic' value of -2
* @param originalQualityIndex quality index to use, as chosen by YouTube
*/
public static int setVideoQuality(Object[] qualities, final int originalQualityIndex, Object qInterface, String qIndexMethod) {
public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInterface menu, int originalQualityIndex) {
try {
boolean useShortsPreference = ShortsPlayerState.isOpen();
final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE
? (useShortsPreference ? shortsQualityMobile : videoQualityMobile).get()
: (useShortsPreference ? shortsQualityWifi : videoQualityWifi).get();
Utils.verifyOnMainThread();
currentMenuInterface = menu;
if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) {
final boolean availableQualitiesChanged = (currentQualities == null)
|| !Arrays.equals(currentQualities, qualities);
if (availableQualitiesChanged) {
currentQualities = qualities;
Logger.printDebug(() -> "VideoQualities: " + Arrays.toString(currentQualities));
}
VideoQuality updatedCurrentQuality = qualities[originalQualityIndex];
if (updatedCurrentQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE
&& (currentQuality == null || currentQuality != updatedCurrentQuality)) {
currentQuality = updatedCurrentQuality;
Logger.printDebug(() -> "Current quality changed to: " + updatedCurrentQuality);
VideoQualityDialogButton.updateButtonIcon(updatedCurrentQuality);
}
final int preferredQuality = getDefaultQualityResolution();
if (preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) {
return originalQualityIndex; // Nothing to do.
}
if (videoQualities == null || videoQualities.size() != qualities.length) {
videoQualities = new ArrayList<>(qualities.length);
for (Object streamQuality : qualities) {
for (Field field : streamQuality.getClass().getFields()) {
if (field.getType().isAssignableFrom(Integer.TYPE)
&& field.getName().length() <= 2) {
videoQualities.add(field.getInt(streamQuality));
}
}
}
// After changing videos the qualities can initially be for the prior video.
// So if the qualities have changed an update is needed.
qualityNeedsUpdating = true;
Logger.printDebug(() -> "VideoQualities: " + videoQualities);
}
if (userChangedDefaultQuality) {
userChangedDefaultQuality = false;
final int quality = videoQualities.get(userSelectedQualityIndex);
Logger.printDebug(() -> "User changed default quality to: " + quality);
changeDefaultQuality(quality);
return userSelectedQualityIndex;
}
if (!qualityNeedsUpdating) {
// After changing videos the qualities can initially be for the prior video.
// If the qualities have changed and the default is not auto then an update is needed.
if (!qualityNeedsUpdating && !availableQualitiesChanged) {
return originalQualityIndex;
}
qualityNeedsUpdating = false;
// Find the highest quality that is equal to or less than the preferred.
int qualityToUse = videoQualities.get(0); // first element is automatic mode
int qualityIndexToUse = 0;
int i = 0;
for (Integer quality : videoQualities) {
if (quality <= preferredQuality && qualityToUse < quality) {
qualityToUse = quality;
qualityIndexToUse = i;
for (VideoQuality quality : qualities) {
final int qualityResolution = quality.patch_getResolution();
if ((qualityResolution != AUTOMATIC_VIDEO_QUALITY_VALUE && qualityResolution <= preferredQuality)
// Use the lowest video quality if the default is lower than all available.
|| i == qualities.length - 1) {
final boolean qualityNeedsChange = (i != originalQualityIndex);
Logger.printDebug(() -> qualityNeedsChange
? "Changing video quality from: " + updatedCurrentQuality + " to: " + quality
: "Video is already the preferred quality: " + quality
);
// On first load of a new regular video, if the video is already the
// desired quality then the quality flyout will show 'Auto' (ie: Auto (720p)).
//
// To prevent user confusion, set the video index even if the
// quality is already correct so the UI picker will not display "Auto".
//
// Only change Shorts quality if the quality actually needs to change,
// because the "auto" option is not shown in the flyout
// and setting the same quality again can cause the Short to restart.
if (qualityNeedsChange || !ShortsPlayerState.isOpen()) {
menu.patch_setQuality(qualities[i]);
return i;
}
return originalQualityIndex;
}
i++;
}
// If the desired quality index is equal to the original index,
// then the video is already set to the desired default quality.
final int qualityToUseFinal = qualityToUse;
if (qualityIndexToUse == originalQualityIndex) {
// On first load of a new video, if the UI video quality flyout menu
// is not updated then it will still show 'Auto' (ie: Auto (480p)),
// even though it's already set to the desired resolution.
//
// To prevent confusion, set the video index anyways (even if it matches the existing index)
// as that will force the UI picker to not display "Auto".
Logger.printDebug(() -> "Video is already preferred quality: " + qualityToUseFinal);
} else {
Logger.printDebug(() -> "Changing video quality from: "
+ videoQualities.get(originalQualityIndex) + " to: " + qualityToUseFinal);
}
Method m = qInterface.getClass().getMethod(qIndexMethod, Integer.TYPE);
m.invoke(qInterface, qualityToUse);
return qualityIndexToUse;
} catch (Exception ex) {
Logger.printException(() -> "Failed to set quality", ex);
return originalQualityIndex;
Logger.printException(() -> "setVideoQuality failure", ex);
}
return originalQualityIndex;
}
/**
* Injection point.
* @param userSelectedQualityIndex Element index of {@link #currentQualities}.
*/
public static void userChangedShortsQuality(int userSelectedQualityIndex) {
try {
if (shouldRememberVideoQuality()) {
if (currentQualities == null) {
Logger.printDebug(() -> "Cannot save default quality, qualities is null");
return;
}
VideoQuality quality = currentQualities[userSelectedQualityIndex];
saveDefaultQuality(quality.patch_getResolution());
}
} catch (Exception ex) {
Logger.printException(() -> "userChangedShortsQuality failure", ex);
}
}
/**
* Injection point. Old quality menu.
* Injection point. Regular videos.
* @param videoResolution Human readable resolution: 480, 720, 1080.
*/
public static void userChangedQuality(int selectedQualityIndex) {
public static void userChangedQuality(int videoResolution) {
Utils.verifyOnMainThread();
if (shouldRememberVideoQuality()) {
userSelectedQualityIndex = selectedQualityIndex;
userChangedDefaultQuality = true;
saveDefaultQuality(videoResolution);
}
}
/**
* Injection point. New quality menu.
*/
public static void userChangedQualityInNewFlyout(int selectedQuality) {
if (!shouldRememberVideoQuality()) return;
changeDefaultQuality(selectedQuality); // Quality is human readable resolution (ie: 1080).
}
/**
* Injection point.
*/
public static void newVideoStarted(VideoInformation.PlaybackController ignoredPlayerController) {
Utils.verifyOnMainThread();
Logger.printDebug(() -> "newVideoStarted");
currentQualities = null;
currentQuality = null;
currentMenuInterface = null;
qualityNeedsUpdating = true;
videoQualities = null;
// Hide the quality button until playback starts and the qualities are available.
VideoQualityDialogButton.updateButtonIcon(null);
}
/**
* Injection point. Fixes bad data used by YouTube.
*/
public static int fixVideoQualityResolution(String name, int quality) {
final int correctQuality = 480;
if (name.equals("480p") && quality != correctQuality) {
return correctQuality;
}
return quality;
}
}

View File

@@ -23,7 +23,6 @@ import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.Animation;
@@ -42,7 +41,7 @@ import java.util.function.Function;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilterPatch;
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilter;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
import kotlin.Unit;
@@ -80,6 +79,16 @@ public class CustomPlaybackSpeedPatch {
*/
public static final float[] customPlaybackSpeeds;
/**
* Minimum and maximum custom playback speeds of {@link #customPlaybackSpeeds}.
*/
private static final float customPlaybackSpeedsMin, customPlaybackSpeedsMax;
/**
* The last time the old playback menu was forcefully called.
*/
private static volatile long lastTimeOldPlaybackMenuInvoked;
/**
* Formats speeds to UI strings.
*/
@@ -90,11 +99,6 @@ public class CustomPlaybackSpeedPatch {
*/
private static WeakReference<Dialog> currentDialog = new WeakReference<>(null);
/**
* Minimum and maximum custom playback speeds of {@link #customPlaybackSpeeds}.
*/
private static final float customPlaybackSpeedsMin, customPlaybackSpeedsMax;
static {
// Cap at 2 decimals (rounds automatically).
speedFormatter.setMaximumFractionDigits(2);
@@ -174,25 +178,33 @@ public class CustomPlaybackSpeedPatch {
public static void onFlyoutMenuCreate(RecyclerView recyclerView) {
recyclerView.getViewTreeObserver().addOnDrawListener(() -> {
try {
if (PlaybackSpeedMenuFilterPatch.isPlaybackRateSelectorMenuVisible) {
if (hideLithoMenuAndShowCustomSpeedMenu(recyclerView, 5)) {
PlaybackSpeedMenuFilterPatch.isPlaybackRateSelectorMenuVisible = false;
if (PlaybackSpeedMenuFilter.isPlaybackRateSelectorMenuVisible) {
if (hideLithoMenuAndShowSpeedMenu(recyclerView, 5)) {
PlaybackSpeedMenuFilter.isPlaybackRateSelectorMenuVisible = false;
}
}
} catch (Exception ex) {
Logger.printException(() -> "onFlyoutMenuCreate failure", ex);
Logger.printException(() -> "isPlaybackRateSelectorMenuVisible failure", ex);
}
try {
if (PlaybackSpeedMenuFilter.isOldPlaybackSpeedMenuVisible) {
if (hideLithoMenuAndShowSpeedMenu(recyclerView, 8)) {
PlaybackSpeedMenuFilter.isOldPlaybackSpeedMenuVisible = false;
}
}
} catch (Exception ex) {
Logger.printException(() -> "isOldPlaybackSpeedMenuVisible failure", ex);
}
});
}
@SuppressWarnings("SameParameterValue")
private static boolean hideLithoMenuAndShowCustomSpeedMenu(RecyclerView recyclerView, int expectedChildCount) {
private static boolean hideLithoMenuAndShowSpeedMenu(RecyclerView recyclerView, int expectedChildCount) {
if (recyclerView.getChildCount() == 0) {
return false;
}
View firstChild = recyclerView.getChildAt(0);
if (!(firstChild instanceof ViewGroup playbackSpeedParentView)) {
if (!(recyclerView.getChildAt(0) instanceof ViewGroup playbackSpeedParentView)) {
return false;
}
@@ -200,33 +212,49 @@ public class CustomPlaybackSpeedPatch {
return false;
}
ViewParent parentView3rd = Utils.getParentView(recyclerView, 3);
if (!(parentView3rd instanceof ViewGroup)) {
return true;
if (!(Utils.getParentView(recyclerView, 3) instanceof ViewGroup parentView3rd)) {
return false;
}
ViewParent parentView4th = parentView3rd.getParent();
if (!(parentView4th instanceof ViewGroup)) {
return true;
if (!(parentView3rd.getParent() instanceof ViewGroup parentView4th)) {
return false;
}
// Dismiss View [R.id.touch_outside] is the 1st ChildView of the 4th ParentView.
// This only shows in phone layout.
final var touchInsidedView = ((ViewGroup) parentView4th).getChildAt(0);
var touchInsidedView = parentView4th.getChildAt(0);
touchInsidedView.setSoundEffectsEnabled(false);
touchInsidedView.performClick();
// In tablet layout there is no Dismiss View, instead we just hide all two parent views.
((ViewGroup) parentView3rd).setVisibility(View.GONE);
((ViewGroup) parentView4th).setVisibility(View.GONE);
parentView3rd.setVisibility(View.GONE);
parentView4th.setVisibility(View.GONE);
// Close the litho speed menu and show the modern custom speed dialog.
showModernCustomPlaybackSpeedDialog(recyclerView.getContext());
Logger.printDebug(() -> "Modern playback speed dialog shown");
// Close the litho speed menu and show the custom speeds.
if (Settings.RESTORE_OLD_SPEED_MENU.get()) {
showOldPlaybackSpeedMenu();
Logger.printDebug(() -> "Old playback speed dialog shown");
} else {
showModernCustomPlaybackSpeedDialog(recyclerView.getContext());
Logger.printDebug(() -> "Modern playback speed dialog shown");
}
return true;
}
public static void showOldPlaybackSpeedMenu() {
// This method is sometimes used multiple times.
// To prevent this, ignore method reuse within 1 second.
final long now = System.currentTimeMillis();
if (now - lastTimeOldPlaybackMenuInvoked < 1000) {
Logger.printDebug(() -> "Ignoring call to showOldPlaybackSpeedMenu");
return;
}
lastTimeOldPlaybackMenuInvoked = now;
// Rest of the implementation added by patch.
}
/**
* Displays a modern custom dialog for adjusting video playback speed.
* <p>
@@ -643,11 +671,9 @@ public class CustomPlaybackSpeedPatch {
*/
public static int getAdjustedBackgroundColor(boolean isHandleBar) {
final int baseColor = Utils.getDialogBackgroundColor();
float darkThemeFactor = isHandleBar ? 1.25f : 1.115f; // 1.25f for handleBar, 1.115f for others in dark theme.
float lightThemeFactor = isHandleBar ? 0.9f : 0.95f; // 0.9f for handleBar, 0.95f for others in light theme.
return Utils.isDarkModeEnabled()
? Utils.adjustColorBrightness(baseColor, darkThemeFactor) // Lighten for dark theme.
: Utils.adjustColorBrightness(baseColor, lightThemeFactor); // Darken for light theme.
final float darkThemeFactor = isHandleBar ? 1.25f : 1.115f; // 1.25f for handleBar, 1.115f for others in dark theme.
final float lightThemeFactor = isHandleBar ? 0.9f : 0.95f; // 0.9f for handleBar, 0.95f for others in light theme.
return Utils.adjustColorBrightness(baseColor, lightThemeFactor, darkThemeFactor);
}
}

View File

@@ -5,6 +5,7 @@ import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.preference.PreferenceFragment;
import android.util.TypedValue;
import android.view.ViewGroup;
@@ -171,4 +172,10 @@ public class LicenseActivityHook extends Activity {
Utils.setIsDarkModeEnabled(themeOrdinal == 1);
}
}
public static void handleConfigurationChanged(Activity activity, Configuration newConfig) {
if (searchViewController != null) {
searchViewController.handleOrientationChange(newConfig.orientation);
}
}
}

View File

@@ -95,6 +95,7 @@ public class SearchViewController {
this.originalTitle = toolbar.getTitle();
this.showSettingsSearchHistory = Settings.SETTINGS_SEARCH_HISTORY.get();
this.searchHistory = new LinkedList<>();
this.currentOrientation = activity.getResources().getConfiguration().orientation;
StringSetting searchEntries = Settings.SETTINGS_SEARCH_ENTRIES;
if (showSettingsSearchHistory) {
String entries = searchEntries.get();
@@ -208,8 +209,6 @@ public class SearchViewController {
Logger.printException(() -> "navigation click failure", ex);
}
});
monitorOrientationChanges();
}
/**
@@ -292,19 +291,14 @@ public class SearchViewController {
}
}
private void monitorOrientationChanges() {
currentOrientation = activity.getResources().getConfiguration().orientation;
searchView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
int newOrientation = activity.getResources().getConfiguration().orientation;
if (newOrientation != currentOrientation) {
currentOrientation = newOrientation;
if (autoCompleteTextView != null) {
autoCompleteTextView.dismissDropDown();
Logger.printDebug(() -> "Orientation changed, search history dismissed");
}
public void handleOrientationChange(int newOrientation) {
if (newOrientation != currentOrientation) {
currentOrientation = newOrientation;
if (autoCompleteTextView != null) {
autoCompleteTextView.dismissDropDown();
Logger.printDebug(() -> "Orientation changed, search history dismissed");
}
});
}
}
/**
@@ -350,14 +344,14 @@ public class SearchViewController {
public static boolean handleBackPress() {
if (LicenseActivityHook.searchViewController != null
&& LicenseActivityHook.searchViewController.isSearchExpanded()) {
&& LicenseActivityHook.searchViewController.isSearchActive()) {
LicenseActivityHook.searchViewController.closeSearch();
return true;
}
return false;
}
public boolean isSearchExpanded() {
public boolean isSearchActive() {
return isSearchActive;
}

View File

@@ -68,8 +68,9 @@ public class Settings extends BaseSettings {
public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", FALSE);
public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_playback_speed_last_selected_toast", TRUE, false,
parent(REMEMBER_PLAYBACK_SPEED_LAST_SELECTED));
public static final BooleanSetting CUSTOM_SPEED_MENU = new BooleanSetting("revanced_custom_speed_menu", TRUE);
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
public static final BooleanSetting CUSTOM_SPEED_MENU = new BooleanSetting("revanced_custom_speed_menu", TRUE);
public static final BooleanSetting RESTORE_OLD_SPEED_MENU = new BooleanSetting("revanced_restore_old_speed_menu", FALSE, parent(CUSTOM_SPEED_MENU));
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.5\n3.0\n4.0\n5.0\n6.0\n7.0\n8.0", true);
@@ -171,6 +172,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_VIDEO_CHANNEL_WATERMARK = new BooleanSetting("revanced_hide_channel_watermark", TRUE);
public static final BooleanSetting OPEN_VIDEOS_FULLSCREEN_PORTRAIT = new BooleanSetting("revanced_open_videos_fullscreen_portrait", FALSE);
public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE);
public static final BooleanSetting VIDEO_QUALITY_DIALOG_BUTTON = new BooleanSetting("revanced_video_quality_dialog_button", FALSE);
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true);
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
@@ -191,7 +193,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action_button", FALSE);
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
"com.deniscerri.ytdl" /* YTDLnis */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
// Comments
public static final BooleanSetting HIDE_COMMENTS_AI_CHAT_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_chat_summary", FALSE);

View File

@@ -16,10 +16,8 @@ import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings({"unused", "deprecation"})
public final class CustomVideoSpeedListPreference extends CustomDialogListPreference {
/**
* Initialize a settings preference list with the available playback speeds.
*/
private void initializeEntryValues() {
{
// Initialize a settings preference list with the available playback speeds.
float[] customPlaybackSpeeds = CustomPlaybackSpeedPatch.customPlaybackSpeeds;
final int numberOfEntries = customPlaybackSpeeds.length + 1;
String[] preferenceListEntries = new String[numberOfEntries];
@@ -41,10 +39,6 @@ public final class CustomVideoSpeedListPreference extends CustomDialogListPrefer
setEntryValues(preferenceListEntryValues);
}
{
initializeEntryValues();
}
public CustomVideoSpeedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

View File

@@ -0,0 +1,444 @@
package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.sf;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.util.TypedValue;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
import app.revanced.extension.youtube.settings.Settings;
/**
* A custom ListPreference for selecting an external downloader package with checkmarks and EditText for custom package names.
*/
@SuppressWarnings({"unused", "deprecation"})
public class ExternalDownloaderPreference extends CustomDialogListPreference {
/**
* Enum representing supported external downloaders with their display names, package names, and download URLs.
*/
private enum Downloader {
YTDLNIS("YTDLnis",
"com.deniscerri.ytdl",
"https://ytdlnis.org",
true),
SEAL("Seal",
"com.junkfood.seal",
"https://github.com/JunkFood02/Seal/releases/latest",
true),
GRAYJAY("Grayjay",
"com.futo.platformplayer",
"https://grayjay.app"),
LIBRETUBE("LibreTube",
"com.github.libretube",
"https://libretube.dev"),
NEWPIPE("NewPipe",
"org.schabi.newpipe",
"https://newpipe.net"),
PIPEPIPE("PipePipe",
"InfinityLoop1309.NewPipeEnhanced",
"https://pipepipe.dev"),
TUBULAR("Tubular",
"org.polymorphicshade.tubular",
"https://github.com/polymorphicshade/Tubular/releases/latest"),
OTHER(sf("revanced_external_downloader_other_item").toString(),
null,
null,
true);
private static final Map<String, Downloader> PACKAGE_TO_ENUM = new HashMap<>();
static {
for (Downloader downloader : values()) {
String packageName = downloader.packageName;
if (packageName != null) {
PACKAGE_TO_ENUM.put(packageName, downloader);
}
}
}
/**
* Finds a Downloader by its package name. This method can never return {@link #OTHER}.
* @return The Downloader enum or null if not found.
*/
@Nullable
public static Downloader findByPackageName(String packageName) {
return PACKAGE_TO_ENUM.get(Objects.requireNonNull(packageName));
}
public final String name;
@Nullable
public final String packageName;
@Nullable
public final String downloadUrl;
/**
* If a downloader app should be shown in the preference settings
* if the app is not currently installed.
*/
public final boolean isPreferred;
Downloader(String name, String packageName, String downloadUrl) {
this(name, packageName, downloadUrl, false);
}
Downloader(String name, @Nullable String packageName, @Nullable String downloadUrl, boolean isPreferred) {
this.name = name;
this.packageName = packageName;
this.downloadUrl = downloadUrl;
this.isPreferred = isPreferred;
}
public boolean isInstalled() {
return packageName != null && isAppInstalledAndEnabled(packageName);
}
}
private static boolean isAppInstalledAndEnabled(String packageName) {
try {
if (Utils.getContext().getPackageManager().getApplicationInfo(packageName, 0).enabled) {
Logger.printDebug(() -> "App installed: " + packageName);
return true;
}
} catch (PackageManager.NameNotFoundException error) {
Logger.printDebug(() -> "App not installed: " + packageName);
}
return false;
}
private EditText editText;
private CustomDialogListPreference.ListPreferenceArrayAdapter adapter;
public ExternalDownloaderPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ExternalDownloaderPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ExternalDownloaderPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExternalDownloaderPreference(Context context) {
super(context);
}
private void updateEntries() {
List<CharSequence> entries = new ArrayList<>();
List<CharSequence> entryValues = new ArrayList<>();
for (Downloader downloader : Downloader.values()) {
if (downloader.isPreferred || downloader.isInstalled()) {
String packageName = downloader.packageName;
entries.add(downloader.name);
entryValues.add(packageName != null
? packageName
: Downloader.OTHER.name);
}
}
setEntries(entries.toArray(new CharSequence[0]));
setEntryValues(entryValues.toArray(new CharSequence[0]));
}
/**
* Sets the summary for this ListPreference.
*/
@Override
public void setSummary(CharSequence summary) {
// Ignore calls to set the summary.
// Summary is always the description of the category.
//
// This is required otherwise the ReVanced preference fragment
// sets all ListPreference summaries to show the current selection.
}
/**
* Shows a custom dialog with a ListView for predefined downloader packages and EditText for custom package input.
*/
@Override
protected void showDialog(@Nullable Bundle state) {
// Must set entries before showing the dialog, to handle if
// an app is installed while the settings are open in the background.
updateEntries();
Context context = getContext();
String packageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get();
// Create the main layout for the dialog content.
LinearLayout contentLayout = new LinearLayout(context);
contentLayout.setOrientation(LinearLayout.VERTICAL);
// Create ListView for predefined downloader apps.
ListView listView = new ListView(context);
listView.setId(android.R.id.list);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Create custom adapter for the ListView.
final boolean usingCustomDownloader = Downloader.findByPackageName(packageName) == null;
adapter = new CustomDialogListPreference.ListPreferenceArrayAdapter(
context,
Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"),
getEntries(),
getEntryValues(),
usingCustomDownloader
? Downloader.OTHER.name
: packageName
);
listView.setAdapter(adapter);
Function<String, Void> updateListViewSelection = (updatedPackageName) -> {
String entryValueName = Downloader.findByPackageName(updatedPackageName) == null
? Downloader.OTHER.name
: updatedPackageName;
CharSequence[] entryValues = getEntryValues();
for (int i = 0, length = entryValues.length; i < length; i++) {
String entryString = entryValues[i].toString();
if (entryString.equals(entryValueName)) {
listView.setItemChecked(i, true);
listView.setSelection(i);
adapter.setSelectedValue(entryString);
adapter.notifyDataSetChanged();
break;
}
}
return null;
};
updateListViewSelection.apply(packageName);
// Handle item click to select value.
listView.setOnItemClickListener((parent, view, position, id) -> {
String selectedValue = getEntryValues()[position].toString();
Downloader selectedApp = Downloader.findByPackageName(selectedValue);
if (selectedApp != null) {
editText.setText(selectedApp.packageName);
editText.setEnabled(false); // Disable editing for predefined options.
} else {
String savedPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.get();
editText.setText(Downloader.findByPackageName(savedPackageName) == null
? savedPackageName // If the user is clicking thru options then retain existing other app.
: ""
);
editText.setEnabled(true); // Enable editing for Custom.
editText.requestFocus();
}
editText.setSelection(editText.getText().length());
adapter.setSelectedValue(selectedValue);
adapter.notifyDataSetChanged();
});
// Add ListView to content layout with initial height.
LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
0 // Initial height, will be updated.
);
listViewParams.bottomMargin = dipToPixels(16);
contentLayout.addView(listView, listViewParams);
// Add EditText for custom package name.
editText = new EditText(context);
editText.setText(packageName);
editText.setSelection(packageName.length());
editText.setHint(str("revanced_external_downloader_other_item_hint"));
editText.setSingleLine(true); // Restrict EditText to a single line.
editText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
// Set initial EditText state based on selected downloader.
editText.setEnabled(usingCustomDownloader);
editText.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) {
String updatedPackageName = edit.toString().trim();
updateListViewSelection.apply(updatedPackageName);
}
});
ShapeDrawable editTextBackground = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(10), null, null));
editTextBackground.getPaint().setColor(Utils.getEditTextBackground());
final int dip8 = dipToPixels(8);
editText.setPadding(dip8, dip8, dip8, dip8);
editText.setBackground(editTextBackground);
editText.setClipToOutline(true);
contentLayout.addView(editText);
// Create the custom dialog.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
getTitle() != null ? getTitle().toString() : "",
null,
null,
null,
() -> {
String newValue = editText.getText().toString().trim();
if (newValue.isEmpty()) {
// Show dialog if EditText is empty.
Utils.createCustomDialog(
context,
str("revanced_external_downloader_name_title"),
str("revanced_external_downloader_empty_warning"),
null,
null,
() -> {}, // OK button does nothing (dismiss only).
null,
null,
null,
false
).first.show();
return;
}
if (showDialogIfAppIsNotInstalled(getContext(), newValue)) {
return; // Invalid package. Do not save.
}
// Save custom package name.
if (callChangeListener(newValue)) {
setValue(newValue);
}
},
() -> {}, // Cancel button action (dismiss only).
str("revanced_settings_reset"),
() -> { // Reset action.
String defaultValue = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME.defaultValue;
editText.setText(defaultValue);
editText.setSelection(defaultValue.length());
editText.setEnabled(false); // Disable editing on reset.
updateListViewSelection.apply(defaultValue);
},
false
);
// Add the content layout directly to the dialog's main layout.
LinearLayout dialogMainLayout = dialogPair.second;
dialogMainLayout.addView(contentLayout, dialogMainLayout.getChildCount() - 1);
// Update ListView height dynamically based on orientation.
//noinspection ExtractMethodRecommender
Runnable updateListViewHeight = () -> {
int totalHeight = 0;
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter != null) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
final int listAdapterCount = listAdapter.getCount();
for (int i = 0; i < listAdapterCount; i++) {
View item = listAdapter.getView(i, null, listView);
item.measure(
View.MeasureSpec.makeMeasureSpec(metrics.widthPixels, View.MeasureSpec.AT_MOST),
View.MeasureSpec.UNSPECIFIED
);
totalHeight += item.getMeasuredHeight();
}
totalHeight += listView.getDividerHeight() * (listAdapterCount - 1);
}
final int orientation = context.getResources().getConfiguration().orientation;
if (orientation == android.content.res.Configuration.ORIENTATION_PORTRAIT) {
// In portrait orientation, use WRAP_CONTENT for ListView height.
listViewParams.height = LinearLayout.LayoutParams.WRAP_CONTENT;
} else {
// In landscape orientation, limit ListView height to 30% of screen height.
final int maxHeight = Utils.percentageHeightToPixels(30);
listViewParams.height = Math.min(totalHeight, maxHeight);
}
listView.setLayoutParams(listViewParams);
};
// Initial height calculation.
updateListViewHeight.run();
// Listen for configuration changes (e.g., orientation).
View dialogView = dialogPair.second;
// Recalculate height when layout changes (e.g., orientation change).
dialogView.getViewTreeObserver().addOnGlobalLayoutListener(updateListViewHeight::run);
// Show the dialog.
dialogPair.first.show();
}
/**
* @return If the app is not installed and a dialog was shown.
*/
public static boolean showDialogIfAppIsNotInstalled(Context context, String packageName) {
if (isAppInstalledAndEnabled(packageName)) {
return false;
}
Downloader downloader = Downloader.findByPackageName(packageName);
String downloadUrl = downloader != null
? downloader.downloadUrl
: null;
String okButtonText = downloadUrl != null
? str("gms_core_dialog_open_website_text") // Open website.
: null; // Ok.
// Show a dialog if the recommended app is not installed or if the custom package cannot be found.
String message = downloader != null
? str("revanced_external_downloader_not_installed_warning", downloader.name)
: str("revanced_external_downloader_package_not_found_warning", packageName);
Utils.createCustomDialog(
context,
str("revanced_external_downloader_not_found_title"),
message,
null,
okButtonText,
() -> {
try {
// OK button action: open the downloader's URL if available.
if (downloadUrl != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(downloadUrl));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
} catch (Exception ex) {
Logger.printException(() -> "Failed to open downloader URL: " + downloader, ex);
}
},
() -> {}, // Cancel button action (dismiss only).
null,
null,
false
).first.show();
return true;
}
}

View File

@@ -280,7 +280,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
LicenseActivityHook.setToolbarLayoutParams(toolbar);
if (LicenseActivityHook.searchViewController != null
&& LicenseActivityHook.searchViewController.isSearchExpanded()) {
&& LicenseActivityHook.searchViewController.isSearchActive()) {
toolbar.post(() -> LicenseActivityHook.searchViewController.closeSearch());
}
@@ -333,10 +333,8 @@ class AbstractPreferenceSearchData<T extends Preference> {
return text;
}
final int baseColor = Utils.getAppBackgroundColor();
final int adjustedColor = Utils.isDarkModeEnabled()
? Utils.adjustColorBrightness(baseColor, 1.20f) // Lighten for dark theme.
: Utils.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme.
final int adjustedColor = Utils.adjustColorBrightness(Utils.getAppBackgroundColor(),
0.95f, 1.20f);
BackgroundColorSpan highlightSpan = new BackgroundColorSpan(adjustedColor);
SpannableStringBuilder spannable = new SpannableStringBuilder(text);

View File

@@ -830,11 +830,10 @@ public class SegmentPlaybackController {
WindowManager.LayoutParams params = window.getAttributes();
params.gravity = Gravity.BOTTOM;
params.y = dipToPixels(72);
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
int portraitWidth = (int) (displayMetrics.widthPixels * 0.6);
int portraitWidth = Utils.percentageWidthToPixels(60); // 60% of the screen width.
if (Resources.getSystem().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
portraitWidth = (int) Math.min(portraitWidth, displayMetrics.heightPixels * 0.6);
portraitWidth = Math.min(portraitWidth, Utils.percentageHeightToPixels(60)); // 60% of the screen height.
}
params.width = portraitWidth;
params.dimAmount = 0.0f;

View File

@@ -9,9 +9,6 @@ import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.Settings;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.showToastShort;
@SuppressWarnings("unused")
public class PlaybackSpeedDialogButton {
@Nullable
@@ -29,7 +26,11 @@ public class PlaybackSpeedDialogButton {
Settings.PLAYBACK_SPEED_DIALOG_BUTTON::get,
view -> {
try {
CustomPlaybackSpeedPatch.showModernCustomPlaybackSpeedDialog(view.getContext());
if (Settings.RESTORE_OLD_SPEED_MENU.get()) {
CustomPlaybackSpeedPatch.showOldPlaybackSpeedMenu();
} else {
CustomPlaybackSpeedPatch.showModernCustomPlaybackSpeedDialog(view.getContext());
}
} catch (Exception ex) {
Logger.printException(() -> "speed button onClick failure", ex);
}

View File

@@ -187,4 +187,56 @@ public class PlayerControlButton {
if (view != null) view.setVisibility(View.GONE);
isVisible = false;
}
}
/**
* Sets the icon of the button.
* @param resourceId Drawable identifier, or zero to hide the icon.
*/
public void setIcon(int resourceId) {
try {
View button = buttonRef.get();
if (button instanceof ImageView imageButton) {
imageButton.setImageResource(resourceId);
}
} catch (Exception ex) {
Logger.printException(() -> "setIcon failure", ex);
}
}
/**
* Starts an animation on the button.
* @param animation The animation to apply.
*/
public void startAnimation(Animation animation) {
try {
View button = buttonRef.get();
if (button != null) {
button.startAnimation(animation);
}
} catch (Exception ex) {
Logger.printException(() -> "startAnimation failure", ex);
}
}
/**
* Clears any animation on the button.
*/
public void clearAnimation() {
try {
View button = buttonRef.get();
if (button != null) {
button.clearAnimation();
}
} catch (Exception ex) {
Logger.printException(() -> "clearAnimation failure", ex);
}
}
/**
* Returns the View associated with this button.
* @return The button View.
*/
public View getView() {
return buttonRef.get();
}
}

View File

@@ -0,0 +1,482 @@
package app.revanced.extension.youtube.videoplayer;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.AUTOMATIC_VIDEO_QUALITY_VALUE;
import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.VIDEO_QUALITY_1080P_PREMIUM_NAME;
import static app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch.VideoQualityMenuInterface;
import android.app.Dialog;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.libraries.youtube.innertube.model.media.VideoQuality;
import java.util.ArrayList;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class VideoQualityDialogButton {
private static final int DRAWABLE_LD = getDrawableIdentifier("revanced_video_quality_dialog_button_ld");
private static final int DRAWABLE_SD = getDrawableIdentifier("revanced_video_quality_dialog_button_sd");
private static final int DRAWABLE_HD = getDrawableIdentifier("revanced_video_quality_dialog_button_hd");
private static final int DRAWABLE_FHD = getDrawableIdentifier("revanced_video_quality_dialog_button_fhd");
private static final int DRAWABLE_FHD_PLUS = getDrawableIdentifier("revanced_video_quality_dialog_button_fhd_plus");
private static final int DRAWABLE_QHD = getDrawableIdentifier("revanced_video_quality_dialog_button_qhd");
private static final int DRAWABLE_4K = getDrawableIdentifier("revanced_video_quality_dialog_button_4k");
private static final int DRAWABLE_UNKNOWN = getDrawableIdentifier("revanced_video_quality_dialog_button_unknown");
@Nullable
private static PlayerControlButton instance;
/**
* The current resource name of the button icon.
*/
private static int currentIconResource;
private static int getDrawableIdentifier(String resourceName) {
final int resourceId = Utils.getResourceIdentifier(resourceName, "drawable");
if (resourceId == 0) Logger.printException(() -> "Could not find resource: " + resourceName);
return resourceId;
}
/**
* Updates the button icon based on the current video quality.
*/
public static void updateButtonIcon(@Nullable VideoQuality quality) {
try {
Utils.verifyOnMainThread();
if (instance == null) return;
final int resolution = quality == null
? AUTOMATIC_VIDEO_QUALITY_VALUE // Video is still loading.
: quality.patch_getResolution();
final int iconResource = switch (resolution) {
case 144, 240, 360 -> DRAWABLE_LD;
case 480 -> DRAWABLE_SD;
case 720 -> DRAWABLE_HD;
case 1080 -> VIDEO_QUALITY_1080P_PREMIUM_NAME.equals(quality.patch_getQualityName())
? DRAWABLE_FHD_PLUS
: DRAWABLE_FHD;
case 1440 -> DRAWABLE_QHD;
case 2160 -> DRAWABLE_4K;
default -> DRAWABLE_UNKNOWN;
};
if (iconResource != currentIconResource) {
currentIconResource = iconResource;
Utils.runOnMainThreadDelayed(() -> {
if (iconResource != currentIconResource) {
Logger.printDebug(() -> "Ignoring stale button update to: " + quality);
return;
}
instance.setIcon(iconResource);
}, 100);
}
} catch (Exception ex) {
Logger.printException(() -> "updateButtonIcon failure", ex);
}
}
/**
* Injection point.
*/
public static void initializeButton(View controlsView) {
try {
instance = new PlayerControlButton(
controlsView,
"revanced_video_quality_dialog_button",
"revanced_video_quality_dialog_button_placeholder",
Settings.VIDEO_QUALITY_DIALOG_BUTTON::get,
view -> {
try {
showVideoQualityDialog(view.getContext());
} catch (Exception ex) {
Logger.printException(() -> "Video quality button onClick failure", ex);
}
},
view -> {
try {
VideoQuality[] qualities = RememberVideoQualityPatch.getCurrentQualities();
VideoQualityMenuInterface menu = RememberVideoQualityPatch.getCurrentMenuInterface();
if (qualities == null || menu == null) {
Logger.printDebug(() -> "Cannot reset quality, videoQualities is null");
return true;
}
// Reset to default quality.
final int defaultResolution = RememberVideoQualityPatch.getDefaultQualityResolution();
for (VideoQuality quality : qualities) {
final int resolution = quality.patch_getResolution();
if (resolution != AUTOMATIC_VIDEO_QUALITY_VALUE && resolution <= defaultResolution) {
Logger.printDebug(() -> "Resetting quality to: " + quality);
menu.patch_setQuality(quality);
return true;
}
}
// Existing hook cannot set default quality to auto.
// Instead show the quality dialog.
showVideoQualityDialog(view.getContext());
return true;
} catch (Exception ex) {
Logger.printException(() -> "Video quality button reset failure", ex);
}
return false;
}
);
// Set initial icon.
updateButtonIcon(RememberVideoQualityPatch.getCurrentQuality());
} catch (Exception ex) {
Logger.printException(() -> "initializeButton failure", ex);
}
}
/**
* Injection point.
*/
public static void setVisibilityImmediate(boolean visible) {
if (instance != null) {
instance.setVisibilityImmediate(visible);
}
}
/**
* Injection point.
*/
public static void setVisibility(boolean visible, boolean animated) {
if (instance != null) {
instance.setVisibility(visible, animated);
}
}
/**
* Shows a dialog with available video qualities, excluding Auto, with a title showing the current quality.
*/
private static void showVideoQualityDialog(Context context) {
try {
VideoQuality[] currentQualities = RememberVideoQualityPatch.getCurrentQualities();
VideoQuality currentQuality = RememberVideoQualityPatch.getCurrentQuality();
if (currentQualities == null || currentQuality == null) {
Logger.printDebug(() -> "Cannot show qualities dialog, videoQualities is null");
return;
}
if (currentQualities.length < 2) {
// Should never happen.
Logger.printException(() -> "Cannot show qualities dialog, no qualities available");
return;
}
VideoQualityMenuInterface menu = RememberVideoQualityPatch.getCurrentMenuInterface();
if (menu == null) {
Logger.printDebug(() -> "Cannot show qualities dialog, menu is null");
return;
}
// -1 adjustment for automatic quality at first index.
int listViewSelectedIndex = -1;
for (VideoQuality quality : currentQualities) {
if (quality.patch_getQualityName().equals(currentQuality.patch_getQualityName())) {
break;
}
listViewSelectedIndex++;
}
List<String> qualityLabels = new ArrayList<>(currentQualities.length - 1);
for (VideoQuality availableQuality : currentQualities) {
if (availableQuality.patch_getResolution() != AUTOMATIC_VIDEO_QUALITY_VALUE) {
qualityLabels.add(availableQuality.patch_getQualityName());
}
}
Dialog dialog = new Dialog(context);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setCanceledOnTouchOutside(true);
dialog.setCancelable(true);
final int dip4 = dipToPixels(4); // Height for handle bar.
final int dip5 = dipToPixels(5); // Padding for mainLayout.
final int dip6 = dipToPixels(6); // Bottom margin.
final int dip8 = dipToPixels(8); // Side padding.
final int dip16 = dipToPixels(16); // Left padding for ListView.
final int dip20 = dipToPixels(20); // Margin below handle.
final int dip40 = dipToPixels(40); // Width for handle bar.
LinearLayout mainLayout = new LinearLayout(context);
mainLayout.setOrientation(LinearLayout.VERTICAL);
mainLayout.setPadding(dip5, dip8, dip5, dip8);
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(12), null, null));
background.getPaint().setColor(Utils.getDialogBackgroundColor());
mainLayout.setBackground(background);
View handleBar = new View(context);
ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(4), null, null));
final int baseColor = Utils.getDialogBackgroundColor();
final int adjustedHandleBarBackgroundColor = Utils.adjustColorBrightness(
baseColor, 0.9f, 1.25f);
handleBackground.getPaint().setColor(adjustedHandleBarBackgroundColor);
handleBar.setBackground(handleBackground);
LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(dip40, dip4);
handleParams.gravity = Gravity.CENTER_HORIZONTAL;
handleParams.setMargins(0, 0, 0, dip20);
handleBar.setLayoutParams(handleParams);
mainLayout.addView(handleBar);
// Create SpannableStringBuilder for formatted text.
SpannableStringBuilder spannableTitle = new SpannableStringBuilder();
String titlePart = str("video_quality_quick_menu_title");
String separatorPart = str("video_quality_title_seperator");
// Append title part with default foreground color.
spannableTitle.append(titlePart);
spannableTitle.setSpan(
new ForegroundColorSpan(Utils.getAppForegroundColor()),
0,
titlePart.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
spannableTitle.append(" "); // Space after title.
// Append separator part with adjusted title color.
int separatorStart = spannableTitle.length();
spannableTitle.append(separatorPart);
final int adjustedTitleForegroundColor = Utils.adjustColorBrightness(
Utils.getAppForegroundColor(), 1.6f, 0.6f);
spannableTitle.setSpan(
new ForegroundColorSpan(adjustedTitleForegroundColor),
separatorStart,
separatorStart + separatorPart.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
spannableTitle.append(" "); // Space after separator.
// Append quality label with adjusted title color.
final int qualityStart = spannableTitle.length();
spannableTitle.append(currentQuality.patch_getQualityName());
spannableTitle.setSpan(
new ForegroundColorSpan(adjustedTitleForegroundColor),
qualityStart,
qualityStart + currentQuality.patch_getQualityName().length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
// Add title with current quality.
TextView titleView = new TextView(context);
titleView.setText(spannableTitle);
titleView.setTextSize(16);
// Remove setTextColor since color is handled by SpannableStringBuilder.
LinearLayout.LayoutParams titleParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
titleParams.setMargins(dip8, 0, 0, dip20);
titleView.setLayoutParams(titleParams);
mainLayout.addView(titleView);
ListView listView = new ListView(context);
CustomQualityAdapter adapter = new CustomQualityAdapter(context, qualityLabels);
adapter.setSelectedPosition(listViewSelectedIndex);
listView.setAdapter(adapter);
listView.setDivider(null);
listView.setPadding(dip16, 0, 0, 0);
listView.setOnItemClickListener((parent, view, which, id) -> {
try {
final int originalIndex = which + 1; // Adjust for automatic.
VideoQuality selectedQuality = currentQualities[originalIndex];
Logger.printDebug(() -> "User clicked on quality: " + selectedQuality);
if (RememberVideoQualityPatch.shouldRememberVideoQuality()) {
RememberVideoQualityPatch.saveDefaultQuality(selectedQuality.patch_getResolution());
}
// Don't update button icon now. Icon will update when the actual
// quality is changed by YT. This is needed to ensure the icon is correct
// if YT ignores changing from 1080p Premium to regular 1080p.
menu.patch_setQuality(selectedQuality);
dialog.dismiss();
} catch (Exception ex) {
Logger.printException(() -> "Video quality selection failure", ex);
}
});
LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
listViewParams.setMargins(0, 0, 0, dip5);
listView.setLayoutParams(listViewParams);
mainLayout.addView(listView);
LinearLayout wrapperLayout = new LinearLayout(context);
wrapperLayout.setOrientation(LinearLayout.VERTICAL);
wrapperLayout.setPadding(dip8, 0, dip8, 0);
wrapperLayout.addView(mainLayout);
dialog.setContentView(wrapperLayout);
Window window = dialog.getWindow();
if (window != null) {
WindowManager.LayoutParams params = window.getAttributes();
params.gravity = Gravity.BOTTOM;
params.y = dip6;
int portraitWidth = context.getResources().getDisplayMetrics().widthPixels;
if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
portraitWidth = Math.min(
portraitWidth,
context.getResources().getDisplayMetrics().heightPixels);
}
params.width = portraitWidth;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setAttributes(params);
window.setBackgroundDrawable(null);
}
final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast");
Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom");
slideInABottomAnimation.setDuration(fadeDurationFast);
mainLayout.startAnimation(slideInABottomAnimation);
// noinspection ClickableViewAccessibility
mainLayout.setOnTouchListener(new View.OnTouchListener() {
final float dismissThreshold = dipToPixels(100);
float touchY;
float translationY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchY = event.getRawY();
translationY = mainLayout.getTranslationY();
return true;
case MotionEvent.ACTION_MOVE:
final float deltaY = event.getRawY() - touchY;
if (deltaY >= 0) {
mainLayout.setTranslationY(translationY + deltaY);
}
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mainLayout.getTranslationY() > dismissThreshold) {
//noinspection ExtractMethodRecommender
final float remainingDistance = context.getResources().getDisplayMetrics().heightPixels
- mainLayout.getTop();
TranslateAnimation slideOut = new TranslateAnimation(
0, 0, mainLayout.getTranslationY(), remainingDistance);
slideOut.setDuration(fadeDurationFast);
slideOut.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
dialog.dismiss();
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
mainLayout.startAnimation(slideOut);
} else {
TranslateAnimation slideBack = new TranslateAnimation(
0, 0, mainLayout.getTranslationY(), 0);
slideBack.setDuration(fadeDurationFast);
mainLayout.startAnimation(slideBack);
mainLayout.setTranslationY(0);
}
return true;
default:
return false;
}
}
});
dialog.show();
} catch (Exception ex) {
Logger.printException(() -> "showVideoQualityDialog failure", ex);
}
}
private static class CustomQualityAdapter extends ArrayAdapter<String> {
private int selectedPosition = -1;
public CustomQualityAdapter(@NonNull Context context, @NonNull List<String> objects) {
super(context, 0, objects);
}
private void setSelectedPosition(int position) {
this.selectedPosition = position;
notifyDataSetChanged();
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(
Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"),
parent,
false
);
viewHolder = new ViewHolder();
viewHolder.checkIcon = convertView.findViewById(
Utils.getResourceIdentifier("revanced_check_icon", "id")
);
viewHolder.placeholder = convertView.findViewById(
Utils.getResourceIdentifier("revanced_check_icon_placeholder", "id")
);
viewHolder.textView = convertView.findViewById(
Utils.getResourceIdentifier("revanced_item_text", "id")
);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.textView.setText(getItem(position));
final boolean isSelected = position == selectedPosition;
viewHolder.checkIcon.setVisibility(isSelected ? View.VISIBLE : View.GONE);
viewHolder.placeholder.setVisibility(isSelected ? View.GONE : View.INVISIBLE);
return convertView;
}
private static class ViewHolder {
ImageView checkIcon;
View placeholder;
TextView textView;
}
}
}

View File

@@ -0,0 +1,8 @@
package com.google.android.libraries.youtube.innertube.model.media;
public abstract class VideoQuality implements Comparable<VideoQuality> {
public abstract String patch_getQualityName();
public abstract int patch_getResolution();
}

View File

@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true
android.useAndroidX = true
kotlin.code.style = official
version = 5.31.3-dev.1
version = 5.33.0-dev.13

9
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.10.1",
"semantic-release": "^24.2.6"
"semantic-release": "^24.2.7"
}
},
"node_modules/@babel/code-frame": {
@@ -6889,10 +6889,11 @@
"license": "MIT"
},
"node_modules/semantic-release": {
"version": "24.2.6",
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.6.tgz",
"integrity": "sha512-D0cwjlO5RZzHHxAcsoF1HxiRLfC3ehw+ay+zntzFs6PNX6aV0JzKNG15mpxPipBYa/l4fHly88dHvgDyqwb1Ww==",
"version": "24.2.7",
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.7.tgz",
"integrity": "sha512-g7RssbTAbir1k/S7uSwSVZFfFXwpomUB9Oas0+xi9KStSCmeDXcA7rNhiskjLqvUe/Evhx8fVCT16OSa34eM5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@semantic-release/commit-analyzer": "^13.0.0-beta.1",
"@semantic-release/error": "^4.0.0",

View File

@@ -4,6 +4,6 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.10.1",
"semantic-release": "^24.2.6"
"semantic-release": "^24.2.7"
}
}

View File

@@ -424,6 +424,10 @@ public final class app/revanced/patches/openinghours/misc/fix/crash/FixCrashPatc
public static final fun getFixCrashPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/orfon/detection/root/RemoveRootDetectionPatchKt {
public static final fun getRemoveRootDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/pandora/ads/DisableAudioAdsPatchKt {
public static final fun getDisableAudioAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -476,6 +480,10 @@ public final class app/revanced/patches/primevideo/misc/permissions/RenamePermis
public static final fun getRenamePermissionsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/primevideo/video/speed/PlaybackSpeedPatchKt {
public static final fun getPlaybackSpeedPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/protonmail/account/RemoveFreeAccountsLimitPatchKt {
public static final fun getRemoveFreeAccountsLimitPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
@@ -1656,6 +1664,10 @@ public final class app/revanced/patches/youtube/video/quality/VideoQualityPatchK
public static final fun getVideoQualityPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/video/quality/button/VideoQualityDialogButtonPatchKt {
public static final fun getVideoQualityButtonPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/video/speed/PlaybackSpeedPatchKt {
public static final fun getPlaybackSpeedPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}

View File

@@ -23,7 +23,6 @@ internal val createInboxSubTabsFingerprint = fingerprint {
}
internal val loadInboxAdsFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("V")
strings(
"ads_load_begin",

View File

@@ -5,6 +5,5 @@ import app.revanced.patcher.fingerprint
internal val isFacebookButtonEnabledFingerprint = fingerprint {
parameters()
returns("Z")
strings("com.facebook.messaging.inbox.tab.plugins.core.tabtoolbarbutton." +
"facebookbutton.facebooktoolbarbutton.FacebookButtonTabButtonImplementation")
strings("FacebookButtonTabButtonImplementation")
}

View File

@@ -8,12 +8,7 @@ val hideVideoAdsPatch = bytecodePatch(
name = "Hide music video ads",
description = "Hides ads that appear while listening to or streaming music videos, podcasts, or songs.",
) {
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.16.53",
"8.05.51"
)
)
compatibleWith("com.google.android.apps.youtube.music")
execute {
navigate(showVideoAdsParentFingerprint.originalMethod)

View File

@@ -8,12 +8,7 @@ val enableExclusiveAudioPlaybackPatch = bytecodePatch(
name = "Enable exclusive audio playback",
description = "Enables the option to play audio without video.",
) {
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.16.53",
"8.05.51"
)
)
compatibleWith("com.google.android.apps.youtube.music")
execute {
allowExclusiveAudioPlaybackFingerprint.method.returnEarly(true)

View File

@@ -11,12 +11,7 @@ val permanentRepeatPatch = bytecodePatch(
description = "Permanently remember your repeating preference even if the playlist ends or another track is played.",
use = false,
) {
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.16.53",
"8.05.51"
)
)
compatibleWith("com.google.android.apps.youtube.music")
execute {
val startIndex = repeatTrackFingerprint.patternMatch!!.endIndex

View File

@@ -7,18 +7,9 @@ import app.revanced.patcher.patch.bytecodePatch
@Suppress("unused")
val permanentShufflePatch = bytecodePatch(
description = "Permanently remember your shuffle preference " +
"even if the playlist ends or another track is played.",
use = false,
"even if the playlist ends or another track is played."
) {
compatibleWith(
"com.google.android.apps.youtube.music"(
"6.45.54",
"6.51.53",
"7.01.53",
"7.02.52",
"7.03.52",
),
)
compatibleWith("com.google.android.apps.youtube.music")
execute {
disableShuffleFingerprint.method.addInstruction(0, "return-void")

View File

@@ -11,12 +11,7 @@ val hideCategoryBar = bytecodePatch(
description = "Hides the category bar at the top of the homepage.",
use = false,
) {
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.16.53",
"8.05.51"
)
)
compatibleWith("com.google.android.apps.youtube.music")
execute {
constructCategoryBarFingerprint.method.apply {

View File

@@ -11,12 +11,7 @@ val hideGetPremiumPatch = bytecodePatch(
name = "Hide 'Get Music Premium' label",
description = "Hides the \"Get Music Premium\" label from the account menu and settings.",
) {
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.16.53",
"8.05.51"
)
)
compatibleWith("com.google.android.apps.youtube.music")
execute {
hideGetPremiumFingerprint.method.apply {

View File

@@ -18,12 +18,7 @@ val removeUpgradeButtonPatch = bytecodePatch(
name = "Remove upgrade button",
description = "Removes the upgrade tab from the pivot bar.",
) {
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.16.53",
"8.05.51"
)
)
compatibleWith("com.google.android.apps.youtube.music")
execute {
pivotBarConstructorFingerprint.method.apply {

View File

@@ -8,12 +8,7 @@ val bypassCertificateChecksPatch = bytecodePatch(
name = "Bypass certificate checks",
description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.",
) {
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.16.53",
"8.05.51"
)
)
compatibleWith("com.google.android.apps.youtube.music")
execute {
checkCertificateFingerprint.method.returnEarly(true)

View File

@@ -8,12 +8,7 @@ val backgroundPlaybackPatch = bytecodePatch(
name = "Remove background playback restrictions",
description = "Removes restrictions on background playback, including playing kids videos in the background.",
) {
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.16.53",
"8.05.51"
)
)
compatibleWith("com.google.android.apps.youtube.music")
execute {
kidsBackgroundPlaybackPolicyControllerFingerprint.method.addInstruction(

View File

@@ -25,12 +25,7 @@ val spoofClientPatch = bytecodePatch(
name = "Spoof client",
description = "Spoofs the client to fix playback.",
) {
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.16.53",
"8.05.51"
)
)
compatibleWith("com.google.android.apps.youtube.music")
dependsOn(
sharedExtensionPatch,

View File

@@ -3,6 +3,7 @@ package app.revanced.patches.nfctoolsse.misc.pro
import com.android.tools.smali.dexlib2.AccessFlags
import app.revanced.patcher.fingerprint
@Deprecated("This patch no longer works and will soon be deleted.")
internal val isLicenseRegisteredFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
returns("Z")

View File

@@ -4,9 +4,8 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
@Suppress("unused")
val unlockProPatch = bytecodePatch(
name = "Unlock pro",
) {
@Deprecated("This patch no longer works and will soon be deleted.")
val unlockProPatch = bytecodePatch{
compatibleWith("com.wakdev.apps.nfctools.se")
execute {

View File

@@ -0,0 +1,13 @@
package app.revanced.patches.orfon.detection.root
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal val isDeviceRootedFingeprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
returns("Z")
custom { method, classDef ->
method.name == "isDeviceRooted" &&
classDef.endsWith("/RootChecker;")
}
}

View File

@@ -0,0 +1,16 @@
package app.revanced.patches.orfon.detection.root
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val removeRootDetectionPatch = bytecodePatch(
name = "Remove root detection",
description = "Removes the check for root permissions.",
) {
compatibleWith("com.nousguide.android.orftvthek")
execute {
isDeviceRootedFingeprint.method.returnEarly(false)
}
}

View File

@@ -12,7 +12,7 @@ val skipAdsPatch = bytecodePatch(
name = "Skip ads",
description = "Automatically skips video stream ads.",
) {
compatibleWith("com.amazon.avod.thirdpartyclient"("3.0.403.257"))
compatibleWith("com.amazon.avod.thirdpartyclient"("3.0.412.2947"))
dependsOn(sharedExtensionPatch)

View File

@@ -0,0 +1,23 @@
package app.revanced.patches.primevideo.video.speed
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal val playbackUserControlsInitializeFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
parameters("Lcom/amazon/avod/playbackclient/PlaybackInitializationContext;")
returns("V")
custom { method, classDef ->
method.name == "initialize" && classDef.type == "Lcom/amazon/avod/playbackclient/activity/feature/PlaybackUserControlsFeature;"
}
}
internal val playbackUserControlsPrepareForPlaybackFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
parameters("Lcom/amazon/avod/playbackclient/PlaybackContext;")
returns("V")
custom { method, classDef ->
method.name == "prepareForPlayback" &&
classDef.type == "Lcom/amazon/avod/playbackclient/activity/feature/PlaybackUserControlsFeature;"
}
}

View File

@@ -0,0 +1,56 @@
package app.revanced.patches.primevideo.video.speed
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.primevideo.misc.extension.sharedExtensionPatch
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/primevideo/videoplayer/PlaybackSpeedPatch;"
val playbackSpeedPatch = bytecodePatch(
name = "Playback speed",
description = "Adds playback speed controls to the video player.",
) {
dependsOn(
sharedExtensionPatch,
)
compatibleWith(
"com.amazon.avod.thirdpartyclient"("3.0.412.2947")
)
execute {
playbackUserControlsInitializeFingerprint.method.apply {
val getIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.IPUT_OBJECT &&
getReference<FieldReference>()?.name == "mUserControls"
}
val getRegister = getInstruction<OneRegisterInstruction>(getIndex).registerA
addInstructions(
getIndex + 1,
"""
invoke-static { v$getRegister }, $EXTENSION_CLASS_DESCRIPTOR->initializeSpeedOverlay(Landroid/view/View;)V
"""
)
}
playbackUserControlsPrepareForPlaybackFingerprint.method.apply {
addInstructions(
0,
"""
invoke-virtual { p1 }, Lcom/amazon/avod/playbackclient/PlaybackContext;->getPlayer()Lcom/amazon/video/sdk/player/Player;
move-result-object v0
invoke-static { v0 }, $EXTENSION_CLASS_DESCRIPTOR->setPlayer(Lcom/amazon/video/sdk/player/Player;)V
"""
)
}
}
}

View File

@@ -6,7 +6,6 @@ import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.InputType
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
@@ -40,7 +39,10 @@ private val downloadsResourcePatch = resourcePatch {
preferences = setOf(
SwitchPreference("revanced_external_downloader"),
SwitchPreference("revanced_external_downloader_action_button"),
TextPreference("revanced_external_downloader_name", inputType = InputType.TEXT),
TextPreference(
"revanced_external_downloader_name",
tag = "app.revanced.extension.youtube.settings.preference.ExternalDownloaderPreference",
),
),
),
)

View File

@@ -99,7 +99,7 @@ val hideInfoCardsPatch = bytecodePatch(
)
// Info cards can also appear as Litho components.
val filterClassDescriptor = "Lapp/revanced/extension/youtube/patches/components/HideInfoCardsFilterPatch;"
val filterClassDescriptor = "Lapp/revanced/extension/youtube/patches/components/HideInfoCardsFilter;"
addLithoFilter(filterClassDescriptor)
}
}

View File

@@ -43,7 +43,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/ReturnYouTubeDislikePatch;"
private const val FILTER_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/components/ReturnYouTubeDislikeFilterPatch;"
"Lapp/revanced/extension/youtube/patches/components/ReturnYouTubeDislikeFilter;"
val returnYouTubeDislikePatch = bytecodePatch(
name = "Return YouTube Dislike",

View File

@@ -156,7 +156,7 @@ val lithoFilterPatch = bytecodePatch(
move-object/from16 v$freeRegister, p2
iget-object v$identifierRegister, v$freeRegister, $conversionContextIdentifierField
iget-object v$pathRegister, v$freeRegister, $conversionContextPathBuilderField
invoke-static { v$identifierRegister, v$pathRegister }, $EXTENSION_CLASS_DESCRIPTOR->shouldFilter(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
invoke-static { v$identifierRegister, v$pathRegister }, $EXTENSION_CLASS_DESCRIPTOR->isFiltered(Ljava/lang/String;Ljava/lang/StringBuilder;)Z
move-result v$freeRegister
if-eqz v$freeRegister, :unfiltered

View File

@@ -11,7 +11,6 @@ val recyclerViewTreeHookPatch = bytecodePatch {
dependsOn(sharedExtensionPatch)
execute {
recyclerViewTreeObserverFingerprint.method.apply {
val insertIndex = recyclerViewTreeObserverFingerprint.patternMatch!!.startIndex + 1
val recyclerViewParameter = 2

View File

@@ -235,9 +235,9 @@ val settingsPatch = bytecodePatch(
methods.removeIf { it.name != "onCreate" && !MethodUtil.isConstructor(it) }
}
// Add context override to force a specific settings language.
licenseActivityOnCreateFingerprint.classDef.apply {
val attachBaseContext = ImmutableMethod(
// Add attachBaseContext method to override the context for setting a specific language.
ImmutableMethod(
type,
"attachBaseContext",
listOf(ImmutableMethodParameter("Landroid/content/Context;", null, null)),
@@ -255,13 +255,10 @@ val settingsPatch = bytecodePatch(
return-void
"""
)
}
}.let(methods::add)
methods.add(attachBaseContext)
}
licenseActivityOnCreateFingerprint.classDef.apply {
val onBackPressed = ImmutableMethod(
// Add onBackPressed method to handle back button presses, delegating to SearchViewController.
ImmutableMethod(
type,
"onBackPressed",
emptyList(),
@@ -269,7 +266,7 @@ val settingsPatch = bytecodePatch(
AccessFlags.PUBLIC.value,
null,
null,
MutableMethodImplementation(3)
MutableMethodImplementation(3),
).toMutable().apply {
addInstructions(
"""
@@ -281,10 +278,28 @@ val settingsPatch = bytecodePatch(
return-void
"""
)
}.let(methods::add)
};
methods.add(onBackPressed);
};
// Add onConfigurationChanged method to handle configuration changes (e.g., screen orientation).
ImmutableMethod(
type,
"onConfigurationChanged",
listOf(ImmutableMethodParameter("Landroid/content/res/Configuration;", null, null)),
"V",
AccessFlags.PUBLIC.value,
null,
null,
MutableMethodImplementation(3)
).toMutable().apply {
addInstructions(
"""
invoke-super { p0, p1 }, Landroid/app/Activity;->onConfigurationChanged(Landroid/content/res/Configuration;)V
invoke-static { p0, p1 }, $EXTENSION_CLASS_DESCRIPTOR->handleConfigurationChanged(Landroid/app/Activity;Landroid/content/res/Configuration;)V
return-void
"""
)
}.let(methods::add)
}
// Update shared dark mode status based on YT theme.
// This is needed because YT allows forcing light/dark mode

View File

@@ -121,7 +121,7 @@ internal val subtitleButtonControllerFingerprint = fingerprint {
)
}
internal val newVideoQualityChangedFingerprint = fingerprint {
internal val videoQualityChangedFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("L")
parameters("L")

View File

@@ -1,23 +1,27 @@
package app.revanced.patches.youtube.video.audio
import app.revanced.patcher.fingerprint
import app.revanced.util.containsLiteralInstruction
import com.android.tools.smali.dexlib2.AccessFlags
internal val streamingModelBuilderFingerprint = fingerprint {
internal val formatStreamModelToStringFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("L")
strings("vprng")
returns("Ljava/lang/String;")
custom { method, classDef ->
method.name == "toString" && classDef.type ==
"Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
}
}
internal val menuItemAudioTrackFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameters("L")
returns("V")
strings("menu_item_audio_track")
internal const val AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG = 45666189L
internal val selectAudioStreamFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("L")
custom { method, _ ->
method.parameters.size > 2 // Method has a large number of parameters and may change.
&& method.parameters[1].type == "Lcom/google/android/libraries/youtube/innertube/model/media/PlayerConfigModel;"
&& method.containsLiteralInstruction(AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG)
}
}
internal val audioStreamingTypeSelector = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
returns("L")
strings("raw") // String is not unique
}

View File

@@ -5,22 +5,22 @@ 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.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.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.getReference
import app.revanced.util.findMethodFromToString
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.insertLiteralOverride
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.ImmutableField
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
@@ -37,6 +37,7 @@ val forceOriginalAudioPatch = bytecodePatch(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
versionCheckPatch
)
compatibleWith(
@@ -60,29 +61,25 @@ val forceOriginalAudioPatch = bytecodePatch(
)
)
fun Method.firstFormatStreamingModelCall(
returnType: String = "Ljava/lang/String;"
): MutableMethod {
val audioTrackIdIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == "Lcom/google/android/libraries/youtube/innertube/model/media/FormatStreamModel;"
&& reference.returnType == returnType
}
return navigate(this).to(audioTrackIdIndex).stop()
// Disable feature flag that ignores the default track flag
// and instead overrides to the user region language.
if (is_20_07_or_greater) {
selectAudioStreamFingerprint.method.insertLiteralOverride(
AUDIO_STREAM_IGNORE_DEFAULT_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->ignoreDefaultAudioStream(Z)Z"
)
}
// Accessor methods of FormatStreamModel have no string constants and
// opcodes are identical to other methods in the same class,
// so must walk from another class that use the methods.
val isDefaultMethod = streamingModelBuilderFingerprint.originalMethod.firstFormatStreamingModelCall("Z")
val audioTrackIdMethod = menuItemAudioTrackFingerprint.originalMethod.firstFormatStreamingModelCall()
val audioTrackDisplayNameMethod = audioStreamingTypeSelector.originalMethod.firstFormatStreamingModelCall()
val formatStreamModelClass = proxy(classes.first {
it.type == audioTrackIdMethod.definingClass
}).mutableClass
val isDefaultAudioTrackMethod = formatStreamModelToStringFingerprint.originalMethod
.findMethodFromToString("isDefaultAudioTrack=")
val audioTrackDisplayNameMethod = formatStreamModelToStringFingerprint.originalMethod
.findMethodFromToString("audioTrackDisplayName=")
val audioTrackIdMethod = formatStreamModelToStringFingerprint.originalMethod
.findMethodFromToString("audioTrackId=")
formatStreamModelClass.apply {
proxy(classes.first {
it.type == audioTrackIdMethod.definingClass
}).mutableClass.apply {
// Add a new field to store the override.
val helperFieldName = "isDefaultAudioTrackOverride"
fields.add(
@@ -103,7 +100,7 @@ val forceOriginalAudioPatch = bytecodePatch(
// Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed.
val helperMethodClass = type
val helperMethodName = "extension_isDefaultAudioTrack"
val helperMethodName = "patch_isDefaultAudioTrack"
val helperMethod = ImmutableMethod(
helperMethodClass,
helperMethodName,
@@ -143,7 +140,7 @@ val forceOriginalAudioPatch = bytecodePatch(
methods.add(helperMethod)
// Modify isDefaultAudioTrack() to call extension helper method.
isDefaultMethod.apply {
isDefaultAudioTrackMethod.apply {
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
val register = getInstruction<OneRegisterInstruction>(index).registerA

View File

@@ -1,7 +1,7 @@
package app.revanced.patches.youtube.video.information
import app.revanced.patcher.fingerprint
import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint
import app.revanced.patches.youtube.shared.videoQualityChangedFingerprint
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@@ -110,7 +110,7 @@ internal val seekRelativeFingerprint = fingerprint {
}
/**
* Resolves with the class found in [newVideoQualityChangedFingerprint].
* Resolves with the class found in [videoQualityChangedFingerprint].
*/
internal val playbackSpeedMenuSpeedChangedFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)

View File

@@ -9,7 +9,7 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.util.smali.toInstructions
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint
import app.revanced.patches.youtube.shared.videoQualityChangedFingerprint
import app.revanced.patches.youtube.video.playerresponse.Hook
import app.revanced.patches.youtube.video.playerresponse.addPlayerResponseMethodHook
import app.revanced.patches.youtube.video.playerresponse.playerResponseMethodHookPatch
@@ -263,7 +263,7 @@ val videoInformationPatch = bytecodePatch(
// Handle new playback speed menu.
playbackSpeedMenuSpeedChangedFingerprint.match(
newVideoQualityChangedFingerprint.originalClassDef,
videoQualityChangedFingerprint.originalClassDef,
).method.apply {
val index = indexOfFirstInstructionOrThrow(Opcode.IGET)

View File

@@ -68,7 +68,6 @@ internal val advancedVideoQualityMenuPatch = bytecodePatch {
// region Patch for the old type of the video quality menu.
// Used for regular videos when spoofing to old app version,
// and for the Shorts quality flyout on newer app versions.
videoQualityMenuViewInflateFingerprint.let {
it.method.apply {
val checkCastIndex = it.patternMatch!!.endIndex
@@ -77,7 +76,7 @@ internal val advancedVideoQualityMenuPatch = bytecodePatch {
addInstruction(
checkCastIndex + 1,
"invoke-static { v$listViewRegister }, $EXTENSION_CLASS_DESCRIPTOR->" +
"showAdvancedVideoQualityMenu(Landroid/widget/ListView;)V",
"addVideoQualityListMenuListener(Landroid/widget/ListView;)V",
)
}
}

View File

@@ -5,10 +5,25 @@ import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal const val YOUTUBE_VIDEO_QUALITY_CLASS_TYPE = "Lcom/google/android/libraries/youtube/innertube/model/media/VideoQuality;"
internal val videoQualityFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
parameters(
"I", // Resolution.
"Ljava/lang/String;", // Human readable resolution: "480p", "1080p Premium", etc
"Z",
"L"
)
custom { _, classDef ->
classDef.type == YOUTUBE_VIDEO_QUALITY_CLASS_TYPE
}
}
/**
* Matches with the class found in [videoQualitySetterFingerprint].
*/
internal val setQualityByIndexMethodClassFieldReferenceFingerprint = fingerprint {
internal val setVideoQualityFingerprint = fingerprint {
returns("V")
parameters("L")
opcodes(
@@ -23,6 +38,22 @@ internal val videoQualityItemOnClickParentFingerprint = fingerprint {
strings("VIDEO_QUALITIES_MENU_BOTTOM_SHEET_FRAGMENT")
}
/**
* Resolves to class found in [videoQualityItemOnClickFingerprint].
*/
internal val videoQualityItemOnClickFingerprint = fingerprint {
returns("V")
parameters(
"Landroid/widget/AdapterView;",
"Landroid/view/View;",
"I",
"J"
)
custom { method, _ ->
method.name == "onItemClick"
}
}
internal val videoQualitySetterFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
@@ -37,7 +68,6 @@ internal val videoQualitySetterFingerprint = fingerprint {
strings("menu_item_video_quality")
}
internal val videoQualityMenuOptionsFingerprint = fingerprint {
accessFlags(AccessFlags.STATIC)
returns("[L")

View File

@@ -3,8 +3,8 @@ package app.revanced.patches.youtube.video.quality
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.ListPreference
@@ -12,15 +12,21 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint
import app.revanced.patches.youtube.shared.videoQualityChangedFingerprint
import app.revanced.patches.youtube.video.information.onCreateHook
import app.revanced.patches.youtube.video.information.videoInformationPatch
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch;"
private const val EXTENSION_VIDEO_QUALITY_MENU_INTERFACE =
"Lapp/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch\$VideoQualityMenuInterface;"
val rememberVideoQualityPatch = bytecodePatch {
dependsOn(
@@ -61,81 +67,152 @@ val rememberVideoQualityPatch = bytecodePatch {
SwitchPreference("revanced_remember_video_quality_last_selected_toast")
))
/*
* The following code works by hooking the method which is called when the user selects a video quality
* to remember the last selected video quality.
*
* It also hooks the method which is called when the video quality to set is determined.
* Conveniently, at this point the video quality is overridden to the remembered playback speed.
*/
onCreateHook(EXTENSION_CLASS_DESCRIPTOR, "newVideoStarted")
videoQualityFingerprint.let {
// Fix bad data used by YouTube.
it.method.addInstructions(
0,
"""
invoke-static { p2, p1 }, $EXTENSION_CLASS_DESCRIPTOR->fixVideoQualityResolution(Ljava/lang/String;I)I
move-result p1
"""
)
// Add methods to access obfuscated quality fields.
it.classDef.apply {
methods.add(
ImmutableMethod(
type,
"patch_getQualityName",
listOf(),
"Ljava/lang/String;",
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
null,
null,
MutableMethodImplementation(2),
).toMutable().apply {
// Only one string field.
val qualityNameField = fields.single { field ->
field.type == "Ljava/lang/String;"
}
addInstructions(
0,
"""
iget-object v0, p0, $qualityNameField
return-object v0
"""
)
}
)
methods.add(
ImmutableMethod(
type,
"patch_getResolution",
listOf(),
"I",
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
null,
null,
MutableMethodImplementation(2),
).toMutable().apply {
val resolutionField = fields.single { field ->
field.type == "I"
}
addInstructions(
0,
"""
iget v0, p0, $resolutionField
return v0
"""
)
}
)
}
}
// Inject a call to set the remembered quality once a video loads.
setQualityByIndexMethodClassFieldReferenceFingerprint.match(
videoQualitySetterFingerprint.originalClassDef,
setVideoQualityFingerprint.match(
videoQualitySetterFingerprint.originalClassDef
).let { match ->
// This instruction refers to the field with the type that contains the setQualityByIndex method.
// This instruction refers to the field with the type that contains the setQuality method.
val instructions = match.method.implementation!!.instructions
val getOnItemClickListenerClassReference =
val onItemClickListenerClassReference =
(instructions.elementAt(0) as ReferenceInstruction).reference
val getSetQualityByIndexMethodClassFieldReference =
(instructions.elementAt(1) as ReferenceInstruction).reference
val setQualityFieldReference =
((instructions.elementAt(1) as ReferenceInstruction).reference) as FieldReference
val setQualityByIndexMethodClassFieldReference =
getSetQualityByIndexMethodClassFieldReference as FieldReference
proxy(
classes.find { classDef ->
classDef.type == setQualityFieldReference.type
}!!
).mutableClass.apply {
// Add interface and helper methods to allow extension code to call obfuscated methods.
interfaces.add(EXTENSION_VIDEO_QUALITY_MENU_INTERFACE)
val setQualityByIndexMethodClass = classes
.find { classDef -> classDef.type == setQualityByIndexMethodClassFieldReference.type }!!
methods.add(
ImmutableMethod(
type,
"patch_setQuality",
listOf(
ImmutableMethodParameter(YOUTUBE_VIDEO_QUALITY_CLASS_TYPE, null, null)
),
"V",
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
null,
null,
MutableMethodImplementation(2),
).toMutable().apply {
val setQualityMenuIndexMethod = methods.single { method ->
method.parameterTypes.firstOrNull() == YOUTUBE_VIDEO_QUALITY_CLASS_TYPE
}
// Get the name of the setQualityByIndex method.
val setQualityByIndexMethod = setQualityByIndexMethodClass.methods
.find { method -> method.parameterTypes.first() == "I" }
?: throw PatchException("Could not find setQualityByIndex method")
addInstructions(
0,
"""
invoke-virtual { p0, p1 }, $setQualityMenuIndexMethod
return-void
"""
)
}
)
}
videoQualitySetterFingerprint.method.addInstructions(
0,
"""
# Get the object instance to invoke the setQualityByIndex method on.
iget-object v0, p0, $getOnItemClickListenerClassReference
iget-object v0, v0, $getSetQualityByIndexMethodClassFieldReference
# Get object instance to invoke setQuality method.
iget-object v0, p0, $onItemClickListenerClassReference
iget-object v0, v0, $setQualityFieldReference
# Get the method name.
const-string v1, "${setQualityByIndexMethod.name}"
# Set the quality.
# The first parameter is the array list of video qualities.
# The second parameter is the index of the selected quality.
# The register v0 stores the object instance to invoke the setQualityByIndex method on.
# The register v1 stores the name of the setQualityByIndex method.
invoke-static { p1, p2, v0, v1 }, $EXTENSION_CLASS_DESCRIPTOR->setVideoQuality([Ljava/lang/Object;ILjava/lang/Object;Ljava/lang/String;)I
invoke-static { p1, v0, p2 }, $EXTENSION_CLASS_DESCRIPTOR->setVideoQuality([$YOUTUBE_VIDEO_QUALITY_CLASS_TYPE${EXTENSION_VIDEO_QUALITY_MENU_INTERFACE}I)I
move-result p2
""",
"""
)
}
// Inject a call to remember the selected quality.
videoQualityItemOnClickParentFingerprint.classDef.methods.find { it.name == "onItemClick" }
?.apply {
val listItemIndexParameter = 3
// Inject a call to remember the selected quality for Shorts.
videoQualityItemOnClickFingerprint.match(
videoQualityItemOnClickParentFingerprint.classDef
).method.addInstruction(
0,
"invoke-static { p3 }, $EXTENSION_CLASS_DESCRIPTOR->userChangedShortsQuality(I)V"
)
// Inject a call to remember the user selected quality for regular videos.
videoQualityChangedFingerprint.let {
it.method.apply {
val index = it.patternMatch!!.startIndex
val register = getInstruction<TwoRegisterInstruction>(index).registerA
addInstruction(
0,
"invoke-static { p$listItemIndexParameter }, " +
"$EXTENSION_CLASS_DESCRIPTOR->userChangedQuality(I)V",
index + 1,
"invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->userChangedQuality(I)V",
)
} ?: throw PatchException("Failed to find onItemClick method")
// Remember video quality if not using old layout menu.
newVideoQualityChangedFingerprint.method.apply {
val index = newVideoQualityChangedFingerprint.patternMatch!!.startIndex
val qualityRegister = getInstruction<TwoRegisterInstruction>(index).registerA
addInstruction(
index + 1,
"invoke-static { v$qualityRegister }, " +
"$EXTENSION_CLASS_DESCRIPTOR->userChangedQualityInNewFlyout(I)V",
)
}
}
}
}

View File

@@ -5,6 +5,7 @@ import app.revanced.patches.shared.misc.settings.preference.BasePreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.video.quality.button.videoQualityButtonPatch
/**
* Video quality settings. Used to organize all speed related settings together.
@@ -19,6 +20,7 @@ val videoQualityPatch = bytecodePatch(
dependsOn(
rememberVideoQualityPatch,
advancedVideoQualityMenuPatch,
videoQualityButtonPatch,
)
compatibleWith(

View File

@@ -0,0 +1,64 @@
package app.revanced.patches.youtube.video.quality.button
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playercontrols.*
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.video.quality.rememberVideoQualityPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
private val videoQualityButtonResourcePatch = resourcePatch {
dependsOn(playerControlsResourcePatch)
execute {
copyResources(
"qualitybutton",
ResourceGroup(
"drawable",
"revanced_video_quality_dialog_button_ld.xml",
"revanced_video_quality_dialog_button_sd.xml",
"revanced_video_quality_dialog_button_hd.xml",
"revanced_video_quality_dialog_button_fhd.xml",
"revanced_video_quality_dialog_button_fhd_plus.xml",
"revanced_video_quality_dialog_button_qhd.xml",
"revanced_video_quality_dialog_button_4k.xml",
"revanced_video_quality_dialog_button_unknown.xml",
),
)
addBottomControl("qualitybutton")
}
}
private const val QUALITY_BUTTON_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/videoplayer/VideoQualityDialogButton;"
val videoQualityButtonPatch = bytecodePatch(
description = "Adds the option to display video quality dialog button in the video player.",
) {
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
rememberVideoQualityPatch,
videoQualityButtonResourcePatch,
playerControlsPatch,
)
execute {
addResources("youtube", "video.quality.button.videoQualityButtonPatch")
PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_video_quality_dialog_button"),
)
initializeBottomControl(QUALITY_BUTTON_CLASS_DESCRIPTOR)
injectVisibilityCheckCall(QUALITY_BUTTON_CLASS_DESCRIPTOR)
}
}

View File

@@ -1,11 +1,19 @@
package app.revanced.patches.youtube.video.speed.custom
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.mapping.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.shared.misc.settings.preference.InputType
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.settings.preference.TextPreference
@@ -18,18 +26,34 @@ import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTr
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstruction
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.AccessFlags
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.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.ImmutableField
private const val FILTER_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilterPatch;"
"Lapp/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilter;"
private const val EXTENSION_CLASS_DESCRIPTOR =
internal const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/playback/speed/CustomPlaybackSpeedPatch;"
internal var speedUnavailableId = -1L
private set
private val customPlaybackSpeedResourcePatch = resourcePatch {
dependsOn(resourceMappingPatch)
execute {
speedUnavailableId = resourceMappings["string", "varispeed_unavailable_message"]
}
}
internal val customPlaybackSpeedPatch = bytecodePatch(
description = "Adds custom playback speed options.",
) {
@@ -39,7 +63,8 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
addResourcesPatch,
lithoFilterPatch,
versionCheckPatch,
recyclerViewTreeHookPatch
recyclerViewTreeHookPatch,
customPlaybackSpeedResourcePatch
)
execute {
@@ -48,6 +73,7 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
settingsMenuVideoSpeedGroup.addAll(
listOf(
SwitchPreference("revanced_custom_speed_menu"),
SwitchPreference("revanced_restore_old_speed_menu"),
TextPreference(
"revanced_custom_playback_speeds",
inputType = InputType.TEXT_MULTI_LINE
@@ -77,15 +103,88 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
replaceInstruction(limitMaxIndex, "const/high16 v$limitMaxRegister, 8.0f")
}
// Replace the speeds float array with custom speeds.
// These speeds are used if the speed menu is immediately opened after a video is opened.
speedArrayGeneratorFingerprint.method.apply {
val sizeCallIndex = indexOfFirstInstructionOrThrow { getReference<MethodReference>()?.name == "size" }
val sizeCallResultRegister = getInstruction<OneRegisterInstruction>(sizeCallIndex + 1).registerA
replaceInstruction(sizeCallIndex + 1, "const/4 v$sizeCallResultRegister, 0x0")
val arrayLengthConstIndex = indexOfFirstLiteralInstructionOrThrow(7)
val arrayLengthConstDestination = getInstruction<OneRegisterInstruction>(arrayLengthConstIndex).registerA
val playbackSpeedsArrayType = "$EXTENSION_CLASS_DESCRIPTOR->customPlaybackSpeeds:[F"
addInstructions(
arrayLengthConstIndex + 1,
"""
sget-object v$arrayLengthConstDestination, $playbackSpeedsArrayType
array-length v$arrayLengthConstDestination, v$arrayLengthConstDestination
""",
)
val originalArrayFetchIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<FieldReference>()
reference?.type == "[F" && reference.definingClass.endsWith("/PlayerConfigModel;")
}
val originalArrayFetchDestination =
getInstruction<OneRegisterInstruction>(originalArrayFetchIndex).registerA
replaceInstruction(
originalArrayFetchIndex,
"sget-object v$originalArrayFetchDestination, $playbackSpeedsArrayType",
)
}
// region Force old video quality menu.
// Add a static INSTANCE field to the class.
// This is later used to call "showOldPlaybackSpeedMenu" on the instance.
val instanceField = ImmutableField(
getOldPlaybackSpeedsFingerprint.originalClassDef.type,
"INSTANCE",
getOldPlaybackSpeedsFingerprint.originalClassDef.type,
AccessFlags.PUBLIC.value or AccessFlags.STATIC.value,
null,
null,
null,
).toMutable()
getOldPlaybackSpeedsFingerprint.classDef.staticFields.add(instanceField)
// Set the INSTANCE field to the instance of the class.
// In order to prevent a conflict with another patch, add the instruction at index 1.
getOldPlaybackSpeedsFingerprint.method.addInstruction(1, "sput-object p0, $instanceField")
// Get the "showOldPlaybackSpeedMenu" method.
// This is later called on the field INSTANCE.
val showOldPlaybackSpeedMenuMethod = showOldPlaybackSpeedMenuFingerprint.match(
getOldPlaybackSpeedsFingerprint.classDef,
).method
// Insert the call to the "showOldPlaybackSpeedMenu" method on the field INSTANCE.
showOldPlaybackSpeedMenuExtensionFingerprint.method.apply {
addInstructionsWithLabels(
instructions.lastIndex,
"""
sget-object v0, $instanceField
if-nez v0, :not_null
return-void
:not_null
invoke-virtual { v0 }, $showOldPlaybackSpeedMenuMethod
"""
)
}
// endregion
// Close the unpatched playback dialog and show the modern custom dialog.
addRecyclerViewTreeHook(EXTENSION_CLASS_DESCRIPTOR)
// Required to check if the playback speed menu is currently shown.
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
// endregion
// region Custom tap and hold 2x speed.
if (is_19_25_or_greater) {

View File

@@ -3,10 +3,33 @@ package app.revanced.patches.youtube.video.speed.custom
import app.revanced.patcher.fingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.StringReference
internal val getOldPlaybackSpeedsFingerprint = fingerprint {
parameters("[L", "I")
strings("menu_item_playback_speed")
}
internal val showOldPlaybackSpeedMenuFingerprint = fingerprint {
literal { speedUnavailableId }
}
internal val showOldPlaybackSpeedMenuExtensionFingerprint = fingerprint {
custom { method, classDef ->
method.name == "showOldPlaybackSpeedMenu" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
}
}
internal val speedArrayGeneratorFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("[L")
parameters("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;")
strings("0.0#")
}
internal val speedLimiterFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")

View File

@@ -32,7 +32,10 @@ import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstructio
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
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.iface.reference.StringReference
import com.android.tools.smali.dexlib2.immutable.ImmutableField
import com.android.tools.smali.dexlib2.util.MethodUtil
import java.util.EnumSet
@@ -171,6 +174,79 @@ internal val Instruction.isBranchInstruction: Boolean
internal val Instruction.isReturnInstruction: Boolean
get() = this.opcode in returnOpcodes
/**
* Find the instruction index used for a toString() StringBuilder write of a given String name.
*
* @param fieldName The name of the field to find. Partial matches are allowed.
*/
private fun Method.findInstructionIndexFromToString(fieldName: String) : Int {
val stringIndex = indexOfFirstInstruction {
val reference = getReference<StringReference>()
reference?.string?.contains(fieldName) == true
}
if (stringIndex < 0) {
throw IllegalArgumentException("Could not find usage of string: '$fieldName'")
}
val stringRegister = getInstruction<OneRegisterInstruction>(stringIndex).registerA
// Find use of the string with a StringBuilder.
val stringUsageIndex = indexOfFirstInstruction(stringIndex) {
val reference = getReference<MethodReference>()
reference?.definingClass == "Ljava/lang/StringBuilder;" &&
(this as? FiveRegisterInstruction)?.registerD == stringRegister
}
if (stringUsageIndex < 0) {
throw IllegalArgumentException("Could not find StringBuilder usage in: $this")
}
// Find the next usage of StringBuilder, which should be the desired field.
val fieldUsageIndex = indexOfFirstInstruction(stringUsageIndex + 1) {
val reference = getReference<MethodReference>()
reference?.definingClass == "Ljava/lang/StringBuilder;" && reference.name == "append"
}
if (fieldUsageIndex < 0) {
// Should never happen.
throw IllegalArgumentException("Could not find StringBuilder append usage in: $this")
}
val fieldUsageRegister = getInstruction<FiveRegisterInstruction>(fieldUsageIndex).registerD
// Look backwards up the method to find the instruction that sets the register.
var fieldSetIndex = indexOfFirstInstructionReversedOrThrow(fieldUsageIndex - 1) {
fieldUsageRegister == writeRegister
}
// If the field is a method call, then adjust from MOVE_RESULT to the method call.
val fieldSetOpcode = getInstruction(fieldSetIndex).opcode
if (fieldSetOpcode == MOVE_RESULT ||
fieldSetOpcode == MOVE_RESULT_WIDE ||
fieldSetOpcode == MOVE_RESULT_OBJECT) {
fieldSetIndex--
}
return fieldSetIndex
}
/**
* Find the method used for a toString() StringBuilder write of a given String name.
*
* @param fieldName The name of the field to find. Partial matches are allowed.
*/
context(BytecodePatchContext)
internal fun Method.findMethodFromToString(fieldName: String) : MutableMethod {
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
return navigate(this).to(methodUsageIndex).stop()
}
/**
* Find the field used for a toString() StringBuilder write of a given String name.
*
* @param fieldName The name of the field to find. Partial matches are allowed.
*/
internal fun Method.findFieldFromToString(fieldName: String) : FieldReference {
val methodUsageIndex = findInstructionIndexFromToString(fieldName)
return getInstruction<ReferenceInstruction>(methodUsageIndex).getReference<FieldReference>()!!
}
/**
* Adds public [AccessFlags] and removes private and protected flags (if present).
*/
@@ -594,7 +670,7 @@ fun Method.indexOfFirstInstructionReversed(targetOpcode: Opcode): Int = indexOfF
/**
* Get the index of matching instruction,
* starting from and [startIndex] and searching down.
* starting from [startIndex] and searching down.
*
* @param startIndex Optional starting index to search down from. Searching includes the start index.
* @return The index of the instruction.
@@ -617,7 +693,7 @@ fun Method.indexOfFirstInstructionReversedOrThrow(targetOpcode: Opcode): Int = i
/**
* Get the index of matching instruction,
* starting from and [startIndex] and searching down.
* starting from [startIndex] and searching down.
*
* @param startIndex Optional starting index to search down from. Searching includes the start index.
* @return The index of the instruction.

View File

@@ -127,6 +127,7 @@ Second \"item\" text"</string>
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
</patch>
<patch id="layout.hide.seekbar.hideSeekbarPatch">
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
</patch>
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
@@ -169,8 +170,6 @@ Second \"item\" text"</string>
<patch id="layout.formfactor.changeFormFactorPatch">
</patch>
<patch id="layout.spoofappversion.spoofAppVersionPatch">
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
</patch>
<patch id="layout.startpage.changeStartPagePatch">
</patch>
@@ -223,6 +222,8 @@ Second \"item\" text"</string>
</patch>
<patch id="video.speed.button.playbackSpeedButtonPatch">
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">

View File

@@ -127,6 +127,7 @@ Second \"item\" text"</string>
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
</patch>
<patch id="layout.hide.seekbar.hideSeekbarPatch">
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
</patch>
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
@@ -169,8 +170,6 @@ Second \"item\" text"</string>
<patch id="layout.formfactor.changeFormFactorPatch">
</patch>
<patch id="layout.spoofappversion.spoofAppVersionPatch">
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
</patch>
<patch id="layout.startpage.changeStartPagePatch">
</patch>
@@ -223,6 +222,8 @@ Second \"item\" text"</string>
</patch>
<patch id="video.speed.button.playbackSpeedButtonPatch">
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">

View File

@@ -137,7 +137,7 @@ Second \"item\" text"</string>
<string name="revanced_debug_logs_disabled">تم تعطيل تسجيلات تصحيح الأخطاء</string>
<string name="revanced_debug_logs_none_found">لم يتم العثور على سجلات</string>
<string name="revanced_debug_logs_copied_to_clipboard">تم نسخ السجلات</string>
<string name="revanced_debug_logs_failed_to_export">فشل تصدير السجلات: $s</string>
<string name="revanced_debug_logs_failed_to_export">فشل تصدير السجلات: %s</string>
<string name="revanced_debug_logs_clear_buffer_title">مسح سجلات تصحيح الأخطاء</string>
<string name="revanced_debug_logs_clear_buffer_summary">يمسح جميع سجلات تصحيح أخطاء ReVanced المخزنة</string>
<string name="revanced_debug_logs_clear_toast">تم مسح السجلات</string>
@@ -468,8 +468,15 @@ Second \"item\" text"</string>
<string name="revanced_external_downloader_action_button_summary_on">يفتح زر التنزيل أداة التنزيل الخارجية</string>
<string name="revanced_external_downloader_action_button_summary_off">يفتح زر التنزيل أداة التنزيل الأصلية داخل التطبيق</string>
<string name="revanced_external_downloader_name_title">اسم حزمة أداة التنزيل</string>
<string name="revanced_external_downloader_name_summary">اسم الحزمة لتطبيق التنزيل الخارجي المثبت لديك، مثل NewPipe أو Seal</string>
<string name="revanced_external_downloader_name_summary">اسم حزمة تطبيق التنزيل الخارجي المثبت لديك</string>
<string name="revanced_external_downloader_other_item_hint">أدخل اسم الحزمة</string>
<string name="revanced_external_downloader_other_item">أخرى</string>
<string name="revanced_external_downloader_not_found_title">التطبيق غير مثبت</string>
<string name="revanced_external_downloader_not_installed_warning">لم يتم تثبيت %s . الرجاء تثبيته.</string>
<string name="revanced_external_downloader_package_not_found_warning">"تعذر العثور على التطبيق المثبت باسم الحزمة: %s
تأكد من أن اسم الحزمة صحيح وأن التطبيق مثبت"</string>
<string name="revanced_external_downloader_empty_warning">لا يمكن أن يكون اسم الحزمة فارغًا</string>
</patch>
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
<string name="revanced_disable_precise_seeking_gesture_title">تعطيل إيماءة التمرير الدقيقة</string>
@@ -732,6 +739,7 @@ Second \"item\" text"</string>
<string name="revanced_hide_seekbar_title">إخفاء شريط تقدم مشغل الفيديو</string>
<string name="revanced_hide_seekbar_summary_on">تم إخفاء شريط تقدم الفيديو</string>
<string name="revanced_hide_seekbar_summary_off">يتم عرض شريط تقدم الفيديو</string>
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
<string name="revanced_hide_seekbar_thumbnail_title">إخفاء شريط تقدم صور مصغرة للفيديو</string>
<string name="revanced_hide_seekbar_thumbnail_summary_on">شريط تقدم صور مصغرة للفيديو مخفي</string>
<string name="revanced_hide_seekbar_thumbnail_summary_off">شريط تقدم صور مصغرة للفيديو معروض</string>
@@ -1189,8 +1197,6 @@ Second \"item\" text"</string>
سيؤدي هذا إلى تغيير مظهر ومميزات التطبيق، ولكن قد تحدث تأثيرات جانبية غير معروفة.
إذا تم إيقاف تشغيله لاحقًا، من المستحسن مسح بيانات التطبيق لمنع حدوث أخطاء في واجهة المستخدم."</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
<string name="revanced_spoof_app_version_target_title">الهدف من تغيير إصدار التطبيق</string>
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - استعادة أيقونات مشغل Shorts القديمة</string>
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - استعادة أيقونات التنقل القديمة</string>
@@ -1459,10 +1465,18 @@ Second \"item\" text"</string>
<string name="revanced_playback_speed_dialog_button_summary_on">الزر معروض. انقر مع الاستمرار لإعادة ضبط سرعة التشغيل إلى الوضع الافتراضي</string>
<string name="revanced_playback_speed_dialog_button_summary_off">لا يتم عرض الزر</string>
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
<string name="revanced_video_quality_dialog_button_title">عرض زر جودة الفيديو</string>
<string name="revanced_video_quality_dialog_button_summary_on">الزر معروض. انقر مع الاستمرار لإعادة تعيين الجودة إلى الافتراضي</string>
<string name="revanced_video_quality_dialog_button_summary_off">الزر غير معروض</string>
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
<string name="revanced_custom_speed_menu_title">قائمة سرعة التشغيل المخصصة</string>
<string name="revanced_custom_speed_menu_summary_on">يتم عرض قائمة سرعة التشغيل المخصصة</string>
<string name="revanced_custom_speed_menu_summary_off">لا يتم عرض قائمة سرعة التشغيل المخصصة</string>
<string name="revanced_restore_old_speed_menu_title">استعادة قائمة سرعة التشغيل القديمة</string>
<string name="revanced_restore_old_speed_menu_summary_on">قائمة السرعة القديمة معروضة</string>
<string name="revanced_restore_old_speed_menu_summary_off">قائمة السرعة الحديثة معروضة</string>
<string name="revanced_custom_playback_speeds_title">سرعة التشغيل المخصصة</string>
<string name="revanced_custom_playback_speeds_summary">إضافة أو تغيير سرعة التشغيل المخصصة</string>
<string name="revanced_custom_playback_speeds_invalid">يجب أن تكون سرعات التشغيل المخصصة أقل من %s</string>

View File

@@ -127,6 +127,7 @@ Second \"item\" text"</string>
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
</patch>
<patch id="layout.hide.seekbar.hideSeekbarPatch">
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
</patch>
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
@@ -169,8 +170,6 @@ Second \"item\" text"</string>
<patch id="layout.formfactor.changeFormFactorPatch">
</patch>
<patch id="layout.spoofappversion.spoofAppVersionPatch">
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
</patch>
<patch id="layout.startpage.changeStartPagePatch">
</patch>
@@ -225,6 +224,8 @@ Second \"item\" text"</string>
</patch>
<patch id="video.speed.button.playbackSpeedButtonPatch">
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">

View File

@@ -137,7 +137,7 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
<string name="revanced_debug_logs_disabled">Sazlama qeydi qapalıdır</string>
<string name="revanced_debug_logs_none_found">Qeydlər tapılmadı</string>
<string name="revanced_debug_logs_copied_to_clipboard">Qeydlər köçürüldü</string>
<string name="revanced_debug_logs_failed_to_export">Qeydləri ixrac etmək alınmadı: $s</string>
<string name="revanced_debug_logs_failed_to_export">Qeydləri ixrac etmək alınmadı: %s</string>
<string name="revanced_debug_logs_clear_buffer_title">Sazlama qeydlərini təmizlə</string>
<string name="revanced_debug_logs_clear_buffer_summary">Saxlanılan bütün ReVanced sazlama qeydlərini təmizləyir</string>
<string name="revanced_debug_logs_clear_toast">Qeydlər silindi</string>
@@ -201,6 +201,9 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
<string name="revanced_hide_show_more_button_title">\'Daha çox göstər\' düyməsini gizlət</string>
<string name="revanced_hide_show_more_button_summary_on">Daha çox göstər düyməsi axtarış nəticələrində gizlidir</string>
<string name="revanced_hide_show_more_button_summary_off">Daha çox göstər düyməsi axtarış nəticələrində görünür</string>
<string name="revanced_hide_surveys_title">Sorğuları gizlət</string>
<string name="revanced_hide_surveys_summary_on">Sorğular gizlədilib</string>
<string name="revanced_hide_surveys_summary_off">Sorğular görünür</string>
<string name="revanced_hide_ticket_shelf_title">Bilet bölməsin gizlət</string>
<string name="revanced_hide_ticket_shelf_summary_on">Bilet bölməsi gizlidir</string>
<string name="revanced_hide_ticket_shelf_summary_off">Bilet bölməsi görünür</string>
@@ -248,6 +251,8 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
<string name="revanced_hide_timed_reactions_summary_on">Zamanlanmış reaksiyalar gizlədilir</string>
<string name="revanced_hide_timed_reactions_summary_off">Zamanlanmış reaksiyalar göstərilir</string>
<string name="revanced_hide_ai_generated_video_summary_section_title">\"AI ilə yaradılan video xülasəsini\" gizlət</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">Sİ ilə yaradılan video xülasə bölməsi gizlədilib</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Sİ ilə yaradılan video xülasə bölməsi görünür</string>
<string name="revanced_hide_ask_section_title">Soruş\'u Gizlət</string>
<string name="revanced_hide_ask_section_summary_on">Soruş bölməsi gizlidir</string>
<string name="revanced_hide_ask_section_summary_off">\"Soruş\" bölməsi göstərilir</string>
@@ -275,6 +280,7 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
<string name="revanced_hide_description_components_screen_title">Video təsviri</string>
<string name="revanced_hide_description_components_screen_summary">Video təsviri elementlərini gizlət və ya göstər</string>
<string name="revanced_hide_filter_bar_screen_title">Filtr çubuğu</string>
<string name="revanced_hide_filter_bar_screen_summary">Axınlar, əlaqəli videolar, axtarış nəticələri və baxış tarixçəsində filtr cərgəsin gizlət və ya göstər</string>
<string name="revanced_hide_filter_bar_feed_in_feed_title">Axınlarda gizlət</string>
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">Axınlarda gizlidir</string>
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">Axınlarda göstər</string>
@@ -284,6 +290,9 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
<string name="revanced_hide_filter_bar_feed_in_search_title">Axtarış nəticələrində gizlət</string>
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Axtarış nəticələrində gizlədilib</string>
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Axtarış nəticələrində göstərilir</string>
<string name="revanced_hide_filter_bar_feed_in_history_title">Baxış tarixçəsində gizlət</string>
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Baxış tarixçəsində gizlədilib</string>
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Baxış tarixçəsində görünür</string>
<string name="revanced_channel_screen_title">Kanal səhifəsi</string>
<string name="revanced_channel_screen_summary">Kanal səhifə elementlərini gizlət və ya göstər</string>
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
@@ -306,7 +315,12 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
<string name="revanced_hide_visit_store_button_summary_off">Mağazaya baxın düyməsi görünür</string>
<string name="revanced_comments_screen_title">Şərhlər</string>
<string name="revanced_comments_screen_summary">Şərhlər bölməsi elementlərin gizlət və ya göstər</string>
<string name="revanced_hide_comments_ai_chat_summary_title">Sİ söhbət xülasəsini gizlət</string>
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Sİ söhbət xülasəsi gizlidir</string>
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Sİ söhbət xülasəsi görünür</string>
<string name="revanced_hide_comments_ai_summary_title">AI Ṣərhlər Xülasəsini Gizlət</string>
<string name="revanced_hide_comments_ai_summary_summary_on">Sİ şərhlər xülasəsi gizlidir</string>
<string name="revanced_hide_comments_ai_summary_summary_off">Sİ şərhlər xülasəsi görünür</string>
<string name="revanced_hide_comments_channel_guidelines_title">Kanal təlimatlarını gizlət</string>
<string name="revanced_hide_comments_channel_guidelines_summary_on">Kanal təlimatları gizlidir</string>
<string name="revanced_hide_comments_channel_guidelines_summary_off">Kanal təlimatları görünür</string>
@@ -454,8 +468,15 @@ Bu xüsusiyyət yalnız köhnə cihazlar üçün mövcuddur"</string>
<string name="revanced_external_downloader_action_button_summary_on">Yükləmə düyməsi, xarici yükləyicinizi açır</string>
<string name="revanced_external_downloader_action_button_summary_off">Yükləmə düyməsi tətbiqə xas yükləyicini açır</string>
<string name="revanced_external_downloader_name_title">Yükləyici paketi adı</string>
<string name="revanced_external_downloader_name_summary">NewPipe və ya Seal kimi quraşdırılan xarici yüklə tətbiqinizin paket adı</string>
<string name="revanced_external_downloader_name_summary">Quraşdırılan xarici yükləyici tətbiqinizin paket adı</string>
<string name="revanced_external_downloader_other_item_hint">Paket adını yerləşdir</string>
<string name="revanced_external_downloader_other_item">Digər</string>
<string name="revanced_external_downloader_not_found_title">Tətbiq quraşdırılmayıb</string>
<string name="revanced_external_downloader_not_installed_warning">%s quraşdırılmayıb. Lütfən, bunu quraşdır.</string>
<string name="revanced_external_downloader_package_not_found_warning">"Paket adı ilə quraşdırılan tətbiq tapılmadı: %s
Paket adının düzgün olduğun yoxla və tətbiqi quraşdırın"</string>
<string name="revanced_external_downloader_empty_warning">Paket adı boş ola bilməz</string>
</patch>
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
<string name="revanced_disable_precise_seeking_gesture_title">Dəqiq axtarış jestini qapadın</string>
@@ -715,11 +736,17 @@ Audio trek seçimin göstərmək üçün \"Video axınları saxtalaşdır\"ı iO
<string name="revanced_disable_rolling_number_animations_summary_off">Sürüşən say animasiyasııqdır</string>
</patch>
<patch id="layout.hide.seekbar.hideSeekbarPatch">
<string name="revanced_hide_seekbar_title">Video oynadıcı irəliləyiş cizgisin gizlət</string>
<string name="revanced_hide_seekbar_summary_on">Video oynadıcı irəliləyiş cizgisi gizlidir</string>
<string name="revanced_hide_seekbar_summary_off">Video oynadıcı irəliləyiş cizgisi göstərilir</string>
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
<string name="revanced_hide_seekbar_thumbnail_title">Video miniatür irəliləyiş cizgisin gizlət</string>
<string name="revanced_hide_seekbar_thumbnail_summary_on">Video miniatür irəliləyiş cizgisi gizlidir</string>
<string name="revanced_hide_seekbar_thumbnail_summary_off">Video miniatür irəliləyiş cizgisi görünür</string>
</patch>
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
<string name="revanced_shorts_player_screen_title">Shorts oynadıcı</string>
<string name="revanced_shorts_player_screen_summary">Shorts oynadıcı elementlərini gizlət və ya göstər</string>
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
<string name="revanced_hide_shorts_home_title">Shorts-u Ev axınında gizlət</string>
<string name="revanced_hide_shorts_home_summary_on">Ev axını və əlaqəli videolarda gizlidir</string>
@@ -1169,8 +1196,6 @@ Avtomobil tərtibatı
Bu tətbiqin görünüşün və xüsusiyyətlərin dəyişdirəcək, lakin bilinməyən yan təsirlər ola bilər.
Sonradan qapadılarsa, UI səhvlərin önləmək üçün tətbiq məlumatların silmək tövsiyə olunur."</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
<string name="revanced_spoof_app_version_target_title">Saxta tətbiq versiyası hədəfi</string>
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Köhnə Shorts oynadıcı işarələrin bərpa et</string>
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Köhnə fəaliyyət simvolların bərpa et</string>
@@ -1439,10 +1464,15 @@ Bunu aktivləşdirmə daha yüksək video keyfiyyətləri əngəlin silə bilər
<string name="revanced_playback_speed_dialog_button_summary_on">Düymə göstərilir. Oynatma sürətin standart olaraq qaytarmaq üçün toxunub saxla</string>
<string name="revanced_playback_speed_dialog_button_summary_off">Düymə göstərilmir</string>
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
<string name="revanced_custom_speed_menu_title">Fərdi oynatma sürəti siyahısı</string>
<string name="revanced_custom_speed_menu_summary_on">Fərdi sürət siyahısı göstərilir</string>
<string name="revanced_custom_speed_menu_summary_off">Fərdi sürət siyahısı göstərilmir</string>
<string name="revanced_restore_old_speed_menu_title">Köhnə oynatma sürəti menyusunu bərpa et</string>
<string name="revanced_restore_old_speed_menu_summary_on">Köhnə sürət menyusu göstərilir</string>
<string name="revanced_restore_old_speed_menu_summary_off">Müasir sürət menyusu göstərilir</string>
<string name="revanced_custom_playback_speeds_title">Fərdi oynatma sürəti</string>
<string name="revanced_custom_playback_speeds_summary">Fərdi oynatma sürətlərini əlavə et və ya dəyiş</string>
<string name="revanced_custom_playback_speeds_invalid">Fərdi sürətlər %s dəyərindən az olmalıdır</string>

View File

@@ -137,7 +137,7 @@ Second \"item\" text"</string>
<string name="revanced_debug_logs_disabled">Адладачнае лагаванне адключана</string>
<string name="revanced_debug_logs_none_found">Лагі не знойдзены</string>
<string name="revanced_debug_logs_copied_to_clipboard">Лагі скапіяваны</string>
<string name="revanced_debug_logs_failed_to_export">Не атрымалася экспартаваць лагі: $s</string>
<string name="revanced_debug_logs_failed_to_export">Не ўдалося экспартаваць журналы: %s</string>
<string name="revanced_debug_logs_clear_buffer_title">Ачысціць адладачныя лагі</string>
<string name="revanced_debug_logs_clear_buffer_summary">Ачышчае ўсе захаваныя адладачныя лагі ReVanced</string>
<string name="revanced_debug_logs_clear_toast">Лагі ачышчаны</string>
@@ -468,8 +468,15 @@ Second \"item\" text"</string>
<string name="revanced_external_downloader_action_button_summary_on">Кнопка \"Спампаваць\" адкрывае ваш знешні загрузнік</string>
<string name="revanced_external_downloader_action_button_summary_off">Кнопка \"Спампаваць\" адкрывае ўласную праграму загрузкі ў праграме</string>
<string name="revanced_external_downloader_name_title">Назва пакета загрузніка</string>
<string name="revanced_external_downloader_name_summary">Імя пакета ўсталяванай знешняй праграмы загрузкі, напрыклад NewPipe або Seal</string>
<string name="revanced_external_downloader_name_summary">Назва пакета вашага ўсталяванага знешняга спампоўшчыка праграмы</string>
<string name="revanced_external_downloader_other_item_hint">Увядзіце назву пакета</string>
<string name="revanced_external_downloader_other_item">Іншае</string>
<string name="revanced_external_downloader_not_found_title">Праграма не ўстаноўлена</string>
<string name="revanced_external_downloader_not_installed_warning">%s не ўсталяваны. Калі ласка, усталюйце яго.</string>
<string name="revanced_external_downloader_package_not_found_warning">"Не ўдалося знайсці ўстаноўленую праграму з назвай пакета: %s
Праверце, ці правільная назва пакета і ці ўстаноўлена праграма"</string>
<string name="revanced_external_downloader_empty_warning">Назва пакета не можа быць пустой</string>
</patch>
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
<string name="revanced_disable_precise_seeking_gesture_title">Адключыць жэст дакладнага пошуку</string>
@@ -732,6 +739,7 @@ Second \"item\" text"</string>
<string name="revanced_hide_seekbar_title">Схаваць панэль прагрэсу відэапрайгравальніка</string>
<string name="revanced_hide_seekbar_summary_on">Панэль пошуку відэаплэера схавана</string>
<string name="revanced_hide_seekbar_summary_off">Адлюстроўваецца панэль пошуку відэаплэера</string>
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
<string name="revanced_hide_seekbar_thumbnail_title">Схаваць панэль прагрэсу з эскізамі відэа</string>
<string name="revanced_hide_seekbar_thumbnail_summary_on">Панэль прагрэсу з эскізамі відэа схавана</string>
<string name="revanced_hide_seekbar_thumbnail_summary_off">Панэль прагрэсу з эскізамі відэа паказана</string>
@@ -1190,8 +1198,6 @@ Second \"item\" text"</string>
Гэта зменіць знешні выгляд і функцыі прыкладання, але могуць узнікнуць невядомыя пабочныя эфекты.
Калі пазней будзе адключана, рэкамендуецца ачысціць даныя прыкладання, каб пазбегнуць памылак у інтэрфейсе."</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
<string name="revanced_spoof_app_version_target_title">Падробка мэтавай версіі праграмы</string>
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 — Восстановить старые значки плеера Shorts</string>
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Аднаўленне старых значкоў навігацыі</string>
@@ -1460,10 +1466,18 @@ Second \"item\" text"</string>
<string name="revanced_playback_speed_dialog_button_summary_on">Кнопка паказана. Націсніце і ўтрымлівайце, каб скінуць хуткасць прайгравання да стандартнай</string>
<string name="revanced_playback_speed_dialog_button_summary_off">Кнопка не паказваецца</string>
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
<string name="revanced_video_quality_dialog_button_title">Паказаць кнопку якасці відэа</string>
<string name="revanced_video_quality_dialog_button_summary_on">Кнопка паказана. Націсніце і ўтрымлівайце, каб скінуць якасць да па змаўчанні</string>
<string name="revanced_video_quality_dialog_button_summary_off">Кнопка не паказваецца</string>
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
<string name="revanced_custom_speed_menu_title">Меню пользовательской скорости воспроизведения</string>
<string name="revanced_custom_speed_menu_summary_on">Меню пользовательской скорости отображается</string>
<string name="revanced_custom_speed_menu_summary_off">Меню пользовательской скорости не отображается</string>
<string name="revanced_restore_old_speed_menu_title">Аднавіць старое меню хуткасці прайгравання</string>
<string name="revanced_restore_old_speed_menu_summary_on">Паказана старое меню хуткасці</string>
<string name="revanced_restore_old_speed_menu_summary_off">Паказана сучаснае меню хуткасці</string>
<string name="revanced_custom_playback_speeds_title">Карыстальніцкія хуткасці прайгравання</string>
<string name="revanced_custom_playback_speeds_summary">Добавьте или измените пользовательскую скорость воспроизведения</string>
<string name="revanced_custom_playback_speeds_invalid">Нестандартныя хуткасці павінны быць менш за %s</string>

View File

@@ -137,7 +137,7 @@ Second \"item\" text"</string>
<string name="revanced_debug_logs_disabled">Отстраняването на грешки е деактивирано</string>
<string name="revanced_debug_logs_none_found">Не са намерени логове</string>
<string name="revanced_debug_logs_copied_to_clipboard">Логовете са копирани</string>
<string name="revanced_debug_logs_failed_to_export">Неуспешно експортиране на логове: $s</string>
<string name="revanced_debug_logs_failed_to_export">Неуспешно експортиране на логове: %s</string>
<string name="revanced_debug_logs_clear_buffer_title">Изчистване на логовете за отстраняване на грешки</string>
<string name="revanced_debug_logs_clear_buffer_summary">Изчиства всички съхранени логове за отстраняване на грешки на ReVanced</string>
<string name="revanced_debug_logs_clear_toast">Логовете са изчистени</string>
@@ -468,8 +468,15 @@ Second \"item\" text"</string>
<string name="revanced_external_downloader_action_button_summary_on">Бутонът за изтегляне отваря избраното от Вас външно приложение за изтегляне</string>
<string name="revanced_external_downloader_action_button_summary_off">Бутонът за изтегляне отваря вграденото приложение за изтегляне</string>
<string name="revanced_external_downloader_name_title">Име на пакета на изтеглящото приложение</string>
<string name="revanced_external_downloader_name_summary">Име на пакета на приложението за изтегляне, като NewPipe или Seal</string>
<string name="revanced_external_downloader_name_summary">Име на пакета на вашето инсталирано външно приложение за изтегляне</string>
<string name="revanced_external_downloader_other_item_hint">Въведете името на пакета</string>
<string name="revanced_external_downloader_other_item">Други</string>
<string name="revanced_external_downloader_not_found_title">Приложението не е инсталирано</string>
<string name="revanced_external_downloader_not_installed_warning">%s не е инсталиран. Инсталирайте го.</string>
<string name="revanced_external_downloader_package_not_found_warning">"Не може да бъде намерено инсталирано приложение с име на пакет: %s
Проверете дали името на пакета е правилно и приложението е инсталирано"</string>
<string name="revanced_external_downloader_empty_warning">Името на пакета не може да бъде празно</string>
</patch>
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
<string name="revanced_disable_precise_seeking_gesture_title">Деактивиране на жеста за точно търсене</string>
@@ -732,6 +739,7 @@ Second \"item\" text"</string>
<string name="revanced_hide_seekbar_title">Скриване на лентата за търсене на видео плейър</string>
<string name="revanced_hide_seekbar_summary_on">Лентата за време на плейъра е скрита</string>
<string name="revanced_hide_seekbar_summary_off">Лентата за време на плейъра се показва</string>
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
<string name="revanced_hide_seekbar_thumbnail_title">Скриване на лентата за търсене на миниатюри на видеоклипове</string>
<string name="revanced_hide_seekbar_thumbnail_summary_on">Лентата за търсене на миниатюри на видеоклипове е скрита</string>
<string name="revanced_hide_seekbar_thumbnail_summary_off">Лентата за търсене на миниатюри на видеоклипове е показана</string>
@@ -1189,8 +1197,6 @@ Second \"item\" text"</string>
Това ще промени външния вид и функциите на приложението, но може да възникнат неизвестни странични ефекти.
Ако по-късно бъде изключено, препоръчително е да изчистите данните на приложението, за да предотвратите грешки в потребителския интерфейс."</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
<string name="revanced_spoof_app_version_target_title">Подлъгване за версията на</string>
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Възстановете старите икони на Shorts в плейъра</string>
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Възстановяване на стари икони за навигация</string>
@@ -1459,10 +1465,18 @@ Second \"item\" text"</string>
<string name="revanced_playback_speed_dialog_button_summary_on">Бутонът е показан. Докоснете и задръжте, за да върнете скоростта на възпроизвеждане към стойността по подразбиране</string>
<string name="revanced_playback_speed_dialog_button_summary_off">Бутонът не е показан</string>
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
<string name="revanced_video_quality_dialog_button_title">Покажи бутона за качество на видеото</string>
<string name="revanced_video_quality_dialog_button_summary_on">Бутонът е показан. Докоснете и задръжте, за да възстановите качеството до подразбиране</string>
<string name="revanced_video_quality_dialog_button_summary_off">Бутонът не е показан</string>
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
<string name="revanced_custom_speed_menu_title">Менюто за потребителска скорост</string>
<string name="revanced_custom_speed_menu_summary_on">Менюто за потребителска скорост се показва</string>
<string name="revanced_custom_speed_menu_summary_off">Менюто за потребителска скорост не се показва</string>
<string name="revanced_restore_old_speed_menu_title">Възстановяване на старото меню за скорост на възпроизвеждане</string>
<string name="revanced_restore_old_speed_menu_summary_on">Показва се старото меню за скорост</string>
<string name="revanced_restore_old_speed_menu_summary_off">Показва се модерното меню за скорост</string>
<string name="revanced_custom_playback_speeds_title">Персонализирани скорости на възпроизвеждане</string>
<string name="revanced_custom_playback_speeds_summary">Добавете или променете скоростa на възпроизвеждане</string>
<string name="revanced_custom_playback_speeds_invalid">Персонализираните скорости трябва да са по-малки от %s</string>

View File

@@ -133,7 +133,7 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
<string name="revanced_debug_logs_disabled">ডিবাগ লগিং নিষ্ক্রিয় করা হয়েছে</string>
<string name="revanced_debug_logs_none_found">কোনো লগ পাওয়া যায়নি</string>
<string name="revanced_debug_logs_copied_to_clipboard">লগ অনুলিপি করা হয়েছে</string>
<string name="revanced_debug_logs_failed_to_export">লগ রপ্তানি করতে ব্যর্থ: $s</string>
<string name="revanced_debug_logs_failed_to_export">লগ এক্সপোর্ট করা যায়নি: %s</string>
<string name="revanced_debug_logs_clear_buffer_title">ডিবাগ লগগুলি সাফ করুন</string>
<string name="revanced_debug_logs_clear_buffer_summary">সমস্ত সঞ্চিত ReVanced ডিবাগ লগ সাফ করে</string>
<string name="revanced_debug_logs_clear_toast">লগ সাফ করা হয়েছে</string>
@@ -464,8 +464,15 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
<string name="revanced_external_downloader_action_button_summary_on">ডাউনলোড বোতামটি আপনার বাহিরের ডাউনলোডার খুলবে</string>
<string name="revanced_external_downloader_action_button_summary_off">ডাউনলোড বোতামটি নেটিভ ইন-অ্যাপ ডাউনলোডার খুলবে</string>
<string name="revanced_external_downloader_name_title">ডাউনলোডারের প্যাকেজ নাম</string>
<string name="revanced_external_downloader_name_summary">আপনার ইনস্টল করা বাইরের ডাউনলোডার অ্যাপের প্যাকেজ নাম, যেমন NewPipe বা Seal</string>
<string name="revanced_external_downloader_name_summary">আপনার ইনস্টল করা বাহ্যিক ডাউনলোডার অ্যাপের প্যাকেজের নাম</string>
<string name="revanced_external_downloader_other_item_hint">প্যাকেজের নাম লিখুন</string>
<string name="revanced_external_downloader_other_item">অন্যান্য</string>
<string name="revanced_external_downloader_not_found_title">অ্যাপ ইনস্টল করা নেই</string>
<string name="revanced_external_downloader_not_installed_warning">%s ইনস্টল করা নেই, ইনস্টল করুন।</string>
<string name="revanced_external_downloader_package_not_found_warning">"প্যাকেজের নাম: %s সহ ইনস্টল করা অ্যাপটি খুঁজে পাওয়া যায়নি
প্যাকেজের নামটি সঠিক এবং অ্যাপটি ইনস্টল করা আছে কিনা তা যাচাই করুন"</string>
<string name="revanced_external_downloader_empty_warning">প্যাকেজের নাম খালি রাখা যাবে না</string>
</patch>
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
<string name="revanced_disable_precise_seeking_gesture_title">ভিডিওর নির্দিষ্ট অংশে যাওয়ার অঙ্গভঙ্গি নিষ্ক্রিয় করুন</string>
@@ -728,6 +735,7 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
<string name="revanced_hide_seekbar_title">ভিডিও প্লেয়ারের সিকবার লুকান</string>
<string name="revanced_hide_seekbar_summary_on">ভিডিও প্লেয়ারে সিকবার লুকিয়ে রয়েছে</string>
<string name="revanced_hide_seekbar_summary_off">ভিডিও প্লেয়ারে সিকবার প্রদর্শিত হয়েছে</string>
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
<string name="revanced_hide_seekbar_thumbnail_title">ভিডিও থাম্বনেইল সিকবার লুকান</string>
<string name="revanced_hide_seekbar_thumbnail_summary_on">ভিডিও থাম্বনেইল সিকবার লুকানো আছে</string>
<string name="revanced_hide_seekbar_thumbnail_summary_off">ভিডিও থাম্বনেইল সিকবার দেখানো হয়েছে</string>
@@ -1185,8 +1193,6 @@ YouTube সেটিংসে অটো প্লে পরিবর্তন
এটি অ্যাপ্লিকেশনটির চেহারা এবং বৈশিষ্ট্য পরিবর্তন করবে, তবে অজানা পার্শ্ব প্রতিক্রিয়া হতে পারে।
পরে যদি বন্ধ করা হয়, UI বাগ এড়াতে অ্যাপ্লিকেশন ডেটা পরিষ্কার করার পরামর্শ দেওয়া হয়।"</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
<string name="revanced_spoof_app_version_target_title">স্পুফ অ্যাপ সংস্করণ লক্ষ্য</string>
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - পুরনো Shorts প্লেয়ার আইকন পুনরুদ্ধার করুন</string>
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - পুরনো নেভিগেশন আইকন পুনরুদ্ধার করুন</string>
@@ -1455,10 +1461,18 @@ DeArrow সম্পর্কে আরও জানতে এখানে ট
<string name="revanced_playback_speed_dialog_button_summary_on">বোতামটি দেখানো হয়েছে। প্লেব্যাক স্পীড ডিফল্টে রিসেট করতে ট্যাপ করে ধরে রাখুন।</string>
<string name="revanced_playback_speed_dialog_button_summary_off">বোতাম প্রদর্শিত হয়নি</string>
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
<string name="revanced_video_quality_dialog_button_title">ভিডিও গুণমান বোতাম দেখান</string>
<string name="revanced_video_quality_dialog_button_summary_on">বোতামটি দেখানো হয়েছে। গুণমান ডিফল্টে রিসেট করতে ট্যাপ করে ধরে রাখুন।</string>
<string name="revanced_video_quality_dialog_button_summary_off">বোতামটি দেখানো হয়নি।</string>
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
<string name="revanced_custom_speed_menu_title">কাস্টম প্লেব্যাক গতি মেনু</string>
<string name="revanced_custom_speed_menu_summary_on">কাস্টম স্পিড মেনু দেখানো হচ্ছে</string>
<string name="revanced_custom_speed_menu_summary_off">কাস্টম স্পিড মেনু দেখানো হচ্ছে না</string>
<string name="revanced_restore_old_speed_menu_title">পুরানো প্লেব্যাক গতি মেনু পুনরুদ্ধার করুন</string>
<string name="revanced_restore_old_speed_menu_summary_on">পুরানো গতির মেনু দেখানো হয়েছে</string>
<string name="revanced_restore_old_speed_menu_summary_off">আধুনিক গতির মেনু দেখানো হয়েছে</string>
<string name="revanced_custom_playback_speeds_title">নিজস্ব প্লেব্যাক স্পিড</string>
<string name="revanced_custom_playback_speeds_summary">কাস্টম প্লেব্যাক গতি যোগ করুন অথবা পরিবর্তন করুন</string>
<string name="revanced_custom_playback_speeds_invalid">কাস্টম গতি %s এর চেয়ে কম হতে হবে</string>

View File

@@ -127,6 +127,7 @@ Second \"item\" text"</string>
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
</patch>
<patch id="layout.hide.seekbar.hideSeekbarPatch">
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
</patch>
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
@@ -169,8 +170,6 @@ Second \"item\" text"</string>
<patch id="layout.formfactor.changeFormFactorPatch">
</patch>
<patch id="layout.spoofappversion.spoofAppVersionPatch">
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
</patch>
<patch id="layout.startpage.changeStartPagePatch">
</patch>
@@ -223,6 +222,8 @@ Second \"item\" text"</string>
</patch>
<patch id="video.speed.button.playbackSpeedButtonPatch">
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">

View File

@@ -127,6 +127,7 @@ Second \"item\" text"</string>
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
</patch>
<patch id="layout.hide.seekbar.hideSeekbarPatch">
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
</patch>
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
@@ -169,8 +170,6 @@ Second \"item\" text"</string>
<patch id="layout.formfactor.changeFormFactorPatch">
</patch>
<patch id="layout.spoofappversion.spoofAppVersionPatch">
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
</patch>
<patch id="layout.startpage.changeStartPagePatch">
</patch>
@@ -223,6 +222,8 @@ Second \"item\" text"</string>
</patch>
<patch id="video.speed.button.playbackSpeedButtonPatch">
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">

View File

@@ -137,7 +137,7 @@ Nebudete informováni o žádné neočekávané události."</string>
<string name="revanced_debug_logs_disabled">Ladění je vypnuto</string>
<string name="revanced_debug_logs_none_found">Nebyly nalezeny žádné protokoly</string>
<string name="revanced_debug_logs_copied_to_clipboard">Protokoly zkopírovány</string>
<string name="revanced_debug_logs_failed_to_export">Nepodařilo se exportovat protokoly: $s</string>
<string name="revanced_debug_logs_failed_to_export">Nepodařilo se exportovat protokoly: %s</string>
<string name="revanced_debug_logs_clear_buffer_title">Vymazat ladicí protokoly</string>
<string name="revanced_debug_logs_clear_buffer_summary">Vymaže všechny uložené ladicí protokoly ReVanced</string>
<string name="revanced_debug_logs_clear_toast">Protokoly vymazány</string>
@@ -468,8 +468,15 @@ Tato funkce je dostupná pouze pro starší zařízení"</string>
<string name="revanced_external_downloader_action_button_summary_on">Tlačítko pro stahování otevře váš externí stahovač</string>
<string name="revanced_external_downloader_action_button_summary_off">Tlačítko pro stahování otevře nativní stahovač v aplikaci</string>
<string name="revanced_external_downloader_name_title">Název balíčku stahovače</string>
<string name="revanced_external_downloader_name_summary">Název balíčku vaší nainstalované externí aplikace pro stahování, například NewPipe nebo Seal</string>
<string name="revanced_external_downloader_name_summary">Název balíčku vaší nainstalované externí aplikace pro stahování</string>
<string name="revanced_external_downloader_other_item_hint">Zadejte název balíčku</string>
<string name="revanced_external_downloader_other_item">Jiné</string>
<string name="revanced_external_downloader_not_found_title">Aplikace není nainstalována</string>
<string name="revanced_external_downloader_not_installed_warning">%s není nainstalováno. Prosím, nainstalujte.</string>
<string name="revanced_external_downloader_package_not_found_warning">"Nepodařilo se najít nainstalovanou aplikaci s názvem balíčku: %s
Zkontrolujte, zda je název balíčku správný a aplikace je nainstalována"</string>
<string name="revanced_external_downloader_empty_warning">Název balíčku nemůže být prázdný</string>
</patch>
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
<string name="revanced_disable_precise_seeking_gesture_title">Zakázat gesto pro přesné hledání</string>
@@ -732,6 +739,7 @@ Chcete-li zobrazit nabídku zvukové stopy, změňte možnost „Zfalšovat stre
<string name="revanced_hide_seekbar_title">Skrýt posuvník přehrávače videa</string>
<string name="revanced_hide_seekbar_summary_on">Posuvník ve video přehrávači je skryt</string>
<string name="revanced_hide_seekbar_summary_off">Posuvník ve video přehrávači je zobrazen</string>
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
<string name="revanced_hide_seekbar_thumbnail_title">Skrýt posuvník miniatur videí</string>
<string name="revanced_hide_seekbar_thumbnail_summary_on">Posuvník miniatur videí je skryt</string>
<string name="revanced_hide_seekbar_thumbnail_summary_off">Posuvník miniatur videí je zobrazen</string>
@@ -1189,8 +1197,6 @@ Rozložení automobilu
To změní vzhled a funkce aplikace, ale mohou se objevit neznámé vedlejší efekty.
Pokud bude později vypnuta, doporučujeme vymazat data aplikace, aby se zabránilo chybám uživatelského rozhraní."</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
<string name="revanced_spoof_app_version_target_title">Cíl napodobení verze aplikace</string>
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Obnovuje staré ikony Shorts přehrávače</string>
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 Obnovit staré ikony navigace</string>
@@ -1459,10 +1465,18 @@ Povolením této funkce lze odemknout vyšší kvality videa"</string>
<string name="revanced_playback_speed_dialog_button_summary_on">Tlačítko je zobrazeno. Klepnutím a podržením obnovíte výchozí rychlost přehrávání</string>
<string name="revanced_playback_speed_dialog_button_summary_off">Tlačítko se nezobrazuje</string>
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
<string name="revanced_video_quality_dialog_button_title">Zobrazit tlačítko kvality videa</string>
<string name="revanced_video_quality_dialog_button_summary_on">Tlačítko je zobrazeno. Klepnutím a podržením obnovíte kvalitu na výchozí</string>
<string name="revanced_video_quality_dialog_button_summary_off">Tlačítko není zobrazeno.</string>
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
<string name="revanced_custom_speed_menu_title">Menu vlastní rychlosti přehrávání</string>
<string name="revanced_custom_speed_menu_summary_on">Menu vlastní rychlosti se zobrazuje</string>
<string name="revanced_custom_speed_menu_summary_off">Menu vlastní rychlosti se nezobrazuje</string>
<string name="revanced_restore_old_speed_menu_title">Obnovit staré menu rychlosti přehrávání</string>
<string name="revanced_restore_old_speed_menu_summary_on">Zobrazeno staré menu rychlosti</string>
<string name="revanced_restore_old_speed_menu_summary_off">Zobrazeno moderní menu rychlosti</string>
<string name="revanced_custom_playback_speeds_title">Vlastní rychlosti přehrávání</string>
<string name="revanced_custom_playback_speeds_summary">Přidat nebo změnit vlastní rychlosti přehrávání</string>
<string name="revanced_custom_playback_speeds_invalid">Vlastní rychlosti musí být menší než %s</string>

View File

@@ -137,7 +137,7 @@ Du modtager ikke notifikationer om uventede hændelser."</string>
<string name="revanced_debug_logs_disabled">Fejlsøgningslogning er deaktiveret</string>
<string name="revanced_debug_logs_none_found">Ingen logfiler fundet</string>
<string name="revanced_debug_logs_copied_to_clipboard">Logfiler kopieret</string>
<string name="revanced_debug_logs_failed_to_export">Kunne ikke eksportere logfiler: $s</string>
<string name="revanced_debug_logs_failed_to_export">Kunne ikke eksportere logfiler: %s</string>
<string name="revanced_debug_logs_clear_buffer_title">Ryd fejlsøgningslogfiler</string>
<string name="revanced_debug_logs_clear_buffer_summary">Rydder alle gemte ReVanced-fejlsøgningslogfiler</string>
<string name="revanced_debug_logs_clear_toast">Logfiler ryddet</string>
@@ -468,8 +468,15 @@ Denne funktion er kun tilgængelig for ældre enheder"</string>
<string name="revanced_external_downloader_action_button_summary_on">Download-knappen åbner din eksterne downloader</string>
<string name="revanced_external_downloader_action_button_summary_off">Download-knappen åbner den indfødte in-app downloader</string>
<string name="revanced_external_downloader_name_title">Downloader pakkenavn</string>
<string name="revanced_external_downloader_name_summary">Pakkenavn på din installerede eksterne downloader-app, såsom NewPipe eller Seal</string>
<string name="revanced_external_downloader_name_summary">Pakkenavn på din installerede eksterne download-app</string>
<string name="revanced_external_downloader_other_item_hint">Indtast pakkenavnet</string>
<string name="revanced_external_downloader_other_item">Andet</string>
<string name="revanced_external_downloader_not_found_title">Appen er ikke installeret</string>
<string name="revanced_external_downloader_not_installed_warning">%s er ikke installeret. Installér den.</string>
<string name="revanced_external_downloader_package_not_found_warning">"Kunne ikke finde installeret app med pakkenavn: %s
Kontrollér, at pakkenavnet er korrekt, og at appen er installeret"</string>
<string name="revanced_external_downloader_empty_warning">Pakkenavnet må ikke være tomt</string>
</patch>
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
<string name="revanced_disable_precise_seeking_gesture_title">Deaktivér præcis søgemåde</string>
@@ -732,6 +739,7 @@ For at vise lydspormenuen skal du ændre \"Spoof videostream\" til iOS TV"</stri
<string name="revanced_hide_seekbar_title">Skjul videoafspillerens søgelinje</string>
<string name="revanced_hide_seekbar_summary_on">Videoafspillerens søgelinje er skjult</string>
<string name="revanced_hide_seekbar_summary_off">Videoafspillerens søgelinje vises</string>
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
<string name="revanced_hide_seekbar_thumbnail_title">Skjul video-miniaturebilledernes søgelinje</string>
<string name="revanced_hide_seekbar_thumbnail_summary_on">Video-miniaturebilledernes søgelinje er skjult</string>
<string name="revanced_hide_seekbar_thumbnail_summary_off">Video-miniaturebilledernes søgelinje vises</string>
@@ -1191,8 +1199,6 @@ Automotive-layout
Dette ændrer appens udseende og funktioner, men ukendte bivirkninger kan forekomme.
Hvis det senere slås fra, anbefales det at rydde app-dataene for at forhindre UI-fejl."</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
<string name="revanced_spoof_app_version_target_title">Spoof app version mål</string>
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Gendan gamle Shorts player ikoner</string>
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Gendan gamle navigationsikoner</string>
@@ -1461,10 +1467,18 @@ Aktivering af dette kan låse op for højere videokvalitet"</string>
<string name="revanced_playback_speed_dialog_button_summary_on">Knappen vises. Tryk og hold for at nulstille afspilningshastigheden til standard.</string>
<string name="revanced_playback_speed_dialog_button_summary_off">Knap vises ikke</string>
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
<string name="revanced_video_quality_dialog_button_title">Vis videokvalitetsknap</string>
<string name="revanced_video_quality_dialog_button_summary_on">Knap vises. Tryk og hold nede for at nulstille kvaliteten til standard</string>
<string name="revanced_video_quality_dialog_button_summary_off">Knappen vises ikke</string>
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
<string name="revanced_custom_speed_menu_title">Tilpasset afspilningshastighed menu</string>
<string name="revanced_custom_speed_menu_summary_on">Tilpasset hastighed menu er vist</string>
<string name="revanced_custom_speed_menu_summary_off">Brugerdefineret hastighedsmenu vises ikke</string>
<string name="revanced_restore_old_speed_menu_title">Gendan gammel afspilningshastighedsmenu</string>
<string name="revanced_restore_old_speed_menu_summary_on">Gammel hastighedsmenu vises</string>
<string name="revanced_restore_old_speed_menu_summary_off">Moderne hastighedsmenu vises</string>
<string name="revanced_custom_playback_speeds_title">Tilpasset afspilningshastighed</string>
<string name="revanced_custom_playback_speeds_summary">Tilføj eller ændr den brugerdefinerede afspilningshastighed</string>
<string name="revanced_custom_playback_speeds_invalid">Brugerdefinerede hastigheder skal være mindre end %s</string>

View File

@@ -72,7 +72,9 @@ Um neue Sprachen zu übersetzen, besuchen Sie translate.revanced.app"</string>
<string name="gms_core_toast_not_installed_message">MicroG GmsCore ist nicht installiert. Installieren Sie es.</string>
<string name="gms_core_dialog_title">Aktion notwendig</string>
<string name="gms_core_dialog_not_whitelisted_not_allowed_in_background_message">"MicroG GmsCore hat keine Berechtigung, im Hintergrund zu laufen.
Folgen Sie der Anleitung \"Meine App nicht beenden\" für Ihr Telefon und wenden Sie die Anweisungen auf Ihre MicroG-Installation an.
Folgen Sie der Anleitung \"Don't kill my app\" für Ihr Gerät und wenden Sie die Anweisungen auf Ihre MicroG-Installation an.
Dies ist erforderlich, damit die App funktioniert."</string>
<string name="gms_core_dialog_open_website_text">Website öffnen</string>
<string name="gms_core_dialog_not_whitelisted_using_battery_optimizations_message">"Die Batterieoptimierung von MicroG GmsCore muss deaktiviert werden, um Probleme zu vermeiden.
@@ -85,7 +87,7 @@ Tippen Sie auf die Schaltfläche \"Fortfahren\" und erlauben Sie die Optimierung
<patch id="misc.settings.settingsPatch">
<string name="revanced_settings_screen_00_about_title">Über</string>
<string name="revanced_settings_screen_01_ads_title">Werbung</string>
<string name="revanced_settings_screen_02_alt_thumbnails_title">Alternative Miniaturbilder</string>
<string name="revanced_settings_screen_02_alt_thumbnails_title">Alternative Thumbnails</string>
<string name="revanced_settings_screen_03_feed_title">Feed</string>
<string name="revanced_settings_screen_04_general_title">Allgemein</string>
<string name="revanced_settings_screen_05_player_title">Spieler</string>
@@ -133,7 +135,7 @@ Sie werden nicht über unerwartete Ereignisse informiert."</string>
<string name="revanced_debug_logs_disabled">Debug-Protokollierung ist deaktiviert</string>
<string name="revanced_debug_logs_none_found">Keine Protokolle gefunden</string>
<string name="revanced_debug_logs_copied_to_clipboard">Protokolle kopiert</string>
<string name="revanced_debug_logs_failed_to_export">Fehler beim Exportieren von Protokollen: $s</string>
<string name="revanced_debug_logs_failed_to_export">Fehler beim Exportieren der Protokolle: %s</string>
<string name="revanced_debug_logs_clear_buffer_title">Debug-Protokolle löschen</string>
<string name="revanced_debug_logs_clear_buffer_summary">Löscht alle gespeicherten ReVanced-Debug-Protokolle</string>
<string name="revanced_debug_logs_clear_toast">Protokolle gelöscht</string>
@@ -463,8 +465,15 @@ Diese Funktion ist nur für ältere Geräte verfügbar"</string>
<string name="revanced_external_downloader_action_button_summary_on">Download-Button öffnet den externen Downloader</string>
<string name="revanced_external_downloader_action_button_summary_off">Download-Button öffnet den nativen In-App-Downloader</string>
<string name="revanced_external_downloader_name_title">Paketname des Downloaders</string>
<string name="revanced_external_downloader_name_summary">Paketname deiner installierten externen Downloader-App wie NewPipe oder Siegel</string>
<string name="revanced_external_downloader_name_summary">Paketname Ihrer installierten externen Downloader-App</string>
<string name="revanced_external_downloader_other_item_hint">Paketnamen eingeben</string>
<string name="revanced_external_downloader_other_item">Andere</string>
<string name="revanced_external_downloader_not_found_title">App nicht installiert</string>
<string name="revanced_external_downloader_not_installed_warning">%s ist nicht installiert. Installier es.</string>
<string name="revanced_external_downloader_package_not_found_warning">"Installierte App mit Paketnamen nicht gefunden: %s
Stellen Sie sicher, dass der Paketname korrekt ist und die App installiert ist"</string>
<string name="revanced_external_downloader_empty_warning">Der Paketname darf nicht leer sein</string>
</patch>
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
<string name="revanced_disable_precise_seeking_gesture_title">Genaue Suchgeste deaktivieren</string>
@@ -725,6 +734,7 @@ Um das Audiotrack-Menü anzuzeigen, ändere \"Video-Streams fälschen\" zu iOS T
<string name="revanced_hide_seekbar_title">Video-Player-Suchleiste ausblenden</string>
<string name="revanced_hide_seekbar_summary_on">Video-Player-Suchleiste ist ausgeblendet</string>
<string name="revanced_hide_seekbar_summary_off">Suchleiste für Video-Player wird angezeigt</string>
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
<string name="revanced_hide_seekbar_thumbnail_title">Suchleiste für Video-Thumbnails ausblenden</string>
<string name="revanced_hide_seekbar_thumbnail_summary_on">Suchleiste für Video-Thumbnails ist ausgeblendet</string>
<string name="revanced_hide_seekbar_thumbnail_summary_off">Suchleiste für Video-Thumbnails ist eingeblendet</string>
@@ -1182,8 +1192,6 @@ Automotive-Layout
Dadurch ändert sich das Erscheinungsbild und die Funktionen der App, es können jedoch unbekannte Nebeneffekte auftreten.
Wenn Sie die Funktion später deaktivieren, wird empfohlen, die App-Daten zu löschen, um UI-Fehler zu vermeiden."</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
<string name="revanced_spoof_app_version_target_title">Spoof-App-Versionsziel</string>
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Alte Shorts Spielersymbole wiederherstellen</string>
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Alte Navigations-Symbole wiederherstellen</string>
@@ -1452,10 +1460,18 @@ Durch Aktivieren dieser Option können höhere Videoqualitäten freigeschaltet w
<string name="revanced_playback_speed_dialog_button_summary_on">Die Schaltfläche wird angezeigt. Tippen und halten, um die Wiedergabegeschwindigkeit auf die Standardeinstellung zurückzusetzen.</string>
<string name="revanced_playback_speed_dialog_button_summary_off">Button wird nicht angezeigt</string>
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
<string name="revanced_video_quality_dialog_button_title">Videoqualität-Schaltfläche anzeigen</string>
<string name="revanced_video_quality_dialog_button_summary_on">Schaltfläche wird angezeigt. Tippen und halten, um die Qualität auf Standard zurückzusetzen</string>
<string name="revanced_video_quality_dialog_button_summary_off">Schaltfläche wird nicht angezeigt</string>
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
<string name="revanced_custom_speed_menu_title">Benutzerdefiniertes Wiedergabegeschwindigkeitsmenü</string>
<string name="revanced_custom_speed_menu_summary_on">Benutzerdefiniertes Geschwindigkeitsmenü wird angezeigt</string>
<string name="revanced_custom_speed_menu_summary_off">Benutzerdefiniertes Geschwindigkeitsmenü wird nicht angezeigt</string>
<string name="revanced_restore_old_speed_menu_title">Altes Wiedergabegeschwindigkeitsmenü wiederherstellen</string>
<string name="revanced_restore_old_speed_menu_summary_on">Altes Geschwindigkeitsmenü wird angezeigt</string>
<string name="revanced_restore_old_speed_menu_summary_off">Modernes Geschwindigkeitsmenü wird angezeigt</string>
<string name="revanced_custom_playback_speeds_title">Benutzerdefinierte Wiedergabegeschwindigkeiten</string>
<string name="revanced_custom_playback_speeds_summary">Hinzufügen oder Ändern der benutzerdefinierten Wiedergabegeschwindigkeit</string>
<string name="revanced_custom_playback_speeds_invalid">Benutzerdefinierte Geschwindigkeiten müssen kleiner als %s sein</string>

View File

@@ -137,7 +137,7 @@ Second \"item\" text"</string>
<string name="revanced_debug_logs_disabled">Η καταγραφή εντοπισμού σφαλμάτων είναι απενεργοποιημένη</string>
<string name="revanced_debug_logs_none_found">Δεν βρέθηκαν αρχεία καταγραφής</string>
<string name="revanced_debug_logs_copied_to_clipboard">Τα αρχεία καταγραφής αντιγράφηκαν</string>
<string name="revanced_debug_logs_failed_to_export">Αποτυχία εξαγωγής αρχείων καταγραφής: $s</string>
<string name="revanced_debug_logs_failed_to_export">Αποτυχία εξαγωγής αρχείων καταγραφής: %s</string>
<string name="revanced_debug_logs_clear_buffer_title">Εκκαθάριση αρχείων καταγραφής εντοπισμού σφαλμάτων</string>
<string name="revanced_debug_logs_clear_buffer_summary">Εκκαθάριση όλων των αποθηκευμένων αρχειών καταγραφής εντοπισμού σφαλμάτων ReVanced</string>
<string name="revanced_debug_logs_clear_toast">Τα αρχεία καταγραφής εκκαθαρίστηκαν</string>
@@ -203,7 +203,7 @@ Second \"item\" text"</string>
<string name="revanced_hide_show_more_button_title">Κουμπί «Εμφάνιση περισσότερων»</string>
<string name="revanced_hide_show_more_button_summary_on">Κρυμμένο\n\nΑφορά το κουμπί «Εμφάνιση περισσότερων» στα αποτελέσματα αναζήτησης</string>
<string name="revanced_hide_show_more_button_summary_off">Εμφανίζεται\n\nΑφορά το κουμπί «Εμφάνιση περισσότερων» στα αποτελέσματα αναζήτησης</string>
<string name="revanced_hide_surveys_title">Απόκρυψη ερευνών</string>
<string name="revanced_hide_surveys_title">Έρευνες</string>
<string name="revanced_hide_surveys_summary_on">Κρυμμένες</string>
<string name="revanced_hide_surveys_summary_off">Εμφανίζονται</string>
<string name="revanced_hide_ticket_shelf_title">Ενότητα εισιτηρίων</string>
@@ -243,7 +243,7 @@ Second \"item\" text"</string>
<string name="revanced_hide_quick_actions_title">Γρήγορες ενέργειες</string>
<string name="revanced_hide_quick_actions_summary_on">Κρυμμένες</string>
<string name="revanced_hide_quick_actions_summary_off">Εμφανίζονται</string>
<string name="revanced_hide_related_videos_title">Σχετικά βίντεο στις γρήγορες ενέργειες</string>
<string name="revanced_hide_related_videos_title">Περισσότερα βίντεο στις γρήγορες ενέργειες</string>
<string name="revanced_hide_related_videos_summary_on">Κρυμμένα</string>
<string name="revanced_hide_related_videos_summary_off">Εμφανίζονται</string>
<string name="revanced_hide_subscribers_community_guidelines_title">Οδηγίες συνδρομητών</string>
@@ -253,8 +253,8 @@ Second \"item\" text"</string>
<string name="revanced_hide_timed_reactions_summary_on">Κρυμμένες</string>
<string name="revanced_hide_timed_reactions_summary_off">Εμφανίζονται</string>
<string name="revanced_hide_ai_generated_video_summary_section_title">Σύνοψη βίντεο που δημιουργήθηκε από AI</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">Η ενότητα σύνοψης βίντεο που δημιουργήθηκε από AI είναι κρυμμένη</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Ενότητα σύνοψης βίντεο που δημιουργείται από ΤΝ: Εμφανίζεται</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_on">Κρυμμένη</string>
<string name="revanced_hide_ai_generated_video_summary_section_summary_off">Εμφανίζεται</string>
<string name="revanced_hide_ask_section_title">Ενότητα «Ερώτηση»</string>
<string name="revanced_hide_ask_section_summary_on">Κρυμμένη</string>
<string name="revanced_hide_ask_section_summary_off">Εμφανίζεται</string>
@@ -283,18 +283,18 @@ Second \"item\" text"</string>
<string name="revanced_hide_description_components_screen_summary">Απόκρυψη ή εμφάνιση στοιχείων περιγραφής βίντεο</string>
<string name="revanced_hide_filter_bar_screen_title">Γραμμή φίλτρων</string>
<string name="revanced_hide_filter_bar_screen_summary">Απόκρυψη ή εμφάνιση της γραμμής φίλτρων στις ροές, στα σχετικά βίντεο, στα αποτελέσματα αναζήτησης και στο ιστορικό παρακολούθησης</string>
<string name="revanced_hide_filter_bar_feed_in_feed_title">Γραμμή κατηγοριών στις ροές</string>
<string name="revanced_hide_filter_bar_feed_in_feed_title">Γραμμή φίλτρων στις ροές</string>
<string name="revanced_hide_filter_bar_feed_in_feed_summary_on">Κρυμμένη</string>
<string name="revanced_hide_filter_bar_feed_in_feed_summary_off">Εμφανίζεται</string>
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Γραμμή κατηγοριών στα σχετικά βίντεο</string>
<string name="revanced_hide_filter_bar_feed_in_related_videos_title">Γραμμή φίλτρων στα σχετικά βίντεο</string>
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_on">Κρυμμένη</string>
<string name="revanced_hide_filter_bar_feed_in_related_videos_summary_off">Εμφανίζεται</string>
<string name="revanced_hide_filter_bar_feed_in_search_title">Γραμμή κατηγοριών στα αποτελέσματα αναζήτησης</string>
<string name="revanced_hide_filter_bar_feed_in_search_title">Γραμμή φίλτρων στα αποτελέσματα αναζήτησης</string>
<string name="revanced_hide_filter_bar_feed_in_search_summary_on">Κρυμμένη</string>
<string name="revanced_hide_filter_bar_feed_in_search_summary_off">Εμφανίζεται</string>
<string name="revanced_hide_filter_bar_feed_in_history_title">Απόκρυψη στο ιστορικό παρακολούθησης</string>
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Κρυμμένα</string>
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Εμφανίζονται</string>
<string name="revanced_hide_filter_bar_feed_in_history_title">Γραμμή φίλτρων στο ιστορικό παρακολούθησης</string>
<string name="revanced_hide_filter_bar_feed_in_history_summary_on">Κρυμμένη</string>
<string name="revanced_hide_filter_bar_feed_in_history_summary_off">Εμφανίζεται</string>
<string name="revanced_channel_screen_title">Σελίδα καναλιού</string>
<string name="revanced_channel_screen_summary">Απόκρυψη ή εμφάνιση στοιχείων της σελίδας καναλιού</string>
<!-- 'For You' should be translated using the same localized wording YouTube displays. -->
@@ -317,12 +317,12 @@ Second \"item\" text"</string>
<string name="revanced_hide_visit_store_button_summary_off">Εμφανίζεται</string>
<string name="revanced_comments_screen_title">Σχόλια</string>
<string name="revanced_comments_screen_summary">Απόκρυψη ή εμφάνιση στοιχείων στα σχόλια</string>
<string name="revanced_hide_comments_ai_chat_summary_title">Απόκρυψη σύνοψης συνομιλίας AI</string>
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Η σύνοψη συνομιλίας AI είναι κρυμμένη</string>
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Η σύνοψη συνομιλίας AI εμφανίζεται</string>
<string name="revanced_hide_comments_ai_chat_summary_title">Σύνοψη συζήτησης που δημιουργήθηκε από AI</string>
<string name="revanced_hide_comments_ai_chat_summary_summary_on">Κρυμμένη</string>
<string name="revanced_hide_comments_ai_chat_summary_summary_off">Εμφανίζεται</string>
<string name="revanced_hide_comments_ai_summary_title">Σύνοψη σχολίων που δημιουργήθηκε από AI</string>
<string name="revanced_hide_comments_ai_summary_summary_on">Η σύνοψη σχολίων AI είναι κρυμμένη</string>
<string name="revanced_hide_comments_ai_summary_summary_off">Η σύνοψη σχολίων AI εμφανίζεται</string>
<string name="revanced_hide_comments_ai_summary_summary_on">Κρυμμένη</string>
<string name="revanced_hide_comments_ai_summary_summary_off">Εμφανίζεται</string>
<string name="revanced_hide_comments_channel_guidelines_title">Οδηγίες καναλιού</string>
<string name="revanced_hide_comments_channel_guidelines_summary_on">Κρυμμένες</string>
<string name="revanced_hide_comments_channel_guidelines_summary_off">Εμφανίζονται</string>
@@ -461,17 +461,24 @@ Second \"item\" text"</string>
</patch>
<patch id="interaction.downloads.downloadsResourcePatch">
<string name="revanced_external_downloader_screen_title">Εξωτερικές λήψεις</string>
<string name="revanced_external_downloader_screen_summary">Ρυθμίσεις για χρήση εξωτερικού προγράμματος λήψης</string>
<string name="revanced_external_downloader_screen_summary">Ρυθμίσεις για χρήση εξωτερικής εφαρμογής λήψης</string>
<string name="revanced_external_downloader_title">Εμφάνιση κουμπιού εξωτερικής λήψης</string>
<string name="revanced_external_downloader_summary_on">Το κουμπί λήψης εμφανίζεται στην οθόνη αναπαραγωγής</string>
<string name="revanced_external_downloader_summary_off">Το κουμπί λήψης δεν εμφανίζεται στην οθόνη αναπαραγωγής</string>
<string name="revanced_external_downloader_summary_on">Το κουμπί λήψης εμφανίζεται στην οθόνη αναπαραγωγής</string>
<string name="revanced_external_downloader_summary_off">Το κουμπί λήψης δεν εμφανίζεται στην οθόνη αναπαραγωγής</string>
<!-- 'Download action button' should be translated using the same wording as the translation of 'revanced_hide_download_button_title'. -->
<string name="revanced_external_downloader_action_button_title">Μετατροπή κουμπιού ενέργειας «Λήψη»</string>
<string name="revanced_external_downloader_action_button_summary_on">Το κουμπί «Λήψη» θα ανοίγει το εξωτερικό πρόγραμμα λήψης σας</string>
<string name="revanced_external_downloader_action_button_summary_off">Το κουμπί «Λήψη» θα ανοίγει το ενσωματωμένο πρόγραμμα λήψης του YouTube</string>
<string name="revanced_external_downloader_name_title">Όνομα πακέτου προγράμματος λήψης</string>
<string name="revanced_external_downloader_name_summary">Το όνομα πακέτου της εγκατεστημένης σας εξωτερικής εφαρμογής λήψης όπως το NewPipe ή το Seal</string>
<string name="revanced_external_downloader_action_button_title">Αλλαγή λειτουργίας κουμπιού ενέργειας «Λήψη»</string>
<string name="revanced_external_downloader_action_button_summary_on">Το κουμπί «Λήψη» ανοίγει την εξωτερική εφαρμογή λήψης σας</string>
<string name="revanced_external_downloader_action_button_summary_off">Το κουμπί «Λήψη» ανοίγει το ενσωματωμένο πρόγραμμα λήψης του YouTube</string>
<string name="revanced_external_downloader_name_title">Όνομα πακέτου εφαρμογής λήψης</string>
<string name="revanced_external_downloader_name_summary">Το όνομα πακέτου της εγκατεστημένης σας εξωτερικής εφαρμογής λήψης</string>
<string name="revanced_external_downloader_other_item_hint">Εισάγετε το όνομα πακέτου</string>
<string name="revanced_external_downloader_other_item">Άλλο</string>
<string name="revanced_external_downloader_not_found_title">Η εφαρμογή δεν έχει εγκατασταθεί</string>
<string name="revanced_external_downloader_not_installed_warning">Το %s δεν είναι εγκατεστημένο. Παρακαλούμε εγκαταστήστε το.</string>
<string name="revanced_external_downloader_package_not_found_warning">"Δεν ήταν δυνατή η εύρεση εγκατεστημένης εφαρμογής με όνομα πακέτου: %s
Επιβεβαιώστε ότι το όνομα πακέτου είναι σωστό και ότι η εφαρμογή είναι εγκατεστημένη"</string>
<string name="revanced_external_downloader_empty_warning">Το όνομα πακέτου δεν μπορεί να είναι κενό</string>
</patch>
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
<string name="revanced_disable_precise_seeking_gesture_title">Απενεργοποίηση ακριβής αναζήτησης</string>
@@ -731,16 +738,17 @@ Second \"item\" text"</string>
<string name="revanced_disable_rolling_number_animations_summary_off">Οι αριθμοί κινούνται αυξανόμενοι εκθετικά</string>
</patch>
<patch id="layout.hide.seekbar.hideSeekbarPatch">
<string name="revanced_hide_seekbar_title">Απόκρυψη γραμμής αναζήτησης αναπαραγωγής βίντεο</string>
<string name="revanced_hide_seekbar_title">Γραμμή προόδου στην οθόνη αναπαραγωγής</string>
<string name="revanced_hide_seekbar_summary_on">Κρυμμένη</string>
<string name="revanced_hide_seekbar_summary_off">Εμφανίζεται</string>
<string name="revanced_hide_seekbar_thumbnail_title">Απόκρυψη γραμμής αναζήτησης μικρογραφιών βίντεο</string>
<string name="revanced_hide_seekbar_thumbnail_summary_on">Η γραμμή αναζήτησης μικρογραφιών βίντεο είναι κρυμμένη</string>
<string name="revanced_hide_seekbar_thumbnail_summary_off">Η γραμμή αναζήτησης μικρογραφιών βίντεο εμφανίζεται</string>
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
<string name="revanced_hide_seekbar_thumbnail_title">Γραμμή προόδου στις μικρογραφίες βίντεο</string>
<string name="revanced_hide_seekbar_thumbnail_summary_on">Κρυμμένη</string>
<string name="revanced_hide_seekbar_thumbnail_summary_off">Εμφανίζεται</string>
</patch>
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
<string name="revanced_shorts_player_screen_title">Οθόνη αναπαραγωγής Shorts</string>
<string name="revanced_shorts_player_screen_summary">Απόκρυψη ή εμφάνιση στοιχείων αναπαραγωγής Shorts</string>
<string name="revanced_shorts_player_screen_summary">Απόκρυψη ή εμφάνιση στοιχείων στην οθόνη αναπαραγωγής Shorts</string>
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
<string name="revanced_hide_shorts_home_title">Shorts στην αρχική ροή και στα σχετικά βίντεο</string>
<string name="revanced_hide_shorts_home_summary_on">Κρυμμένα</string>
@@ -861,9 +869,9 @@ Second \"item\" text"</string>
<string name="revanced_end_screen_suggested_video_summary_off">Εμφανίζεται</string>
</patch>
<patch id="layout.hide.relatedvideooverlay.hideRelatedVideoOverlayPatch">
<string name="revanced_hide_related_videos_overlay_title">Πλαίσιο σχετικών βίντεο στην πλήρη οθόνη</string>
<string name="revanced_hide_related_videos_overlay_summary_on">Κρυμμένο</string>
<string name="revanced_hide_related_videos_overlay_summary_off">Εμφανίζεται</string>
<string name="revanced_hide_related_videos_overlay_title">Σχετικά βίντεο στην πλήρη οθόνη</string>
<string name="revanced_hide_related_videos_overlay_summary_on">Κρυμμένα\n\nΑφορά τα σχετικά βίντεο της τελικής οθόνης στη λειτουργία πλήρους οθόνης</string>
<string name="revanced_hide_related_videos_overlay_summary_off">Εμφανίζονται\n\nΑφορά τα σχετικά βίντεο της τελικής οθόνης στη λειτουργία πλήρους οθόνης</string>
</patch>
<patch id="layout.hide.time.hideTimestampPatch">
<string name="revanced_hide_timestamp_title">Χρονική πρόοδος βίντεο</string>
@@ -1190,8 +1198,6 @@ Second \"item\" text"</string>
Αυτό θα αλλάξει την εμφάνιση και τα χαρακτηριστικά της εφαρμογής, αλλά ενδέχεται να εμφανιστούν άγνωστες παρενέργειες.
Αν αργότερα απενεργοποιηθεί, συνιστάται η εκκαθάριση δεδομένων της εφαρμογής για την αποφυγή σφαλμάτων UI."</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
<string name="revanced_spoof_app_version_target_title">Έκδοση παραποίησης της εφαρμογής</string>
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Επαναφορά των παλιών εικονιδίων της οθόνης αναπαραγωγής Shorts</string>
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Επαναφορά παλιών εικονιδίων γραμμής πλοήγησης</string>
@@ -1458,10 +1464,18 @@ Second \"item\" text"</string>
<string name="revanced_playback_speed_dialog_button_summary_on">Το κουμπί εμφανίζεται. Πατήστε παρατεταμένα για επαναφορά της ταχύτητας αναπαραγωγής στην προεπιλογή</string>
<string name="revanced_playback_speed_dialog_button_summary_off">Το κουμπί δεν εμφανίζεται</string>
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
<string name="revanced_video_quality_dialog_button_title">Εμφάνιση κουμπιού αλλαγής ποιότητας βίντεο</string>
<string name="revanced_video_quality_dialog_button_summary_on">Το κουμπί εμφανίζεται. Πατήστε παρατεταμένα για επαναφορά της ποιότητας στην προεπιλογή</string>
<string name="revanced_video_quality_dialog_button_summary_off">Το κουμπί δεν εμφανίζεται</string>
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
<string name="revanced_custom_speed_menu_title">Μενού προσαρμοσμένης ταχύτητας αναπαραγωγής</string>
<string name="revanced_custom_speed_menu_summary_on">Το μενού προσαρμοσμένης ταχύτητας αναπαραγωγής εμφανίζεται</string>
<string name="revanced_custom_speed_menu_summary_off">Το μενού προσαρμοσμένης ταχύτητας αναπαραγωγής δεν εμφανίζεται</string>
<string name="revanced_restore_old_speed_menu_title">Επαναφορά παλιού μενού ταχύτητας αναπαραγωγής</string>
<string name="revanced_restore_old_speed_menu_summary_on">Το παλιό μενού ταχύτητας εμφανίζεται</string>
<string name="revanced_restore_old_speed_menu_summary_off">Το σύγχρονο μενού ταχύτητας εμφανίζεται</string>
<string name="revanced_custom_playback_speeds_title">Προσαρμοσμένες ταχύτητες αναπαραγωγής</string>
<string name="revanced_custom_playback_speeds_summary">Προσθέστε ή αλλάξτε τις προσαρμοσμένες ταχύτητες αναπαραγωγής</string>
<string name="revanced_custom_playback_speeds_invalid">Οι ταχύτητες πρέπει να είναι μικρότερες από %sx</string>

View File

@@ -29,26 +29,26 @@ Second \"item\" text"</string>
<string name="revanced_check_environment_manager_not_expected_installer">No instalado por ReVanced Manager</string>
<string name="revanced_check_environment_not_near_patch_time">Parcheado hace más de 10 minutos</string>
<string name="revanced_check_environment_not_near_patch_time_days">Parcheado hace %s días</string>
<string name="revanced_check_environment_not_near_patch_time_invalid">La fecha de compilación del APK está dañada</string>
<string name="revanced_check_environment_not_near_patch_time_invalid">La fecha de compilación del APK está corrupta</string>
</patch>
<patch id="misc.settings.settingsResourcePatch">
<string name="revanced_settings_submenu_title">Ajustes</string>
<string name="revanced_settings_confirm_user_dialog_title">¿Está seguro de que quiere continuar?</string>
<string name="revanced_settings_confirm_user_dialog_title">¿Estás seguro de que quieres continuar?</string>
<string name="revanced_settings_reset">Restablecer</string>
<string name="revanced_settings_reset_color">Restablecer color</string>
<string name="revanced_settings_color_invalid">Color no válido</string>
<string name="revanced_settings_restart_title">Reinicio necesario</string>
<string name="revanced_settings_restart_dialog_message">Reinicie la aplicación para que este cambio surta efecto.</string>
<string name="revanced_settings_restart_dialog_message">Reinicia la aplicación para que este cambio surta efecto.</string>
<string name="revanced_settings_restart">Reiniciar</string>
<string name="revanced_settings_import">Importar</string>
<string name="revanced_settings_import_copy">Copiar</string>
<string name="revanced_settings_import_reset">Configuración ReVanced restablecida por defecto</string>
<string name="revanced_settings_import_reset">Ajustes de ReVanced restablecidos a los valores predeterminados</string>
<string name="revanced_settings_import_success">Configuración importada de %d</string>
<string name="revanced_settings_import_failure_parse">Error de importación: %s</string>
<string name="revanced_settings_search_hint">Ajustes de búsqueda</string>
<string name="revanced_settings_import_failure_parse">Importación fallida: %s</string>
<string name="revanced_settings_search_hint">Buscar ajustes</string>
<string name="revanced_settings_search_no_results_title">No se encontraron resultados para «%s»</string>
<string name="revanced_settings_search_no_results_summary">Prueba con otra palabra clave</string>
<string name="revanced_settings_search_remove_message">¿Quitar del historial de búsqueda?</string>
<string name="revanced_settings_search_remove_message">¿Eliminar del historial de búsqueda?</string>
<string name="revanced_show_menu_icons_title">Mostrar iconos de configuración de ReVanced</string>
<string name="revanced_show_menu_icons_summary_on">Se muestran los iconos de configuración</string>
<string name="revanced_show_menu_icons_summary_off">No se muestran los iconos de configuración</string>
@@ -62,25 +62,26 @@ Para traducir nuevos idiomas, visita translate.revanced.app"</string>
<!-- Settings about dialog. -->
<string name="revanced_settings_about_links_body">Estás usando la versión de ReVanced Patches: &lt;i&gt;%s&lt;/i&gt;</string>
<string name="revanced_settings_about_links_dev_header">Nota</string>
<string name="revanced_settings_about_links_dev_body">Esta versión es una pre-versión y puedes experimentar problemas inesperados</string>
<string name="revanced_settings_about_links_dev_body">Esta versión es un pre-lanzamiento y podrías experimentar problemas inesperados</string>
<string name="revanced_settings_about_links_header">Enlaces oficiales</string>
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="gms_core_toast_not_installed_message">MicroG GmsCore no está instalado. Instálala.</string>
<string name="gms_core_toast_not_installed_message">MicroG GmsCore no está instalado. Instálalo.</string>
<string name="gms_core_dialog_title">Acción necesaria</string>
<string name="gms_core_dialog_not_whitelisted_not_allowed_in_background_message">"MicroG GmsCore no tiene permiso para ejecutarse en segundo plano.
Sigue la guía \"No cierres mi aplicación\" para su teléfono, y aplica las instrucciones a tu instalación de MicroG
Sigue la guía \"Don't kill my app\" para tu teléfono, y aplica las instrucciones a tu instalación de MicroG
Esto es requerido para el funcionamiento de la APP"</string>
Esto es requerido para el funcionamiento de la aplicación."</string>
<string name="gms_core_dialog_open_website_text">Abrir sitio web</string>
<string name="gms_core_dialog_not_whitelisted_using_battery_optimizations_message">"La optimización de la batería de MicroG GmsCore debe estar deshabilitada para evitar problemas.
<string name="gms_core_dialog_not_whitelisted_using_battery_optimizations_message">"Las optimizaciones de batería para MicroG GmsCore deben estar desactivadas para evitar problemas.
Desactivar las optimizaciones de batería para MicroG no afectará negativamente el gasto de la batería
"</string>
Deshabilitar las optimizaciones de batería para MicroG no afectará negativamente el uso de la batería.
Toca el botón continuar y permite los cambios de optimización."</string>
<string name="gms_core_dialog_continue_text">Continuar</string>
</patch>
</app>
@@ -92,26 +93,26 @@ Desactivar las optimizaciones de batería para MicroG no afectará negativamente
<string name="revanced_settings_screen_03_feed_title">Fuente</string>
<string name="revanced_settings_screen_04_general_title">General</string>
<string name="revanced_settings_screen_05_player_title">Reproductor</string>
<string name="revanced_settings_screen_07_seekbar_title">Barra</string>
<string name="revanced_settings_screen_07_seekbar_title">Barra de progreso</string>
<string name="revanced_settings_screen_08_swipe_controls_title">Controles de deslizamiento</string>
<string name="revanced_settings_screen_11_misc_title">Miscelánea</string>
<string name="revanced_settings_screen_11_misc_title">Otros</string>
<string name="revanced_settings_screen_12_video_title">Video</string>
<string name="revanced_restore_old_settings_menus_title">Restaurar los menús de configuración antiguos</string>
<string name="revanced_restore_old_settings_menus_title">Restaurar menús de configuración antiguos</string>
<string name="revanced_restore_old_settings_menus_summary_on">Se muestran los menús de configuración antiguos</string>
<string name="revanced_restore_old_settings_menus_summary_off">No se muestran los menús de configuración antiguos</string>
<string name="revanced_settings_search_history_title">Mostrar el historial de búsqueda de ajustes</string>
<string name="revanced_settings_search_history_summary_on">Se muestra el historial de búsqueda de ajustes</string>
<string name="revanced_settings_search_history_summary_on">El historial de búsqueda de los ajustes está visible</string>
<string name="revanced_settings_search_history_summary_off">El historial de búsqueda de ajustes no se muestra</string>
</patch>
<patch id="misc.backgroundplayback.backgroundPlaybackPatch">
<string name="revanced_shorts_disable_background_playback_title">Desactivar la reproducción en segundo plano de Shorts</string>
<string name="revanced_shorts_disable_background_playback_summary_on">La reproducción de Shorts en segundo plano está desactivada</string>
<string name="revanced_shorts_disable_background_playback_summary_off">La reproducción de Shorts en segundo plano está activada</string>
<string name="revanced_shorts_disable_background_playback_summary_on">La reproducción en segundo plano de Shorts está deshabilitada</string>
<string name="revanced_shorts_disable_background_playback_summary_off">La reproducción en segundo plano de Shorts está habilitada</string>
</patch>
<patch id="misc.debugging.enableDebuggingPatch">
<string name="revanced_debug_screen_title">Depuración</string>
<string name="revanced_debug_screen_summary">Activar o desactivar las opciones de depuración</string>
<string name="revanced_debug_title">Depurar registro</string>
<string name="revanced_debug_title">Registro de depuración</string>
<string name="revanced_debug_summary_on">Los registros de depuración están habilitados</string>
<string name="revanced_debug_summary_off">Los registros de depuración están desactivados</string>
<string name="revanced_debug_protobuffer_title">Búfer de protocolo de registro</string>
@@ -125,39 +126,41 @@ Sin embargo, si activas esto, también se registrarán algunos datos del usuario
<string name="revanced_debug_stacktrace_title">Registrar stack traces</string>
<string name="revanced_debug_stacktrace_summary_on">Los registros de depuración incluyen stack trace</string>
<string name="revanced_debug_stacktrace_summary_off">Los registros de depuración no incluyen stack trace</string>
<string name="revanced_debug_toast_on_error_title">Mostrar brindis en error ReVanced</string>
<string name="revanced_debug_toast_on_error_summary_on">Se muestra un toast si se produce un error</string>
<string name="revanced_debug_toast_on_error_summary_off">No se muestra un toast si se produce un error</string>
<string name="revanced_debug_toast_on_error_user_dialog_message">"Desactivar las notificaciones de error oculta todas las notificaciones de error de ReVanced."</string>
<string name="revanced_debug_toast_on_error_title">Mostrar mensaje de error en ReVanced</string>
<string name="revanced_debug_toast_on_error_summary_on">Se muestra un mensaje si se produce un error</string>
<string name="revanced_debug_toast_on_error_summary_off">No se muestra un mensaje si se produce un error</string>
<string name="revanced_debug_toast_on_error_user_dialog_message">"Desactivar los avisos de error oculta todas las notificaciones de error de ReVanced.
No se le notificará de ningún evento inesperado."</string>
<string name="revanced_debug_export_logs_to_clipboard_title">Exportar registros de depuración</string>
<string name="revanced_debug_export_logs_to_clipboard_summary">Copia los registros de depuración de ReVanced al portapapeles</string>
<string name="revanced_debug_logs_disabled">El registro de depuración está desactivado</string>
<string name="revanced_debug_logs_none_found">No se encontraron registros</string>
<string name="revanced_debug_logs_copied_to_clipboard">Registros copiados</string>
<string name="revanced_debug_logs_failed_to_export">Error al exportar los registros: $s</string>
<string name="revanced_debug_logs_failed_to_export">Error al exportar registros: %s</string>
<string name="revanced_debug_logs_clear_buffer_title">Borrar registros de depuración</string>
<string name="revanced_debug_logs_clear_buffer_summary">Borra todos los registros de depuración de ReVanced almacenados</string>
<string name="revanced_debug_logs_clear_toast">Registros borrados</string>
</patch>
<patch id="layout.hide.general.hideLayoutComponentsPatch">
<string name="revanced_hide_album_cards_title">Ocultar álbumes</string>
<string name="revanced_hide_album_cards_title">Ocultar tarjetas de álbum</string>
<string name="revanced_hide_album_cards_summary_on">Las tarjetas de álbum están ocultas</string>
<string name="revanced_hide_album_cards_summary_off">Se muestran las tarjetas de álbum</string>
<string name="revanced_hide_artist_cards_title">Ocultar tarjetas de artistas</string>
<string name="revanced_hide_artist_cards_summary_on">Las tarjetas de artistas están ocultas</string>
<string name="revanced_hide_artist_cards_summary_off">Se muestran las tarjetas del artista</string>
<string name="revanced_hide_artist_cards_title">Ocultar tarjetas de artista</string>
<string name="revanced_hide_artist_cards_summary_on">Las tarjetas de artista están ocultas</string>
<string name="revanced_hide_artist_cards_summary_off">Se muestran las tarjetas de artista</string>
<string name="revanced_hide_chips_shelf_title">Ocultar \"Más como esto\"</string>
<string name="revanced_hide_chips_shelf_summary_on">\"Más como esto\" está oculto</string>
<string name="revanced_hide_chips_shelf_summary_off">\"Más como esto\" está habilitado</string>
<string name="revanced_hide_community_posts_title">Ocultar mensajes comunitarios</string>
<string name="revanced_hide_community_posts_summary_on">Los mensajes de la comunidad están ocultos</string>
<string name="revanced_hide_community_posts_title">Ocultar publicaciones de comunidad</string>
<string name="revanced_hide_community_posts_summary_on">Las publicaciones de la comunidad están ocultas</string>
<string name="revanced_hide_community_posts_summary_off">Se muestran las publicaciones de la comunidad</string>
<string name="revanced_hide_compact_banner_title">Ocultar banners compactos</string>
<string name="revanced_hide_compact_banner_summary_on">Los banners compactos están ocultos</string>
<string name="revanced_hide_compact_banner_summary_off">Se muestran los banners compactos</string>
<string name="revanced_hide_crowdfunding_box_title">Ocultar caja de recaudación</string>
<string name="revanced_hide_crowdfunding_box_title">Ocultar caja de Crowdfunding</string>
<string name="revanced_hide_crowdfunding_box_summary_on">La caja de Crowdfunding está oculta</string>
<string name="revanced_hide_crowdfunding_box_summary_off">Se muestra la caja de Crowdfunding</string>
<string name="revanced_hide_crowdfunding_box_summary_off">La caja de Crowdfunding está visible</string>
<string name="revanced_hide_expandable_card_title">Ocultar tarjeta expandible</string>
<string name="revanced_hide_expandable_card_summary_on">Tarjeta expandible debajo de los videos oculta</string>
<string name="revanced_hide_expandable_card_summary_off">Tarjeta expandible debajo de los videos visible</string>
@@ -172,19 +175,19 @@ Sin embargo, si activas esto, también se registrarán algunos datos del usuario
• Más relevantes
• Compras
• Ver de nuevo"</string>
<string name="revanced_hide_horizontal_shelves_summary_off">Se muestran los estantes horizontales</string>
<string name="revanced_hide_horizontal_shelves_summary_off">Los estantes horizontales están visibles</string>
<string name="revanced_hide_image_shelf_title">Ocultar estantería de imágenes</string>
<string name="revanced_hide_image_shelf_summary_on">Estantería de imágenes en los resultados de búsqueda oculta</string>
<string name="revanced_hide_image_shelf_summary_off">Estantería de imágenes en los resultados de búsqueda visible</string>
<string name="revanced_hide_latest_posts_title">Ocultar últimos mensajes</string>
<string name="revanced_hide_latest_posts_title">Ocultar últimas publicaciones</string>
<string name="revanced_hide_latest_posts_summary_on">Las últimas publicaciones están ocultas</string>
<string name="revanced_hide_latest_posts_summary_off">Se muestran las últimas publicaciones</string>
<string name="revanced_hide_mix_playlists_title">Ocultar listas de mezcla</string>
<string name="revanced_hide_mix_playlists_summary_on">Las listas de reproducción mixtas están ocultas</string>
<string name="revanced_hide_mix_playlists_summary_off">Mezclar listas de reproducción son mostradas</string>
<string name="revanced_hide_mix_playlists_title">Ocultar lista de reproducción Mix</string>
<string name="revanced_hide_mix_playlists_summary_on">La lista de reproducción Mix está oculta</string>
<string name="revanced_hide_mix_playlists_summary_off">La lista de reproducción Mix está visible</string>
<string name="revanced_hide_movies_section_title">Ocultar sección de películas</string>
<string name="revanced_hide_movies_section_summary_on">La sección de películas está oculta</string>
<string name="revanced_hide_movies_section_summary_off">Se muestra la sección de películas</string>
<string name="revanced_hide_movies_section_summary_off">La sección de películas está visible</string>
<!-- 'Notify me' should be translated using the same localized wording YouTube displays.
This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
<string name="revanced_hide_notify_me_button_title">Ocultar el botón \'Notificarme\'</string>
@@ -200,9 +203,9 @@ Sin embargo, si activas esto, también se registrarán algunos datos del usuario
<string name="revanced_hide_show_more_button_summary_off">El botón Mostrar más en los resultados de búsqueda está visible</string>
<string name="revanced_hide_surveys_title">Ocultar encuestas</string>
<string name="revanced_hide_surveys_summary_on">Las encuestas están ocultas</string>
<string name="revanced_hide_surveys_summary_off">Las encuestas están mostradas</string>
<string name="revanced_hide_ticket_shelf_title">Ocultar estante de boletos</string>
<string name="revanced_hide_ticket_shelf_summary_on">El estante de boletos está oculto</string>
<string name="revanced_hide_surveys_summary_off">Las encuestas están visibles</string>
<string name="revanced_hide_ticket_shelf_title">Ocultar estantes de tickets</string>
<string name="revanced_hide_ticket_shelf_summary_on">El estante de tickets está oculto</string>
<string name="revanced_hide_ticket_shelf_summary_off">El estante de boletos está visible</string>
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<string name="revanced_hide_video_recommendation_labels_title">Ocultar etiquetas de recomendación de vídeo</string>
@@ -465,8 +468,15 @@ Esta función solo está disponible para dispositivos antiguos"</string>
<string name="revanced_external_downloader_action_button_summary_on">El botón de descarga abre su descarga externa</string>
<string name="revanced_external_downloader_action_button_summary_off">El botón de descarga abre el descargador nativo en la aplicación</string>
<string name="revanced_external_downloader_name_title">Nombre del paquete del descargado</string>
<string name="revanced_external_downloader_name_summary">Nombre del paquete de su aplicación de descarga externa instalada, como NewPipe o Seal</string>
<string name="revanced_external_downloader_name_summary">Nombre del paquete de tu aplicación de descarga externa instalada</string>
<string name="revanced_external_downloader_other_item_hint">Introduce el nombre del paquete</string>
<string name="revanced_external_downloader_other_item">Otro</string>
<string name="revanced_external_downloader_not_found_title">Aplicación no instalada</string>
<string name="revanced_external_downloader_not_installed_warning">%s no está instalado. Por favor, instálelo.</string>
<string name="revanced_external_downloader_package_not_found_warning">"No se pudo encontrar la aplicación instalada con el nombre de paquete: %s
Verifica que el nombre del paquete sea correcto y que la aplicación esté instalada"</string>
<string name="revanced_external_downloader_empty_warning">El nombre del paquete no puede estar vacío</string>
</patch>
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
<string name="revanced_disable_precise_seeking_gesture_title">Desactivar gesto de búsqueda preciso</string>
@@ -729,6 +739,7 @@ Para mostrar el menú de la pista de audio, cambia \"Suplantar transmisiones de
<string name="revanced_hide_seekbar_title">Ocultar barra de búsqueda del reproductor de vídeo</string>
<string name="revanced_hide_seekbar_summary_on">La barra de búsqueda del reproductor de vídeo está oculta</string>
<string name="revanced_hide_seekbar_summary_off">La barra de búsqueda del reproductor de vídeo se muestra</string>
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
<string name="revanced_hide_seekbar_thumbnail_title">Ocultar barra de búsqueda de miniaturas de vídeo</string>
<string name="revanced_hide_seekbar_thumbnail_summary_on">La barra de búsqueda de miniaturas de vídeo está oculta</string>
<string name="revanced_hide_seekbar_thumbnail_summary_off">La barra de búsqueda de miniaturas de vídeo está visible</string>
@@ -1177,8 +1188,6 @@ Diseño para automóviles
Esto cambiará la apariencia y las características de la aplicación, pero pueden producirse efectos secundarios desconocidos.
Si se desactiva posteriormente, se recomienda borrar los datos de la aplicación para evitar errores en la interfaz de usuario."</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
<string name="revanced_spoof_app_version_target_title">Versión de aplicación falsa de destino</string>
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Restaurar iconos antiguos del reproductor de Shorts</string>
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 - Restaurar iconos de navegación antiguos</string>
@@ -1447,10 +1456,18 @@ Habilitar esto puede desbloquear calidades de vídeo más altas"</string>
<string name="revanced_playback_speed_dialog_button_summary_on">Se muestra el botón. Mantén pulsado para restablecer la velocidad de reproducción predeterminada</string>
<string name="revanced_playback_speed_dialog_button_summary_off">El botón no se muestra</string>
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
<string name="revanced_video_quality_dialog_button_title">Mostrar botón de calidad de video</string>
<string name="revanced_video_quality_dialog_button_summary_on">Botón visible. Toca y mantén para restablecer la calidad a los valores predeterminados</string>
<string name="revanced_video_quality_dialog_button_summary_off">Botón no visible</string>
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
<string name="revanced_custom_speed_menu_title">Menú de velocidad de reproducción personalizada</string>
<string name="revanced_custom_speed_menu_summary_on">Menú de velocidad personalizado se muestra</string>
<string name="revanced_custom_speed_menu_summary_off">Menú de velocidad personalizado no se muestra</string>
<string name="revanced_restore_old_speed_menu_title">Restaurar el menú de velocidad de reproducción antiguo</string>
<string name="revanced_restore_old_speed_menu_summary_on">Se muestra el menú de velocidad antiguo</string>
<string name="revanced_restore_old_speed_menu_summary_off">Se muestra el menú de velocidad moderno</string>
<string name="revanced_custom_playback_speeds_title">Velocidades de reproducción personalizadas</string>
<string name="revanced_custom_playback_speeds_summary">Añadir o cambiar las velocidades de reproducción personalizadas</string>
<string name="revanced_custom_playback_speeds_invalid">Las velocidades personalizadas deben ser menores que %s</string>

View File

@@ -137,7 +137,7 @@ Teid ei teavitata ühestki ootamatust sündmusest."</string>
<string name="revanced_debug_logs_disabled">Silumislogimine on keelatud</string>
<string name="revanced_debug_logs_none_found">Logisid ei leitud</string>
<string name="revanced_debug_logs_copied_to_clipboard">Logid kopeeritud</string>
<string name="revanced_debug_logs_failed_to_export">Logide eksportimine ebaõnnestus: $s</string>
<string name="revanced_debug_logs_failed_to_export">Logide eksportimine ebaõnnestus: %s</string>
<string name="revanced_debug_logs_clear_buffer_title">Puhasta silumislogid</string>
<string name="revanced_debug_logs_clear_buffer_summary">Puhastab kõik salvestatud ReVanced silumislogid</string>
<string name="revanced_debug_logs_clear_toast">Logid puhastatud</string>
@@ -468,8 +468,15 @@ See funktsioon on saadaval ainult vanemates seadmetes"</string>
<string name="revanced_external_downloader_action_button_summary_on">Allalaadimise nupp avab teie välise allalaadija</string>
<string name="revanced_external_downloader_action_button_summary_off">Allalaadimise nupp avab seadme sisseehitatud allalaadija</string>
<string name="revanced_external_downloader_name_title">Allalaadija paketi nimi</string>
<string name="revanced_external_downloader_name_summary">Teie installitud välise allalaadija rakenduse paketi nimi, näiteks NewPipe või Seal</string>
<string name="revanced_external_downloader_name_summary">Paigaldatud välise allalaadimisrakenduse paketi nimi</string>
<string name="revanced_external_downloader_other_item_hint">Sisesta paketi nimi</string>
<string name="revanced_external_downloader_other_item">Muu</string>
<string name="revanced_external_downloader_not_found_title">Rakendus pole installitud</string>
<string name="revanced_external_downloader_not_installed_warning">%s ei ole installitud. Palun installige see.</string>
<string name="revanced_external_downloader_package_not_found_warning">"Installitud rakendust ei leitud paketi nimega: %s
Veendu, et paketi nimi on õige ja rakendus on installitud"</string>
<string name="revanced_external_downloader_empty_warning">Paketi nimi ei tohi olla tühi</string>
</patch>
<patch id="interaction.seekbar.disablePreciseSeekingGesturePatch">
<string name="revanced_disable_precise_seeking_gesture_title">Keela täpne otsingu žest</string>
@@ -732,6 +739,7 @@ Heliriba menüü kuvamiseks muutke valikut „Võltsitud videovoogedastus“ vä
<string name="revanced_hide_seekbar_title">Peida videopleieri edenemisriba</string>
<string name="revanced_hide_seekbar_summary_on">Videopleieri otsimisriba on peidetud</string>
<string name="revanced_hide_seekbar_summary_off">Videopleieri otsimisriba on nähtav</string>
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
<string name="revanced_hide_seekbar_thumbnail_title">Peida video pisipiltide edenemisriba</string>
<string name="revanced_hide_seekbar_thumbnail_summary_on">Video pisipiltide edenemisriba on peidetud</string>
<string name="revanced_hide_seekbar_thumbnail_summary_off">Video pisipiltide edenemisriba on nähtav</string>
@@ -1189,8 +1197,6 @@ Autode paigutus
See muudab rakenduse välimust ja funktsioone, kuid võivad esineda tundmatud kõrvalmõjud.
Kui see hiljem välja lülitatakse, on soovitatav rakenduse andmed kustutada, et vältida kasutajaliidese vigu."</string>
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
<string name="revanced_spoof_app_version_target_title">Võltsitud rakenduse versiooni siht</string>
<string name="revanced_spoof_app_version_target_entry_1">19.35.36 - Taastage vanad Shortsi esitajaikoonid</string>
<string name="revanced_spoof_app_version_target_entry_2">19.01.34 Taasta vanad navigeerimisikoonid</string>
@@ -1459,10 +1465,18 @@ Selle lubamine võib avada kõrgema video kvaliteedi"</string>
<string name="revanced_playback_speed_dialog_button_summary_on">Nupp on nähtaval. Puudutage ja hoidke all, et taastada taasesituse kiirus vaikeväärtusele</string>
<string name="revanced_playback_speed_dialog_button_summary_off">Nuppi ei kuvata</string>
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
<string name="revanced_video_quality_dialog_button_title">Näita video kvaliteedi nuppu</string>
<string name="revanced_video_quality_dialog_button_summary_on">Nupp on nähtaval. Kvaliteedi lähtestamiseks vaikeseadeteks puudutage ja hoidke</string>
<string name="revanced_video_quality_dialog_button_summary_off">Nuppu ei kuvata</string>
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
<string name="revanced_custom_speed_menu_title">Kohandatud taasesituse kiiruse menüü</string>
<string name="revanced_custom_speed_menu_summary_on">Kohandatud kiiruse menüü kuvatakse</string>
<string name="revanced_custom_speed_menu_summary_off">Kohandatud kiiruse menüüd ei kuvata</string>
<string name="revanced_restore_old_speed_menu_title">Taasta vana taasesituse kiiruse menüü</string>
<string name="revanced_restore_old_speed_menu_summary_on">Kuvatakse vana kiiruse menüü</string>
<string name="revanced_restore_old_speed_menu_summary_off">Kuvatakse kaasaegne kiiruse menüü</string>
<string name="revanced_custom_playback_speeds_title">Kohandatud taasesituse kiirused</string>
<string name="revanced_custom_playback_speeds_summary">Lisa või muuda kohandatud taasesituse kiirusi</string>
<string name="revanced_custom_playback_speeds_invalid">Kohandatud kiirused peavad olema alla %s</string>

View File

@@ -127,6 +127,7 @@ Second \"item\" text"</string>
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
</patch>
<patch id="layout.hide.seekbar.hideSeekbarPatch">
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
</patch>
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
@@ -169,8 +170,6 @@ Second \"item\" text"</string>
<patch id="layout.formfactor.changeFormFactorPatch">
</patch>
<patch id="layout.spoofappversion.spoofAppVersionPatch">
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
</patch>
<patch id="layout.startpage.changeStartPagePatch">
</patch>
@@ -223,6 +222,8 @@ Second \"item\" text"</string>
</patch>
<patch id="video.speed.button.playbackSpeedButtonPatch">
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">

View File

@@ -174,6 +174,7 @@ Second \"item\" text"</string>
<patch id="layout.hide.rollingnumber.disableRollingNumberAnimationPatch">
</patch>
<patch id="layout.hide.seekbar.hideSeekbarPatch">
<!-- Seekbar shown inside video thumbnails found the home/feed/search/history. The seekbar shows the prior watch progress when the video was last open. -->
</patch>
<patch id="layout.hide.shorts.hideShortsComponentsResourcePatch">
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
@@ -219,8 +220,6 @@ Second \"item\" text"</string>
<patch id="layout.formfactor.changeFormFactorPatch">
</patch>
<patch id="layout.spoofappversion.spoofAppVersionPatch">
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch. -->
</patch>
<patch id="layout.startpage.changeStartPagePatch">
</patch>
@@ -273,6 +272,8 @@ Second \"item\" text"</string>
</patch>
<patch id="video.speed.button.playbackSpeedButtonPatch">
</patch>
<patch id="video.quality.button.videoQualityButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">

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