Compare commits

...

63 Commits

Author SHA1 Message Date
semantic-release-bot
c23a926b07 chore: Release v5.34.0-dev.9 [skip ci]
# [5.34.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.8...v5.34.0-dev.9) (2025-08-16)

### Features

* **YouTube - Hide video action buttons:** Add "Hide Hype button" setting ([fe66bae](fe66baedb7))
2025-08-16 15:48:20 +00:00
LisoUseInAIKyrios
fe66baedb7 feat(YouTube - Hide video action buttons): Add "Hide Hype button" setting 2025-08-16 11:45:36 -04:00
semantic-release-bot
959f23d1e4 chore: Release v5.34.0-dev.8 [skip ci]
# [5.34.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.7...v5.34.0-dev.8) (2025-08-15)

### Features

* **NU.nl:** Support latest app version ([#5643](https://github.com/ReVanced/revanced-patches/issues/5643)) ([1bb8c53](1bb8c53ed3))
* **YouTube:** Add `Disable sign in to TV popup` patch ([#5639](https://github.com/ReVanced/revanced-patches/issues/5639)) ([56fbd8c](56fbd8cce0))
2025-08-15 10:04:52 +00:00
AndnixSH
56fbd8cce0 feat(YouTube): Add Disable sign in to TV popup patch (#5639) 2025-08-15 06:00:55 -04:00
Jasper Abbink
1bb8c53ed3 feat(NU.nl): Support latest app version (#5643) 2025-08-15 06:00:06 -04:00
github-actions[bot]
5fc0631a15 chore: Sync translations (#5652) 2025-08-15 05:59:43 -04:00
semantic-release-bot
bdbe96beba chore: Release v5.34.0-dev.7 [skip ci]
# [5.34.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.6...v5.34.0-dev.7) (2025-08-13)

### Bug Fixes

* **YouTube - Video quality:** Fix additional incorrect quality resolutions used by YouTube ([6bd9e49](6bd9e49c7a))
2025-08-13 19:20:51 +00:00
LisoUseInAIKyrios
6bd9e49c7a fix(YouTube - Video quality): Fix additional incorrect quality resolutions used by YouTube 2025-08-13 15:16:45 -04:00
semantic-release-bot
f904ca6d7e chore: Release v5.34.0-dev.6 [skip ci]
# [5.34.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.5...v5.34.0-dev.6) (2025-08-11)

### Bug Fixes

* **YouTube - Video quality:** Show FHD+ icon for 1080p 60fps enhanced bitrate ([e579c56](e579c56921))
2025-08-11 01:21:07 +00:00
LisoUseInAIKyrios
e579c56921 fix(YouTube - Video quality): Show FHD+ icon for 1080p 60fps enhanced bitrate 2025-08-10 21:17:32 -04:00
github-actions[bot]
83f239065a chore: Sync translations (#5637) 2025-08-10 21:13:31 -04:00
semantic-release-bot
6499318f33 chore: Release v5.34.0-dev.5 [skip ci]
# [5.34.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.4...v5.34.0-dev.5) (2025-08-10)

### Features

* **YouTube - Hide player flyout menu items:** Add option to hide quality flyout menu ([809e013](809e013c4e))
2025-08-10 13:57:25 +00:00
LisoUseInAIKyrios
809e013c4e feat(YouTube - Hide player flyout menu items): Add option to hide quality flyout menu 2025-08-10 09:54:40 -04:00
semantic-release-bot
182829d51c chore: Release v5.34.0-dev.4 [skip ci]
# [5.34.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.3...v5.34.0-dev.4) (2025-08-10)

### Bug Fixes

* **YouTube - Hide layout components:** Do not hide community posts on channel profiles ([#5634](https://github.com/ReVanced/revanced-patches/issues/5634)) ([61824ad](61824ade23))
2025-08-10 13:11:44 +00:00
LisoUseInAIKyrios
61824ade23 fix(YouTube - Hide layout components): Do not hide community posts on channel profiles (#5634) 2025-08-10 09:09:08 -04:00
LisoUseInAIKyrios
ff4308e961 refactor(YouTube Music - Hide category bar): Fix possible crash when patching certain app targets 2025-08-09 11:13:35 -04:00
semantic-release-bot
b5eb13c0a8 chore: Release v5.34.0-dev.3 [skip ci]
# [5.34.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.2...v5.34.0-dev.3) (2025-08-09)

### Bug Fixes

* **pixiv - Hide ads:** Constrain patch to last working app target ([b702dce](b702dceda0))
2025-08-09 15:06:42 +00:00
LisoUseInAIKyrios
b702dceda0 fix(pixiv - Hide ads): Constrain patch to last working app target 2025-08-09 11:04:07 -04:00
semantic-release-bot
d616652058 chore: Release v5.34.0-dev.2 [skip ci]
# [5.34.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.1...v5.34.0-dev.2) (2025-08-09)

### Bug Fixes

* **Backdrops:** Remove broken patch that is no longer supported ([#5627](https://github.com/ReVanced/revanced-patches/issues/5627)) ([c3e571e](c3e571e765))

### Features

* **YouTube - Playback speed:** Show current playback speed on player speed dialog button ([#5607](https://github.com/ReVanced/revanced-patches/issues/5607)) ([30176a3](30176a3318))
2025-08-09 01:31:11 +00:00
LisoUseInAIKyrios
c3e571e765 fix(Backdrops): Remove broken patch that is no longer supported (#5627) 2025-08-08 21:28:07 -04:00
MarcaD
30176a3318 feat(YouTube - Playback speed): Show current playback speed on player speed dialog button (#5607)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-08-08 21:27:52 -04:00
semantic-release-bot
9c0638d128 chore: Release v5.34.0-dev.1 [skip ci]
# [5.34.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.33.0...v5.34.0-dev.1) (2025-08-08)

### Bug Fixes

* **Twitch:** Constrain patches to last working app targets ([#5373](https://github.com/ReVanced/revanced-patches/issues/5373)) ([d7eb6e8](d7eb6e87a5))

### Features

* **Instagram:** Support latest app version ([#5611](https://github.com/ReVanced/revanced-patches/issues/5611)) ([562e005](562e005772))
2025-08-08 01:55:22 +00:00
LisoUseInAIKyrios
d7eb6e87a5 fix(Twitch): Constrain patches to last working app targets (#5373) 2025-08-07 21:51:52 -04:00
LisoUseInAIKyrios
562e005772 feat(Instagram): Support latest app version (#5611) 2025-08-07 21:51:01 -04:00
github-actions[bot]
f61218de52 chore: Sync translations (#5616) 2025-08-07 21:50:45 -04:00
semantic-release-bot
a19b670e19 chore: Release v5.33.0 [skip ci]
# [5.33.0](https://github.com/ReVanced/revanced-patches/compare/v5.32.0...v5.33.0) (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))
* **NFC Tools:** Remove broken patch that is no longer supported ([#5584](https://github.com/ReVanced/revanced-patches/issues/5584)) ([2e177a8](2e177a8839))
* **YouTube - Force original audio:** Disable a/b feature flag that forces localized audio ([#5582](https://github.com/ReVanced/revanced-patches/issues/5582)) ([1dd01cf](1dd01cf54a))
* **YouTube - Litho filter:** Correctly filter identifier of older YouTube targets ([b1d164b](b1d164b446))
* **YouTube - Playback speed:** Use old speed menu for player button if enabled ([a4817df](a4817dfdd0))
* **YouTube - Video quality:** Fix 144p default not always used ([9afa7d2](9afa7d2ac6))
* **YouTube - Video quality:** Fix dialog quality list check mark not always shown ([1bc63e5](1bc63e50a7))
* **YouTube - Video quality:** Fix wrong qualities sometimes shown in player button dialog ([178eed7](178eed7fcd))
* **YouTube - Video quality:** Use 1080p enhanced bitrate for Premium users ([#5565](https://github.com/ReVanced/revanced-patches/issues/5565)) ([1adbd56](1adbd563b2))

### Features

* **ORF ON:** Add `Remove root detection` patch ([#5551](https://github.com/ReVanced/revanced-patches/issues/5551)) ([d92362b](d92362b0d9))
* **YouTube - Playback speed:** Add "Restore old playback speed menu" option ([#5552](https://github.com/ReVanced/revanced-patches/issues/5552)) ([e9e4cf3](e9e4cf39b6))
* **YouTube:** Add player button to change video quality ([#5435](https://github.com/ReVanced/revanced-patches/issues/5435)) ([7bdc328](7bdc32867a))

### Performance Improvements

* **YouTube:** Filter identifier callback only on root component creation ([#5558](https://github.com/ReVanced/revanced-patches/issues/5558)) ([5d08fdd](5d08fdddb8))
2025-08-05 17:57:09 +00:00
LisoUseInAIKyrios
300d816350 chore: Merge branch dev to main (#5553) 2025-08-05 13:53:31 -04:00
github-actions[bot]
63d64a5c87 chore: Sync translations (#5595) 2025-08-05 13:52:06 -04:00
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
167 changed files with 3153 additions and 1108 deletions

View File

@@ -1,3 +1,211 @@
# [5.34.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.8...v5.34.0-dev.9) (2025-08-16)
### Features
* **YouTube - Hide video action buttons:** Add "Hide Hype button" setting ([f13f377](https://github.com/ReVanced/revanced-patches/commit/f13f3770e7c4fd5bff8f3e224fb1b1ead50a3c18))
# [5.34.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.7...v5.34.0-dev.8) (2025-08-15)
### Features
* **NU.nl:** Support latest app version ([#5643](https://github.com/ReVanced/revanced-patches/issues/5643)) ([7338e4a](https://github.com/ReVanced/revanced-patches/commit/7338e4a5a99f913256120d0d58fede3aa4ee8922))
* **YouTube:** Add `Disable sign in to TV popup` patch ([#5639](https://github.com/ReVanced/revanced-patches/issues/5639)) ([d0e5bd0](https://github.com/ReVanced/revanced-patches/commit/d0e5bd0479a8910b081c483ed2a6ab4d7134e3c3))
# [5.34.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.6...v5.34.0-dev.7) (2025-08-13)
### Bug Fixes
* **YouTube - Video quality:** Fix additional incorrect quality resolutions used by YouTube ([a2a1fbe](https://github.com/ReVanced/revanced-patches/commit/a2a1fbe2959be8334c54cfc3426c24a960c55c8f))
# [5.34.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.5...v5.34.0-dev.6) (2025-08-11)
### Bug Fixes
* **YouTube - Video quality:** Show FHD+ icon for 1080p 60fps enhanced bitrate ([76bed37](https://github.com/ReVanced/revanced-patches/commit/76bed3734093713af24ef065d5ffc5b1cd83f29a))
# [5.34.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.4...v5.34.0-dev.5) (2025-08-10)
### Features
* **YouTube - Hide player flyout menu items:** Add option to hide quality flyout menu ([eb55068](https://github.com/ReVanced/revanced-patches/commit/eb5506856a2eaf2a8585e598868ddba3e1429159))
# [5.34.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.3...v5.34.0-dev.4) (2025-08-10)
### Bug Fixes
* **YouTube - Hide layout components:** Do not hide community posts on channel profiles ([#5634](https://github.com/ReVanced/revanced-patches/issues/5634)) ([9e3d5a2](https://github.com/ReVanced/revanced-patches/commit/9e3d5a2b36106479470f3f69920518b57e8c4dca))
# [5.34.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.2...v5.34.0-dev.3) (2025-08-09)
### Bug Fixes
* **pixiv - Hide ads:** Constrain patch to last working app target ([d8ea56c](https://github.com/ReVanced/revanced-patches/commit/d8ea56ca4be47df1c43f96ec41b91c800f1d9daf))
# [5.34.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.1...v5.34.0-dev.2) (2025-08-09)
### Bug Fixes
* **Backdrops:** Remove broken patch that is no longer supported ([#5627](https://github.com/ReVanced/revanced-patches/issues/5627)) ([ebb8332](https://github.com/ReVanced/revanced-patches/commit/ebb83320838aa99dd4417d45a50333dd42c1218a))
### Features
* **YouTube - Playback speed:** Show current playback speed on player speed dialog button ([#5607](https://github.com/ReVanced/revanced-patches/issues/5607)) ([279436a](https://github.com/ReVanced/revanced-patches/commit/279436a3657b50f98bb4cc64dc88dc14e422f204))
# [5.34.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.33.0...v5.34.0-dev.1) (2025-08-08)
### Bug Fixes
* **Twitch:** Constrain patches to last working app targets ([#5373](https://github.com/ReVanced/revanced-patches/issues/5373)) ([29a4748](https://github.com/ReVanced/revanced-patches/commit/29a47481c4efa209a3a53df60613b59a73adbe07))
### Features
* **Instagram:** Support latest app version ([#5611](https://github.com/ReVanced/revanced-patches/issues/5611)) ([26fe690](https://github.com/ReVanced/revanced-patches/commit/26fe690dfbefe6c412c5f81f208a3b1d2fbd7a0a))
# [5.33.0](https://github.com/ReVanced/revanced-patches/compare/v5.32.0...v5.33.0) (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))
* **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))
* **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))
* **YouTube - Litho filter:** Correctly filter identifier of older YouTube targets ([bf29d69](https://github.com/ReVanced/revanced-patches/commit/bf29d6909e389819bad878ad3b94bbc90d823cc9))
* **YouTube - Playback speed:** Use old speed menu for player button if enabled ([1e8f436](https://github.com/ReVanced/revanced-patches/commit/1e8f4368e117f4b278c24709231cb32546e46dc0))
* **YouTube - Video quality:** Fix 144p default not always used ([2f7483a](https://github.com/ReVanced/revanced-patches/commit/2f7483a2d789c28a243b58bb7a252c0d590858ee))
* **YouTube - Video quality:** Fix dialog quality list check mark not always shown ([295f0f2](https://github.com/ReVanced/revanced-patches/commit/295f0f216b5e8aa9d68457862e73e312b7342703))
* **YouTube - Video quality:** Fix wrong qualities sometimes shown in player button dialog ([7378ae3](https://github.com/ReVanced/revanced-patches/commit/7378ae3c5fc88f91bf5cd6db47c6cd170a8c5a4f))
* **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))
### 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))
* **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))
* **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))
### 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.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)

View File

@@ -329,7 +329,7 @@ public class Utils {
return (R) child;
}
throw new IllegalArgumentException("View with resource name '" + str + "' not found");
throw new IllegalArgumentException("View with resource name not found: " + str);
}
/**
@@ -1460,6 +1460,16 @@ public class Utils {
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,16 +1,20 @@
package app.revanced.extension.youtube
import app.revanced.extension.shared.Logger
import java.util.Collections
/**
* generic event provider class
*/
class Event<T> {
private val eventListeners = mutableSetOf<(T) -> Unit>()
private val eventListeners = Collections.synchronizedSet(mutableSetOf<(T) -> Unit>())
operator fun plusAssign(observer: (T) -> Unit) {
addObserver(observer)
}
fun addObserver(observer: (T) -> Unit) {
Logger.printDebug { "Adding observer: $observer" }
eventListeners.add(observer)
}
@@ -23,7 +27,8 @@ class Event<T> {
}
operator fun invoke(value: T) {
for (observer in eventListeners)
for (observer in eventListeners) {
observer.invoke(value)
}
}
}

View File

@@ -0,0 +1,14 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class DisableSignInToTvPopupPatch {
/**
* Injection point.
*/
public static boolean disableSignInToTvPopup() {
return Settings.DISABLE_SIGNIN_TO_TV_POPUP.get();
}
}

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

@@ -1,12 +1,18 @@
package app.revanced.extension.youtube.patches;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.libraries.youtube.innertube.model.media.VideoQuality;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.Event;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
import app.revanced.extension.youtube.shared.VideoState;
/**
@@ -16,11 +22,30 @@ import app.revanced.extension.youtube.shared.VideoState;
public final class VideoInformation {
public interface PlaybackController {
// Methods are added to YT classes during patching.
boolean seekTo(long videoTime);
void seekToRelative(long videoTimeOffset);
// Methods are added during patching.
boolean patch_seekTo(long videoTime);
void patch_seekToRelative(long videoTimeOffset);
}
/**
* Interface to use obfuscated methods.
*/
public interface VideoQualityMenuInterface {
// Method is added during patching.
void patch_setQuality(VideoQuality quality);
}
/**
* Video resolution of the automatic quality option..
*/
public static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2;
/**
* Video quality names are the same text for all languages.
* Premium can be "1080p Premium" or "1080p60 Premium"
*/
public static final String VIDEO_QUALITY_PREMIUM_NAME = "Premium";
private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f;
/**
* Prefix present in all Short player parameters signature.
@@ -30,12 +55,10 @@ public final class VideoInformation {
private static WeakReference<PlaybackController> playerControllerRef = new WeakReference<>(null);
private static WeakReference<PlaybackController> mdxPlayerDirectorRef = new WeakReference<>(null);
@NonNull
private static String videoId = "";
private static long videoLength = 0;
private static long videoTime = -1;
@NonNull
private static volatile String playerResponseVideoId = "";
private static volatile boolean playerResponseVideoIdIsShort;
private static volatile boolean videoIdIsShort;
@@ -45,6 +68,44 @@ public final class VideoInformation {
*/
private static float playbackSpeed = DEFAULT_YOUTUBE_PLAYBACK_SPEED;
private static int desiredVideoResolution = AUTOMATIC_VIDEO_QUALITY_VALUE;
private static boolean qualityNeedsUpdating;
/**
* The available qualities of the current video.
*/
@Nullable
private static VideoQuality[] currentQualities;
/**
* 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;
/**
* Callback for when the current quality changes.
*/
public static final Event<VideoQuality> onQualityChange = new Event<>();
@Nullable
public static VideoQuality[] getCurrentQualities() {
return currentQualities;
}
@Nullable
public static VideoQuality getCurrentQuality() {
return currentQuality;
}
/**
* Injection point.
*
@@ -52,12 +113,18 @@ public final class VideoInformation {
*/
public static void initialize(@NonNull PlaybackController playerController) {
try {
Logger.printDebug(() -> "newVideoStarted");
playerControllerRef = new WeakReference<>(Objects.requireNonNull(playerController));
videoTime = -1;
videoLength = 0;
playbackSpeed = DEFAULT_YOUTUBE_PLAYBACK_SPEED;
desiredVideoResolution = AUTOMATIC_VIDEO_QUALITY_VALUE;
currentQualities = null;
currentMenuInterface = null;
setCurrentQuality(null);
} catch (Exception ex) {
Logger.printException(() -> "Failed to initialize", ex);
Logger.printException(() -> "initialize failure", ex);
}
}
@@ -197,14 +264,14 @@ public final class VideoInformation {
if (controller == null) {
Logger.printDebug(() -> "Cannot seekTo because player controller is null");
} else {
if (controller.seekTo(adjustedSeekTime)) return true;
if (controller.patch_seekTo(adjustedSeekTime)) return true;
Logger.printDebug(() -> "seekTo did not succeeded. Trying MXD.");
// Else the video is loading or changing videos, or video is casting to a different device.
}
// Try calling the seekTo method of the MDX player director (called when casting).
// The difference has to be a different second mark in order to avoid infinite skip loops
// as the Lounge API only supports seconds.
// as the Lounge API only supports whole seconds.
if (adjustedSeekTime / 1000 == videoTime / 1000) {
Logger.printDebug(() -> "Skipping seekTo for MDX because seek time is too small "
+ "(" + (adjustedSeekTime - videoTime) + "ms)");
@@ -217,9 +284,9 @@ public final class VideoInformation {
return false;
}
return controller.seekTo(adjustedSeekTime);
return controller.patch_seekTo(adjustedSeekTime);
} catch (Exception ex) {
Logger.printException(() -> "Failed to seek", ex);
Logger.printException(() -> "seekTo failure", ex);
return false;
}
}
@@ -239,7 +306,7 @@ public final class VideoInformation {
if (controller == null) {
Logger.printDebug(() -> "Cannot seek relative as player controller is null");
} else {
controller.seekToRelative(seekTime);
controller.patch_seekToRelative(seekTime);
}
// Adjust the fine adjustment function so it's at least 1 second before/after.
@@ -255,10 +322,10 @@ public final class VideoInformation {
if (controller == null) {
Logger.printDebug(() -> "Cannot seek relative as MXD player controller is null");
} else {
controller.seekToRelative(adjustedSeekTime);
controller.patch_seekToRelative(adjustedSeekTime);
}
} catch (Exception ex) {
Logger.printException(() -> "Failed to seek relative", ex);
Logger.printException(() -> "seekToRelative failure", ex);
}
}
@@ -339,14 +406,13 @@ public final class VideoInformation {
}
/**
* @return If the playback is at the end of the video.
* <p>
* If video is playing in the background with no video visible,
* this always returns false (even if the video is actually at the end).
* <p>
* This is equivalent to checking for {@link VideoState#ENDED},
* but can give a more up-to-date result for code calling from some hooks.
*
* @return If the playback is at the end of the video.
* @see VideoState
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
@@ -373,4 +439,134 @@ public final class VideoInformation {
playbackSpeed = newlyLoadedPlaybackSpeed;
}
}
/**
* @param resolution The desired video quality resolution to use.
*/
public static void setDesiredVideoResolution(int resolution) {
Utils.verifyOnMainThread();
Logger.printDebug(() -> "Setting desired video resolution: " + resolution);
desiredVideoResolution = resolution;
qualityNeedsUpdating = true;
}
private static void setCurrentQuality(@Nullable VideoQuality quality) {
Utils.verifyOnMainThread();
if (currentQuality != quality) {
Logger.printDebug(() -> "Current quality changed to: " + quality);
currentQuality = quality;
onQualityChange.invoke(quality);
}
}
/**
* Forcefully changes the video quality of the currently playing video.
*/
public static void changeQuality(VideoQuality quality) {
Utils.verifyOnMainThread();
if (currentMenuInterface == null) {
Logger.printException(() -> "Cannot change quality, menu interface is null");
return;
}
currentMenuInterface.patch_setQuality(quality);
}
/**
* Injection point. Fixes bad data used by YouTube.
* Issue can be reproduced by selecting 480p quality on any Short,
* and occasionally with random regular videos.
*/
public static int fixVideoQualityResolution(String name, int quality) {
try {
if (!name.startsWith(Integer.toString(quality))) {
final int suffixIndex = name.indexOf('p');
if (suffixIndex > 0) {
final int fixedQuality = Integer.parseInt(name.substring(0, suffixIndex));
Logger.printDebug(() -> "Fixing wrong quality resolution from: " +
name + "(" + quality + ") to: " + name + ")" + fixedQuality + ")");
return fixedQuality;
}
}
} catch (Exception ex) {
Logger.printException(() -> "fixVideoQualityResolution failed", ex);
}
return quality;
}
/**
* Injection point.
*
* @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(VideoQuality[] qualities, VideoQualityMenuInterface menu, int originalQualityIndex) {
try {
Utils.verifyOnMainThread();
currentMenuInterface = menu;
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)) {
setCurrentQuality(updatedCurrentQuality);
}
final int preferredQuality = desiredVideoResolution;
if (preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) {
return originalQualityIndex; // Nothing to do.
}
// 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) {
qualityNeedsUpdating = false;
} else if (!availableQualitiesChanged) {
return originalQualityIndex;
}
// Find the highest quality that is equal to or less than the preferred.
int i = 0;
final int lastQualityIndex = qualities.length - 1;
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 == lastQualityIndex) {
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()) {
changeQuality(quality);
return i;
}
return originalQualityIndex;
}
i++;
}
} catch (Exception ex) {
Logger.printException(() -> "setVideoQuality failure", ex);
}
return originalQualityIndex;
}
}

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")
@@ -85,6 +83,10 @@ final class ButtonsFilter extends Filter {
new ByteArrayFilterGroup(
Settings.HIDE_CLIP_BUTTON,
"yt_outline_scissors"
),
new ByteArrayFilterGroup(
Settings.HIDE_HYPE_BUTTON,
"yt_outline_star_shooting"
)
);
}
@@ -100,7 +102,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 +119,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;
@@ -59,7 +57,6 @@ abstract class Filter {
* Called after an enabled filter has been matched.
* Default implementation is to always filter the matched component and log the action.
* Subclasses can perform additional or different checks if needed.
*
* <p>
* Method is called off the main thread.
*
@@ -68,7 +65,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

@@ -32,6 +32,7 @@ public final class LayoutComponentsFilter extends Filter {
);
private final StringTrieSearch exceptions = new StringTrieSearch();
private final StringFilterGroup communityPosts;
private final StringFilterGroup surveys;
private final StringFilterGroup notifyMe;
private final StringFilterGroup singleItemInformationPanel;
@@ -68,7 +69,7 @@ public final class LayoutComponentsFilter extends Filter {
// Paths.
final var communityPosts = new StringFilterGroup(
communityPosts = new StringFilterGroup(
Settings.HIDE_COMMUNITY_POSTS,
"post_base_wrapper", // may be obsolete and no longer needed.
"text_post_root.eml",
@@ -304,7 +305,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 +323,13 @@ public final class LayoutComponentsFilter extends Filter {
}
if (matchedGroup == channelProfile) {
return channelProfileBuffer.check(protobufBufferArray).isFiltered();
return channelProfileBuffer.check(buffer).isFiltered();
}
if (matchedGroup == communityPosts && NavigationBar.isBackButtonVisible()) {
// Allow community posts on channel profile page,
// or if viewing an individual channel in the feed.
return false;
}
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
@@ -331,11 +338,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,11 +1,9 @@
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;
import app.revanced.extension.youtube.shared.PlayerType;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
@SuppressWarnings("unused")
public class PlayerFlyoutMenuItemsFilter extends Filter {
@@ -22,17 +20,9 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
}
private final ByteArrayFilterGroupList flyoutFilterGroupList = new ByteArrayFilterGroupList();
private final ByteArrayFilterGroup exception;
private final StringFilterGroup videoQualityMenuFooter;
public PlayerFlyoutMenuItemsFilter() {
exception = new ByteArrayFilterGroup(
// Whitelist Quality menu item when "Hide Additional settings menu" is enabled
Settings.HIDE_PLAYER_FLYOUT_ADDITIONAL_SETTINGS,
"quality_sheet"
);
videoQualityMenuFooter = new StringFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER,
"quality_sheet_footer"
@@ -46,11 +36,11 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
flyoutFilterGroupList.addAll(
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_CAPTIONS,
"closed_caption"
"closed_caption_"
),
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_ADDITIONAL_SETTINGS,
"yt_outline_gear"
"yt_outline_gear_"
),
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_LOOP_VIDEO,
@@ -58,31 +48,31 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
),
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_AMBIENT_MODE,
"yt_outline_screen_light"
"yt_outline_screen_light_"
),
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_STABLE_VOLUME,
"volume_stable"
"volume_stable_"
),
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_HELP,
"yt_outline_question_circle"
"yt_outline_question_circle_"
),
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_MORE_INFO,
"yt_outline_info_circle"
"yt_outline_info_circle_"
),
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_LOCK_SCREEN,
"yt_outline_lock"
"yt_outline_lock_"
),
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_SPEED,
"yt_outline_play_arrow_half_circle"
"yt_outline_play_arrow_half_circle_"
),
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_AUDIO_TRACK,
"yt_outline_person_radar"
"yt_outline_person_radar_"
),
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_SLEEP_TIMER,
@@ -90,13 +80,17 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
),
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_WATCH_IN_VR,
"yt_outline_vr"
"yt_outline_vr_"
),
new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_FLYOUT_VIDEO_QUALITY,
"yt_outline_adjust_"
)
);
}
@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 +101,10 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
}
// Shorts also use this player flyout panel
if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(protobufBufferArray).isFiltered()) {
if (ShortsPlayerState.isOpen()) {
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

@@ -3,12 +3,7 @@ package app.revanced.extension.youtube.patches.playback.quality;
import static app.revanced.extension.shared.StringRef.str;
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 app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
@@ -20,166 +15,96 @@ import app.revanced.extension.youtube.shared.ShortsPlayerState;
@SuppressWarnings("unused")
public class RememberVideoQualityPatch {
private static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2;
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;
private static final IntegerSetting shortsQualityMobile = Settings.SHORTS_QUALITY_DEFAULT_MOBILE;
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]
*/
@Nullable
private static List<Integer> videoQualities;
private static boolean shouldRememberVideoQuality() {
BooleanSetting preference = ShortsPlayerState.isOpen() ?
Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED
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)
);
}
}
/**
* Injection point.
*
* @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
* @param userSelectedQualityIndex Element index of {@link VideoInformation#getCurrentQualities()}.
*/
public static int setVideoQuality(Object[] qualities, final int originalQualityIndex, Object qInterface, String qIndexMethod) {
public static void userChangedShortsQuality(int userSelectedQualityIndex) {
try {
boolean useShortsPreference = ShortsPlayerState.isOpen();
final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE
? (useShortsPreference ? shortsQualityMobile : videoQualityMobile).get()
: (useShortsPreference ? shortsQualityWifi : videoQualityWifi).get();
if (!userChangedDefaultQuality && 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));
}
}
if (shouldRememberVideoQuality()) {
VideoQuality[] currentQualities = VideoInformation.getCurrentQualities();
if (currentQualities == null) {
Logger.printDebug(() -> "Cannot save default quality, qualities is null");
return;
}
// 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);
VideoQuality quality = currentQualities[userSelectedQualityIndex];
saveDefaultQuality(quality.patch_getResolution());
}
if (userChangedDefaultQuality) {
userChangedDefaultQuality = false;
final int quality = videoQualities.get(userSelectedQualityIndex);
Logger.printDebug(() -> "User changed default quality to: " + quality);
changeDefaultQuality(quality);
return userSelectedQualityIndex;
}
if (!qualityNeedsUpdating) {
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;
}
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(() -> "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();
Logger.printDebug(() -> "User changed quality to: " + videoResolution);
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) {
Logger.printDebug(() -> "newVideoStarted");
qualityNeedsUpdating = true;
videoQualities = null;
VideoInformation.setDesiredVideoResolution(getDefaultQualityResolution());
}
}

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,13 +99,9 @@ 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).
// Use same 2 digit format as built in speed picker,
speedFormatter.setMinimumFractionDigits(2);
speedFormatter.setMaximumFractionDigits(2);
final float holdSpeed = Settings.SPEED_TAP_AND_HOLD.get();
@@ -174,25 +179,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 +213,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>
@@ -293,7 +322,7 @@ public class CustomPlaybackSpeedPatch {
TextView currentSpeedText = new TextView(context);
float currentSpeed = VideoInformation.getPlaybackSpeed();
// Initially show with only 0 minimum digits, so 1.0 shows as 1x
currentSpeedText.setText(formatSpeedStringX(currentSpeed, 0));
currentSpeedText.setText(formatSpeedStringX(currentSpeed));
currentSpeedText.setTextColor(Utils.getAppForegroundColor());
currentSpeedText.setTextSize(16);
currentSpeedText.setTypeface(Typeface.DEFAULT_BOLD);
@@ -370,10 +399,11 @@ public class CustomPlaybackSpeedPatch {
return null;
}
VideoInformation.overridePlaybackSpeed(roundedSpeed);
RememberPlaybackSpeedPatch.userSelectedPlaybackSpeed(roundedSpeed);
currentSpeedText.setText(formatSpeedStringX(roundedSpeed, 2)); // Update display.
currentSpeedText.setText(formatSpeedStringX(roundedSpeed)); // Update display.
speedSlider.setProgress(speedToProgressValue(roundedSpeed)); // Update slider.
RememberPlaybackSpeedPatch.userSelectedPlaybackSpeed(roundedSpeed);
VideoInformation.overridePlaybackSpeed(roundedSpeed);
return null;
};
@@ -409,7 +439,7 @@ public class CustomPlaybackSpeedPatch {
gridParams.setMargins(0, 0, 0, 0); // No margins around GridLayout.
gridLayout.setLayoutParams(gridParams);
// For all buttons show at least 1 zero in decimal (2 -> "2.0").
// For button use 1 digit minimum.
speedFormatter.setMinimumFractionDigits(1);
// Add buttons for each preset playback speed.
@@ -427,7 +457,7 @@ public class CustomPlaybackSpeedPatch {
// Create speed button.
Button speedButton = new Button(context, null, 0);
speedButton.setText(speedFormatter.format(speed)); // Do not use 'x' speed format.
speedButton.setText(speedFormatter.format(speed));
speedButton.setTextColor(Utils.getAppForegroundColor());
speedButton.setTextSize(12);
speedButton.setAllCaps(false);
@@ -470,6 +500,9 @@ public class CustomPlaybackSpeedPatch {
gridLayout.addView(buttonContainer);
}
// Restore 2 digit minimum.
speedFormatter.setMinimumFractionDigits(2);
// Add in-rows speed buttons layout to main layout.
mainLayout.addView(gridLayout);
@@ -603,8 +636,7 @@ public class CustomPlaybackSpeedPatch {
* @param speed The playback speed value to format.
* @return A string representation of the speed with 'x' (e.g. "1.25x" or "1.00x").
*/
private static String formatSpeedStringX(float speed, int minimumFractionDigits) {
speedFormatter.setMinimumFractionDigits(minimumFractionDigits);
private static String formatSpeedStringX(float speed) {
return speedFormatter.format(speed) + 'x';
}
@@ -643,11 +675,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

@@ -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);
@@ -221,6 +223,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_ASK_BUTTON = new BooleanSetting("revanced_hide_ask_button", FALSE);
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", TRUE);
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE);
public static final BooleanSetting HIDE_HYPE_BUTTON = new BooleanSetting("revanced_hide_hype_button", FALSE);
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
public static final BooleanSetting HIDE_REMIX_BUTTON = new BooleanSetting("revanced_hide_remix_button", TRUE);
public static final BooleanSetting HIDE_REPORT_BUTTON = new BooleanSetting("revanced_hide_report_button", FALSE);
@@ -242,6 +245,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_PLAYER_FLYOUT_SPEED = new BooleanSetting("revanced_hide_player_flyout_speed", FALSE);
public static final BooleanSetting HIDE_PLAYER_FLYOUT_STABLE_VOLUME = new BooleanSetting("revanced_hide_player_flyout_stable_volume", FALSE);
public static final BooleanSetting HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER = new BooleanSetting("revanced_hide_player_flyout_video_quality_footer", FALSE);
public static final BooleanSetting HIDE_PLAYER_FLYOUT_VIDEO_QUALITY = new BooleanSetting("revanced_hide_player_flyout_video_quality", FALSE);
public static final BooleanSetting HIDE_PLAYER_FLYOUT_WATCH_IN_VR = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", TRUE);
// General layout
@@ -253,6 +257,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
public static final EnumSetting<SplashScreenAnimationStyle> SPLASH_SCREEN_ANIMATION_STYLE = new EnumSetting<>("revanced_splash_screen_animation_style", SplashScreenAnimationStyle.FPS_60_ONE_SECOND, true);
public static final EnumSetting<HeaderLogo> HEADER_LOGO = new EnumSetting<>("revanced_header_logo", HeaderLogo.DEFAULT, true);
public static final BooleanSetting DISABLE_SIGNIN_TO_TV_POPUP = new BooleanSetting("revanced_disable_signin_to_tv_popup", FALSE);
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
"revanced_remove_viewer_discretion_dialog_user_dialog_message");

View File

@@ -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

@@ -26,6 +26,7 @@ public class CreateSegmentButton {
controlsView,
"revanced_sb_create_segment_button",
null,
null,
CreateSegmentButton::shouldBeShown,
v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility(),
null

View File

@@ -28,6 +28,7 @@ public class VotingButton {
controlsView,
"revanced_sb_voting_button",
null,
null,
VotingButton::shouldBeShown,
v -> SponsorBlockUtils.onVotingClicked(v.getContext()),
null

View File

@@ -7,7 +7,6 @@ import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.patches.CopyVideoUrlPatch;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public class CopyVideoUrlButton {
@@ -23,6 +22,7 @@ public class CopyVideoUrlButton {
controlsView,
"revanced_copy_video_url_button",
"revanced_copy_video_url_button_placeholder",
null,
Settings.COPY_VIDEO_URL::get,
view -> CopyVideoUrlPatch.copyUrl(false),
view -> {

View File

@@ -7,7 +7,6 @@ import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.patches.CopyVideoUrlPatch;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public class CopyVideoUrlTimestampButton {
@@ -23,6 +22,7 @@ public class CopyVideoUrlTimestampButton {
controlsView,
"revanced_copy_video_url_timestamp_button",
"revanced_copy_video_url_timestamp_button_placeholder",
null,
Settings.COPY_VIDEO_URL_TIMESTAMP::get,
view -> CopyVideoUrlPatch.copyUrl(true),
view -> {

View File

@@ -23,6 +23,7 @@ public class ExternalDownloadButton {
controlsView,
"revanced_external_download_button",
"revanced_external_download_button_placeholder",
null,
Settings.EXTERNAL_DOWNLOADER::get,
ExternalDownloadButton::onDownloadClick,
null

View File

@@ -4,19 +4,26 @@ import android.view.View;
import androidx.annotation.Nullable;
import java.text.DecimalFormat;
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.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
private static PlayerControlButton instance;
private static final DecimalFormat speedDecimalFormatter = new DecimalFormat();
static {
speedDecimalFormatter.setMinimumFractionDigits(1);
speedDecimalFormatter.setMaximumFractionDigits(2);
}
/**
* Injection point.
*/
@@ -24,23 +31,29 @@ public class PlaybackSpeedDialogButton {
try {
instance = new PlayerControlButton(
controlsView,
"revanced_playback_speed_dialog_button_container",
"revanced_playback_speed_dialog_button",
"revanced_playback_speed_dialog_button_placeholder",
"revanced_playback_speed_dialog_button_text",
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 -> {
try {
final float defaultSpeed = Settings.PLAYBACK_SPEED_DEFAULT.get();
final float speed = (!Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED.get() ||
VideoInformation.getPlaybackSpeed() == Settings.PLAYBACK_SPEED_DEFAULT.get())
VideoInformation.getPlaybackSpeed() == defaultSpeed)
? 1.0f
: Settings.PLAYBACK_SPEED_DEFAULT.get();
: defaultSpeed;
VideoInformation.overridePlaybackSpeed(speed);
} catch (Exception ex) {
Logger.printException(() -> "speed button reset failure", ex);
@@ -48,22 +61,53 @@ public class PlaybackSpeedDialogButton {
return true;
}
);
// Set the appropriate icon.
updateButtonAppearance();
} catch (Exception ex) {
Logger.printException(() -> "initializeButton failure", ex);
}
}
/**
* injection point
* Injection point.
*/
public static void setVisibilityImmediate(boolean visible) {
if (instance != null) instance.setVisibilityImmediate(visible);
if (instance != null) {
instance.setVisibilityImmediate(visible);
}
}
/**
* injection point
* Injection point.
*/
public static void setVisibility(boolean visible, boolean animated) {
if (instance != null) instance.setVisibility(visible, animated);
if (instance != null) {
instance.setVisibility(visible, animated);
}
}
}
/**
* Injection point.
*/
public static void videoSpeedChanged(float currentVideoSpeed) {
updateButtonAppearance();
}
/**
* Updates the button's appearance, including icon and text overlay.
*/
private static void updateButtonAppearance() {
if (instance == null) return;
try {
Utils.verifyOnMainThread();
String speedText = speedDecimalFormatter.format(VideoInformation.getPlaybackSpeed());
instance.setTextOverlay(speedText);
Logger.printDebug(() -> "Updated playback speed button text to: " + speedText);
} catch (Exception ex) {
Logger.printException(() -> "updateButtonAppearance failure", ex);
}
}
}

View File

@@ -3,6 +3,7 @@ package app.revanced.extension.youtube.videoplayer;
import android.view.View;
import android.view.animation.Animation;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
@@ -14,11 +15,12 @@ import app.revanced.extension.youtube.shared.PlayerType;
import kotlin.Unit;
public class PlayerControlButton {
public interface PlayerControlButtonVisibility {
public interface PlayerControlButtonStatus {
/**
* @return If the button should be shown when the player overlay is visible.
*/
boolean shouldBeShown();
boolean buttonEnabled();
}
private static final int fadeInDuration;
@@ -44,23 +46,46 @@ public class PlayerControlButton {
fadeOutImmediate.setDuration(Utils.getResourceInteger("fade_duration_fast"));
}
private final WeakReference<View> containerRef;
private final WeakReference<View> buttonRef;
/**
* Empty view with the same layout size as the button. Used to fill empty space while the
* fade out animation runs. Without this the chapter titles overlapping the button when fading out.
*/
private final WeakReference<View> placeHolderRef;
private final PlayerControlButtonVisibility visibilityCheck;
private final WeakReference<TextView> textOverlayRef;
private final PlayerControlButtonStatus enabledStatus;
private boolean isVisible;
public PlayerControlButton(View controlsViewGroup,
String imageViewButtonId,
String buttonId,
@Nullable String placeholderId,
PlayerControlButtonVisibility buttonVisibility,
@Nullable String textOverlayId,
PlayerControlButtonStatus enabledStatus,
View.OnClickListener onClickListener,
@Nullable View.OnLongClickListener longClickListener) {
ImageView imageView = Utils.getChildViewByResourceName(controlsViewGroup, imageViewButtonId);
imageView.setVisibility(View.GONE);
this(controlsViewGroup, buttonId, buttonId, placeholderId, textOverlayId,
enabledStatus, onClickListener, longClickListener);
}
public PlayerControlButton(View controlsViewGroup,
String viewToHide,
String buttonId,
@Nullable String placeholderId,
@Nullable String textOverlayId,
PlayerControlButtonStatus enabledStatus,
View.OnClickListener onClickListener,
@Nullable View.OnLongClickListener longClickListener) {
View containerView = Utils.getChildViewByResourceName(controlsViewGroup, viewToHide);
containerView.setVisibility(View.GONE);
containerRef = new WeakReference<>(containerView);
View button = Utils.getChildViewByResourceName(controlsViewGroup, buttonId);
button.setOnClickListener(onClickListener);
if (longClickListener != null) {
button.setOnLongClickListener(longClickListener);
}
buttonRef = new WeakReference<>(button);
View tempPlaceholder = null;
if (placeholderId != null) {
@@ -69,19 +94,19 @@ public class PlayerControlButton {
}
placeHolderRef = new WeakReference<>(tempPlaceholder);
imageView.setOnClickListener(onClickListener);
if (longClickListener != null) {
imageView.setOnLongClickListener(longClickListener);
TextView tempTextOverlay = null;
if (textOverlayId != null) {
tempTextOverlay = Utils.getChildViewByResourceName(controlsViewGroup, textOverlayId);
}
textOverlayRef = new WeakReference<>(tempTextOverlay);
visibilityCheck = buttonVisibility;
buttonRef = new WeakReference<>(imageView);
this.enabledStatus = enabledStatus;
isVisible = false;
// Update the visibility after the player type changes.
// This ensures that button animations are cleared and their states are updated correctly
// when switching between states like minimized, maximized, or fullscreen, preventing
// "stuck" animations or incorrect visibility. Without this fix the issue is most noticable
// "stuck" animations or incorrect visibility. Without this fix the issue is most noticeable
// when maximizing type 3 miniplayer.
PlayerType.getOnChange().addObserver((PlayerType type) -> {
playerTypeChanged(type);
@@ -111,33 +136,33 @@ public class PlayerControlButton {
if (isVisible == visible) return;
isVisible = visible;
View button = buttonRef.get();
if (button == null) return;
View container = containerRef.get();
if (container == null) return;
View placeholder = placeHolderRef.get();
final boolean shouldBeShown = visibilityCheck.shouldBeShown();
final boolean buttonEnabled = enabledStatus.buttonEnabled();
if (visible && shouldBeShown) {
button.clearAnimation();
if (visible && buttonEnabled) {
container.clearAnimation();
if (animated) {
button.startAnimation(PlayerControlButton.fadeInAnimation);
container.startAnimation(fadeInAnimation);
}
button.setVisibility(View.VISIBLE);
container.setVisibility(View.VISIBLE);
if (placeholder != null) {
placeholder.setVisibility(View.GONE);
}
} else {
if (button.getVisibility() == View.VISIBLE) {
button.clearAnimation();
if (container.getVisibility() == View.VISIBLE) {
container.clearAnimation();
if (animated) {
button.startAnimation(PlayerControlButton.fadeOutAnimation);
container.startAnimation(fadeOutAnimation);
}
button.setVisibility(View.GONE);
container.setVisibility(View.GONE);
}
if (placeholder != null) {
placeholder.setVisibility(shouldBeShown
placeholder.setVisibility(buttonEnabled
? View.VISIBLE
: View.GONE);
}
@@ -155,36 +180,59 @@ public class PlayerControlButton {
return;
}
View button = buttonRef.get();
if (button == null) return;
View container = containerRef.get();
if (container == null) return;
button.clearAnimation();
container.clearAnimation();
View placeholder = placeHolderRef.get();
if (visibilityCheck.shouldBeShown()) {
if (enabledStatus.buttonEnabled()) {
if (isVisible) {
button.setVisibility(View.VISIBLE);
container.setVisibility(View.VISIBLE);
if (placeholder != null) placeholder.setVisibility(View.GONE);
} else {
button.setVisibility(View.GONE);
container.setVisibility(View.GONE);
if (placeholder != null) placeholder.setVisibility(View.VISIBLE);
}
} else {
button.setVisibility(View.GONE);
container.setVisibility(View.GONE);
if (placeholder != null) placeholder.setVisibility(View.GONE);
}
}
public void hide() {
Utils.verifyOnMainThread();
if (!isVisible) return;
Utils.verifyOnMainThread();
View view = buttonRef.get();
View view = containerRef.get();
if (view == null) return;
view.setVisibility(View.GONE);
view = placeHolderRef.get();
if (view != null) view.setVisibility(View.GONE);
View placeHolder = placeHolderRef.get();
if (placeHolder != 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) {
View button = buttonRef.get();
if (button instanceof ImageView imageButton) {
imageButton.setImageResource(resourceId);
}
}
/**
* Sets the text to be displayed on the text overlay.
* @param text The text to set on the overlay, or null to clear the text.
*/
public void setTextOverlay(CharSequence text) {
TextView textOverlay = textOverlayRef.get();
if (textOverlay != null) {
textOverlay.setText(text);
}
}
}

View File

@@ -0,0 +1,471 @@
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.VideoInformation.AUTOMATIC_VIDEO_QUALITY_VALUE;
import static app.revanced.extension.youtube.patches.VideoInformation.VIDEO_QUALITY_PREMIUM_NAME;
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.text.style.UnderlineSpan;
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.VideoInformation;
import app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch;
import app.revanced.extension.youtube.settings.Settings;
import kotlin.Unit;
@SuppressWarnings("unused")
public class VideoQualityDialogButton {
@Nullable
private static PlayerControlButton instance;
@Nullable
private static CharSequence currentOverlayText;
static {
VideoInformation.onQualityChange.addObserver((@Nullable VideoQuality quality) -> {
updateButtonText(quality);
return Unit.INSTANCE;
});
}
/**
* Injection point.
*/
public static void initializeButton(View controlsView) {
try {
instance = new PlayerControlButton(
controlsView,
"revanced_video_quality_dialog_button_container",
"revanced_video_quality_dialog_button",
"revanced_video_quality_dialog_button_placeholder",
"revanced_video_quality_dialog_button_text",
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 = VideoInformation.getCurrentQualities();
if (qualities == 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);
VideoInformation.changeQuality(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 text.
updateButtonText(VideoInformation.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);
}
}
/**
* Updates the button text based on the current video quality.
*/
public static void updateButtonText(@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();
SpannableStringBuilder text = new SpannableStringBuilder();
String qualityText = switch (resolution) {
case AUTOMATIC_VIDEO_QUALITY_VALUE -> "";
case 144, 240, 360 -> "LD";
case 480 -> "SD";
case 720 -> "HD";
case 1080 -> "FHD";
case 1440 -> "QHD";
case 2160 -> "4K";
default -> "?"; // Should never happen.
};
text.append(qualityText);
if (quality != null && quality.patch_getQualityName().contains(VIDEO_QUALITY_PREMIUM_NAME)) {
// Underline the entire "FHD" text for 1080p Premium.
text.setSpan(new UnderlineSpan(), 0, qualityText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
currentOverlayText = text;
Utils.runOnMainThreadDelayed(() -> {
if (currentOverlayText != text) {
Logger.printDebug(() -> "Ignoring stale button text update of: " + text);
return;
}
instance.setTextOverlay(text);
}, 100);
} catch (Exception ex) {
Logger.printException(() -> "updateButtonText failure", ex);
}
}
/**
* 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 = VideoInformation.getCurrentQualities();
VideoQuality currentQuality = VideoInformation.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;
}
// -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];
RememberVideoQualityPatch.userChangedQuality(selectedQuality.patch_getResolution());
VideoInformation.changeQuality(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);
// Limit height in landscape mode.
params.height = Utils.percentageHeightToPixels(80);
} else {
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
}
params.width = portraitWidth;
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 static final int CUSTOM_LIST_ITEM_CHECKED_ID = Utils.getResourceIdentifier(
"revanced_custom_list_item_checked", "layout");
private static final int CHECK_ICON_ID = Utils.getResourceIdentifier(
"revanced_check_icon", "id");
private static final int CHECK_ICON_PLACEHOLDER_ID = Utils.getResourceIdentifier(
"revanced_check_icon_placeholder", "id");
private static final int ITEM_TEXT_ID = Utils.getResourceIdentifier(
"revanced_item_text", "id");
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(
CUSTOM_LIST_ITEM_CHECKED_ID,
parent,
false
);
viewHolder = new ViewHolder();
viewHolder.checkIcon = convertView.findViewById(CHECK_ICON_ID);
viewHolder.placeholder = convertView.findViewById(CHECK_ICON_PLACEHOLDER_ID);
viewHolder.textView = convertView.findViewById(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.32.0-dev.5
version = 5.34.0-dev.9

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;
}
@@ -1353,6 +1357,10 @@ public final class app/revanced/patches/youtube/layout/hide/shorts/HideShortsCom
public static final fun getHideShortsComponentsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/layout/hide/signintotvpopup/DisableSignInToTvPatchPopupKt {
public static final fun getDisableSignInToTvPopupPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/layout/hide/suggestedvideoendscreen/DisableSuggestedVideoEndScreenPatchKt {
public static final fun getDisableSuggestedVideoEndScreenPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -1656,6 +1664,10 @@ public final class app/revanced/patches/youtube/video/quality/RememberVideoQuali
public static final fun getRememberVideoQualityPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/video/quality/VideoQualityDialogButtonPatchKt {
public static final fun getVideoQualityDialogButtonPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/video/quality/VideoQualityPatchKt {
public static final fun getVideoQualityPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}

View File

@@ -3,6 +3,7 @@ package app.revanced.patches.backdrops.misc.pro
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.Opcode
@Deprecated("Fingerprint no longer resolves and will soon be deleted.")
internal val proUnlockFingerprint = fingerprint {
opcodes(
Opcode.INVOKE_VIRTUAL,

View File

@@ -6,9 +6,8 @@ import app.revanced.patcher.patch.bytecodePatch
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Suppress("unused")
val proUnlockPatch = bytecodePatch(
name = "Pro unlock",
) {
@Deprecated("This patch no longer works and will soon be deleted.")
val proUnlockPatch = bytecodePatch{
compatibleWith("com.backdrops.wallpapers")
execute {

View File

@@ -8,7 +8,7 @@ val signatureCheckPatch = bytecodePatch(
name = "Disable signature check",
description = "Disables the signature check that causes the app to crash on startup."
) {
compatibleWith("com.instagram.android"("378.0.0.52.68"))
compatibleWith("com.instagram.android")
execute {
isValidSignatureMethodFingerprint

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

@@ -3,6 +3,7 @@ package app.revanced.patches.music.layout.compactheader
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.findFreeRegister
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Suppress("unused")
@@ -17,13 +18,14 @@ val hideCategoryBar = bytecodePatch(
constructCategoryBarFingerprint.method.apply {
val insertIndex = constructCategoryBarFingerprint.patternMatch!!.startIndex
val register = getInstruction<OneRegisterInstruction>(insertIndex - 1).registerA
val freeRegister = findFreeRegister(insertIndex, register)
addInstructions(
insertIndex,
"""
const/16 v2, 0x8
invoke-virtual {v$register, v2}, Landroid/view/View;->setVisibility(I)V
""",
const/16 v$freeRegister, 0x8
invoke-virtual { v$register, v$freeRegister }, Landroid/view/View;->setVisibility(I)V
"""
)
}
}

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

@@ -14,7 +14,7 @@ val hideAdsPatch = bytecodePatch(
name = "Hide ads",
description = "Hide ads and sponsored articles in list pages and remove pre-roll ads on videos.",
) {
compatibleWith("nl.sanomamedia.android.nu"("11.3.0"))
compatibleWith("nl.sanomamedia.android.nu")
dependsOn(sharedExtensionPatch("nunl", mainActivityOnCreateHook))

View File

@@ -4,6 +4,6 @@ import app.revanced.patches.shared.misc.extension.extensionHook
internal val mainActivityOnCreateHook = extensionHook {
custom { method, classDef ->
classDef.type == "Lnl/sanomamedia/android/nu/main/NUMainActivity;" && method.name == "onCreate"
classDef.endsWith("/NUApplication;") && method.name == "onCreate"
}
}

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

@@ -1,21 +1,15 @@
package app.revanced.patches.pixiv.ads
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val hideAdsPatch = bytecodePatch(
name = "Hide ads",
) {
compatibleWith("jp.pxv.android")
compatibleWith("jp.pxv.android"("6.141.1"))
execute {
shouldShowAdsFingerprint.method.addInstructions(
0,
"""
const/4 v0, 0x0
return v0
""",
)
shouldShowAdsFingerprint.method.returnEarly(false)
}
}

View File

@@ -21,7 +21,7 @@ val audioAdsPatch = bytecodePatch(
addResourcesPatch,
)
compatibleWith("tv.twitch.android.app")
compatibleWith("tv.twitch.android.app"("16.9.1", "25.3.0"))
execute {
addResources("twitch", "ad.audio.audioAdsPatch")

View File

@@ -19,7 +19,7 @@ val embeddedAdsPatch = bytecodePatch(
settingsPatch,
)
compatibleWith("tv.twitch.android.app")
compatibleWith("tv.twitch.android.app"("16.9.1", "25.3.0"))
execute {
addResources("twitch", "ad.embedded.embeddedAdsPatch")

View File

@@ -155,7 +155,5 @@ val videoAdsPatch = bytecodePatch(
},
)
compatibleWith(
"tv.twitch.android.app",
)
compatibleWith("tv.twitch.android.app"("16.9.1", "25.3.0"))
}

View File

@@ -22,7 +22,7 @@ val showDeletedMessagesPatch = bytecodePatch(
addResourcesPatch,
)
compatibleWith("tv.twitch.android.app")
compatibleWith("tv.twitch.android.app"("16.9.1", "25.3.0"))
fun createSpoilerConditionInstructions(register: String = "v0") = """
invoke-static {}, Lapp/revanced/extension/twitch/patches/ShowDeletedMessagesPatch;->shouldUseSpoiler()Z

View File

@@ -20,7 +20,7 @@ val autoClaimChannelPointsPatch = bytecodePatch(
addResourcesPatch,
)
compatibleWith("tv.twitch.android.app")
compatibleWith("tv.twitch.android.app"("16.9.1", "25.3.0"))
execute {
addResources("twitch", "chat.autoclaim.autoClaimChannelPointsPatch")

View File

@@ -20,7 +20,7 @@ val debugModePatch = bytecodePatch(
addResourcesPatch,
)
compatibleWith("tv.twitch.android.app")
compatibleWith("tv.twitch.android.app"("16.9.1", "25.3.0"))
execute {
addResources("twitch", "debug.debugModePatch")

View File

@@ -48,7 +48,7 @@ val settingsPatch = bytecodePatch(
settingsPatch(preferences = preferences),
)
compatibleWith("tv.twitch.android.app")
compatibleWith("tv.twitch.android.app"("16.9.1"))
execute {
addResources("twitch", "misc.settings.settingsPatch")

View File

@@ -49,6 +49,7 @@ val hideButtonsPatch = resourcePatch(
SwitchPreference("revanced_hide_share_button"),
SwitchPreference("revanced_hide_stop_ads_button"),
SwitchPreference("revanced_hide_thanks_button"),
SwitchPreference("revanced_hide_hype_button"),
)
)
)

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

@@ -57,6 +57,7 @@ val hidePlayerFlyoutMenuPatch = bytecodePatch(
),
SwitchPreference("revanced_hide_player_flyout_watch_in_vr"),
SwitchPreference("revanced_hide_player_flyout_sleep_timer"),
SwitchPreference("revanced_hide_player_flyout_video_quality"),
SwitchPreference("revanced_hide_player_flyout_video_quality_footer"),
),
),

View File

@@ -0,0 +1,68 @@
package app.revanced.patches.youtube.layout.hide.signintotvpopup
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.patch.bytecodePatch
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.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
internal var mdx_seamless_tv_sign_in_drawer_fragment_title_id = -1L
private set
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/DisableSignInToTvPopupPatch;"
val disableSignInToTvPopupPatch = bytecodePatch(
name = "Disable sign in to TV popup",
description = "Adds an option to disable the popup asking to sign into a TV on the same local network.",
) {
dependsOn(
settingsPatch,
sharedExtensionPatch,
addResourcesPatch,
resourceMappingPatch
)
compatibleWith(
"com.google.android.youtube"(
"19.34.42",
"19.43.41",
"19.47.53",
"20.07.39",
"20.12.46",
"20.13.41",
)
)
execute {
addResources("youtube", "layout.hide.signintotv.disableSignInToTvPopupPatch")
PreferenceScreen.MISC.addPreferences(
SwitchPreference("revanced_disable_signin_to_tv_popup"),
)
mdx_seamless_tv_sign_in_drawer_fragment_title_id = resourceMappings[
"string",
"mdx_seamless_tv_sign_in_drawer_fragment_title",
]
signInToTvPopupFingerprint.method.addInstructionsWithLabels(
0,
"""
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->disableSignInToTvPopup()Z
move-result v0
if-eqz v0, :allow_sign_in_popup
const/4 v0, 0x0
return v0
:allow_sign_in_popup
nop
"""
)
}
}

View File

@@ -0,0 +1,12 @@
package app.revanced.patches.youtube.layout.hide.signintotvpopup
import app.revanced.patcher.fingerprint
import app.revanced.util.literal
internal val signInToTvPopupFingerprint = fingerprint {
returns("Z")
parameters("Ljava/lang/String;", "Z", "L")
literal {
mdx_seamless_tv_sign_in_drawer_fragment_title_id
}
}

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

@@ -282,7 +282,7 @@ val playerControlsPatch = bytecodePatch(
// The change to support this is simple and only requires adding buttons to both layout files,
// but for now force this different layout off since it's still an experimental test.
if (is_19_35_or_greater) {
playerBottomControlsExploderFeatureFlagFingerprint.method.returnEarly()
playerBottomControlsExploderFeatureFlagFingerprint.method.returnLate(false)
}
// A/B test of new top overlay controls. Two different layouts can be used:

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

@@ -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,100 +61,93 @@ 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
formatStreamModelToStringFingerprint.let {
val isDefaultAudioTrackMethod = it.originalMethod.findMethodFromToString("isDefaultAudioTrack=")
val audioTrackDisplayNameMethod = it.originalMethod.findMethodFromToString("audioTrackDisplayName=")
val audioTrackIdMethod = it.originalMethod.findMethodFromToString("audioTrackId=")
formatStreamModelClass.apply {
// Add a new field to store the override.
val helperFieldName = "isDefaultAudioTrackOverride"
fields.add(
ImmutableField(
type,
helperFieldName,
"Ljava/lang/Boolean;",
// Boolean is a 100% immutable class (all fields are final)
// and safe to write to a shared field without volatile/synchronization,
// but without volatile the field can show stale data
// and the same field is calculated more than once by different threads.
AccessFlags.PRIVATE.value or AccessFlags.VOLATILE.value,
it.classDef.apply {
// Add a new field to store the override.
val helperFieldName = "patch_isDefaultAudioTrackOverride"
fields.add(
ImmutableField(
type,
helperFieldName,
"Ljava/lang/Boolean;",
// Boolean is a 100% immutable class (all fields are final)
// and safe to write to a shared field without volatile/synchronization,
// but without volatile the field can show stale data
// and the same field is calculated more than once by different threads.
AccessFlags.PRIVATE.value or AccessFlags.VOLATILE.value,
null,
null,
null
).toMutable()
)
// Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed.
val helperMethodClass = type
val helperMethodName = "patch_isDefaultAudioTrack"
val helperMethod = ImmutableMethod(
helperMethodClass,
helperMethodName,
listOf(ImmutableMethodParameter("Z", null, null)),
"Z",
AccessFlags.PRIVATE.value,
null,
null,
null
).toMutable()
)
MutableMethodImplementation(6),
).toMutable().apply {
addInstructionsWithLabels(
0,
"""
iget-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean;
if-eqz v0, :call_extension
invoke-virtual { v0 }, Ljava/lang/Boolean;->booleanValue()Z
move-result v3
return v3
:call_extension
invoke-virtual { p0 }, $audioTrackIdMethod
move-result-object v1
invoke-virtual { p0 }, $audioTrackDisplayNameMethod
move-result-object v2
invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->isDefaultAudioStream(ZLjava/lang/String;Ljava/lang/String;)Z
move-result v3
invoke-static { v3 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;
move-result-object v0
iput-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean;
return v3
"""
)
}
methods.add(helperMethod)
// Add a helper method because the isDefaultAudioTrack() has only 2 registers and 3 are needed.
val helperMethodClass = type
val helperMethodName = "extension_isDefaultAudioTrack"
val helperMethod = ImmutableMethod(
helperMethodClass,
helperMethodName,
listOf(ImmutableMethodParameter("Z", null, null)),
"Z",
AccessFlags.PRIVATE.value,
null,
null,
MutableMethodImplementation(6),
).toMutable().apply {
addInstructionsWithLabels(
0,
"""
iget-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean;
if-eqz v0, :call_extension
invoke-virtual { v0 }, Ljava/lang/Boolean;->booleanValue()Z
move-result v3
return v3
:call_extension
invoke-virtual { p0 }, $audioTrackIdMethod
move-result-object v1
invoke-virtual { p0 }, $audioTrackDisplayNameMethod
move-result-object v2
invoke-static { p1, v1, v2 }, $EXTENSION_CLASS_DESCRIPTOR->isDefaultAudioStream(ZLjava/lang/String;Ljava/lang/String;)Z
move-result v3
invoke-static { v3 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean;
move-result-object v0
iput-object v0, p0, $helperMethodClass->$helperFieldName:Ljava/lang/Boolean;
return v3
"""
)
}
methods.add(helperMethod)
// Modify isDefaultAudioTrack() to call extension helper method.
isDefaultAudioTrackMethod.apply {
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
val register = getInstruction<OneRegisterInstruction>(index).registerA
// Modify isDefaultAudioTrack() to call extension helper method.
isDefaultMethod.apply {
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN)
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index,
"""
invoke-direct { p0, v$register }, $helperMethodClass->$helperMethodName(Z)Z
move-result v$register
"""
)
addInstructions(
index,
"""
invoke-direct { p0, v$register }, $helperMethodClass->$helperMethodName(Z)Z
move-result v$register
"""
)
}
}
}
}

View File

@@ -63,7 +63,7 @@ val disableHdrPatch = bytecodePatch(
return v0
:useHdr
nop
"""
"""
)
}
}

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)
@@ -133,3 +133,46 @@ internal val playbackSpeedClassFingerprint = fingerprint {
)
strings("PLAYBACK_RATE_MENU_BOTTOM_SHEET_FRAGMENT")
}
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
}
}
internal val videoQualitySetterFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters("[L", "I", "Z")
opcodes(
Opcode.IF_EQZ,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.IPUT_BOOLEAN,
)
strings("menu_item_video_quality")
}
/**
* Matches with the class found in [videoQualitySetterFingerprint].
*/
internal val setVideoQualityFingerprint = fingerprint {
returns("V")
parameters("L")
opcodes(
Opcode.IGET_OBJECT,
Opcode.IPUT_OBJECT,
Opcode.IGET_OBJECT,
)
}

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
@@ -38,7 +38,9 @@ import com.android.tools.smali.dexlib2.util.MethodUtil
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/VideoInformation;"
private const val EXTENSION_PLAYER_INTERFACE =
"Lapp/revanced/extension/youtube/patches/VideoInformation${'$'}PlaybackController;"
"Lapp/revanced/extension/youtube/patches/VideoInformation\$PlaybackController;"
private const val EXTENSION_VIDEO_QUALITY_MENU_INTERFACE =
"Lapp/revanced/extension/youtube/patches/VideoInformation\$VideoQualityMenuInterface;"
private lateinit var playerInitMethod: MutableMethod
private var playerInitInsertIndex = -1
@@ -83,7 +85,6 @@ val videoInformationPatch = bytecodePatch(
)
execute {
playerInitMethod = playerInitFingerprint.classDef.methods.first { MethodUtil.isConstructor(it) }
// Find the location of the first invoke-direct call and extract the register storing the 'this' object reference.
@@ -93,9 +94,6 @@ val videoInformationPatch = bytecodePatch(
playerInitInsertRegister = playerInitMethod.getInstruction<FiveRegisterInstruction>(initThisIndex).registerC
playerInitInsertIndex = initThisIndex + 1
// Hook the player controller for use through the extension.
onCreateHook(EXTENSION_CLASS_DESCRIPTOR, "initialize")
val seekFingerprintResultMethod = seekFingerprint.match(playerInitFingerprint.originalClassDef).method
val seekRelativeFingerprintResultMethod =
seekRelativeFingerprint.match(playerInitFingerprint.originalClassDef).method
@@ -263,7 +261,7 @@ val videoInformationPatch = bytecodePatch(
// Handle new playback speed menu.
playbackSpeedMenuSpeedChangedFingerprint.match(
newVideoQualityChangedFingerprint.originalClassDef,
videoQualityChangedFingerprint.originalClassDef,
).method.apply {
val index = indexOfFirstInstructionOrThrow(Opcode.IGET)
@@ -272,6 +270,131 @@ val videoInformationPatch = bytecodePatch(
speedSelectionValueRegister = getInstruction<TwoRegisterInstruction>(index).registerA
}
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
"""
)
}
)
}
}
// Detect video quality changes and override the current quality.
setVideoQualityFingerprint.match(
videoQualitySetterFingerprint.originalClassDef
).let { match ->
// This instruction refers to the field with the type that contains the setQuality method.
val onItemClickListenerClassReference = match.method
.getInstruction<ReferenceInstruction>(0).reference
val setQualityFieldReference = match.method
.getInstruction<ReferenceInstruction>(1).reference 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)
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
}
addInstructions(
0,
"""
invoke-virtual { p0, p1 }, $setQualityMenuIndexMethod
return-void
"""
)
}
)
}
videoQualitySetterFingerprint.method.addInstructions(
0,
"""
# Get object instance to invoke setQuality method.
iget-object v0, p0, $onItemClickListenerClassReference
iget-object v0, v0, $setQualityFieldReference
invoke-static { p1, v0, p2 }, $EXTENSION_CLASS_DESCRIPTOR->setVideoQuality([$YOUTUBE_VIDEO_QUALITY_CLASS_TYPE${EXTENSION_VIDEO_QUALITY_MENU_INTERFACE}I)I
move-result p2
"""
)
}
onCreateHook(EXTENSION_CLASS_DESCRIPTOR, "initialize")
videoSpeedChangedHook(EXTENSION_CLASS_DESCRIPTOR, "videoSpeedChanged")
userSelectedPlaybackSpeedHook(EXTENSION_CLASS_DESCRIPTOR, "userSelectedPlaybackSpeed")
}
@@ -282,8 +405,8 @@ private fun addSeekInterfaceMethods(targetClass: MutableClass, seekToMethod: Met
targetClass.interfaces.add(EXTENSION_PLAYER_INTERFACE)
arrayOf(
Triple(seekToMethod, "seekTo", true),
Triple(seekToRelativeMethod, "seekToRelative", false),
Triple(seekToMethod, "patch_seekTo", true),
Triple(seekToRelativeMethod, "patch_seekToRelative", false),
).forEach { (method, name, returnsBoolean) ->
// Add interface method.
// Get enum type for the seek helper method.

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,36 +5,25 @@ import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
/**
* Matches with the class found in [videoQualitySetterFingerprint].
*/
internal val setQualityByIndexMethodClassFieldReferenceFingerprint = fingerprint {
returns("V")
parameters("L")
opcodes(
Opcode.IGET_OBJECT,
Opcode.IPUT_OBJECT,
Opcode.IGET_OBJECT,
)
}
internal val videoQualityItemOnClickParentFingerprint = fingerprint {
returns("V")
strings("VIDEO_QUALITIES_MENU_BOTTOM_SHEET_FRAGMENT")
}
internal val videoQualitySetterFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
/**
* Resolves to class found in [videoQualityItemOnClickFingerprint].
*/
internal val videoQualityItemOnClickFingerprint = fingerprint {
returns("V")
parameters("[L", "I", "Z")
opcodes(
Opcode.IF_EQZ,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.IPUT_BOOLEAN,
parameters(
"Landroid/widget/AdapterView;",
"Landroid/view/View;",
"I",
"J"
)
strings("menu_item_video_quality")
custom { method, _ ->
method.name == "onItemClick"
}
}

View File

@@ -1,9 +1,7 @@
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.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
@@ -12,12 +10,10 @@ 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.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch;"
@@ -61,81 +57,27 @@ 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")
// Inject a call to set the remembered quality once a video loads.
setQualityByIndexMethodClassFieldReferenceFingerprint.match(
videoQualitySetterFingerprint.originalClassDef,
).let { match ->
// This instruction refers to the field with the type that contains the setQualityByIndex method.
val instructions = match.method.implementation!!.instructions
// 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"
)
val getOnItemClickListenerClassReference =
(instructions.elementAt(0) as ReferenceInstruction).reference
val getSetQualityByIndexMethodClassFieldReference =
(instructions.elementAt(1) as ReferenceInstruction).reference
val setQualityByIndexMethodClassFieldReference =
getSetQualityByIndexMethodClassFieldReference as FieldReference
val setQualityByIndexMethodClass = classes
.find { classDef -> classDef.type == setQualityByIndexMethodClassFieldReference.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")
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 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
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 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

@@ -0,0 +1,56 @@
package app.revanced.patches.youtube.video.quality
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.util.ResourceGroup
import app.revanced.util.copyResources
private val videoQualityButtonResourcePatch = resourcePatch {
dependsOn(playerControlsResourcePatch)
execute {
copyResources(
"qualitybutton",
ResourceGroup(
"drawable",
"revanced_video_quality_dialog_button_rectangle.xml",
),
)
addBottomControl("qualitybutton")
}
}
private const val QUALITY_BUTTON_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/videoplayer/VideoQualityDialogButton;"
val videoQualityDialogButtonPatch = 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.videoQualityDialogButtonPatch")
PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_video_quality_dialog_button"),
)
initializeBottomControl(QUALITY_BUTTON_CLASS_DESCRIPTOR)
injectVisibilityCheckCall(QUALITY_BUTTON_CLASS_DESCRIPTOR)
}
}

View File

@@ -19,6 +19,7 @@ val videoQualityPatch = bytecodePatch(
dependsOn(
rememberVideoQualityPatch,
advancedVideoQualityMenuPatch,
videoQualityDialogButtonPatch,
)
compatibleWith(

View File

@@ -9,6 +9,9 @@ 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.information.userSelectedPlaybackSpeedHook
import app.revanced.patches.youtube.video.information.videoInformationPatch
import app.revanced.patches.youtube.video.information.videoSpeedChangedHook
import app.revanced.patches.youtube.video.speed.custom.customPlaybackSpeedPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
@@ -21,8 +24,8 @@ private val playbackSpeedButtonResourcePatch = resourcePatch {
"speedbutton",
ResourceGroup(
"drawable",
"revanced_playback_speed_dialog_button.xml",
),
"revanced_playback_speed_dialog_button_rectangle.xml"
)
)
addBottomControl("speedbutton")
@@ -42,6 +45,7 @@ val playbackSpeedButtonPatch = bytecodePatch(
customPlaybackSpeedPatch,
playbackSpeedButtonResourcePatch,
playerControlsPatch,
videoInformationPatch,
)
execute {
@@ -53,5 +57,8 @@ val playbackSpeedButtonPatch = bytecodePatch(
initializeBottomControl(SPEED_BUTTON_CLASS_DESCRIPTOR)
injectVisibilityCheckCall(SPEED_BUTTON_CLASS_DESCRIPTOR)
videoSpeedChangedHook(SPEED_BUTTON_CLASS_DESCRIPTOR, "videoSpeedChanged")
userSelectedPlaybackSpeedHook(SPEED_BUTTON_CLASS_DESCRIPTOR, "videoSpeedChanged")
}
}

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

@@ -222,6 +222,8 @@ Second \"item\" text"</string>
</patch>
<patch id="video.speed.button.playbackSpeedButtonPatch">
</patch>
<patch id="video.quality.button.videoQualityDialogButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">

View File

@@ -222,6 +222,8 @@ Second \"item\" text"</string>
</patch>
<patch id="video.speed.button.playbackSpeedButtonPatch">
</patch>
<patch id="video.quality.button.videoQualityDialogButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">

View File

@@ -693,6 +693,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_player_flyout_watch_in_vr_title">إخفاء المشاهدة في VR</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">تم إخفاء قائمة المشاهدة في الوضع الافتراضي</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_off">يتم عرض قائمة المشاهدة في الوضع الافتراضي</string>
<string name="revanced_hide_player_flyout_video_quality_title">إخفاء قائمة جودة الفيديو</string>
<string name="revanced_hide_player_flyout_video_quality_summary_on">قائمة جودة الفيديو مخفية</string>
<string name="revanced_hide_player_flyout_video_quality_summary_off">قائمة جودة الفيديو معروضة</string>
<string name="revanced_hide_player_flyout_video_quality_footer_title">إخفاء تذييل قائمة جودة الفيديو</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_on">تم إخفاء تذييل قائمة جودة الفيديو</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_off">يتم عرض تذييل قائمة جودة الفيديو</string>
@@ -1465,10 +1468,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.videoQualityDialogButtonPatch">
<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

@@ -224,6 +224,8 @@ Second \"item\" text"</string>
</patch>
<patch id="video.speed.button.playbackSpeedButtonPatch">
</patch>
<patch id="video.quality.button.videoQualityDialogButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">

View File

@@ -693,6 +693,9 @@ Audio trek seçimin göstərmək üçün \"Video axınları saxtalaşdır\"ı iO
<string name="revanced_hide_player_flyout_watch_in_vr_title">\"VR-da İzləni\" gizlət</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">VR menyusunda izləmə gizlidir</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_off">VR menyusunda izləmə göstərilir</string>
<string name="revanced_hide_player_flyout_video_quality_title">Video keyfiyyət menyusunu gizlət</string>
<string name="revanced_hide_player_flyout_video_quality_summary_on">Video keyfiyyət menyusu gizlidir</string>
<string name="revanced_hide_player_flyout_video_quality_summary_off">Video keyfiyyət menyusu görünür</string>
<string name="revanced_hide_player_flyout_video_quality_footer_title">Video keyfiyyət menyusu alt məlumatını gizlət</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_on">Video keyfiyyət menyusu alt məlumatı gizlidir</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_off">Video keyfiyyət menyusu alt məlumatı göstərilir</string>
@@ -1464,10 +1467,18 @@ 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.videoQualityDialogButtonPatch">
<string name="revanced_video_quality_dialog_button_title">Video keyfiyyəti düyməsini göstər</string>
<string name="revanced_video_quality_dialog_button_summary_on">Düymə görünür. Keyfiyyəti ilkin vəziyyətinə qaytarmaq üçün toxunub saxlayın</string>
<string name="revanced_video_quality_dialog_button_summary_off">Düymə görünmür</string>
</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

@@ -693,6 +693,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_player_flyout_watch_in_vr_title">Схаваць гадзіннік у VR</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">Меню прагляду ў VR схавана</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_off">Паказана меню \"Глядзець у VR\"</string>
<string name="revanced_hide_player_flyout_video_quality_title">Схаваць меню якасці відэа</string>
<string name="revanced_hide_player_flyout_video_quality_summary_on">Меню якасці відэа схавана</string>
<string name="revanced_hide_player_flyout_video_quality_summary_off">Меню якасці відэа паказана</string>
<string name="revanced_hide_player_flyout_video_quality_footer_title">Схаваць калонтытул меню якасці відэа</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_on">Ніжні калонтытул меню якасці відэа схаваны</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_off">Паказваецца ніжні калонтытул меню якасці відэа</string>
@@ -1466,10 +1469,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.videoQualityDialogButtonPatch">
<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

@@ -693,6 +693,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_player_flyout_watch_in_vr_title">Гледайте във VR</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">Менюто за гледане в VR е скрито</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_off">Менюто за гледане в VR се показва</string>
<string name="revanced_hide_player_flyout_video_quality_title">Скриване на менюто за качество на видеото</string>
<string name="revanced_hide_player_flyout_video_quality_summary_on">Менюто за качество на видеото е скрито</string>
<string name="revanced_hide_player_flyout_video_quality_summary_off">Менюто за качество на видеото е показано</string>
<string name="revanced_hide_player_flyout_video_quality_footer_title">Скриване на футъра на менюто за качество на видеото</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_on">Долният колонтитул на менюто за качество на видеото е скрит</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_off">Долният колонтитул на менюто за качество на видеото се показва</string>
@@ -1465,10 +1468,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.videoQualityDialogButtonPatch">
<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

@@ -689,6 +689,9 @@ MicroG-এর জন্য ব্যাটারি অপ্টিমাইজ
<string name="revanced_hide_player_flyout_watch_in_vr_title">ভিআর-এ ঘড়ি লুকান</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">ভিআর মেনুতে দেখুন লুকানো আছে</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_off">ভিআর মেনুতে দেখুন দেখানো হয়েছে</string>
<string name="revanced_hide_player_flyout_video_quality_title">ভিডিও গুণমান মেনু লুকান</string>
<string name="revanced_hide_player_flyout_video_quality_summary_on">ভিডিও মানের মেনু লুকানো আছে</string>
<string name="revanced_hide_player_flyout_video_quality_summary_off">ভিডিও মানের মেনু দেখানো হয়েছে</string>
<string name="revanced_hide_player_flyout_video_quality_footer_title">ভিডিও গুণমান মেনুর ফুটার লুকান</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_on">ভিডিও গুণমান মেনু ফুটার লুকানো আছে</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_off">ভিডিও গুণমান মেনু ফুটার দেখানো হচ্ছে</string>
@@ -1461,10 +1464,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.videoQualityDialogButtonPatch">
<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

@@ -222,6 +222,8 @@ Second \"item\" text"</string>
</patch>
<patch id="video.speed.button.playbackSpeedButtonPatch">
</patch>
<patch id="video.quality.button.videoQualityDialogButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">

View File

@@ -222,6 +222,8 @@ Second \"item\" text"</string>
</patch>
<patch id="video.speed.button.playbackSpeedButtonPatch">
</patch>
<patch id="video.quality.button.videoQualityDialogButtonPatch">
</patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch">
</patch>
<patch id="video.speed.remember.rememberPlaybackSpeedPatch">

View File

@@ -693,6 +693,9 @@ Chcete-li zobrazit nabídku zvukové stopy, změňte možnost „Zfalšovat stre
<string name="revanced_hide_player_flyout_watch_in_vr_title">Skrýt Sledovat ve VR</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">Menu Sledovat ve VR je skryto</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_off">Menu Sledovat ve VR je zobrazeno</string>
<string name="revanced_hide_player_flyout_video_quality_title">Skrýt nabídku kvality videa</string>
<string name="revanced_hide_player_flyout_video_quality_summary_on">Nabídka kvality videa je skryta</string>
<string name="revanced_hide_player_flyout_video_quality_summary_off">Nabídka kvality videa je zobrazena</string>
<string name="revanced_hide_player_flyout_video_quality_footer_title">Skrýt zápatí menu kvality videa</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_on">Zápatí menu kvality videa je skryto</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_off">Zápatí menu kvality videa je zobrazeno</string>
@@ -1465,10 +1468,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.videoQualityDialogButtonPatch">
<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

@@ -693,6 +693,9 @@ For at vise lydspormenuen skal du ændre \"Spoof videostream\" til iOS TV"</stri
<string name="revanced_hide_player_flyout_watch_in_vr_title">Skjul vagt i VR</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">Se i VR-menuen er skjult</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_off">Se i VR-menuen vises</string>
<string name="revanced_hide_player_flyout_video_quality_title">Skjul menu for videokvalitet</string>
<string name="revanced_hide_player_flyout_video_quality_summary_on">Videokvalitetsmenuen er skjult</string>
<string name="revanced_hide_player_flyout_video_quality_summary_off">Videokvalitetsmenuen vises</string>
<string name="revanced_hide_player_flyout_video_quality_footer_title">Skjul sidefod til videokvalitet</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_on">Videokvalitetsmenuens sidefod er skjult</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_off">Videokvalitet menu footer er vist</string>
@@ -1467,10 +1470,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.videoQualityDialogButtonPatch">
<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

@@ -688,6 +688,9 @@ Um das Audiotrack-Menü anzuzeigen, ändere \"Video-Streams fälschen\" zu iOS T
<string name="revanced_hide_player_flyout_watch_in_vr_title">Überwachung in VR ausblenden</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">Im VR-Menü beobachten ist ausgeblendet</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_off">Im VR-Menü beobachten wird angezeigt</string>
<string name="revanced_hide_player_flyout_video_quality_title">Videoqualitätsmenü ausblenden</string>
<string name="revanced_hide_player_flyout_video_quality_summary_on">Videomenü ist ausgeblendet</string>
<string name="revanced_hide_player_flyout_video_quality_summary_off">Videomenü ist sichtbar</string>
<string name="revanced_hide_player_flyout_video_quality_footer_title">Videoqualitätsmenüfußzeile ausblenden</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_on">Video-Qualität Menü-Fußzeile ist ausgeblendet</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_off">Video-Qualität Menü-Fußzeile wird angezeigt</string>
@@ -1460,10 +1463,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.videoQualityDialogButtonPatch">
<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

@@ -695,6 +695,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_player_flyout_watch_in_vr_title">Μενού «Προβολή σε VR»</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">Κρυμμένο</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_off">Εμφανίζεται</string>
<string name="revanced_hide_player_flyout_video_quality_title">Μενού «Ποιότητα»</string>
<string name="revanced_hide_player_flyout_video_quality_summary_on">Κρυμμένο</string>
<string name="revanced_hide_player_flyout_video_quality_summary_off">Εμφανίζεται</string>
<string name="revanced_hide_player_flyout_video_quality_footer_title">Οδηγίες του μενού ποιότητας βίντεο</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_on">Κρυμμένες</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_off">Εμφανίζονται</string>
@@ -904,7 +907,7 @@ Second \"item\" text"</string>
<!-- Toast shown if network connection times out. Translations of this should not be longer than the original English or the text can be clipped and not entirely shown. -->
<string name="revanced_ryd_failure_connection_timeout">Dislike προσωρινά μη διαθέσιμα (καθυστέρηση API)</string>
<string name="revanced_ryd_failure_connection_status_code">Δεδομένα dislike μη διαθέσιμα (κατάσταση %d)</string>
<string name="revanced_ryd_failure_client_rate_limit_requested">Τα dislikes δεν είναι διαθέσιμα (όριο API πελάτη)</string>
<string name="revanced_ryd_failure_client_rate_limit_requested">Δεδομένα dislike μη διαθέσιμα (όριο API πελάτη)</string>
<string name="revanced_ryd_failure_generic">Δεδομένα dislike μη διαθέσιμα (%s)</string>
<!-- Toast shown if the user enables RYD while a video is opened, and then tries to vote for the video. -->
<string name="revanced_ryd_failure_ryd_enabled_while_playing_video_then_user_voted">Επαναφορτώστε το βίντεο για να ψηφίσετε χρησιμοποιώντας το Return YouTube Dislike</string>
@@ -1149,7 +1152,7 @@ Second \"item\" text"</string>
<string name="revanced_sb_new_segment_edit_by_hand_parse_error">Δόθηκε μη έγκυρος χρόνος</string>
<string name="revanced_sb_stats_title">Στατιστικά</string>
<!-- Shown in the settings preferences, and translations can be any text length. -->
<string name="revanced_sb_stats_connection_failure">Τα στατιστικά είναι προσωρινά μη διαθέσιμα (το API είναι εκτός λειτουργίας)</string>
<string name="revanced_sb_stats_connection_failure">Στατιστικά προσωρινά μη διαθέσιμα (API εκτός λειτουργίας)</string>
<string name="revanced_sb_stats_loading">Φόρτωση...</string>
<string name="revanced_sb_stats_sb_disabled">Το SponsorBlock είναι απενεργοποιημένο</string>
<string name="revanced_sb_stats_username">Το όνομα χρήστη σας: &lt;b&gt;%s&lt;/b&gt;</string>
@@ -1362,8 +1365,8 @@ Second \"item\" text"</string>
<string name="revanced_alt_thumbnail_stills_time_entry_2">Μέση του βίντεο</string>
<string name="revanced_alt_thumbnail_stills_time_entry_3">Τέλος του βίντεο</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="revanced_alt_thumbnail_dearrow_error">Το DeArrow είναι προσωρινά μη διαθέσιμο. (κωδικός κατάστασης: %s)</string>
<string name="revanced_alt_thumbnail_dearrow_error_generic">Το DeArrow είναι προσωρινά μη διαθέσιμο</string>
<string name="revanced_alt_thumbnail_dearrow_error">DeArrow προσωρινά μη διαθέσιμο (κωδικός: %s)</string>
<string name="revanced_alt_thumbnail_dearrow_error_generic">DeArrow προσωρινά μη διαθέσιμο</string>
</patch>
<patch id="misc.announcements.announcementsPatch">
<string name="revanced_announcements_title">Εμφάνιση ανακοινώσεων ReVanced</string>
@@ -1464,10 +1467,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.videoQualityDialogButtonPatch">
<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

@@ -90,7 +90,7 @@ Toca el botón continuar y permite los cambios de optimización."</string>
<string name="revanced_settings_screen_00_about_title">Acerca de</string>
<string name="revanced_settings_screen_01_ads_title">Anuncios</string>
<string name="revanced_settings_screen_02_alt_thumbnails_title">Miniaturas alternativas</string>
<string name="revanced_settings_screen_03_feed_title">Fuente</string>
<string name="revanced_settings_screen_03_feed_title">Feed</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 de progreso</string>
@@ -126,9 +126,9 @@ 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 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_title">Mostrar aviso de error en ReVanced</string>
<string name="revanced_debug_toast_on_error_summary_on">Se mostra un aviso si se produce un error</string>
<string name="revanced_debug_toast_on_error_summary_off">No se mostra un aviso 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>
@@ -336,9 +336,9 @@ Si un doodle se está mostrando actualmente en tu región y este ajuste de ocult
<string name="revanced_hide_comments_create_a_short_button_title">Ocultar botón \'Crear un Short\'</string>
<string name="revanced_hide_comments_create_a_short_button_summary_on">El botón Crear un Short está oculto</string>
<string name="revanced_hide_comments_create_a_short_button_summary_off">Se muestra el botón Crear un Short</string>
<string name="revanced_hide_comments_preview_comment_title">Ocultar comentario de vista previa</string>
<string name="revanced_hide_comments_preview_comment_summary_on">El comentario de la vista previa está oculto</string>
<string name="revanced_hide_comments_preview_comment_summary_off">Vista previa del comentario se muestra</string>
<string name="revanced_hide_comments_preview_comment_title">Ocultar vista previa de comentarios</string>
<string name="revanced_hide_comments_preview_comment_summary_on">La vista previa de comentarios está oculta</string>
<string name="revanced_hide_comments_preview_comment_summary_off">La vista previa de comentarios es visible</string>
<string name="revanced_hide_comments_thanks_button_title">Ocultar botón Gracias</string>
<string name="revanced_hide_comments_thanks_button_summary_on">El botón de gracias está oculto</string>
<string name="revanced_hide_comments_thanks_button_summary_off">Se muestra el botón de gracias</string>
@@ -357,14 +357,14 @@ Si un doodle se está mostrando actualmente en tu región y este ajuste de ocult
<string name="revanced_hide_keyword_content_screen_title">Ocultar contenido de palabra clave</string>
<string name="revanced_hide_keyword_content_screen_summary">Ocultar videos de búsqueda y feed usando filtros de palabras clave</string>
<string name="revanced_hide_keyword_content_home_title">Ocultar videos domésticos por palabras clave</string>
<string name="revanced_hide_keyword_content_home_summary_on">Los videos en la pestaña de inicio son filtrados por palabras clave</string>
<string name="revanced_hide_keyword_content_home_summary_off">Los videos en la pestaña de inicio no son filtrados por palabras clave</string>
<string name="revanced_hide_keyword_content_home_summary_on">Los videos en la pestaña \'Inicio\' son filtrados por palabras clave</string>
<string name="revanced_hide_keyword_content_home_summary_off">Los videos en la pestaña \'Inicio\' no son filtrados por palabras clave</string>
<string name="revanced_hide_keyword_content_search_title">Ocultar resultados de búsqueda por palabras clave</string>
<string name="revanced_hide_keyword_content_search_summary_on">Los resultados de la búsqueda son filtrados por palabras clave</string>
<string name="revanced_hide_keyword_content_search_summary_off">Los resultados de la búsqueda no son filtrados por palabras clave</string>
<string name="revanced_hide_keyword_content_subscriptions_title">Ocultar vídeos de suscripción por palabras clave</string>
<string name="revanced_hide_keyword_content_subscriptions_summary_on">Los videos en la pestaña de suscripciones son filtrados por palabras clave</string>
<string name="revanced_hide_keyword_content_subscriptions_summary_off">Los videos en la pestaña de suscripciones no son filtrados por palabras clave</string>
<string name="revanced_hide_keyword_content_subscriptions_summary_on">Los videos en la pestaña \'Suscripciones\' son filtrados por palabras clave</string>
<string name="revanced_hide_keyword_content_subscriptions_summary_off">Los videos en la pestaña \'Suscripciones\' no son filtrados por palabras clave</string>
<string name="revanced_hide_keyword_content_phrases_title">Palabras clave a ocultar</string>
<!-- For localization, it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
@@ -388,7 +388,7 @@ Limitaciones
<string name="revanced_hide_keyword_toast_invalid_common_whole_word_required">Añadir comillas para usar palabra clave: %s</string>
<string name="revanced_hide_keyword_toast_invalid_conflicting">La palabra clave tiene declaraciones conflictivas: %s</string>
<string name="revanced_hide_keyword_toast_invalid_length">La palabra clave es demasiado corta y requiere comillas: %s</string>
<string name="revanced_hide_keyword_toast_invalid_broad">Palabra clave ocultará todos los vídeos: %s</string>
<string name="revanced_hide_keyword_toast_invalid_broad">La palabra clave ocultará todos los vídeos: %s</string>
</patch>
<patch id="ad.general.hideAdsResourcePatch">
<string name="revanced_hide_creator_store_shelf_title">Ocultar estantería de la tienda del creador</string>
@@ -403,7 +403,7 @@ Limitaciones
Esta función solo está disponible para dispositivos antiguos"</string>
<string name="revanced_hide_fullscreen_ads_summary_off">Se muestran anuncios a pantalla completa</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="revanced_hide_fullscreen_ads_feature_not_available_toast">Ocultar anuncio solo con dispositivos viejos</string>
<string name="revanced_hide_fullscreen_ads_feature_not_available_toast">Ocultar anuncios a pantalla completa solo funciona con dispositivos antiguos</string>
<string name="revanced_hide_general_ads_title">Ocultar anuncios generales</string>
<string name="revanced_hide_general_ads_summary_on">Los anuncios generales están ocultos</string>
<string name="revanced_hide_general_ads_summary_off">Se muestran anuncios generales</string>
@@ -447,10 +447,10 @@ Esta función solo está disponible para dispositivos antiguos"</string>
<string name="revanced_copy_video_url_timestamp_summary_off">El botón no se muestra</string>
</patch>
<patch id="interaction.dialog.removeViewerDiscretionDialogPatch">
<string name="revanced_remove_viewer_discretion_dialog_title">Eliminar diálogo de discreción del visor</string>
<string name="revanced_remove_viewer_discretion_dialog_summary_on">Se eliminará el diálogo</string>
<string name="revanced_remove_viewer_discretion_dialog_title">Eliminar diálogo de discreción del espectador</string>
<string name="revanced_remove_viewer_discretion_dialog_summary_on">Se quitará el diálogo</string>
<string name="revanced_remove_viewer_discretion_dialog_summary_off">Se mostrará el diálogo</string>
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">Esto no pasa por alto la restricción de edad, sino que simplemente la acepta automáticamente.</string>
<string name="revanced_remove_viewer_discretion_dialog_user_dialog_message">Esto no evita la restricción de edad. Solo la acepta automáticamente.</string>
</patch>
<patch id="interaction.doubletap.disableDoubleTapActionsPatch">
<string name="revanced_disable_chapter_skip_double_tap_title">Deshabilitar el salto de capítulo con doble toque</string>
@@ -516,7 +516,7 @@ Ajusta el volumen deslizando verticalmente en el lado derecho de la pantalla"</s
<string name="revanced_swipe_overlay_timeout_summary">La cantidad de milisegundos que la superposición es visible</string>
<string name="revanced_swipe_overlay_background_opacity_title">Opacidad del fondo de la superposición de deslizamiento</string>
<string name="revanced_swipe_overlay_background_opacity_summary">Valor de opacidad entre 0-100</string>
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">La opacidad de la superposición de deslizamiento debe estar entre 0 y 100</string>
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">La opacidad de superposición de deslizamiento debe ser de entre 0-100</string>
<string name="revanced_swipe_overlay_progress_brightness_color_title">Color del brillo de la superposición de deslizamiento</string>
<string name="revanced_swipe_overlay_progress_brightness_color_summary">El color de la barra de progreso para los controles de brillo</string>
<string name="revanced_swipe_overlay_progress_volume_color_title">Color del volumen de la superposición de deslizamiento</string>
@@ -597,11 +597,11 @@ Ajusta el volumen deslizando verticalmente en el lado derecho de la pantalla"</s
<string name="revanced_navigation_buttons_screen_title">Botones de navegación</string>
<string name="revanced_navigation_buttons_screen_summary">Ocultar o cambiar botones en la barra de navegación</string>
<!-- 'Home' should be translated using the same localized wording YouTube displays for the tab. -->
<string name="revanced_hide_home_button_title">Ocultar Principal</string>
<string name="revanced_hide_home_button_title">Ocultar pestaña \'Inicio\'</string>
<string name="revanced_hide_home_button_summary_on">El botón de inicio está oculto</string>
<string name="revanced_hide_home_button_summary_off">El botón de inicio es visible</string>
<!-- 'Shorts' should be translated using the same localized wording YouTube displays the tab. -->
<string name="revanced_hide_shorts_button_title">Ocultar Shorts</string>
<string name="revanced_hide_shorts_button_title">Ocultar pestaña \'Shorts\'</string>
<string name="revanced_hide_shorts_button_summary_on">El botón de Shorts está oculto</string>
<string name="revanced_hide_shorts_button_summary_off">El botón de Shorts es visible</string>
<!-- 'Create' has no display name. Translate normally. -->
@@ -609,34 +609,34 @@ Ajusta el volumen deslizando verticalmente en el lado derecho de la pantalla"</s
<string name="revanced_hide_create_button_summary_on">El botón Crear está oculto</string>
<string name="revanced_hide_create_button_summary_off">Se muestra el botón Crear</string>
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays the tab. -->
<string name="revanced_hide_subscriptions_button_title">Ocultar Suscripciones</string>
<string name="revanced_hide_subscriptions_button_title">Ocultar pestaña \'Suscripciones\'</string>
<string name="revanced_hide_subscriptions_button_summary_on">El botón Suscripciones está oculto</string>
<string name="revanced_hide_subscriptions_button_summary_off">Se muestra el botón Suscripciones</string>
<string name="revanced_hide_notifications_button_title">Ocultar notificaciones</string>
<string name="revanced_hide_notifications_button_summary_on">El botón de notificaciones está oculto</string>
<string name="revanced_hide_notifications_button_summary_off">Se muestra el botón de notificaciones</string>
<string name="revanced_hide_notifications_button_title">Ocultar Notificaciones</string>
<string name="revanced_hide_notifications_button_summary_on">El botón de Notificaciones está oculto</string>
<string name="revanced_hide_notifications_button_summary_off">Se muestra el botón de Notificaciones</string>
<!-- 'Notifications' should be translated using the same localized wording YouTube displays the tab. -->
<string name="revanced_switch_create_with_notifications_button_title">Cambiar Crear con Notificaciones</string>
<string name="revanced_switch_create_with_notifications_button_title">Cambiar botón Crear con el de Notificaciones</string>
<string name="revanced_switch_create_with_notifications_button_summary_on">"El botón Crear se cambia por el botón Notificaciones
Nota: Habilitar esto también oculta a la fuerza los anuncios de vídeo"</string>
Nota: Habilitar esto también ocultará forzosamente los anuncios de video"</string>
<string name="revanced_switch_create_with_notifications_button_summary_off">El botón Crear no se cambia con el botón de notificaciones</string>
<string name="revanced_switch_create_with_notifications_button_user_dialog_message">"Deshabilitar este ajuste también deshabilitará el bloqueo de anuncios de Shorts.
Si cambiar este ajuste no tiene efecto, intenta cambiar al modo incógnito."</string>
<string name="revanced_hide_navigation_button_labels_title">Ocultar etiquetas de botón de navegación</string>
<string name="revanced_hide_navigation_button_labels_title">Ocultar etiquetas de navegación</string>
<string name="revanced_hide_navigation_button_labels_summary_on">Las etiquetas están ocultas</string>
<string name="revanced_hide_navigation_button_labels_summary_off">Las etiquetas se muestran</string>
<string name="revanced_hide_navigation_button_labels_summary_off">Las etiquetas están visibles</string>
<string name="revanced_disable_translucent_status_bar_title">Desactivar la barra de estado translúcida</string>
<string name="revanced_disable_translucent_status_bar_summary_on">La barra de estado es opaca</string>
<string name="revanced_disable_translucent_status_bar_summary_off">La barra de estado es opaca o translúcida</string>
<string name="revanced_disable_translucent_status_bar_user_dialog_message">En algunos dispositivos, habilitar esta función puede cambiar la barra de navegación del sistema a transparente.</string>
<string name="revanced_disable_translucent_navigation_bar_light_title">Desactivar la barra translúcida clara</string>
<string name="revanced_disable_translucent_navigation_bar_light_summary_on">La barra de navegación en modo claro es opaca</string>
<string name="revanced_disable_translucent_navigation_bar_light_summary_off">La barra de navegación del modo claro es opaca o translúcida</string>
<string name="revanced_disable_translucent_navigation_bar_light_summary_off">La barra de navegación en modo claro es opaca o translúcida</string>
<string name="revanced_disable_translucent_navigation_bar_dark_title">Desactivar la barra translúcida oscura</string>
<string name="revanced_disable_translucent_navigation_bar_dark_summary_on">La barra de navegación en modo oscuro es opaca</string>
<string name="revanced_disable_translucent_navigation_bar_dark_summary_off">La barra de navegación del modo oscuro es opaca o translúcida</string>
<string name="revanced_disable_translucent_navigation_bar_dark_summary_off">La barra de navegación en modo oscuro es opaca o translúcida</string>
</patch>
<patch id="layout.hide.player.flyoutmenupanel.hidePlayerFlyoutMenuPatch">
<string name="revanced_hide_player_flyout_title">Menú desplegable</string>
@@ -661,8 +661,8 @@ Si cambiar este ajuste no tiene efecto, intenta cambiar al modo incógnito."</st
<string name="revanced_hide_player_flyout_ambient_mode_title">Ocultar Modo ambiente</string>
<string name="revanced_hide_player_flyout_ambient_mode_summary_on">Menú de Modo ambiente oculto</string>
<string name="revanced_hide_player_flyout_ambient_mode_summary_off">Se muestra el menú de Modo ambiente</string>
<string name="revanced_hide_player_flyout_stable_volume_title">Ocultar volumen estable</string>
<string name="revanced_hide_player_flyout_stable_volume_summary_off">Se muestra el menú de volumen estable</string>
<string name="revanced_hide_player_flyout_stable_volume_title">Ocultar \'Volumen estable\'</string>
<string name="revanced_hide_player_flyout_stable_volume_summary_off">El menú de volumen estable es visible</string>
<string name="revanced_hide_player_flyout_stable_volume_summary_on">El menú de volumen estable está oculto</string>
<!-- 'Help & feedback' should be translated using the same localized wording YouTube displays for the menu item. -->
<string name="revanced_hide_player_flyout_help_title">Ocultar Ayuda &amp; comentarios</string>
@@ -693,6 +693,9 @@ Para mostrar el menú de la pista de audio, cambia \"Suplantar transmisiones de
<string name="revanced_hide_player_flyout_watch_in_vr_title">Ocultar reloj en VR</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">Ver en el menú VR está oculto</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_off">Ver en el menú VR se muestra</string>
<string name="revanced_hide_player_flyout_video_quality_title">Ocultar menú de calidad de vídeo</string>
<string name="revanced_hide_player_flyout_video_quality_summary_on">El menú de calidad de video está oculto</string>
<string name="revanced_hide_player_flyout_video_quality_summary_off">El menú de calidad de video está visible</string>
<string name="revanced_hide_player_flyout_video_quality_footer_title">Ocultar pie de página del menú de calidad de vídeo</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_on">Pie de menú de calidad de vídeo oculto</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_off">El pie del menú de calidad de vídeo se muestra</string>
@@ -748,14 +751,14 @@ Para mostrar el menú de la pista de audio, cambia \"Suplantar transmisiones de
<string name="revanced_shorts_player_screen_title">Reproductor de Shorts</string>
<string name="revanced_shorts_player_screen_summary">Ocultar o mostrar componentes del reproductor de Shorts</string>
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
<string name="revanced_hide_shorts_home_title">Ocultar Shorts en la página principal</string>
<string name="revanced_hide_shorts_home_title">Ocultar Shorts en la pestaña de Inicio</string>
<string name="revanced_hide_shorts_home_summary_on">Oculto en la página principal y vídeos relacionados</string>
<string name="revanced_hide_shorts_home_summary_off">Visible en la página principal y vídeos relacionados</string>
<string name="revanced_hide_shorts_search_title">Ocultar Shorts en los resultados de búsqueda</string>
<string name="revanced_hide_shorts_search_summary_on">Ocultos en los resultados de búsqueda</string>
<string name="revanced_hide_shorts_search_summary_off">Mostrar en los resultados de búsqueda</string>
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
<string name="revanced_hide_shorts_subscriptions_title">Ocultar Shorts en el feed de Suscripciones</string>
<string name="revanced_hide_shorts_subscriptions_title">Ocultar Shorts en la pestaña de Suscripciones</string>
<string name="revanced_hide_shorts_subscriptions_summary_on">Oculto en el feed de Suscripciones</string>
<string name="revanced_hide_shorts_subscriptions_summary_off">Visible en el feed de Suscripciones</string>
<string name="revanced_hide_shorts_history_title">Ocultar Shorts en el historial de visualización</string>
@@ -786,9 +789,9 @@ Para mostrar el menú de la pista de audio, cambia \"Suplantar transmisiones de
<string name="revanced_hide_shorts_paused_overlay_buttons_title">Ocultar botones de superposición en pausa</string>
<string name="revanced_hide_shorts_paused_overlay_buttons_summary_on">Los botones de superposición pausados están ocultos</string>
<string name="revanced_hide_shorts_paused_overlay_buttons_summary_off">Se muestran los botones de superposición pausados</string>
<string name="revanced_hide_shorts_preview_comment_title">Ocultar comentario de vista previa</string>
<string name="revanced_hide_shorts_preview_comment_summary_on">El comentario de vista previa está oculto</string>
<string name="revanced_hide_shorts_preview_comment_summary_off">Se muestra la vista previa del comentario</string>
<string name="revanced_hide_shorts_preview_comment_title">Ocultar vista previa de comentarios</string>
<string name="revanced_hide_shorts_preview_comment_summary_on">La vista previa de comentarios está oculta</string>
<string name="revanced_hide_shorts_preview_comment_summary_off">La vista previa de comentarios es visible</string>
<string name="revanced_hide_shorts_save_sound_button_title">Ocultar el botón Guardar música</string>
<string name="revanced_hide_shorts_save_sound_button_summary_on">El botón Guardar música está oculto</string>
<string name="revanced_hide_shorts_save_sound_button_summary_off">Mostrar el botón de guardar música</string>
@@ -855,8 +858,8 @@ Para mostrar el menú de la pista de audio, cambia \"Suplantar transmisiones de
<string name="revanced_hide_shorts_full_video_link_label_summary_on">Etiqueta de enlace de vídeo oculto</string>
<string name="revanced_hide_shorts_full_video_link_label_summary_off">Etiqueta de enlace de vídeo mostrada</string>
<string name="revanced_hide_shorts_navigation_bar_title">Ocultar barra de navegación</string>
<string name="revanced_hide_shorts_navigation_bar_summary_on">Barra de navegación oculta</string>
<string name="revanced_hide_shorts_navigation_bar_summary_off">Se muestra la barra de navegación</string>
<string name="revanced_hide_shorts_navigation_bar_summary_on">La barra de navegación está oculta</string>
<string name="revanced_hide_shorts_navigation_bar_summary_off">La barra de navegación está visible</string>
</patch>
<patch id="layout.hide.endscreensuggestion.hideEndScreenSuggestedVideoPatch">
<string name="revanced_end_screen_suggested_video_title">Ocultar el vídeo sugerido de la pantalla final</string>
@@ -1057,7 +1060,7 @@ Tu ID de usuario es como una contraseña y nunca debe compartirse."</string>
<string name="revanced_sb_segments_intro_sum">Un intervalo sin contenido real. Podría ser una pausa, marco estático o animación de repetición. No incluye transiciones que contengan información</string>
<string name="revanced_sb_segments_outro">Tarjetas finales / Créditos</string>
<string name="revanced_sb_segments_outro_sum">Créditos o cuando aparecen las tarjetas finales de YouTube. No para conclusiones con información</string>
<string name="revanced_sb_segments_preview">Vista previa/Recapitular/Juego</string>
<string name="revanced_sb_segments_preview">Vista previa/Resumen/Enganche</string>
<string name="revanced_sb_segments_preview_sum">Colección de clips que muestran lo que viene o lo que pasó en el vídeo o en otros videos de una serie, donde toda la información se repite en otro lugar</string>
<string name="revanced_sb_segments_filler">Bromas de relleno</string>
<string name="revanced_sb_segments_filler_sum">Escenas tangenciales añadidas sólo para relleno o humor que no están obligadas a entender el contenido principal del vídeo. No incluye segmentos proporcionando detalles de contexto o fondo</string>
@@ -1165,11 +1168,11 @@ Ya existe"</string>
<string name="revanced_sb_about_api_summary">Los datos son proporcionados por la API de SponsorBlock. Pulsa aquí para aprender más y ver las descargas para otras plataformas</string>
</patch>
<patch id="layout.formfactor.changeFormFactorPatch">
<string name="revanced_change_form_factor_title">Diseño del diseño factor</string>
<string name="revanced_change_form_factor_entry_1">Defecto</string>
<string name="revanced_change_form_factor_title">Factor de forma del diseño</string>
<string name="revanced_change_form_factor_entry_1">Predeterminado</string>
<string name="revanced_change_form_factor_entry_2">Teléfono</string>
<string name="revanced_change_form_factor_entry_3">Tableta</string>
<string name="revanced_change_form_factor_entry_4">Automotriz</string>
<string name="revanced_change_form_factor_entry_4">Automóvil</string>
<string name="revanced_change_form_factor_user_dialog_message">"Los cambios incluyen:
Diseño para tablets
@@ -1291,8 +1294,8 @@ El minireproductor se puede arrastrar fuera de la pantalla hacia la izquierda o
<string name="revanced_miniplayer_opacity_invalid_toast"> Opacidad de reproductor debe estar en 0 -100</string>
</patch>
<patch id="layout.theme.themePatch">
<string name="revanced_gradient_loading_screen_title">Activar la pantalla de carga del degradado</string>
<string name="revanced_gradient_loading_screen_summary_on">La pantalla de carga tendrá un fondo de degradado</string>
<string name="revanced_gradient_loading_screen_title">Activar la pantalla de carga gradiente</string>
<string name="revanced_gradient_loading_screen_summary_on">La pantalla de carga tendrá un fondo gradiente</string>
<string name="revanced_gradient_loading_screen_summary_off">La pantalla de carga tendrá un fondo sólido</string>
<string name="revanced_splash_screen_animation_style_title">Estilo de la pantalla de presentación</string>
<string name="revanced_splash_screen_animation_style_entry_1">Color</string>
@@ -1323,9 +1326,9 @@ Habilitar esto puede solucionar las imágenes que faltan que están bloqueadas e
</patch>
<patch id="layout.thumbnails.alternativeThumbnailsPatch">
<!-- 'Home' should be translated using the same localized wording YouTube displays for the Home tab. -->
<string name="revanced_alt_thumbnail_home_title">Pestaña</string>
<string name="revanced_alt_thumbnail_home_title">Pestaña de Inicio</string>
<!-- 'Subscriptions' should be translated using the same localized wording YouTube displays for the Subscriptions tab. -->
<string name="revanced_alt_thumbnail_subscription_title">Pestaña Suscripciones</string>
<string name="revanced_alt_thumbnail_subscription_title">Pestaña de Suscripciones</string>
<!-- 'You' should be translated using the same localized wording YouTube displays for the You (Library) tab. -->
<string name="revanced_alt_thumbnail_library_title">Pestaña</string>
<string name="revanced_alt_thumbnail_player_title">Listas de reproducción del reproductor &amp; recomendaciones</string>
@@ -1456,10 +1459,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.videoQualityDialogButtonPatch">
<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

@@ -693,6 +693,9 @@ Heliriba menüü kuvamiseks muutke valikut „Võltsitud videovoogedastus“ vä
<string name="revanced_hide_player_flyout_watch_in_vr_title">Peida Vaata VR-is</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_on">Vaata VR-is menüü on peidetud</string>
<string name="revanced_hide_player_flyout_watch_in_vr_summary_off">Vaata VR-is menüü on nähtav</string>
<string name="revanced_hide_player_flyout_video_quality_title">Peida videokvaliteedi menüü</string>
<string name="revanced_hide_player_flyout_video_quality_summary_on">Videokvaliteedi menüü on peidetud</string>
<string name="revanced_hide_player_flyout_video_quality_summary_off">Videokvaliteedi menüü on nähtav</string>
<string name="revanced_hide_player_flyout_video_quality_footer_title">Peida video kvaliteedi menüü jalg</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_on">Video kvaliteedi menüü jalg on peidetud</string>
<string name="revanced_hide_player_flyout_video_quality_footer_summary_off">Video kvaliteedi menüü jalg on nähtav</string>
@@ -1465,10 +1468,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.videoQualityDialogButtonPatch">
<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>

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