Compare commits

..

400 Commits

Author SHA1 Message Date
semantic-release-bot
ebb446b22a chore: Release v5.40.0 [skip ci]
# [5.40.0](https://github.com/ReVanced/revanced-patches/compare/v5.39.0...v5.40.0) (2025-09-21)

### Bug Fixes

* **Instagram - Limit feed to followed profiles:** Change patch to default off ([767f1e3](767f1e3695))
* **Spoof video streams:** Resolve occasional playback stuttering ([5c7c8b5](5c7c8b5364))
* **YouTube - Force original audio:** Show UI setting summary if spoofing to Android Studio ([b7026b7](b7026b7086))
* **YouTube - Spoof video streams:** Add "Force original audio" disclaimer for Android Studio client ([f97d332](f97d33206b))
* **YouTube - Spoof video streams:** Add stream audio selector disclaimer for Android Studio client ([a8a4107](a8a410708d))

### Features

* **Instagram:** Add `Limit feed to followed profiles` patch ([#5908](https://github.com/ReVanced/revanced-patches/issues/5908)) ([8ba9a19](8ba9a19ade))
* **Viber - Hide ads:** Support latest app target ([#5863](https://github.com/ReVanced/revanced-patches/issues/5863)) ([e6cce85](e6cce85541))
* **YouTube - Hide video action buttons:** Add "Hide comments" button ([db796fb](db796fb883))
* **YouTube Music:** Add `Enable debugging` patch ([#5939](https://github.com/ReVanced/revanced-patches/issues/5939)) ([418f594](418f5945c2))
* **YouTube Music:** Add `Hide cast button` and `Navigation bar` patches ([#5934](https://github.com/ReVanced/revanced-patches/issues/5934)) ([651d358](651d358096))
* **YouTube Music:** Support version `8.10.52` ([#5941](https://github.com/ReVanced/revanced-patches/issues/5941)) ([01c0f1b](01c0f1bd1a))
* **YouTube:** Support version `20.14.43` ([#5940](https://github.com/ReVanced/revanced-patches/issues/5940)) ([f7f4a1b](f7f4a1b0f0))
2025-09-21 06:58:02 +00:00
LisoUseInAIKyrios
b44a369f59 chore: Merge branch dev to main (#5916) 2025-09-21 10:54:28 +04:00
github-actions[bot]
092a72c774 chore: Sync translations (#5946) 2025-09-21 10:52:29 +04:00
LisoUseInAIKyrios
6330773bfc chore(YouTube Music): Add missing target version 2025-09-21 10:50:35 +04:00
LisoUseInAIKyrios
43dbb4710b docs: Add new issue links to the FAQ and troubleshooting guide (#5929) 2025-09-21 10:42:02 +04:00
LisoUseInAIKyrios
966727ca2d chore(YouTube Music): Use string language similar to YouTube 2025-09-20 23:31:01 +04:00
semantic-release-bot
1f371c8156 chore: Release v5.40.0-dev.11 [skip ci]
# [5.40.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.10...v5.40.0-dev.11) (2025-09-20)

### Bug Fixes

* **YouTube - Spoof video streams:** Add stream audio selector disclaimer for Android Studio client ([a8a4107](a8a410708d))
2025-09-20 19:20:13 +00:00
LisoUseInAIKyrios
a8a410708d fix(YouTube - Spoof video streams): Add stream audio selector disclaimer for Android Studio client 2025-09-20 23:15:41 +04:00
semantic-release-bot
7651ef0881 chore: Release v5.40.0-dev.10 [skip ci]
# [5.40.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.9...v5.40.0-dev.10) (2025-09-20)

### Bug Fixes

* **YouTube - Spoof video streams:** Add "Force original audio" disclaimer for Android Studio client ([f97d332](f97d33206b))
2025-09-20 18:13:06 +00:00
LisoUseInAIKyrios
f97d33206b fix(YouTube - Spoof video streams): Add "Force original audio" disclaimer for Android Studio client 2025-09-20 22:08:50 +04:00
semantic-release-bot
3d986e6716 chore: Release v5.40.0-dev.9 [skip ci]
# [5.40.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.8...v5.40.0-dev.9) (2025-09-20)

### Features

* **YouTube Music:** Support version `8.10.52` ([#5941](https://github.com/ReVanced/revanced-patches/issues/5941)) ([01c0f1b](01c0f1bd1a))
2025-09-20 16:12:57 +00:00
LisoUseInAIKyrios
01c0f1bd1a feat(YouTube Music): Support version 8.10.52 (#5941) 2025-09-20 20:09:52 +04:00
github-actions[bot]
4178e8a64f chore: Sync translations (#5943) 2025-09-20 20:09:07 +04:00
semantic-release-bot
7e1bb8f3c7 chore: Release v5.40.0-dev.8 [skip ci]
# [5.40.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.7...v5.40.0-dev.8) (2025-09-20)

### Features

* **YouTube:** Support version `20.14.43` ([#5940](https://github.com/ReVanced/revanced-patches/issues/5940)) ([f7f4a1b](f7f4a1b0f0))
2025-09-20 15:33:42 +00:00
LisoUseInAIKyrios
f7f4a1b0f0 feat(YouTube): Support version 20.14.43 (#5940) 2025-09-20 19:30:05 +04:00
semantic-release-bot
e89660d234 chore: Release v5.40.0-dev.7 [skip ci]
# [5.40.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.6...v5.40.0-dev.7) (2025-09-20)

### Features

* **YouTube - Hide video action buttons:** Add "Hide comments" button ([db796fb](db796fb883))
2025-09-20 15:03:00 +00:00
LisoUseInAIKyrios
db796fb883 feat(YouTube - Hide video action buttons): Add "Hide comments" button
Button is only shown when using YouTube 20.14+ and the video information area is collapsed to a compact state
2025-09-20 19:00:00 +04:00
LisoUseInAIKyrios
6bb8bad8d7 chore(YouTube Music): Fix fingerprint typo, change hide cast button to default off 2025-09-20 18:03:41 +04:00
semantic-release-bot
aa1fb41ad8 chore: Release v5.40.0-dev.6 [skip ci]
# [5.40.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.5...v5.40.0-dev.6) (2025-09-20)

### Features

* **YouTube Music:** Add `Enable debugging` patch ([#5939](https://github.com/ReVanced/revanced-patches/issues/5939)) ([418f594](418f5945c2))
2025-09-20 12:37:33 +00:00
LisoUseInAIKyrios
418f5945c2 feat(YouTube Music): Add Enable debugging patch (#5939) 2025-09-20 16:33:03 +04:00
github-actions[bot]
e26c971067 chore: Sync translations (#5942) 2025-09-20 16:32:50 +04:00
semantic-release-bot
eb1d07fd98 chore: Release v5.40.0-dev.5 [skip ci]
# [5.40.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.4...v5.40.0-dev.5) (2025-09-20)

### Features

* **YouTube Music:** Add `Hide cast button` and `Navigation bar` patches ([#5934](https://github.com/ReVanced/revanced-patches/issues/5934)) ([651d358](651d358096))
2025-09-20 11:30:04 +00:00
MarcaD
651d358096 feat(YouTube Music): Add Hide cast button and Navigation bar patches (#5934) 2025-09-20 15:26:14 +04:00
semantic-release-bot
0d15c5f338 chore: Release v5.40.0-dev.4 [skip ci]
# [5.40.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.3...v5.40.0-dev.4) (2025-09-20)

### Bug Fixes

* **Spoof video streams:** Resolve occasional playback stuttering ([5c7c8b5](5c7c8b5364))
2025-09-20 10:39:29 +00:00
LisoUseInAIKyrios
5c7c8b5364 fix(Spoof video streams): Resolve occasional playback stuttering
Code adapted from:
2cf9db66ac
50d9c60374
2025-09-20 14:36:15 +04:00
semantic-release-bot
729997ec3e chore: Release v5.40.0-dev.3 [skip ci]
# [5.40.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.2...v5.40.0-dev.3) (2025-09-19)

### Bug Fixes

* **Instagram - Limit feed to followed profiles:** Change patch to default off ([767f1e3](767f1e3695))
2025-09-19 15:43:08 +00:00
LisoUseInAIKyrios
767f1e3695 fix(Instagram - Limit feed to followed profiles): Change patch to default off
Co-authored-by: brosssh <44944126+brosssh@users.noreply.github.com>
2025-09-19 19:40:32 +04:00
github-actions[bot]
7857876551 chore: Sync translations (#5933) 2025-09-19 19:40:03 +04:00
semantic-release-bot
04057c6e56 chore: Release v5.40.0-dev.2 [skip ci]
# [5.40.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.40.0-dev.1...v5.40.0-dev.2) (2025-09-18)

### Features

* **Instagram:** Add `Limit feed to followed profiles` patch ([#5908](https://github.com/ReVanced/revanced-patches/issues/5908)) ([8ba9a19](8ba9a19ade))
2025-09-18 06:16:27 +00:00
brosssh
8ba9a19ade feat(Instagram): Add Limit feed to followed profiles patch (#5908) 2025-09-18 10:13:46 +04:00
LisoUseInAIKyrios
6862200a28 chore: Fix api dump 2025-09-17 23:42:11 +04:00
semantic-release-bot
dfff3d7c0a chore: Release v5.40.0-dev.1 [skip ci]
# [5.40.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.39.1-dev.1...v5.40.0-dev.1) (2025-09-17)

### Features

* **Viber - Hide ads:** Support latest app target ([#5863](https://github.com/ReVanced/revanced-patches/issues/5863)) ([e6cce85](e6cce85541))
2025-09-17 17:54:19 +00:00
Samo Hribar
e6cce85541 feat(Viber - Hide ads): Support latest app target (#5863) 2025-09-17 21:51:33 +04:00
github-actions[bot]
8502eb8eac chore: Sync translations (#5918) 2025-09-17 21:51:15 +04:00
semantic-release-bot
0652c56d0d chore: Release v5.39.1-dev.1 [skip ci]
## [5.39.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.39.0...v5.39.1-dev.1) (2025-09-17)

### Bug Fixes

* **YouTube - Force original audio:** Show UI setting summary if spoofing to Android Studio ([b7026b7](b7026b7086))
2025-09-17 16:18:22 +00:00
LisoUseInAIKyrios
b7026b7086 fix(YouTube - Force original audio): Show UI setting summary if spoofing to Android Studio 2025-09-17 20:13:44 +04:00
semantic-release-bot
fa4f422a15 chore: Release v5.39.0 [skip ci]
# [5.39.0](https://github.com/ReVanced/revanced-patches/compare/v5.38.0...v5.39.0) (2025-09-17)

### Bug Fixes

* **YouTube - Spoof video streams:** Do not use Android Creator for livestreams ([cbe576b](cbe576bc38))
* **YouTube - Spoof video streams:** Show Android Studio in spoof stream menu ([c9f741e](c9f741e616))
* **YouTube Music - Spoof video streams:** Remove iPadOS client ([7eeffd3](7eeffd3392))

### Features

* **YouTube - Hide video action buttons:** Add "Hide Shop button" setting ([a84db7b](a84db7be7f))
2025-09-17 09:15:36 +00:00
LisoUseInAIKyrios
38e0cbd724 chore: Merge branch dev to main (#5907) 2025-09-17 13:12:21 +04:00
semantic-release-bot
0bdebd927d chore: Release v5.39.0-dev.2 [skip ci]
# [5.39.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.39.0-dev.1...v5.39.0-dev.2) (2025-09-17)

### Bug Fixes

* **YouTube - Spoof video streams:** Show Android Studio in spoof stream menu ([c9f741e](c9f741e616))
2025-09-17 09:01:12 +00:00
github-actions[bot]
3eac25cf7f chore: Sync translations (#5914) 2025-09-17 12:56:47 +04:00
LisoUseInAIKyrios
c9f741e616 fix(YouTube - Spoof video streams): Show Android Studio in spoof stream menu 2025-09-17 12:54:52 +04:00
semantic-release-bot
cba44ccfc8 chore: Release v5.39.0-dev.1 [skip ci]
# [5.39.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.38.1-dev.2...v5.39.0-dev.1) (2025-09-17)

### Features

* **YouTube - Hide video action buttons:** Add "Hide Shop button" setting ([a84db7b](a84db7be7f))
2025-09-17 07:19:06 +00:00
LisoUseInAIKyrios
a84db7be7f feat(YouTube - Hide video action buttons): Add "Hide Shop button" setting 2025-09-17 11:14:24 +04:00
semantic-release-bot
2520129ace chore: Release v5.38.1-dev.2 [skip ci]
## [5.38.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.38.1-dev.1...v5.38.1-dev.2) (2025-09-16)

### Bug Fixes

* **YouTube Music - Spoof video streams:** Remove iPadOS client ([7eeffd3](7eeffd3392))
2025-09-16 21:49:36 +00:00
LisoUseInAIKyrios
7eeffd3392 fix(YouTube Music - Spoof video streams): Remove iPadOS client 2025-09-17 01:44:48 +04:00
LisoUseInAIKyrios
6c3391164e chore: Remove spoof stream data migration since iPadOS can cause 1 minute playback failure for users in some regions 2025-09-16 23:44:01 +04:00
semantic-release-bot
0b8b46c73e chore: Release v5.38.1-dev.1 [skip ci]
## [5.38.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.38.0...v5.38.1-dev.1) (2025-09-16)

### Bug Fixes

* **YouTube - Spoof video streams:** Do not use Android Creator for livestreams ([cbe576b](cbe576bc38))
2025-09-16 19:27:03 +00:00
LisoUseInAIKyrios
cbe576bc38 fix(YouTube - Spoof video streams): Do not use Android Creator for livestreams 2025-09-16 23:24:07 +04:00
github-actions[bot]
3a29f2a805 chore: Sync translations (#5909) 2025-09-16 23:21:01 +04:00
LisoUseInAIKyrios
50069c7e05 chore: Fix merge typo 2025-09-16 17:26:48 +04:00
semantic-release-bot
2e9c9dc244 chore: Release v5.38.0 [skip ci]
# [5.38.0](https://github.com/ReVanced/revanced-patches/compare/v5.37.0...v5.38.0) (2025-09-16)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Support v397.1.0.52.81 ([#5855](https://github.com/ReVanced/revanced-patches/issues/5855)) ([f11d1ef](f11d1ef990))
* **Spoof video streams:** Remove Android TV and iOS TV clients, add experimental VisionOS, add temporary fix for `Force original audio` to work with any spoof client ([#5861](https://github.com/ReVanced/revanced-patches/issues/5861)) ([abe3943](abe3943f98))
* **YouTube - Spoof video streams:** Show settings summary if `Force original audio` is enabled ([3776dda](3776dda710))
* **YouTube Music - Spoof video streams:** Fix playback issues when using a cellular network ([fa04c8e](fa04c8eecf))
* **YouTube Music:** Use correct light/dark mode settings UI ([1475643](1475643f84))

### Features

* **Instagram:** Add `Hide explore feed` patch ([#5856](https://github.com/ReVanced/revanced-patches/issues/5856)) ([1d65887](1d65887e01))
* **YouTube - Spoof video streams:** Add iPadOS client ([2726231](2726231404))
* **YouTube Music:** Add `Settings` patch ([#5838](https://github.com/ReVanced/revanced-patches/issues/5838)) ([5e20bd8](5e20bd80f1))
2025-09-16 13:01:23 +00:00
LisoUseInAIKyrios
56166896d9 chore: Merge branch dev to main (#5857) 2025-09-16 16:57:55 +04:00
semantic-release-bot
b4c695b1d5 chore: Release v5.38.0-dev.5 [skip ci]
# [5.38.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.4...v5.38.0-dev.5) (2025-09-16)

### Bug Fixes

* **YouTube Music:** Use correct light/dark mode settings UI ([1475643](1475643f84))
2025-09-16 12:34:52 +00:00
LisoUseInAIKyrios
1475643f84 fix(YouTube Music): Use correct light/dark mode settings UI 2025-09-16 16:31:04 +04:00
github-actions[bot]
9a7179f9cf chore: Sync translations (#5906) 2025-09-16 16:29:53 +04:00
semantic-release-bot
6fb94a7a41 chore: Release v5.38.0-dev.4 [skip ci]
# [5.38.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.3...v5.38.0-dev.4) (2025-09-16)

### Bug Fixes

* **YouTube - Spoof video streams:** Show settings summary if `Force original audio` is enabled ([3776dda](3776dda710))
2025-09-16 12:05:23 +00:00
LisoUseInAIKyrios
3776dda710 fix(YouTube - Spoof video streams): Show settings summary if Force original audio is enabled 2025-09-16 15:59:32 +04:00
LisoUseInAIKyrios
f88b3a5162 refactor(YouTube - Spoof video streams): Adjust preferred client order 2025-09-16 15:40:55 +04:00
semantic-release-bot
0eeaf7ad67 chore: Release v5.38.0-dev.3 [skip ci]
# [5.38.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.2...v5.38.0-dev.3) (2025-09-16)

### Features

* **YouTube - Spoof video streams:** Add iPadOS client ([2726231](2726231404))
2025-09-16 11:36:54 +00:00
LisoUseInAIKyrios
2726231404 feat(YouTube - Spoof video streams): Add iPadOS client 2025-09-16 15:33:55 +04:00
github-actions[bot]
9f0558e494 chore: Sync translations (#5905) 2025-09-16 15:11:04 +04:00
semantic-release-bot
01f7bc9f8d chore: Release v5.38.0-dev.2 [skip ci]
# [5.38.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.38.0-dev.1...v5.38.0-dev.2) (2025-09-16)

### Features

* **YouTube Music:** Add `Settings` patch ([#5838](https://github.com/ReVanced/revanced-patches/issues/5838)) ([5e20bd8](5e20bd80f1))
2025-09-16 06:57:43 +00:00
MarcaD
5e20bd80f1 feat(YouTube Music): Add Settings patch (#5838)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-09-16 10:53:49 +04:00
semantic-release-bot
f304c178e2 chore: Release v5.38.0-dev.1 [skip ci]
# [5.38.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.3...v5.38.0-dev.1) (2025-09-15)

### Features

* **Instagram:** Add `Hide explore feed` patch ([#5856](https://github.com/ReVanced/revanced-patches/issues/5856)) ([1d65887](1d65887e01))
2025-09-15 19:30:52 +00:00
brosssh
1d65887e01 feat(Instagram): Add Hide explore feed patch (#5856) 2025-09-15 23:28:01 +04:00
github-actions[bot]
6b6eea8414 chore: Sync translations (#5864) 2025-09-15 23:26:07 +04:00
semantic-release-bot
1db131e90e chore: Release v5.37.1-dev.3 [skip ci]
## [5.37.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.2...v5.37.1-dev.3) (2025-09-15)

### Bug Fixes

* **Spoof video streams:** Remove Android TV and iOS TV clients, add experimental VisionOS, add temporary fix for `Force original audio` to work with any spoof client ([#5861](https://github.com/ReVanced/revanced-patches/issues/5861)) ([abe3943](abe3943f98))
2025-09-15 17:02:01 +00:00
LisoUseInAIKyrios
abe3943f98 fix(Spoof video streams): Remove Android TV and iOS TV clients, add experimental VisionOS, add temporary fix for Force original audio to work with any spoof client (#5861) 2025-09-15 20:58:56 +04:00
semantic-release-bot
cb6d802de3 chore: Release v5.37.1-dev.2 [skip ci]
## [5.37.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.37.1-dev.1...v5.37.1-dev.2) (2025-09-15)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Support v397.1.0.52.81 ([#5855](https://github.com/ReVanced/revanced-patches/issues/5855)) ([f11d1ef](f11d1ef990))
2025-09-15 12:52:54 +00:00
brosssh
f11d1ef990 fix(Instagram - Hide navigation buttons): Support v397.1.0.52.81 (#5855) 2025-09-15 16:48:55 +04:00
semantic-release-bot
3d25da18bc chore: Release v5.37.1-dev.1 [skip ci]
## [5.37.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.37.0...v5.37.1-dev.1) (2025-09-15)

### Bug Fixes

* **YouTube Music - Spoof video streams:** Fix playback issues when using a cellular network ([fa04c8e](fa04c8eecf))
2025-09-15 12:47:02 +00:00
LisoUseInAIKyrios
fa04c8eecf fix(YouTube Music - Spoof video streams): Fix playback issues when using a cellular network
Code adapted from 5f35e51a27
2025-09-15 16:43:04 +04:00
semantic-release-bot
105f6e0e97 chore: Release v5.37.0 [skip ci]
# [5.37.0](https://github.com/ReVanced/revanced-patches/compare/v5.36.0...v5.37.0) (2025-09-15)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Add constrain to known working version ([e6c79f1](e6c79f1383))
* Resolve patching with dev branch ([09b941a](09b941abf0))
* **Spotify:** Remove broken `Spoof client` patch ([#5833](https://github.com/ReVanced/revanced-patches/issues/5833)) ([dcd4245](dcd42454bd))
* **Viber - Hide ads:** Add constrain to known working version ([2db0948](2db0948bea))
* **YouTube Music - Spoof streaming data:** Fix audio playback stuttering ([#5839](https://github.com/ReVanced/revanced-patches/issues/5839)) ([2a85a3b](2a85a3b290))

### Features

* **Viber:** Add `Hide ads` patch ([#5826](https://github.com/ReVanced/revanced-patches/issues/5826)) ([0abfab7](0abfab79d7))
2025-09-15 06:45:56 +00:00
LisoUseInAIKyrios
7d59efe05d chore: Merge branch dev to main (#5830) 2025-09-15 10:43:05 +04:00
github-actions[bot]
81ff5576b0 chore: Sync translations (#5854) 2025-09-15 10:41:42 +04:00
semantic-release-bot
9a5c102c0d chore: Release v5.37.0-dev.6 [skip ci]
# [5.37.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.5...v5.37.0-dev.6) (2025-09-15)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Add constrain to known working version ([e6c79f1](e6c79f1383))
2025-09-15 06:40:30 +00:00
LisoUseInAIKyrios
e6c79f1383 fix(Instagram - Hide navigation buttons): Add constrain to known working version 2025-09-15 10:36:57 +04:00
semantic-release-bot
2a582eced8 chore: Release v5.37.0-dev.5 [skip ci]
# [5.37.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.4...v5.37.0-dev.5) (2025-09-15)

### Bug Fixes

* **Viber - Hide ads:** Add constrain to known working version ([2db0948](2db0948bea))
2025-09-15 06:29:31 +00:00
LisoUseInAIKyrios
2db0948bea fix(Viber - Hide ads): Add constrain to known working version 2025-09-15 10:26:30 +04:00
semantic-release-bot
a3ba92e742 chore: Release v5.37.0-dev.4 [skip ci]
# [5.37.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.3...v5.37.0-dev.4) (2025-09-14)

### Bug Fixes

* **YouTube Music - Spoof streaming data:** Fix audio playback stuttering ([#5839](https://github.com/ReVanced/revanced-patches/issues/5839)) ([2a85a3b](2a85a3b290))
2025-09-14 18:22:57 +00:00
LisoUseInAIKyrios
2a85a3b290 fix(YouTube Music - Spoof streaming data): Fix audio playback stuttering (#5839) 2025-09-14 22:19:13 +04:00
semantic-release-bot
eee72208dd chore: Release v5.37.0-dev.3 [skip ci]
# [5.37.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.2...v5.37.0-dev.3) (2025-09-14)

### Bug Fixes

* **Spotify:** Remove broken `Spoof client` patch ([#5833](https://github.com/ReVanced/revanced-patches/issues/5833)) ([dcd4245](dcd42454bd))
2025-09-14 17:15:28 +00:00
LisoUseInAIKyrios
dcd42454bd fix(Spotify): Remove broken Spoof client patch (#5833) 2025-09-14 21:11:15 +04:00
LisoUseInAIKyrios
782353c18a refactor(Spoof video streams): Handle migration of default spoof client for users upgrading from very old patches 2025-09-14 18:06:40 +04:00
semantic-release-bot
b53b870e8f chore: Release v5.37.0-dev.2 [skip ci]
# [5.37.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.37.0-dev.1...v5.37.0-dev.2) (2025-09-14)

### Bug Fixes

* Resolve patching with dev branch ([09b941a](09b941abf0))
2025-09-14 12:00:38 +00:00
LisoUseInAIKyrios
09b941abf0 fix: Resolve patching with dev branch 2025-09-14 15:58:05 +04:00
semantic-release-bot
678ef4052e chore: Release v5.37.0-dev.1 [skip ci]
# [5.37.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.36.0...v5.37.0-dev.1) (2025-09-14)

### Features

* **Viber:** Add `Hide ads` patch ([#5826](https://github.com/ReVanced/revanced-patches/issues/5826)) ([0abfab7](0abfab79d7))
2025-09-14 11:52:21 +00:00
Samo Hribar
0abfab79d7 feat(Viber): Add Hide ads patch (#5826) 2025-09-14 15:49:52 +04:00
LisoUseInAIKyrios
61cadf72cd refactor(Spoof video streams): Back port code from v22 branch to support patching the latest YT Music. Using any target above 7.49.52 is untested and only recommended for experimental or development purposes. 2025-09-14 15:49:35 +04:00
github-actions[bot]
e12359b94f chore: Sync translations (#5829) 2025-09-14 15:46:32 +04:00
semantic-release-bot
c001daba4a chore: Release v5.36.0 [skip ci]
# [5.36.0](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0) (2025-09-14)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](a53b00dd51))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-14 06:56:22 +00:00
LisoUseInAIKyrios
e136f62d6e chore: Merge branch dev to main (#5800) 2025-09-14 10:53:28 +04:00
semantic-release-bot
8ec405a359 chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](a53b00dd51))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-13 15:31:54 +00:00
github-actions[bot]
2f4b3a887b chore: Sync translations (#5821) 2025-09-13 19:28:15 +04:00
semantic-release-bot
d1fabb242b chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))
* **YouTube Music:** Resolve playback issues, change recommended app target to `7.29.52` ([#5813](https://github.com/ReVanced/revanced-patches/issues/5813)) ([a53b00d](a53b00dd51))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-13 15:15:40 +00:00
LisoUseInAIKyrios
a53b00dd51 fix(YouTube Music): Resolve playback issues, change recommended app target to 7.29.52 (#5813) 2025-09-13 19:12:00 +04:00
semantic-release-bot
850c13e98e chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-13)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-13 07:00:16 +00:00
LisoUseInAIKyrios
4310789a26 chore: Fix api 2025-09-13 10:56:43 +04:00
semantic-release-bot
c4a720fbd3 chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.36.0-dev.1) (2025-09-12)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-12 15:08:03 +00:00
LisoUseInAIKyrios
3bdb8dbce0 chore(YouTube - SponsorBlock): Adjust strings for consistency / clarity
Strings taken from https://github.com/ajayyy/ExtensionTranslations/blob/master/en/messages.json
2025-09-12 18:49:34 +04:00
LisoUseInAIKyrios
4894f33c96 chore: fix compilation 2025-09-12 18:49:33 +04:00
semantic-release-bot
7f6093ee66 chore: Release v5.36.0-dev.1 [skip ci]
# [5.36.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.1-dev.1...v5.36.0-dev.1) (2025-09-12)

### Features

* **YouTube - SponsorBlock:** Add 'Hook' segment category ([#5783](https://github.com/ReVanced/revanced-patches/issues/5783)) ([9d4aa5c](9d4aa5cd16))
2025-09-12 01:59:47 +00:00
LisoUseInAIKyrios
9d4aa5cd16 feat(YouTube - SponsorBlock): Add 'Hook' segment category (#5783) 2025-09-12 05:56:50 +04:00
oSumAtrIX
5ace6f587c chore: Add ads.fund verification file [skip ci] (#5786) 2025-09-11 16:00:24 +02:00
semantic-release-bot
796f56745e chore: Release v5.35.1-dev.1 [skip ci]
## [5.35.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.35.0...v5.35.1-dev.1) (2025-09-11)

### Bug Fixes

* **Duolingo - Disable ads:** Support latest app target ([#5782](https://github.com/ReVanced/revanced-patches/issues/5782)) ([88b47ef](88b47ef414))
* **YouTube - Hide layout components:** Hide new type of Playable shelf ([8cd8e59](8cd8e59bbc))
2025-09-11 01:29:16 +00:00
hoodles
88b47ef414 fix(Duolingo - Disable ads): Support latest app target (#5782) 2025-09-11 03:26:28 +02:00
LisoUseInAIKyrios
8cd8e59bbc fix(YouTube - Hide layout components): Hide new type of Playable shelf 2025-09-11 03:24:09 +02:00
LisoUseInAIKyrios
6e72b14d07 refactor(YouTube - Video Quality): Handle extremely slow internet connections that initially can use -1 quality index 2025-09-11 02:58:33 +02:00
LisoUseInAIKyrios
52b088327b chore: Fix api dump 2025-09-10 21:58:27 +02:00
semantic-release-bot
8e934cc56b chore: Release v5.35.0 [skip ci]
# [5.35.0](https://github.com/ReVanced/revanced-patches/compare/v5.34.0...v5.35.0) (2025-09-09)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Fix Manager patching error ([0a8cd7a](0a8cd7a7db))
* **Proton mail:** Constrain patches to last working app target ([1895291](189529151a))
* Revert dependency updates to fix Manager pre-release patching ([9256aa4](9256aa4548))
* **Spotify - Unlock Premium:** Make compatible with latest versions again by fixing fingerprint ([#5684](https://github.com/ReVanced/revanced-patches/issues/5684)) ([23496c7](23496c7c36))
* **YouTube - Hide layout components:** Hide Playable shelf header ([1473db0](1473db0bef))

### Features

* **BaconReader:** Add `Fix Redgifs API` patch ([#5761](https://github.com/ReVanced/revanced-patches/issues/5761)) ([144af2f](144af2f07e))
* **Boost/Sync for Reddit:** Add `Fix Redgifs` patch  ([#5725](https://github.com/ReVanced/revanced-patches/issues/5725)) ([c66c42e](c66c42e946))
* **Instagram:** Add `Hide navigation buttons` patch ([#5678](https://github.com/ReVanced/revanced-patches/issues/5678)) ([1dbc2d4](1dbc2d4057))
* **Instagram:** Add `Hide Stories from Home` patch ([#5756](https://github.com/ReVanced/revanced-patches/issues/5756)) ([b8629aa](b8629aacb6))
2025-09-09 19:38:18 +00:00
LisoUseInAIKyrios
b3140d909b chore: Merge branch dev to main (#5691) 2025-09-09 21:34:30 +02:00
github-actions[bot]
97645aa9f4 chore: Sync translations (#5777) 2025-09-09 21:32:56 +02:00
semantic-release-bot
603e2d018c chore: Release v5.35.0-dev.5 [skip ci]
# [5.35.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.4...v5.35.0-dev.5) (2025-09-06)

### Features

* **BaconReader:** Add `Fix Redgifs API` patch ([#5761](https://github.com/ReVanced/revanced-patches/issues/5761)) ([144af2f](144af2f07e))
* **Instagram:** Add `Hide Stories from Home` patch ([#5756](https://github.com/ReVanced/revanced-patches/issues/5756)) ([b8629aa](b8629aacb6))
2025-09-06 10:56:54 +00:00
Eric Ahn
144af2f07e feat(BaconReader): Add Fix Redgifs API patch (#5761) 2025-09-06 12:53:26 +02:00
PainfulPaladins
b8629aacb6 feat(Instagram): Add Hide Stories from Home patch (#5756) 2025-09-06 12:53:08 +02:00
github-actions[bot]
3951527f51 chore: Sync translations (#5768) 2025-09-06 12:52:48 +02:00
semantic-release-bot
7a8b618c4e chore: Release v5.35.0-dev.4 [skip ci]
# [5.35.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.3...v5.35.0-dev.4) (2025-09-04)

### Features

* **Boost/Sync for Reddit:** Add `Fix Redgifs` patch  ([#5725](https://github.com/ReVanced/revanced-patches/issues/5725)) ([c66c42e](c66c42e946))
2025-09-04 21:33:33 +00:00
Eric Ahn
c66c42e946 feat(Boost/Sync for Reddit): Add Fix Redgifs patch (#5725) 2025-09-04 23:29:58 +02:00
semantic-release-bot
b340769cf3 chore: Release v5.35.0-dev.3 [skip ci]
# [5.35.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.2...v5.35.0-dev.3) (2025-09-04)

### Bug Fixes

* **Instagram - Hide navigation buttons:** Fix Manager patching error ([0a8cd7a](0a8cd7a7db))
2025-09-04 14:06:03 +00:00
LisoUseInAIKyrios
0a8cd7a7db fix(Instagram - Hide navigation buttons): Fix Manager patching error 2025-09-04 16:01:50 +02:00
semantic-release-bot
39f90e4b11 chore: Release v5.35.0-dev.2 [skip ci]
# [5.35.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.35.0-dev.1...v5.35.0-dev.2) (2025-09-04)

### Bug Fixes

* Revert dependency updates to fix Manager pre-release patching ([9256aa4](9256aa4548))
2025-09-04 10:27:39 +00:00
LisoUseInAIKyrios
9256aa4548 fix: Revert dependency updates to fix Manager pre-release patching 2025-09-04 12:23:56 +02:00
semantic-release-bot
7973c75552 chore: Release v5.35.0-dev.1 [skip ci]
# [5.35.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.34.1-dev.3...v5.35.0-dev.1) (2025-09-03)

### Features

* **Instagram:** Add `Hide navigation buttons` patch ([#5678](https://github.com/ReVanced/revanced-patches/issues/5678)) ([1dbc2d4](1dbc2d4057))
2025-09-03 17:43:47 +00:00
github-actions[bot]
2b2307416a chore: Sync translations (#5755) 2025-09-03 19:41:04 +02:00
PainfulPaladins
1dbc2d4057 feat(Instagram): Add Hide navigation buttons patch (#5678)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-09-03 19:39:25 +02:00
dependabot[bot]
f6917dc361 chore(deps): Bump com.google.protobuf:protoc from 4.31.1 to 4.32.0 (#5751) 2025-09-02 18:28:15 +02:00
dependabot[bot]
d2f043e11a chore(deps): Bump com.google.protobuf:protobuf-javalite from 4.31.1 to 4.32.0 (#5750) 2025-09-02 17:10:45 +02:00
dependabot[bot]
a392bc0dfd chore(deps): Bump actions/setup-java from 4 to 5 (#5746) 2025-09-02 12:43:12 +02:00
dependabot[bot]
dfc127048a chore(deps): Bump actions/attest-build-provenance from 2 to 3 (#5743) 2025-09-02 12:42:08 +02:00
dependabot[bot]
ed31d0cab6 chore(deps): Bump actions/checkout from 4 to 5 (#5745) 2025-09-02 12:41:29 +02:00
dependabot[bot]
0df6315f9c chore(deps): Bump cycjimmy/semantic-release-action from 4 to 5 (#5741) 2025-09-02 12:40:08 +02:00
semantic-release-bot
f14259f9ef chore: Release v5.34.1-dev.3 [skip ci]
## [5.34.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.34.1-dev.2...v5.34.1-dev.3) (2025-08-24)

### Bug Fixes

* **YouTube - Hide layout components:** Hide Playable shelf header ([1473db0](1473db0bef))
2025-08-24 03:30:00 +00:00
LisoUseInAIKyrios
1473db0bef fix(YouTube - Hide layout components): Hide Playable shelf header 2025-08-23 23:26:02 -04:00
github-actions[bot]
829ca58a55 chore: Sync translations (#5707) 2025-08-23 23:23:49 -04:00
semantic-release-bot
aace741e25 chore: Release v5.34.1-dev.2 [skip ci]
## [5.34.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.34.1-dev.1...v5.34.1-dev.2) (2025-08-22)

### Bug Fixes

* **Proton mail:** Constrain patches to last working app target ([1895291](189529151a))
2025-08-22 04:12:59 +00:00
LisoUseInAIKyrios
189529151a fix(Proton mail): Constrain patches to last working app target 2025-08-22 00:10:03 -04:00
semantic-release-bot
51237c177a chore: Release v5.34.1-dev.1 [skip ci]
## [5.34.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.34.0...v5.34.1-dev.1) (2025-08-21)

### Bug Fixes

* **Spotify - Unlock Premium:** Make compatible with latest versions again by fixing fingerprint ([#5684](https://github.com/ReVanced/revanced-patches/issues/5684)) ([23496c7](23496c7c36))
2025-08-21 19:20:39 +00:00
Nuckyz
23496c7c36 fix(Spotify - Unlock Premium): Make compatible with latest versions again by fixing fingerprint (#5684) 2025-08-21 15:17:29 -04:00
semantic-release-bot
e6823d8924 chore: Release v5.34.0 [skip ci]
# [5.34.0](https://github.com/ReVanced/revanced-patches/compare/v5.33.0...v5.34.0) (2025-08-19)

### Bug Fixes

* **Backdrops:** Remove broken patch that is no longer supported ([#5627](https://github.com/ReVanced/revanced-patches/issues/5627)) ([c3e571e](c3e571e765))
* **pixiv - Hide ads:** Constrain patch to last working app target ([b702dce](b702dceda0))
* **Twitch:** Constrain patches to last working app targets ([#5373](https://github.com/ReVanced/revanced-patches/issues/5373)) ([d7eb6e8](d7eb6e87a5))
* **YouTube - Hide layout components:** Do not hide community posts on channel profiles ([#5634](https://github.com/ReVanced/revanced-patches/issues/5634)) ([61824ad](61824ade23))
* **YouTube - Player Controls:** Fix chapter title overlapping the bottom buttons ([#5673](https://github.com/ReVanced/revanced-patches/issues/5673)) ([150bee2](150bee2833))
* **YouTube - SponsorBlock:** Do not hide voting or create button when the video ends ([25470ba](25470baeee))
* **YouTube - Video playback:** Disable HDR video does not disable Dolby Vision HDR ([#5661](https://github.com/ReVanced/revanced-patches/issues/5661)) ([4aaa7ca](4aaa7ca895))
* **YouTube - Video quality:** Fix additional incorrect quality resolutions used by YouTube ([6bd9e49](6bd9e49c7a))
* **YouTube - Video quality:** Show FHD+ icon for 1080p 60fps enhanced bitrate ([e579c56](e579c56921))
* **YouTube:** Use correct fade out animation when tapping to dismiss the video overlay ([#5670](https://github.com/ReVanced/revanced-patches/issues/5670)) ([01a04c3](01a04c338c))

### Features

* **Instagram:** Support latest app version ([#5611](https://github.com/ReVanced/revanced-patches/issues/5611)) ([562e005](562e005772))
* **NU.nl:** Support latest app version ([#5643](https://github.com/ReVanced/revanced-patches/issues/5643)) ([1bb8c53](1bb8c53ed3))
* **YouTube - Hide player flyout menu items:** Add option to hide quality flyout menu ([809e013](809e013c4e))
* **YouTube - Hide video action buttons:** Add "Hide Hype button" setting ([fe66bae](fe66baedb7))
* **YouTube - Hide video action buttons:** Add "Hide Promote button" setting ([40ac8e1](40ac8e1142))
* **YouTube - Playback speed:** Show current playback speed on player speed dialog button ([#5607](https://github.com/ReVanced/revanced-patches/issues/5607)) ([30176a3](30176a3318))
* **YouTube:** Add `Disable sign in to TV popup` patch ([#5639](https://github.com/ReVanced/revanced-patches/issues/5639)) ([56fbd8c](56fbd8cce0))
2025-08-19 15:12:22 +00:00
LisoUseInAIKyrios
43597dab21 chore: Merge branch dev to main (#5617) 2025-08-19 11:08:46 -04:00
semantic-release-bot
c0824db142 chore: Release v5.34.0-dev.13 [skip ci]
# [5.34.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.12...v5.34.0-dev.13) (2025-08-19)

### Bug Fixes

* **YouTube - Player Controls:** Fix chapter title overlapping the bottom buttons ([#5673](https://github.com/ReVanced/revanced-patches/issues/5673)) ([150bee2](150bee2833))
2025-08-19 14:55:36 +00:00
github-actions[bot]
1b7f84b7fa chore: Sync translations (#5677) 2025-08-19 10:52:35 -04:00
semantic-release-bot
6d87c848d6 chore: Release v5.34.0-dev.13 [skip ci]
# [5.34.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.12...v5.34.0-dev.13) (2025-08-18)

### Bug Fixes

* **YouTube - Player Controls:** Fix chapter title overlapping the bottom buttons ([#5673](https://github.com/ReVanced/revanced-patches/issues/5673)) ([150bee2](150bee2833))
2025-08-18 22:29:41 +00:00
MarcaD
150bee2833 fix(YouTube - Player Controls): Fix chapter title overlapping the bottom buttons (#5673)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-08-18 18:27:14 -04:00
semantic-release-bot
c3ee6eca44 chore: Release v5.34.0-dev.12 [skip ci]
# [5.34.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.11...v5.34.0-dev.12) (2025-08-18)

### Bug Fixes

* **YouTube:** Use correct fade out animation when tapping to dismiss the video overlay ([#5670](https://github.com/ReVanced/revanced-patches/issues/5670)) ([01a04c3](01a04c338c))
2025-08-18 00:17:30 +00:00
LisoUseInAIKyrios
01a04c338c fix(YouTube): Use correct fade out animation when tapping to dismiss the video overlay (#5670) 2025-08-17 20:14:44 -04:00
github-actions[bot]
3130225d9d chore: Sync translations (#5671) 2025-08-17 20:13:55 -04:00
LisoUseInAIKyrios
16b27fb872 chore: Fix typo 2025-08-17 11:17:54 -04:00
LisoUseInAIKyrios
bedabd3fa3 refactor: Show SB buttons with other overlay buttons when the video has ended 2025-08-16 16:24:24 -04:00
semantic-release-bot
84f3c6f02d chore: Release v5.34.0-dev.11 [skip ci]
# [5.34.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.10...v5.34.0-dev.11) (2025-08-16)

### Bug Fixes

* **YouTube - SponsorBlock:** Do not hide voting or create button when the video ends ([25470ba](25470baeee))
2025-08-16 20:21:35 +00:00
LisoUseInAIKyrios
25470baeee fix(YouTube - SponsorBlock): Do not hide voting or create button when the video ends
This logic is no longer needed, since YouTube no longer hides the overlay buttons when the video ends
2025-08-16 16:17:43 -04:00
semantic-release-bot
b86da73a87 chore: Release v5.34.0-dev.10 [skip ci]
# [5.34.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.9...v5.34.0-dev.10) (2025-08-16)

### Bug Fixes

* **YouTube - Video playback:** Disable HDR video does not disable Dolby Vision HDR ([#5661](https://github.com/ReVanced/revanced-patches/issues/5661)) ([4aaa7ca](4aaa7ca895))

### Features

* **YouTube - Hide video action buttons:** Add "Hide Promote button" setting ([40ac8e1](40ac8e1142))
2025-08-16 19:14:17 +00:00
LisoUseInAIKyrios
4aaa7ca895 fix(YouTube - Video playback): Disable HDR video does not disable Dolby Vision HDR (#5661) 2025-08-16 15:10:31 -04:00
semantic-release-bot
d3f63461e7 chore: Release v5.34.0-dev.10 [skip ci]
# [5.34.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.9...v5.34.0-dev.10) (2025-08-16)

### Features

* **YouTube - Hide video action buttons:** Add "Hide Promote button" setting ([40ac8e1](40ac8e1142))
2025-08-16 17:53:26 +00:00
github-actions[bot]
7a3ace2231 chore: Sync translations (#5664) 2025-08-16 13:50:35 -04:00
semantic-release-bot
c89668a540 chore: Release v5.34.0-dev.10 [skip ci]
# [5.34.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.34.0-dev.9...v5.34.0-dev.10) (2025-08-16)

### Features

* **YouTube - Hide video action buttons:** Add "Hide Promote button" setting ([40ac8e1](40ac8e1142))
2025-08-16 17:26:01 +00:00
LisoUseInAIKyrios
40ac8e1142 feat(YouTube - Hide video action buttons): Add "Hide Promote button" setting 2025-08-16 13:21:00 -04:00
github-actions[bot]
26c6420de5 chore: Sync translations (#5663) 2025-08-16 13:20:25 -04:00
github-actions[bot]
bfd3989995 chore: Sync translations (#5662) 2025-08-16 13:15:10 -04:00
LisoUseInAIKyrios
7e812ae1a8 chore: Fix typo 2025-08-16 12:23:09 -04:00
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
semantic-release-bot
9e4113555b chore: Release v5.32.0-dev.5 [skip ci]
# [5.32.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.32.0-dev.4...v5.32.0-dev.5) (2025-07-26)

### Features

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Features

* **Prime Video:** Add `Playback speed` patch ([#5444](https://github.com/ReVanced/revanced-patches/issues/5444)) ([f46dbcd](f46dbcd084))
2025-07-16 19:30:50 +00:00
Sujitha Wijewantha
f46dbcd084 feat(Prime Video): Add Playback speed patch (#5444) 2025-07-16 23:27:55 +04:00
github-actions[bot]
2136573cb6 chore: Sync translations (#5484) 2025-07-16 23:27:18 +04:00
MarcaD
86ec08993c refactor(YouTube - Settings): Back button/gesture closes search instead of exiting (#5439) 2025-07-16 23:26:20 +04:00
semantic-release-bot
44da5a71c5 chore: Release v5.31.3-dev.1 [skip ci]
## [5.31.3-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.2...v5.31.3-dev.1) (2025-07-16)

### Bug Fixes

* **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))
2025-07-16 19:16:27 +00:00
sm455
e4e81b89ea fix(YouTube - GmsCore support): Fix search suggestions when logged out by using correct search provider (#5483) 2025-07-16 23:13:22 +04:00
LisoUseInAIKyrios
165df659a1 chore(YouTube): Add string contexts 2025-07-16 12:02:47 +04:00
LisoUseInAIKyrios
bb87afe0f6 ci: Revert "Group all Dependabot update into one PR (#5336)"
This reverts commit e019f83232.
2025-07-16 11:54:40 +04:00
semantic-release-bot
ac5fb17937 chore: Release v5.31.2 [skip ci]
## [5.31.2](https://github.com/ReVanced/revanced-patches/compare/v5.31.1...v5.31.2) (2025-07-14)

### Bug Fixes

* **Spotify - Spoof client:** Fix login failing by spoofing login request in addition ([#5448](https://github.com/ReVanced/revanced-patches/issues/5448)) ([c972267](c972267cd8))
* **YouTube - Disable double tap actions:** Remove old incompatible targets ([294b2dc](294b2dce2e))
* **YouTube - Hide layout components:** Hide quick actions does not work ([#5423](https://github.com/ReVanced/revanced-patches/issues/5423)) ([cc6984e](cc6984e919))
* **YouTube - Hide layout components:** Show correct custom header logo if 'Hide YouTube Doodles' is enabled ([#5431](https://github.com/ReVanced/revanced-patches/issues/5431)) ([19bc5b6](19bc5b63c5))
* **YouTube - Settings:** Back button/gesture closes search instead of exiting ([#5418](https://github.com/ReVanced/revanced-patches/issues/5418)) ([f994264](f994264d9c))
2025-07-14 12:02:46 +00:00
oSumAtrIX
e88356b3c5 chore: Merge branch dev to main (#5428) 2025-07-14 13:59:59 +02:00
github-actions[bot]
dead9c2d94 chore: Sync translations (#5449)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-07-14 13:59:35 +02:00
semantic-release-bot
ca640b2839 chore: Release v5.31.2-dev.5 [skip ci]
## [5.31.2-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.4...v5.31.2-dev.5) (2025-07-14)

### Bug Fixes

* **Spotify - Spoof client:** Fix login failing by spoofing login request in addition ([#5448](https://github.com/ReVanced/revanced-patches/issues/5448)) ([c972267](c972267cd8))
2025-07-14 11:58:39 +00:00
oSumAtrIX
c972267cd8 fix(Spotify - Spoof client): Fix login failing by spoofing login request in addition (#5448) 2025-07-14 13:55:37 +02:00
ILoveOpenSourceApplications
d0d2c13d16 refactor(YouTube): Sort and standardize strings (#5442) 2025-07-14 15:01:10 +04:00
semantic-release-bot
e7b4ab53cf chore: Release v5.31.2-dev.4 [skip ci]
## [5.31.2-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.3...v5.31.2-dev.4) (2025-07-13)

### Bug Fixes

* **YouTube - Settings:** Back button/gesture closes search instead of exiting ([#5418](https://github.com/ReVanced/revanced-patches/issues/5418)) ([f994264](f994264d9c))
2025-07-13 10:59:17 +00:00
MarcaD
f994264d9c fix(YouTube - Settings): Back button/gesture closes search instead of exiting (#5418) 2025-07-13 14:56:30 +04:00
github-actions[bot]
eb61c1f5d1 chore: Sync translations (#5437) 2025-07-13 14:55:36 +04:00
semantic-release-bot
e578347277 chore: Release v5.31.2-dev.3 [skip ci]
## [5.31.2-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.2...v5.31.2-dev.3) (2025-07-13)

### Bug Fixes

* **YouTube - Disable double tap actions:** Remove old incompatible targets ([294b2dc](294b2dce2e))
2025-07-13 06:18:39 +00:00
LisoUseInAIKyrios
294b2dce2e fix(YouTube - Disable double tap actions): Remove old incompatible targets 2025-07-13 10:15:16 +04:00
github-actions[bot]
aa37105ea3 chore: Sync translations (#5436) 2025-07-13 10:03:04 +04:00
semantic-release-bot
eb57a2697b chore: Release v5.31.2-dev.2 [skip ci]
## [5.31.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.1...v5.31.2-dev.2) (2025-07-12)

### Bug Fixes

* **YouTube - Hide layout components:** Show correct custom header logo if 'Hide YouTube Doodles' is enabled ([#5431](https://github.com/ReVanced/revanced-patches/issues/5431)) ([19bc5b6](19bc5b63c5))
2025-07-12 14:37:52 +00:00
LisoUseInAIKyrios
19bc5b63c5 fix(YouTube - Hide layout components): Show correct custom header logo if 'Hide YouTube Doodles' is enabled (#5431) 2025-07-12 18:34:29 +04:00
semantic-release-bot
2b93ff6cfc chore: Release v5.31.2-dev.1 [skip ci]
## [5.31.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.1...v5.31.2-dev.1) (2025-07-12)

### Bug Fixes

* **YouTube - Hide layout components:** Hide quick actions does not work ([#5423](https://github.com/ReVanced/revanced-patches/issues/5423)) ([cc6984e](cc6984e919))
2025-07-12 09:46:21 +00:00
MarcaD
cc6984e919 fix(YouTube - Hide layout components): Hide quick actions does not work (#5423) 2025-07-12 13:43:26 +04:00
github-actions[bot]
8bf575e778 chore: Sync translations (#5427) 2025-07-12 13:42:55 +04:00
semantic-release-bot
2e625ee1a2 chore: Release v5.31.1 [skip ci]
## [5.31.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.0...v5.31.1) (2025-07-11)

### Bug Fixes

* **Spotify - Unlock Premium:** Fix hiding context menu ads for latest version ([#5415](https://github.com/ReVanced/revanced-patches/issues/5415)) ([82255a0](82255a09d3))
2025-07-11 16:28:51 +00:00
oSumAtrIX
6bcba48ee7 chore: Merge branch dev to main (#5414) 2025-07-11 18:25:35 +02:00
semantic-release-bot
c3034edc43 chore: Release v5.31.1-dev.1 [skip ci]
## [5.31.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.0...v5.31.1-dev.1) (2025-07-11)

### Bug Fixes

* **Spotify - Unlock Premium:** Fix hiding context menu ads for latest version ([#5415](https://github.com/ReVanced/revanced-patches/issues/5415)) ([82255a0](82255a09d3))
2025-07-11 16:25:25 +00:00
Nuckyz
82255a09d3 fix(Spotify - Unlock Premium): Fix hiding context menu ads for latest version (#5415) 2025-07-11 18:21:49 +02:00
LisoUseInAIKyrios
594dce13cd chore(YouTube): Adjust settings UI text to not clip/wrap 2025-07-11 20:05:52 +04:00
semantic-release-bot
479e205808 chore: Release v5.31.0 [skip ci]
# [5.31.0](https://github.com/ReVanced/revanced-patches/compare/v5.30.0...v5.31.0) (2025-07-11)

### Bug Fixes

* **Bacon Reader - Spoof client:** Use www instead of ssl API to fix auth related issues  ([#5402](https://github.com/ReVanced/revanced-patches/issues/5402)) ([37a8682](37a8682901))
* Correctly name `Enable ROM signature spoofing` patch ([bd2a939](bd2a939a72))
* Fix accidental changes ([42195b9](42195b9f63))
* Fix refactoring typo ([b0129d3](b0129d383a))
* Handle empty list of announcements ([eafe3df](eafe3dfc45))
* **SoundCloud:** Constrain patches to last working app target ([89ec5d5](89ec5d5bc6))
* **Spotify - Unlock Premium:** Remove wrongfully hidden non ad browse sections ([#5403](https://github.com/ReVanced/revanced-patches/issues/5403)) ([b3e6c21](b3e6c215cc))
* **Spotify:** Remove other ads type from the browse screen ([#5333](https://github.com/ReVanced/revanced-patches/issues/5333)) ([4c8cfc8](4c8cfc8800))
* **Sync for Reddit - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5392](https://github.com/ReVanced/revanced-patches/issues/5392)) ([6412a5c](6412a5cb1a))
* **YouTube - Hide ads:** Hide new type of general ad ([#5345](https://github.com/ReVanced/revanced-patches/issues/5345)) ([f9abec3](f9abec358a))
* **YouTube - Hide layout components:** Do not hide playlist sort button if 'Hide AI comments summary' is on ([cc4aef8](cc4aef89d3))
* **YouTube - Playback speed:** Allow custom speeds with 0.01x precision ([#5360](https://github.com/ReVanced/revanced-patches/issues/5360)) ([10f4464](10f4464735))
* **YouTube - Slide to seek:** Show tap and hold 2x speed overlay when active ([#5398](https://github.com/ReVanced/revanced-patches/issues/5398)) ([6833d37](6833d37c26))

### Features

* **Cricbuzz - Hide ads:** Hide Cricbuzz11 UI elements ([#5381](https://github.com/ReVanced/revanced-patches/issues/5381)) ([a3d47e7](a3d47e72e3))
* **Lightroom:** Constrain patches to last working version ([#5335](https://github.com/ReVanced/revanced-patches/issues/5335)) ([f7f49b8](f7f49b834e))
* **Spotify - Spoof client:** Fix issues like songs skipping by spoofing to iOS ([#5388](https://github.com/ReVanced/revanced-patches/issues/5388)) ([65cbf3c](65cbf3c1eb))
* **Spotify:** Remove support for old versions ([#5404](https://github.com/ReVanced/revanced-patches/issues/5404)) ([c9cc3d5](c9cc3d5c41))
* **YouTube - Change header:** Add in-app setting to change the app header ([#5346](https://github.com/ReVanced/revanced-patches/issues/5346)) ([4e74207](4e742075f3))
* **YouTube - Hide layout components:** Add `Hide channel links preview` and `Hide 'Visit Community' button` in channel page ([#5320](https://github.com/ReVanced/revanced-patches/issues/5320)) ([3eac215](3eac215e13))
* **YouTube:** Disable two-finger tap gesture for skipping chapters ([#5374](https://github.com/ReVanced/revanced-patches/issues/5374)) ([61c1a7a](61c1a7a75a))
2025-07-11 15:58:36 +00:00
oSumAtrIX
3d1b7e8101 chore: Merge branch dev to main (#5339) 2025-07-11 17:54:40 +02:00
LisoUseInAIKyrios
e951184b7a chore: Fix announcement url encoding 2025-07-11 19:51:19 +04:00
github-actions[bot]
d088b1e7ed chore: Sync translations (#5411) 2025-07-11 19:48:19 +04:00
semantic-release-bot
a38f635514 chore: Release v5.31.0-dev.17 [skip ci]
# [5.31.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.16...v5.31.0-dev.17) (2025-07-11)

### Bug Fixes

* **Spotify - Unlock Premium:** Remove wrongfully hidden non ad browse sections ([#5403](https://github.com/ReVanced/revanced-patches/issues/5403)) ([b3e6c21](b3e6c215cc))

### Features

* **Spotify:** Remove support for old versions ([#5404](https://github.com/ReVanced/revanced-patches/issues/5404)) ([c9cc3d5](c9cc3d5c41))
2025-07-11 15:41:53 +00:00
Nuckyz
b3e6c215cc fix(Spotify - Unlock Premium): Remove wrongfully hidden non ad browse sections (#5403) 2025-07-11 17:38:33 +02:00
Nuckyz
c9cc3d5c41 feat(Spotify): Remove support for old versions (#5404) 2025-07-11 17:37:59 +02:00
semantic-release-bot
536e64565c chore: Release v5.31.0-dev.16 [skip ci]
# [5.31.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.15...v5.31.0-dev.16) (2025-07-11)

### Features

* **Spotify - Spoof client:** Fix issues like songs skipping by spoofing to iOS ([#5388](https://github.com/ReVanced/revanced-patches/issues/5388)) ([65cbf3c](65cbf3c1eb))
* **YouTube:** Disable two-finger tap gesture for skipping chapters ([#5374](https://github.com/ReVanced/revanced-patches/issues/5374)) ([61c1a7a](61c1a7a75a))
2025-07-11 15:37:29 +00:00
Dawid Krajcarz
65cbf3c1eb feat(Spotify - Spoof client): Fix issues like songs skipping by spoofing to iOS (#5388)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-07-11 17:34:02 +02:00
abel1502
61c1a7a75a feat(YouTube): Disable two-finger tap gesture for skipping chapters (#5374)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-07-11 17:32:59 +02:00
Pun Butrach
1e39db06b8 ci: Remove fetch-depth from checkout (#5311) 2025-07-11 17:31:12 +02:00
Pun Butrach
e019f83232 ci: Group all Dependabot update into one PR (#5336) 2025-07-11 17:31:03 +02:00
semantic-release-bot
3b57a5f8c0 chore: Release v5.31.0-dev.15 [skip ci]
# [5.31.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.14...v5.31.0-dev.15) (2025-07-11)

### Bug Fixes

* Handle empty list of announcements ([eafe3df](eafe3dfc45))
2025-07-11 09:31:21 +00:00
oSumAtrIX
eafe3dfc45 fix: Handle empty list of announcements 2025-07-11 11:28:13 +02:00
semantic-release-bot
d56d8d990c chore: Release v5.31.0-dev.14 [skip ci]
# [5.31.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.13...v5.31.0-dev.14) (2025-07-10)

### Bug Fixes

* **Bacon Reader - Spoof client:** Use www instead of ssl API to fix auth related issues  ([#5402](https://github.com/ReVanced/revanced-patches/issues/5402)) ([37a8682](37a8682901))
2025-07-10 18:51:55 +00:00
Chirag Gada
37a8682901 fix(Bacon Reader - Spoof client): Use www instead of ssl API to fix auth related issues (#5402) 2025-07-10 20:49:04 +02:00
semantic-release-bot
11ba7d4e3e chore: Release v5.31.0-dev.13 [skip ci]
# [5.31.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.12...v5.31.0-dev.13) (2025-07-10)

### Bug Fixes

* **YouTube - Slide to seek:** Show tap and hold 2x speed overlay when active ([#5398](https://github.com/ReVanced/revanced-patches/issues/5398)) ([6833d37](6833d37c26))
2025-07-10 13:38:38 +00:00
LisoUseInAIKyrios
6833d37c26 fix(YouTube - Slide to seek): Show tap and hold 2x speed overlay when active (#5398) 2025-07-10 17:35:08 +04:00
github-actions[bot]
e6f72bcb7d chore: Sync translations (#5399) 2025-07-10 17:34:47 +04:00
LisoUseInAIKyrios
e8a227c082 chore: Fix api dump 2025-07-10 15:15:34 +04:00
semantic-release-bot
0472ec2830 chore: Release v5.31.0-dev.12 [skip ci]
# [5.31.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.11...v5.31.0-dev.12) (2025-07-09)

### Bug Fixes

* **Sync for Reddit - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5392](https://github.com/ReVanced/revanced-patches/issues/5392)) ([6412a5c](6412a5cb1a))
2025-07-09 18:28:47 +00:00
oSumAtrIX
6412a5cb1a fix(Sync for Reddit - Spoof client): Use www instead of ssl API to fix auth related issues (#5392) 2025-07-09 20:25:48 +02:00
semantic-release-bot
cc548689ac chore: Release v5.31.0-dev.11 [skip ci]
# [5.31.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.10...v5.31.0-dev.11) (2025-07-09)

### Features

* **Cricbuzz - Hide ads:** Hide Cricbuzz11 UI elements ([#5381](https://github.com/ReVanced/revanced-patches/issues/5381)) ([a3d47e7](a3d47e72e3))
2025-07-09 17:50:37 +00:00
hoodles
a3d47e72e3 feat(Cricbuzz - Hide ads): Hide Cricbuzz11 UI elements (#5381) 2025-07-09 21:47:10 +04:00
semantic-release-bot
f37482443a chore: Release v5.31.0-dev.10 [skip ci]
# [5.31.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.9...v5.31.0-dev.10) (2025-07-09)

### Bug Fixes

* **YouTube - Hide layout components:** Do not hide playlist sort button if 'Hide AI comments summary' is on ([cc4aef8](cc4aef89d3))
2025-07-09 14:37:19 +00:00
LisoUseInAIKyrios
cc4aef89d3 fix(YouTube - Hide layout components): Do not hide playlist sort button if 'Hide AI comments summary' is on 2025-07-09 18:33:24 +04:00
github-actions[bot]
1c0a0eb4b5 chore: Sync translations (#5389) 2025-07-09 18:33:07 +04:00
semantic-release-bot
b1d6c46763 chore: Release v5.31.0-dev.9 [skip ci]
# [5.31.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.8...v5.31.0-dev.9) (2025-07-07)

### Bug Fixes

* Fix accidental changes ([42195b9](42195b9f63))
2025-07-07 10:32:07 +00:00
oSumAtrIX
42195b9f63 fix: Fix accidental changes 2025-07-07 12:29:21 +02:00
semantic-release-bot
a4e08ea13d chore: Release v5.31.0-dev.8 [skip ci]
# [5.31.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.7...v5.31.0-dev.8) (2025-07-07)

### Bug Fixes

* Correctly name `Enable ROM signature spoofing` patch ([bd2a939](bd2a939a72))
2025-07-07 07:43:53 +00:00
oSumAtrIX
bd2a939a72 fix: Correctly name Enable ROM signature spoofing patch 2025-07-07 09:40:28 +02:00
semantic-release-bot
a89179ab79 chore: Release v5.31.0-dev.7 [skip ci]
# [5.31.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.6...v5.31.0-dev.7) (2025-07-06)

### Bug Fixes

* Fix refactoring typo ([b0129d3](b0129d383a))
2025-07-06 14:22:39 +00:00
LisoUseInAIKyrios
b0129d383a fix: Fix refactoring typo 2025-07-06 18:19:43 +04:00
semantic-release-bot
23b6c42630 chore: Release v5.31.0-dev.6 [skip ci]
# [5.31.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.5...v5.31.0-dev.6) (2025-07-06)

### Bug Fixes

* **YouTube - Playback speed:** Allow custom speeds with 0.01x precision ([#5360](https://github.com/ReVanced/revanced-patches/issues/5360)) ([10f4464](10f4464735))
2025-07-06 13:16:35 +00:00
LisoUseInAIKyrios
10f4464735 fix(YouTube - Playback speed): Allow custom speeds with 0.01x precision (#5360) 2025-07-06 17:13:31 +04:00
github-actions[bot]
4e5addbba5 chore: Sync translations (#5369) 2025-07-06 17:12:43 +04:00
LisoUseInAIKyrios
8d11ede927 chore: Fix resource compile errors from last refactor 2025-07-06 17:07:19 +04:00
ILoveOpenSourceApplications
83a3f4da00 refactor: Standardize string formatting and apply alphabetical sorting (#5343) 2025-07-06 12:24:25 +04:00
LisoUseInAIKyrios
caf3b69731 refactor(YouTube - Change header): Handle importing bad settings data 2025-07-05 13:03:41 +04:00
LisoUseInAIKyrios
3135203b55 chore: Set untranslatable strings as untranslatable 2025-07-05 12:33:07 +04:00
semantic-release-bot
8d113a7c67 chore: Release v5.31.0-dev.5 [skip ci]
# [5.31.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.4...v5.31.0-dev.5) (2025-07-05)

### Features

* **YouTube - Change header:** Add in-app setting to change the app header ([#5346](https://github.com/ReVanced/revanced-patches/issues/5346)) ([4e74207](4e742075f3))
2025-07-05 08:06:28 +00:00
LisoUseInAIKyrios
4e742075f3 feat(YouTube - Change header): Add in-app setting to change the app header (#5346) 2025-07-05 12:02:58 +04:00
github-actions[bot]
04caa66662 chore: Sync translations (#5350) 2025-07-05 12:02:36 +04:00
semantic-release-bot
dacc85f5e7 chore: Release v5.31.0-dev.4 [skip ci]
# [5.31.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.3...v5.31.0-dev.4) (2025-07-04)

### Bug Fixes

* **YouTube - Hide ads:** Hide new type of general ad ([#5345](https://github.com/ReVanced/revanced-patches/issues/5345)) ([f9abec3](f9abec358a))
2025-07-04 20:09:13 +00:00
ILoveOpenSourceApplications
f9abec358a fix(YouTube - Hide ads): Hide new type of general ad (#5345) 2025-07-05 00:06:30 +04:00
github-actions[bot]
7e11514cc1 chore: Sync translations (#5347) 2025-07-05 00:06:16 +04:00
semantic-release-bot
2e9c8df8f6 chore: Release v5.31.0-dev.3 [skip ci]
# [5.31.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.2...v5.31.0-dev.3) (2025-07-04)

### Bug Fixes

* **Spotify:** Remove other ads type from the browse screen ([#5333](https://github.com/ReVanced/revanced-patches/issues/5333)) ([4c8cfc8](4c8cfc8800))
2025-07-04 08:44:24 +00:00
brosssh
4c8cfc8800 fix(Spotify): Remove other ads type from the browse screen (#5333) 2025-07-04 12:41:30 +04:00
semantic-release-bot
0ba6fad33f chore: Release v5.31.0-dev.2 [skip ci]
# [5.31.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.1...v5.31.0-dev.2) (2025-07-04)

### Features

* **YouTube - Hide layout components:** Add `Hide channel links preview` and `Hide 'Visit Community' button` in channel page ([#5320](https://github.com/ReVanced/revanced-patches/issues/5320)) ([3eac215](3eac215e13))
2025-07-04 08:35:55 +00:00
ILoveOpenSourceApplications
3eac215e13 feat(YouTube - Hide layout components): Add Hide channel links preview and Hide 'Visit Community' button in channel page (#5320) 2025-07-04 12:32:48 +04:00
semantic-release-bot
90a3262f68 chore: Release v5.31.0-dev.1 [skip ci]
# [5.31.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.30.0...v5.31.0-dev.1) (2025-07-04)

### Bug Fixes

* **SoundCloud:** Constrain patches to last working app target ([89ec5d5](89ec5d5bc6))

### Features

* **Lightroom:** Constrain patches to last working version ([#5335](https://github.com/ReVanced/revanced-patches/issues/5335)) ([f7f49b8](f7f49b834e))
2025-07-04 08:32:35 +00:00
LisoUseInAIKyrios
f7f49b834e feat(Lightroom): Constrain patches to last working version (#5335) 2025-07-04 12:29:45 +04:00
LisoUseInAIKyrios
89ec5d5bc6 fix(SoundCloud): Constrain patches to last working app target 2025-07-04 12:28:58 +04:00
LisoUseInAIKyrios
e3bc8be936 chore(YouTube - Video Quality): Fix setting parent typo 2025-07-04 01:25:56 +04:00
semantic-release-bot
6c5c3f5a4d chore: Release v5.30.0 [skip ci]
# [5.30.0](https://github.com/ReVanced/revanced-patches/compare/v5.29.0...v5.30.0) (2025-07-02)

### Bug Fixes

* **Spotify - Spoof client patch:** Block sending bad integrity verdicts to potentially fix account suspensions ([#5274](https://github.com/ReVanced/revanced-patches/issues/5274)) ([69600d0](69600d08a4))
* **Spotify - Spoof client:** Handle remaining edge cases to obtain a session ([#5285](https://github.com/ReVanced/revanced-patches/issues/5285)) ([b2e601f](b2e601f0f0))
* **Spotify - Spoof client:** Skip native login screens ([#5228](https://github.com/ReVanced/revanced-patches/issues/5228)) ([d7ed325](d7ed32571f))
* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([8b9e044](8b9e04475d))
* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions by simplifying fingerprint ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([d1313e3](d1313e3ea1))
* **Spotify:** Add `Spoof client` patch to fix various issues by using a web platform access token ([#5173](https://github.com/ReVanced/revanced-patches/issues/5173)) ([1a8aacd](1a8aacdff6))
* **YouTube - Hide ads:** Fix "Hide shopping links" ([#5267](https://github.com/ReVanced/revanced-patches/issues/5267)) ([e169056](e169056b70))
* **YouTube - Hide layout components:** Fix "Hide AI Comments summary" in Comments ([#5284](https://github.com/ReVanced/revanced-patches/issues/5284)) ([f084743](f08474369b))
* **YouTube - Hide layout components:** Fix "Hide AI-generated video summary" in video description ([#5269](https://github.com/ReVanced/revanced-patches/issues/5269)) ([ca694c7](ca694c78d2))
* **YouTube - Hide layout components:** Fix "Hide ticket shelf" hiding unwanted components ([#5292](https://github.com/ReVanced/revanced-patches/issues/5292)) ([ad6da67](ad6da67281))
* **YouTube - Hide Shorts components:** Fix hiding of untoggled components ([#5266](https://github.com/ReVanced/revanced-patches/issues/5266)) ([b6bf1e0](b6bf1e026c))
* **YouTube - SponsorBlock:** Do not show undo skip if PiP is active ([#5314](https://github.com/ReVanced/revanced-patches/issues/5314)) ([209a3a3](209a3a3626))

### Features

* **Spotify:** Remove ads section from browse ([#5193](https://github.com/ReVanced/revanced-patches/issues/5193)) ([92b588c](92b588c866))
* **YouTube - Hide layout components:** Add `Hide in history` option to filter bar ([#5271](https://github.com/ReVanced/revanced-patches/issues/5271)) ([da20e56](da20e565cd))
* **YouTube - SponsorBlock:** Add "Undo automatic skip toast" ([#5277](https://github.com/ReVanced/revanced-patches/issues/5277)) ([6ee94f8](6ee94f8532))
2025-07-02 14:55:17 +00:00
oSumAtrIX
629bd0644b chore: Merge branch dev to main (#5265) 2025-07-02 16:50:31 +02:00
github-actions[bot]
b4005079e3 chore: Sync translations (#5322) 2025-07-02 18:21:04 +04:00
semantic-release-bot
a354c443ad chore: Release v5.30.0-dev.10 [skip ci]
# [5.30.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.9...v5.30.0-dev.10) (2025-07-02)

### Bug Fixes

* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions by simplifying fingerprint ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([d1313e3](d1313e3ea1))
2025-07-02 14:08:17 +00:00
oSumAtrIX
d1313e3ea1 fix(Spotify - Unlock Premium): Fix hiding context menu ads on newest versions by simplifying fingerprint (#5318) 2025-07-02 16:04:26 +02:00
semantic-release-bot
11338008c6 chore: Release v5.30.0-dev.9 [skip ci]
# [5.30.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.8...v5.30.0-dev.9) (2025-07-02)

### Bug Fixes

* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([8b9e044](8b9e04475d))
2025-07-02 12:12:04 +00:00
Nuckyz
8b9e04475d fix(Spotify - Unlock Premium): Fix hiding context menu ads on newest versions (#5318)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-07-02 14:08:11 +02:00
semantic-release-bot
d3c9dc6ed7 chore: Release v5.30.0-dev.8 [skip ci]
# [5.30.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.7...v5.30.0-dev.8) (2025-07-02)

### Bug Fixes

* **Spotify - Spoof client:** Skip native login screens ([#5228](https://github.com/ReVanced/revanced-patches/issues/5228)) ([d7ed325](d7ed32571f))
2025-07-02 10:23:13 +00:00
brosssh
d7ed32571f fix(Spotify - Spoof client): Skip native login screens (#5228)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Dawid Krajcarz <80264606+drobotk@users.noreply.github.com>
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-07-02 12:19:20 +02:00
semantic-release-bot
d3935f03c0 chore: Release v5.30.0-dev.7 [skip ci]
# [5.30.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.6...v5.30.0-dev.7) (2025-07-01)

### Bug Fixes

* **Spotify - Spoof client:** Handle remaining edge cases to obtain a session ([#5285](https://github.com/ReVanced/revanced-patches/issues/5285)) ([b2e601f](b2e601f0f0))
2025-07-01 21:15:22 +00:00
oSumAtrIX
b2e601f0f0 fix(Spotify - Spoof client): Handle remaining edge cases to obtain a session (#5285)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2025-07-01 23:11:05 +02:00
dependabot[bot]
d3ec219a29 chore(deps-dev): bump semantic-release from 24.2.5 to 24.2.6 (#5317) 2025-07-01 22:54:09 +04:00
semantic-release-bot
5ed07d4aaa chore: Release v5.30.0-dev.6 [skip ci]
# [5.30.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.5...v5.30.0-dev.6) (2025-07-01)

### Bug Fixes

* **YouTube - SponsorBlock:** Do not show undo skip if PiP is active ([#5314](https://github.com/ReVanced/revanced-patches/issues/5314)) ([209a3a3](209a3a3626))
2025-07-01 17:40:13 +00:00
LisoUseInAIKyrios
209a3a3626 fix(YouTube - SponsorBlock): Do not show undo skip if PiP is active (#5314) 2025-07-01 21:36:08 +04:00
github-actions[bot]
2b3419571f chore: Sync translations (#5315) 2025-07-01 21:35:50 +04:00
ILoveOpenSourceApplications
bbe504e616 refactor(YouTube): Match YouTube naming and sort strings (#5309) 2025-07-01 00:12:01 +04:00
semantic-release-bot
6c32591f62 chore: Release v5.30.0-dev.5 [skip ci]
# [5.30.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.4...v5.30.0-dev.5) (2025-06-30)

### Bug Fixes

* **YouTube - Hide layout components:** Fix "Hide ticket shelf" hiding unwanted components ([#5292](https://github.com/ReVanced/revanced-patches/issues/5292)) ([ad6da67](ad6da67281))
2025-06-30 08:01:39 +00:00
ILoveOpenSourceApplications
ad6da67281 fix(YouTube - Hide layout components): Fix "Hide ticket shelf" hiding unwanted components (#5292) 2025-06-30 11:58:01 +04:00
github-actions[bot]
14dc593eba chore: Sync translations (#5294) 2025-06-30 11:57:44 +04:00
semantic-release-bot
e52ee41222 chore: Release v5.30.0-dev.4 [skip ci]
# [5.30.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.3...v5.30.0-dev.4) (2025-06-30)

### Features

* **YouTube - SponsorBlock:** Add "Undo automatic skip toast" ([#5277](https://github.com/ReVanced/revanced-patches/issues/5277)) ([6ee94f8](6ee94f8532))
2025-06-30 06:54:53 +00:00
MarcaD
6ee94f8532 feat(YouTube - SponsorBlock): Add "Undo automatic skip toast" (#5277)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-06-30 10:50:52 +04:00
semantic-release-bot
21688201af chore: Release v5.30.0-dev.3 [skip ci]
# [5.30.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.2...v5.30.0-dev.3) (2025-06-28)

### Bug Fixes

* **YouTube - Hide layout components:** Fix "Hide AI Comments summary" in Comments ([#5284](https://github.com/ReVanced/revanced-patches/issues/5284)) ([f084743](f08474369b))
2025-06-28 18:05:53 +00:00
ILoveOpenSourceApplications
f08474369b fix(YouTube - Hide layout components): Fix "Hide AI Comments summary" in Comments (#5284) 2025-06-28 22:02:03 +04:00
LisoUseInAIKyrios
ed617094ea refactor(YouTube - Litho): Use a simpler hook that does not require using a thread local (#5281) 2025-06-28 11:51:29 +04:00
semantic-release-bot
9131c50f1b chore: Release v5.30.0-dev.2 [skip ci]
# [5.30.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.1...v5.30.0-dev.2) (2025-06-27)

### Bug Fixes

* **Spotify - Spoof client patch:** Block sending bad integrity verdicts to potentially fix account suspensions ([#5274](https://github.com/ReVanced/revanced-patches/issues/5274)) ([69600d0](69600d08a4))
2025-06-27 14:07:00 +00:00
brosssh
69600d08a4 fix(Spotify - Spoof client patch): Block sending bad integrity verdicts to potentially fix account suspensions (#5274)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-06-27 16:03:07 +02:00
semantic-release-bot
5dba77612b chore: Release v5.30.0-dev.1 [skip ci]
# [5.30.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.29.1-dev.1...v5.30.0-dev.1) (2025-06-27)

### Bug Fixes

* **YouTube - Hide ads:** Fix "Hide shopping links" ([#5267](https://github.com/ReVanced/revanced-patches/issues/5267)) ([e169056](e169056b70))
* **YouTube - Hide layout components:** Fix "Hide AI-generated video summary" in video description ([#5269](https://github.com/ReVanced/revanced-patches/issues/5269)) ([ca694c7](ca694c78d2))
* **YouTube - Hide Shorts components:** Fix hiding of untoggled components ([#5266](https://github.com/ReVanced/revanced-patches/issues/5266)) ([b6bf1e0](b6bf1e026c))

### Features

* **Spotify:** Remove ads section from browse ([#5193](https://github.com/ReVanced/revanced-patches/issues/5193)) ([92b588c](92b588c866))
* **YouTube - Hide layout components:** Add `Hide in history` option to filter bar ([#5271](https://github.com/ReVanced/revanced-patches/issues/5271)) ([da20e56](da20e565cd))
2025-06-27 11:38:02 +00:00
brosssh
92b588c866 feat(Spotify): Remove ads section from browse (#5193) 2025-06-27 15:34:13 +04:00
ILoveOpenSourceApplications
da20e565cd feat(YouTube - Hide layout components): Add Hide in history option to filter bar (#5271) 2025-06-27 15:33:53 +04:00
ILoveOpenSourceApplications
ca694c78d2 fix(YouTube - Hide layout components): Fix "Hide AI-generated video summary" in video description (#5269) 2025-06-27 15:33:37 +04:00
ILoveOpenSourceApplications
e169056b70 fix(YouTube - Hide ads): Fix "Hide shopping links" (#5267) 2025-06-27 15:33:21 +04:00
ILoveOpenSourceApplications
b6bf1e026c fix(YouTube - Hide Shorts components): Fix hiding of untoggled components (#5266) 2025-06-27 15:33:02 +04:00
github-actions[bot]
9fa89d48c0 chore: Sync translations (#5272) 2025-06-27 15:32:34 +04:00
semantic-release-bot
5d2c21540c chore: Release v5.29.1-dev.1 [skip ci]
## [5.29.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.29.0...v5.29.1-dev.1) (2025-06-26)

### Bug Fixes

* **Spotify:** Add `Spoof client` patch to fix various issues by using a web platform access token ([#5173](https://github.com/ReVanced/revanced-patches/issues/5173)) ([1a8aacd](1a8aacdff6))
2025-06-26 17:56:10 +00:00
oSumAtrIX
1a8aacdff6 fix(Spotify): Add Spoof client patch to fix various issues by using a web platform access token (#5173)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: brosssh <tiabroch@gmail.com>
Co-authored-by: Dawid Krajcarz <80264606+drobotk@users.noreply.github.com>
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-06-26 19:51:18 +02:00
semantic-release-bot
1804bd9bfc chore: Release v5.29.0 [skip ci]
# [5.29.0](https://github.com/ReVanced/revanced-patches/compare/v5.28.0...v5.29.0) (2025-06-26)

### Bug Fixes

* Add scrollable content to modern style settings dialogs ([#5211](https://github.com/ReVanced/revanced-patches/issues/5211)) ([2b62fc2](2b62fc2224))
* **Google Photos:** Resolve startup crash for Android 5.0 devices ([7be3741](7be374100b))
* **YouTube - Hide ads:** Hide new type of product ad in video description ([#5225](https://github.com/ReVanced/revanced-patches/issues/5225)) ([b656976](b65697603d))
* **YouTube - Hide layout components:** Fix "Hide video description attributes" ([#5250](https://github.com/ReVanced/revanced-patches/issues/5250)) ([978c244](978c24458b))
* **YouTube - Hide Shorts components:** Fix "Hide Use this sound button" ([#5233](https://github.com/ReVanced/revanced-patches/issues/5233)) ([a678f17](a678f178e1))
* **YouTube - Hide Shorts components:** Fix "Hide Use this template button" ([#5249](https://github.com/ReVanced/revanced-patches/issues/5249)) ([957bece](957bece3e9))
* **YouTube:** Always use single threaded layout to resolve layout bugs in unpatched YouTube ([#5226](https://github.com/ReVanced/revanced-patches/issues/5226)) ([ccd1691](ccd169121a))
* **YouTube:** Fix refactoring app startup exception ([0dbd058](0dbd058099))

### Features

* Add `Spoof app signature` patch ([#5158](https://github.com/ReVanced/revanced-patches/issues/5158)) ([fb83e58](fb83e58f79))
* **Cricbuzz:** Add `Hide ads` patch ([#4998](https://github.com/ReVanced/revanced-patches/issues/4998)) ([558bf8b](558bf8bca8))
* **Crunchyroll:** Add `Hide ads` patch ([#5201](https://github.com/ReVanced/revanced-patches/issues/5201)) ([d338989](d338989cb4))
* **YouTube - Hide Shorts components:** Add `Hide Effects button` ([#5255](https://github.com/ReVanced/revanced-patches/issues/5255)) ([29c86ac](29c86ac6a3))
* **YouTube - Hide video action buttons:** Add `Hide Stop ads` ([#5245](https://github.com/ReVanced/revanced-patches/issues/5245)) ([0e63f49](0e63f49e13))
* **YouTube:** Add an option to disable toasts when changing default playback speed or quality ([#5230](https://github.com/ReVanced/revanced-patches/issues/5230)) ([6b719df](6b719dfcd7))
* **YouTube:** Support version `20.13.41` ([#5253](https://github.com/ReVanced/revanced-patches/issues/5253)) ([439ca37](439ca37e99))
2025-06-26 08:06:08 +00:00
LisoUseInAIKyrios
7eb4e62762 chore: Merge branch dev to main (#5227) 2025-06-26 12:02:33 +04:00
LisoUseInAIKyrios
b8e10b5c1f chore: Fix api dump 2025-06-26 11:51:54 +04:00
github-actions[bot]
a7c11b9b08 chore: Sync translations (#5258) 2025-06-26 11:50:50 +04:00
semantic-release-bot
443c0a74d5 chore: Release v5.29.0-dev.11 [skip ci]
# [5.29.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.10...v5.29.0-dev.11) (2025-06-26)

### Features

* **Cricbuzz:** Add `Hide ads` patch ([#4998](https://github.com/ReVanced/revanced-patches/issues/4998)) ([558bf8b](558bf8bca8))
2025-06-26 07:13:07 +00:00
github-actions[bot]
84a0f7f7d7 chore: Sync translations (#5257) 2025-06-26 11:10:05 +04:00
Sourav Agrawal
558bf8bca8 feat(Cricbuzz): Add Hide ads patch (#4998) 2025-06-26 11:08:22 +04:00
semantic-release-bot
e22d4e6a4b chore: Release v5.29.0-dev.10 [skip ci]
# [5.29.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.9...v5.29.0-dev.10) (2025-06-25)

### Features

* **YouTube - Hide Shorts components:** Add `Hide Effects button` ([#5255](https://github.com/ReVanced/revanced-patches/issues/5255)) ([29c86ac](29c86ac6a3))
2025-06-25 08:59:45 +00:00
LisoUseInAIKyrios
a07f946633 chore: Fix typo 2025-06-25 12:56:42 +04:00
LisoUseInAIKyrios
29c86ac6a3 feat(YouTube - Hide Shorts components): Add Hide Effects button (#5255) 2025-06-25 12:54:59 +04:00
semantic-release-bot
19cf5667d8 chore: Release v5.29.0-dev.9 [skip ci]
# [5.29.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.8...v5.29.0-dev.9) (2025-06-25)

### Features

* Add `Spoof app signature` patch ([#5158](https://github.com/ReVanced/revanced-patches/issues/5158)) ([fb83e58](fb83e58f79))
2025-06-25 07:16:33 +00:00
Markus Probst
fb83e58f79 feat: Add Spoof app signature patch (#5158) 2025-06-25 11:13:32 +04:00
semantic-release-bot
9844081d04 chore: Release v5.29.0-dev.8 [skip ci]
# [5.29.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.7...v5.29.0-dev.8) (2025-06-25)

### Features

* **YouTube:** Support version `20.13.41` ([#5253](https://github.com/ReVanced/revanced-patches/issues/5253)) ([439ca37](439ca37e99))
2025-06-25 07:06:01 +00:00
LisoUseInAIKyrios
439ca37e99 feat(YouTube): Support version 20.13.41 (#5253) 2025-06-25 11:03:25 +04:00
semantic-release-bot
113a3d9f19 chore: Release v5.29.0-dev.7 [skip ci]
# [5.29.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.6...v5.29.0-dev.7) (2025-06-24)

### Bug Fixes

* **YouTube - Hide layout components:** Fix "Hide video description attributes" ([#5250](https://github.com/ReVanced/revanced-patches/issues/5250)) ([978c244](978c24458b))
* **YouTube - Hide Shorts components:** Fix "Hide Use this template button" ([#5249](https://github.com/ReVanced/revanced-patches/issues/5249)) ([957bece](957bece3e9))
2025-06-24 18:50:18 +00:00
ILoveOpenSourceApplications
978c24458b fix(YouTube - Hide layout components): Fix "Hide video description attributes" (#5250) 2025-06-24 22:47:46 +04:00
ILoveOpenSourceApplications
957bece3e9 fix(YouTube - Hide Shorts components): Fix "Hide Use this template button" (#5249) 2025-06-24 22:47:05 +04:00
github-actions[bot]
d32c3ac51d chore: Sync translations (#5251) 2025-06-24 22:46:50 +04:00
LisoUseInAIKyrios
26102a70a2 chore: Fix compile warning 2025-06-24 22:43:25 +04:00
semantic-release-bot
2b44bf4c23 chore: Release v5.29.0-dev.6 [skip ci]
# [5.29.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.5...v5.29.0-dev.6) (2025-06-24)

### Features

* **YouTube - Hide video action buttons:** Add `Hide Stop ads` ([#5245](https://github.com/ReVanced/revanced-patches/issues/5245)) ([0e63f49](0e63f49e13))
2025-06-24 11:14:05 +00:00
ILoveOpenSourceApplications
0e63f49e13 feat(YouTube - Hide video action buttons): Add Hide Stop ads (#5245) 2025-06-24 15:10:23 +04:00
semantic-release-bot
674a5b8d29 chore: Release v5.29.0-dev.5 [skip ci]
# [5.29.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.4...v5.29.0-dev.5) (2025-06-23)

### Bug Fixes

* **Google Photos:** Resolve startup crash for Android 5.0 devices ([7be3741](7be374100b))
2025-06-23 12:28:49 +00:00
LisoUseInAIKyrios
7be374100b fix(Google Photos): Resolve startup crash for Android 5.0 devices 2025-06-23 16:24:42 +04:00
semantic-release-bot
e48c152b95 chore: Release v5.29.0-dev.4 [skip ci]
# [5.29.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.3...v5.29.0-dev.4) (2025-06-23)

### Bug Fixes

* **YouTube - Hide Shorts components:** Fix "Hide Use this sound button" ([#5233](https://github.com/ReVanced/revanced-patches/issues/5233)) ([a678f17](a678f178e1))
2025-06-23 09:33:13 +00:00
ILoveOpenSourceApplications
a678f178e1 fix(YouTube - Hide Shorts components): Fix "Hide Use this sound button" (#5233) 2025-06-23 13:29:53 +04:00
semantic-release-bot
2d8f5641f9 chore: Release v5.29.0-dev.3 [skip ci]
# [5.29.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.2...v5.29.0-dev.3) (2025-06-23)

### Bug Fixes

* **YouTube:** Fix refactoring app startup exception ([0dbd058](0dbd058099))
2025-06-23 09:18:27 +00:00
LisoUseInAIKyrios
0dbd058099 fix(YouTube): Fix refactoring app startup exception 2025-06-23 13:15:43 +04:00
semantic-release-bot
c1a8fd0766 chore: Release v5.29.0-dev.2 [skip ci]
# [5.29.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.1...v5.29.0-dev.2) (2025-06-23)

### Features

* **Crunchyroll:** Add `Hide ads` patch ([#5201](https://github.com/ReVanced/revanced-patches/issues/5201)) ([d338989](d338989cb4))
2025-06-23 08:47:04 +00:00
hoodles
d338989cb4 feat(Crunchyroll): Add Hide ads patch (#5201) 2025-06-23 12:44:07 +04:00
github-actions[bot]
b94daacf01 chore: Sync translations (#5236) 2025-06-23 12:43:45 +04:00
semantic-release-bot
c764c4f197 chore: Release v5.29.0-dev.1 [skip ci]
# [5.29.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.28.1-dev.2...v5.29.0-dev.1) (2025-06-23)

### Bug Fixes

* **YouTube:** Always use single threaded layout to resolve layout bugs in unpatched YouTube ([#5226](https://github.com/ReVanced/revanced-patches/issues/5226)) ([ccd1691](ccd169121a))

### Features

* **YouTube:** Add an option to disable toasts when changing default playback speed or quality ([#5230](https://github.com/ReVanced/revanced-patches/issues/5230)) ([6b719df](6b719dfcd7))
2025-06-23 08:24:13 +00:00
MarcaD
6b719dfcd7 feat(YouTube): Add an option to disable toasts when changing default playback speed or quality (#5230) 2025-06-23 12:20:37 +04:00
LisoUseInAIKyrios
ccd169121a fix(YouTube): Always use single threaded layout to resolve layout bugs in unpatched YouTube (#5226) 2025-06-23 12:19:07 +04:00
semantic-release-bot
dcfbd8bf93 chore: Release v5.28.1-dev.2 [skip ci]
## [5.28.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.1-dev.1...v5.28.1-dev.2) (2025-06-23)

### Bug Fixes

* **YouTube - Hide ads:** Hide new type of product ad in video description ([#5225](https://github.com/ReVanced/revanced-patches/issues/5225)) ([b656976](b65697603d))
2025-06-23 07:19:38 +00:00
ILoveOpenSourceApplications
b65697603d fix(YouTube - Hide ads): Hide new type of product ad in video description (#5225) 2025-06-23 11:17:08 +04:00
semantic-release-bot
25da5cca8b chore: Release v5.28.1-dev.1 [skip ci]
## [5.28.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.28.0...v5.28.1-dev.1) (2025-06-22)

### Bug Fixes

* Add scrollable content to modern style settings dialogs ([#5211](https://github.com/ReVanced/revanced-patches/issues/5211)) ([2b62fc2](2b62fc2224))
2025-06-22 11:23:52 +00:00
MarcaD
2b62fc2224 fix: Add scrollable content to modern style settings dialogs (#5211) 2025-06-22 15:21:00 +04:00
semantic-release-bot
a9e9456b6b chore: Release v5.28.0 [skip ci]
# [5.28.0](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0) (2025-06-20)

### Bug Fixes

* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([1cea6bf](1cea6bfdff))
* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([a8d2a1e](a8d2a1e028))
* **Pandora - Disable ads:** Support latest app target ([#5185](https://github.com/ReVanced/revanced-patches/issues/5185)) ([90868ff](90868ff025))
* **Spotify:** Fix `Hide Create button` and `Sanitize sharing links` for older but supported app targets ([#5159](https://github.com/ReVanced/revanced-patches/issues/5159)) ([5540136](55401368b8))
* **Threads - Hide ads:** Constrain patch to the last working app target ([#5189](https://github.com/ReVanced/revanced-patches/issues/5189)) ([e138501](e138501657))
* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([e790cfb](e790cfbf59))

### Features

* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([7bbaca7](7bbaca77ad))
* Use modern style settings dialogs ([#5109](https://github.com/ReVanced/revanced-patches/issues/5109)) ([a426e2a](a426e2af50))
2025-06-20 08:17:57 +00:00
LisoUseInAIKyrios
b01523e97d chore: Merge branch dev to main (#5160) 2025-06-20 12:14:29 +04:00
github-actions[bot]
b8afb4e821 chore: Sync translations (#5210) 2025-06-20 12:13:39 +04:00
github-actions[bot]
0d2198faed chore: Sync translations (#5207) 2025-06-20 01:33:20 +04:00
semantic-release-bot
5c7c407b82 chore: Release v5.28.0-dev.8 [skip ci]
# [5.28.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.7...v5.28.0-dev.8) (2025-06-19)

### Bug Fixes

* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([a8d2a1e](a8d2a1e028))
2025-06-19 06:10:06 +00:00
Dawid Krajcarz
a8d2a1e028 fix(Messenger - Remove Meta AI): Improve patch logic (#5153) 2025-06-19 10:06:46 +04:00
semantic-release-bot
d31624cae8 chore: Release v5.28.0-dev.7 [skip ci]
# [5.28.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.6...v5.28.0-dev.7) (2025-06-18)

### Bug Fixes

* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([e790cfb](e790cfbf59))
2025-06-18 10:38:43 +00:00
LisoUseInAIKyrios
e790cfbf59 fix(YouTube): Remove old app targets that are no longer supported by YouTube (#5192) 2025-06-18 12:35:43 +02:00
LisoUseInAIKyrios
a54d408d3e chore: Fix string typos, fix missing long/wide returnEarly/returnLate 2025-06-18 11:06:48 +02:00
503 changed files with 36598 additions and 21621 deletions

View File

@@ -72,6 +72,7 @@ body:
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-patches/issues?q=label%3A%22Bug+report%22).
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md).
- **Check the troubleshooting guide**: A solution to your issue might be found in the [FAQ](https://github.com/ReVanced/revanced-documentation/blob/main/docs/revanced-resources/questions.md) or the [troubleshooting guide](https://github.com/ReVanced/revanced-documentation/blob/main/docs/revanced-resources/troubleshooting.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea
attributes:

View File

@@ -72,6 +72,7 @@ body:
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-patches/issues?q=label%3A%22Feature+request%22).
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-patches/blob/main/CONTRIBUTING.md).
- **Check the troubleshooting guide**: Information about your issue might be found in the [FAQ](https://github.com/ReVanced/revanced-documentation/blob/main/docs/revanced-resources/questions.md) or the [troubleshooting guide](https://github.com/ReVanced/revanced-documentation/blob/main/docs/revanced-resources/troubleshooting.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea
attributes:

View File

@@ -13,8 +13,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v4

View File

@@ -17,7 +17,6 @@ jobs:
uses: actions/checkout@v4
with:
ref: dev
fetch-depth: 0
clean: true
- name: Pull strings

View File

@@ -15,8 +15,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Preprocess strings
env:

View File

@@ -19,8 +19,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v4

File diff suppressed because it is too large Load Diff

8
adsfund.json Normal file
View File

@@ -0,0 +1,8 @@
{
"info": "This is verification file for ads.fund project",
"project": {
"name": "Revanced Patches",
"walletAddress": "0x7ab4091e00363654bf84B34151225742cd92FCE5",
"tokenAddress": "0xadf325f255083a3f3d9a9d01ffb3db52a148d802"
}
}

3
build.gradle.kts Normal file
View File

@@ -0,0 +1,3 @@
plugins {
alias(libs.plugins.android.library) apply false
}

View File

@@ -0,0 +1,5 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,22 @@
package app.revanced.extension.baconreader;
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
import okhttp3.OkHttpClient;
/**
* @noinspection unused
*/
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
static {
INSTANCE = new FixRedgifsApiPatch();
}
public String getDefaultUserAgent() {
// BaconReader uses a static user agent for Redgifs API calls
return "BaconReader";
}
public static OkHttpClient install(OkHttpClient.Builder builder) {
return builder.addInterceptor(INSTANCE).build();
}
}

View File

@@ -1,4 +1,6 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:boostforreddit:stub"))
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -0,0 +1,22 @@
package app.revanced.extension.boostforreddit;
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
import okhttp3.OkHttpClient;
/**
* @noinspection unused
*/
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
static {
INSTANCE = new FixRedgifsApiPatch();
}
public String getDefaultUserAgent() {
// Boost uses a static user agent for Redgifs API calls
return "Boost";
}
public static OkHttpClient createClient() {
return new OkHttpClient.Builder().addInterceptor(INSTANCE).build();
}
}

View File

@@ -1,5 +1,5 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
alias(libs.plugins.android.library)
}
android {

View File

@@ -0,0 +1,4 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:cricbuzz:stub"))
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,28 @@
package app.revanced.extension.cricbuzz.ads;
import com.cricbuzz.android.data.rest.model.BottomBar;
import java.util.List;
import java.util.Iterator;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class HideAdsPatch {
/**
* Injection point.
*/
public static void filterCb11(List<BottomBar> list) {
try {
Iterator<BottomBar> iterator = list.iterator();
while (iterator.hasNext()) {
BottomBar bar = iterator.next();
if (bar.getName().equals("Cricbuzz11")) {
Logger.printInfo(() -> "Removing Cricbuzz11 bar: " + bar);
iterator.remove();
}
}
} catch (Exception ex) {
Logger.printException(() -> "filterCb11 failure", ex);
}
}
}

View File

@@ -0,0 +1,17 @@
plugins {
alias(libs.plugins.android.library)
}
android {
namespace = "app.revanced.extension"
compileSdk = 34
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,5 @@
package com.cricbuzz.android.data.rest.model;
public final class BottomBar {
public final String getName() { throw new UnsupportedOperationException(); }
}

View File

@@ -0,0 +1,3 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,18 @@
package app.revanced.extension.instagram.feed;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unused")
public class LimitFeedToFollowedProfiles {
/**
* Injection point.
*/
public static Map<String, String> setFollowingHeader(Map<String, String> requestHeaderMap) {
// Create new map as original is unmodifiable.
Map<String, String> patchedRequestHeaderMap = new HashMap<>(requestHeaderMap);
patchedRequestHeaderMap.put("pagination_source", "following");
return patchedRequestHeaderMap;
}
}

View File

@@ -1,15 +1,23 @@
package app.revanced.extension.messenger.metaai;
import java.util.*;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class RemoveMetaAIPatch {
private static final Set<Long> loggedIDs = Collections.synchronizedSet(new HashSet<>());
public static boolean overrideBooleanFlag(long id, boolean value) {
// This catches all flag IDs related to Meta AI.
// The IDs change slightly with every update,
// so to work around this, IDs from different versions were compared
// to find what they have in common, which turned out to be those first bits.
// TODO: Find the specific flags that we care about and patch the code they control instead.
if ((id & 0x7FFFFFC000000000L) == 0x810A8000000000L) {
return false;
try {
if (Long.toString(id).startsWith("REPLACED_BY_PATCH")) {
if (loggedIDs.add(id))
Logger.printInfo(() -> "Overriding " + id + " from " + value + " to false");
return false;
}
} catch (Exception ex) {
Logger.printException(() -> "overrideBooleanFlag failure", ex);
}
return value;

View File

@@ -1,3 +1,9 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:youtube:stub"))
compileOnly(libs.annotation)
}
android {
defaultConfig {
minSdk = 26

View File

@@ -0,0 +1,24 @@
package app.revanced.extension.music.patches;
import static app.revanced.extension.shared.Utils.hideViewBy0dpUnderCondition;
import android.view.View;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideCastButtonPatch {
/**
* Injection point
*/
public static int hideCastButton(int original) {
return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original;
}
/**
* Injection point
*/
public static void hideCastButton(View view) {
hideViewBy0dpUnderCondition(Settings.HIDE_CAST_BUTTON.get(), view);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideVideoAdsPatch {
/**
* Injection point
*/
public static boolean showVideoAds(boolean original) {
if (Settings.HIDE_VIDEO_ADS.get()) {
return false;
}
return original;
}
}

View File

@@ -0,0 +1,74 @@
package app.revanced.extension.music.patches;
import static app.revanced.extension.shared.Utils.hideViewUnderCondition;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class NavigationBarPatch {
@NonNull
private static String lastYTNavigationEnumName = "";
public static void setLastAppNavigationEnum(@Nullable Enum<?> ytNavigationEnumName) {
if (ytNavigationEnumName != null) {
lastYTNavigationEnumName = ytNavigationEnumName.name();
}
}
public static void hideNavigationLabel(TextView textview) {
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BAR_LABEL.get(), textview);
}
public static void hideNavigationButton(@NonNull View view) {
// Hide entire navigation bar.
if (Settings.HIDE_NAVIGATION_BAR.get() && view.getParent() != null) {
hideViewUnderCondition(true, (View) view.getParent());
return;
}
// Hide navigation buttons based on their type.
for (NavigationButton button : NavigationButton.values()) {
if (button.ytEnumNames.equals(lastYTNavigationEnumName)) {
hideViewUnderCondition(button.hidden, view);
break;
}
}
}
private enum NavigationButton {
HOME(
"TAB_HOME",
Settings.HIDE_NAVIGATION_BAR_HOME_BUTTON.get()
),
SAMPLES(
"TAB_SAMPLES",
Settings.HIDE_NAVIGATION_BAR_SAMPLES_BUTTON.get()
),
EXPLORE(
"TAB_EXPLORE",
Settings.HIDE_NAVIGATION_BAR_EXPLORE_BUTTON.get()
),
LIBRARY(
"LIBRARY_MUSIC",
Settings.HIDE_NAVIGATION_BAR_LIBRARY_BUTTON.get()
),
UPGRADE(
"TAB_MUSIC_PREMIUM",
Settings.HIDE_NAVIGATION_BAR_UPGRADE_BUTTON.get()
);
private final String ytEnumNames;
private final boolean hidden;
NavigationButton(@NonNull String ytEnumNames, boolean hidden) {
this.ytEnumNames = ytEnumNames;
this.hidden = hidden;
}
}
}

View File

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

View File

@@ -0,0 +1,28 @@
package app.revanced.extension.music.patches.spoof;
import static app.revanced.extension.music.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
import java.util.List;
import app.revanced.extension.shared.spoof.ClientType;
@SuppressWarnings("unused")
public class SpoofVideoStreamsPatch {
/**
* Injection point.
*/
public static void setClientOrderToUse() {
List<ClientType> availableClients = List.of(
ANDROID_VR_1_43_32,
ANDROID_VR_1_61_48,
VISIONOS
);
app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse(
availableClients, SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get());
}
}

View File

@@ -0,0 +1,87 @@
package app.revanced.extension.music.settings;
import android.app.Activity;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceFragment;
import android.view.View;
import app.revanced.extension.music.settings.preference.ReVancedPreferenceFragment;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook;
/**
* Hooks GoogleApiActivity to inject a custom ReVancedPreferenceFragment with a toolbar.
*/
public class GoogleApiActivityHook extends BaseActivityHook {
/**
* Injection point
* <p>
* Creates an instance of GoogleApiActivityHook for use in static initialization.
*/
@SuppressWarnings("unused")
public static GoogleApiActivityHook createInstance() {
// Must touch the Music settings to ensure the class is loaded and
// the values can be found when setting the UI preferences.
// Logging anything under non debug ensures this is set.
Logger.printInfo(() -> "Permanent repeat enabled: " + Settings.PERMANENT_REPEAT.get());
// YT Music always uses dark mode.
Utils.setIsDarkModeEnabled(true);
return new GoogleApiActivityHook();
}
/**
* Sets the fixed theme for the activity.
*/
@Override
protected void customizeActivityTheme(Activity activity) {
// Override the default YouTube Music theme to increase start padding of list items.
// Custom style located in resources/music/values/style.xml
activity.setTheme(Utils.getResourceIdentifier("Theme.ReVanced.YouTubeMusic.Settings", "style"));
}
/**
* Returns the resource ID for the YouTube Music settings layout.
*/
@Override
protected int getContentViewResourceId() {
return Utils.getResourceIdentifier("revanced_music_settings_with_toolbar", "layout");
}
/**
* Returns the fixed background color for the toolbar.
*/
@Override
protected int getToolbarBackgroundColor() {
return Utils.getResourceColor("ytm_color_black");
}
/**
* Returns the navigation icon with a color filter applied.
*/
@Override
protected Drawable getNavigationIcon() {
Drawable navigationIcon = ReVancedPreferenceFragment.getBackButtonDrawable();
navigationIcon.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
return navigationIcon;
}
/**
* Returns the click listener that finishes the activity when the navigation icon is clicked.
*/
@Override
protected View.OnClickListener getNavigationClickListener(Activity activity) {
return view -> activity.finish();
}
/**
* Creates a new ReVancedPreferenceFragment for the activity.
*/
@Override
protected PreferenceFragment createPreferenceFragment() {
return new ReVancedPreferenceFragment();
}
}

View File

@@ -0,0 +1,36 @@
package app.revanced.extension.music.settings;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.settings.Setting.parent;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.EnumSetting;
import app.revanced.extension.shared.spoof.ClientType;
public class Settings extends BaseSettings {
// Ads
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);
public static final BooleanSetting HIDE_GET_PREMIUM_LABEL = new BooleanSetting("revanced_music_hide_get_premium_label", TRUE, true);
// General
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_music_hide_cast_button", TRUE, false);
public static final BooleanSetting HIDE_CATEGORY_BAR = new BooleanSetting("revanced_music_hide_category_bar", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_HOME_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_home_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_SAMPLES_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_samples_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_EXPLORE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_explore_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_LIBRARY_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_library_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_UPGRADE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_upgrade_button", TRUE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR = new BooleanSetting("revanced_music_hide_navigation_bar", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_LABEL = new BooleanSetting("revanced_music_hide_navigation_bar_labels", FALSE, true);
// Player
public static final BooleanSetting PERMANENT_REPEAT = new BooleanSetting("revanced_music_play_permanent_repeat", FALSE, true);
// Miscellaneous
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type",
ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
}

View File

@@ -0,0 +1,38 @@
package app.revanced.extension.music.settings.preference;
import android.widget.Toolbar;
import app.revanced.extension.music.settings.GoogleApiActivityHook;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
/**
* Preference fragment for ReVanced settings.
*/
@SuppressWarnings({"deprecation", "NewApi"})
public class ReVancedPreferenceFragment extends ToolbarPreferenceFragment {
/**
* Initializes the preference fragment.
*/
@Override
protected void initialize() {
super.initialize();
try {
Utils.sortPreferenceGroups(getPreferenceScreen());
setPreferenceScreenToolbar(getPreferenceScreen());
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
/**
* Sets toolbar for all nested preference screens.
*/
@Override
protected void customizeToolbar(Toolbar toolbar) {
GoogleApiActivityHook.setToolbarLayoutParams(toolbar);
}
}

View File

@@ -1,27 +0,0 @@
package app.revanced.extension.music.spoof;
/**
* @noinspection unused
*/
public class SpoofClientPatch {
private static final int CLIENT_TYPE_ID = 26;
private static final String CLIENT_VERSION = "6.21";
private static final String DEVICE_MODEL = "iPhone16,2";
private static final String OS_VERSION = "17.7.2.21H221";
public static int getClientId() {
return CLIENT_TYPE_ID;
}
public static String getClientVersion() {
return CLIENT_VERSION;
}
public static String getClientModel() {
return DEVICE_MODEL;
}
public static String getOsVersion() {
return OS_VERSION;
}
}

View File

@@ -1,5 +1,5 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
alias(libs.plugins.android.library)
}
android {

View File

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

View File

@@ -1,5 +1,5 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
alias(libs.plugins.android.library)
}
android {

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
alias(libs.plugins.android.library)
}
android {

View File

@@ -1,3 +1,4 @@
dependencies {
implementation(project(":extensions:shared:library"))
compileOnly(libs.okhttp)
}

View File

@@ -1,5 +1,5 @@
plugins {
id("com.android.library")
alias(libs.plugins.android.library)
}
android {
@@ -18,4 +18,5 @@ android {
dependencies {
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -1,6 +1,4 @@
package app.revanced.extension.youtube;
import androidx.annotation.NonNull;
package app.revanced.extension.shared;
import java.nio.charset.StandardCharsets;
@@ -39,7 +37,7 @@ public final class ByteTrieSearch extends TrieSearch<byte[]> {
return replacement;
}
public ByteTrieSearch(@NonNull byte[]... patterns) {
public ByteTrieSearch(byte[]... patterns) {
super(new ByteTrieNode(), patterns);
}
}

View File

@@ -19,7 +19,6 @@ import android.util.Pair;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
@@ -28,7 +27,6 @@ import java.util.Locale;
import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route;
import app.revanced.extension.shared.Utils;
@SuppressWarnings("unused")
public class GmsCoreSupport {
@@ -109,7 +107,6 @@ public class GmsCoreSupport {
/**
* Injection point.
*/
@RequiresApi(api = Build.VERSION_CODES.N)
public static void checkGmsCore(Activity context) {
try {
// Verify the user has not included GmsCore for a root installation.
@@ -157,7 +154,9 @@ public class GmsCoreSupport {
}
// Check if GmsCore is currently running in the background.
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) {
var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER);
//noinspection TryFinallyCanBeTryWithResources
try {
if (client == null) {
Logger.printInfo(() -> "GmsCore is not running in the background");
checkIfDontKillMyAppSupportsManufacturer();
@@ -167,6 +166,8 @@ public class GmsCoreSupport {
"gms_core_dialog_open_website_text",
(dialog, id) -> openDontKillMyApp());
}
} finally {
if (client != null) client.close();
}
} catch (Exception ex) {
Logger.printException(() -> "checkGmsCore failure", ex);
@@ -226,6 +227,11 @@ public class GmsCoreSupport {
* @return If GmsCore is not whitelisted from battery optimizations.
*/
private static boolean batteryOptimizationsEnabled(Context context) {
//noinspection ObsoleteSdkInt
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Android 5.0 does not have battery optimization settings.
return false;
}
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
}

View File

@@ -1,6 +1,4 @@
package app.revanced.extension.youtube;
import androidx.annotation.NonNull;
package app.revanced.extension.shared;
/**
* Text pattern searching using a prefix tree (trie).
@@ -28,7 +26,7 @@ public final class StringTrieSearch extends TrieSearch<String> {
}
}
public StringTrieSearch(@NonNull String... patterns) {
public StringTrieSearch(String... patterns) {
super(new StringTrieNode(), patterns);
}
}

View File

@@ -1,6 +1,5 @@
package app.revanced.extension.youtube;
package app.revanced.extension.shared;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
@@ -57,11 +56,13 @@ public abstract class TrieSearch<T> {
if (searchTextLength - searchTextIndex < patternLength - patternStartIndex) {
return false; // Remaining search text is shorter than the remaining leaf pattern and they cannot match.
}
for (int i = searchTextIndex, j = patternStartIndex; j < patternLength; i++, j++) {
if (enclosingNode.getCharValue(searchText, i) != enclosingNode.getCharValue(pattern, j)) {
return false;
}
}
return callback == null || callback.patternMatched(searchText,
searchTextIndex - patternStartIndex, patternLength, callbackParameter);
}
@@ -136,7 +137,7 @@ public abstract class TrieSearch<T> {
* @param patternLength Length of the pattern.
* @param callback Callback, where a value of NULL indicates to always accept a pattern match.
*/
private void addPattern(@NonNull T pattern, int patternIndex, int patternLength,
private void addPattern(T pattern, int patternIndex, int patternLength,
@Nullable TriePatternMatchedCallback<T> callback) {
if (patternIndex == patternLength) { // Reached the end of the pattern.
if (endOfPatternCallback == null) {
@@ -145,6 +146,7 @@ public abstract class TrieSearch<T> {
endOfPatternCallback.add(callback);
return;
}
if (leaf != null) {
// Reached end of the graph and a leaf exist.
// Recursively call back into this method and push the existing leaf down 1 level.
@@ -159,6 +161,7 @@ public abstract class TrieSearch<T> {
leaf = new TrieCompressedPath<>(pattern, patternIndex, patternLength, callback);
return;
}
final char character = getCharValue(pattern, patternIndex);
final int arrayIndex = hashIndexForTableSize(children.length, character);
TrieNode<T> child = children[arrayIndex];
@@ -183,6 +186,7 @@ public abstract class TrieSearch<T> {
//noinspection unchecked
TrieNode<T>[] replacement = new TrieNode[replacementArraySize];
addNodeToArray(replacement, child);
boolean collision = false;
for (TrieNode<T> existingChild : children) {
if (existingChild != null) {
@@ -195,6 +199,7 @@ public abstract class TrieSearch<T> {
if (collision) {
continue;
}
children = replacement;
return;
}
@@ -234,6 +239,7 @@ public abstract class TrieSearch<T> {
if (leaf != null && leaf.matches(startNode, searchText, searchTextEndIndex, searchTextIndex, callbackParameter)) {
return true; // Leaf exists and it matched the search text.
}
List<TriePatternMatchedCallback<T>> endOfPatternCallback = node.endOfPatternCallback;
if (endOfPatternCallback != null) {
final int matchStartIndex = searchTextIndex - currentMatchLength;
@@ -246,6 +252,7 @@ public abstract class TrieSearch<T> {
}
}
}
TrieNode<T>[] children = node.children;
if (children == null) {
return false; // Reached a graph end point and there's no further patterns to search.
@@ -278,9 +285,11 @@ public abstract class TrieSearch<T> {
if (leaf != null) {
numberOfPointers += 4; // Number of fields in leaf node.
}
if (endOfPatternCallback != null) {
numberOfPointers += endOfPatternCallback.size();
}
if (children != null) {
numberOfPointers += children.length;
for (TrieNode<T> child : children) {
@@ -308,13 +317,13 @@ public abstract class TrieSearch<T> {
private final List<T> patterns = new ArrayList<>();
@SafeVarargs
TrieSearch(@NonNull TrieNode<T> root, @NonNull T... patterns) {
TrieSearch(TrieNode<T> root, T... patterns) {
this.root = Objects.requireNonNull(root);
addPatterns(patterns);
}
@SafeVarargs
public final void addPatterns(@NonNull T... patterns) {
public final void addPatterns(T... patterns) {
for (T pattern : patterns) {
addPattern(pattern);
}
@@ -325,7 +334,7 @@ public abstract class TrieSearch<T> {
*
* @param pattern Pattern to add. Calling this with a zero length pattern does nothing.
*/
public void addPattern(@NonNull T pattern) {
public void addPattern(T pattern) {
addPattern(pattern, root.getTextLength(pattern), null);
}
@@ -333,31 +342,31 @@ public abstract class TrieSearch<T> {
* @param pattern Pattern to add. Calling this with a zero length pattern does nothing.
* @param callback Callback to determine if searching should halt when a match is found.
*/
public void addPattern(@NonNull T pattern, @NonNull TriePatternMatchedCallback<T> callback) {
public void addPattern(T pattern, TriePatternMatchedCallback<T> callback) {
addPattern(pattern, root.getTextLength(pattern), Objects.requireNonNull(callback));
}
void addPattern(@NonNull T pattern, int patternLength, @Nullable TriePatternMatchedCallback<T> callback) {
void addPattern(T pattern, int patternLength, @Nullable TriePatternMatchedCallback<T> callback) {
if (patternLength == 0) return; // Nothing to match
patterns.add(pattern);
root.addPattern(pattern, 0, patternLength, callback);
}
public final boolean matches(@NonNull T textToSearch) {
public final boolean matches(T textToSearch) {
return matches(textToSearch, 0);
}
public boolean matches(@NonNull T textToSearch, @NonNull Object callbackParameter) {
public boolean matches(T textToSearch, Object callbackParameter) {
return matches(textToSearch, 0, root.getTextLength(textToSearch),
Objects.requireNonNull(callbackParameter));
}
public boolean matches(@NonNull T textToSearch, int startIndex) {
public boolean matches(T textToSearch, int startIndex) {
return matches(textToSearch, startIndex, root.getTextLength(textToSearch));
}
public final boolean matches(@NonNull T textToSearch, int startIndex, int endIndex) {
public final boolean matches(T textToSearch, int startIndex, int endIndex) {
return matches(textToSearch, startIndex, endIndex, null);
}
@@ -370,11 +379,11 @@ public abstract class TrieSearch<T> {
* @param callbackParameter Optional parameter passed to the callbacks.
* @return If any pattern matched, and it's callback halted searching.
*/
public boolean matches(@NonNull T textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter) {
public boolean matches(T textToSearch, int startIndex, int endIndex, @Nullable Object callbackParameter) {
return matches(textToSearch, root.getTextLength(textToSearch), startIndex, endIndex, callbackParameter);
}
private boolean matches(@NonNull T textToSearch, int textToSearchLength, int startIndex, int endIndex,
private boolean matches(T textToSearch, int textToSearchLength, int startIndex, int endIndex,
@Nullable Object callbackParameter) {
if (endIndex > textToSearchLength) {
throw new IllegalArgumentException("endIndex: " + endIndex

View File

@@ -42,6 +42,7 @@ import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Toolbar;
@@ -115,7 +116,7 @@ public class Utils {
}
/**
* @return The version name of the app, such as 19.11.43
* @return The version name of the app, such as 20.13.41
*/
public static String getAppVersionName() {
if (versionName == null) {
@@ -310,6 +311,10 @@ public class Utils {
return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen"));
}
public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getStringArray(getResourceIdentifier(resourceIdentifierName, "array"));
}
public interface MatchFilter<T> {
boolean matches(T object);
}
@@ -324,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);
}
/**
@@ -578,7 +583,7 @@ public class Utils {
Context currentContext = context;
if (currentContext == null) {
Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast, null);
Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast);
} else {
Logger.printDebug(() -> "Showing toast: " + messageToToast);
Toast.makeText(currentContext, messageToToast, toastDuration).show();
@@ -773,16 +778,15 @@ public class Utils {
Dialog dialog = new Dialog(context);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar.
// Create main layout.
LinearLayout mainLayout = new LinearLayout(context);
mainLayout.setOrientation(LinearLayout.VERTICAL);
// Preset size constants.
final int dip4 = dipToPixels(4);
final int dip8 = dipToPixels(8);
final int dip16 = dipToPixels(16);
final int dip24 = dipToPixels(24);
// Create main layout.
LinearLayout mainLayout = new LinearLayout(context);
mainLayout.setOrientation(LinearLayout.VERTICAL);
mainLayout.setPadding(dip24, dip16, dip24, dip24);
// Set rounded rectangle background.
ShapeDrawable mainBackground = new ShapeDrawable(new RoundRectShape(
@@ -802,55 +806,71 @@ public class Utils {
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
layoutParams.setMargins(0, 0, 0, dip8);
layoutParams.setMargins(0, 0, 0, dip16);
titleView.setLayoutParams(layoutParams);
mainLayout.addView(titleView);
}
// Message (if not replaced by EditText).
if (editText == null && message != null) {
TextView messageView = new TextView(context);
messageView.setText(message); // Supports Spanned (HTML).
messageView.setTextSize(16);
messageView.setTextColor(getAppForegroundColor());
// Enable HTML link clicking if the message contains links.
if (message instanceof Spanned) {
messageView.setMovementMethod(LinkMovementMethod.getInstance());
// Create content container (message/EditText) inside a ScrollView only if message or editText is provided.
ScrollView contentScrollView = null;
LinearLayout contentContainer;
if (message != null || editText != null) {
contentScrollView = new ScrollView(context);
contentScrollView.setVerticalScrollBarEnabled(false); // Disable the vertical scrollbar.
contentScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
if (editText != null) {
ShapeDrawable scrollViewBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(10), null, null));
scrollViewBackground.getPaint().setColor(getEditTextBackground());
contentScrollView.setPadding(dip8, dip8, dip8, dip8);
contentScrollView.setBackground(scrollViewBackground);
contentScrollView.setClipToOutline(true);
}
LinearLayout.LayoutParams messageParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
0,
1.0f // Weight to take available space.
);
messageParams.setMargins(0, dip8, 0, dip16);
messageView.setLayoutParams(messageParams);
mainLayout.addView(messageView);
}
contentScrollView.setLayoutParams(contentParams);
contentContainer = new LinearLayout(context);
contentContainer.setOrientation(LinearLayout.VERTICAL);
contentScrollView.addView(contentContainer);
// EditText (if provided).
if (editText != null) {
// Remove EditText from its current parent, if any.
ViewGroup parent = (ViewGroup) editText.getParent();
if (parent != null) {
parent.removeView(editText);
// Message (if not replaced by EditText).
if (editText == null) {
TextView messageView = new TextView(context);
messageView.setText(message); // Supports Spanned (HTML).
messageView.setTextSize(16);
messageView.setTextColor(getAppForegroundColor());
// Enable HTML link clicking if the message contains links.
if (message instanceof Spanned) {
messageView.setMovementMethod(LinkMovementMethod.getInstance());
}
LinearLayout.LayoutParams messageParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
messageView.setLayoutParams(messageParams);
contentContainer.addView(messageView);
}
// Style the EditText to match the dialog theme.
editText.setTextColor(getAppForegroundColor());
editText.setBackgroundColor(isDarkModeEnabled() ? Color.BLACK : Color.WHITE);
editText.setPadding(dip8, dip8, dip8, dip8);
ShapeDrawable editTextBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(10), null, null));
editTextBackground.getPaint().setColor(getEditTextBackground()); // Background color for EditText.
editText.setBackground(editTextBackground);
LinearLayout.LayoutParams editTextParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
editTextParams.setMargins(0, dip8, 0, dip16);
// Prevent buttons from moving off the screen by fixing the height of the EditText.
final int maxHeight = (int) (context.getResources().getDisplayMetrics().heightPixels * 0.6);
editText.setMaxHeight(maxHeight);
mainLayout.addView(editText, 1, editTextParams);
// EditText (if provided).
if (editText != null) {
// Remove EditText from its current parent, if any.
ViewGroup parent = (ViewGroup) editText.getParent();
if (parent != null) {
parent.removeView(editText);
}
// Style the EditText to match the dialog theme.
editText.setTextColor(getAppForegroundColor());
editText.setBackgroundColor(Color.TRANSPARENT);
editText.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams editTextParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
contentContainer.addView(editText, editTextParams);
}
}
// Button container.
@@ -861,7 +881,7 @@ public class Utils {
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
buttonContainerParams.setMargins(0, dip8, 0, 0);
buttonContainerParams.setMargins(0, dip16, 0, 0);
buttonContainer.setLayoutParams(buttonContainerParams);
// Lists to track buttons.
@@ -1036,25 +1056,29 @@ public class Utils {
}
}
// Add ScrollView to main layout only if content exist.
if (contentScrollView != null) {
mainLayout.addView(contentScrollView);
}
mainLayout.addView(buttonContainer);
dialog.setContentView(mainLayout);
// Set dialog window attributes.
Window window = dialog.getWindow();
if (window != null) {
setDialogWindowParameters(context, window);
setDialogWindowParameters(window);
}
return new Pair<>(dialog, mainLayout);
}
public static void setDialogWindowParameters(Context context, Window window) {
public static void setDialogWindowParameters(Window window) {
WindowManager.LayoutParams params = window.getAttributes();
Resources resources = context.getResources();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
int portraitWidth = (int) (displayMetrics.widthPixels * 0.9);
if (resources.getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
if (Resources.getSystem().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
portraitWidth = (int) Math.min(portraitWidth, displayMetrics.heightPixels * 0.9);
}
params.width = portraitWidth;
@@ -1199,7 +1223,7 @@ public class Utils {
return darkColor == Color.BLACK
// Lighten the background a little if using AMOLED dark theme
// as the dialogs are almost invisible.
? 0xFF0D0D0D
? 0xFF080808 // 3%
: darkColor;
}
return getThemeLightColor();
@@ -1414,6 +1438,38 @@ public class Utils {
);
}
/**
* Converts a percentage of the screen height to actual device pixels.
*
* @param percentage The percentage of the screen height (e.g., 30 for 30%).
* @return The device pixel value corresponding to the percentage of screen height.
*/
public static int percentageHeightToPixels(int percentage) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (metrics.heightPixels * (percentage / 100.0f));
}
/**
* Converts a percentage of the screen width to actual device pixels.
*
* @param percentage The percentage of the screen width (e.g., 30 for 30%).
* @return The device pixel value corresponding to the percentage of screen width.
*/
public static int percentageWidthToPixels(int percentage) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (metrics.widthPixels * (percentage / 100.0f));
}
/**
* Uses {@link #adjustColorBrightness(int, float)} depending if light or dark mode is active.
*/
@ColorInt
public static int adjustColorBrightness(@ColorInt int baseColor, float lightThemeFactor, float darkThemeFactor) {
return isDarkModeEnabled()
? adjustColorBrightness(baseColor, darkThemeFactor)
: adjustColorBrightness(baseColor, lightThemeFactor);
}
/**
* Adjusts the brightness of a color by lightening or darkening it based on the given factor.
* <p>

View File

@@ -129,8 +129,7 @@ abstract class Check {
ImageView iconView = new ImageView(activity);
iconView.setImageResource(Utils.getResourceIdentifier("revanced_ic_dialog_alert", "drawable"));
iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
final int dip8 = dipToPixels(8);
iconView.setPadding(0, dip8, 0, dip8);
iconView.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT

View File

@@ -0,0 +1,71 @@
package app.revanced.extension.shared.fixes.redgifs;
import androidx.annotation.NonNull;
import org.json.JSONException;
import java.io.IOException;
import java.net.HttpURLConnection;
import app.revanced.extension.shared.Logger;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public abstract class BaseFixRedgifsApiPatch implements Interceptor {
protected static BaseFixRedgifsApiPatch INSTANCE;
public abstract String getDefaultUserAgent();
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request request = chain.request();
if (!request.url().host().equals("api.redgifs.com")) {
return chain.proceed(request);
}
String userAgent = getDefaultUserAgent();
if (request.header("Authorization") != null) {
Response response = chain.proceed(request.newBuilder().header("User-Agent", userAgent).build());
if (response.isSuccessful()) {
return response;
}
// It's possible that the user agent is being overwritten later down in the interceptor
// chain, so make sure we grab the new user agent from the request headers.
userAgent = response.request().header("User-Agent");
response.close();
}
try {
RedgifsTokenManager.RedgifsToken token = RedgifsTokenManager.refreshToken(userAgent);
// Emulate response for old OAuth endpoint
if (request.url().encodedPath().equals("/v2/oauth/client")) {
String responseBody = RedgifsTokenManager.getEmulatedOAuthResponseBody(token);
return new Response.Builder()
.message("OK")
.code(HttpURLConnection.HTTP_OK)
.protocol(Protocol.HTTP_1_1)
.request(request)
.header("Content-Type", "application/json")
.body(ResponseBody.create(
responseBody, MediaType.get("application/json")))
.build();
}
Request modifiedRequest = request.newBuilder()
.header("Authorization", "Bearer " + token.getAccessToken())
.header("User-Agent", userAgent)
.build();
return chain.proceed(modifiedRequest);
} catch (JSONException ex) {
Logger.printException(() -> "Could not parse Redgifs response", ex);
throw new IOException(ex);
}
}
}

View File

@@ -0,0 +1,94 @@
package app.revanced.extension.shared.fixes.redgifs;
import static app.revanced.extension.shared.requests.Route.Method.GET;
import androidx.annotation.GuardedBy;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import app.revanced.extension.shared.requests.Requester;
/**
* Manages Redgifs token lifecycle.
*/
public class RedgifsTokenManager {
public static class RedgifsToken {
// Expire after 23 hours to provide some breathing room
private static final long EXPIRY_SECONDS = 23 * 60 * 60;
private final String accessToken;
private final long refreshTimeInSeconds;
public RedgifsToken(String accessToken, long refreshTime) {
this.accessToken = accessToken;
this.refreshTimeInSeconds = refreshTime;
}
public String getAccessToken() {
return accessToken;
}
public long getExpiryTimeInSeconds() {
return refreshTimeInSeconds + EXPIRY_SECONDS;
}
public boolean isValid() {
if (accessToken == null) return false;
return getExpiryTimeInSeconds() >= System.currentTimeMillis() / 1000;
}
}
public static final String REDGIFS_API_HOST = "https://api.redgifs.com";
private static final String GET_TEMPORARY_TOKEN = REDGIFS_API_HOST + "/v2/auth/temporary";
@GuardedBy("itself")
private static final Map<String, RedgifsToken> tokenMap = new HashMap<>();
private static String getToken(String userAgent) throws IOException, JSONException {
HttpURLConnection connection = (HttpURLConnection) new URL(GET_TEMPORARY_TOKEN).openConnection();
connection.setFixedLengthStreamingMode(0);
connection.setRequestMethod(GET.name());
connection.setRequestProperty("User-Agent", userAgent);
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Accept", "application/json");
connection.setUseCaches(false);
JSONObject responseObject = Requester.parseJSONObject(connection);
return responseObject.getString("token");
}
public static RedgifsToken refreshToken(String userAgent) throws IOException, JSONException {
synchronized(tokenMap) {
// Reference: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/pull/67
RedgifsToken token = tokenMap.get(userAgent);
if (token != null && token.isValid()) {
return token;
}
// Copy user agent from original request if present because Redgifs verifies
// that the user agent in subsequent requests matches the one in the OAuth token.
String accessToken = getToken(userAgent);
long refreshTime = System.currentTimeMillis() / 1000;
token = new RedgifsToken(accessToken, refreshTime);
tokenMap.put(userAgent, token);
return token;
}
}
public static String getEmulatedOAuthResponseBody(RedgifsToken token) throws JSONException {
// Reference: https://github.com/JeffreyCA/Apollo-ImprovedCustomApi/pull/67
JSONObject responseObject = new JSONObject();
responseObject.put("access_token", token.accessToken);
responseObject.put("expiry_time", token.getExpiryTimeInSeconds() - (System.currentTimeMillis() / 1000));
responseObject.put("scope", "read");
responseObject.put("token_type", "Bearer");
return responseObject.toString();
}
}

View File

@@ -1,4 +1,4 @@
package app.revanced.extension.youtube.patches;
package app.revanced.extension.shared.patches;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

View File

@@ -0,0 +1,142 @@
package app.revanced.extension.shared.settings;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.preference.PreferenceFragment;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toolbar;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
/**
* Base class for hooking activities to inject a custom PreferenceFragment with a toolbar.
* Provides common logic for initializing the activity and setting up the toolbar.
*/
@SuppressWarnings({"deprecation", "NewApi"})
public abstract class BaseActivityHook extends Activity {
/**
* Layout parameters for the toolbar, extracted from the dummy toolbar.
*/
protected static ViewGroup.LayoutParams toolbarLayoutParams;
/**
* Sets the layout parameters for the toolbar.
*/
public static void setToolbarLayoutParams(Toolbar toolbar) {
if (toolbarLayoutParams != null) {
toolbar.setLayoutParams(toolbarLayoutParams);
}
}
/**
* Initializes the activity by setting the theme, content view and injecting a PreferenceFragment.
*/
public static void initialize(BaseActivityHook hook, Activity activity) {
try {
hook.customizeActivityTheme(activity);
activity.setContentView(hook.getContentViewResourceId());
// Sanity check.
String dataString = activity.getIntent().getDataString();
if (!"revanced_settings_intent".equals(dataString)) {
Logger.printException(() -> "Unknown intent: " + dataString);
return;
}
PreferenceFragment fragment = hook.createPreferenceFragment();
hook.createToolbar(activity, fragment);
activity.getFragmentManager()
.beginTransaction()
.replace(Utils.getResourceIdentifier("revanced_settings_fragments", "id"), fragment)
.commit();
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
/**
* Creates and configures a toolbar for the activity, replacing a dummy placeholder.
*/
@SuppressLint("UseCompatLoadingForDrawables")
protected void createToolbar(Activity activity, PreferenceFragment fragment) {
// Replace dummy placeholder toolbar.
// This is required to fix submenu title alignment issue with Android ASOP 15+
ViewGroup toolBarParent = activity.findViewById(
Utils.getResourceIdentifier("revanced_toolbar_parent", "id"));
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent, "revanced_toolbar");
toolbarLayoutParams = dummyToolbar.getLayoutParams();
toolBarParent.removeView(dummyToolbar);
// Sets appropriate system navigation bar color for the activity.
ToolbarPreferenceFragment.setNavigationBarColor(activity.getWindow());
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
toolbar.setBackgroundColor(getToolbarBackgroundColor());
toolbar.setNavigationIcon(getNavigationIcon());
toolbar.setNavigationOnClickListener(getNavigationClickListener(activity));
toolbar.setTitle(Utils.getResourceIdentifier("revanced_settings_title", "string"));
final int margin = Utils.dipToPixels(16);
toolbar.setTitleMarginStart(margin);
toolbar.setTitleMarginEnd(margin);
TextView toolbarTextView = Utils.getChildView(toolbar, false, view -> view instanceof TextView);
if (toolbarTextView != null) {
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
}
setToolbarLayoutParams(toolbar);
onPostToolbarSetup(activity, toolbar, fragment);
toolBarParent.addView(toolbar, 0);
}
/**
* Customizes the activity's theme.
*/
protected abstract void customizeActivityTheme(Activity activity);
/**
* Returns the resource ID for the content view layout.
*/
protected abstract int getContentViewResourceId();
/**
* Returns the background color for the toolbar.
*/
protected abstract int getToolbarBackgroundColor();
/**
* Returns the navigation icon drawable for the toolbar.
*/
protected abstract Drawable getNavigationIcon();
/**
* Returns the click listener for the toolbar's navigation icon.
*/
protected abstract View.OnClickListener getNavigationClickListener(Activity activity);
/**
* Creates the PreferenceFragment to be injected into the activity.
*/
protected PreferenceFragment createPreferenceFragment() {
return new ToolbarPreferenceFragment();
}
/**
* Performs additional setup after the toolbar is configured.
*
* @param activity The activity hosting the toolbar.
* @param toolbar The configured toolbar.
* @param fragment The PreferenceFragment associated with the activity.
*/
protected void onPostToolbarSetup(Activity activity, Toolbar toolbar, PreferenceFragment fragment) {}
}

View File

@@ -4,9 +4,6 @@ import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.settings.Setting.parent;
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability;
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.SpoofiOSAvailability;
import app.revanced.extension.shared.spoof.ClientType;
/**
* Settings shared across multiple apps.
@@ -31,9 +28,4 @@ public class BaseSettings {
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
public static final EnumSetting<AppLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
// Client type must be last spoof setting due to cyclic references.
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_UNPLUGGED, true, parent(SPOOF_VIDEO_STREAMS));
}

View File

@@ -71,15 +71,20 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
json.put(importExportKey, value.name().toLowerCase(Locale.ENGLISH));
}
@NonNull
private T getEnumFromString(String enumName) {
/**
* @param enumName Enum name. Casing does not matter.
* @return Enum of this type with the same declared name.
* @throws IllegalArgumentException if the name is not a valid enum of this type.
*/
protected T getEnumFromString(String enumName) {
//noinspection ConstantConditions
for (Enum<?> value : defaultValue.getClass().getEnumConstants()) {
if (value.name().equalsIgnoreCase(enumName)) {
// noinspection unchecked
//noinspection unchecked
return (T) value;
}
}
throw new IllegalArgumentException("Unknown enum value: " + enumName);
}
@@ -103,7 +108,9 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
* Availability based on if this setting is currently set to any of the provided types.
*/
@SafeVarargs
public final Setting.Availability availability(@NonNull T... types) {
public final Setting.Availability availability(T... types) {
Objects.requireNonNull(types);
return () -> {
T currentEnumType = get();
for (T enumType : types) {

View File

@@ -28,16 +28,14 @@ public abstract class Setting<T> {
/**
* Availability based on a single parent setting being enabled.
*/
@NonNull
public static Availability parent(@NonNull BooleanSetting parent) {
public static Availability parent(BooleanSetting parent) {
return parent::get;
}
/**
* Availability based on all parents being enabled.
*/
@NonNull
public static Availability parentsAll(@NonNull BooleanSetting... parents) {
public static Availability parentsAll(BooleanSetting... parents) {
return () -> {
for (BooleanSetting parent : parents) {
if (!parent.get()) return false;
@@ -49,8 +47,7 @@ public abstract class Setting<T> {
/**
* Availability based on any parent being enabled.
*/
@NonNull
public static Availability parentsAny(@NonNull BooleanSetting... parents) {
public static Availability parentsAny(BooleanSetting... parents) {
return () -> {
for (BooleanSetting parent : parents) {
if (parent.get()) return true;
@@ -79,7 +76,7 @@ public abstract class Setting<T> {
/**
* Adds a callback for {@link #importFromJSON(Context, String)} and {@link #exportToJson(Context)}.
*/
public static void addImportExportCallback(@NonNull ImportExportCallback callback) {
public static void addImportExportCallback(ImportExportCallback callback) {
importExportCallbacks.add(Objects.requireNonNull(callback));
}
@@ -100,14 +97,13 @@ public abstract class Setting<T> {
public static final SharedPrefCategory preferences = new SharedPrefCategory("revanced_prefs");
@Nullable
public static Setting<?> getSettingFromPath(@NonNull String str) {
public static Setting<?> getSettingFromPath(String str) {
return PATH_TO_SETTINGS.get(str);
}
/**
* @return All settings that have been created.
*/
@NonNull
public static List<Setting<?>> allLoadedSettings() {
return Collections.unmodifiableList(SETTINGS);
}
@@ -115,7 +111,6 @@ public abstract class Setting<T> {
/**
* @return All settings that have been created, sorted by keys.
*/
@NonNull
private static List<Setting<?>> allLoadedSettingsSorted() {
Collections.sort(SETTINGS, (Setting<?> o1, Setting<?> o2) -> o1.key.compareTo(o2.key));
return allLoadedSettings();
@@ -124,13 +119,11 @@ public abstract class Setting<T> {
/**
* The key used to store the value in the shared preferences.
*/
@NonNull
public final String key;
/**
* The default value of the setting.
*/
@NonNull
public final T defaultValue;
/**
@@ -161,7 +154,6 @@ public abstract class Setting<T> {
/**
* The value of the setting.
*/
@NonNull
protected volatile T value;
public Setting(String key, T defaultValue) {
@@ -199,8 +191,8 @@ public abstract class Setting<T> {
* @param userDialogMessage Confirmation message to display, if the user tries to change the setting from the default value.
* @param availability Condition that must be true, for this setting to be available to configure.
*/
public Setting(@NonNull String key,
@NonNull T defaultValue,
public Setting(String key,
T defaultValue,
boolean rebootApp,
boolean includeWithImportExport,
@Nullable String userDialogMessage,
@@ -227,7 +219,7 @@ public abstract class Setting<T> {
/**
* Migrate a setting value if the path is renamed but otherwise the old and new settings are identical.
*/
public static <T> void migrateOldSettingToNew(@NonNull Setting<T> oldSetting, @NonNull Setting<T> newSetting) {
public static <T> void migrateOldSettingToNew(Setting<T> oldSetting, Setting<T> newSetting) {
if (oldSetting == newSetting) throw new IllegalArgumentException();
if (!oldSetting.isSetToDefault()) {
@@ -243,7 +235,7 @@ public abstract class Setting<T> {
* This method will be deleted in the future.
*/
@SuppressWarnings("rawtypes")
public static void migrateFromOldPreferences(@NonNull SharedPrefCategory oldPrefs, @NonNull Setting setting, String settingKey) {
public static void migrateFromOldPreferences(SharedPrefCategory oldPrefs, Setting setting, String settingKey) {
if (!oldPrefs.preferences.contains(settingKey)) {
return; // Nothing to do.
}
@@ -285,7 +277,7 @@ public abstract class Setting<T> {
* This intentionally is a static method to deter
* accidental usage when {@link #save(Object)} was intended.
*/
public static void privateSetValueFromString(@NonNull Setting<?> setting, @NonNull String newValue) {
public static void privateSetValueFromString(Setting<?> setting, String newValue) {
setting.setValueFromString(newValue);
// Clear the preference value since default is used, to allow changing
@@ -299,7 +291,7 @@ public abstract class Setting<T> {
/**
* Sets the value of {@link #value}, but do not save to {@link #preferences}.
*/
protected abstract void setValueFromString(@NonNull String newValue);
protected abstract void setValueFromString(String newValue);
/**
* Load and set the value of {@link #value}.
@@ -309,7 +301,7 @@ public abstract class Setting<T> {
/**
* Persistently saves the value.
*/
public final void save(@NonNull T newValue) {
public final void save(T newValue) {
if (value.equals(newValue)) {
return;
}
@@ -406,7 +398,6 @@ public abstract class Setting<T> {
json.put(importExportKey, value);
}
@NonNull
public static String exportToJson(@Nullable Context alertDialogContext) {
try {
JSONObject json = new JSONObject();
@@ -445,7 +436,7 @@ public abstract class Setting<T> {
/**
* @return if any settings that require a reboot were changed.
*/
public static boolean importFromJSON(@NonNull Context alertDialogContext, @NonNull String settingsJsonString) {
public static boolean importFromJSON(Context alertDialogContext, String settingsJsonString) {
try {
if (!settingsJsonString.matches("[\\s\\S]*\\{")) {
settingsJsonString = '{' + settingsJsonString + '}'; // Restore outer JSON braces

View File

@@ -1,9 +1,8 @@
package app.revanced.extension.youtube.settings.preference;
package app.revanced.extension.shared.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.preference.Preference;
import app.revanced.extension.shared.settings.preference.LogBufferManager;
/**
* A custom preference that clears the ReVanced debug log buffer when clicked.

View File

@@ -24,10 +24,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.*;
import androidx.annotation.ColorInt;
@@ -298,7 +295,6 @@ public class ColorPickerPreference extends EditTextPreference {
// Horizontal layout for preview and EditText.
LinearLayout inputLayout = new LinearLayout(context);
inputLayout.setOrientation(LinearLayout.HORIZONTAL);
inputLayout.setPadding(0, 0, 0, dipToPixels(10));
dialogColorPreview = new TextView(context);
LinearLayout.LayoutParams previewParams = new LinearLayout.LayoutParams(
@@ -338,11 +334,23 @@ public class ColorPickerPreference extends EditTextPreference {
paddingView.setLayoutParams(params);
inputLayout.addView(paddingView);
// Create main container for color picker and input layout.
LinearLayout container = new LinearLayout(context);
container.setOrientation(LinearLayout.VERTICAL);
container.addView(colorPicker);
container.addView(inputLayout);
// Create content container for color picker and input layout.
LinearLayout contentContainer = new LinearLayout(context);
contentContainer.setOrientation(LinearLayout.VERTICAL);
contentContainer.addView(colorPicker);
contentContainer.addView(inputLayout);
// Create ScrollView to wrap the content container.
ScrollView contentScrollView = new ScrollView(context);
contentScrollView.setVerticalScrollBarEnabled(false); // Disable vertical scrollbar.
contentScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); // Disable overscroll effect.
LinearLayout.LayoutParams scrollViewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
0,
1.0f
);
contentScrollView.setLayoutParams(scrollViewParams);
contentScrollView.addView(contentContainer);
// Create custom dialog.
final int originalColor = currentColor & 0x00FFFFFF;
@@ -391,9 +399,9 @@ public class ColorPickerPreference extends EditTextPreference {
false // Do not dismiss dialog when onNeutralClick.
);
// Add the custom container to the dialog's main layout.
// Add the ScrollView to the dialog's main layout.
LinearLayout dialogMainLayout = dialogPair.second;
dialogMainLayout.addView(container, 1);
dialogMainLayout.addView(contentScrollView, dialogMainLayout.getChildCount() - 1);
// Set up color picker listener with debouncing.
// Add listener last to prevent callbacks from set calls above.

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
@@ -11,11 +9,7 @@ import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.*;
import androidx.annotation.NonNull;
@@ -30,7 +24,7 @@ public class CustomDialogListPreference extends ListPreference {
/**
* Custom ArrayAdapter to handle checkmark visibility.
*/
private static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
public static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
private static class SubViewDataContainer {
ImageView checkIcon;
View placeholder;
@@ -107,14 +101,16 @@ public class CustomDialogListPreference extends ListPreference {
@Override
protected void showDialog(Bundle state) {
Context context = getContext();
// Create ListView.
ListView listView = new ListView(getContext());
ListView listView = new ListView(context);
listView.setId(android.R.id.list);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Create custom adapter for the ListView.
ListPreferenceArrayAdapter adapter = new ListPreferenceArrayAdapter(
getContext(),
context,
Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"),
getEntries(),
getEntryValues(),
@@ -137,7 +133,7 @@ public class CustomDialogListPreference extends ListPreference {
// Create the custom dialog without OK button.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
getContext(),
context,
getTitle() != null ? getTitle().toString() : "",
null,
null,
@@ -149,35 +145,13 @@ public class CustomDialogListPreference extends ListPreference {
true
);
Dialog dialog = dialogPair.first;
// Add the ListView to the main layout.
LinearLayout mainLayout = dialogPair.second;
// Measure content height before adding ListView to layout.
// Otherwise, the ListView will push the buttons off the screen.
int totalHeight = 0;
int widthSpec = View.MeasureSpec.makeMeasureSpec(
getContext().getResources().getDisplayMetrics().widthPixels,
View.MeasureSpec.AT_MOST
);
int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
for (int i = 0; i < adapter.getCount(); i++) {
View listItem = adapter.getView(i, null, listView);
listItem.measure(widthSpec, heightSpec);
totalHeight += listItem.getMeasuredHeight();
}
// Cap the height at maxHeight.
int maxHeight = (int) (getContext().getResources().getDisplayMetrics().heightPixels * 0.6);
int finalHeight = Math.min(totalHeight, maxHeight);
// Add ListView to the main layout with calculated height.
LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
finalHeight // Use calculated height directly.
0,
1.0f
);
final int marginHorizontal = dipToPixels(8);
listViewParams.setMargins(0, marginHorizontal, 0, marginHorizontal);
mainLayout.addView(listView, mainLayout.getChildCount() - 1, listViewParams);
// Handle item click to select value and dismiss dialog.
@@ -188,10 +162,10 @@ public class CustomDialogListPreference extends ListPreference {
adapter.setSelectedValue(selectedValue);
adapter.notifyDataSetChanged();
}
dialog.dismiss();
dialogPair.first.dismiss();
});
// Show the dialog.
dialog.show();
dialogPair.first.show();
}
}

View File

@@ -1,9 +1,8 @@
package app.revanced.extension.youtube.settings.preference;
package app.revanced.extension.shared.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.preference.Preference;
import app.revanced.extension.shared.settings.preference.LogBufferManager;
/**
* A custom preference that triggers exporting ReVanced debug logs to the clipboard when clicked.

View File

@@ -17,6 +17,7 @@ import android.os.Handler;
import android.os.Looper;
import android.preference.Preference;
import android.util.AttributeSet;
import android.view.View;
import android.view.Window;
import android.webkit.WebView;
import android.webkit.WebViewClient;
@@ -216,6 +217,8 @@ class WebViewDialog extends Dialog {
// Create WebView.
WebView webView = new WebView(getContext());
webView.setVerticalScrollBarEnabled(false); // Disable the vertical scrollbar.
webView.setOverScrollMode(View.OVER_SCROLL_NEVER);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new OpenLinksExternallyWebClient());
webView.loadDataWithBaseURL(null, htmlContent, "text/html", "utf-8", null);
@@ -228,7 +231,7 @@ class WebViewDialog extends Dialog {
// Set dialog window attributes
Window window = getWindow();
if (window != null) {
Utils.setDialogWindowParameters(getContext(), window);
Utils.setDialogWindowParameters(window);
}
}

View File

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

View File

@@ -0,0 +1,150 @@
package app.revanced.extension.shared.settings.preference;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.graphics.Insets;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.preference.Preference;
import android.preference.PreferenceScreen;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets;
import android.widget.TextView;
import android.widget.Toolbar;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook;
@SuppressWarnings({"deprecation", "NewApi"})
public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
/**
* Sets toolbar for all nested preference screens.
*/
protected void setPreferenceScreenToolbar(PreferenceScreen parentScreen) {
for (int i = 0, count = parentScreen.getPreferenceCount(); i < count; i++) {
Preference childPreference = parentScreen.getPreference(i);
if (childPreference instanceof PreferenceScreen) {
// Recursively set sub preferences.
setPreferenceScreenToolbar((PreferenceScreen) childPreference);
childPreference.setOnPreferenceClickListener(
childScreen -> {
Dialog preferenceScreenDialog = ((PreferenceScreen) childScreen).getDialog();
ViewGroup rootView = (ViewGroup) preferenceScreenDialog
.findViewById(android.R.id.content)
.getParent();
// Allow package-specific background customization.
customizeDialogBackground(rootView);
// Fix the system navigation bar color for submenus.
setNavigationBarColor(preferenceScreenDialog.getWindow());
// Fix edge-to-edge screen with Android 15 and YT 19.45+
// https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
rootView.setOnApplyWindowInsetsListener((v, insets) -> {
Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars());
Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars());
Insets cutoutInsets = insets.getInsets(WindowInsets.Type.displayCutout());
// Apply padding for display cutout in landscape.
int leftPadding = cutoutInsets.left;
int rightPadding = cutoutInsets.right;
int topPadding = statusInsets.top;
int bottomPadding = navInsets.bottom;
v.setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
return insets;
});
}
Toolbar toolbar = new Toolbar(childScreen.getContext());
toolbar.setTitle(childScreen.getTitle());
toolbar.setNavigationIcon(getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
final int margin = Utils.dipToPixels(16);
toolbar.setTitleMargin(margin, 0, margin, 0);
TextView toolbarTextView = Utils.getChildView(toolbar,
true, TextView.class::isInstance);
if (toolbarTextView != null) {
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
}
// Allow package-specific toolbar customization.
customizeToolbar(toolbar);
// Allow package-specific post-toolbar setup.
onPostToolbarSetup(toolbar, preferenceScreenDialog);
rootView.addView(toolbar, 0);
return false;
}
);
}
}
}
/**
* Sets the system navigation bar color for the activity.
* Applies the background color obtained from {@link Utils#getAppBackgroundColor()} to the navigation bar.
* For Android 10 (API 29) and above, enforces navigation bar contrast to ensure visibility.
*/
public static void setNavigationBarColor(@Nullable Window window) {
if (window == null) {
Logger.printDebug(() -> "Cannot set navigation bar color, window is null");
return;
}
window.setNavigationBarColor(Utils.getAppBackgroundColor());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(true);
}
}
/**
* Returns the drawable for the back button.
*/
@SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getBackButtonDrawable() {
final int backButtonResource = Utils.getResourceIdentifier(
"revanced_settings_toolbar_arrow_left", "drawable");
Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource);
customizeBackButtonDrawable(drawable);
return drawable;
}
/**
* Customizes the back button drawable.
*/
protected static void customizeBackButtonDrawable(Drawable drawable) {
drawable.setTint(Utils.getAppForegroundColor());
}
/**
* Allows subclasses to customize the dialog's root view background.
*/
protected void customizeDialogBackground(ViewGroup rootView) {
rootView.setBackgroundColor(Utils.getAppBackgroundColor());
}
/**
* Allows subclasses to customize the toolbar.
*/
protected void customizeToolbar(Toolbar toolbar) {
BaseActivityHook.setToolbarLayoutParams(toolbar);
}
/**
* Allows subclasses to perform actions after toolbar setup.
*/
protected void onPostToolbarSetup(Toolbar toolbar, Dialog preferenceScreenDialog) {}
}

View File

@@ -2,17 +2,22 @@ package app.revanced.extension.shared.spoof;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Locale;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings;
@SuppressWarnings("ConstantLocale")
public enum ClientType {
/**
* Video not playable: Kids / Paid / Movie / Private / Age-restricted.
* This client can only be used when logged out.
*/
// https://dumps.tadiphone.dev/dumps/oculus/eureka
ANDROID_VR_NO_AUTH(
ANDROID_VR_1_61_48(
28,
"ANDROID_VR",
"com.google.android.apps.youtube.vr.oculus",
@@ -26,30 +31,31 @@ public enum ClientType {
"132.0.6808.3",
"1.61.48",
false,
false,
"Android VR No auth"
"Android VR 1.61"
),
// Chromecast with Google TV 4K.
// https://dumps.tadiphone.dev/dumps/google/kirkwood
ANDROID_UNPLUGGED(
29,
"ANDROID_UNPLUGGED",
"com.google.android.apps.youtube.unplugged",
"Google",
"Google TV Streamer",
"Android",
"14",
"34",
"UTT3.240625.001.K5",
"132.0.6808.3",
"8.49.0",
true,
true,
"Android TV"
/**
* Uses non adaptive bitrate, which fixes audio stuttering with YT Music.
* Does not use AV1.
*/
ANDROID_VR_1_43_32(
ANDROID_VR_1_61_48.id,
ANDROID_VR_1_61_48.clientName,
Objects.requireNonNull(ANDROID_VR_1_61_48.packageName),
ANDROID_VR_1_61_48.deviceMake,
ANDROID_VR_1_61_48.deviceModel,
ANDROID_VR_1_61_48.osName,
ANDROID_VR_1_61_48.osVersion,
Objects.requireNonNull(ANDROID_VR_1_61_48.androidSdkVersion),
Objects.requireNonNull(ANDROID_VR_1_61_48.buildId),
"107.0.5284.2",
"1.43.32",
ANDROID_VR_1_61_48.useAuth,
"Android VR 1.43"
),
// Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
// Google Pixel 9 Pro Fold
// https://dumps.tadiphone.dev/dumps/google/barbet
/**
* Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
* <a href="https://dumps.tadiphone.dev/dumps/google/barbet">Google Pixel 9 Pro Fold</a>
*/
ANDROID_CREATOR(
14,
"ANDROID_CREATOR",
@@ -63,61 +69,47 @@ public enum ClientType {
"132.0.6779.0",
"23.47.101",
true,
true,
"Android Creator"
"Android Studio"
),
IOS_UNPLUGGED(
33,
"IOS_UNPLUGGED",
"com.google.ios.youtubeunplugged",
/**
* Internal YT client for an unreleased YT client. May stop working at any time.
*/
VISIONOS(101,
"VISIONOS",
"Apple",
forceAVC()
// 11 Pro Max (last device with iOS 13)
? "iPhone12,5"
// 15 Pro Max
: "iPhone16,2",
"iOS",
forceAVC()
// iOS 13 and earlier uses only AVC. 14+ adds VP9 and AV1.
? "13.7.17H35"
: "18.2.22C152",
null,
null,
null,
// Version number should be a valid iOS release.
// https://www.ipa4fun.com/history/152043/
forceAVC()
// Some newer versions can also force AVC,
// but 6.45 is the last version that supports iOS 13.
? "6.45"
: "8.49",
true,
true,
forceAVC()
? "iOS TV Force AVC"
: "iOS TV"
"RealityDevice14,1",
"visionOS",
"1.3.21O771",
"0.1",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
false,
"visionOS"
),
ANDROID_VR_AUTH(
ANDROID_VR_NO_AUTH.id,
ANDROID_VR_NO_AUTH.clientName,
ANDROID_VR_NO_AUTH.packageName,
ANDROID_VR_NO_AUTH.deviceMake,
ANDROID_VR_NO_AUTH.deviceModel,
ANDROID_VR_NO_AUTH.osName,
ANDROID_VR_NO_AUTH.osVersion,
ANDROID_VR_NO_AUTH.androidSdkVersion,
ANDROID_VR_NO_AUTH.buildId,
ANDROID_VR_NO_AUTH.cronetVersion,
ANDROID_VR_NO_AUTH.clientVersion,
ANDROID_VR_NO_AUTH.requiresAuth,
true,
"Android VR Auth"
/**
* The device machine id for the iPad 6th Gen (iPad7,6).
* AV1 hardware decoding is not supported.
* See [this GitHub Gist](https://gist.github.com/adamawolf/3048717) for more information.
*
* Based on Google's actions to date, PoToken may not be required on devices with very low specs.
* For example, suppose the User-Agent for a PlayStation 3 (with 256MB of RAM) is used.
* Accessing 'Web' (https://www.youtube.com) will redirect to 'TV' (https://www.youtube.com/tv).
* 'TV' target devices with very low specs, such as embedded devices, game consoles, and blu-ray players, so PoToken is not required.
*
* For this reason, the device machine id for the iPad 6th Gen (with 2GB of RAM),
* the lowest spec device capable of running iPadOS 17, was used.
*/
IPADOS(5,
"IOS",
"Apple",
"iPad7,6",
"iPadOS",
"17.7.10.21H450",
"19.22.3",
"com.google.ios.youtube/19.22.3 (iPad7,6; U; CPU iPadOS 17_7_10 like Mac OS X; " + Locale.getDefault() + ")",
false,
"iPadOS"
);
private static boolean forceAVC() {
return BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
}
/**
* YouTube
* <a href="https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients">client type</a>
@@ -129,6 +121,7 @@ public enum ClientType {
/**
* App package name.
*/
@Nullable
private final String packageName;
/**
@@ -182,12 +175,6 @@ public enum ClientType {
*/
public final String clientVersion;
/**
* If this client requires authentication and does not work
* if logged out or in incognito mode.
*/
public final boolean requiresAuth;
/**
* If the client should use authentication if available.
*/
@@ -198,19 +185,20 @@ public enum ClientType {
*/
public final String friendlyName;
@SuppressWarnings("ConstantLocale")
/**
* Android constructor.
*/
ClientType(int id,
String clientName,
String packageName,
@NonNull String packageName,
String deviceMake,
String deviceModel,
String osName,
String osVersion,
@Nullable String androidSdkVersion,
@Nullable String buildId,
@Nullable String cronetVersion,
@NonNull String androidSdkVersion,
@NonNull String buildId,
@NonNull String cronetVersion,
String clientVersion,
boolean requiresAuth,
boolean useAuth,
String friendlyName) {
this.id = id;
@@ -224,36 +212,46 @@ public enum ClientType {
this.buildId = buildId;
this.cronetVersion = cronetVersion;
this.clientVersion = clientVersion;
this.requiresAuth = requiresAuth;
this.useAuth = useAuth;
this.friendlyName = friendlyName;
Locale defaultLocale = Locale.getDefault();
if (androidSdkVersion == null) {
// Convert version from '18.2.22C152' into '18_2_22'
String userAgentOsVersion = osVersion
.replaceAll("(\\d+\\.\\d+\\.\\d+).*", "$1")
.replace(".", "_");
// https://github.com/mitmproxy/mitmproxy/issues/4836
this.userAgent = String.format("%s/%s (%s; U; CPU iOS %s like Mac OS X; %s)",
packageName,
clientVersion,
deviceModel,
userAgentOsVersion,
defaultLocale
);
} else {
this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s; %s; Build/%s; Cronet/%s)",
packageName,
clientVersion,
osVersion,
defaultLocale,
deviceModel,
Objects.requireNonNull(buildId),
Objects.requireNonNull(cronetVersion)
);
}
this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s; %s; Build/%s; Cronet/%s)",
packageName,
clientVersion,
osVersion,
defaultLocale,
deviceModel,
Objects.requireNonNull(buildId),
Objects.requireNonNull(cronetVersion)
);
Logger.printDebug(() -> "userAgent: " + this.userAgent);
}
@SuppressWarnings("ConstantLocale")
ClientType(int id,
String clientName,
String deviceMake,
String deviceModel,
String osName,
String osVersion,
String clientVersion,
String userAgent,
boolean useAuth,
String friendlyName) {
this.id = id;
this.clientName = clientName;
this.deviceMake = deviceMake;
this.deviceModel = deviceModel;
this.osName = osName;
this.osVersion = osVersion;
this.clientVersion = clientVersion;
this.userAgent = userAgent;
this.useAuth = useAuth;
this.friendlyName = friendlyName;
this.packageName = null;
this.androidSdkVersion = null;
this.buildId = null;
this.cronetVersion = null;
}
}

View File

@@ -6,38 +6,70 @@ import android.text.TextUtils;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
@SuppressWarnings("unused")
public class SpoofVideoStreamsPatch {
/**
* Domain used for internet connectivity verification.
* It has an empty response body and is only used to check for a 204 response code.
* <p>
* If an unreachable IP address (127.0.0.1) is used, no response code is provided.
* <p>
* YouTube handles unreachable IP addresses without issue.
* YouTube Music has an issue with waiting for the Cronet connect timeout of 30s on mobile networks.
* <p>
* Using a VPN or DNS can temporarily resolve this issue,
* But the ideal workaround is to avoid using an unreachable IP address.
*/
private static final String INTERNET_CONNECTION_CHECK_URI_STRING = "https://www.google.com/gen_204";
private static final Uri INTERNET_CONNECTION_CHECK_URI = Uri.parse(INTERNET_CONNECTION_CHECK_URI_STRING);
private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_VIDEO_STREAMS.get();
private static final boolean FIX_HLS_CURRENT_TIME = SPOOF_STREAMING_DATA
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
@Nullable
private static volatile AppLanguage languageOverride;
/**
* Any unreachable ip address. Used to intentionally fail requests.
*/
private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0";
private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING);
private static volatile ClientType preferredClient = ClientType.ANDROID_VR_1_61_48;
/**
* @return If this patch was included during patching.
*/
private static boolean isPatchIncluded() {
public static boolean isPatchIncluded() {
return false; // Modified during patching.
}
public static boolean notSpoofingToAndroid() {
return !isPatchIncluded()
|| !BaseSettings.SPOOF_VIDEO_STREAMS.get()
|| BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
@Nullable
public static AppLanguage getLanguageOverride() {
return languageOverride;
}
/**
* @param language Language override for non-authenticated requests. If this is null then
* {@link BaseSettings#SPOOF_VIDEO_STREAMS_LANGUAGE} is used.
*/
public static void setLanguageOverride(@Nullable AppLanguage language) {
languageOverride = language;
}
public static void setClientsToUse(List<ClientType> availableClients, ClientType client) {
preferredClient = Objects.requireNonNull(client);
StreamingDataRequest.setClientOrderToUse(availableClients, client);
}
public static boolean spoofingToClientWithNoMultiAudioStreams() {
return isPatchIncluded()
&& SPOOF_STREAMING_DATA
&& preferredClient != ClientType.IPADOS;
}
/**
@@ -53,9 +85,9 @@ public class SpoofVideoStreamsPatch {
String path = playerRequestUri.getPath();
if (path != null && path.contains("get_watch")) {
Logger.printDebug(() -> "Blocking 'get_watch' by returning unreachable uri");
Logger.printDebug(() -> "Blocking 'get_watch' by returning internet connection check uri");
return UNREACHABLE_HOST_URI;
return INTERNET_CONNECTION_CHECK_URI;
}
} catch (Exception ex) {
Logger.printException(() -> "blockGetWatchRequest failure", ex);
@@ -65,6 +97,35 @@ public class SpoofVideoStreamsPatch {
return playerRequestUri;
}
/**
* Injection point.
*
* Blocks /get_watch requests by returning an unreachable URI.
* /att/get requests are used to obtain a PoToken challenge.
* See: <a href="https://github.com/FreeTubeApp/FreeTube/blob/4b7208430bc1032019a35a35eb7c8a84987ddbd7/src/botGuardScript.js#L15">botGuardScript.js#L15</a>
* <p>
* Since the Spoof streaming data patch was implemented because a valid PoToken cannot be obtained,
* Blocking /att/get requests are not a problem.
*/
public static String blockGetAttRequest(String originalUrlString) {
if (SPOOF_STREAMING_DATA) {
try {
var originalUri = Uri.parse(originalUrlString);
String path = originalUri.getPath();
if (path != null && path.contains("att/get")) {
Logger.printDebug(() -> "Blocking 'att/get' by returning internet connection check uri");
return INTERNET_CONNECTION_CHECK_URI_STRING;
}
} catch (Exception ex) {
Logger.printException(() -> "blockGetAttRequest failure", ex);
}
}
return originalUrlString;
}
/**
* Injection point.
* <p>
@@ -77,9 +138,9 @@ public class SpoofVideoStreamsPatch {
String path = originalUri.getPath();
if (path != null && path.contains("initplayback")) {
Logger.printDebug(() -> "Blocking 'initplayback' by clearing query");
Logger.printDebug(() -> "Blocking 'initplayback' by returning internet connection check uri");
return originalUri.buildUpon().clearQuery().build().toString();
return INTERNET_CONNECTION_CHECK_URI_STRING;
}
} catch (Exception ex) {
Logger.printException(() -> "blockInitPlaybackRequest failure", ex);
@@ -98,7 +159,7 @@ public class SpoofVideoStreamsPatch {
/**
* Injection point.
* Only invoked when playing a livestream on an iOS client.
* Only invoked when playing a livestream on an Apple client.
*/
public static boolean fixHLSCurrentTime(boolean original) {
if (!SPOOF_STREAMING_DATA) {
@@ -107,6 +168,14 @@ public class SpoofVideoStreamsPatch {
return false;
}
/*
* Injection point.
* Fix audio stuttering in YouTube Music.
*/
public static boolean disableSABR() {
return SPOOF_STREAMING_DATA;
}
/**
* Injection point.
* Turns off a feature flag that interferes with spoofing.
@@ -252,16 +321,7 @@ public class SpoofVideoStreamsPatch {
public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
return BaseSettings.SPOOF_VIDEO_STREAMS.get()
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_VR_NO_AUTH;
}
}
public static final class SpoofiOSAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
return BaseSettings.SPOOF_VIDEO_STREAMS.get()
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
return BaseSettings.SPOOF_VIDEO_STREAMS.get() && !preferredClient.useAuth;
}
}
}

View File

@@ -1,5 +1,7 @@
package app.revanced.extension.shared.spoof.requests;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
import org.json.JSONException;
import org.json.JSONObject;
@@ -10,8 +12,10 @@ import java.util.Locale;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.spoof.ClientType;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
final class PlayerRoutes {
static final Route.CompiledRoute GET_STREAMING_DATA = new Route(
@@ -37,14 +41,16 @@ final class PlayerRoutes {
try {
JSONObject context = new JSONObject();
// Can override default language only if no login is used.
// Could use preferred audio for all clients that do not login,
// but if this is a fall over client it will set the language even though
// the audio language is not selectable in the UI.
ClientType userSelectedClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
Locale streamLocale = userSelectedClient == ClientType.ANDROID_VR_NO_AUTH
? BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get().getLocale()
: Locale.getDefault();
AppLanguage language = SpoofVideoStreamsPatch.getLanguageOverride();
if (language == null || clientType == ANDROID_VR_1_43_32) {
// Force original audio has not overrode the language.
// Or if YT has fallen over to the last unauthenticated client (VR 1.43), then
// always use the app language because forcing an audio stream of specific languages
// can sometimes fail so it's better to try and load something rather than nothing.
language = BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get();
}
//noinspection ExtractMethodRecommender
Locale streamLocale = language.getLocale();
JSONObject client = new JSONObject();
client.put("deviceMake", clientType.deviceMake);

View File

@@ -1,5 +1,6 @@
package app.revanced.extension.shared.spoof.requests;
import static app.revanced.extension.shared.ByteTrieSearch.convertStringsToBytes;
import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_STREAMING_DATA;
import androidx.annotation.NonNull;
@@ -13,12 +14,18 @@ import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
@@ -35,21 +42,27 @@ import app.revanced.extension.shared.spoof.ClientType;
*/
public class StreamingDataRequest {
private static final ClientType[] CLIENT_ORDER_TO_USE;
private static volatile ClientType[] clientOrderToUse = ClientType.values();
static {
ClientType[] allClientTypes = ClientType.values();
ClientType preferredClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
public static void setClientOrderToUse(List<ClientType> availableClients, ClientType preferredClient) {
Objects.requireNonNull(preferredClient);
CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length];
CLIENT_ORDER_TO_USE[0] = preferredClient;
int availableClientSize = availableClients.size();
if (!availableClients.contains(preferredClient)) {
availableClientSize++;
}
clientOrderToUse = new ClientType[availableClientSize];
clientOrderToUse[0] = preferredClient;
int i = 1;
for (ClientType c : allClientTypes) {
for (ClientType c : availableClients) {
if (c != preferredClient) {
CLIENT_ORDER_TO_USE[i++] = c;
clientOrderToUse[i++] = c;
}
}
Logger.printDebug(() -> "Available spoof clients: " + Arrays.toString(clientOrderToUse));
}
private static final String AUTHORIZATION_HEADER = "Authorization";
@@ -87,6 +100,16 @@ public class StreamingDataRequest {
}
});
/**
* Strings found in the response if the video is a livestream.
*/
private static final ByteTrieSearch liveStreamBufferSearch = new ByteTrieSearch(
convertStringsToBytes(
"yt_live_broadcast",
"yt_premiere_broadcast"
)
);
private static volatile ClientType lastSpoofedClientType;
public static String getLastSpoofedClientName() {
@@ -154,7 +177,7 @@ public class StreamingDataRequest {
}
}
if (!authHeadersIncludes && clientType.requiresAuth) {
if (!authHeadersIncludes && clientType.useAuth) {
Logger.printDebug(() -> "Skipping client since user is not logged in: " + clientType
+ " videoId: " + videoId);
return null;
@@ -193,9 +216,9 @@ public class StreamingDataRequest {
// Retry with different client if empty response body is received.
int i = 0;
for (ClientType clientType : CLIENT_ORDER_TO_USE) {
for (ClientType clientType : clientOrderToUse) {
// Show an error if the last client type fails, or if debug is enabled then show for all attempts.
final boolean showErrorToast = (++i == CLIENT_ORDER_TO_USE.length) || debugEnabled;
final boolean showErrorToast = (++i == clientOrderToUse.length) || debugEnabled;
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
if (connection != null) {
@@ -215,9 +238,13 @@ public class StreamingDataRequest {
while ((bytesRead = inputStream.read(buffer)) >= 0) {
baos.write(buffer, 0, bytesRead);
}
lastSpoofedClientType = clientType;
if (clientType == ClientType.ANDROID_CREATOR && liveStreamBufferSearch.matches(buffer)) {
Logger.printDebug(() -> "Skipping Android Studio as video is a livestream: " + videoId);
} else {
lastSpoofedClientType = clientType;
return ByteBuffer.wrap(baos.toByteArray());
return ByteBuffer.wrap(baos.toByteArray());
}
}
}
} catch (IOException ex) {

View File

@@ -1,7 +1,14 @@
plugins {
alias(libs.plugins.protobuf)
}
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:spotify:stub"))
compileOnly(libs.annotation)
implementation(libs.nanohttpd)
implementation(libs.protobuf.javalite)
}
android {
@@ -14,3 +21,19 @@ android {
targetCompatibility = JavaVersion.VERSION_1_8
}
}
protobuf {
protoc {
artifact = libs.protobuf.protoc.get().toString()
}
generateProtoTasks {
all().forEach { task ->
task.builtins {
create("java") {
option("lite")
}
}
}
}
}

View File

@@ -1,9 +1,11 @@
package app.revanced.extension.spotify.layout.hide.createbutton;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.spotify.shared.ComponentFilters.*;
import app.revanced.extension.spotify.shared.ComponentFilters.ComponentFilter;
import app.revanced.extension.spotify.shared.ComponentFilters.ResourceIdComponentFilter;
import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFilter;
import java.util.List;
@SuppressWarnings("unused")
public final class HideCreateButtonPatch {
@@ -53,7 +55,9 @@ public final class HideCreateButtonPatch {
return null;
}
}
} catch (Exception ex) {
} catch (Throwable ex) {
// Catch Throwable as calling toString can cause crashes with wrongfully generated code that throws
// NoSuchMethod errors.
Logger.printException(() -> "returnNullIfIsCreateButton failure", ex);
}

View File

@@ -0,0 +1,115 @@
package app.revanced.extension.spotify.misc.fix;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import static app.revanced.extension.spotify.misc.fix.Constants.*;
class ClientTokenService {
private static final String IOS_CLIENT_ID = "58bd3c95768941ea9eb4350aaa033eb3";
private static final String IOS_USER_AGENT;
static {
String clientVersion = getClientVersion();
int commitHashIndex = clientVersion.lastIndexOf(".");
String version = clientVersion.substring(
clientVersion.indexOf("-") + 1,
clientVersion.lastIndexOf(".", commitHashIndex - 1)
);
IOS_USER_AGENT = "Spotify/" + version + " iOS/" + getSystemVersion() + " (" + getHardwareMachine() + ")";
}
private static final ConnectivitySdkData.Builder IOS_CONNECTIVITY_SDK_DATA =
ConnectivitySdkData.newBuilder()
.setPlatformSpecificData(PlatformSpecificData.newBuilder()
.setIos(NativeIOSData.newBuilder()
.setHwMachine(getHardwareMachine())
.setSystemVersion(getSystemVersion())
)
);
private static final ClientDataRequest.Builder IOS_CLIENT_DATA_REQUEST =
ClientDataRequest.newBuilder()
.setClientVersion(getClientVersion())
.setClientId(IOS_CLIENT_ID);
private static final ClientTokenRequest.Builder IOS_CLIENT_TOKEN_REQUEST =
ClientTokenRequest.newBuilder()
.setRequestType(ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST);
@NonNull
static ClientTokenRequest newIOSClientTokenRequest(String deviceId) {
Logger.printInfo(() -> "Creating new iOS client token request with device ID: " + deviceId);
return IOS_CLIENT_TOKEN_REQUEST
.setClientData(IOS_CLIENT_DATA_REQUEST
.setConnectivitySdkData(IOS_CONNECTIVITY_SDK_DATA
.setDeviceId(deviceId)
)
)
.build();
}
@Nullable
static ClientTokenResponse getClientTokenResponse(@NonNull ClientTokenRequest request) {
if (request.getRequestType() == ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST) {
Logger.printInfo(() -> "Requesting iOS client token");
String deviceId = request.getClientData().getConnectivitySdkData().getDeviceId();
request = newIOSClientTokenRequest(deviceId);
}
ClientTokenResponse response;
try {
response = requestClientToken(request);
} catch (IOException ex) {
Logger.printException(() -> "Failed to handle request", ex);
return null;
}
return response;
}
@NonNull
private static ClientTokenResponse requestClientToken(@NonNull ClientTokenRequest request) throws IOException {
HttpURLConnection urlConnection = (HttpURLConnection) new URL(CLIENT_TOKEN_API_URL).openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(true);
urlConnection.setRequestProperty("Content-Type", "application/x-protobuf");
urlConnection.setRequestProperty("Accept", "application/x-protobuf");
urlConnection.setRequestProperty("User-Agent", IOS_USER_AGENT);
byte[] requestArray = request.toByteArray();
urlConnection.setFixedLengthStreamingMode(requestArray.length);
urlConnection.getOutputStream().write(requestArray);
try (InputStream inputStream = urlConnection.getInputStream()) {
return ClientTokenResponse.parseFrom(inputStream);
}
}
@Nullable
static ClientTokenResponse serveClientTokenRequest(@NonNull InputStream inputStream) {
ClientTokenRequest request;
try {
request = ClientTokenRequest.parseFrom(inputStream);
} catch (IOException ex) {
Logger.printException(() -> "Failed to parse request from input stream", ex);
return null;
}
Logger.printInfo(() -> "Request of type: " + request.getRequestType());
ClientTokenResponse response = getClientTokenResponse(request);
if (response != null) Logger.printInfo(() -> "Response of type: " + response.getResponseType());
return response;
}
}

View File

@@ -0,0 +1,26 @@
package app.revanced.extension.spotify.misc.fix;
import androidx.annotation.NonNull;
class Constants {
static final String CLIENT_TOKEN_API_PATH = "/v1/clienttoken";
static final String CLIENT_TOKEN_API_URL = "https://clienttoken.spotify.com" + CLIENT_TOKEN_API_PATH;
// Modified by a patch. Do not touch.
@NonNull
static String getClientVersion() {
return "";
}
// Modified by a patch. Do not touch.
@NonNull
static String getSystemVersion() {
return "";
}
// Modified by a patch. Do not touch.
@NonNull
static String getHardwareMachine() {
return "";
}
}

View File

@@ -0,0 +1,94 @@
package app.revanced.extension.spotify.misc.fix;
import androidx.annotation.NonNull;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.ClientTokenResponse;
import com.google.protobuf.MessageLite;
import fi.iki.elonen.NanoHTTPD;
import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import static app.revanced.extension.spotify.misc.fix.ClientTokenService.serveClientTokenRequest;
import static app.revanced.extension.spotify.misc.fix.Constants.CLIENT_TOKEN_API_PATH;
import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR;
class RequestListener extends NanoHTTPD {
RequestListener(int port) {
super(port);
try {
start();
} catch (IOException ex) {
Logger.printException(() -> "Failed to start request listener on port " + port, ex);
throw new RuntimeException(ex);
}
}
@NonNull
@Override
public Response serve(@NonNull IHTTPSession session) {
String uri = session.getUri();
if (!uri.equals(CLIENT_TOKEN_API_PATH)) return INTERNAL_ERROR_RESPONSE;
Logger.printInfo(() -> "Serving request for URI: " + uri);
ClientTokenResponse response = serveClientTokenRequest(getInputStream(session));
if (response != null) return newResponse(Response.Status.OK, response);
Logger.printException(() -> "Failed to serve client token request");
return INTERNAL_ERROR_RESPONSE;
}
@NonNull
private static InputStream newLimitedInputStream(InputStream inputStream, long contentLength) {
return new FilterInputStream(inputStream) {
private long remaining = contentLength;
@Override
public int read() throws IOException {
if (remaining <= 0) return -1;
int result = super.read();
if (result != -1) remaining--;
return result;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (remaining <= 0) return -1;
len = (int) Math.min(len, remaining);
int result = super.read(b, off, len);
if (result != -1) remaining -= result;
return result;
}
};
}
@NonNull
private static InputStream getInputStream(@NonNull IHTTPSession session) {
long requestContentLength = Long.parseLong(Objects.requireNonNull(session.getHeaders().get("content-length")));
return newLimitedInputStream(session.getInputStream(), requestContentLength);
}
private static final Response INTERNAL_ERROR_RESPONSE = newResponse(INTERNAL_ERROR);
@SuppressWarnings("SameParameterValue")
@NonNull
private static Response newResponse(Response.Status status) {
return newResponse(status, null);
}
@NonNull
private static Response newResponse(Response.IStatus status, MessageLite messageLite) {
if (messageLite == null) {
return newFixedLengthResponse(status, "application/x-protobuf", null);
}
byte[] messageBytes = messageLite.toByteArray();
InputStream stream = new ByteArrayInputStream(messageBytes);
return newFixedLengthResponse(status, "application/x-protobuf", stream, messageBytes.length);
}
}

View File

@@ -0,0 +1,25 @@
package app.revanced.extension.spotify.misc.fix;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class SpoofClientPatch {
private static RequestListener listener;
/**
* Injection point. Launch requests listener server.
*/
public synchronized static void launchListener(int port) {
if (listener != null) {
Logger.printInfo(() -> "Listener already running on port " + port);
return;
}
try {
Logger.printInfo(() -> "Launching listener on port " + port);
listener = new RequestListener(port);
} catch (Exception ex) {
Logger.printException(() -> "launchListener failure", ex);
}
}
}

View File

@@ -0,0 +1,73 @@
syntax = "proto3";
package spotify.clienttoken.data.v0;
option optimize_for = LITE_RUNTIME;
option java_package = "app.revanced.extension.spotify.misc.fix.clienttoken.data.v0";
message ClientTokenRequest {
ClientTokenRequestType request_type = 1;
oneof request {
ClientDataRequest client_data = 2;
}
}
enum ClientTokenRequestType {
REQUEST_UNKNOWN = 0;
REQUEST_CLIENT_DATA_REQUEST = 1;
REQUEST_CHALLENGE_ANSWERS_REQUEST = 2;
}
message ClientDataRequest {
string client_version = 1;
string client_id = 2;
oneof data {
ConnectivitySdkData connectivity_sdk_data = 3;
}
}
message ConnectivitySdkData {
PlatformSpecificData platform_specific_data = 1;
string device_id = 2;
}
message PlatformSpecificData {
oneof data {
NativeIOSData ios = 2;
}
}
message NativeIOSData {
int32 user_interface_idiom = 1;
bool target_iphone_simulator = 2;
string hw_machine = 3;
string system_version = 4;
string simulator_model_identifier = 5;
}
message ClientTokenResponse {
ClientTokenResponseType response_type = 1;
oneof response {
GrantedTokenResponse granted_token = 2;
}
}
enum ClientTokenResponseType {
RESPONSE_UNKNOWN = 0;
RESPONSE_GRANTED_TOKEN_RESPONSE = 1;
RESPONSE_CHALLENGES_RESPONSE = 2;
}
message GrantedTokenResponse {
string token = 1;
int32 expires_after_seconds = 2;
int32 refresh_after_seconds = 3;
repeated TokenDomain domains = 4;
}
message TokenDomain {
string domain = 1;
}

View File

@@ -1,5 +1,5 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
alias(libs.plugins.android.library)
}
android {

View File

@@ -0,0 +1,5 @@
package app.revanced;
public interface ContextMenuItemPlaceholder {
Object getViewModel();
}

View File

@@ -0,0 +1,6 @@
package com.spotify.browsita.v1.resolved;
public final class Section {
public static final int BRAND_ADS_FIELD_NUMBER = 6;
public int sectionTypeCase_;
}

View File

@@ -1,8 +0,0 @@
package com.spotify.useraccount.v1;
/**
* Used for target 8.6.98.900. Class is still present in newer app targets.
*/
public class AccountAttribute {
public Object value_;
}

View File

@@ -2,4 +2,5 @@ dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:syncforreddit:stub"))
compileOnly(libs.annotation)
compileOnly(libs.okhttp)
}

View File

@@ -0,0 +1,22 @@
package app.revanced.extension.syncforreddit;
import app.revanced.extension.shared.fixes.redgifs.BaseFixRedgifsApiPatch;
import okhttp3.OkHttpClient;
/**
* @noinspection unused
*/
public class FixRedgifsApiPatch extends BaseFixRedgifsApiPatch {
static {
INSTANCE = new FixRedgifsApiPatch();
}
public String getDefaultUserAgent() {
// To be filled in by patch
return "";
}
public static OkHttpClient install(OkHttpClient.Builder builder) {
return builder.addInterceptor(INSTANCE).build();
}
}

View File

@@ -1,5 +1,5 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
alias(libs.plugins.android.library)
}
android {

View File

@@ -1,5 +1,5 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
alias(libs.plugins.android.library)
}
android {

View File

@@ -1,7 +1,7 @@
android.namespace = "app.revanced.extension"
plugins {
id(libs.plugins.android.library.get().pluginId)
alias(libs.plugins.android.library)
}
android {

View File

@@ -1,5 +1,5 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
alias(libs.plugins.android.library)
}
android {

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,101 @@
package app.revanced.extension.youtube.patches;
import android.graphics.drawable.Drawable;
import androidx.annotation.Nullable;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class ChangeHeaderPatch {
public enum HeaderLogo {
DEFAULT(null, null),
REGULAR("ytWordmarkHeader", "yt_ringo2_wordmark_header"),
PREMIUM("ytPremiumWordmarkHeader", "yt_ringo2_premium_wordmark_header"),
REVANCED("revanced_header_logo", "revanced_header_logo"),
REVANCED_MINIMAL("revanced_header_logo_minimal", "revanced_header_logo_minimal"),
CUSTOM("custom_header", "custom_header");
@Nullable
private final String attributeName;
@Nullable
private final String drawableName;
HeaderLogo(@Nullable String attributeName, @Nullable String drawableName) {
this.attributeName = attributeName;
this.drawableName = drawableName;
}
/**
* @return The attribute id of this header logo, or NULL if the logo should not be replaced.
*/
@Nullable
private Integer getAttributeId() {
if (attributeName == null) {
return null;
}
final int identifier = Utils.getResourceIdentifier(attributeName, "attr");
if (identifier == 0) {
// Identifier is zero if custom header setting was included in imported settings
// and a custom image was not included during patching.
Logger.printDebug(() -> "Could not find attribute: " + drawableName);
Settings.HEADER_LOGO.resetToDefault();
return null;
}
return identifier;
}
@Nullable
public Drawable getDrawable() {
if (drawableName == null) {
return null;
}
String drawableFullName = drawableName + (Utils.isDarkModeEnabled()
? "_dark"
: "_light");
final int identifier = Utils.getResourceIdentifier(drawableFullName, "drawable");
if (identifier == 0) {
Logger.printDebug(() -> "Could not find drawable: " + drawableFullName);
Settings.HEADER_LOGO.resetToDefault();
return null;
}
return Utils.getContext().getDrawable(identifier);
}
}
/**
* Injection point.
*/
public static int getHeaderAttributeId(int original) {
return Objects.requireNonNullElse(Settings.HEADER_LOGO.get().getAttributeId(), original);
}
public static Drawable getDrawable(Drawable original) {
Drawable logo = Settings.HEADER_LOGO.get().getDrawable();
if (logo != null) {
return logo;
}
// TODO: If 'Hide Doodles' is enabled, this will force the regular logo regardless
// what account the user has. This can be improved the next time a Doodle is
// active and the attribute id is passed to this method so the correct
// regular/premium logo is returned.
logo = HeaderLogo.REGULAR.getDrawable();
if (logo != null) {
return logo;
}
// Should never happen.
Logger.printException(() -> "Could not find regular header logo resource");
return original;
}
}

View File

@@ -0,0 +1,16 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class DisableDoubleTapActionsPatch {
/**
* Injection point.
*
* @return If "should skip to chapter start" flag is set.
*/
public static boolean disableDoubleTapChapters(boolean original) {
return original && !Settings.DISABLE_CHAPTER_SKIP_DOUBLE_TAP.get();
}
}

View File

@@ -1,5 +1,7 @@
package app.revanced.extension.youtube.patches;
import android.view.Display;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
@@ -8,8 +10,10 @@ public class DisableHdrPatch {
/**
* Injection point.
*/
public static boolean disableHDRVideo() {
return !Settings.DISABLE_HDR_VIDEO.get();
public static int[] disableHdrVideo(Display.HdrCapabilities capabilities) {
return Settings.DISABLE_HDR_VIDEO.get()
? new int[0]
: capabilities.getSupportedHdrTypes();
}
}

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

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

View File

@@ -1,7 +1,7 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
import app.revanced.extension.youtube.settings.Settings;
@@ -11,19 +11,34 @@ public class ForceOriginalAudioPatch {
private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4";
/**
* If the conditions to use this patch were present when the app launched.
* Injection point.
*/
public static boolean PATCH_AVAILABLE = SpoofVideoStreamsPatch.notSpoofingToAndroid();
public static final class ForceOriginalAudioAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
// Check conditions of launch and now. Otherwise if spoofing is changed
// without a restart the setting will show as available when it's not.
return PATCH_AVAILABLE && SpoofVideoStreamsPatch.notSpoofingToAndroid();
public static void setPreferredLanguage() {
if (Settings.FORCE_ORIGINAL_AUDIO.get()
&& SpoofVideoStreamsPatch.spoofingToClientWithNoMultiAudioStreams()) {
// If client spoofing does not use authentication and lacks multi-audio streams,
// then can use any language code for the request and if that requested language is
// not available YT uses the original audio language. Authenticated requests ignore
// the language code and always use the account language. Use a language that is
// not auto-dubbed by YouTube: https://support.google.com/youtube/answer/15569972
// but the language is also supported natively by the Meta Quest device that
// Android VR is spoofing.
AppLanguage override = AppLanguage.SV;
Logger.printDebug(() -> "Setting language override: " + override);
SpoofVideoStreamsPatch.setLanguageOverride(override);
}
}
/**
* Injection point.
*/
public static boolean ignoreDefaultAudioStream(boolean original) {
if (Settings.FORCE_ORIGINAL_AUDIO.get()) {
return false;
}
return original;
}
/**
* Injection point.
*/
@@ -50,7 +65,6 @@ public class ForceOriginalAudioPatch {
return isOriginal;
} catch (Exception ex) {
Logger.printException(() -> "isDefaultAudioStream failure", ex);
return isDefault;
}
}

View File

@@ -8,6 +8,6 @@ public final class HideRelatedVideoOverlayPatch {
* Injection point.
*/
public static boolean hideRelatedVideoOverlay() {
return Settings.HIDE_RELATED_VIDEO_OVERLAY.get();
return Settings.HIDE_RELATED_VIDEOS_OVERLAY.get();
}
}

View File

@@ -57,11 +57,4 @@ public class PlayerControlsPatch {
private static void fullscreenButtonVisibilityChanged(boolean isVisible) {
// Code added during patching.
}
/**
* Injection point.
*/
public static String getPlayerTopControlsLayoutResourceName(String original) {
return "default";
}
}

View File

@@ -0,0 +1,18 @@
package app.revanced.extension.youtube.patches;
import androidx.annotation.Nullable;
import app.revanced.extension.youtube.shared.PlayerControlsVisibility;
@SuppressWarnings("unused")
public class PlayerControlsVisibilityHookPatch {
/**
* Injection point.
*/
public static void setPlayerControlsVisibility(@Nullable Enum<?> youTubePlayerControlsVisibility) {
if (youTubePlayerControlsVisibility == null) return;
PlayerControlsVisibility.setFromString(youTubePlayerControlsVisibility.name());
}
}

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

@@ -7,11 +7,17 @@ public class VersionCheckPatch {
return Utils.getAppVersionName().compareTo(version) >= 0;
}
@Deprecated
public static final boolean IS_19_17_OR_GREATER = isVersionOrGreater("19.17.00");
@Deprecated
public static final boolean IS_19_20_OR_GREATER = isVersionOrGreater("19.20.00");
@Deprecated
public static final boolean IS_19_21_OR_GREATER = isVersionOrGreater("19.21.00");
@Deprecated
public static final boolean IS_19_26_OR_GREATER = isVersionOrGreater("19.26.00");
@Deprecated
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
@Deprecated
public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00");
public static final boolean IS_19_46_OR_GREATER = isVersionOrGreater("19.46.00");
}

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,137 @@ 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));
}
// On extremely slow internet connections the index can initially be -1
originalQualityIndex = Math.max(0, originalQualityIndex);
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

@@ -59,10 +59,11 @@ public final class AnnouncementsPatch {
int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
try {
final var announcementIds = new JSONArray(jsonString);
if (announcementIds.length() == 0) return true;
id = announcementIds.getJSONObject(0).getInt("id");
} catch (Throwable ex) {
Logger.printException(() -> "Failed to parse announcement IDs", ex);
Logger.printException(() -> "Failed to parse announcement ID", ex);
}
// Do not show the announcement, if the last announcement id is the same as the current one.

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