diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 000000000..2d6d258f4
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,3 @@
+[*.{kt,kts}]
+ktlint_code_style = intellij_idea
+ktlint_standard_no-wildcard-imports = disabled
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 788a30544..da09df8e0 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -6,11 +6,12 @@ body:
- type: markdown
attributes:
value: |
- # ReVanced Extended Patches bug report
+ # ReVanced Extended bug report
Before creating a new bug report, please keep the following in mind:
- - **Do not submit a duplicate bug report**: You can review existing bug reports [here](https://github.com/YT-Advanced/ReX-patches/labels/Bug%20report).
+ - If your bug is not related to [unique features](https://github.com/anddea/revanced-patches/wiki/Unique-features), open your bug report in [ReVanced](https://github.com/ReVanced/revanced-patches/issues) and [RVX](https://github.com/inotia00/ReVanced_Extended/issues) first.
+ - **Do not submit a duplicate bug report**: You can review existing bug reports [here](https://github.com/anddea/revanced-patches/labels/Bug%20report).
- **Check if this issue reproduces in unpatched apps as well**: Most bugs can also be reproduced in unpatched apps.
- type: dropdown
attributes:
@@ -36,7 +37,7 @@ body:
attributes:
label: Application
description: Write down the application and version where the issue occurs.
- placeholder: e.g. YouTube v19.02.39
+ placeholder: e.g. YouTube v19.16.39
validations:
required: true
- type: textarea
@@ -48,7 +49,7 @@ body:
- Add images and videos if possible
- List used patches if applicable
validations:
- required: true
+ required: true
- type: textarea
attributes:
label: Error logs
@@ -73,10 +74,14 @@ body:
label: Acknowledgements
description: Your bug report will be closed if you don't follow the checklist below.
options:
- - label: This issue does not reproduce on unpatched YouTube or YT Music.
+ - label: This issue does not reproduce on unpatched apps.
required: true
- label: This issue is not a duplicate of an existing bug report.
required: true
+ - label: I did not use any settings marked as `Experimental Flags`.
+ required: true
+ - label: I have patched the APK according to the [documentation](https://github.com/inotia00/revanced-documentation#readme).
+ required: true
- label: I have chosen an appropriate title.
required: true
- label: All requested information has been provided properly.
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
index 548db4c8e..ac8a99163 100644
--- a/.github/ISSUE_TEMPLATE/feature-request.yml
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -6,11 +6,11 @@ body:
- type: markdown
attributes:
value: |
- # ReVanced Extended Patches feature request
+ # ReVanced Extended feature request
Before creating a new feature request, please keep the following in mind:
- - **Do not submit a duplicate feature request**: You can review existing feature requests [here](https://github.com/YT-Advanced/ReX-patches/labels/Feature%20request).
+ - **Do not submit a duplicate feature request**: You can review existing feature requests [here](https://github.com/anddea/revanced-patches/labels/Feature%20request).
- type: dropdown
attributes:
label: Application
@@ -30,7 +30,7 @@ body:
- type: textarea
attributes:
label: Motivation
- description: |
+ description: |
A strong motivation is necessary for a feature request to be considered.
- Why should this feature be implemented?
diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml
deleted file mode 100644
index 1a7c4fa42..000000000
--- a/.github/ISSUE_TEMPLATE/question.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-name: ❓ Question
-description: Leave your questions about the patches or other things.
-title: 'question: '
-labels: ['Question']
-body:
- - type: markdown
- attributes:
- value: |
- # ReX question
-
- Before creating a new question, please keep the following in mind:
-
- - **Do not submit a duplicate question**: You can review existing questions [here](https://github.com/YT-Advanced/ReX-patches/labels/Question).
- - type: dropdown
- attributes:
- label: Application
- options:
- - YouTube
- - YouTube Music
- - Other
- validations:
- required: true
- - type: textarea
- attributes:
- label: Question description
- description: |
- - Describe your question in detail
- - Add images and videos if possible
- - type: checkboxes
- id: acknowledgements
- attributes:
- label: Acknowledgements
- description: Your issue will be closed if you haven't done these steps.
- options:
- - label: This issue is not a duplicate of an existing question.
- required: true
- - label: I have chosen an appropriate title.
- required: true
- - label: All requested information has been provided properly.
- required: true
- - label: I have written the title and contents in English.
- required: true
diff --git a/.github/ISSUE_TEMPLATE/suggestion.yml b/.github/ISSUE_TEMPLATE/suggestion.yml
deleted file mode 100644
index c15d8aef1..000000000
--- a/.github/ISSUE_TEMPLATE/suggestion.yml
+++ /dev/null
@@ -1,45 +0,0 @@
-name: 📜 Suggestion
-description: Leave any other suggestions about the patches or other things.
-title: 'suggestion: '
-labels: ['Suggestion']
-body:
- - type: markdown
- attributes:
- value: |
- # ReX suggestion
-
- Before creating a new suggestion, please keep the following in mind:
-
- - **Do not submit a duplicate suggestion**: You can review existing suggestions [here](https://github.com/YT-Advanced/ReX-patches/labels/Suggestion).
- - **Do not write feature requests**: This is a suggestion, not a feature request.
- - type: dropdown
- attributes:
- label: Application
- options:
- - YouTube
- - YouTube Music
- - Other
- validations:
- required: true
- - type: textarea
- attributes:
- label: Suggestion description
- description: |
- - Describe your suggestion in detail
- - Add images and videos if possible
- - type: checkboxes
- id: acknowledgements
- attributes:
- label: Acknowledgements
- description: Your issue will be closed if you haven't done these steps.
- options:
- - label: This issue is not a duplicate of an existing suggestion.
- required: true
- - label: This is not a feature request.
- required: true
- - label: I have chosen an appropriate title.
- required: true
- - label: All requested information has been provided properly.
- required: true
- - label: I have written the title and contents in English.
- required: true
diff --git a/.github/config.yml b/.github/config.yml
index 09ed019c1..075f56b53 100644
--- a/.github/config.yml
+++ b/.github/config.yml
@@ -1,2 +1,2 @@
firstPRMergeComment: >
- Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) if you want to receive a contributor role.
+ Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) to receive a role for your contribution.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..93e7caf35
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,22 @@
+version: 2
+updates:
+ - package-ecosystem: github-actions
+ labels: []
+ directory: /
+ target-branch: dev
+ schedule:
+ interval: monthly
+
+ - package-ecosystem: npm
+ labels: []
+ directory: /
+ target-branch: dev
+ schedule:
+ interval: monthly
+
+ - package-ecosystem: gradle
+ labels: []
+ directory: /
+ target-branch: dev
+ schedule:
+ interval: monthly
diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml
index 250871bcc..193a26af0 100644
--- a/.github/workflows/build_pull_request.yml
+++ b/.github/workflows/build_pull_request.yml
@@ -16,6 +16,12 @@ jobs:
with:
fetch-depth: 0
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: "temurin"
+ java-version: "17"
+
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
diff --git a/.github/workflows/open_pull_request.yml b/.github/workflows/open_pull_request.yml
index 46021f4a8..721ab088d 100644
--- a/.github/workflows/open_pull_request.yml
+++ b/.github/workflows/open_pull_request.yml
@@ -20,8 +20,9 @@ jobs:
- name: Open pull request
uses: repo-sync/pull-request@v2
with:
- destination_branch: 'main'
+ destination_branch: main
pr_title: 'chore: ${{ env.MESSAGE }}'
pr_body: |
This pull request will ${{ env.MESSAGE }}.
+
pr_draft: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4d42ee549..9aa3a001b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -10,6 +10,11 @@ on:
jobs:
release:
name: Release
+ permissions:
+ contents: write
+ issues: write
+ pull-requests: write
+ packages: write
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -20,13 +25,20 @@ jobs:
persist-credentials: false
fetch-depth: 0
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: "temurin"
+ java-version: "17"
+
- name: Cache Gradle
uses: burrunan/gradle-cache-action@v1
- name: Build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: ./gradlew generateMeta generatePatchesFiles clean
+ # To update `README.md` and `patches.json`, the command `./gradlew generatePatchesFiles clean` should be used instead of the command `./gradlew build clean`
+ run: ./gradlew generatePatchesFiles clean
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -46,5 +58,5 @@ jobs:
- name: Release
env:
- GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm exec semantic-release
diff --git a/.gitignore b/.gitignore
index 9f2e41679..a30497ffc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -122,9 +122,12 @@ gradle-app.setting
# Dependency directories
node_modules/
-# gradle properties, due to Github token
+# Gradle properties, due to Github token
./gradle.properties
+# One package is called the same as the Gradle build folder
+!**/src/**/build/
+
.DS_Store
local.properties
__pycache__
diff --git a/.idea/misc.xml b/.idea/misc.xml
index e7f3afad1..bbdaad2de 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,8 +1,7 @@
-
-
+
\ No newline at end of file
diff --git a/.releaserc b/.releaserc
index 6193511b8..0abaf5291 100644
--- a/.releaserc
+++ b/.releaserc
@@ -24,8 +24,9 @@
"README.md",
"CHANGELOG.md",
"gradle.properties",
- "patches.json"
- ]
+ "patches.json",
+ ],
+ "message": "chore: Release v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
[
@@ -33,11 +34,11 @@
{
"assets": [
{
- "path": "build/libs/revanced-patches*"
+ "path": "patches/build/libs/patches-!(*sources*|*javadoc*).rvp?(.asc)"
},
{
"path": "patches.json"
- }
+ },
],
successComment: false
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b78e26954..b50ea264e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,159 @@
+# [3.0.0-dev.8](https://github.com/anddea/revanced-patches/compare/v3.0.0-dev.7...v3.0.0-dev.8) (2024-12-23)
+
+
+### Features
+
+* **YouTube - Navigation bar components:** Add `Enable translucent status bar` setting (for YouTube 19.25+) ([2cf269b](https://github.com/anddea/revanced-patches/commit/2cf269bcda307b6546d7ab82d5fcd33bc8c0b329))
+
+# [3.0.0-dev.7](https://github.com/anddea/revanced-patches/compare/v3.0.0-dev.6...v3.0.0-dev.7) (2024-12-23)
+
+
+### Bug Fixes
+
+* **Reddit - Hide ads:** `Hide ads` patch fails on `2024.17.0` ([8426f74](https://github.com/anddea/revanced-patches/commit/8426f7471e1a54963cb56131f3e1362068375d1b))
+* **YouTube - Shorts components:** Patch failing during building with certain patch selection ([a442ff3](https://github.com/anddea/revanced-patches/commit/a442ff38ca1a4ee9372fb4bf1bbdc8f8c67a1c20))
+* **YouTube - Toolbar components:** `Hide voice search button` setting does not work ([cb6868a](https://github.com/anddea/revanced-patches/commit/cb6868a8eb46b75f4741ea58adb684aa9c37b4d3))
+* **YouTube Music:** App crashes when including `Hide action bar components` patch ([e7646e2](https://github.com/anddea/revanced-patches/commit/e7646e2410e6e622207ddf8b89011d3507f1ea30))
+
+
+### Features
+
+* **YouTube - Navigation bar components:** Bring back`Enable translucent navigation bar` setting ([158f8da](https://github.com/anddea/revanced-patches/commit/158f8da6c7432c9df07a3b91d7c25f87ec14aaef))
+* **YouTube - Seekbar components:** Bring back `Enable Cairo seekbar` setting (for YouTube 19.23.40-19.32.36) ([fed8915](https://github.com/anddea/revanced-patches/commit/fed89158dd9dd63552362c580d4eebfd57715d68))
+* **YouTube Music - Spoof client:** Limit support version to 7.16.53 and change default client preset ([20beb64](https://github.com/anddea/revanced-patches/commit/20beb648ae675e313ff099da4dbdf6d6b91e9285))
+
+# [3.0.0-dev.6](https://github.com/anddea/revanced-patches/compare/v3.0.0-dev.5...v3.0.0-dev.6) (2024-12-21)
+
+
+### Bug Fixes
+
+* **YouTube - Custom branding icon:** Patch option `restoreOldSplashAnimation` not working in YouTube 19.32.39+ ([b81d32e](https://github.com/anddea/revanced-patches/commit/b81d32eea4ca5ba26c6294d9467f6823d2088ca0))
+* **YouTube - Hide feed components:** `Hide carousel shelf` hiding in library in certain situations ([10e4667](https://github.com/anddea/revanced-patches/commit/10e466737ac09f4ab328d520c211a9b1a8e413ab))
+* **YouTube - Miniplayer:** Use estimated maximum on screen size for devices with low density screens ([efeb5fb](https://github.com/anddea/revanced-patches/commit/efeb5fba02d660bfd3f19b59fc1ef21ba11ea062))
+* **YouTube - Theme:** Splash background color not applied in latest YouTube client ([a8c4462](https://github.com/anddea/revanced-patches/commit/a8c446222b9a75e0c74af78dfe029334d8f3dfa0))
+* **YouTube - Video playback:** Applying default video quality to Shorts causes the beginning of the shorts to get stuck in a loop ([9c4c56e](https://github.com/anddea/revanced-patches/commit/9c4c56eefa65b1c3f61e23cf78375272b33e679f))
+* **YouTube Music - Hide action bar components:** `Hide Download button` setting not working in YouTube Music 7.25.52 ([85dfb09](https://github.com/anddea/revanced-patches/commit/85dfb09521cff167e9029b25d48c9d84a33ed57b))
+* **YouTube Music - SponsorBlock:** `Change segment behavior` and `About` sections are hidden in the settings ([176adb8](https://github.com/anddea/revanced-patches/commit/176adb89102f96c22e8a437d019c688df933e2dd))
+* **YouTube:** Splash screen background color does not change in dark mode if `Theme` patch is excluded ([28df1b4](https://github.com/anddea/revanced-patches/commit/28df1b4172262719fcc86b038e4a617bce0dc6e9))
+* **YouTube:** When clicking on timestamps in comments, playback speed sometimes changes to 1.0x (unpatched YouTube bug) ([93c4bf8](https://github.com/anddea/revanced-patches/commit/93c4bf8fb63fe7f06f3908bc6e755c568f86667f))
+
+
+### Features
+
+* **YouTube - Navigation bar components:** Add `Disable translucent status bar` setting ([fe09dbc](https://github.com/anddea/revanced-patches/commit/fe09dbcece9324f224950cd784caabf2db64bf6c))
+* **YouTube - Navigation bar components:** Separate `Enable translucent navigation bar` setting into `Disable light translucent bar` and `Disable dark translucent bar` settings ([602de6e](https://github.com/anddea/revanced-patches/commit/602de6e9692c1af4ab892726305ce2e6d4a04f08))
+* **YouTube - Shorts components:** Add `Restore old player layout` setting (YouTube 18.29.38 ~ 19.16.39) ([bf8afdd](https://github.com/anddea/revanced-patches/commit/bf8afddae275b9ef7568fb68398749d2b3f47941))
+* **YouTube - Shorts components:** Add styles to custom actions dialog ([e95b064](https://github.com/anddea/revanced-patches/commit/e95b0643131bfe20ded2747734c34c603838812c))
+* **YouTube - Swipe controls:** Change the setting name `Enable watch panel gestures` to `Disable watch panel gestures`, and change the setting name `Enable swipe to change video` to `Disable swipe to change video` ([375cf3a](https://github.com/anddea/revanced-patches/commit/375cf3ad331c3823ee17cbea450f8e87510e58b0))
+* **YouTube Music - Hide action bar components:** Limit the available versions of the `Override Download action button` setting to 7.16.53 ([16ead35](https://github.com/anddea/revanced-patches/commit/16ead35768678e7ac4a3327635a2f6f3ecf6bce2))
+* **YouTube Music - Spoof client:** Add `Use old client` and `Default client` settings ([bb3bd2a](https://github.com/anddea/revanced-patches/commit/bb3bd2a9d799c0177ad39c8d59b0884c510b5864))
+* **YouTube:** Support version `19.44.39` ([22419fd](https://github.com/anddea/revanced-patches/commit/22419fd9d53bd2234283423a8e9fec30b1fff3fc))
+
+# [3.0.0-dev.5](https://github.com/anddea/revanced-patches/compare/v3.0.0-dev.4...v3.0.0-dev.5) (2024-12-17)
+
+
+### Bug Fixes
+
+* **YouTube - Enable gradient loading screen:** `Enable gradient loading screen` not working on YouTube 19.34.42+ ([d6b6a42](https://github.com/anddea/revanced-patches/commit/d6b6a427e31e3324163679735b7dceac0234460a))
+* **YouTube - Hide ads:** Hide new type of featured promotions ([c1dadc0](https://github.com/anddea/revanced-patches/commit/c1dadc0e2b45149974d8a50c2d7de0e05e1537a2))
+* **YouTube - Hide feed components:** `Hide carousel shelf` hiding in library in certain situations ([6323421](https://github.com/anddea/revanced-patches/commit/6323421d7c3a99c278258f19ca36a489b6345875))
+* **YouTube - Hide feed components:** `Hide carousel shelf` not hiding in home feed in certain situations ([0c690b1](https://github.com/anddea/revanced-patches/commit/0c690b1ee732764588ef600f37097b662b11e902))
+* **YouTube - Hide feed components:** New kind of community posts are not hidden ([a58ed6b](https://github.com/anddea/revanced-patches/commit/a58ed6b19727940f28b8e1d1247dcd742942495c))
+* **YouTube - Hide player flyout menu:** `Sleep timer menu` always hidden in YouTube 19.34.42 ([fa42f5f](https://github.com/anddea/revanced-patches/commit/fa42f5f820772fddba720f9895a21b7f21c2182f))
+* **YouTube - MaterialYou:** Theme not applied to notification dots in YouTube 19.34.42+ ([fd31d87](https://github.com/anddea/revanced-patches/commit/fd31d87ba5925305882d761f3455c05007988ddf))
+* **YouTube - Player components:** `Hide seek message` not working on YouTube 19.34.42 ([8cb3b4b](https://github.com/anddea/revanced-patches/commit/8cb3b4b91ae9531824a124ae897b1d9729f8f340))
+* **YouTube - Seekbar components:** `Custom seekbar color` not applied to gradient seekbar in YouTube 19.34.42 ([b3ac64c](https://github.com/anddea/revanced-patches/commit/b3ac64c05a44d8b7d035de94f2d9cec26d7514f3))
+* **YouTube - Shorts components:** `Hide Shorts shelves` not hiding in home feed in certain situations ([3481e01](https://github.com/anddea/revanced-patches/commit/3481e01a7c224d262ec7b23e1f132787dc3f838e))
+* **YouTube - Spoof streaming data:** On `iOS` clients, livestreams always start from the beginning ([4e60bf5](https://github.com/anddea/revanced-patches/commit/4e60bf514bd5480e35bb102d2f4758f766b311a8))
+* **YouTube - Spoof streaming data:** Videos end 1 second early on iOS client ([b2cc033](https://github.com/anddea/revanced-patches/commit/b2cc03320e934532425d852ac5832dffdfefb98c))
+* **YouTube - VideoInformation:** Channel name not fetched in YouTube 19.34.42 ([2e19453](https://github.com/anddea/revanced-patches/commit/2e194533eb9c7169fdbd6ec321422dca19d54a34))
+* **YouTube & YouTube Music - Custom branding icon:** Patching fails in some environments when the path entered in the patch options contains uppercase letters ([786bc36](https://github.com/anddea/revanced-patches/commit/786bc36e2a99ae9b2c15c7659c3ac1f4c9ee26f6))
+* **YouTube Music - Spoof client:** Action bar not loading as of YouTube Music 7.17.51 ([943c288](https://github.com/anddea/revanced-patches/commit/943c28866ab7af53fb986b648c87f8baf0d67ff9))
+
+
+### Features
+
+* **YouTube - Custom branding icon:** Add `YouTube Black` icon ([e706c5f](https://github.com/anddea/revanced-patches/commit/e706c5fc67c2318505ea1e5588437bba0040c85a))
+* **YouTube - Custom branding icon:** Restrict the version that can use the patch option `Restore old splash animation` to 19.16.39 (deprecated) ([8589c5a](https://github.com/anddea/revanced-patches/commit/8589c5afc4b84ef680f56dee7b93ad3e93df9985))
+* **YouTube - Navigation bar components:** Add missing resource for Cairo notification icon (YouTube 19.34.42+) ([2982725](https://github.com/anddea/revanced-patches/commit/2982725d8a9fdd81280d9ba79425d0bd05b3431d))
+* **YouTube - Player components:** Add `Hide Chat summary in live chat` setting ([963dbe8](https://github.com/anddea/revanced-patches/commit/963dbe89970b64ff99d277bfada6c3dfb403f4bb))
+* **YouTube - Remove background playback restrictions:** Add PiP mode support in Shorts ([4fc44b2](https://github.com/anddea/revanced-patches/commit/4fc44b2ba56c78aaa87b28396a0ef72e0e9fe3f9))
+* **YouTube - Seekbar components:** Change default seekbar color to match new branding ([26d8ba6](https://github.com/anddea/revanced-patches/commit/26d8ba6c2bc2a81725bbf61a7bf3a4090175e156))
+* **YouTube - Seekbar components:** Remove `Enable Cairo seekbar` setting, which is no longer needed (Enabled by default in YouTube 19.34.42) ([c12f4ae](https://github.com/anddea/revanced-patches/commit/c12f4aeddff61a1c2ac00c26c59777bd76e91f69))
+* **YouTube - Shorts components:** Add `Change Shorts background repeat state` setting (YouTube 19.34.42+) ([84d6ccc](https://github.com/anddea/revanced-patches/commit/84d6ccc7cf738650bbfbf15b42fc5b1be02f3d98))
+* **YouTube - Shorts components:** Add `Custom actions in toolbar` setting (YouTube 18.38.44+) ([6732b2b](https://github.com/anddea/revanced-patches/commit/6732b2b373b6e4208c2966f4ea262ebe49886f92))
+* **YouTube - Shorts components:** Add `Custom actions` setting (YouTube 19.05.36+) ([ff5b527](https://github.com/anddea/revanced-patches/commit/ff5b5279d4c21eabeedaf2d6a1e8ae25871ae042))
+* **YouTube - Spoof app version:** Add target version `19.26.42 - Disable Cairo icon in navigation and toolbar` and `19.33.37 - Restore old playback speed flyout panel` ([671e809](https://github.com/anddea/revanced-patches/commit/671e8098c516b05af82eb1609e6740e49d79838c))
+* **YouTube - Spoof streaming data:** Remove `Skip iOS livestream playback` setting (no longer needed) ([b5e507c](https://github.com/anddea/revanced-patches/commit/b5e507c7a3718843469f7e366f96a53d279c28a0))
+* **YouTube & YouTube Music - Settings:** Add `RVX settings summaries` to patch options ([6211b44](https://github.com/anddea/revanced-patches/commit/6211b448c9979218fe566c8c00e69c09ebe37790))
+* **YouTube Music - Custom branding icon:** Delete old `Revancify Yellow` icon ([#893](https://github.com/anddea/revanced-patches/issues/893)) ([0c09f4d](https://github.com/anddea/revanced-patches/commit/0c09f4df563c57f9645506f04fb8ca8f1a399334))
+* **YouTube Music - Hide player flyout menu:** Add `Hide Speed dial menu` setting ([42b6bd5](https://github.com/anddea/revanced-patches/commit/42b6bd5e994c3fc22457bf79bfbc55cd15de5734))
+* **YouTube Music:** Add `Disable DRC audio` patch ([a3b458d](https://github.com/anddea/revanced-patches/commit/a3b458d50f769cd35d7e7b5ff9144aec0f8dc199))
+* **YouTube Music:** Add `Spoof streaming data` patch ([ef14e5a](https://github.com/anddea/revanced-patches/commit/ef14e5acc93e9a9ad5d9eef861023f1d5623e4ff))
+* **YouTube Music:** Support version `7.25.52` ([d8fac8b](https://github.com/anddea/revanced-patches/commit/d8fac8b82c5983efd8096f533e175befe4ba396a))
+* **YouTube:** Support version `19.38.41` ([756e02f](https://github.com/anddea/revanced-patches/commit/756e02f91a642936f5ab502dcfc33d05eff6eabd))
+
+# [3.0.0-dev.4](https://github.com/anddea/revanced-patches/compare/v3.0.0-dev.3...v3.0.0-dev.4) (2024-12-12)
+
+
+### Bug Fixes
+
+* **YouTube Music - Visual preferences icons:** Custom branding icons did not work ([45fa7fd](https://github.com/anddea/revanced-patches/commit/45fa7fd9169218bd0cee46b8413aee7611212b0b))
+
+# [3.0.0-dev.3](https://github.com/anddea/revanced-patches/compare/v3.0.0-dev.2...v3.0.0-dev.3) (2024-12-12)
+
+
+### Bug Fixes
+
+* **YouTube - Overlay buttons:** Play all button did not work for all videos when using all content by time ascending ([e4e51f5](https://github.com/anddea/revanced-patches/commit/e4e51f583ebbf2986a1077860503e3e94c3a3f05))
+* **YouTube - Seekbr components:** Reverse start and end colors for Cairo seekbar ([e9bd106](https://github.com/anddea/revanced-patches/commit/e9bd106114c1669426d10830b544d7936a0728a1))
+
+# [3.0.0-dev.2](https://github.com/anddea/revanced-patches/compare/v3.0.0-dev.1...v3.0.0-dev.2) (2024-12-12)
+
+
+### Bug Fixes
+
+* **YouTube - Visual preferences icons:** Add missing icons in the Manager ([a9b443a](https://github.com/anddea/revanced-patches/commit/a9b443a738ca6f94a98ee32d9cd7ad0837ce66a8))
+
+# [3.0.0-dev.1](https://github.com/anddea/revanced-patches/compare/v2.232.0-dev.1...v3.0.0-dev.1) (2024-12-11)
+
+
+### Bug Fixes
+
+* **YouTube - Return YouTube Dislike:** Show Shorts dislikes with new A/B button icons ([ad0d15e](https://github.com/anddea/revanced-patches/commit/ad0d15e832c0a51997c35780d880f1bcb2a8b495))
+* **YouTube - Shorts components:** Do not hide Shorts action buttons on app first launch ([f5cd017](https://github.com/anddea/revanced-patches/commit/f5cd0173a845352da7d4d5d13f5d545eaf8217b1))
+* **YouTube - SponsorBlock:** Fix create new segment crash on tablet custom roms ([58b5fbf](https://github.com/anddea/revanced-patches/commit/58b5fbfcc77d46831f61a3099fbf37c01ae2b2ba))
+* **YouTube - Spoof streaming data:** Fix memory leak in `ByteArrayOutputStream` ([42d7bbe](https://github.com/anddea/revanced-patches/commit/42d7bbe8da244c73802e37cab646ccad590d7bdb))
+* **YouTube - Video playback:** Correctly set default quality when changing from a low quality video ([8cbe976](https://github.com/anddea/revanced-patches/commit/8cbe9766a4f26aeee909a799b834c4c85efd7e9d))
+
+
+### Code Refactoring
+
+* Bump ReVanced Patcher & merge integrations ([7dde697](https://github.com/anddea/revanced-patches/commit/7dde697995b3fa02749eff52cf50d1f903fc54ef))
+
+
+### Features
+
+* **YouTube - Overlay buttons:** Replace `Time-ordered playlist` button with `Play all` button ([5a15809](https://github.com/anddea/revanced-patches/commit/5a15809c96c4d6e988b196be8d0a85a828fe1d1b))
+* **YouTube - Spoof streaming data:** Rename the `iOS Compatibility mode` setting to `Skip iOS livestream playback` ([efbc77d](https://github.com/anddea/revanced-patches/commit/efbc77d6a0090cc15bec935aa993155cbf8bdd0c))
+* **YouTube - Theme:** Add `Pale Blue`, `Pale Green`, `Pale Orange` light colors ([1bed931](https://github.com/anddea/revanced-patches/commit/1bed9310b7343f1d860c4738b512fce91ffc3895))
+* **YouTube Music - Hide ads:** Changed the default value of `Hide fullscreen ads` setting to off and added a warning to the setting ([d337d21](https://github.com/anddea/revanced-patches/commit/d337d2115ef78fd1b2c8d2fd2e528d913b7978e9))
+* **YouTube Music:** Add `Spoof client` patch ([09c7967](https://github.com/anddea/revanced-patches/commit/09c796784cbd70fde471773d2ecb5f2123855b73))
+* **YouTube:** Support version `19.34.42` ([2018306](https://github.com/anddea/revanced-patches/commit/2018306d5f578ac9915f0a6001391999896fdacc))
+
+
+### BREAKING CHANGES
+
+* Patches and Integrations are now merged
+
+# [2.232.0-dev.1](https://github.com/anddea/revanced-patches/compare/v2.231.0...v2.232.0-dev.1) (2024-11-10)
+
+
+### Features
+
+* **YouTube - Spoof app version:** Remove obsolete `19.13.37` spoof target ([743999e](https://github.com/anddea/revanced-patches/commit/743999e864892f33ee4153950339edc8ffae2578))
+* **YouTube - Spoof streaming data:** Add `iOS Compatibility mode` setting ([48b26eb](https://github.com/anddea/revanced-patches/commit/48b26eb9d2b5076248af96c2342fdcd7f29b8a51))
+
# [2.231.0](https://github.com/anddea/revanced-patches/compare/v2.230.0...v2.231.0) (2024-11-07)
diff --git a/README-template.md b/README-template.md
index 34f7342a3..08f6dd760 100644
--- a/README-template.md
+++ b/README-template.md
@@ -21,40 +21,28 @@ Example:
{
"name": "Alternative thumbnails",
"description": "Adds options to replace video thumbnails using the DeArrow API or image captures from the video.",
- "compatiblePackages":[
- {
- "name": "com.google.android.youtube",
- "versions": "COMPATIBLE_PACKAGE_YOUTUBE"
- }
- ],
"use":true,
- "requiresIntegrations":false,
+ "compatiblePackages": {
+ "com.google.android.youtube": "COMPATIBLE_PACKAGE_YOUTUBE"
+ },
"options": []
},
{
"name": "Bitrate default value",
"description": "Sets the audio quality to 'Always High' when you first install the app.",
- "compatiblePackages": [
- {
- "name": "com.google.android.apps.youtube.music",
- "versions": "COMPATIBLE_PACKAGE_MUSIC"
- }
- ],
"use":true,
- "requiresIntegrations":false,
+ "compatiblePackages": {
+ "com.google.android.apps.youtube.music": "COMPATIBLE_PACKAGE_MUSIC"
+ },
"options": []
},
{
"name": "Hide ads",
"description": "Adds options to hide ads.",
- "compatiblePackages": [
- {
- "name": "com.reddit.frontpage",
- "versions": "COMPATIBLE_PACKAGE_REDDIT"
- }
- ],
"use":true,
- "requiresIntegrations":true,
+ "compatiblePackages": {
+ "com.reddit.frontpage": "COMPATIBLE_PACKAGE_REDDIT"
+ },
"options": []
}
]
diff --git a/README.md b/README.md
index aeb70118a..b331530fd 100644
--- a/README.md
+++ b/README.md
@@ -13,68 +13,69 @@ Check the [wiki](https://github.com/anddea/revanced-patches/wiki) for resources
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
-| `Alternative thumbnails` | Adds options to replace video thumbnails using the DeArrow API or image captures from the video. | 18.29.38 ~ 19.16.39 |
-| `Ambient mode control` | Adds options to disable Ambient mode and to bypass Ambient mode restrictions. | 18.29.38 ~ 19.16.39 |
-| `Bypass image region restrictions` | Adds an option to use a different host for static images, so that images blocked in some countries can be received. | 18.29.38 ~ 19.16.39 |
-| `Change player flyout menu toggles` | Adds an option to use text toggles instead of switch toggles within the additional settings menu. | 18.29.38 ~ 19.16.39 |
-| `Change share sheet` | Add option to change from in-app share sheet to system share sheet. | 18.29.38 ~ 19.16.39 |
-| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 18.29.38 ~ 19.16.39 |
-| `Custom Shorts action buttons` | Changes, at compile time, the icon of the action buttons of the Shorts player. | 18.29.38 ~ 19.16.39 |
-| `Custom branding icon for YouTube` | Changes the YouTube app icon to the icon specified in options.json. | 18.29.38 ~ 19.16.39 |
-| `Custom branding name for YouTube` | Renames the YouTube app to the name specified in options.json. | 18.29.38 ~ 19.16.39 |
-| `Custom double tap length` | Adds Double-tap to seek values that are specified in options.json. | 18.29.38 ~ 19.16.39 |
-| `Description components` | Adds options to hide and disable description components. | 18.29.38 ~ 19.16.39 |
-| `Disable QUIC protocol` | Adds an option to disable CronetEngine's QUIC protocol. | 18.29.38 ~ 19.16.39 |
-| `Disable auto audio tracks` | Adds an option to disable audio tracks from being automatically enabled. | 18.29.38 ~ 19.16.39 |
-| `Disable auto captions` | Adds an option to disable captions from being automatically enabled. | 18.29.38 ~ 19.16.39 |
-| `Disable haptic feedback` | Adds options to disable haptic feedback when swiping in the video player. | 18.29.38 ~ 19.16.39 |
-| `Disable resuming Shorts on startup` | Adds an option to disable the Shorts player from resuming on app startup when Shorts were last being watched. | 18.29.38 ~ 19.16.39 |
-| `Disable splash animation` | Adds an option to disable the splash animation on app startup. | 18.29.38 ~ 19.16.39 |
-| `Enable OPUS codec` | Adds an options to enable the OPUS audio codec if the player response includes. | 18.29.38 ~ 19.16.39 |
-| `Enable debug logging` | Adds an option to enable debug logging. | 18.29.38 ~ 19.16.39 |
-| `Enable external browser` | Adds an option to always open links in your browser instead of in the in-app-browser. | 18.29.38 ~ 19.16.39 |
-| `Enable gradient loading screen` | Adds an option to enable the gradient loading screen. | 18.29.38 ~ 19.16.39 |
-| `Enable open links directly` | Adds an option to skip over redirection URLs in external links. | 18.29.38 ~ 19.16.39 |
-| `Force player buttons background` | Changes the dark background surrounding the video player controls at compile time. | 18.29.38 ~ 19.16.39 |
-| `Force snackbar theme` | Force snackbar background color to match selected theme. | 18.29.38 ~ 19.16.39 |
-| `Fullscreen components` | Adds options to hide or change components related to fullscreen. | 18.29.38 ~ 19.16.39 |
-| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 18.29.38 ~ 19.16.39 |
-| `Hide Shorts dimming` | Removes, at compile time, the dimming effect at the top and bottom of Shorts videos. | 18.29.38 ~ 19.16.39 |
-| `Hide action buttons` | Adds options to hide action buttons under videos. | 18.29.38 ~ 19.16.39 |
-| `Hide ads` | Adds options to hide ads. | 18.29.38 ~ 19.16.39 |
-| `Hide comments components` | Adds options to hide components related to comments. | 18.29.38 ~ 19.16.39 |
-| `Hide feed components` | Adds options to hide components related to feeds. | 18.29.38 ~ 19.16.39 |
-| `Hide feed flyout menu` | Adds the ability to hide feed flyout menu components using a custom filter. | 18.29.38 ~ 19.16.39 |
-| `Hide layout components` | Adds options to hide general layout components. | 18.29.38 ~ 19.16.39 |
-| `Hide player buttons` | Adds options to hide buttons in the video player. | 18.29.38 ~ 19.16.39 |
-| `Hide player flyout menu` | Adds options to hide player flyout menu components. | 18.29.38 ~ 19.16.39 |
-| `Hide shortcuts` | Remove, at compile time, the app shortcuts that appears when app icon is long pressed. | 18.29.38 ~ 19.16.39 |
-| `Hook YouTube Music actions` | Adds support for opening music in RVX Music using the in-app YouTube Music button. | 18.29.38 ~ 19.16.39 |
-| `Hook download actions` | Adds support to download videos with an external downloader app using the in-app download button. | 18.29.38 ~ 19.16.39 |
-| `Layout switch` | Adds an option to spoof the dpi in order to use a tablet or phone layout. | 18.29.38 ~ 19.16.39 |
-| `MaterialYou` | Applies the MaterialYou theme for Android 12+ devices. | 18.29.38 ~ 19.16.39 |
-| `Miniplayer` | Adds options to change the in app minimized player, and if patching target 19.16+ adds options to use modern miniplayers. | 18.29.38 ~ 19.16.39 |
-| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 18.29.38 ~ 19.16.39 |
-| `Overlay buttons` | Adds options to display overlay buttons in the video player. | 18.29.38 ~ 19.16.39 |
-| `Player components` | Adds options to hide or change components related to the video player. | 18.29.38 ~ 19.16.39 |
-| `Remove background playback restrictions` | Removes restrictions on background playback, including for music and kids videos. | 18.29.38 ~ 19.16.39 |
-| `Remove viewer discretion dialog` | Adds an option to remove the dialog that appears when opening a video that has been age-restricted by accepting it automatically. This does not bypass the age restriction. | 18.29.38 ~ 19.16.39 |
-| `Return YouTube Dislike` | Adds an option to show the dislike count of videos using the Return YouTube Dislike API. | 18.29.38 ~ 19.16.39 |
-| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 18.29.38 ~ 19.16.39 |
-| `Sanitize sharing links` | Adds an option to remove tracking query parameters from URLs when sharing links. | 18.29.38 ~ 19.16.39 |
-| `Seekbar components` | Adds options to hide or change components related to the seekbar. | 18.29.38 ~ 19.16.39 |
-| `Settings for YouTube` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 18.29.38 ~ 19.16.39 |
-| `Shorts components` | Adds options to hide or change components related to YouTube Shorts. | 18.29.38 ~ 19.16.39 |
-| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as sponsored content. | 18.29.38 ~ 19.16.39 |
-| `Spoof app version` | Adds options to spoof the YouTube client version. This can be used to restore old UI elements and features. | 18.29.38 ~ 19.16.39 |
-| `Spoof streaming data` | Adds options to spoof the streaming data to allow video playback. | 18.29.38 ~ 19.16.39 |
-| `Spoof watch history` | Adds an option to change the domain of the watch history or check its status. | 18.29.38 ~ 19.16.39 |
-| `Swipe controls` | Adds options for controlling volume and brightness with swiping, and whether to enter fullscreen when swiping down below the player. | 18.29.38 ~ 19.16.39 |
-| `Theme` | Changes the app's theme to the values specified in options.json. | 18.29.38 ~ 19.16.39 |
-| `Toolbar components` | Adds options to hide or change components located on the toolbar, such as toolbar buttons, search bar, and header. | 18.29.38 ~ 19.16.39 |
-| `Translations for YouTube` | Add translations or remove string resources. | 18.29.38 ~ 19.16.39 |
-| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 18.29.38 ~ 19.16.39 |
-| `Visual preferences icons for YouTube` | Adds icons to specific preferences in the settings. | 18.29.38 ~ 19.16.39 |
+| `Alternative thumbnails` | Adds options to replace video thumbnails using the DeArrow API or image captures from the video. | 18.29.38 ~ 19.44.39 |
+| `Ambient mode control` | Adds options to disable Ambient mode and to bypass Ambient mode restrictions. | 18.29.38 ~ 19.44.39 |
+| `Bypass image region restrictions` | Adds an option to use a different host for static images, so that images blocked in some countries can be received. | 18.29.38 ~ 19.44.39 |
+| `Change player flyout menu toggles` | Adds an option to use text toggles instead of switch toggles within the additional settings menu. | 18.29.38 ~ 19.44.39 |
+| `Change share sheet` | Add option to change from in-app share sheet to system share sheet. | 18.29.38 ~ 19.44.39 |
+| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 18.29.38 ~ 19.44.39 |
+| `Custom Shorts action buttons` | Changes, at compile time, the icon of the action buttons of the Shorts player. | 18.29.38 ~ 19.44.39 |
+| `Custom branding icon for YouTube` | Changes the YouTube app icon to the icon specified in patch options. | 18.29.38 ~ 19.44.39 |
+| `Custom branding name for YouTube` | Renames the YouTube app to the name specified in patch options. | 18.29.38 ~ 19.44.39 |
+| `Custom double tap length` | Adds Double-tap to seek values that are specified in patch options. | 18.29.38 ~ 19.44.39 |
+| `Custom header for YouTube` | Applies a custom header in the top left corner within the app. | 18.29.38 ~ 19.44.39 |
+| `Description components` | Adds options to hide and disable description components. | 18.29.38 ~ 19.44.39 |
+| `Disable QUIC protocol` | Adds an option to disable CronetEngine's QUIC protocol. | 18.29.38 ~ 19.44.39 |
+| `Disable auto audio tracks` | Adds an option to disable audio tracks from being automatically enabled. | 18.29.38 ~ 19.44.39 |
+| `Disable auto captions` | Adds an option to disable captions from being automatically enabled. | 18.29.38 ~ 19.44.39 |
+| `Disable haptic feedback` | Adds options to disable haptic feedback when swiping in the video player. | 18.29.38 ~ 19.44.39 |
+| `Disable resuming Shorts on startup` | Adds an option to disable the Shorts player from resuming on app startup when Shorts were last being watched. | 18.29.38 ~ 19.44.39 |
+| `Disable splash animation` | Adds an option to disable the splash animation on app startup. | 18.29.38 ~ 19.44.39 |
+| `Enable OPUS codec` | Adds an options to enable the OPUS audio codec if the player response includes. | 18.29.38 ~ 19.44.39 |
+| `Enable debug logging` | Adds an option to enable debug logging. | 18.29.38 ~ 19.44.39 |
+| `Enable external browser` | Adds an option to always open links in your browser instead of in the in-app-browser. | 18.29.38 ~ 19.44.39 |
+| `Enable gradient loading screen` | Adds an option to enable the gradient loading screen. | 18.29.38 ~ 19.44.39 |
+| `Enable open links directly` | Adds an option to skip over redirection URLs in external links. | 18.29.38 ~ 19.44.39 |
+| `Force player buttons background` | Changes the dark background surrounding the video player controls at compile time. | 18.29.38 ~ 19.44.39 |
+| `Force snackbar theme` | Changes snackbar background color to match selected theme at compile time. | 18.29.38 ~ 19.44.39 |
+| `Fullscreen components` | Adds options to hide or change components related to fullscreen. | 18.29.38 ~ 19.44.39 |
+| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 18.29.38 ~ 19.44.39 |
+| `Hide Shorts dimming` | Removes, at compile time, the dimming effect at the top and bottom of Shorts videos. | 18.29.38 ~ 19.44.39 |
+| `Hide action buttons` | Adds options to hide action buttons under videos. | 18.29.38 ~ 19.44.39 |
+| `Hide ads` | Adds options to hide ads. | 18.29.38 ~ 19.44.39 |
+| `Hide comments components` | Adds options to hide components related to comments. | 18.29.38 ~ 19.44.39 |
+| `Hide feed components` | Adds options to hide components related to feeds. | 18.29.38 ~ 19.44.39 |
+| `Hide feed flyout menu` | Adds the ability to hide feed flyout menu components using a custom filter. | 18.29.38 ~ 19.44.39 |
+| `Hide layout components` | Adds options to hide general layout components. | 18.29.38 ~ 19.44.39 |
+| `Hide player buttons` | Adds options to hide buttons in the video player. | 18.29.38 ~ 19.44.39 |
+| `Hide player flyout menu` | Adds options to hide player flyout menu components. | 18.29.38 ~ 19.44.39 |
+| `Hide shortcuts` | Remove, at compile time, the app shortcuts that appears when app icon is long pressed. | 18.29.38 ~ 19.44.39 |
+| `Hook YouTube Music actions` | Adds support for opening music in RVX Music using the in-app YouTube Music button. | 18.29.38 ~ 19.44.39 |
+| `Hook download actions` | Adds support to download videos with an external downloader app using the in-app download button. | 18.29.38 ~ 19.44.39 |
+| `Layout switch` | Adds an option to spoof the dpi in order to use a tablet or phone layout. | 18.29.38 ~ 19.44.39 |
+| `MaterialYou` | Applies the MaterialYou theme for Android 12+ devices. | 18.29.38 ~ 19.44.39 |
+| `Miniplayer` | Adds options to change the in app minimized player, and if patching target 19.16+ adds options to use modern miniplayers. | 18.29.38 ~ 19.44.39 |
+| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 18.29.38 ~ 19.44.39 |
+| `Overlay buttons` | Adds options to display overlay buttons in the video player. | 18.29.38 ~ 19.44.39 |
+| `Player components` | Adds options to hide or change components related to the video player. | 18.29.38 ~ 19.44.39 |
+| `Remove background playback restrictions` | Removes restrictions on background playback, including for music and kids videos. | 18.29.38 ~ 19.44.39 |
+| `Remove viewer discretion dialog` | Adds an option to remove the dialog that appears when opening a video that has been age-restricted by accepting it automatically. This does not bypass the age restriction. | 18.29.38 ~ 19.44.39 |
+| `Return YouTube Dislike` | Adds an option to show the dislike count of videos using the Return YouTube Dislike API. | 18.29.38 ~ 19.44.39 |
+| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 18.29.38 ~ 19.44.39 |
+| `Sanitize sharing links` | Adds an option to remove tracking query parameters from URLs when sharing links. | 18.29.38 ~ 19.44.39 |
+| `Seekbar components` | Adds options to hide or change components related to the seekbar. | 18.29.38 ~ 19.44.39 |
+| `Settings for YouTube` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 18.29.38 ~ 19.44.39 |
+| `Shorts components` | Adds options to hide or change components related to YouTube Shorts. | 18.29.38 ~ 19.44.39 |
+| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as sponsored content. | 18.29.38 ~ 19.44.39 |
+| `Spoof app version` | Adds options to spoof the YouTube client version. This can be used to restore old UI elements and features. | 18.29.38 ~ 19.44.39 |
+| `Spoof streaming data` | Adds options to spoof the streaming data to allow playback. | 18.29.38 ~ 19.44.39 |
+| `Spoof watch history` | Adds an option to change the domain of the watch history or check its status. | 18.29.38 ~ 19.44.39 |
+| `Swipe controls` | Adds options for controlling volume and brightness with swiping, and whether to enter fullscreen when swiping down below the player. | 18.29.38 ~ 19.44.39 |
+| `Theme` | Changes the app's theme to the values specified in patch options. | 18.29.38 ~ 19.44.39 |
+| `Toolbar components` | Adds options to hide or change components located on the toolbar, such as toolbar buttons, search bar, and header. | 18.29.38 ~ 19.44.39 |
+| `Translations for YouTube` | Add translations or remove string resources. | 18.29.38 ~ 19.44.39 |
+| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 18.29.38 ~ 19.44.39 |
+| `Visual preferences icons for YouTube` | Adds icons to specific preferences in the settings. | 18.29.38 ~ 19.44.39 |
### [📦 `com.google.android.apps.youtube.music`](https://play.google.com/store/apps/details?id=com.google.android.apps.youtube.music)
@@ -82,43 +83,46 @@ Check the [wiki](https://github.com/anddea/revanced-patches/wiki) for resources
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
-| `Amoled` | Applies a pure black theme to some components. | 6.20.51 ~ 7.16.53 |
-| `Bitrate default value` | Sets the audio quality to 'Always High' when you first install the app. | 6.20.51 ~ 7.16.53 |
-| `Bypass image region restrictions` | Adds an option to use a different host for static images, so that images blocked in some countries can be received. | 6.20.51 ~ 7.16.53 |
-| `Certificate spoof` | Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate. | 6.20.51 ~ 7.16.53 |
-| `Change share sheet` | Add option to change from in-app share sheet to system share sheet. | 6.20.51 ~ 7.16.53 |
-| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 6.20.51 ~ 7.16.53 |
-| `Custom branding icon for YouTube Music` | Changes the YouTube Music app icon to the icon specified in options.json. | 6.20.51 ~ 7.16.53 |
-| `Custom branding name for YouTube Music` | Renames the YouTube Music app to the name specified in options.json. | 6.20.51 ~ 7.16.53 |
-| `Custom header for YouTube Music` | Applies a custom header in the top left corner within the app. | 6.20.51 ~ 7.16.53 |
-| `Disable Cairo splash animation` | Adds an option to disable Cairo splash animation. | 7.06.54 ~ 7.16.53 |
-| `Disable auto captions` | Adds an option to disable captions from being automatically enabled. | 6.20.51 ~ 7.16.53 |
-| `Disable dislike redirection` | Adds an option to disable redirection to the next track when clicking the Dislike button. | 6.20.51 ~ 7.16.53 |
-| `Enable OPUS codec` | Adds an option to use the OPUS audio codec instead of the MP4A audio codec. | 6.20.51 ~ 7.16.53 |
-| `Enable debug logging` | Adds an option to enable debug logging. | 6.20.51 ~ 7.16.53 |
-| `Enable landscape mode` | Adds an option to enable landscape mode when rotating the screen on phones. | 6.20.51 ~ 7.16.53 |
-| `Flyout menu components` | Adds options to hide or change flyout menu components. | 6.20.51 ~ 7.16.53 |
-| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 6.20.51 ~ 7.16.53 |
-| `Hide account components` | Adds options to hide components related to the account menu. | 6.20.51 ~ 7.16.53 |
-| `Hide action bar components` | Adds options to hide action bar components and replace the offline download button with an external download button. | 6.20.51 ~ 7.16.53 |
-| `Hide ads` | Adds options to hide ads. | 6.20.51 ~ 7.16.53 |
-| `Hide layout components` | Adds options to hide general layout components. | 6.20.51 ~ 7.16.53 |
-| `Hide overlay filter` | Removes, at compile time, the dark overlay that appears when player flyout menus are open. | 6.20.51 ~ 7.16.53 |
-| `Hide player overlay filter` | Removes, at compile time, the dark overlay that appears when single-tapping in the player. | 6.20.51 ~ 7.16.53 |
-| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 6.20.51 ~ 7.16.53 |
-| `Player components` | Adds options to hide or change components related to the player. | 6.20.51 ~ 7.16.53 |
-| `Remove background playback restrictions` | Removes restrictions on background playback, including for kids videos. | 6.20.51 ~ 7.16.53 |
-| `Remove viewer discretion dialog` | Adds an option to remove the dialog that appears when opening a video that has been age-restricted by accepting it automatically. This does not bypass the age restriction. | 6.20.51 ~ 7.16.53 |
-| `Restore old style library shelf` | Adds an option to return the Library tab to the old style. | 6.20.51 ~ 7.16.53 |
-| `Return YouTube Dislike` | Adds an option to show the dislike count of songs using the Return YouTube Dislike API. | 6.20.51 ~ 7.16.53 |
-| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 6.20.51 ~ 7.16.53 |
-| `Sanitize sharing links` | Adds an option to remove tracking query parameters from URLs when sharing links. | 6.20.51 ~ 7.16.53 |
-| `Settings for YouTube Music` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 6.20.51 ~ 7.16.53 |
-| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections. | 6.20.51 ~ 7.16.53 |
+| `Amoled` | Applies a pure black theme to some components. | 6.20.51 ~ 7.25.53 |
+| `Bitrate default value` | Sets the audio quality to 'Always High' when you first install the app. | 6.20.51 ~ 7.25.53 |
+| `Bypass image region restrictions` | Adds an option to use a different host for static images, so that images blocked in some countries can be received. | 6.20.51 ~ 7.25.53 |
+| `Certificate spoof` | Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate. | 6.20.51 ~ 7.25.53 |
+| `Change share sheet` | Add option to change from in-app share sheet to system share sheet. | 6.20.51 ~ 7.25.53 |
+| `Change start page` | Adds an option to set which page the app opens in instead of the homepage. | 6.20.51 ~ 7.25.53 |
+| `Custom branding icon for YouTube Music` | Changes the YouTube Music app icon to the icon specified in patch options. | 6.20.51 ~ 7.25.53 |
+| `Custom branding name for YouTube Music` | Renames the YouTube Music app to the name specified in patch options. | 6.20.51 ~ 7.25.53 |
+| `Custom header for YouTube Music` | Applies a custom header in the top left corner within the app. | 6.20.51 ~ 7.25.53 |
+| `Disable Cairo splash animation` | Adds an option to disable Cairo splash animation. | 7.06.54 ~ 7.25.53 |
+| `Disable DRC audio` | Adds an option to disable DRC (Dynamic Range Compression) audio. | 6.20.51 ~ 7.25.53 |
+| `Disable auto captions` | Adds an option to disable captions from being automatically enabled. | 6.20.51 ~ 7.25.53 |
+| `Disable dislike redirection` | Adds an option to disable redirection to the next track when clicking the Dislike button. | 6.20.51 ~ 7.25.53 |
+| `Enable OPUS codec` | Adds an options to enable the OPUS audio codec if the player response includes. | 6.20.51 ~ 7.25.53 |
+| `Enable debug logging` | Adds an option to enable debug logging. | 6.20.51 ~ 7.25.53 |
+| `Enable landscape mode` | Adds an option to enable landscape mode when rotating the screen on phones. | 6.20.51 ~ 7.25.53 |
+| `Flyout menu components` | Adds options to hide or change flyout menu components. | 6.20.51 ~ 7.25.53 |
+| `GmsCore support` | Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services. | 6.20.51 ~ 7.25.53 |
+| `Hide account components` | Adds options to hide components related to the account menu. | 6.20.51 ~ 7.25.53 |
+| `Hide action bar components` | Adds options to hide action bar components and replace the offline download button with an external download button. | 6.20.51 ~ 7.25.53 |
+| `Hide ads` | Adds options to hide ads. | 6.20.51 ~ 7.25.53 |
+| `Hide layout components` | Adds options to hide general layout components. | 6.20.51 ~ 7.25.53 |
+| `Hide overlay filter` | Removes, at compile time, the dark overlay that appears when player flyout menus are open. | 6.20.51 ~ 7.25.53 |
+| `Hide player overlay filter` | Removes, at compile time, the dark overlay that appears when single-tapping in the player. | 6.20.51 ~ 7.25.53 |
+| `Navigation bar components` | Adds options to hide or change components related to the navigation bar. | 6.20.51 ~ 7.25.53 |
+| `Player components` | Adds options to hide or change components related to the player. | 6.20.51 ~ 7.25.53 |
+| `Remove background playback restrictions` | Removes restrictions on background playback, including for kids videos. | 6.20.51 ~ 7.25.53 |
+| `Remove viewer discretion dialog` | Adds an option to remove the dialog that appears when opening a video that has been age-restricted by accepting it automatically. This does not bypass the age restriction. | 6.20.51 ~ 7.25.53 |
+| `Restore old style library shelf` | Adds an option to return the Library tab to the old style. | 6.20.51 ~ 7.25.53 |
+| `Return YouTube Dislike` | Adds an option to show the dislike count of songs using the Return YouTube Dislike API. | 6.20.51 ~ 7.25.53 |
+| `Return YouTube Username` | Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3. | 6.20.51 ~ 7.25.53 |
+| `Sanitize sharing links` | Adds an option to remove tracking query parameters from URLs when sharing links. | 6.20.51 ~ 7.25.53 |
+| `Settings for YouTube Music` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 6.20.51 ~ 7.25.53 |
+| `SponsorBlock` | Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections. | 6.20.51 ~ 7.25.53 |
| `Spoof app version` | Adds options to spoof the YouTube Music client version. This can remove the radio mode restriction in Canadian regions or disable real-time lyrics. | 6.20.51 ~ 7.16.53 |
-| `Translations for YouTube Music` | Add translations or remove string resources. | 6.20.51 ~ 7.16.53 |
-| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 6.20.51 ~ 7.16.53 |
-| `Visual preferences icons for YouTube Music` | Adds icons to specific preferences in the settings. | 6.20.51 ~ 7.16.53 |
+| `Spoof client` | Adds options to spoof the client to allow playback. | 6.20.51 ~ 7.16.53 |
+| `Spoof streaming data` | Adds options to spoof the streaming data to allow playback. | 6.20.51 ~ 7.25.53 |
+| `Translations for YouTube Music` | Add translations or remove string resources. | 6.20.51 ~ 7.25.53 |
+| `Video playback` | Adds options to customize settings related to video playback, such as default video quality and playback speed. | 6.20.51 ~ 7.25.53 |
+| `Visual preferences icons for YouTube Music` | Adds icons to specific preferences in the settings. | 6.20.51 ~ 7.25.53 |
### [📦 `com.reddit.frontpage`](https://play.google.com/store/apps/details?id=com.reddit.frontpage)
@@ -126,19 +130,19 @@ Check the [wiki](https://github.com/anddea/revanced-patches/wiki) for resources
| 💊 Patch | 📜 Description | 🏹 Target Version |
|:--------:|:--------------:|:-----------------:|
-| `Change package name` | Changes the package name for Reddit to the name specified in options.json. | 2023.12.0 ~ 2024.17.0 |
-| `Custom branding name for Reddit` | Renames the Reddit app to the name specified in options.json. | 2023.12.0 ~ 2024.17.0 |
-| `Disable screenshot popup` | Adds an option to disable the popup that appears when taking a screenshot. | 2023.12.0 ~ 2024.17.0 |
-| `Hide Recently Visited shelf` | Adds an option to hide the Recently Visited shelf in the sidebar. | 2023.12.0 ~ 2024.17.0 |
-| `Hide ads` | Adds options to hide ads. | 2023.12.0 ~ 2024.17.0 |
-| `Hide navigation buttons` | Adds options to hide buttons in the navigation bar. | 2023.12.0 ~ 2024.17.0 |
-| `Hide recommended communities shelf` | Adds an option to hide the recommended communities shelves in subreddits. | 2023.12.0 ~ 2024.17.0 |
-| `Open links directly` | Adds an option to skip over redirection URLs in external links. | 2023.12.0 ~ 2024.17.0 |
-| `Open links externally` | Adds an option to always open links in your browser instead of in the in-app-browser. | 2023.12.0 ~ 2024.17.0 |
-| `Premium icon` | Unlocks premium app icons. | 2023.12.0 ~ 2024.17.0 |
-| `Remove subreddit dialog` | Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically. | 2023.12.0 ~ 2024.17.0 |
-| `Sanitize sharing links` | Adds an option to remove tracking query parameters from URLs when sharing links. | 2023.12.0 ~ 2024.17.0 |
-| `Settings for Reddit` | Applies mandatory patches to implement ReVanced Extended settings into the application. | 2023.12.0 ~ 2024.17.0 |
+| `Change package name` | Changes the package name for Reddit to the name specified in patch options. | ALL |
+| `Custom branding name for Reddit` | Renames the Reddit app to the name specified in patch options. | ALL |
+| `Disable screenshot popup` | Adds an option to disable the popup that appears when taking a screenshot. | ALL |
+| `Hide Recently Visited shelf` | Adds an option to hide the Recently Visited shelf in the sidebar. | ALL |
+| `Hide ads` | Adds options to hide ads. | ALL |
+| `Hide navigation buttons` | Adds options to hide buttons in the navigation bar. | ALL |
+| `Hide recommended communities shelf` | Adds an option to hide the recommended communities shelves in subreddits. | ALL |
+| `Open links directly` | Adds an option to skip over redirection URLs in external links. | ALL |
+| `Open links externally` | Adds an option to always open links in your browser instead of in the in-app-browser. | ALL |
+| `Premium icon` | Unlocks premium app icons. | ALL |
+| `Remove subreddit dialog` | Adds options to remove the NSFW community warning and notifications suggestion dialogs by dismissing them automatically. | ALL |
+| `Sanitize sharing links` | Adds an option to remove tracking query parameters from URLs when sharing links. | ALL |
+| `Settings for Reddit` | Applies mandatory patches to implement ReVanced Extended settings into the application. | ALL |
@@ -154,56 +158,43 @@ Example:
{
"name": "Alternative thumbnails",
"description": "Adds options to replace video thumbnails using the DeArrow API or image captures from the video.",
- "compatiblePackages":[
- {
- "name": "com.google.android.youtube",
- "versions": [
- "18.29.38",
- "18.33.40",
- "18.38.44",
- "18.48.39",
- "19.05.36",
- "19.16.39"
- ]
- }
- ],
"use":true,
- "requiresIntegrations":false,
+ "compatiblePackages": {
+ "com.google.android.youtube": [
+ "18.29.38",
+ "18.33.40",
+ "18.38.44",
+ "18.48.39",
+ "19.05.36",
+ "19.16.39",
+ "19.44.39"
+ ]
+ },
"options": []
},
{
"name": "Bitrate default value",
"description": "Sets the audio quality to 'Always High' when you first install the app.",
- "compatiblePackages": [
- {
- "name": "com.google.android.apps.youtube.music",
- "versions": [
- "6.20.51",
- "6.29.59",
- "6.42.55",
- "6.51.53",
- "7.16.53"
- ]
- }
- ],
"use":true,
- "requiresIntegrations":false,
+ "compatiblePackages": {
+ "com.google.android.apps.youtube.music": [
+ "6.20.51",
+ "6.29.59",
+ "6.42.55",
+ "6.51.53",
+ "7.16.53",
+ "7.25.53"
+ ]
+ },
"options": []
},
{
"name": "Hide ads",
"description": "Adds options to hide ads.",
- "compatiblePackages": [
- {
- "name": "com.reddit.frontpage",
- "versions": [
- "2023.12.0",
- "2024.17.0"
- ]
- }
- ],
"use":true,
- "requiresIntegrations":true,
+ "compatiblePackages": {
+ "com.reddit.frontpage": "ALL"
+ },
"options": []
}
]
diff --git a/api/revanced-patches.api b/api/revanced-patches.api
deleted file mode 100644
index 6d688233b..000000000
--- a/api/revanced-patches.api
+++ /dev/null
@@ -1,2025 +0,0 @@
-public final class app/revanced/generator/MainKt {
- public static synthetic fun main ([Ljava/lang/String;)V
-}
-
-public final class app/revanced/patches/all/misc/versioncode/ChangeVersionCodePatch : app/revanced/patcher/patch/ResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/all/misc/versioncode/ChangeVersionCodePatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/account/components/AccountComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/account/components/AccountComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/actionbar/components/ActionBarComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/actionbar/components/ActionBarComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/ads/general/AdsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/ads/general/AdsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/ads/general/MusicAdsPatch : app/revanced/patches/shared/ads/BaseAdsPatch {
- public static final field INSTANCE Lapp/revanced/patches/music/ads/general/MusicAdsPatch;
-}
-
-public final class app/revanced/patches/music/flyoutmenu/components/FlyoutMenuComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/flyoutmenu/components/FlyoutMenuComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/flyoutmenu/components/FlyoutMenuComponentsResourcePatch : app/revanced/patcher/patch/ResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/flyoutmenu/components/FlyoutMenuComponentsResourcePatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/flyoutmenu/components/fingerprints/TrimSilenceConfigFingerprint : app/revanced/util/fingerprint/LiteralValueFingerprint {
- public static final field INSTANCE Lapp/revanced/patches/music/flyoutmenu/components/fingerprints/TrimSilenceConfigFingerprint;
-}
-
-public final class app/revanced/patches/music/flyoutmenu/components/fingerprints/TrimSilenceSwitchFingerprint : app/revanced/util/fingerprint/LiteralValueFingerprint {
- public static final field INSTANCE Lapp/revanced/patches/music/flyoutmenu/components/fingerprints/TrimSilenceSwitchFingerprint;
-}
-
-public final class app/revanced/patches/music/general/amoled/AmoledPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/general/amoled/AmoledPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/general/autocaptions/AutoCaptionsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/general/autocaptions/AutoCaptionsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/general/components/LayoutComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/general/components/LayoutComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/general/components/fingerprints/SearchBarFingerprint : app/revanced/patcher/fingerprint/MethodFingerprint {
- public static final field INSTANCE Lapp/revanced/patches/music/general/components/fingerprints/SearchBarFingerprint;
- public final fun indexOfVisibilityInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;)I
-}
-
-public final class app/revanced/patches/music/general/components/fingerprints/SearchBarParentFingerprint : app/revanced/patcher/fingerprint/MethodFingerprint {
- public static final field INSTANCE Lapp/revanced/patches/music/general/components/fingerprints/SearchBarParentFingerprint;
-}
-
-public final class app/revanced/patches/music/general/dialog/ViewerDiscretionDialogBytecodePatch : app/revanced/patches/shared/dialog/BaseViewerDiscretionDialogPatch {
- public static final field INSTANCE Lapp/revanced/patches/music/general/dialog/ViewerDiscretionDialogBytecodePatch;
-}
-
-public final class app/revanced/patches/music/general/dialog/ViewerDiscretionDialogPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/general/dialog/ViewerDiscretionDialogPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/general/landscapemode/LandScapeModePatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/general/landscapemode/LandScapeModePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/general/oldstylelibraryshelf/OldStyleLibraryShelfPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/general/oldstylelibraryshelf/OldStyleLibraryShelfPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/general/redirection/DislikeRedirectionPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/general/redirection/DislikeRedirectionPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/general/spoofappversion/SpoofAppVersionBytecodePatch : app/revanced/patches/shared/spoofappversion/BaseSpoofAppVersionPatch {
- public static final field INSTANCE Lapp/revanced/patches/music/general/spoofappversion/SpoofAppVersionBytecodePatch;
-}
-
-public final class app/revanced/patches/music/general/spoofappversion/SpoofAppVersionPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/general/spoofappversion/SpoofAppVersionPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/general/startpage/ChangeStartPagePatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/general/startpage/ChangeStartPagePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/layout/branding/icon/CustomBrandingIconPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
- public final fun getAppIcon ()Lapp/revanced/patcher/patch/options/PatchOption;
-}
-
-public final class app/revanced/patches/music/layout/branding/name/CustomBrandingNamePatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/layout/branding/name/CustomBrandingNamePatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/layout/header/ChangeHeaderPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/layout/header/ChangeHeaderPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/layout/overlayfilter/OverlayFilterBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/layout/overlayfilter/OverlayFilterBytecodePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/layout/overlayfilter/OverlayFilterPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/layout/overlayfilter/OverlayFilterPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/layout/playeroverlay/PlayerOverlayFilterPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/layout/playeroverlay/PlayerOverlayFilterPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/layout/translations/TranslationsPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/layout/translations/TranslationsPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/misc/bitrate/BitrateDefaultValuePatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/misc/bitrate/BitrateDefaultValuePatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/misc/codecs/OpusCodecBytecodePatch : app/revanced/patches/shared/opus/BaseOpusCodecsPatch {
- public static final field INSTANCE Lapp/revanced/patches/music/misc/codecs/OpusCodecBytecodePatch;
-}
-
-public final class app/revanced/patches/music/misc/codecs/OpusCodecPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/misc/codecs/OpusCodecPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/misc/debugging/DebuggingPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/misc/debugging/DebuggingPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/misc/share/ShareSheetPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/misc/share/ShareSheetPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/misc/splash/CairoSplashAnimationPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/misc/splash/CairoSplashAnimationPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/misc/thumbnails/BypassImageRegionRestrictionsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/misc/thumbnails/BypassImageRegionRestrictionsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/misc/tracking/SanitizeUrlQueryPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/misc/tracking/SanitizeUrlQueryPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/navigation/components/NavigationBarComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/navigation/components/NavigationBarComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/player/components/PlayerComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/player/components/PlayerComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/player/components/PlayerComponentsResourcePatch : app/revanced/patcher/patch/ResourcePatch, java/io/Closeable {
- public static final field INSTANCE Lapp/revanced/patches/music/player/components/PlayerComponentsResourcePatch;
- public fun close ()V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/utils/compatibility/Constants {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/compatibility/Constants;
- public final fun getCOMPATIBLE_PACKAGE ()Ljava/util/Set;
-}
-
-public final class app/revanced/patches/music/utils/fix/androidauto/AndroidAutoCertificatePatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/fix/androidauto/AndroidAutoCertificatePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/utils/fix/client/SpoofUserAgentPatch : app/revanced/patches/shared/spoofuseragent/BaseSpoofUserAgentPatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/fix/client/SpoofUserAgentPatch;
-}
-
-public final class app/revanced/patches/music/utils/fix/fileprovider/FileProviderPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/fix/fileprovider/FileProviderPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/utils/fix/header/RestoreOldHeaderPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/fix/header/RestoreOldHeaderPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/utils/flyoutmenu/FlyoutMenuHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/flyoutmenu/FlyoutMenuHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/utils/gms/GmsCoreSupportPatch : app/revanced/patches/shared/gms/BaseGmsCoreSupportPatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/gms/GmsCoreSupportPatch;
-}
-
-public final class app/revanced/patches/music/utils/gms/GmsCoreSupportResourcePatch : app/revanced/patches/shared/gms/BaseGmsCoreSupportResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/gms/GmsCoreSupportResourcePatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/utils/imageurlhook/CronetImageUrlHookPatch : app/revanced/patches/shared/imageurlhook/BaseCronetImageUrlHookPatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/imageurlhook/CronetImageUrlHookPatch;
-}
-
-public final class app/revanced/patches/music/utils/integrations/Constants {
- public static final field ACCOUNT_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field ACCOUNT_PATH Ljava/lang/String;
- public static final field ACTIONBAR_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field ACTIONBAR_PATH Ljava/lang/String;
- public static final field ADS_PATH Ljava/lang/String;
- public static final field COMPONENTS_PATH Ljava/lang/String;
- public static final field FLYOUT_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field FLYOUT_PATH Ljava/lang/String;
- public static final field GENERAL_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field GENERAL_PATH Ljava/lang/String;
- public static final field INSTANCE Lapp/revanced/patches/music/utils/integrations/Constants;
- public static final field INTEGRATIONS_PATH Ljava/lang/String;
- public static final field MISC_PATH Ljava/lang/String;
- public static final field NAVIGATION_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field NAVIGATION_PATH Ljava/lang/String;
- public static final field PATCHES_PATH Ljava/lang/String;
- public static final field PATCH_STATUS_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field PLAYER_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field PLAYER_PATH Ljava/lang/String;
- public static final field SHARED_PATH Ljava/lang/String;
- public static final field UTILS_PATH Ljava/lang/String;
- public static final field VIDEO_PATH Ljava/lang/String;
-}
-
-public final class app/revanced/patches/music/utils/integrations/IntegrationsPatch : app/revanced/patches/shared/integrations/BaseIntegrationsPatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/integrations/IntegrationsPatch;
-}
-
-public final class app/revanced/patches/music/utils/mainactivity/MainActivityResolvePatch : app/revanced/patches/shared/mainactivity/BaseMainActivityResolvePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/mainactivity/MainActivityResolvePatch;
-}
-
-public final class app/revanced/patches/music/utils/playertype/PlayerTypeHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/playertype/PlayerTypeHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch : app/revanced/patcher/patch/ResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/resourceid/SharedResourceIdPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
- public final fun getAccountSwitcherAccessibility ()J
- public final fun getBottomSheetRecyclerView ()J
- public final fun getButtonContainer ()J
- public final fun getButtonIconPaddingMedium ()J
- public final fun getChipCloud ()J
- public final fun getColorGrey ()J
- public final fun getDarkBackground ()J
- public final fun getDesignBottomSheetDialog ()J
- public final fun getEndButtonsContainer ()J
- public final fun getFloatingLayout ()J
- public final fun getHistoryMenuItem ()J
- public final fun getInlineTimeBarAdBreakMarkerColor ()J
- public final fun getInterstitialsContainer ()J
- public final fun getIsTablet ()J
- public final fun getLikeDislikeContainer ()J
- public final fun getMainActivityLaunchAnimation ()J
- public final fun getMenuEntry ()J
- public final fun getMiniPlayerDefaultText ()J
- public final fun getMiniPlayerMdxPlaying ()J
- public final fun getMiniPlayerPlayPauseReplayButton ()J
- public final fun getMiniPlayerViewPager ()J
- public final fun getMusicNotifierShelf ()J
- public final fun getMusicTasteBuilderShelf ()J
- public final fun getNamesInactiveAccountThumbnailSize ()J
- public final fun getOfflineSettingsMenuItem ()J
- public final fun getPlayerOverlayChip ()J
- public final fun getPlayerViewPager ()J
- public final fun getPrivacyTosFooter ()J
- public final fun getQualityAuto ()J
- public final fun getRemixGenericButtonSize ()J
- public final fun getSlidingDialogAnimation ()J
- public final fun getTapBloomView ()J
- public final fun getText1 ()J
- public final fun getToolTipContentView ()J
- public final fun getTopBarMenuItemImageView ()J
- public final fun getTopEnd ()J
- public final fun getTopStart ()J
- public final fun getTosFooter ()J
- public final fun getTouchOutside ()J
- public final fun getTrimSilenceSwitch ()J
- public final fun getVarispeedUnavailableTitle ()J
- public final fun setAccountSwitcherAccessibility (J)V
- public final fun setBottomSheetRecyclerView (J)V
- public final fun setButtonContainer (J)V
- public final fun setButtonIconPaddingMedium (J)V
- public final fun setChipCloud (J)V
- public final fun setColorGrey (J)V
- public final fun setDarkBackground (J)V
- public final fun setDesignBottomSheetDialog (J)V
- public final fun setEndButtonsContainer (J)V
- public final fun setFloatingLayout (J)V
- public final fun setHistoryMenuItem (J)V
- public final fun setInlineTimeBarAdBreakMarkerColor (J)V
- public final fun setInterstitialsContainer (J)V
- public final fun setIsTablet (J)V
- public final fun setLikeDislikeContainer (J)V
- public final fun setMainActivityLaunchAnimation (J)V
- public final fun setMenuEntry (J)V
- public final fun setMiniPlayerDefaultText (J)V
- public final fun setMiniPlayerMdxPlaying (J)V
- public final fun setMiniPlayerPlayPauseReplayButton (J)V
- public final fun setMiniPlayerViewPager (J)V
- public final fun setMusicNotifierShelf (J)V
- public final fun setMusicTasteBuilderShelf (J)V
- public final fun setNamesInactiveAccountThumbnailSize (J)V
- public final fun setOfflineSettingsMenuItem (J)V
- public final fun setPlayerOverlayChip (J)V
- public final fun setPlayerViewPager (J)V
- public final fun setPrivacyTosFooter (J)V
- public final fun setQualityAuto (J)V
- public final fun setRemixGenericButtonSize (J)V
- public final fun setSlidingDialogAnimation (J)V
- public final fun setTapBloomView (J)V
- public final fun setText1 (J)V
- public final fun setToolTipContentView (J)V
- public final fun setTopBarMenuItemImageView (J)V
- public final fun setTopEnd (J)V
- public final fun setTopStart (J)V
- public final fun setTosFooter (J)V
- public final fun setTouchOutside (J)V
- public final fun setTrimSilenceSwitch (J)V
- public final fun setVarispeedUnavailableTitle (J)V
-}
-
-public final class app/revanced/patches/music/utils/returnyoutubedislike/ReturnYouTubeDislikeBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/returnyoutubedislike/ReturnYouTubeDislikeBytecodePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/utils/returnyoutubedislike/ReturnYouTubeDislikePatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/returnyoutubedislike/ReturnYouTubeDislikePatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/utils/returnyoutubeusername/ReturnYouTubeUsernamePatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/returnyoutubeusername/ReturnYouTubeUsernamePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/utils/settings/CategoryType : java/lang/Enum {
- public static final field ACCOUNT Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static final field ACTION_BAR Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static final field ADS Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static final field FLYOUT Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static final field GENERAL Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static final field MISC Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static final field NAVIGATION Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static final field PLAYER Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static final field RETURN_YOUTUBE_DISLIKE Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static final field RETURN_YOUTUBE_USERNAME Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static final field SETTINGS Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static final field SPONSOR_BLOCK Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static final field VIDEO Lapp/revanced/patches/music/utils/settings/CategoryType;
- public final fun getAdded ()Z
- public static fun getEntries ()Lkotlin/enums/EnumEntries;
- public final fun getValue ()Ljava/lang/String;
- public final fun setAdded (Z)V
- public static fun valueOf (Ljava/lang/String;)Lapp/revanced/patches/music/utils/settings/CategoryType;
- public static fun values ()[Lapp/revanced/patches/music/utils/settings/CategoryType;
-}
-
-public final class app/revanced/patches/music/utils/settings/ResourceUtils {
- public static final field ACTIVITY_HOOK_TARGET_CLASS Ljava/lang/String;
- public static final field INSTANCE Lapp/revanced/patches/music/utils/settings/ResourceUtils;
- public static final field PREFERENCE_CATEGORY_TAG_NAME Ljava/lang/String;
- public static final field PREFERENCE_SCREEN_TAG_NAME Ljava/lang/String;
- public static final field SETTINGS_HEADER_PATH Ljava/lang/String;
- public static final field SWITCH_PREFERENCE_TAG_NAME Ljava/lang/String;
- public final fun addMicroGPreference (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- public final fun addPreferenceCategory (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;)V
- public final fun addPreferenceCategoryUnderPreferenceScreen (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;)V
- public final fun addPreferenceWithIntent (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
- public final fun addRVXSettingsPreference (Lapp/revanced/patcher/data/ResourceContext;)V
- public final fun addSwitchPreference (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
- public final fun getIconType ()Ljava/lang/String;
- public final fun getMusicPackageName ()Ljava/lang/String;
- public final fun setIconType (Ljava/lang/String;)V
- public final fun setMusicPackageName (Ljava/lang/String;)V
- public final fun setPreferenceScreenIcon (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;)V
- public final fun sortPreferenceCategory (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;)V
- public final fun updatePackageName (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;)V
-}
-
-public final class app/revanced/patches/music/utils/settings/SettingsBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/settings/SettingsBytecodePatch;
- public static field contexts Lapp/revanced/patcher/data/BytecodeContext;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public final fun getContexts ()Lapp/revanced/patcher/data/BytecodeContext;
- public final fun setContexts (Lapp/revanced/patcher/data/BytecodeContext;)V
-}
-
-public final class app/revanced/patches/music/utils/settings/SettingsPatch : app/revanced/util/patch/BaseResourcePatch, java/io/Closeable {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/settings/SettingsPatch;
- public static field contexts Lapp/revanced/patcher/data/ResourceContext;
- public fun close ()V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
- public final fun getContexts ()Lapp/revanced/patcher/data/ResourceContext;
- public final fun setContexts (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/utils/settings/VisualPreferencesIconsPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/settings/VisualPreferencesIconsPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/utils/sponsorblock/SponsorBlockBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/sponsorblock/SponsorBlockBytecodePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/utils/sponsorblock/SponsorBlockPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/sponsorblock/SponsorBlockPatch;
- public static field context Lapp/revanced/patcher/data/ResourceContext;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
- public final fun getContext ()Lapp/revanced/patcher/data/ResourceContext;
- public final fun setContext (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/music/utils/videotype/VideoTypeHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/utils/videotype/VideoTypeHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/video/information/VideoInformationPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/video/information/VideoInformationPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/music/video/playback/CustomPlaybackSpeedPatch : app/revanced/patches/shared/customspeed/BaseCustomPlaybackSpeedPatch {
- public static final field INSTANCE Lapp/revanced/patches/music/video/playback/CustomPlaybackSpeedPatch;
-}
-
-public final class app/revanced/patches/music/video/playback/VideoPlaybackPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/music/video/playback/VideoPlaybackPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/ad/banner/BannerAdsPatch : app/revanced/patcher/patch/ResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/ad/banner/BannerAdsPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/reddit/ad/comments/CommentAdsPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/ad/comments/CommentAdsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/ad/general/AdsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/ad/general/AdsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/layout/branding/name/CustomBrandingNamePatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/layout/branding/name/CustomBrandingNamePatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/reddit/layout/branding/packagename/ChangePackageNamePatch : app/revanced/util/patch/BaseResourcePatch, java/io/Closeable {
- public static final field INSTANCE Lapp/revanced/patches/reddit/layout/branding/packagename/ChangePackageNamePatch;
- public fun close ()V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/reddit/layout/communities/RecommendedCommunitiesPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/layout/communities/RecommendedCommunitiesPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/layout/navigation/NavigationButtonsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/layout/navigation/NavigationButtonsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/layout/premiumicon/PremiumIconPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/layout/premiumicon/PremiumIconPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/layout/recentlyvisited/RecentlyVisitedShelfPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/layout/recentlyvisited/RecentlyVisitedShelfPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/layout/screenshotpopup/ScreenshotPopupPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/layout/screenshotpopup/ScreenshotPopupPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/layout/subredditdialog/SubRedditDialogPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/layout/toolbar/ToolBarButtonPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/layout/toolbar/ToolBarButtonPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/misc/openlink/OpenLinksDirectlyPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/misc/openlink/OpenLinksDirectlyPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/misc/openlink/OpenLinksExternallyPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/misc/openlink/OpenLinksExternallyPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/misc/tracking/url/SanitizeUrlQueryPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/misc/tracking/url/SanitizeUrlQueryPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/utils/compatibility/Constants {
- public static final field INSTANCE Lapp/revanced/patches/reddit/utils/compatibility/Constants;
- public final fun getCOMPATIBLE_PACKAGE ()Ljava/util/Set;
-}
-
-public final class app/revanced/patches/reddit/utils/integrations/Constants {
- public static final field INSTANCE Lapp/revanced/patches/reddit/utils/integrations/Constants;
- public static final field INTEGRATIONS_PATH Ljava/lang/String;
- public static final field PATCHES_PATH Ljava/lang/String;
-}
-
-public final class app/revanced/patches/reddit/utils/integrations/IntegrationsPatch : app/revanced/patches/shared/integrations/BaseIntegrationsPatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/utils/integrations/IntegrationsPatch;
-}
-
-public final class app/revanced/patches/reddit/utils/resourceid/SharedResourceIdPatch : app/revanced/patcher/patch/ResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/utils/resourceid/SharedResourceIdPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
- public final fun getCancelButton ()J
- public final fun getLabelAcknowledgements ()J
- public final fun getScreenShotShareBanner ()J
- public final fun getTextAppearanceRedditBaseOldButtonColored ()J
- public final fun getToolBarNavSearchCtaContainer ()J
- public final fun setCancelButton (J)V
- public final fun setLabelAcknowledgements (J)V
- public final fun setScreenShotShareBanner (J)V
- public final fun setTextAppearanceRedditBaseOldButtonColored (J)V
- public final fun setToolBarNavSearchCtaContainer (J)V
-}
-
-public final class app/revanced/patches/reddit/utils/settings/SettingsBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/utils/settings/SettingsBytecodePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/reddit/utils/settings/SettingsPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/reddit/utils/settings/SettingsPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public abstract class app/revanced/patches/shared/ads/BaseAdsPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INTEGRATIONS_CLASS_DESCRIPTOR Ljava/lang/String;
- public fun (Ljava/lang/String;Ljava/lang/String;)V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/shared/captions/BaseAutoCaptionsPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/shared/captions/BaseAutoCaptionsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public abstract class app/revanced/patches/shared/customspeed/BaseCustomPlaybackSpeedPatch : app/revanced/patcher/patch/BytecodePatch {
- public fun (Ljava/lang/String;F)V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public abstract class app/revanced/patches/shared/dialog/BaseViewerDiscretionDialogPatch : app/revanced/patcher/patch/BytecodePatch {
- public fun (Ljava/lang/String;Ljava/util/Set;)V
- public synthetic fun (Ljava/lang/String;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/shared/drawable/DrawableColorPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/shared/drawable/DrawableColorPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public final fun injectCall (Ljava/lang/String;)V
-}
-
-public final class app/revanced/patches/shared/elements/StringsElementsUtils {
- public static final field INSTANCE Lapp/revanced/patches/shared/elements/StringsElementsUtils;
-}
-
-public abstract class app/revanced/patches/shared/gms/BaseGmsCoreSupportPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INTEGRATIONS_CLASS_DESCRIPTOR Ljava/lang/String;
- public fun (Ljava/lang/String;Lapp/revanced/patcher/fingerprint/MethodFingerprint;Lkotlin/reflect/KClass;Lapp/revanced/patches/shared/gms/BaseGmsCoreSupportResourcePatch;Ljava/util/Set;Ljava/util/Set;)V
- public synthetic fun (Ljava/lang/String;Lapp/revanced/patcher/fingerprint/MethodFingerprint;Lkotlin/reflect/KClass;Lapp/revanced/patches/shared/gms/BaseGmsCoreSupportResourcePatch;Ljava/util/Set;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public abstract class app/revanced/patches/shared/gms/BaseGmsCoreSupportResourcePatch : app/revanced/patcher/patch/ResourcePatch {
- public static final field Companion Lapp/revanced/patches/shared/gms/BaseGmsCoreSupportResourcePatch$Companion;
- public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;)V
- public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/shared/gms/BaseGmsCoreSupportResourcePatch$Companion {
-}
-
-public abstract class app/revanced/patches/shared/imageurlhook/BaseCronetImageUrlHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field Companion Lapp/revanced/patches/shared/imageurlhook/BaseCronetImageUrlHookPatch$Companion;
- public fun (Z)V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/shared/imageurlhook/BaseCronetImageUrlHookPatch$Companion {
-}
-
-public abstract class app/revanced/patches/shared/integrations/BaseIntegrationsPatch : app/revanced/patcher/patch/BytecodePatch {
- public fun (Ljava/util/Set;)V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public abstract class app/revanced/patches/shared/integrations/BaseIntegrationsPatch$IntegrationsFingerprint : app/revanced/patcher/fingerprint/MethodFingerprint {
- public fun ()V
- public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/Iterable;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
- public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/Iterable;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public final fun invoke (Ljava/lang/String;)V
-}
-
-public abstract interface class app/revanced/patches/shared/integrations/BaseIntegrationsPatch$IntegrationsFingerprint$IHookInsertIndexResolver : kotlin/jvm/functions/Function1 {
- public abstract fun invoke (Lcom/android/tools/smali/dexlib2/iface/Method;)Ljava/lang/Integer;
-}
-
-public final class app/revanced/patches/shared/integrations/BaseIntegrationsPatch$IntegrationsFingerprint$IHookInsertIndexResolver$DefaultImpls {
- public static fun invoke (Lapp/revanced/patches/shared/integrations/BaseIntegrationsPatch$IntegrationsFingerprint$IHookInsertIndexResolver;Lcom/android/tools/smali/dexlib2/iface/Method;)Ljava/lang/Integer;
-}
-
-public abstract interface class app/revanced/patches/shared/integrations/BaseIntegrationsPatch$IntegrationsFingerprint$IRegisterResolver : kotlin/jvm/functions/Function1 {
- public abstract fun invoke (Lcom/android/tools/smali/dexlib2/iface/Method;)Ljava/lang/String;
-}
-
-public final class app/revanced/patches/shared/integrations/BaseIntegrationsPatch$IntegrationsFingerprint$IRegisterResolver$DefaultImpls {
- public static fun invoke (Lapp/revanced/patches/shared/integrations/BaseIntegrationsPatch$IntegrationsFingerprint$IRegisterResolver;Lcom/android/tools/smali/dexlib2/iface/Method;)Ljava/lang/String;
-}
-
-public final class app/revanced/patches/shared/integrations/Constants {
- public static final field COMPONENTS_PATH Ljava/lang/String;
- public static final field INSTANCE Lapp/revanced/patches/shared/integrations/Constants;
- public static final field INTEGRATIONS_PATH Ljava/lang/String;
- public static final field INTEGRATIONS_SETTING_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field INTEGRATIONS_UTILS_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field INTEGRATIONS_UTILS_PATH Ljava/lang/String;
- public static final field PATCHES_PATH Ljava/lang/String;
- public static final field SPANS_PATH Ljava/lang/String;
-}
-
-public final class app/revanced/patches/shared/litho/LithoFilterPatch : app/revanced/patcher/patch/BytecodePatch, java/io/Closeable {
- public static final field INSTANCE Lapp/revanced/patches/shared/litho/LithoFilterPatch;
- public fun close ()V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public abstract class app/revanced/patches/shared/mainactivity/BaseMainActivityResolvePatch : app/revanced/patcher/patch/BytecodePatch {
- public field mainActivityMutableClass Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
- public field onConfigurationChangedMethod Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
- public field onCreateMethod Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
- public fun (Lapp/revanced/patcher/fingerprint/MethodFingerprint;)V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public final fun getMainActivityMutableClass ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;
- public final fun getOnConfigurationChangedMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
- public final fun getOnCreateMethod ()Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
- public final fun injectConstructorMethodCall (Ljava/lang/String;Ljava/lang/String;)V
- public final fun injectOnBackPressedMethodCall (Ljava/lang/String;Ljava/lang/String;)V
- public final fun injectOnCreateMethodCall (Ljava/lang/String;Ljava/lang/String;)V
- public final fun setMainActivityMutableClass (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;)V
- public final fun setOnConfigurationChangedMethod (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)V
- public final fun setOnCreateMethod (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)V
-}
-
-public final class app/revanced/patches/shared/mapping/ResourceMappingPatch : app/revanced/patcher/patch/ResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/shared/mapping/ResourceMappingPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
- public final fun getId (Lapp/revanced/patches/shared/mapping/ResourceType;Ljava/lang/String;)J
-}
-
-public final class app/revanced/patches/shared/mapping/ResourceMappingPatch$ResourceElement {
- public fun (Ljava/lang/String;Ljava/lang/String;J)V
- public final fun component1 ()Ljava/lang/String;
- public final fun component2 ()Ljava/lang/String;
- public final fun component3 ()J
- public final fun copy (Ljava/lang/String;Ljava/lang/String;J)Lapp/revanced/patches/shared/mapping/ResourceMappingPatch$ResourceElement;
- public static synthetic fun copy$default (Lapp/revanced/patches/shared/mapping/ResourceMappingPatch$ResourceElement;Ljava/lang/String;Ljava/lang/String;JILjava/lang/Object;)Lapp/revanced/patches/shared/mapping/ResourceMappingPatch$ResourceElement;
- public fun equals (Ljava/lang/Object;)Z
- public final fun getId ()J
- public final fun getName ()Ljava/lang/String;
- public final fun getType ()Ljava/lang/String;
- public fun hashCode ()I
- public fun toString ()Ljava/lang/String;
-}
-
-public final class app/revanced/patches/shared/mapping/ResourceType : java/lang/Enum {
- public static final field ATTR Lapp/revanced/patches/shared/mapping/ResourceType;
- public static final field BOOL Lapp/revanced/patches/shared/mapping/ResourceType;
- public static final field COLOR Lapp/revanced/patches/shared/mapping/ResourceType;
- public static final field DIMEN Lapp/revanced/patches/shared/mapping/ResourceType;
- public static final field DRAWABLE Lapp/revanced/patches/shared/mapping/ResourceType;
- public static final field ID Lapp/revanced/patches/shared/mapping/ResourceType;
- public static final field INTEGER Lapp/revanced/patches/shared/mapping/ResourceType;
- public static final field LAYOUT Lapp/revanced/patches/shared/mapping/ResourceType;
- public static final field STRING Lapp/revanced/patches/shared/mapping/ResourceType;
- public static final field STYLE Lapp/revanced/patches/shared/mapping/ResourceType;
- public static fun getEntries ()Lkotlin/enums/EnumEntries;
- public final fun getValue ()Ljava/lang/String;
- public static fun valueOf (Ljava/lang/String;)Lapp/revanced/patches/shared/mapping/ResourceType;
- public static fun values ()[Lapp/revanced/patches/shared/mapping/ResourceType;
-}
-
-public abstract class app/revanced/patches/shared/opus/BaseOpusCodecsPatch : app/revanced/patcher/patch/BytecodePatch {
- public fun (Ljava/lang/String;)V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/shared/overlaybackground/OverlayBackgroundUtils {
- public static final field INSTANCE Lapp/revanced/patches/shared/overlaybackground/OverlayBackgroundUtils;
-}
-
-public final class app/revanced/patches/shared/returnyoutubeusername/BaseReturnYouTubeUsernamePatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/shared/returnyoutubeusername/BaseReturnYouTubeUsernamePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/shared/settingmenu/SettingsMenuPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/shared/settingmenu/SettingsMenuPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/shared/spans/InclusiveSpanPatch : app/revanced/patcher/patch/BytecodePatch, java/io/Closeable {
- public static final field INSTANCE Lapp/revanced/patches/shared/spans/InclusiveSpanPatch;
- public fun close ()V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public abstract class app/revanced/patches/shared/spoofappversion/BaseSpoofAppVersionPatch : app/revanced/patcher/patch/BytecodePatch {
- public fun (Ljava/lang/String;)V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public abstract class app/revanced/patches/shared/spoofuseragent/BaseSpoofUserAgentPatch : app/revanced/patches/shared/transformation/BaseTransformInstructionsPatch {
- public fun (Ljava/lang/String;)V
- public synthetic fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Ljava/lang/Object;
- public fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Lkotlin/Triple;
- public synthetic fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/Object;)V
- public fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lkotlin/Triple;)V
-}
-
-public final class app/revanced/patches/shared/textcomponent/TextComponentPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/shared/textcomponent/TextComponentPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public final fun hookSpannableString (Ljava/lang/String;Ljava/lang/String;)V
- public final fun hookTextComponent (Ljava/lang/String;Ljava/lang/String;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
- public static synthetic fun hookTextComponent$default (Lapp/revanced/patches/shared/textcomponent/TextComponentPatch;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
-}
-
-public final class app/revanced/patches/shared/tracking/BaseSanitizeUrlQueryPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/shared/tracking/BaseSanitizeUrlQueryPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public abstract class app/revanced/patches/shared/transformation/BaseTransformInstructionsPatch : app/revanced/patcher/patch/BytecodePatch {
- public fun ()V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public abstract fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Ljava/lang/Object;
- public final fun findPatchIndices (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;)Lkotlin/sequences/Sequence;
- public abstract fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/Object;)V
-}
-
-public abstract interface class app/revanced/patches/shared/transformation/IMethodCall {
- public abstract fun getDefinedClassName ()Ljava/lang/String;
- public abstract fun getMethodName ()Ljava/lang/String;
- public abstract fun getMethodParams ()[Ljava/lang/String;
- public abstract fun getReturnType ()Ljava/lang/String;
- public abstract fun replaceInvokeVirtualWithIntegrations (Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lcom/android/tools/smali/dexlib2/iface/instruction/formats/Instruction35c;I)V
-}
-
-public final class app/revanced/patches/shared/transformation/IMethodCall$DefaultImpls {
- public static fun replaceInvokeVirtualWithIntegrations (Lapp/revanced/patches/shared/transformation/IMethodCall;Ljava/lang/String;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lcom/android/tools/smali/dexlib2/iface/instruction/formats/Instruction35c;I)V
-}
-
-public final class app/revanced/patches/shared/translations/TranslationsUtils {
- public static final field INSTANCE Lapp/revanced/patches/shared/translations/TranslationsUtils;
-}
-
-public final class app/revanced/patches/shared/translations/TranslationsUtilsKt {
- public static final fun getAPP_LANGUAGES ()[Ljava/lang/String;
-}
-
-public final class app/revanced/patches/shared/viewgroup/ViewGroupMarginLayoutParamsHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/shared/viewgroup/ViewGroupMarginLayoutParamsHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/ads/general/AdsBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/ads/general/AdsBytecodePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/ads/general/AdsPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/ads/general/AdsPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/ads/general/VideoAdsPatch : app/revanced/patches/shared/ads/BaseAdsPatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/ads/general/VideoAdsPatch;
-}
-
-public final class app/revanced/patches/youtube/alternative/thumbnails/AlternativeThumbnailsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/alternative/thumbnails/AlternativeThumbnailsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/alternative/thumbnails/BypassImageRegionRestrictionsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/alternative/thumbnails/BypassImageRegionRestrictionsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/feed/components/FeedComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/feed/components/FeedComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/feed/flyoutmenu/FeedFlyoutMenuPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/feed/flyoutmenu/FeedFlyoutMenuPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/audiotracks/AudioTracksPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/audiotracks/AudioTracksPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/autocaptions/AutoCaptionsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/autocaptions/AutoCaptionsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/components/LayoutComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/components/LayoutComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/dialog/ViewerDiscretionDialogBytecodePatch : app/revanced/patches/shared/dialog/BaseViewerDiscretionDialogPatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/dialog/ViewerDiscretionDialogBytecodePatch;
-}
-
-public final class app/revanced/patches/youtube/general/dialog/ViewerDiscretionDialogPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/dialog/ViewerDiscretionDialogPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/downloads/DownloadActionsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/downloads/DownloadActionsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/layoutswitch/LayoutSwitchPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/layoutswitch/LayoutSwitchPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/loadingscreen/GradientLoadingScreenPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/loadingscreen/GradientLoadingScreenPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/miniplayer/MiniplayerPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/miniplayer/MiniplayerPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/music/YouTubeMusicActionsPatch : app/revanced/util/patch/BaseBytecodePatch, java/io/Closeable {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/music/YouTubeMusicActionsPatch;
- public fun close ()V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/navigation/NavigationBarComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/navigation/NavigationBarComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/snackbar/ForceSnackbarTheme : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/snackbar/ForceSnackbarTheme;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/general/splashanimation/SplashAnimationPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/splashanimation/SplashAnimationPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionBytecodePatch : app/revanced/patches/shared/spoofappversion/BaseSpoofAppVersionPatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionBytecodePatch;
-}
-
-public final class app/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/spoofappversion/SpoofAppVersionPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/general/startpage/ChangeStartPagePatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/startpage/ChangeStartPagePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/toolbar/ToolBarComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/general/toolbar/fingerprints/SearchBarFingerprint : app/revanced/patcher/fingerprint/MethodFingerprint {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/toolbar/fingerprints/SearchBarFingerprint;
-}
-
-public final class app/revanced/patches/youtube/general/toolbar/fingerprints/SearchBarParentFingerprint : app/revanced/util/fingerprint/LiteralValueFingerprint {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/toolbar/fingerprints/SearchBarParentFingerprint;
-}
-
-public final class app/revanced/patches/youtube/general/toolbar/fingerprints/SearchResultFingerprint : app/revanced/util/fingerprint/LiteralValueFingerprint {
- public static final field INSTANCE Lapp/revanced/patches/youtube/general/toolbar/fingerprints/SearchResultFingerprint;
-}
-
-public final class app/revanced/patches/youtube/layout/actionbuttons/ShortsActionButtonsPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/layout/actionbuttons/ShortsActionButtonsPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/layout/branding/icon/CustomBrandingIconPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
- public final fun getAppIcon ()Lapp/revanced/patcher/patch/options/PatchOption;
-}
-
-public final class app/revanced/patches/youtube/layout/branding/name/CustomBrandingNamePatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/layout/branding/name/CustomBrandingNamePatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/layout/dimming/ShortsDimmingPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/layout/dimming/ShortsDimmingPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/layout/doubletaplength/DoubleTapLengthPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/layout/doubletaplength/DoubleTapLengthPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/layout/playerbuttonbg/PlayerButtonBackgroundPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/layout/playerbuttonbg/PlayerButtonBackgroundPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/layout/shortcut/ShortcutPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/layout/shortcut/ShortcutPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/layout/theme/BaseThemePatch : app/revanced/patcher/patch/ResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/layout/theme/BaseThemePatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/layout/theme/MaterialYouPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/layout/theme/MaterialYouPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/layout/theme/ThemePatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/layout/theme/ThemePatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/layout/translations/TranslationsPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/layout/translations/TranslationsPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/layout/visual/VisualPreferencesIconsPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/layout/visual/VisualPreferencesIconsPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/backgroundplayback/BackgroundPlaybackPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/misc/codecs/OpusCodecBytecodePatch : app/revanced/patches/shared/opus/BaseOpusCodecsPatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/codecs/OpusCodecBytecodePatch;
-}
-
-public final class app/revanced/patches/youtube/misc/codecs/OpusCodecPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/codecs/OpusCodecPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/misc/debugging/DebuggingPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/debugging/DebuggingPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/misc/externalbrowser/OpenLinksExternallyBytecodePatch : app/revanced/patches/shared/transformation/BaseTransformInstructionsPatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/externalbrowser/OpenLinksExternallyBytecodePatch;
- public synthetic fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Ljava/lang/Object;
- public fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Lkotlin/Pair;
- public synthetic fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/Object;)V
- public fun transform (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lkotlin/Pair;)V
-}
-
-public final class app/revanced/patches/youtube/misc/externalbrowser/OpenLinksExternallyPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/externalbrowser/OpenLinksExternallyPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/misc/openlinksdirectly/OpenLinksDirectlyPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/openlinksdirectly/OpenLinksDirectlyPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintPrimary : app/revanced/patcher/fingerprint/MethodFingerprint {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintPrimary;
-}
-
-public final class app/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintSecondary : app/revanced/patcher/fingerprint/MethodFingerprint {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/openlinksdirectly/fingerprints/OpenLinksDirectlyFingerprintSecondary;
-}
-
-public final class app/revanced/patches/youtube/misc/quic/QUICProtocolPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/quic/QUICProtocolPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/misc/share/ShareSheetPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/share/ShareSheetPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/misc/tracking/SanitizeUrlQueryPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/tracking/SanitizeUrlQueryPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/misc/watchhistory/WatchHistoryPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/misc/watchhistory/WatchHistoryPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/player/action/ActionButtonsPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/action/ActionButtonsPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/player/ambientmode/AmbientModeSwitchPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/ambientmode/AmbientModeSwitchPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/player/buttons/PlayerButtonsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/buttons/PlayerButtonsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/player/comments/CommentsComponentPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/comments/CommentsComponentPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/player/components/PlayerComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/components/PlayerComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/player/descriptions/DescriptionComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/descriptions/DescriptionComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/player/flyoutmenu/hide/PlayerFlyoutMenuPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/flyoutmenu/hide/PlayerFlyoutMenuPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/player/flyoutmenu/toggle/ChangeTogglePatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/flyoutmenu/toggle/ChangeTogglePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/player/fullscreen/FullscreenComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/fullscreen/FullscreenComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/player/hapticfeedback/HapticFeedBackPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/hapticfeedback/HapticFeedBackPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsBytecodePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/overlaybuttons/OverlayButtonsPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/seekbar/SeekbarComponentsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/player/speedoverlay/SpeedOverlayPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/player/speedoverlay/SpeedOverlayPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/shorts/components/ShortsAnimationPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/shorts/components/ShortsAnimationPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/shorts/components/ShortsComponentPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/shorts/components/ShortsComponentPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/shorts/components/ShortsNavigationBarPatch : app/revanced/util/patch/MultiMethodBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/shorts/components/ShortsNavigationBarPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/shorts/components/ShortsRepeatPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/shorts/components/ShortsRepeatPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/shorts/components/ShortsTimeStampPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/shorts/components/ShortsTimeStampPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/shorts/components/ShortsToolBarPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/shorts/components/ShortsToolBarPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/shorts/startupshortsreset/ResumingShortsOnStartupPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/shorts/startupshortsreset/ResumingShortsOnStartupPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/swipe/controls/SwipeControlsPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/swipe/controls/SwipeControlsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/bottomsheet/BottomSheetHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/bottomsheet/BottomSheetHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/castbutton/CastButtonPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/castbutton/CastButtonPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/compatibility/Constants {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/compatibility/Constants;
- public final fun getCOMPATIBLE_PACKAGE ()Ljava/util/Set;
-}
-
-public final class app/revanced/patches/youtube/utils/controlsoverlay/ControlsOverlayConfigPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/controlsoverlay/ControlsOverlayConfigPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/fix/bottomui/CfBottomUIPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/fix/bottomui/CfBottomUIPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/fix/cairo/CairoSettingsPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/fix/cairo/CairoSettingsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/fix/doublebacktoclose/DoubleBackToClosePatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/fix/doublebacktoclose/DoubleBackToClosePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/fix/shortsplayback/ShortsPlaybackPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/fix/shortsplayback/ShortsPlaybackPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/fix/streamingdata/SpoofStreamingDataPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/fix/streamingdata/SpoofUserAgentPatch : app/revanced/patches/shared/spoofuseragent/BaseSpoofUserAgentPatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/fix/streamingdata/SpoofUserAgentPatch;
-}
-
-public final class app/revanced/patches/youtube/utils/fix/suggestedvideoendscreen/SuggestedVideoEndScreenPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/fix/suggestedvideoendscreen/SuggestedVideoEndScreenPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/fix/swiperefresh/SwipeRefreshPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/fix/swiperefresh/SwipeRefreshPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/flyoutmenu/FlyoutMenuHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/flyoutmenu/FlyoutMenuHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/gms/GmsCoreSupportPatch : app/revanced/patches/shared/gms/BaseGmsCoreSupportPatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/gms/GmsCoreSupportPatch;
-}
-
-public final class app/revanced/patches/youtube/utils/gms/GmsCoreSupportResourcePatch : app/revanced/patches/shared/gms/BaseGmsCoreSupportResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/gms/GmsCoreSupportResourcePatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/utils/imageurlhook/CronetImageUrlHookPatch : app/revanced/patches/shared/imageurlhook/BaseCronetImageUrlHookPatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/imageurlhook/CronetImageUrlHookPatch;
-}
-
-public final class app/revanced/patches/youtube/utils/integrations/Constants {
- public static final field ADS_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field ADS_PATH Ljava/lang/String;
- public static final field ALTERNATIVE_THUMBNAILS_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field ALTERNATIVE_THUMBNAILS_PATH Ljava/lang/String;
- public static final field COMPONENTS_PATH Ljava/lang/String;
- public static final field FEED_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field FEED_PATH Ljava/lang/String;
- public static final field GENERAL_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field GENERAL_PATH Ljava/lang/String;
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/integrations/Constants;
- public static final field INTEGRATIONS_PATH Ljava/lang/String;
- public static final field MISC_PATH Ljava/lang/String;
- public static final field OVERLAY_BUTTONS_PATH Ljava/lang/String;
- public static final field PATCHES_PATH Ljava/lang/String;
- public static final field PATCH_STATUS_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field PLAYER_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field PLAYER_PATH Ljava/lang/String;
- public static final field SHARED_PATH Ljava/lang/String;
- public static final field SHORTS_CLASS_DESCRIPTOR Ljava/lang/String;
- public static final field SHORTS_PATH Ljava/lang/String;
- public static final field SPANS_PATH Ljava/lang/String;
- public static final field SWIPE_PATH Ljava/lang/String;
- public static final field UTILS_PATH Ljava/lang/String;
- public static final field VIDEO_PATH Ljava/lang/String;
-}
-
-public final class app/revanced/patches/youtube/utils/integrations/IntegrationsPatch : app/revanced/patches/shared/integrations/BaseIntegrationsPatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/integrations/IntegrationsPatch;
-}
-
-public final class app/revanced/patches/youtube/utils/lockmodestate/LockModeStateHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/lockmodestate/LockModeStateHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/lottie/LottieAnimationViewHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/lottie/LottieAnimationViewHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/mainactivity/MainActivityResolvePatch : app/revanced/patches/shared/mainactivity/BaseMainActivityResolvePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/mainactivity/MainActivityResolvePatch;
-}
-
-public final class app/revanced/patches/youtube/utils/navigation/NavigationBarHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/navigation/NavigationBarHookPatch;
- public final fun addBottomBarContainerHook (Ljava/lang/String;)V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public final fun getHookNavigationButtonCreated ()Lkotlin/jvm/functions/Function1;
-}
-
-public final class app/revanced/patches/youtube/utils/pip/PiPStateHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/pip/PiPStateHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/playercontrols/PlayerControlsVisibilityHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/playercontrols/PlayerControlsVisibilityHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/playertype/PlayerTypeHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/playertype/PlayerTypeHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/recyclerview/BottomSheetRecyclerViewPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/recyclerview/BottomSheetRecyclerViewPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch : app/revanced/patcher/patch/ResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/resourceid/SharedResourceIdPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
- public final fun getAccountSwitcherAccessibility ()J
- public final fun getActionBarRingo ()J
- public final fun getActionBarRingoBackground ()J
- public final fun getActionBarSearchResultsViewMic ()J
- public final fun getAdAttribution ()J
- public final fun getAppRelatedEndScreenResults ()J
- public final fun getAppearance ()J
- public final fun getAutoNavPreviewStub ()J
- public final fun getAutoNavToggle ()J
- public final fun getBackgroundCategory ()J
- public final fun getBadgeLabel ()J
- public final fun getBar ()J
- public final fun getBarContainerHeight ()J
- public final fun getBottomBarContainer ()J
- public final fun getBottomSheetFooterText ()J
- public final fun getBottomSheetRecyclerView ()J
- public final fun getBottomUiContainerStub ()J
- public final fun getCaptionToggleContainer ()J
- public final fun getCastMediaRouteButton ()J
- public final fun getCfFullscreenButton ()J
- public final fun getChannelListSubMenu ()J
- public final fun getCompactLink ()J
- public final fun getCompactListItem ()J
- public final fun getComponentLongClickListener ()J
- public final fun getContentPill ()J
- public final fun getControlsLayoutStub ()J
- public final fun getDarkBackground ()J
- public final fun getDarkSplashAnimation ()J
- public final fun getDesignBottomSheet ()J
- public final fun getDonationCompanion ()J
- public final fun getDrawerContentView ()J
- public final fun getDrawerResults ()J
- public final fun getEasySeekEduContainer ()J
- public final fun getEditSettingsAction ()J
- public final fun getEmojiPickerIcon ()J
- public final fun getEndScreenElementLayoutCircle ()J
- public final fun getEndScreenElementLayoutIcon ()J
- public final fun getEndScreenElementLayoutVideo ()J
- public final fun getExpandButtonDown ()J
- public final fun getFab ()J
- public final fun getFadeDurationFast ()J
- public final fun getFilterBarHeight ()J
- public final fun getFloatyBarTopMargin ()J
- public final fun getFullScreenButton ()J
- public final fun getFullScreenEngagementOverlay ()J
- public final fun getFullScreenEngagementPanel ()J
- public final fun getHorizontalCardList ()J
- public final fun getImageOnlyTab ()J
- public final fun getInlineTimeBarColorizedBarPlayedColorDark ()J
- public final fun getInlineTimeBarPlayedNotHighlightedColor ()J
- public final fun getInsetOverlayViewLayout ()J
- public final fun getInterstitialsContainer ()J
- public final fun getMenuItemView ()J
- public final fun getMetaPanel ()J
- public final fun getModernMiniPlayerClose ()J
- public final fun getModernMiniPlayerExpand ()J
- public final fun getModernMiniPlayerForwardButton ()J
- public final fun getModernMiniPlayerRewindButton ()J
- public final fun getMusicAppDeeplinkButtonView ()J
- public final fun getNotice ()J
- public final fun getNotificationBigPictureIconWidth ()J
- public final fun getOfflineActionsVideoDeletedUndoSnackbarText ()J
- public final fun getPlayerCollapseButton ()J
- public final fun getPlayerVideoTitleView ()J
- public final fun getPosterArtWidthDefault ()J
- public final fun getQualityAuto ()J
- public final fun getQuickActionsElementContainer ()J
- public final fun getReelDynRemix ()J
- public final fun getReelDynShare ()J
- public final fun getReelFeedbackLike ()J
- public final fun getReelFeedbackPause ()J
- public final fun getReelFeedbackPlay ()J
- public final fun getReelForcedMuteButton ()J
- public final fun getReelPlayerFooter ()J
- public final fun getReelPlayerRightPivotV2Size ()J
- public final fun getReelRightDislikeIcon ()J
- public final fun getReelRightLikeIcon ()J
- public final fun getReelTimeBarPlayedColor ()J
- public final fun getReelVodTimeStampsContainer ()J
- public final fun getReelWatchPlayer ()J
- public final fun getRelatedChipCloudMargin ()J
- public final fun getRightComment ()J
- public final fun getScrimOverlay ()J
- public final fun getScrubbing ()J
- public final fun getSeekEasyHorizontalTouchOffsetToStartScrubbing ()J
- public final fun getSeekUndoEduOverlayStub ()J
- public final fun getSlidingDialogAnimation ()J
- public final fun getSubtitleMenuSettingsFooterInfo ()J
- public final fun getSuggestedAction ()J
- public final fun getTapBloomView ()J
- public final fun getTitleAnchor ()J
- public final fun getToolTipContentView ()J
- public final fun getTotalTime ()J
- public final fun getTouchArea ()J
- public final fun getVarispeedUnavailableTitle ()J
- public final fun getVideoQualityBottomSheet ()J
- public final fun getVideoQualityUnavailableAnnouncement ()J
- public final fun getVideoZoomSnapIndicator ()J
- public final fun getVoiceSearch ()J
- public final fun getYouTubeControlsOverlaySubtitleButton ()J
- public final fun getYouTubeLogo ()J
- public final fun getYtOutlinePictureInPictureWhite ()J
- public final fun getYtOutlineVideoCamera ()J
- public final fun getYtOutlineXWhite ()J
- public final fun getYtPremiumWordMarkHeader ()J
- public final fun getYtWordMarkHeader ()J
- public final fun setAccountSwitcherAccessibility (J)V
- public final fun setActionBarRingo (J)V
- public final fun setActionBarRingoBackground (J)V
- public final fun setActionBarSearchResultsViewMic (J)V
- public final fun setAdAttribution (J)V
- public final fun setAppRelatedEndScreenResults (J)V
- public final fun setAppearance (J)V
- public final fun setAutoNavPreviewStub (J)V
- public final fun setAutoNavToggle (J)V
- public final fun setBackgroundCategory (J)V
- public final fun setBadgeLabel (J)V
- public final fun setBar (J)V
- public final fun setBarContainerHeight (J)V
- public final fun setBottomBarContainer (J)V
- public final fun setBottomSheetFooterText (J)V
- public final fun setBottomSheetRecyclerView (J)V
- public final fun setBottomUiContainerStub (J)V
- public final fun setCaptionToggleContainer (J)V
- public final fun setCastMediaRouteButton (J)V
- public final fun setCfFullscreenButton (J)V
- public final fun setChannelListSubMenu (J)V
- public final fun setCompactLink (J)V
- public final fun setCompactListItem (J)V
- public final fun setComponentLongClickListener (J)V
- public final fun setContentPill (J)V
- public final fun setControlsLayoutStub (J)V
- public final fun setDarkBackground (J)V
- public final fun setDarkSplashAnimation (J)V
- public final fun setDesignBottomSheet (J)V
- public final fun setDonationCompanion (J)V
- public final fun setDrawerContentView (J)V
- public final fun setDrawerResults (J)V
- public final fun setEasySeekEduContainer (J)V
- public final fun setEditSettingsAction (J)V
- public final fun setEmojiPickerIcon (J)V
- public final fun setEndScreenElementLayoutCircle (J)V
- public final fun setEndScreenElementLayoutIcon (J)V
- public final fun setEndScreenElementLayoutVideo (J)V
- public final fun setExpandButtonDown (J)V
- public final fun setFab (J)V
- public final fun setFadeDurationFast (J)V
- public final fun setFilterBarHeight (J)V
- public final fun setFloatyBarTopMargin (J)V
- public final fun setFullScreenButton (J)V
- public final fun setFullScreenEngagementOverlay (J)V
- public final fun setFullScreenEngagementPanel (J)V
- public final fun setHorizontalCardList (J)V
- public final fun setImageOnlyTab (J)V
- public final fun setInlineTimeBarColorizedBarPlayedColorDark (J)V
- public final fun setInlineTimeBarPlayedNotHighlightedColor (J)V
- public final fun setInsetOverlayViewLayout (J)V
- public final fun setInterstitialsContainer (J)V
- public final fun setMenuItemView (J)V
- public final fun setMetaPanel (J)V
- public final fun setModernMiniPlayerClose (J)V
- public final fun setModernMiniPlayerExpand (J)V
- public final fun setModernMiniPlayerForwardButton (J)V
- public final fun setModernMiniPlayerRewindButton (J)V
- public final fun setMusicAppDeeplinkButtonView (J)V
- public final fun setNotice (J)V
- public final fun setNotificationBigPictureIconWidth (J)V
- public final fun setOfflineActionsVideoDeletedUndoSnackbarText (J)V
- public final fun setPlayerCollapseButton (J)V
- public final fun setPlayerVideoTitleView (J)V
- public final fun setPosterArtWidthDefault (J)V
- public final fun setQualityAuto (J)V
- public final fun setQuickActionsElementContainer (J)V
- public final fun setReelDynRemix (J)V
- public final fun setReelDynShare (J)V
- public final fun setReelFeedbackLike (J)V
- public final fun setReelFeedbackPause (J)V
- public final fun setReelFeedbackPlay (J)V
- public final fun setReelForcedMuteButton (J)V
- public final fun setReelPlayerFooter (J)V
- public final fun setReelPlayerRightPivotV2Size (J)V
- public final fun setReelRightDislikeIcon (J)V
- public final fun setReelRightLikeIcon (J)V
- public final fun setReelTimeBarPlayedColor (J)V
- public final fun setReelVodTimeStampsContainer (J)V
- public final fun setReelWatchPlayer (J)V
- public final fun setRelatedChipCloudMargin (J)V
- public final fun setRightComment (J)V
- public final fun setScrimOverlay (J)V
- public final fun setScrubbing (J)V
- public final fun setSeekEasyHorizontalTouchOffsetToStartScrubbing (J)V
- public final fun setSeekUndoEduOverlayStub (J)V
- public final fun setSlidingDialogAnimation (J)V
- public final fun setSubtitleMenuSettingsFooterInfo (J)V
- public final fun setSuggestedAction (J)V
- public final fun setTapBloomView (J)V
- public final fun setTitleAnchor (J)V
- public final fun setToolTipContentView (J)V
- public final fun setTotalTime (J)V
- public final fun setTouchArea (J)V
- public final fun setVarispeedUnavailableTitle (J)V
- public final fun setVideoQualityBottomSheet (J)V
- public final fun setVideoQualityUnavailableAnnouncement (J)V
- public final fun setVideoZoomSnapIndicator (J)V
- public final fun setVoiceSearch (J)V
- public final fun setYouTubeControlsOverlaySubtitleButton (J)V
- public final fun setYouTubeLogo (J)V
- public final fun setYtOutlinePictureInPictureWhite (J)V
- public final fun setYtOutlineVideoCamera (J)V
- public final fun setYtOutlineXWhite (J)V
- public final fun setYtPremiumWordMarkHeader (J)V
- public final fun setYtWordMarkHeader (J)V
-}
-
-public final class app/revanced/patches/youtube/utils/returnyoutubedislike/general/ReturnYouTubeDislikePatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/returnyoutubedislike/general/ReturnYouTubeDislikePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/returnyoutubedislike/rollingnumber/ReturnYouTubeDislikeRollingNumberPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/returnyoutubedislike/rollingnumber/ReturnYouTubeDislikeRollingNumberPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/returnyoutubedislike/shorts/ReturnYouTubeDislikeShortsPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/returnyoutubedislike/shorts/ReturnYouTubeDislikeShortsPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/returnyoutubeusername/ReturnYouTubeUsernamePatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/returnyoutubeusername/ReturnYouTubeUsernamePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/settings/ResourceUtils {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/settings/ResourceUtils;
- public static final field TARGET_PREFERENCE_PATH Ljava/lang/String;
- public static final field YOUTUBE_SETTINGS_PATH Ljava/lang/String;
- public final fun addPreference (Lapp/revanced/patcher/data/ResourceContext;[Ljava/lang/String;)V
- public final fun addPreferenceFragment (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;)V
- public final fun getIconType ()Ljava/lang/String;
- public final fun getYoutubePackageName ()Ljava/lang/String;
- public final fun setYoutubePackageName (Ljava/lang/String;)V
- public final fun updateGmsCorePackageName (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;)V
- public final fun updatePackageName (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;)V
- public final fun updatePatchStatus (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;)V
- public final fun updatePatchStatusIcon (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;)V
- public final fun updatePatchStatusLabel (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;)V
- public final fun updatePatchStatusSettings (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;)V
- public final fun updatePatchStatusTheme (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;)V
-}
-
-public final class app/revanced/patches/youtube/utils/settings/SettingsBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/settings/SettingsBytecodePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/settings/SettingsPatch : app/revanced/util/patch/BaseResourcePatch, java/io/Closeable {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/settings/SettingsPatch;
- public fun close ()V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/utils/sponsorblock/SponsorBlockBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/sponsorblock/SponsorBlockBytecodePatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/sponsorblock/SponsorBlockPatch : app/revanced/util/patch/BaseResourcePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/sponsorblock/SponsorBlockPatch;
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
-}
-
-public final class app/revanced/patches/youtube/utils/toolbar/ToolBarHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/toolbar/ToolBarHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/utils/trackingurlhook/TrackingUrlHookPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/utils/trackingurlhook/TrackingUrlHookPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/video/information/VideoInformationPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/video/information/VideoInformationPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/video/information/fingerprints/PlayerControllerSetTimeReferenceFingerprint : app/revanced/patcher/fingerprint/MethodFingerprint {
- public static final field INSTANCE Lapp/revanced/patches/youtube/video/information/fingerprints/PlayerControllerSetTimeReferenceFingerprint;
-}
-
-public final class app/revanced/patches/youtube/video/playback/CustomPlaybackSpeedPatch : app/revanced/patches/shared/customspeed/BaseCustomPlaybackSpeedPatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/video/playback/CustomPlaybackSpeedPatch;
-}
-
-public final class app/revanced/patches/youtube/video/playback/VideoPlaybackPatch : app/revanced/util/patch/BaseBytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/video/playback/VideoPlaybackPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
-}
-
-public final class app/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch : app/revanced/patcher/patch/BytecodePatch, java/io/Closeable, java/util/Set, kotlin/jvm/internal/markers/KMutableSet {
- public static final field INSTANCE Lapp/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch;
- public fun add (Lapp/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch$Hook;)Z
- public synthetic fun add (Ljava/lang/Object;)Z
- public fun addAll (Ljava/util/Collection;)Z
- public fun clear ()V
- public fun close ()V
- public fun contains (Lapp/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch$Hook;)Z
- public final fun contains (Ljava/lang/Object;)Z
- public fun containsAll (Ljava/util/Collection;)Z
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public fun getSize ()I
- public fun isEmpty ()Z
- public fun iterator ()Ljava/util/Iterator;
- public fun remove (Lapp/revanced/patches/youtube/video/playerresponse/PlayerResponseMethodHookPatch$Hook;)Z
- public final fun remove (Ljava/lang/Object;)Z
- public fun removeAll (Ljava/util/Collection;)Z
- public fun retainAll (Ljava/util/Collection;)Z
- public final fun size ()I
- public fun toArray ()[Ljava/lang/Object;
- public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object;
-}
-
-public final class app/revanced/patches/youtube/video/videoid/VideoIdPatch : app/revanced/patcher/patch/BytecodePatch {
- public static final field INSTANCE Lapp/revanced/patches/youtube/video/videoid/VideoIdPatch;
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public final fun hookPlayerResponseVideoId (Ljava/lang/String;)V
- public final fun hookVideoId (Ljava/lang/String;)V
-}
-
-public final class app/revanced/util/BytecodeUtilsKt {
- public static final field REGISTER_TEMPLATE_REPLACEMENT Ljava/lang/String;
- public static final fun addStaticFieldToIntegration (Lapp/revanced/patcher/data/BytecodeContext;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
- public static synthetic fun addStaticFieldToIntegration$default (Lapp/revanced/patcher/data/BytecodeContext;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)V
- public static final fun alsoResolve (Lapp/revanced/patcher/fingerprint/MethodFingerprint;Lapp/revanced/patcher/data/BytecodeContext;Lapp/revanced/patcher/fingerprint/MethodFingerprint;)Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;
- public static final fun cloneMutable (Lcom/android/tools/smali/dexlib2/iface/Method;IZLjava/lang/String;ILjava/util/List;Ljava/lang/String;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
- public static synthetic fun cloneMutable$default (Lcom/android/tools/smali/dexlib2/iface/Method;IZLjava/lang/String;ILjava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
- public static final fun containsWideLiteralInstructionValue (Lcom/android/tools/smali/dexlib2/iface/Method;J)Z
- public static final fun findMethodOrThrow (Lapp/revanced/patcher/data/BytecodeContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
- public static synthetic fun findMethodOrThrow$default (Lapp/revanced/patcher/data/BytecodeContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
- public static final fun findMethodsOrThrow (Lapp/revanced/patcher/data/BytecodeContext;Ljava/lang/String;)Ljava/util/Set;
- public static final fun findMutableMethodOf (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lcom/android/tools/smali/dexlib2/iface/Method;)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
- public static final fun findOpcodeIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)Ljava/util/List;
- public static final fun findOpcodeIndicesReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Lkotlin/jvm/functions/Function1;)Ljava/util/List;
- public static final fun getException (Lapp/revanced/patcher/fingerprint/MethodFingerprint;)Lapp/revanced/patcher/patch/PatchException;
- public static final fun getException (Lapp/revanced/util/fingerprint/MultiMethodFingerprint;)Lapp/revanced/patcher/patch/PatchException;
- public static final fun getFiveRegisters (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)Ljava/lang/String;
- public static final fun getMethodCall (Lapp/revanced/patcher/fingerprint/MethodFingerprint;)Ljava/lang/String;
- public static final fun getMethodCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)Ljava/lang/String;
- public static final fun getWalkerMethod (Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;Lapp/revanced/patcher/data/BytecodeContext;I)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
- public static final fun getWalkerMethod (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Lapp/revanced/patcher/data/BytecodeContext;I)Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;
- public static final fun indexOfFirstInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;ILcom/android/tools/smali/dexlib2/Opcode;)I
- public static final fun indexOfFirstInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;)I
- public static synthetic fun indexOfFirstInstruction$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I
- public static synthetic fun indexOfFirstInstruction$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
- public static final fun indexOfFirstInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;ILcom/android/tools/smali/dexlib2/Opcode;)I
- public static final fun indexOfFirstInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;)I
- public static final fun indexOfFirstInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)I
- public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I
- public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
- public static final fun indexOfFirstInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;)I
- public static final fun indexOfFirstInstructionReversed (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I
- public static synthetic fun indexOfFirstInstructionReversed$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I
- public static synthetic fun indexOfFirstInstructionReversed$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
- public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/Opcode;)I
- public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;)I
- public static final fun indexOfFirstInstructionReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;)I
- public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lcom/android/tools/smali/dexlib2/Opcode;ILjava/lang/Object;)I
- public static synthetic fun indexOfFirstInstructionReversedOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/Integer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
- public static final fun indexOfFirstStringInstruction (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
- public static final fun indexOfFirstStringInstructionOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
- public static final fun indexOfFirstWideLiteralInstructionValue (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
- public static final fun indexOfFirstWideLiteralInstructionValueOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
- public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V
- public static final fun injectLiteralInstructionBooleanCall (Lapp/revanced/patcher/fingerprint/MethodFingerprint;ILjava/lang/String;)V
- public static final fun injectLiteralInstructionBooleanCall (Lapp/revanced/patcher/fingerprint/MethodFingerprint;JLjava/lang/String;)V
- public static final fun injectLiteralInstructionViewCall (Lapp/revanced/patcher/data/BytecodeContext;JLjava/lang/String;)V
- public static final fun injectLiteralInstructionViewCall (Lapp/revanced/patcher/fingerprint/MethodFingerprint;JLjava/lang/String;)V
- public static final fun injectLiteralInstructionViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;JLjava/lang/String;)V
- public static final fun isDeprecated (Lapp/revanced/patcher/fingerprint/MethodFingerprint;)Z
- public static final fun parametersEqual (Ljava/lang/Iterable;Ljava/lang/Iterable;)Z
- public static final fun replaceLiteralInstructionCall (Lapp/revanced/patcher/data/BytecodeContext;JLjava/lang/String;)V
- public static final fun resultOrThrow (Lapp/revanced/patcher/fingerprint/MethodFingerprint;)Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;
- public static final fun resultOrThrow (Lapp/revanced/util/fingerprint/MultiMethodFingerprint;)Ljava/util/List;
- public static final fun returnEarly (Ljava/util/List;Z)V
- public static synthetic fun returnEarly$default (Ljava/util/List;ZILjava/lang/Object;)V
- public static final fun transformFields (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V
- public static final fun transformMethods (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V
- public static final fun traverseClassHierarchy (Lapp/revanced/patcher/data/BytecodeContext;Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V
- public static final fun updatePatchStatus (Lapp/revanced/patcher/data/BytecodeContext;Ljava/lang/String;Ljava/lang/String;)V
-}
-
-public final class app/revanced/util/ResourceGroup {
- public fun (Ljava/lang/String;[Ljava/lang/String;)V
- public final fun getResourceDirectoryName ()Ljava/lang/String;
- public final fun getResources ()[Ljava/lang/String;
-}
-
-public final class app/revanced/util/ResourceUtilsKt {
- public static final fun addEntryValues (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
- public static synthetic fun addEntryValues$default (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)V
- public static final fun adoptChild (Lorg/w3c/dom/Node;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
- public static final fun appendAppVersion (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;)V
- public static final fun cloneNodes (Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;)V
- public static final fun copyFile (Lapp/revanced/patcher/data/ResourceContext;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;)Z
- public static final fun copyResources (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;[Lapp/revanced/util/ResourceGroup;Z)V
- public static synthetic fun copyResources$default (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;[Lapp/revanced/util/ResourceGroup;ZILjava/lang/Object;)V
- public static final fun copyResourcesWithRename (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/util/Map;)V
- public static final fun copyXmlNode (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lkotlin/Unit;
- public static final fun copyXmlNode (Ljava/lang/String;Lapp/revanced/patcher/util/DomFileEditor;Lapp/revanced/patcher/util/DomFileEditor;)Ljava/lang/AutoCloseable;
- public static final fun doRecursively (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V
- public static final fun getClassLoader ()Ljava/lang/ClassLoader;
- public static final fun getResourceGroup (Ljava/util/List;[Ljava/lang/String;)Ljava/util/List;
- public static final fun insertNode (Lorg/w3c/dom/Node;Ljava/lang/String;Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V
- public static final fun lowerCaseOrThrow (Lapp/revanced/patcher/patch/options/PatchOption;)Ljava/lang/String;
- public static final fun startsWithAny (Ljava/lang/String;[Ljava/lang/String;)Z
- public static final fun underBarOrThrow (Lapp/revanced/patcher/patch/options/PatchOption;)Ljava/lang/String;
- public static final fun updatePathData (Lorg/w3c/dom/Document;Ljava/lang/String;)V
- public static final fun valueOrThrow (Lapp/revanced/patcher/patch/options/PatchOption;)I
- public static final fun valueOrThrow (Lapp/revanced/patcher/patch/options/PatchOption;)Ljava/lang/String;
-}
-
-public abstract class app/revanced/util/fingerprint/LiteralValueFingerprint : app/revanced/patcher/fingerprint/MethodFingerprint {
- public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/Iterable;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function0;)V
- public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/Iterable;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
-}
-
-public abstract class app/revanced/util/fingerprint/MultiMethodFingerprint {
- public static final field Companion Lapp/revanced/util/fingerprint/MultiMethodFingerprint$Companion;
- public fun ()V
- public fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/Iterable;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;)V
- public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Iterable;Ljava/lang/Iterable;Ljava/lang/Iterable;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public final fun getAccessFlags ()Ljava/lang/Integer;
- public final fun getCustomFingerprint ()Lkotlin/jvm/functions/Function2;
- public final fun getOpcodes ()Ljava/lang/Iterable;
- public final fun getParameters ()Ljava/lang/Iterable;
- public final fun getResult ()Ljava/util/List;
- public final fun getReturnType ()Ljava/lang/String;
- public final fun getStrings ()Ljava/lang/Iterable;
- public final fun setResult (Ljava/util/List;)V
-}
-
-public final class app/revanced/util/fingerprint/MultiMethodFingerprint$Companion {
- public final fun resolve (Lapp/revanced/util/fingerprint/MultiMethodFingerprint;Lapp/revanced/patcher/data/BytecodeContext;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
- public final fun resolve (Lapp/revanced/util/fingerprint/MultiMethodFingerprint;Lapp/revanced/patcher/data/BytecodeContext;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/ClassDef;)Z
- public final fun resolve (Ljava/lang/Iterable;Lapp/revanced/patcher/data/BytecodeContext;Ljava/lang/Iterable;)V
-}
-
-public abstract class app/revanced/util/patch/BaseBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
- public fun ()V
- public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;ZZ)V
- public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
-}
-
-public abstract class app/revanced/util/patch/BaseResourcePatch : app/revanced/patcher/patch/ResourcePatch {
- public fun ()V
- public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZ)V
- public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/Set;Ljava/util/Set;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
-}
-
-public abstract class app/revanced/util/patch/MultiMethodBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
- public fun ()V
- public fun (Ljava/util/Set;Ljava/util/Set;)V
- public synthetic fun (Ljava/util/Set;Ljava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
- public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
- public final fun getFingerprints ()Ljava/util/Set;
- public final fun getMultiFingerprints ()Ljava/util/Set;
-}
-
diff --git a/build.gradle.kts b/build.gradle.kts
deleted file mode 100644
index a98b4d837..000000000
--- a/build.gradle.kts
+++ /dev/null
@@ -1,146 +0,0 @@
-import org.gradle.kotlin.dsl.support.listFilesOrdered
-
-plugins {
- alias(libs.plugins.kotlin)
- alias(libs.plugins.binary.compatibility.validator)
- `maven-publish`
- signing
-}
-
-group = "app.revanced"
-
-repositories {
- mavenCentral()
- mavenLocal()
- google()
- maven {
- // A repository must be speficied for some reason. "registry" is a dummy.
- url = uri("https://maven.pkg.github.com/revanced/registry")
- credentials {
- username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
- password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
- }
- }
-}
-
-dependencies {
- implementation(libs.revanced.patcher)
- implementation(libs.smali)
- // TODO: Required because build fails without it. Find a way to remove this dependency.
- implementation(libs.guava)
- // Used in JsonGenerator.
- implementation(libs.gson)
-}
-
-kotlin {
- jvmToolchain(11)
-}
-
-tasks {
- withType(Jar::class) {
- exclude("app/revanced/meta")
-
- manifest {
- attributes["Name"] = "ReVanced Patches"
- attributes["Description"] = "Patches for ReVanced."
- attributes["Version"] = version
- attributes["Timestamp"] = System.currentTimeMillis().toString()
- attributes["Source"] = "git@github.com:revanced/revanced-patches.git"
- attributes["Author"] = "ReVanced"
- attributes["Contact"] = "contact@revanced.app"
- attributes["Origin"] = "https://revanced.app"
- attributes["License"] = "GNU General Public License v3.0"
- }
- }
-
- register("buildDexJar") {
- description = "Build and add a DEX to the JAR file"
- group = "build"
-
- dependsOn(build)
-
- doLast {
- val d8 = File(System.getenv("ANDROID_HOME")).resolve("build-tools")
- .listFilesOrdered().last().resolve("d8").absolutePath
-
- val patchesJar = configurations.archives.get().allArtifacts.files.files.first().absolutePath
- val workingDirectory = layout.buildDirectory.dir("libs").get().asFile
-
- exec {
- workingDir = workingDirectory
- commandLine = listOf(d8, "--release", patchesJar)
- }
-
- exec {
- workingDir = workingDirectory
- commandLine = listOf("zip", "-u", patchesJar, "classes.dex")
- }
- }
- }
-
- register("generatePatchesFiles") {
- description = "Generate patches files"
-
- dependsOn(build)
-
- classpath = sourceSets["main"].runtimeClasspath
- mainClass.set("app.revanced.generator.MainKt")
- }
-
- // Needed by gradle-semantic-release-plugin.
- // Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
- publish {
- dependsOn("buildDexJar")
- dependsOn("generatePatchesFiles")
- }
-}
-
-publishing {
- repositories {
- maven {
- name = "GitHubPackages"
- url = uri("https://maven.pkg.github.com/anddea/revanced-patches")
- credentials {
- username = System.getenv("GITHUB_ACTOR")
- password = System.getenv("GITHUB_TOKEN")
- }
- }
- }
-
- publications {
- create("revanced-patches-publication") {
- from(components["java"])
-
- pom {
- name = "ReVanced Patches"
- description = "Patches for ReVanced."
- url = "https://revanced.app"
-
- licenses {
- license {
- name = "GNU General Public License v3.0"
- url = "https://www.gnu.org/licenses/gpl-3.0.en.html"
- }
- }
- developers {
- developer {
- id = "ReVanced"
- name = "ReVanced"
- email = "contact@revanced.app"
- }
- }
- scm {
- connection = "scm:git:git://github.com/revanced/revanced-patches.git"
- developerConnection = "scm:git:git@github.com:revanced/revanced-patches.git"
- url = "https://github.com/revanced/revanced-patches"
- }
- }
- }
- }
-}
-
-signing {
- useGpgCmd()
-
- sign(publishing.publications["revanced-patches-publication"])
-}
diff --git a/extensions/shared/build.gradle.kts b/extensions/shared/build.gradle.kts
new file mode 100644
index 000000000..90bd2ac9e
--- /dev/null
+++ b/extensions/shared/build.gradle.kts
@@ -0,0 +1,30 @@
+extension {
+ name = "extensions/shared.rve"
+}
+
+android {
+ namespace = "app.revanced.extension"
+ compileSdk = 34
+
+ defaultConfig {
+ minSdk = 24
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = true
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+}
+
+dependencies {
+ compileOnly(libs.annotation)
+ compileOnly(libs.preference)
+ implementation(libs.lang3)
+
+ compileOnly(project(":extensions:shared:stub"))
+}
diff --git a/extensions/shared/proguard-rules.pro b/extensions/shared/proguard-rules.pro
new file mode 100644
index 000000000..8f804140d
--- /dev/null
+++ b/extensions/shared/proguard-rules.pro
@@ -0,0 +1,9 @@
+-dontobfuscate
+-dontoptimize
+-keepattributes *
+-keep class app.revanced.** {
+ *;
+}
+-keep class com.google.** {
+ *;
+}
diff --git a/extensions/shared/src/main/AndroidManifest.xml b/extensions/shared/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..e960b0003
--- /dev/null
+++ b/extensions/shared/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/account/AccountPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/account/AccountPatch.java
new file mode 100644
index 000000000..5e5fb6a06
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/account/AccountPatch.java
@@ -0,0 +1,66 @@
+package app.revanced.extension.music.patches.account;
+
+import static app.revanced.extension.shared.utils.StringRef.str;
+import static app.revanced.extension.shared.utils.Utils.isSDKAbove;
+
+import android.view.View;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import app.revanced.extension.music.settings.Settings;
+
+@SuppressWarnings("unused")
+public class AccountPatch {
+
+ private static String[] accountMenuBlockList;
+
+ static {
+ accountMenuBlockList = Settings.HIDE_ACCOUNT_MENU_FILTER_STRINGS.get().split("\\n");
+ // Some settings should not be hidden.
+ if (isSDKAbove(24)) {
+ accountMenuBlockList = Arrays.stream(accountMenuBlockList)
+ .filter(item -> !Objects.equals(item, str("settings")))
+ .toArray(String[]::new);
+ } else {
+ List tmp = new ArrayList<>(Arrays.asList(accountMenuBlockList));
+ tmp.remove(str("settings")); // "Settings" should appear only once in the account menu
+ accountMenuBlockList = tmp.toArray(new String[0]);
+ }
+ }
+
+ public static void hideAccountMenu(CharSequence charSequence, View view) {
+ if (!Settings.HIDE_ACCOUNT_MENU.get())
+ return;
+
+ if (charSequence == null) {
+ if (Settings.HIDE_ACCOUNT_MENU_EMPTY_COMPONENT.get())
+ view.setVisibility(View.GONE);
+
+ return;
+ }
+
+ for (String filter : accountMenuBlockList) {
+ if (!filter.isEmpty() && charSequence.toString().equals(filter))
+ view.setVisibility(View.GONE);
+ }
+ }
+
+ public static boolean hideHandle(boolean original) {
+ return Settings.HIDE_HANDLE.get() || original;
+ }
+
+ public static void hideHandle(TextView textView, int visibility) {
+ final int finalVisibility = Settings.HIDE_HANDLE.get()
+ ? View.GONE
+ : visibility;
+ textView.setVisibility(finalVisibility);
+ }
+
+ public static int hideTermsContainer() {
+ return Settings.HIDE_TERMS_CONTAINER.get() ? View.GONE : View.VISIBLE;
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/actionbar/ActionBarPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/actionbar/ActionBarPatch.java
new file mode 100644
index 000000000..d973918ee
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/actionbar/ActionBarPatch.java
@@ -0,0 +1,89 @@
+package app.revanced.extension.music.patches.actionbar;
+
+import static app.revanced.extension.shared.utils.Utils.hideViewBy0dpUnderCondition;
+import static app.revanced.extension.shared.utils.Utils.hideViewUnderCondition;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.music.utils.VideoUtils;
+
+@SuppressWarnings("unused")
+public class ActionBarPatch {
+
+ @NonNull
+ private static String buttonType = "";
+
+ public static boolean hideActionBarLabel() {
+ return Settings.HIDE_ACTION_BUTTON_LABEL.get();
+ }
+
+ public static boolean hideActionButton() {
+ for (ActionButton actionButton : ActionButton.values())
+ if (actionButton.enabled && actionButton.name.equals(buttonType))
+ return true;
+
+ return false;
+ }
+
+ public static void hideLikeDislikeButton(View view) {
+ final boolean enabled = Settings.HIDE_ACTION_BUTTON_LIKE_DISLIKE.get();
+ hideViewUnderCondition(
+ enabled,
+ view
+ );
+ hideViewBy0dpUnderCondition(
+ enabled,
+ view
+ );
+ }
+
+ public static void inAppDownloadButtonOnClick(View view) {
+ if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) {
+ return;
+ }
+
+ if (buttonType.equals(ActionButton.DOWNLOAD.name))
+ view.setOnClickListener(imageView -> VideoUtils.launchExternalDownloader());
+ }
+
+ public static void setButtonType(@NonNull Object obj) {
+ final String buttonType = obj.toString();
+
+ for (ActionButton actionButton : ActionButton.values())
+ if (buttonType.contains(actionButton.identifier))
+ setButtonType(actionButton.name);
+ }
+
+ public static void setButtonType(@NonNull String newButtonType) {
+ buttonType = newButtonType;
+ }
+
+ public static void setButtonTypeDownload(int type) {
+ if (type != 0)
+ return;
+
+ setButtonType(ActionButton.DOWNLOAD.name);
+ }
+
+ private enum ActionButton {
+ ADD_TO_PLAYLIST("ACTION_BUTTON_ADD_TO_PLAYLIST", "69487224", Settings.HIDE_ACTION_BUTTON_ADD_TO_PLAYLIST.get()),
+ COMMENT_DISABLED("ACTION_BUTTON_COMMENT", "76623563", Settings.HIDE_ACTION_BUTTON_COMMENT.get()),
+ COMMENT_ENABLED("ACTION_BUTTON_COMMENT", "138681778", Settings.HIDE_ACTION_BUTTON_COMMENT.get()),
+ DOWNLOAD("ACTION_BUTTON_DOWNLOAD", "73080600", Settings.HIDE_ACTION_BUTTON_DOWNLOAD.get()),
+ RADIO("ACTION_BUTTON_RADIO", "48687757", Settings.HIDE_ACTION_BUTTON_RADIO.get()),
+ SHARE("ACTION_BUTTON_SHARE", "90650344", Settings.HIDE_ACTION_BUTTON_SHARE.get());
+
+ private final String name;
+ private final String identifier;
+ private final boolean enabled;
+
+ ActionButton(String name, String identifier, boolean enabled) {
+ this.name = name;
+ this.identifier = identifier;
+ this.enabled = enabled;
+ }
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/MusicAdsPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/MusicAdsPatch.java
new file mode 100644
index 000000000..3cf53b27e
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/MusicAdsPatch.java
@@ -0,0 +1,15 @@
+package app.revanced.extension.music.patches.ads;
+
+import app.revanced.extension.music.settings.Settings;
+
+@SuppressWarnings("unused")
+public class MusicAdsPatch {
+
+ public static boolean hideMusicAds() {
+ return !Settings.HIDE_MUSIC_ADS.get();
+ }
+
+ public static boolean hideMusicAds(boolean original) {
+ return !Settings.HIDE_MUSIC_ADS.get() && original;
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumPromotionPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumPromotionPatch.java
new file mode 100644
index 000000000..7a863606f
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumPromotionPatch.java
@@ -0,0 +1,40 @@
+package app.revanced.extension.music.patches.ads;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.utils.Logger;
+
+@SuppressWarnings("unused")
+public class PremiumPromotionPatch {
+
+ public static void hidePremiumPromotion(View view) {
+ if (!Settings.HIDE_PREMIUM_PROMOTION.get())
+ return;
+
+ view.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ try {
+ if (!(view instanceof ViewGroup viewGroup)) {
+ return;
+ }
+ if (!(viewGroup.getChildAt(0) instanceof ViewGroup mealBarLayoutRoot)) {
+ return;
+ }
+ if (!(mealBarLayoutRoot.getChildAt(0) instanceof LinearLayout linearLayout)) {
+ return;
+ }
+ if (!(linearLayout.getChildAt(0) instanceof ImageView imageView)) {
+ return;
+ }
+ if (imageView.getVisibility() == View.VISIBLE) {
+ view.setVisibility(View.GONE);
+ }
+ } catch (Exception ex) {
+ Logger.printException(() -> "hideGetPremium failure", ex);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumRenewalPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumRenewalPatch.java
new file mode 100644
index 000000000..f5efd9c56
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/ads/PremiumRenewalPatch.java
@@ -0,0 +1,39 @@
+package app.revanced.extension.music.patches.ads;
+
+import static app.revanced.extension.shared.utils.StringRef.str;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.Utils;
+
+@SuppressWarnings("unused")
+public class PremiumRenewalPatch {
+
+ public static void hidePremiumRenewal(LinearLayout buttonContainerView) {
+ if (!Settings.HIDE_PREMIUM_RENEWAL.get())
+ return;
+
+ buttonContainerView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ try {
+ Utils.runOnMainThreadDelayed(() -> {
+ if (!(buttonContainerView.getChildAt(0) instanceof ViewGroup closeButtonParentView))
+ return;
+ if (!(closeButtonParentView.getChildAt(0) instanceof TextView closeButtonView))
+ return;
+ if (closeButtonView.getText().toString().equals(str("dialog_got_it_text")))
+ Utils.clickView(closeButtonView);
+ else
+ Utils.hideViewByLayoutParams((View) buttonContainerView.getParent());
+ }, 0
+ );
+ } catch (Exception ex) {
+ Logger.printException(() -> "hidePremiumRenewal failure", ex);
+ }
+ });
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/ActionButtonsFilter.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/ActionButtonsFilter.java
new file mode 100644
index 000000000..f0d636279
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/ActionButtonsFilter.java
@@ -0,0 +1,89 @@
+package app.revanced.extension.music.patches.components;
+
+import androidx.annotation.Nullable;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
+import app.revanced.extension.shared.patches.components.ByteArrayFilterGroupList;
+import app.revanced.extension.shared.patches.components.Filter;
+import app.revanced.extension.shared.patches.components.StringFilterGroup;
+
+@SuppressWarnings("unused")
+public final class ActionButtonsFilter extends Filter {
+ private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
+
+ private final StringFilterGroup actionBarRule;
+ private final StringFilterGroup bufferFilterPathRule;
+ private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
+
+ public ActionButtonsFilter() {
+ actionBarRule = new StringFilterGroup(
+ null,
+ VIDEO_ACTION_BAR_PATH_PREFIX
+ );
+ addIdentifierCallbacks(actionBarRule);
+
+ bufferFilterPathRule = new StringFilterGroup(
+ null,
+ "|ContainerType|button.eml|"
+ );
+ final StringFilterGroup downloadButton = new StringFilterGroup(
+ Settings.HIDE_ACTION_BUTTON_DOWNLOAD,
+ "music_download_button.eml"
+ );
+ final StringFilterGroup likeDislikeContainer = new StringFilterGroup(
+ Settings.HIDE_ACTION_BUTTON_LIKE_DISLIKE,
+ "segmented_like_dislike_button.eml"
+ );
+ addPathCallbacks(
+ bufferFilterPathRule,
+ downloadButton,
+ likeDislikeContainer
+ );
+
+ bufferButtonsGroupList.addAll(
+ new ByteArrayFilterGroup(
+ Settings.HIDE_ACTION_BUTTON_COMMENT,
+ "yt_outline_message_bubble"
+ ),
+ new ByteArrayFilterGroup(
+ Settings.HIDE_ACTION_BUTTON_ADD_TO_PLAYLIST,
+ "yt_outline_list_add"
+ ),
+ new ByteArrayFilterGroup(
+ Settings.HIDE_ACTION_BUTTON_SHARE,
+ "yt_outline_share"
+ ),
+ new ByteArrayFilterGroup(
+ Settings.HIDE_ACTION_BUTTON_RADIO,
+ "yt_outline_youtube_mix"
+ )
+ );
+ }
+
+ private boolean isEveryFilterGroupEnabled() {
+ for (StringFilterGroup group : pathCallbacks)
+ if (!group.isEnabled()) return false;
+
+ for (ByteArrayFilterGroup group : bufferButtonsGroupList)
+ if (!group.isEnabled()) return false;
+
+ return true;
+ }
+
+ @Override
+ public boolean isFiltered(String path, @Nullable String identifier, String allValue, byte[] protobufBufferArray,
+ StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
+ if (!path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX)) {
+ return false;
+ }
+ if (matchedGroup == actionBarRule && !isEveryFilterGroupEnabled()) {
+ return false;
+ }
+ if (matchedGroup == bufferFilterPathRule && !bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) {
+ return false;
+ }
+
+ return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex);
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/AdsFilter.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/AdsFilter.java
new file mode 100644
index 000000000..de4c65985
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/AdsFilter.java
@@ -0,0 +1,31 @@
+package app.revanced.extension.music.patches.components;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.patches.components.Filter;
+import app.revanced.extension.shared.patches.components.StringFilterGroup;
+
+@SuppressWarnings("unused")
+public final class AdsFilter extends Filter {
+
+ public AdsFilter() {
+ final StringFilterGroup alertBannerPromo = new StringFilterGroup(
+ Settings.HIDE_PROMOTION_ALERT_BANNER,
+ "alert_banner_promo.eml"
+ );
+
+ final StringFilterGroup paidPromotionLabel = new StringFilterGroup(
+ Settings.HIDE_PAID_PROMOTION_LABEL,
+ "music_paid_content_overlay.eml"
+ );
+
+ addIdentifierCallbacks(alertBannerPromo, paidPromotionLabel);
+
+ final StringFilterGroup statementBanner = new StringFilterGroup(
+ Settings.HIDE_GENERAL_ADS,
+ "statement_banner"
+ );
+
+ addPathCallbacks(statementBanner);
+
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/CustomFilter.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/CustomFilter.java
new file mode 100644
index 000000000..b3c766133
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/CustomFilter.java
@@ -0,0 +1,164 @@
+package app.revanced.extension.music.patches.components;
+
+import static app.revanced.extension.shared.utils.StringRef.str;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.patches.components.Filter;
+import app.revanced.extension.shared.patches.components.StringFilterGroup;
+import app.revanced.extension.shared.utils.ByteTrieSearch;
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.Utils;
+
+/**
+ * Allows custom filtering using a path and optionally a proto buffer string.
+ */
+@SuppressWarnings("unused")
+public final class CustomFilter extends Filter {
+
+ private static void showInvalidSyntaxToast(@NonNull String expression) {
+ Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
+ }
+
+ private static class CustomFilterGroup extends StringFilterGroup {
+ /**
+ * Optional character for the path that indicates the custom filter path must match the start.
+ * Must be the first character of the expression.
+ */
+ public static final String SYNTAX_STARTS_WITH = "^";
+
+ /**
+ * Optional character that separates the path from a proto buffer string pattern.
+ */
+ public static final String SYNTAX_BUFFER_SYMBOL = "$";
+
+ /**
+ * @return the parsed objects
+ */
+ @NonNull
+ @SuppressWarnings("ConstantConditions")
+ static Collection parseCustomFilterGroups() {
+ String rawCustomFilterText = Settings.CUSTOM_FILTER_STRINGS.get();
+ if (rawCustomFilterText.isBlank()) {
+ return Collections.emptyList();
+ }
+
+ // Map key is the path including optional special characters (^ and/or $)
+ Map result = new HashMap<>();
+ Pattern pattern = Pattern.compile(
+ "(" // map key group
+ + "(\\Q" + SYNTAX_STARTS_WITH + "\\E?)" // optional starts with
+ + "([^\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E]*)" // path
+ + "(\\Q" + SYNTAX_BUFFER_SYMBOL + "\\E?)" // optional buffer symbol
+ + ")" // end map key group
+ + "(.*)"); // optional buffer string
+
+ for (String expression : rawCustomFilterText.split("\n")) {
+ if (expression.isBlank()) continue;
+
+ Matcher matcher = pattern.matcher(expression);
+ if (!matcher.find()) {
+ showInvalidSyntaxToast(expression);
+ continue;
+ }
+
+ final String mapKey = matcher.group(1);
+ final boolean pathStartsWith = !matcher.group(2).isEmpty();
+ final String path = matcher.group(3);
+ final boolean hasBufferSymbol = !matcher.group(4).isEmpty();
+ final String bufferString = matcher.group(5);
+
+ if (path.isBlank() || (hasBufferSymbol && bufferString.isBlank())) {
+ showInvalidSyntaxToast(expression);
+ continue;
+ }
+
+ // Use one group object for all expressions with the same path.
+ // This ensures the buffer is searched exactly once
+ // when multiple paths are used with different buffer strings.
+ CustomFilterGroup group = result.get(mapKey);
+ if (group == null) {
+ group = new CustomFilterGroup(pathStartsWith, path);
+ result.put(mapKey, group);
+ }
+ if (hasBufferSymbol) {
+ group.addBufferString(bufferString);
+ }
+ }
+
+ return result.values();
+ }
+
+ final boolean startsWith;
+ ByteTrieSearch bufferSearch;
+
+ CustomFilterGroup(boolean startsWith, @NonNull String path) {
+ super(Settings.CUSTOM_FILTER, path);
+ this.startsWith = startsWith;
+ }
+
+ void addBufferString(@NonNull String bufferString) {
+ if (bufferSearch == null) {
+ bufferSearch = new ByteTrieSearch();
+ }
+ bufferSearch.addPattern(bufferString.getBytes());
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("CustomFilterGroup{");
+ builder.append("path=");
+ if (startsWith) builder.append(SYNTAX_STARTS_WITH);
+ builder.append(filters[0]);
+
+ if (bufferSearch != null) {
+ String delimitingCharacter = "❙";
+ builder.append(", bufferStrings=");
+ builder.append(delimitingCharacter);
+ for (byte[] bufferString : bufferSearch.getPatterns()) {
+ builder.append(new String(bufferString));
+ builder.append(delimitingCharacter);
+ }
+ }
+ builder.append("}");
+ return builder.toString();
+ }
+ }
+
+ public CustomFilter() {
+ Collection groups = CustomFilterGroup.parseCustomFilterGroups();
+
+ if (!groups.isEmpty()) {
+ CustomFilterGroup[] groupsArray = groups.toArray(new CustomFilterGroup[0]);
+ Logger.printDebug(() -> "Using Custom filters: " + Arrays.toString(groupsArray));
+ addPathCallbacks(groupsArray);
+ }
+ }
+
+ @Override
+ public boolean isFiltered(String path, @Nullable String identifier, String allValue, byte[] protobufBufferArray,
+ StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
+ // All callbacks are custom filter groups.
+ CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
+ if (custom.startsWith && contentIndex != 0) {
+ return false;
+ }
+ if (custom.bufferSearch != null && !custom.bufferSearch.matches(protobufBufferArray)) {
+ return false;
+ }
+
+ return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex);
+ }
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/LayoutComponentsFilter.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/LayoutComponentsFilter.java
new file mode 100644
index 000000000..672431969
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/LayoutComponentsFilter.java
@@ -0,0 +1,39 @@
+package app.revanced.extension.music.patches.components;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.patches.components.Filter;
+import app.revanced.extension.shared.patches.components.StringFilterGroup;
+
+@SuppressWarnings("unused")
+public final class LayoutComponentsFilter extends Filter {
+
+ public LayoutComponentsFilter() {
+
+ final StringFilterGroup buttonShelf = new StringFilterGroup(
+ Settings.HIDE_BUTTON_SHELF,
+ "entry_point_button_shelf.eml"
+ );
+
+ final StringFilterGroup carouselShelf = new StringFilterGroup(
+ Settings.HIDE_CAROUSEL_SHELF,
+ "music_grid_item_carousel.eml"
+ );
+
+ final StringFilterGroup playlistCardShelf = new StringFilterGroup(
+ Settings.HIDE_PLAYLIST_CARD_SHELF,
+ "music_container_card_shelf.eml"
+ );
+
+ final StringFilterGroup sampleShelf = new StringFilterGroup(
+ Settings.HIDE_SAMPLE_SHELF,
+ "immersive_card_shelf.eml"
+ );
+
+ addIdentifierCallbacks(
+ buttonShelf,
+ carouselShelf,
+ playlistCardShelf,
+ sampleShelf
+ );
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/PlayerComponentsFilter.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/PlayerComponentsFilter.java
new file mode 100644
index 000000000..52056aecc
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/PlayerComponentsFilter.java
@@ -0,0 +1,25 @@
+package app.revanced.extension.music.patches.components;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.patches.components.Filter;
+import app.revanced.extension.shared.patches.components.StringFilterGroup;
+
+@SuppressWarnings("unused")
+public final class PlayerComponentsFilter extends Filter {
+
+ public PlayerComponentsFilter() {
+ addIdentifierCallbacks(
+ new StringFilterGroup(
+ Settings.HIDE_COMMENT_CHANNEL_GUIDELINES,
+ "channel_guidelines_entry_banner.eml",
+ "community_guidelines.eml"
+ )
+ );
+ addPathCallbacks(
+ new StringFilterGroup(
+ Settings.HIDE_COMMENT_TIMESTAMP_AND_EMOJI_BUTTONS,
+ "|CellType|ContainerType|ContainerType|ContainerType|ContainerType|ContainerType|"
+ )
+ );
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/PlayerFlyoutMenuFilter.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/PlayerFlyoutMenuFilter.java
new file mode 100644
index 000000000..5f6701466
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/PlayerFlyoutMenuFilter.java
@@ -0,0 +1,19 @@
+package app.revanced.extension.music.patches.components;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.patches.components.Filter;
+import app.revanced.extension.shared.patches.components.StringFilterGroup;
+
+@SuppressWarnings("unused")
+public final class PlayerFlyoutMenuFilter extends Filter {
+
+ public PlayerFlyoutMenuFilter() {
+ addIdentifierCallbacks(
+ new StringFilterGroup(
+ Settings.HIDE_FLYOUT_MENU_3_COLUMN_COMPONENT,
+ "music_highlight_menu_item_carousel.eml",
+ "tile_button_carousel.eml"
+ )
+ );
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/ShareSheetMenuFilter.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/ShareSheetMenuFilter.java
new file mode 100644
index 000000000..7c6d16817
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/components/ShareSheetMenuFilter.java
@@ -0,0 +1,33 @@
+package app.revanced.extension.music.patches.components;
+
+import androidx.annotation.Nullable;
+
+import app.revanced.extension.music.patches.misc.ShareSheetPatch;
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.patches.components.Filter;
+import app.revanced.extension.shared.patches.components.StringFilterGroup;
+
+/**
+ * Abuse LithoFilter for {@link ShareSheetPatch}.
+ */
+public final class ShareSheetMenuFilter extends Filter {
+ // Must be volatile or synchronized, as litho filtering runs off main thread and this field is then access from the main thread.
+ public static volatile boolean isShareSheetMenuVisible;
+
+ public ShareSheetMenuFilter() {
+ addIdentifierCallbacks(
+ new StringFilterGroup(
+ Settings.CHANGE_SHARE_SHEET,
+ "share_sheet_container.eml"
+ )
+ );
+ }
+
+ @Override
+ public boolean isFiltered(String path, @Nullable String identifier, String allValue, byte[] protobufBufferArray,
+ StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
+ isShareSheetMenuVisible = true;
+
+ return false;
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/flyout/FlyoutPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/flyout/FlyoutPatch.java
new file mode 100644
index 000000000..d3b86723d
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/flyout/FlyoutPatch.java
@@ -0,0 +1,177 @@
+package app.revanced.extension.music.patches.flyout;
+
+import static app.revanced.extension.shared.utils.ResourceUtils.getIdentifier;
+import static app.revanced.extension.shared.utils.StringRef.str;
+import static app.revanced.extension.shared.utils.Utils.clickView;
+import static app.revanced.extension.shared.utils.Utils.runOnMainThreadDelayed;
+
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.ref.WeakReference;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.music.shared.VideoType;
+import app.revanced.extension.music.utils.VideoUtils;
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.ResourceUtils.ResourceType;
+
+@SuppressWarnings("unused")
+public class FlyoutPatch {
+
+ public static int enableCompactDialog(int original) {
+ if (!Settings.ENABLE_COMPACT_DIALOG.get())
+ return original;
+
+ return Math.max(original, 600);
+ }
+
+ public static boolean enableTrimSilence(boolean original) {
+ if (!Settings.ENABLE_TRIM_SILENCE.get())
+ return original;
+
+ return VideoType.getCurrent().isPodCast() || original;
+ }
+
+ public static boolean enableTrimSilenceSwitch(boolean original) {
+ if (!Settings.ENABLE_TRIM_SILENCE.get())
+ return original;
+
+ return VideoType.getCurrent().isPodCast() && original;
+ }
+
+ public static boolean hideComponents(@Nullable Enum> flyoutMenuEnum) {
+ if (flyoutMenuEnum == null)
+ return false;
+
+ final String flyoutMenuName = flyoutMenuEnum.name();
+
+ Logger.printDebug(() -> "flyoutMenu: " + flyoutMenuName);
+
+ for (FlyoutPanelComponent component : FlyoutPanelComponent.values())
+ if (component.name.equals(flyoutMenuName) && component.enabled)
+ return true;
+
+ return false;
+ }
+
+ public static void hideLikeDislikeContainer(View view) {
+ if (!Settings.HIDE_FLYOUT_MENU_LIKE_DISLIKE.get())
+ return;
+
+ if (view.getParent() instanceof ViewGroup viewGroup) {
+ viewGroup.removeView(view);
+ }
+ }
+
+ private static volatile boolean lastMenuWasDismissQueue = false;
+
+ private static WeakReference touchOutSideViewRef = new WeakReference<>(null);
+
+ public static void setTouchOutSideView(View touchOutSideView) {
+ touchOutSideViewRef = new WeakReference<>(touchOutSideView);
+ }
+
+ public static void replaceComponents(@Nullable Enum> flyoutPanelEnum, @NonNull TextView textView, @NonNull ImageView imageView) {
+ if (flyoutPanelEnum == null)
+ return;
+
+ final String enumString = flyoutPanelEnum.name();
+ final boolean isDismissQue = enumString.equals("DISMISS_QUEUE");
+ final boolean isReport = enumString.equals("FLAG");
+
+ if (isDismissQue) {
+ replaceDismissQueue(textView, imageView);
+ } else if (isReport) {
+ replaceReport(textView, imageView, lastMenuWasDismissQueue);
+ }
+ lastMenuWasDismissQueue = isDismissQue;
+ }
+
+ private static void replaceDismissQueue(@NonNull TextView textView, @NonNull ImageView imageView) {
+ if (!Settings.REPLACE_FLYOUT_MENU_DISMISS_QUEUE.get())
+ return;
+
+ if (!(textView.getParent() instanceof ViewGroup clickAbleArea))
+ return;
+
+ runOnMainThreadDelayed(() -> {
+ textView.setText(str("revanced_replace_flyout_menu_dismiss_queue_watch_on_youtube_label"));
+ imageView.setImageResource(getIdentifier("yt_outline_youtube_logo_icon_vd_theme_24", ResourceType.DRAWABLE, clickAbleArea.getContext()));
+ clickAbleArea.setOnClickListener(viewGroup -> VideoUtils.openInYouTube());
+ }, 0L
+ );
+ }
+
+ private static final ColorFilter cf = new PorterDuffColorFilter(Color.parseColor("#ffffffff"), PorterDuff.Mode.SRC_ATOP);
+
+ private static void replaceReport(@NonNull TextView textView, @NonNull ImageView imageView, boolean wasDismissQueue) {
+ if (!Settings.REPLACE_FLYOUT_MENU_REPORT.get())
+ return;
+
+ if (Settings.REPLACE_FLYOUT_MENU_REPORT_ONLY_PLAYER.get() && !wasDismissQueue)
+ return;
+
+ if (!(textView.getParent() instanceof ViewGroup clickAbleArea))
+ return;
+
+ runOnMainThreadDelayed(() -> {
+ textView.setText(str("playback_rate_title"));
+ imageView.setImageResource(getIdentifier("yt_outline_play_arrow_half_circle_black_24", ResourceType.DRAWABLE, clickAbleArea.getContext()));
+ imageView.setColorFilter(cf);
+ clickAbleArea.setOnClickListener(view -> {
+ clickView(touchOutSideViewRef.get());
+ VideoUtils.showPlaybackSpeedFlyoutMenu();
+ });
+ }, 0L
+ );
+ }
+
+ private enum FlyoutPanelComponent {
+ SAVE_EPISODE_FOR_LATER("BOOKMARK_BORDER", Settings.HIDE_FLYOUT_MENU_SAVE_EPISODE_FOR_LATER.get()),
+ SHUFFLE_PLAY("SHUFFLE", Settings.HIDE_FLYOUT_MENU_SHUFFLE_PLAY.get()),
+ RADIO("MIX", Settings.HIDE_FLYOUT_MENU_START_RADIO.get()),
+ SUBSCRIBE("SUBSCRIBE", Settings.HIDE_FLYOUT_MENU_SUBSCRIBE.get()),
+ EDIT_PLAYLIST("EDIT", Settings.HIDE_FLYOUT_MENU_EDIT_PLAYLIST.get()),
+ DELETE_PLAYLIST("DELETE", Settings.HIDE_FLYOUT_MENU_DELETE_PLAYLIST.get()),
+ PLAY_NEXT("QUEUE_PLAY_NEXT", Settings.HIDE_FLYOUT_MENU_PLAY_NEXT.get()),
+ ADD_TO_QUEUE("QUEUE_MUSIC", Settings.HIDE_FLYOUT_MENU_ADD_TO_QUEUE.get()),
+ SAVE_TO_LIBRARY("LIBRARY_ADD", Settings.HIDE_FLYOUT_MENU_SAVE_TO_LIBRARY.get()),
+ REMOVE_FROM_LIBRARY("LIBRARY_REMOVE", Settings.HIDE_FLYOUT_MENU_REMOVE_FROM_LIBRARY.get()),
+ SAVE_TO_PLAYLIST("ADD_TO_PLAYLIST", Settings.HIDE_FLYOUT_MENU_SAVE_TO_PLAYLIST.get()),
+ REMOVE_FROM_PLAYLIST("REMOVE_FROM_PLAYLIST", Settings.HIDE_FLYOUT_MENU_REMOVE_FROM_PLAYLIST.get()),
+ DOWNLOAD("OFFLINE_DOWNLOAD", Settings.HIDE_FLYOUT_MENU_DOWNLOAD.get()),
+ GO_TO_EPISODE("INFO", Settings.HIDE_FLYOUT_MENU_GO_TO_EPISODE.get()),
+ GO_TO_PODCAST("BROADCAST", Settings.HIDE_FLYOUT_MENU_GO_TO_PODCAST.get()),
+ GO_TO_ALBUM("ALBUM", Settings.HIDE_FLYOUT_MENU_GO_TO_ALBUM.get()),
+ GO_TO_ARTIST("ARTIST", Settings.HIDE_FLYOUT_MENU_GO_TO_ARTIST.get()),
+ VIEW_SONG_CREDIT("PEOPLE_GROUP", Settings.HIDE_FLYOUT_MENU_VIEW_SONG_CREDIT.get()),
+ PIN_TO_SPEED_DIAL("KEEP", Settings.HIDE_FLYOUT_MENU_PIN_TO_SPEED_DIAL.get()),
+ UNPIN_FROM_SPEED_DIAL("KEEP_OFF", Settings.HIDE_FLYOUT_MENU_UNPIN_FROM_SPEED_DIAL.get()),
+ SHARE("SHARE", Settings.HIDE_FLYOUT_MENU_SHARE.get()),
+ DISMISS_QUEUE("DISMISS_QUEUE", Settings.HIDE_FLYOUT_MENU_DISMISS_QUEUE.get()),
+ HELP("HELP_OUTLINE", Settings.HIDE_FLYOUT_MENU_HELP.get()),
+ REPORT("FLAG", Settings.HIDE_FLYOUT_MENU_REPORT.get()),
+ QUALITY("SETTINGS_MATERIAL", Settings.HIDE_FLYOUT_MENU_QUALITY.get()),
+ CAPTIONS("CAPTIONS", Settings.HIDE_FLYOUT_MENU_CAPTIONS.get()),
+ STATS_FOR_NERDS("PLANNER_REVIEW", Settings.HIDE_FLYOUT_MENU_STATS_FOR_NERDS.get()),
+ SLEEP_TIMER("MOON_Z", Settings.HIDE_FLYOUT_MENU_SLEEP_TIMER.get());
+
+ private final boolean enabled;
+ private final String name;
+
+ FlyoutPanelComponent(String name, boolean enabled) {
+ this.enabled = enabled;
+ this.name = name;
+ }
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/GeneralPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/GeneralPatch.java
new file mode 100644
index 000000000..72d3ba3f3
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/general/GeneralPatch.java
@@ -0,0 +1,181 @@
+package app.revanced.extension.music.patches.general;
+
+import static app.revanced.extension.music.utils.ExtendedUtils.isSpoofingToLessThan;
+import static app.revanced.extension.shared.utils.Utils.hideViewBy0dpUnderCondition;
+
+import android.app.AlertDialog;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+
+import app.revanced.extension.music.settings.Settings;
+
+/**
+ * @noinspection ALL
+ */
+@SuppressWarnings("unused")
+public class GeneralPatch {
+
+ // region [Change start page] patch
+
+ public static String changeStartPage(final String browseId) {
+ if (!browseId.equals("FEmusic_home"))
+ return browseId;
+
+ return Settings.CHANGE_START_PAGE.get();
+ }
+
+ // endregion
+
+ // region [Disable dislike redirection] patch
+
+ public static boolean disableDislikeRedirection() {
+ return Settings.DISABLE_DISLIKE_REDIRECTION.get();
+ }
+
+ // endregion
+
+ // region [Enable landscape mode] patch
+
+ public static boolean enableLandScapeMode(boolean original) {
+ return Settings.ENABLE_LANDSCAPE_MODE.get() || original;
+ }
+
+ // endregion
+
+ // region [Hide layout components] patch
+
+ public static int hideCastButton(int original) {
+ return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original;
+ }
+
+ public static void hideCastButton(View view) {
+ hideViewBy0dpUnderCondition(Settings.HIDE_CAST_BUTTON.get(), view);
+ }
+
+ public static void hideCategoryBar(View view) {
+ hideViewBy0dpUnderCondition(Settings.HIDE_CATEGORY_BAR.get(), view);
+ }
+
+ public static boolean hideFloatingButton() {
+ return Settings.HIDE_FLOATING_BUTTON.get();
+ }
+
+ public static boolean hideTapToUpdateButton() {
+ return Settings.HIDE_TAP_TO_UPDATE_BUTTON.get();
+ }
+
+ public static boolean hideHistoryButton(boolean original) {
+ return !Settings.HIDE_HISTORY_BUTTON.get() && original;
+ }
+
+ public static void hideNotificationButton(View view) {
+ if (view.getParent() instanceof ViewGroup viewGroup) {
+ hideViewBy0dpUnderCondition(Settings.HIDE_NOTIFICATION_BUTTON, viewGroup);
+ }
+ }
+
+ public static boolean hideSoundSearchButton(boolean original) {
+ if (!Settings.SETTINGS_INITIALIZED.get()) {
+ return original;
+ }
+ return !Settings.HIDE_SOUND_SEARCH_BUTTON.get();
+ }
+
+ public static void hideVoiceSearchButton(ImageView view, int visibility) {
+ final int finalVisibility = Settings.HIDE_VOICE_SEARCH_BUTTON.get()
+ ? View.GONE
+ : visibility;
+
+ view.setVisibility(finalVisibility);
+ }
+
+ public static void hideTasteBuilder(View view) {
+ view.setVisibility(View.GONE);
+ }
+
+
+ // endregion
+
+ // region [Hide overlay filter] patch
+
+ public static void disableDimBehind(Window window) {
+ if (window != null) {
+ // Disable AlertDialog's background dim.
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ }
+ }
+
+ // endregion
+
+ // region [Remove viewer discretion dialog] patch
+
+ /**
+ * Injection point.
+ *
+ * The {@link AlertDialog#getButton(int)} method must be used after {@link AlertDialog#show()} is called.
+ * Otherwise {@link AlertDialog#getButton(int)} method will always return null.
+ * https://stackoverflow.com/a/4604145
+ *
+ * That's why {@link AlertDialog#show()} is absolutely necessary.
+ * Instead, use two tricks to hide Alertdialog.
+ *
+ * 1. Change the size of AlertDialog to 0.
+ * 2. Disable AlertDialog's background dim.
+ *
+ * When spoofing the client to iOS, the playback speed menu is missing from the player response.
+ * This fix is required because playback speed is not available in YouTube Music Podcasts.
+ *
+ * Return true to force create the playback speed menu.
+ */
+ public static boolean forceCreatePlaybackSpeedMenu(boolean original) {
+ if (SPOOF_CLIENT) {
+ return true;
+ }
+ return original;
+ }
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/client/AppClient.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/client/AppClient.java
new file mode 100644
index 000000000..487ef08c4
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/client/AppClient.java
@@ -0,0 +1,118 @@
+package app.revanced.extension.music.patches.misc.client;
+
+import android.os.Build;
+
+public class AppClient {
+
+ // Audio codec is MP4A.
+ private static final String CLIENT_VERSION_ANDROID_MUSIC_4_27 = "4.27.53";
+
+ // Audio codec is OPUS.
+ private static final String CLIENT_VERSION_ANDROID_MUSIC_5_29 = "5.29.53";
+
+ private static final String PACKAGE_NAME_ANDROID_MUSIC = "com.google.android.apps.youtube.music";
+ private static final String DEVICE_MODEL_ANDROID_MUSIC = Build.MODEL;
+ private static final String OS_VERSION_ANDROID_MUSIC = Build.VERSION.RELEASE;
+
+ // Audio codec is MP4A.
+ private static final String CLIENT_VERSION_IOS_MUSIC_6_21 = "6.21";
+
+ // Audio codec is OPUS.
+ private static final String CLIENT_VERSION_IOS_MUSIC_7_04 = "7.04";
+
+ private static final String PACKAGE_NAME_IOS_MUSIC = "com.google.ios.youtubemusic";
+ private static final String DEVICE_MODEL_IOS_MUSIC = "iPhone14,3";
+ private static final String OS_VERSION_IOS_MUSIC = "15.7.1.19H117";
+ private static final String USER_AGENT_VERSION_IOS_MUSIC = "15_7_1";
+
+ private AppClient() {
+ }
+
+ private static String androidUserAgent(String clientVersion) {
+ return PACKAGE_NAME_ANDROID_MUSIC +
+ "/" +
+ clientVersion +
+ " (Linux; U; Android " +
+ OS_VERSION_ANDROID_MUSIC +
+ "; GB) gzip";
+ }
+
+ private static String iOSUserAgent(String clientVersion) {
+ return PACKAGE_NAME_IOS_MUSIC +
+ "/" +
+ clientVersion +
+ "(" +
+ DEVICE_MODEL_IOS_MUSIC +
+ "; U; CPU iOS " +
+ USER_AGENT_VERSION_IOS_MUSIC +
+ " like Mac OS X)";
+ }
+
+ public enum ClientType {
+ ANDROID_MUSIC_4_27(21,
+ DEVICE_MODEL_ANDROID_MUSIC,
+ OS_VERSION_ANDROID_MUSIC,
+ androidUserAgent(CLIENT_VERSION_ANDROID_MUSIC_4_27),
+ CLIENT_VERSION_ANDROID_MUSIC_4_27
+ ),
+ ANDROID_MUSIC_5_29(21,
+ DEVICE_MODEL_ANDROID_MUSIC,
+ OS_VERSION_ANDROID_MUSIC,
+ androidUserAgent(CLIENT_VERSION_ANDROID_MUSIC_5_29),
+ CLIENT_VERSION_ANDROID_MUSIC_5_29
+ ),
+ IOS_MUSIC_6_21(
+ 26,
+ DEVICE_MODEL_IOS_MUSIC,
+ OS_VERSION_IOS_MUSIC,
+ iOSUserAgent(CLIENT_VERSION_IOS_MUSIC_6_21),
+ CLIENT_VERSION_IOS_MUSIC_6_21
+ ),
+ IOS_MUSIC_7_04(
+ 26,
+ DEVICE_MODEL_IOS_MUSIC,
+ OS_VERSION_IOS_MUSIC,
+ iOSUserAgent(CLIENT_VERSION_IOS_MUSIC_7_04),
+ CLIENT_VERSION_IOS_MUSIC_7_04
+ );
+
+ /**
+ * YouTube
+ * client type
+ */
+ public final int id;
+
+ /**
+ * Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model)
+ */
+ public final String deviceModel;
+
+ /**
+ * Device OS version.
+ */
+ public final String osVersion;
+
+ /**
+ * Player user-agent.
+ */
+ public final String userAgent;
+
+ /**
+ * App version.
+ */
+ public final String clientVersion;
+
+ ClientType(int id,
+ String deviceModel,
+ String osVersion,
+ String userAgent,
+ String clientVersion
+ ) {
+ this.id = id;
+ this.deviceModel = deviceModel;
+ this.clientVersion = clientVersion;
+ this.osVersion = osVersion;
+ this.userAgent = userAgent;
+ }
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/navigation/NavigationPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/navigation/NavigationPatch.java
new file mode 100644
index 000000000..bf99b8fe6
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/navigation/NavigationPatch.java
@@ -0,0 +1,56 @@
+package app.revanced.extension.music.patches.navigation;
+
+import static app.revanced.extension.shared.utils.Utils.hideViewUnderCondition;
+
+import android.graphics.Color;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.utils.ResourceUtils;
+
+@SuppressWarnings("unused")
+public class NavigationPatch {
+ private static final int colorGrey12 =
+ ResourceUtils.getColor("revanced_color_grey_12");
+ public static Enum> lastPivotTab;
+
+ public static int enableBlackNavigationBar() {
+ return Settings.ENABLE_BLACK_NAVIGATION_BAR.get()
+ ? Color.BLACK
+ : colorGrey12;
+ }
+
+ public static void hideNavigationLabel(TextView textview) {
+ hideViewUnderCondition(Settings.HIDE_NAVIGATION_LABEL.get(), textview);
+ }
+
+ public static void hideNavigationButton(@NonNull View view) {
+ if (Settings.HIDE_NAVIGATION_BAR.get() && view.getParent() != null) {
+ hideViewUnderCondition(true, (View) view.getParent());
+ return;
+ }
+
+ for (NavigationButton button : NavigationButton.values())
+ if (lastPivotTab.name().equals(button.name))
+ hideViewUnderCondition(button.enabled, view);
+ }
+
+ private enum NavigationButton {
+ HOME("TAB_HOME", Settings.HIDE_NAVIGATION_HOME_BUTTON.get()),
+ SAMPLES("TAB_SAMPLES", Settings.HIDE_NAVIGATION_SAMPLES_BUTTON.get()),
+ EXPLORE("TAB_EXPLORE", Settings.HIDE_NAVIGATION_EXPLORE_BUTTON.get()),
+ LIBRARY("LIBRARY_MUSIC", Settings.HIDE_NAVIGATION_LIBRARY_BUTTON.get()),
+ UPGRADE("TAB_MUSIC_PREMIUM", Settings.HIDE_NAVIGATION_UPGRADE_BUTTON.get());
+
+ private final boolean enabled;
+ private final String name;
+
+ NavigationButton(String name, boolean enabled) {
+ this.enabled = enabled;
+ this.name = name;
+ }
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/player/PlayerPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/player/PlayerPatch.java
new file mode 100644
index 000000000..71eed73e2
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/player/PlayerPatch.java
@@ -0,0 +1,212 @@
+package app.revanced.extension.music.patches.player;
+
+import static app.revanced.extension.shared.utils.Utils.hideViewByRemovingFromParentUnderCondition;
+import static app.revanced.extension.shared.utils.Utils.hideViewUnderCondition;
+import static app.revanced.extension.shared.utils.Utils.isSDKAbove;
+
+import android.annotation.SuppressLint;
+import android.graphics.Color;
+import android.view.View;
+
+import java.util.Arrays;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.music.shared.VideoType;
+import app.revanced.extension.music.utils.VideoUtils;
+import app.revanced.extension.shared.utils.Utils;
+
+@SuppressWarnings({"unused"})
+public class PlayerPatch {
+ private static final int MUSIC_VIDEO_GREY_BACKGROUND_COLOR = -12566464;
+ private static final int MUSIC_VIDEO_ORIGINAL_BACKGROUND_COLOR = -16579837;
+
+ @SuppressLint("StaticFieldLeak")
+ public static View previousButton;
+ @SuppressLint("StaticFieldLeak")
+ public static View nextButton;
+
+ public static boolean disableMiniPlayerGesture() {
+ return Settings.DISABLE_MINI_PLAYER_GESTURE.get();
+ }
+
+ public static boolean disablePlayerGesture() {
+ return Settings.DISABLE_PLAYER_GESTURE.get();
+ }
+
+ public static boolean enableColorMatchPlayer() {
+ return Settings.ENABLE_COLOR_MATCH_PLAYER.get();
+ }
+
+ public static int enableBlackPlayerBackground(int originalColor) {
+ return Settings.ENABLE_BLACK_PLAYER_BACKGROUND.get()
+ && originalColor != MUSIC_VIDEO_GREY_BACKGROUND_COLOR
+ ? Color.BLACK
+ : originalColor;
+ }
+
+ public static boolean enableForceMinimizedPlayer(boolean original) {
+ return Settings.ENABLE_FORCE_MINIMIZED_PLAYER.get() || original;
+ }
+
+ public static boolean enableMiniPlayerNextButton(boolean original) {
+ return !Settings.ENABLE_MINI_PLAYER_NEXT_BUTTON.get() && original;
+ }
+
+ public static View[] getViewArray(View[] oldViewArray) {
+ if (previousButton != null) {
+ if (nextButton != null) {
+ return getViewArray(getViewArray(oldViewArray, previousButton), nextButton);
+ } else {
+ return getViewArray(oldViewArray, previousButton);
+ }
+ } else {
+ return oldViewArray;
+ }
+ }
+
+ private static View[] getViewArray(View[] oldViewArray, View newView) {
+ final int oldViewArrayLength = oldViewArray.length;
+
+ View[] newViewArray = Arrays.copyOf(oldViewArray, oldViewArrayLength + 1);
+ newViewArray[oldViewArrayLength] = newView;
+ return newViewArray;
+ }
+
+ public static void setNextButton(View nextButtonView) {
+ if (nextButtonView == null)
+ return;
+
+ hideViewUnderCondition(
+ !Settings.ENABLE_MINI_PLAYER_NEXT_BUTTON.get(),
+ nextButtonView
+ );
+
+ nextButtonView.setOnClickListener(PlayerPatch::setNextButtonOnClickListener);
+ }
+
+ // rest of the implementation added by patch.
+ private static void setNextButtonOnClickListener(View view) {
+ if (Settings.ENABLE_MINI_PLAYER_NEXT_BUTTON.get())
+ view.getClass();
+ }
+
+ public static void setPreviousButton(View previousButtonView) {
+ if (previousButtonView == null)
+ return;
+
+ hideViewUnderCondition(
+ !Settings.ENABLE_MINI_PLAYER_PREVIOUS_BUTTON.get(),
+ previousButtonView
+ );
+
+ previousButtonView.setOnClickListener(PlayerPatch::setPreviousButtonOnClickListener);
+ }
+
+ // rest of the implementation added by patch.
+ private static void setPreviousButtonOnClickListener(View view) {
+ if (Settings.ENABLE_MINI_PLAYER_PREVIOUS_BUTTON.get())
+ view.getClass();
+ }
+
+ public static boolean enableSwipeToDismissMiniPlayer() {
+ return Settings.ENABLE_SWIPE_TO_DISMISS_MINI_PLAYER.get();
+ }
+
+ public static boolean enableSwipeToDismissMiniPlayer(boolean original) {
+ return !Settings.ENABLE_SWIPE_TO_DISMISS_MINI_PLAYER.get() && original;
+ }
+
+ public static Object enableSwipeToDismissMiniPlayer(Object object) {
+ return Settings.ENABLE_SWIPE_TO_DISMISS_MINI_PLAYER.get() ? null : object;
+ }
+
+ public static int enableZenMode(int originalColor) {
+ if (Settings.ENABLE_ZEN_MODE.get() && originalColor == MUSIC_VIDEO_ORIGINAL_BACKGROUND_COLOR) {
+ if (Settings.ENABLE_ZEN_MODE_PODCAST.get() || !VideoType.getCurrent().isPodCast()) {
+ return MUSIC_VIDEO_GREY_BACKGROUND_COLOR;
+ }
+ }
+ return originalColor;
+ }
+
+ public static void hideAudioVideoSwitchToggle(View view, int originalVisibility) {
+ if (Settings.HIDE_AUDIO_VIDEO_SWITCH_TOGGLE.get()) {
+ originalVisibility = View.GONE;
+ }
+ view.setVisibility(originalVisibility);
+ }
+
+ public static void hideDoubleTapOverlayFilter(View view) {
+ hideViewByRemovingFromParentUnderCondition(Settings.HIDE_DOUBLE_TAP_OVERLAY_FILTER, view);
+ }
+
+ public static int hideFullscreenShareButton(int original) {
+ return Settings.HIDE_FULLSCREEN_SHARE_BUTTON.get() ? 0 : original;
+ }
+
+ public static void setShuffleState(Enum> shuffleState) {
+ if (!Settings.REMEMBER_SHUFFLE_SATE.get())
+ return;
+ Settings.ALWAYS_SHUFFLE.save(shuffleState.ordinal() == 1);
+ }
+
+ public static void shuffleTracks() {
+ shuffleTracks(false);
+ }
+
+ public static void shuffleTracksWithDelay() {
+ shuffleTracks(true);
+ }
+
+ private static void shuffleTracks(boolean needDelay) {
+ if (!Settings.ALWAYS_SHUFFLE.get())
+ return;
+
+ if (needDelay) {
+ Utils.runOnMainThreadDelayed(VideoUtils::shuffleTracks, 1000);
+ } else {
+ VideoUtils.shuffleTracks();
+ }
+ }
+
+ public static boolean rememberRepeatState(boolean original) {
+ return Settings.REMEMBER_REPEAT_SATE.get() || original;
+ }
+
+ public static boolean rememberShuffleState() {
+ return Settings.REMEMBER_SHUFFLE_SATE.get();
+ }
+
+ public static boolean restoreOldCommentsPopUpPanels() {
+ return restoreOldCommentsPopUpPanels(true);
+ }
+
+ public static boolean restoreOldCommentsPopUpPanels(boolean original) {
+ if (!Settings.SETTINGS_INITIALIZED.get()) {
+ return original;
+ }
+ return !Settings.RESTORE_OLD_COMMENTS_POPUP_PANELS.get() && original;
+ }
+
+ public static boolean restoreOldPlayerBackground(boolean original) {
+ if (!Settings.SETTINGS_INITIALIZED.get()) {
+ return original;
+ }
+ if (!isSDKAbove(23)) {
+ // Disable this patch on Android 5.0 / 5.1 to fix a black play button.
+ // Android 5.x have a different design for play button,
+ // and if the new background is applied forcibly, the play button turns black.
+ // 6.20.51 uses the old background from the beginning, so there is no impact.
+ return original;
+ }
+ return !Settings.RESTORE_OLD_PLAYER_BACKGROUND.get();
+ }
+
+ public static boolean restoreOldPlayerLayout(boolean original) {
+ if (!Settings.SETTINGS_INITIALIZED.get()) {
+ return original;
+ }
+ return !Settings.RESTORE_OLD_PLAYER_LAYOUT.get();
+ }
+
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/DrawableColorPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/DrawableColorPatch.java
new file mode 100644
index 000000000..ddb7b0101
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/DrawableColorPatch.java
@@ -0,0 +1,22 @@
+package app.revanced.extension.music.patches.utils;
+
+@SuppressWarnings("unused")
+public class DrawableColorPatch {
+ private static final int[] DARK_VALUES = {
+ -14606047 // comments box background
+ };
+
+ public static int getLithoColor(int originalValue) {
+ if (anyEquals(originalValue, DARK_VALUES))
+ return -16777215;
+
+ return originalValue;
+ }
+
+ private static boolean anyEquals(int value, int... of) {
+ for (int v : of) if (value == v) return true;
+ return false;
+ }
+}
+
+
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/InitializationPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/InitializationPatch.java
new file mode 100644
index 000000000..d968f6886
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/InitializationPatch.java
@@ -0,0 +1,28 @@
+package app.revanced.extension.music.patches.utils;
+
+import static app.revanced.extension.music.utils.RestartUtils.showRestartDialog;
+
+import android.app.Activity;
+
+import androidx.annotation.NonNull;
+
+import app.revanced.extension.shared.settings.BaseSettings;
+import app.revanced.extension.shared.utils.Utils;
+
+@SuppressWarnings("unused")
+public class InitializationPatch {
+
+ /**
+ * The new layout is not loaded normally when the app is first installed.
+ * (Also reproduced on unPatched YouTube Music)
+ *
+ * To fix this, show the reboot dialog when the app is installed for the first time.
+ */
+ public static void onCreate(@NonNull Activity mActivity) {
+ if (BaseSettings.SETTINGS_INITIALIZED.get())
+ return;
+
+ showRestartDialog(mActivity, "revanced_extended_restart_first_run", 3000);
+ Utils.runOnMainThreadDelayed(() -> BaseSettings.SETTINGS_INITIALIZED.save(true), 3000);
+ }
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/PatchStatus.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/PatchStatus.java
new file mode 100644
index 000000000..08abcf94a
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/PatchStatus.java
@@ -0,0 +1,12 @@
+package app.revanced.extension.music.patches.utils;
+
+@SuppressWarnings("unused")
+public class PatchStatus {
+ public static boolean SpoofAppVersionDefaultBoolean() {
+ return false;
+ }
+
+ public static String SpoofAppVersionDefaultString() {
+ return "6.11.52";
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/PlayerTypeHookPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/PlayerTypeHookPatch.java
new file mode 100644
index 000000000..1efe2d1a8
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/PlayerTypeHookPatch.java
@@ -0,0 +1,19 @@
+package app.revanced.extension.music.patches.utils;
+
+import androidx.annotation.Nullable;
+
+import app.revanced.extension.music.shared.PlayerType;
+
+@SuppressWarnings("unused")
+public class PlayerTypeHookPatch {
+ /**
+ * Injection point.
+ */
+ public static void setPlayerType(@Nullable Enum> musicPlayerType) {
+ if (musicPlayerType == null)
+ return;
+
+ PlayerType.setFromString(musicPlayerType.name());
+ }
+}
+
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/ReturnYouTubeDislikePatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/ReturnYouTubeDislikePatch.java
new file mode 100644
index 000000000..5e74f70a5
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/ReturnYouTubeDislikePatch.java
@@ -0,0 +1,153 @@
+package app.revanced.extension.music.patches.utils;
+
+import static app.revanced.extension.shared.returnyoutubedislike.ReturnYouTubeDislike.Vote;
+
+import android.text.SpannableString;
+import android.text.Spanned;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import app.revanced.extension.music.returnyoutubedislike.ReturnYouTubeDislike;
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
+import app.revanced.extension.shared.utils.Logger;
+
+/**
+ * Handles all interaction of UI patch components.
+ *
+ * Does not handle creating dislike spans or anything to do with {@link ReturnYouTubeDislikeApi}.
+ */
+@SuppressWarnings("unused")
+public class ReturnYouTubeDislikePatch {
+
+ /**
+ * Injection point.
+ *
+ * Called when a litho text component is initially created,
+ * and also when a Span is later reused again (such as scrolling off/on screen).
+ *
+ * This method is sometimes called on the main thread, but it usually is called _off_ the main thread.
+ * This method can be called multiple times for the same UI element (including after dislikes was added).
+ *
+ * @param original Original char sequence was created or reused by Litho.
+ * @return The original char sequence (if nothing should change), or a replacement char sequence that contains dislikes.
+ */
+ public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext,
+ @NonNull CharSequence original) {
+ try {
+ if (!Settings.RYD_ENABLED.get()) {
+ return original;
+ }
+
+ String conversionContextString = conversionContext.toString();
+
+ if (!conversionContextString.contains("segmented_like_dislike_button.eml")) {
+ return original;
+ }
+ ReturnYouTubeDislike videoData = currentVideoData;
+ if (videoData == null) {
+ return original; // User enabled RYD while a video was on screen.
+ }
+ if (!(original instanceof Spanned)) {
+ original = new SpannableString(original);
+ }
+ return videoData.getDislikesSpan((Spanned) original, true);
+ } catch (Exception ex) {
+ Logger.printException(() -> "onLithoTextLoaded failure", ex);
+ }
+ return original;
+ }
+
+ /**
+ * RYD data for the current video on screen.
+ */
+ @Nullable
+ private static volatile ReturnYouTubeDislike currentVideoData;
+
+ public static void onRYDStatusChange(boolean rydEnabled) {
+ ReturnYouTubeDislikeApi.resetRateLimits();
+ // Must remove all values to protect against using stale data
+ // if the user enables RYD while a video is on screen.
+ clearData();
+ }
+
+ private static void clearData() {
+ currentVideoData = null;
+ }
+
+ /**
+ * Injection point
+ *
+ * Called when the user likes or dislikes.
+ */
+ public static void sendVote(int vote) {
+ try {
+ if (!Settings.RYD_ENABLED.get()) {
+ return;
+ }
+ ReturnYouTubeDislike videoData = currentVideoData;
+ if (videoData == null) {
+ Logger.printDebug(() -> "Cannot send vote, as current video data is null");
+ return; // User enabled RYD while a regular video was minimized.
+ }
+
+ for (Vote v : Vote.values()) {
+ if (v.value == vote) {
+ videoData.sendVote(v);
+
+ return;
+ }
+ }
+ Logger.printException(() -> "Unknown vote type: " + vote);
+ } catch (Exception ex) {
+ Logger.printException(() -> "sendVote failure", ex);
+ }
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/VideoTypeHookPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/VideoTypeHookPatch.java
new file mode 100644
index 000000000..c6c3e90c9
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/VideoTypeHookPatch.java
@@ -0,0 +1,19 @@
+package app.revanced.extension.music.patches.utils;
+
+import androidx.annotation.Nullable;
+
+import app.revanced.extension.music.shared.VideoType;
+
+@SuppressWarnings("unused")
+public class VideoTypeHookPatch {
+ /**
+ * Injection point.
+ */
+ public static void setVideoType(@Nullable Enum> musicVideoType) {
+ if (musicVideoType == null)
+ return;
+
+ VideoType.setFromString(musicVideoType.name());
+ }
+}
+
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/video/CustomPlaybackSpeedPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/video/CustomPlaybackSpeedPatch.java
new file mode 100644
index 000000000..4e5fc0d33
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/video/CustomPlaybackSpeedPatch.java
@@ -0,0 +1,94 @@
+package app.revanced.extension.music.patches.video;
+
+import static app.revanced.extension.shared.utils.StringRef.str;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.Utils;
+
+@SuppressWarnings("unused")
+public class CustomPlaybackSpeedPatch {
+ /**
+ * Maximum playback speed, exclusive value. Custom speeds must be less than this value.
+ */
+ private static final float MAXIMUM_PLAYBACK_SPEED = 5;
+
+ /**
+ * Custom playback speeds.
+ */
+ private static float[] customPlaybackSpeeds;
+
+ static {
+ loadCustomSpeeds();
+ }
+
+ /**
+ * Injection point.
+ */
+ public static float[] getArray(float[] original) {
+ return userChangedCustomPlaybackSpeed() ? customPlaybackSpeeds : original;
+ }
+
+ /**
+ * Injection point.
+ */
+ public static int getLength(int original) {
+ return userChangedCustomPlaybackSpeed() ? customPlaybackSpeeds.length : original;
+ }
+
+ /**
+ * Injection point.
+ */
+ public static int getSize(int original) {
+ return userChangedCustomPlaybackSpeed() ? 0 : original;
+ }
+
+ private static void resetCustomSpeeds(@NonNull String toastMessage) {
+ Utils.showToastLong(toastMessage);
+ Utils.showToastShort(str("revanced_extended_reset_to_default_toast"));
+ Settings.CUSTOM_PLAYBACK_SPEEDS.resetToDefault();
+ }
+
+ public static void loadCustomSpeeds() {
+ try {
+ String[] speedStrings = Settings.CUSTOM_PLAYBACK_SPEEDS.get().split("\\s+");
+ Arrays.sort(speedStrings);
+ if (speedStrings.length == 0) {
+ throw new IllegalArgumentException();
+ }
+ customPlaybackSpeeds = new float[speedStrings.length];
+ for (int i = 0, length = speedStrings.length; i < length; i++) {
+ final float speed = Float.parseFloat(speedStrings[i]);
+ if (speed <= 0 || arrayContains(customPlaybackSpeeds, speed)) {
+ throw new IllegalArgumentException();
+ }
+ if (speed > MAXIMUM_PLAYBACK_SPEED) {
+ resetCustomSpeeds(str("revanced_custom_playback_speeds_invalid", MAXIMUM_PLAYBACK_SPEED + ""));
+ loadCustomSpeeds();
+ return;
+ }
+ customPlaybackSpeeds[i] = speed;
+ }
+ } catch (Exception ex) {
+ Logger.printInfo(() -> "parse error", ex);
+ resetCustomSpeeds(str("revanced_custom_playback_speeds_parse_exception"));
+ loadCustomSpeeds();
+ }
+ }
+
+ private static boolean userChangedCustomPlaybackSpeed() {
+ return !Settings.CUSTOM_PLAYBACK_SPEEDS.isSetToDefault() && customPlaybackSpeeds != null;
+ }
+
+ private static boolean arrayContains(float[] array, float value) {
+ for (float arrayValue : array) {
+ if (arrayValue == value) return true;
+ }
+ return false;
+ }
+
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/video/PlaybackSpeedPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/video/PlaybackSpeedPatch.java
new file mode 100644
index 000000000..843f0c84e
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/video/PlaybackSpeedPatch.java
@@ -0,0 +1,32 @@
+package app.revanced.extension.music.patches.video;
+
+import static app.revanced.extension.shared.utils.StringRef.str;
+import static app.revanced.extension.shared.utils.Utils.showToastShort;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.utils.Logger;
+
+@SuppressWarnings("unused")
+public class PlaybackSpeedPatch {
+
+ public static float getPlaybackSpeed(final float playbackSpeed) {
+ try {
+ return Settings.DEFAULT_PLAYBACK_SPEED.get();
+ } catch (Exception ex) {
+ Logger.printException(() -> "Failed to getPlaybackSpeed", ex);
+ }
+ return playbackSpeed;
+ }
+
+ public static void userSelectedPlaybackSpeed(final float playbackSpeed) {
+ if (!Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED.get())
+ return;
+
+ Settings.DEFAULT_PLAYBACK_SPEED.save(playbackSpeed);
+
+ if (!Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST.get())
+ return;
+
+ showToastShort(str("revanced_remember_playback_speed_toast", playbackSpeed + "x"));
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/video/VideoQualityPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/video/VideoQualityPatch.java
new file mode 100644
index 000000000..d9e7f8819
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/video/VideoQualityPatch.java
@@ -0,0 +1,68 @@
+package app.revanced.extension.music.patches.video;
+
+import static app.revanced.extension.shared.utils.StringRef.str;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.music.shared.VideoInformation;
+import app.revanced.extension.shared.settings.IntegerSetting;
+import app.revanced.extension.shared.utils.Utils;
+
+@SuppressWarnings("unused")
+public class VideoQualityPatch {
+ private static final int DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY = -2;
+ private static final IntegerSetting mobileQualitySetting = Settings.DEFAULT_VIDEO_QUALITY_MOBILE;
+ private static final IntegerSetting wifiQualitySetting = Settings.DEFAULT_VIDEO_QUALITY_WIFI;
+
+ /**
+ * Injection point.
+ */
+ public static void newVideoStarted(final String ignoredVideoId) {
+ final int preferredQuality =
+ Utils.getNetworkType() == Utils.NetworkType.MOBILE
+ ? mobileQualitySetting.get()
+ : wifiQualitySetting.get();
+
+ if (preferredQuality == DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY)
+ return;
+
+ Utils.runOnMainThreadDelayed(() ->
+ VideoInformation.overrideVideoQuality(
+ VideoInformation.getAvailableVideoQuality(preferredQuality)
+ ),
+ 500
+ );
+ }
+
+ /**
+ * Injection point.
+ */
+ public static void userSelectedVideoQuality() {
+ Utils.runOnMainThreadDelayed(() ->
+ userSelectedVideoQuality(VideoInformation.getVideoQuality()),
+ 300
+ );
+ }
+
+ private static void userSelectedVideoQuality(final int defaultQuality) {
+ if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.get())
+ return;
+ if (defaultQuality == DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY)
+ return;
+
+ final Utils.NetworkType networkType = Utils.getNetworkType();
+
+ switch (networkType) {
+ case NONE -> {
+ Utils.showToastShort(str("revanced_remember_video_quality_none"));
+ return;
+ }
+ case MOBILE -> mobileQualitySetting.save(defaultQuality);
+ default -> wifiQualitySetting.save(defaultQuality);
+ }
+
+ if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get())
+ return;
+
+ Utils.showToastShort(str("revanced_remember_video_quality_" + networkType.getName(), defaultQuality + "p"));
+ }
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/returnyoutubedislike/ReturnYouTubeDislike.java b/extensions/shared/src/main/java/app/revanced/extension/music/returnyoutubedislike/ReturnYouTubeDislike.java
new file mode 100644
index 000000000..69389d1a7
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/returnyoutubedislike/ReturnYouTubeDislike.java
@@ -0,0 +1,584 @@
+package app.revanced.extension.music.returnyoutubedislike;
+
+import static app.revanced.extension.shared.returnyoutubedislike.ReturnYouTubeDislike.Vote;
+import static app.revanced.extension.shared.utils.StringRef.str;
+import static app.revanced.extension.shared.utils.Utils.isSDKAbove;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.OvalShape;
+import android.graphics.drawable.shapes.RectShape;
+import android.icu.text.CompactDecimalFormat;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.ImageSpan;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.text.NumberFormat;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.returnyoutubedislike.requests.RYDVoteData;
+import app.revanced.extension.shared.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.Utils;
+
+/**
+ * Because Litho creates spans using multiple threads, this entire class supports multithreading as well.
+ */
+public class ReturnYouTubeDislike {
+
+ /**
+ * Maximum amount of time to block the UI from updates while waiting for network call to complete.
+ *
+ * Must be less than 5 seconds, as per:
+ *
+ */
+ private static final long MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH = 4000;
+
+ /**
+ * How long to retain successful RYD fetches.
+ */
+ private static final long CACHE_TIMEOUT_SUCCESS_MILLISECONDS = 7 * 60 * 1000; // 7 Minutes
+
+ /**
+ * How long to retain unsuccessful RYD fetches,
+ * and also the minimum time before retrying again.
+ */
+ private static final long CACHE_TIMEOUT_FAILURE_MILLISECONDS = 3 * 60 * 1000; // 3 Minutes
+
+ /**
+ * Unique placeholder character, used to detect if a segmented span already has dislikes added to it.
+ * Can be any almost any non-visible character.
+ */
+ private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye'
+
+ /**
+ * Cached lookup of all video ids.
+ */
+ @GuardedBy("itself")
+ private static final Map fetchCache = new HashMap<>();
+
+ /**
+ * Used to send votes, one by one, in the same order the user created them.
+ */
+ private static final ExecutorService voteSerialExecutor = Executors.newSingleThreadExecutor();
+
+ /**
+ * For formatting dislikes as number.
+ */
+ @GuardedBy("ReturnYouTubeDislike.class") // not thread safe
+ private static CompactDecimalFormat dislikeCountFormatter;
+
+ /**
+ * For formatting dislikes as percentage.
+ */
+ @GuardedBy("ReturnYouTubeDislike.class")
+ private static NumberFormat dislikePercentageFormatter;
+
+ public static Rect leftSeparatorBounds;
+ private static Rect middleSeparatorBounds;
+
+
+ static {
+ ReturnYouTubeDislikeApi.toastOnConnectionError = Settings.RYD_TOAST_ON_CONNECTION_ERROR.get();
+ }
+
+ private final String videoId;
+
+ /**
+ * Stores the results of the vote api fetch, and used as a barrier to wait until fetch completes.
+ * Absolutely cannot be holding any lock during calls to {@link Future#get()}.
+ */
+ private final Future future;
+
+ /**
+ * Time this instance and the fetch future was created.
+ */
+ private final long timeFetched;
+
+ /**
+ * Optional current vote status of the UI. Used to apply a user vote that was done on a previous video viewing.
+ */
+ @Nullable
+ @GuardedBy("this")
+ private Vote userVote;
+
+ /**
+ * Original dislike span, before modifications.
+ */
+ @Nullable
+ @GuardedBy("this")
+ private Spanned originalDislikeSpan;
+
+ /**
+ * Replacement like/dislike span that includes formatted dislikes.
+ * Used to prevent recreating the same span multiple times.
+ */
+ @Nullable
+ @GuardedBy("this")
+ private SpannableString replacementLikeDislikeSpan;
+
+
+ /**
+ * Color of the left and middle separator, based on the color of the right separator.
+ * It's unknown where YT gets the color from, and the values here are approximated by hand.
+ * Ideally, this would be the actual color YT uses at runtime.
+ *
+ * Older versions before the 'Me' library tab use a slightly different color.
+ * If spoofing was previously used and is now turned off,
+ * or an old version was recently upgraded then the old colors are sometimes still used.
+ */
+ private static int getSeparatorColor(boolean isLithoText) {
+ return isLithoText
+ ? 0x29AAAAAA
+ : 0x33FFFFFF;
+ }
+
+ @NonNull
+ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable,
+ @NonNull RYDVoteData voteData,
+ boolean isLithoText) {
+ CharSequence oldLikes = oldSpannable;
+
+ // YouTube creators can hide the like count on a video,
+ // and the like count appears as a device language specific string that says 'Like'.
+ // Check if the string contains any numbers.
+ if (!Utils.containsNumber(oldLikes)) {
+ if (Settings.RYD_ESTIMATED_LIKE.get()) {
+ // Likes are hidden by video creator
+ //
+ // RYD does not directly provide like data, but can use an estimated likes
+ // using the same scale factor RYD applied to the raw dislikes.
+ //
+ // example video: https://www.youtube.com/watch?v=UnrU5vxCHxw
+ // RYD data: https://returnyoutubedislikeapi.com/votes?videoId=UnrU5vxCHxw
+ Logger.printDebug(() -> "Using estimated likes");
+ oldLikes = formatDislikeCount(voteData.getLikeCount());
+ } else {
+ // Change the "Likes" string to show that likes and dislikes are hidden.
+ String hiddenMessageString = str("revanced_ryd_video_likes_hidden_by_video_owner");
+ return newSpanUsingStylingOfAnotherSpan(oldSpannable, hiddenMessageString);
+ }
+ }
+
+ SpannableStringBuilder builder = new SpannableStringBuilder("\u2009");
+ if (!isLithoText) {
+ builder.append("\u2009");
+ }
+ final boolean compactLayout = Settings.RYD_COMPACT_LAYOUT.get();
+
+ if (middleSeparatorBounds == null) {
+ final DisplayMetrics dp = Utils.getResources().getDisplayMetrics();
+ leftSeparatorBounds = new Rect(0, 0,
+ (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1.2f, dp),
+ (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, isLithoText ? 23 : 25, dp));
+ final int middleSeparatorSize =
+ (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
+ middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize);
+ }
+
+ if (!compactLayout) {
+ String leftSeparatorString = "\u200E "; // u200E = left to right character
+ Spannable leftSeparatorSpan = new SpannableString(leftSeparatorString);
+ ShapeDrawable shapeDrawable = new ShapeDrawable(new RectShape());
+ shapeDrawable.getPaint().setColor(getSeparatorColor(isLithoText));
+ shapeDrawable.setBounds(leftSeparatorBounds);
+ leftSeparatorSpan.setSpan(new VerticallyCenteredImageSpan(shapeDrawable), 1, 2,
+ Spannable.SPAN_INCLUSIVE_EXCLUSIVE); // drawable cannot overwrite RTL or LTR character
+ builder.append(leftSeparatorSpan);
+ }
+
+ // likes
+ builder.append(newSpanUsingStylingOfAnotherSpan(oldSpannable, oldLikes));
+
+ // middle separator
+ String middleSeparatorString = compactLayout
+ ? "\u200E " + MIDDLE_SEPARATOR_CHARACTER + " "
+ : "\u200E \u2009" + MIDDLE_SEPARATOR_CHARACTER + "\u2009 "; // u2009 = 'narrow space' character
+ final int shapeInsertionIndex = middleSeparatorString.length() / 2;
+ Spannable middleSeparatorSpan = new SpannableString(middleSeparatorString);
+ ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
+ shapeDrawable.getPaint().setColor(getSeparatorColor(isLithoText));
+ shapeDrawable.setBounds(middleSeparatorBounds);
+ // Use original text width if using Rolling Number,
+ // to ensure the replacement styled span has the same width as the measured String,
+ // otherwise layout can be broken (especially on devices with small system font sizes).
+ middleSeparatorSpan.setSpan(
+ new VerticallyCenteredImageSpan(shapeDrawable),
+ shapeInsertionIndex, shapeInsertionIndex + 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+ builder.append(middleSeparatorSpan);
+
+ // dislikes
+ builder.append(newSpannableWithDislikes(oldSpannable, voteData));
+
+ return new SpannableString(builder);
+ }
+
+ /**
+ * @return If the text is likely for a previously created likes/dislikes segmented span.
+ */
+ public static boolean isPreviouslyCreatedSegmentedSpan(@NonNull String text) {
+ return text.indexOf(MIDDLE_SEPARATOR_CHARACTER) >= 0;
+ }
+
+ private static boolean spansHaveEqualTextAndColor(@NonNull Spanned one, @NonNull Spanned two) {
+ // Cannot use equals on the span, because many of the inner styling spans do not implement equals.
+ // Instead, compare the underlying text and the text color to handle when dark mode is changed.
+ // Cannot compare the status of device dark mode, as Litho components are updated just before dark mode status changes.
+ if (!one.toString().equals(two.toString())) {
+ return false;
+ }
+ ForegroundColorSpan[] oneColors = one.getSpans(0, one.length(), ForegroundColorSpan.class);
+ ForegroundColorSpan[] twoColors = two.getSpans(0, two.length(), ForegroundColorSpan.class);
+ final int oneLength = oneColors.length;
+ if (oneLength != twoColors.length) {
+ return false;
+ }
+ for (int i = 0; i < oneLength; i++) {
+ if (oneColors[i].getForegroundColor() != twoColors[i].getForegroundColor()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static SpannableString newSpannableWithDislikes(@NonNull Spanned sourceStyling, @NonNull RYDVoteData voteData) {
+ return newSpanUsingStylingOfAnotherSpan(sourceStyling,
+ Settings.RYD_DISLIKE_PERCENTAGE.get()
+ ? formatDislikePercentage(voteData.getDislikePercentage())
+ : formatDislikeCount(voteData.getDislikeCount()));
+ }
+
+ private static SpannableString newSpanUsingStylingOfAnotherSpan(@NonNull Spanned sourceStyle, @NonNull CharSequence newSpanText) {
+ SpannableString destination = new SpannableString(newSpanText);
+ Object[] spans = sourceStyle.getSpans(0, sourceStyle.length(), Object.class);
+ for (Object span : spans) {
+ destination.setSpan(span, 0, destination.length(), sourceStyle.getSpanFlags(span));
+ }
+ return destination;
+ }
+
+ private static String formatDislikeCount(long dislikeCount) {
+ if (isSDKAbove(24)) {
+ if (dislikeCountFormatter == null) {
+ // Note: Java number formatters will use the locale specific number characters.
+ // such as Arabic which formats "1.234" into "۱,۲۳٤"
+ // But YouTube disregards locale specific number characters
+ // and instead shows english number characters everywhere.
+ Locale locale = Objects.requireNonNull(Utils.getContext()).getResources().getConfiguration().getLocales().get(0);
+ Logger.printDebug(() -> "Locale: " + locale);
+ dislikeCountFormatter = CompactDecimalFormat.getInstance(locale, CompactDecimalFormat.CompactStyle.SHORT);
+ }
+ return dislikeCountFormatter.format(dislikeCount);
+ } else {
+ return String.valueOf(dislikeCount);
+ }
+ }
+
+ private static String formatDislikePercentage(float dislikePercentage) {
+ if (isSDKAbove(24)) {
+ synchronized (ReturnYouTubeDislike.class) { // number formatter is not thread safe, must synchronize
+ if (dislikePercentageFormatter == null) {
+ Locale locale = Objects.requireNonNull(Utils.getContext()).getResources().getConfiguration().getLocales().get(0);
+ Logger.printDebug(() -> "Locale: " + locale);
+ dislikePercentageFormatter = NumberFormat.getPercentInstance(locale);
+ }
+ if (dislikePercentage >= 0.01) { // at least 1%
+ dislikePercentageFormatter.setMaximumFractionDigits(0); // show only whole percentage points
+ } else {
+ dislikePercentageFormatter.setMaximumFractionDigits(1); // show up to 1 digit precision
+ }
+ return dislikePercentageFormatter.format(dislikePercentage);
+ }
+ } else {
+ return String.valueOf((int) (dislikePercentage * 100));
+ }
+ }
+
+ @NonNull
+ public static ReturnYouTubeDislike getFetchForVideoId(@Nullable String videoId) {
+ Objects.requireNonNull(videoId);
+ synchronized (fetchCache) {
+ // Remove any expired entries.
+ final long now = System.currentTimeMillis();
+ if (isSDKAbove(24)) {
+ fetchCache.values().removeIf(value -> {
+ final boolean expired = value.isExpired(now);
+ if (expired)
+ Logger.printDebug(() -> "Removing expired fetch: " + value.videoId);
+ return expired;
+ });
+ } else {
+ final Iterator> itr = fetchCache.entrySet().iterator();
+ while (itr.hasNext()) {
+ final Map.Entry entry = itr.next();
+ if (entry.getValue().isExpired(now)) {
+ Logger.printDebug(() -> "Removing expired fetch: " + entry.getValue().videoId);
+ itr.remove();
+ }
+ }
+ }
+
+ ReturnYouTubeDislike fetch = fetchCache.get(videoId);
+ if (fetch == null) {
+ fetch = new ReturnYouTubeDislike(videoId);
+ fetchCache.put(videoId, fetch);
+ }
+ return fetch;
+ }
+ }
+
+ /**
+ * Should be called if the user changes dislikes appearance settings.
+ */
+ public static void clearAllUICaches() {
+ synchronized (fetchCache) {
+ for (ReturnYouTubeDislike fetch : fetchCache.values()) {
+ fetch.clearUICache();
+ }
+ }
+ }
+
+ private ReturnYouTubeDislike(@NonNull String videoId) {
+ this.videoId = Objects.requireNonNull(videoId);
+ this.timeFetched = System.currentTimeMillis();
+ this.future = Utils.submitOnBackgroundThread(() -> ReturnYouTubeDislikeApi.fetchVotes(videoId));
+ }
+
+ private boolean isExpired(long now) {
+ final long timeSinceCreation = now - timeFetched;
+ if (timeSinceCreation < CACHE_TIMEOUT_FAILURE_MILLISECONDS) {
+ return false; // Not expired, even if the API call failed.
+ }
+ if (timeSinceCreation > CACHE_TIMEOUT_SUCCESS_MILLISECONDS) {
+ return true; // Always expired.
+ }
+ // Only expired if the fetch failed (API null response).
+ return (!fetchCompleted() || getFetchData(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH) == null);
+ }
+
+ @Nullable
+ public RYDVoteData getFetchData(long maxTimeToWait) {
+ try {
+ return future.get(maxTimeToWait, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException ex) {
+ Logger.printDebug(() -> "Waited but future was not complete after: " + maxTimeToWait + "ms");
+ } catch (ExecutionException | InterruptedException ex) {
+ Logger.printException(() -> "Future failure ", ex); // will never happen
+ }
+ return null;
+ }
+
+ /**
+ * @return if the RYD fetch call has completed.
+ */
+ public boolean fetchCompleted() {
+ return future.isDone();
+ }
+
+ private synchronized void clearUICache() {
+ if (replacementLikeDislikeSpan != null) {
+ Logger.printDebug(() -> "Clearing replacement span for: " + videoId);
+ }
+ replacementLikeDislikeSpan = null;
+ }
+
+ /**
+ * Must call off main thread, as this will make a network call if user is not yet registered.
+ *
+ * @return ReturnYouTubeDislike user ID. If user registration has never happened
+ * and the network call fails, this returns NULL.
+ */
+ @Nullable
+ private static String getUserId() {
+ Utils.verifyOffMainThread();
+
+ String userId = Settings.RYD_USER_ID.get();
+ if (!userId.isEmpty()) {
+ return userId;
+ }
+
+ userId = ReturnYouTubeDislikeApi.registerAsNewUser();
+ if (userId != null) {
+ Settings.RYD_USER_ID.save(userId);
+ }
+ return userId;
+ }
+
+ @NonNull
+ public String getVideoId() {
+ return videoId;
+ }
+
+ /**
+ * @return the replacement span containing dislikes, or the original span if RYD is not available.
+ */
+ @NonNull
+ public synchronized Spanned getDislikesSpan(@NonNull Spanned original, boolean isLithoText) {
+ return waitForFetchAndUpdateReplacementSpan(original, isLithoText);
+ }
+
+ @NonNull
+ private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, boolean isLithoText) {
+ try {
+ RYDVoteData votingData = getFetchData(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH);
+ if (votingData == null) {
+ Logger.printDebug(() -> "Cannot add dislike to UI (RYD data not available)");
+ return original;
+ }
+
+ synchronized (this) {
+ if (originalDislikeSpan != null && replacementLikeDislikeSpan != null) {
+ if (spansHaveEqualTextAndColor(original, replacementLikeDislikeSpan)) {
+ Logger.printDebug(() -> "Ignoring previously created dislikes span of data: " + videoId);
+ return original;
+ }
+ if (spansHaveEqualTextAndColor(original, originalDislikeSpan)) {
+ Logger.printDebug(() -> "Replacing span with previously created dislike span of data: " + videoId);
+ return replacementLikeDislikeSpan;
+ }
+ }
+ if (isPreviouslyCreatedSegmentedSpan(original.toString())) {
+ // need to recreate using original, as original has prior outdated dislike values
+ if (originalDislikeSpan == null) {
+ // Should never happen.
+ Logger.printDebug(() -> "Cannot add dislikes - original span is null. videoId: " + videoId);
+ return original;
+ }
+ original = originalDislikeSpan;
+ }
+
+ // No replacement span exist, create it now.
+
+ if (userVote != null) {
+ votingData.updateUsingVote(userVote);
+ }
+ originalDislikeSpan = original;
+ replacementLikeDislikeSpan = createDislikeSpan(original, votingData, isLithoText);
+ Logger.printDebug(() -> "Replaced: '" + originalDislikeSpan + "' with: '"
+ + replacementLikeDislikeSpan + "'" + " using video: " + videoId);
+
+ return replacementLikeDislikeSpan;
+ }
+ } catch (Exception e) {
+ Logger.printException(() -> "waitForFetchAndUpdateReplacementSpan failure", e); // should never happen
+ }
+ return original;
+ }
+
+ public void sendVote(@NonNull Vote vote) {
+ Utils.verifyOnMainThread();
+ Objects.requireNonNull(vote);
+ try {
+ setUserVote(vote);
+
+ voteSerialExecutor.execute(() -> {
+ try { // Must wrap in try/catch to properly log exceptions.
+ ReturnYouTubeDislikeApi.sendVote(getUserId(), videoId, vote);
+ } catch (Exception ex) {
+ Logger.printException(() -> "Failed to send vote", ex);
+ }
+ });
+ } catch (Exception ex) {
+ Logger.printException(() -> "Error trying to send vote", ex);
+ }
+ }
+
+ /**
+ * Sets the current user vote value, and does not send the vote to the RYD API.
+ *
+ * Only used to set value if thumbs up/down is already selected on video load.
+ */
+ public void setUserVote(@NonNull Vote vote) {
+ Objects.requireNonNull(vote);
+ try {
+ Logger.printDebug(() -> "setUserVote: " + vote);
+
+ synchronized (this) {
+ userVote = vote;
+ clearUICache();
+ }
+
+ if (future.isDone()) {
+ // Update the fetched vote data.
+ RYDVoteData voteData = getFetchData(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH);
+ if (voteData == null) {
+ // RYD fetch failed.
+ Logger.printDebug(() -> "Cannot update UI (vote data not available)");
+ return;
+ }
+ voteData.updateUsingVote(vote);
+ } // Else, vote will be applied after fetch completes.
+
+ } catch (Exception ex) {
+ Logger.printException(() -> "setUserVote failure", ex);
+ }
+ }
+}
+
+/**
+ * Vertically centers a Spanned Drawable.
+ */
+class VerticallyCenteredImageSpan extends ImageSpan {
+
+ public VerticallyCenteredImageSpan(Drawable drawable) {
+ super(drawable);
+ }
+
+ @Override
+ public int getSize(@NonNull Paint paint, @NonNull CharSequence text,
+ int start, int end, @Nullable Paint.FontMetricsInt fontMetrics) {
+ Drawable drawable = getDrawable();
+ Rect bounds = drawable.getBounds();
+ if (fontMetrics != null) {
+ Paint.FontMetricsInt paintMetrics = paint.getFontMetricsInt();
+ final int fontHeight = paintMetrics.descent - paintMetrics.ascent;
+ final int drawHeight = bounds.bottom - bounds.top;
+ final int halfDrawHeight = drawHeight / 2;
+ final int yCenter = paintMetrics.ascent + fontHeight / 2;
+
+ fontMetrics.ascent = yCenter - halfDrawHeight;
+ fontMetrics.top = fontMetrics.ascent;
+ fontMetrics.bottom = yCenter + halfDrawHeight;
+ fontMetrics.descent = fontMetrics.bottom;
+ }
+ return bounds.right;
+ }
+
+ @Override
+ public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end,
+ float x, int top, int y, int bottom, @NonNull Paint paint) {
+ Drawable drawable = getDrawable();
+ canvas.save();
+ Paint.FontMetricsInt paintMetrics = paint.getFontMetricsInt();
+ final int fontHeight = paintMetrics.descent - paintMetrics.ascent;
+ final int yCenter = y + paintMetrics.descent - fontHeight / 2;
+ final Rect drawBounds = drawable.getBounds();
+ final int translateY = yCenter - (drawBounds.bottom - drawBounds.top) / 2;
+ canvas.translate(x, translateY);
+ drawable.draw(canvas);
+ canvas.restore();
+ }
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/ActivityHook.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/ActivityHook.java
new file mode 100644
index 000000000..628c44ed6
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/ActivityHook.java
@@ -0,0 +1,80 @@
+package app.revanced.extension.music.settings;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import java.lang.ref.WeakReference;
+
+import app.revanced.extension.music.settings.preference.ReVancedPreferenceFragment;
+import app.revanced.extension.shared.utils.Logger;
+
+/**
+ * @noinspection ALL
+ */
+public class ActivityHook {
+ private static WeakReference activityRef = new WeakReference<>(null);
+
+ public static Activity getActivity() {
+ return activityRef.get();
+ }
+
+ /**
+ * Injection point.
+ *
+ * @param object object is usually Activity, but sometimes object cannot be cast to Activity.
+ * Check whether object can be cast as Activity for a safe hook.
+ */
+ public static void setActivity(@NonNull Object object) {
+ if (object instanceof Activity mActivity) {
+ activityRef = new WeakReference<>(mActivity);
+ }
+ }
+
+ /**
+ * Injection point.
+ *
+ * @param baseActivity Activity containing intent data.
+ * It should be finished immediately after obtaining the dataString.
+ * @return Whether or not dataString is included.
+ */
+ public static boolean initialize(@NonNull Activity baseActivity) {
+ try {
+ final Intent baseActivityIntent = baseActivity.getIntent();
+ if (baseActivityIntent == null)
+ return false;
+
+ // If we do not finish the activity immediately, the YT Music logo will remain on the screen.
+ baseActivity.finish();
+
+ String dataString = baseActivityIntent.getDataString();
+ if (dataString == null || dataString.isEmpty())
+ return false;
+
+ // Checks whether dataString contains settings that use Intent.
+ if (!Settings.includeWithIntent(dataString))
+ return false;
+
+
+ // Save intent data in settings activity.
+ Activity mActivity = activityRef.get();
+ Intent intent = mActivity.getIntent();
+ intent.setData(Uri.parse(dataString));
+ mActivity.setIntent(intent);
+
+ // Starts a new PreferenceFragment to handle activities freely.
+ mActivity.getFragmentManager()
+ .beginTransaction()
+ .add(new ReVancedPreferenceFragment(), "")
+ .commit();
+
+ return true;
+ } catch (Exception ex) {
+ Logger.printException(() -> "initializeSettings failure", ex);
+ }
+ return false;
+ }
+
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/Settings.java
new file mode 100644
index 000000000..5032dc814
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/Settings.java
@@ -0,0 +1,270 @@
+package app.revanced.extension.music.settings;
+
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
+import static app.revanced.extension.music.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
+
+import androidx.annotation.NonNull;
+
+import app.revanced.extension.music.patches.misc.client.AppClient.ClientType;
+import app.revanced.extension.music.patches.utils.PatchStatus;
+import app.revanced.extension.music.sponsorblock.SponsorBlockSettings;
+import app.revanced.extension.shared.settings.BaseSettings;
+import app.revanced.extension.shared.settings.BooleanSetting;
+import app.revanced.extension.shared.settings.EnumSetting;
+import app.revanced.extension.shared.settings.FloatSetting;
+import app.revanced.extension.shared.settings.IntegerSetting;
+import app.revanced.extension.shared.settings.LongSetting;
+import app.revanced.extension.shared.settings.Setting;
+import app.revanced.extension.shared.settings.StringSetting;
+import app.revanced.extension.shared.utils.Utils;
+
+
+@SuppressWarnings("unused")
+public class Settings extends BaseSettings {
+ // PreferenceScreen: Account
+ public static final BooleanSetting HIDE_ACCOUNT_MENU = new BooleanSetting("revanced_hide_account_menu", FALSE);
+ public static final StringSetting HIDE_ACCOUNT_MENU_FILTER_STRINGS = new StringSetting("revanced_hide_account_menu_filter_strings", "");
+ public static final BooleanSetting HIDE_ACCOUNT_MENU_EMPTY_COMPONENT = new BooleanSetting("revanced_hide_account_menu_empty_component", FALSE);
+ public static final BooleanSetting HIDE_HANDLE = new BooleanSetting("revanced_hide_handle", TRUE, true);
+ public static final BooleanSetting HIDE_TERMS_CONTAINER = new BooleanSetting("revanced_hide_terms_container", FALSE);
+
+
+ // PreferenceScreen: Action Bar
+ public static final BooleanSetting HIDE_ACTION_BUTTON_LIKE_DISLIKE = new BooleanSetting("revanced_hide_action_button_like_dislike", FALSE, true);
+ public static final BooleanSetting HIDE_ACTION_BUTTON_COMMENT = new BooleanSetting("revanced_hide_action_button_comment", FALSE, true);
+ public static final BooleanSetting HIDE_ACTION_BUTTON_ADD_TO_PLAYLIST = new BooleanSetting("revanced_hide_action_button_add_to_playlist", FALSE, true);
+ public static final BooleanSetting HIDE_ACTION_BUTTON_DOWNLOAD = new BooleanSetting("revanced_hide_action_button_download", FALSE, true);
+ public static final BooleanSetting HIDE_ACTION_BUTTON_SHARE = new BooleanSetting("revanced_hide_action_button_share", FALSE, true);
+ public static final BooleanSetting HIDE_ACTION_BUTTON_RADIO = new BooleanSetting("revanced_hide_action_button_radio", FALSE, true);
+ public static final BooleanSetting HIDE_ACTION_BUTTON_LABEL = new BooleanSetting("revanced_hide_action_button_label", FALSE, true);
+ public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action", FALSE, true);
+ public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_package_name", "com.deniscerri.ytdl");
+
+
+ // PreferenceScreen: Ads
+ public static final BooleanSetting HIDE_GENERAL_ADS = new BooleanSetting("revanced_hide_general_ads", TRUE, true);
+ public static final BooleanSetting HIDE_MUSIC_ADS = new BooleanSetting("revanced_hide_music_ads", TRUE, true);
+ public static final BooleanSetting HIDE_PAID_PROMOTION_LABEL = new BooleanSetting("revanced_hide_paid_promotion_label", TRUE, true);
+ public static final BooleanSetting HIDE_PREMIUM_PROMOTION = new BooleanSetting("revanced_hide_premium_promotion", TRUE, true);
+ public static final BooleanSetting HIDE_PREMIUM_RENEWAL = new BooleanSetting("revanced_hide_premium_renewal", TRUE, true);
+
+
+ // PreferenceScreen: Flyout menu
+ public static final BooleanSetting ENABLE_COMPACT_DIALOG = new BooleanSetting("revanced_enable_compact_dialog", FALSE);
+ public static final BooleanSetting ENABLE_TRIM_SILENCE = new BooleanSetting("revanced_enable_trim_silence", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_LIKE_DISLIKE = new BooleanSetting("revanced_hide_flyout_menu_like_dislike", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_3_COLUMN_COMPONENT = new BooleanSetting("revanced_hide_flyout_menu_3_column_component", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_ADD_TO_QUEUE = new BooleanSetting("revanced_hide_flyout_menu_add_to_queue", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_CAPTIONS = new BooleanSetting("revanced_hide_flyout_menu_captions", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_DELETE_PLAYLIST = new BooleanSetting("revanced_hide_flyout_menu_delete_playlist", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_DISMISS_QUEUE = new BooleanSetting("revanced_hide_flyout_menu_dismiss_queue", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_DOWNLOAD = new BooleanSetting("revanced_hide_flyout_menu_download", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_EDIT_PLAYLIST = new BooleanSetting("revanced_hide_flyout_menu_edit_playlist", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_GO_TO_ALBUM = new BooleanSetting("revanced_hide_flyout_menu_go_to_album", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_GO_TO_ARTIST = new BooleanSetting("revanced_hide_flyout_menu_go_to_artist", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_GO_TO_EPISODE = new BooleanSetting("revanced_hide_flyout_menu_go_to_episode", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_GO_TO_PODCAST = new BooleanSetting("revanced_hide_flyout_menu_go_to_podcast", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_HELP = new BooleanSetting("revanced_hide_flyout_menu_help", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_PIN_TO_SPEED_DIAL = new BooleanSetting("revanced_hide_flyout_menu_pin_to_speed_dial", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_PLAY_NEXT = new BooleanSetting("revanced_hide_flyout_menu_play_next", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_QUALITY = new BooleanSetting("revanced_hide_flyout_menu_quality", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_REMOVE_FROM_LIBRARY = new BooleanSetting("revanced_hide_flyout_menu_remove_from_library", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_REMOVE_FROM_PLAYLIST = new BooleanSetting("revanced_hide_flyout_menu_remove_from_playlist", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_REPORT = new BooleanSetting("revanced_hide_flyout_menu_report", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_SAVE_EPISODE_FOR_LATER = new BooleanSetting("revanced_hide_flyout_menu_save_episode_for_later", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_SAVE_TO_LIBRARY = new BooleanSetting("revanced_hide_flyout_menu_save_to_library", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_SAVE_TO_PLAYLIST = new BooleanSetting("revanced_hide_flyout_menu_save_to_playlist", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_SHARE = new BooleanSetting("revanced_hide_flyout_menu_share", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_SHUFFLE_PLAY = new BooleanSetting("revanced_hide_flyout_menu_shuffle_play", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_SLEEP_TIMER = new BooleanSetting("revanced_hide_flyout_menu_sleep_timer", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_START_RADIO = new BooleanSetting("revanced_hide_flyout_menu_start_radio", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_STATS_FOR_NERDS = new BooleanSetting("revanced_hide_flyout_menu_stats_for_nerds", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_SUBSCRIBE = new BooleanSetting("revanced_hide_flyout_menu_subscribe", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_UNPIN_FROM_SPEED_DIAL = new BooleanSetting("revanced_hide_flyout_menu_unpin_from_speed_dial", FALSE, true);
+ public static final BooleanSetting HIDE_FLYOUT_MENU_VIEW_SONG_CREDIT = new BooleanSetting("revanced_hide_flyout_menu_view_song_credit", FALSE, true);
+ public static final BooleanSetting REPLACE_FLYOUT_MENU_DISMISS_QUEUE = new BooleanSetting("revanced_replace_flyout_menu_dismiss_queue", FALSE, true);
+ public static final BooleanSetting REPLACE_FLYOUT_MENU_DISMISS_QUEUE_CONTINUE_WATCH = new BooleanSetting("revanced_replace_flyout_menu_dismiss_queue_continue_watch", TRUE);
+ public static final BooleanSetting REPLACE_FLYOUT_MENU_REPORT = new BooleanSetting("revanced_replace_flyout_menu_report", TRUE, true);
+ public static final BooleanSetting REPLACE_FLYOUT_MENU_REPORT_ONLY_PLAYER = new BooleanSetting("revanced_replace_flyout_menu_report_only_player", TRUE, true);
+
+
+ // PreferenceScreen: General
+ public static final StringSetting CHANGE_START_PAGE = new StringSetting("revanced_change_start_page", "FEmusic_home", true);
+ public static final BooleanSetting DISABLE_DISLIKE_REDIRECTION = new BooleanSetting("revanced_disable_dislike_redirection", FALSE);
+ public static final BooleanSetting ENABLE_LANDSCAPE_MODE = new BooleanSetting("revanced_enable_landscape_mode", FALSE, true);
+ public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
+ public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true);
+ public static final BooleanSetting HIDE_BUTTON_SHELF = new BooleanSetting("revanced_hide_button_shelf", FALSE, true);
+ public static final BooleanSetting HIDE_CAROUSEL_SHELF = new BooleanSetting("revanced_hide_carousel_shelf", FALSE, true);
+ public static final BooleanSetting HIDE_PLAYLIST_CARD_SHELF = new BooleanSetting("revanced_hide_playlist_card_shelf", FALSE, true);
+ public static final BooleanSetting HIDE_SAMPLE_SHELF = new BooleanSetting("revanced_hide_samples_shelf", FALSE, true);
+ public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE);
+ public static final BooleanSetting HIDE_CATEGORY_BAR = new BooleanSetting("revanced_hide_category_bar", FALSE, true);
+ public static final BooleanSetting HIDE_FLOATING_BUTTON = new BooleanSetting("revanced_hide_floating_button", FALSE, true);
+ public static final BooleanSetting HIDE_TAP_TO_UPDATE_BUTTON = new BooleanSetting("revanced_hide_tap_to_update_button", FALSE, true);
+ public static final BooleanSetting HIDE_HISTORY_BUTTON = new BooleanSetting("revanced_hide_history_button", FALSE);
+ public static final BooleanSetting HIDE_NOTIFICATION_BUTTON = new BooleanSetting("revanced_hide_notification_button", FALSE, true);
+ public static final BooleanSetting HIDE_SOUND_SEARCH_BUTTON = new BooleanSetting("revanced_hide_sound_search_button", FALSE, true);
+ public static final BooleanSetting HIDE_VOICE_SEARCH_BUTTON = new BooleanSetting("revanced_hide_voice_search_button", FALSE, true);
+ public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE);
+ public static final BooleanSetting RESTORE_OLD_STYLE_LIBRARY_SHELF = new BooleanSetting("revanced_restore_old_style_library_shelf", FALSE, true);
+ public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version",
+ PatchStatus.SpoofAppVersionDefaultBoolean(), true);
+ public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target",
+ PatchStatus.SpoofAppVersionDefaultString(), true);
+
+
+ // PreferenceScreen: Navigation bar
+ public static final BooleanSetting ENABLE_BLACK_NAVIGATION_BAR = new BooleanSetting("revanced_enable_black_navigation_bar", TRUE);
+ public static final BooleanSetting HIDE_NAVIGATION_HOME_BUTTON = new BooleanSetting("revanced_hide_navigation_home_button", FALSE, true);
+ public static final BooleanSetting HIDE_NAVIGATION_SAMPLES_BUTTON = new BooleanSetting("revanced_hide_navigation_samples_button", FALSE, true);
+ public static final BooleanSetting HIDE_NAVIGATION_EXPLORE_BUTTON = new BooleanSetting("revanced_hide_navigation_explore_button", FALSE, true);
+ public static final BooleanSetting HIDE_NAVIGATION_LIBRARY_BUTTON = new BooleanSetting("revanced_hide_navigation_library_button", FALSE, true);
+ public static final BooleanSetting HIDE_NAVIGATION_UPGRADE_BUTTON = new BooleanSetting("revanced_hide_navigation_upgrade_button", TRUE, true);
+ public static final BooleanSetting HIDE_NAVIGATION_BAR = new BooleanSetting("revanced_hide_navigation_bar", FALSE, true);
+ public static final BooleanSetting HIDE_NAVIGATION_LABEL = new BooleanSetting("revanced_hide_navigation_label", FALSE, true);
+
+
+ // PreferenceScreen: Player
+ public static final BooleanSetting DISABLE_MINI_PLAYER_GESTURE = new BooleanSetting("revanced_disable_mini_player_gesture", FALSE, true);
+ public static final BooleanSetting DISABLE_PLAYER_GESTURE = new BooleanSetting("revanced_disable_player_gesture", FALSE, true);
+ public static final BooleanSetting ENABLE_BLACK_PLAYER_BACKGROUND = new BooleanSetting("revanced_enable_black_player_background", FALSE, true);
+ public static final BooleanSetting ENABLE_COLOR_MATCH_PLAYER = new BooleanSetting("revanced_enable_color_match_player", TRUE);
+ public static final BooleanSetting ENABLE_FORCE_MINIMIZED_PLAYER = new BooleanSetting("revanced_enable_force_minimized_player", TRUE);
+ public static final BooleanSetting ENABLE_MINI_PLAYER_NEXT_BUTTON = new BooleanSetting("revanced_enable_mini_player_next_button", TRUE, true);
+ public static final BooleanSetting ENABLE_MINI_PLAYER_PREVIOUS_BUTTON = new BooleanSetting("revanced_enable_mini_player_previous_button", TRUE, true);
+ public static final BooleanSetting ENABLE_SWIPE_TO_DISMISS_MINI_PLAYER = new BooleanSetting("revanced_enable_swipe_to_dismiss_mini_player", TRUE, true);
+ public static final BooleanSetting ENABLE_ZEN_MODE = new BooleanSetting("revanced_enable_zen_mode", FALSE);
+ public static final BooleanSetting ENABLE_ZEN_MODE_PODCAST = new BooleanSetting("revanced_enable_zen_mode_podcast", FALSE);
+ public static final BooleanSetting HIDE_AUDIO_VIDEO_SWITCH_TOGGLE = new BooleanSetting("revanced_hide_audio_video_switch_toggle", FALSE, true);
+ public static final BooleanSetting HIDE_COMMENT_CHANNEL_GUIDELINES = new BooleanSetting("revanced_hide_comment_channel_guidelines", TRUE);
+ public static final BooleanSetting HIDE_COMMENT_TIMESTAMP_AND_EMOJI_BUTTONS = new BooleanSetting("revanced_hide_comment_timestamp_and_emoji_buttons", FALSE);
+ public static final BooleanSetting HIDE_DOUBLE_TAP_OVERLAY_FILTER = new BooleanSetting("revanced_hide_double_tap_overlay_filter", FALSE, true);
+ public static final BooleanSetting HIDE_FULLSCREEN_SHARE_BUTTON = new BooleanSetting("revanced_hide_fullscreen_share_button", FALSE, true);
+ public static final BooleanSetting REMEMBER_REPEAT_SATE = new BooleanSetting("revanced_remember_repeat_state", TRUE);
+ public static final BooleanSetting REMEMBER_SHUFFLE_SATE = new BooleanSetting("revanced_remember_shuffle_state", TRUE);
+ public static final BooleanSetting ALWAYS_SHUFFLE = new BooleanSetting("revanced_always_shuffle", FALSE);
+ public static final BooleanSetting RESTORE_OLD_COMMENTS_POPUP_PANELS = new BooleanSetting("revanced_restore_old_comments_popup_panels", FALSE, true);
+ public static final BooleanSetting RESTORE_OLD_PLAYER_BACKGROUND = new BooleanSetting("revanced_restore_old_player_background", FALSE, true);
+ public static final BooleanSetting RESTORE_OLD_PLAYER_LAYOUT = new BooleanSetting("revanced_restore_old_player_layout", FALSE, true);
+
+
+ // PreferenceScreen: Settings menu
+ public static final BooleanSetting HIDE_SETTINGS_MENU_PARENT_TOOLS = new BooleanSetting("revanced_hide_settings_menu_parent_tools", FALSE, true);
+ public static final BooleanSetting HIDE_SETTINGS_MENU_GENERAL = new BooleanSetting("revanced_hide_settings_menu_general", FALSE, true);
+ public static final BooleanSetting HIDE_SETTINGS_MENU_PLAYBACK = new BooleanSetting("revanced_hide_settings_menu_playback", FALSE, true);
+ public static final BooleanSetting HIDE_SETTINGS_MENU_DATA_SAVING = new BooleanSetting("revanced_hide_settings_menu_data_saving", FALSE, true);
+ public static final BooleanSetting HIDE_SETTINGS_MENU_DOWNLOADS_AND_STORAGE = new BooleanSetting("revanced_hide_settings_menu_downloads_and_storage", FALSE, true);
+ public static final BooleanSetting HIDE_SETTINGS_MENU_NOTIFICATIONS = new BooleanSetting("revanced_hide_settings_menu_notification", FALSE, true);
+ public static final BooleanSetting HIDE_SETTINGS_MENU_PRIVACY_AND_LOCATION = new BooleanSetting("revanced_hide_settings_menu_privacy_and_location", FALSE, true);
+ public static final BooleanSetting HIDE_SETTINGS_MENU_RECOMMENDATIONS = new BooleanSetting("revanced_hide_settings_menu_recommendations", FALSE, true);
+ public static final BooleanSetting HIDE_SETTINGS_MENU_PAID_MEMBERSHIPS = new BooleanSetting("revanced_hide_settings_menu_paid_memberships", TRUE, true);
+ public static final BooleanSetting HIDE_SETTINGS_MENU_ABOUT = new BooleanSetting("revanced_hide_settings_menu_about", FALSE, true);
+
+
+ // PreferenceScreen: Video
+ public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds", "0.5\n0.8\n1.0\n1.2\n1.5\n1.8\n2.0", true);
+ public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", TRUE);
+ public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_playback_speed_last_selected_toast", TRUE);
+ public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", TRUE);
+ public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_video_quality_last_selected_toast", TRUE);
+ public static final FloatSetting DEFAULT_PLAYBACK_SPEED = new FloatSetting("revanced_default_playback_speed", 1.0f);
+ public static final IntegerSetting DEFAULT_VIDEO_QUALITY_MOBILE = new IntegerSetting("revanced_default_video_quality_mobile", -2);
+ public static final IntegerSetting DEFAULT_VIDEO_QUALITY_WIFI = new IntegerSetting("revanced_default_video_quality_wifi", -2);
+
+
+ // PreferenceScreen: Miscellaneous
+ public static final BooleanSetting CHANGE_SHARE_SHEET = new BooleanSetting("revanced_change_share_sheet", FALSE, true);
+ public static final BooleanSetting DISABLE_CAIRO_SPLASH_ANIMATION = new BooleanSetting("revanced_disable_cairo_splash_animation", FALSE, true);
+ public static final BooleanSetting DISABLE_DRC_AUDIO = new BooleanSetting("revanced_disable_drc_audio", FALSE, true);
+ public static final BooleanSetting ENABLE_OPUS_CODEC = new BooleanSetting("revanced_enable_opus_codec", FALSE, true);
+ public static final BooleanSetting SETTINGS_IMPORT_EXPORT = new BooleanSetting("revanced_extended_settings_import_export", FALSE, false);
+ public static final BooleanSetting SPOOF_CLIENT = new BooleanSetting("revanced_spoof_client", FALSE, true);
+ public static final EnumSetting SPOOF_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_client_type", ClientType.IOS_MUSIC_6_21, true);
+
+
+ // PreferenceScreen: Return YouTube Dislike
+ public static final BooleanSetting RYD_ENABLED = new BooleanSetting("revanced_ryd_enabled", TRUE);
+ public static final StringSetting RYD_USER_ID = new StringSetting("revanced_ryd_user_id", "", false, false);
+ public static final BooleanSetting RYD_DISLIKE_PERCENTAGE = new BooleanSetting("revanced_ryd_dislike_percentage", FALSE);
+ public static final BooleanSetting RYD_COMPACT_LAYOUT = new BooleanSetting("revanced_ryd_compact_layout", FALSE);
+ public static final BooleanSetting RYD_ESTIMATED_LIKE = new BooleanSetting("revanced_ryd_estimated_like", FALSE, true);
+ public static final BooleanSetting RYD_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("revanced_ryd_toast_on_connection_error", FALSE);
+
+ // PreferenceScreen: Return YouTube Username
+ public static final BooleanSetting RETURN_YOUTUBE_USERNAME_ABOUT = new BooleanSetting("revanced_return_youtube_username_youtube_data_api_v3_about", FALSE, false);
+
+
+ // PreferenceScreen: SponsorBlock
+ public static final BooleanSetting SB_ENABLED = new BooleanSetting("sb_enabled", TRUE);
+ public static final BooleanSetting SB_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("sb_toast_on_connection_error", FALSE);
+ public static final BooleanSetting SB_TOAST_ON_SKIP = new BooleanSetting("sb_toast_on_skip", TRUE);
+ public static final StringSetting SB_API_URL = new StringSetting("sb_api_url", "https://sponsor.ajay.app");
+ public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id", "");
+ public static final BooleanSetting SB_USER_IS_VIP = new BooleanSetting("sb_user_is_vip", FALSE);
+
+ public static final StringSetting SB_CATEGORY_SPONSOR = new StringSetting("sb_sponsor", SKIP_AUTOMATICALLY.reVancedKeyValue);
+ public static final StringSetting SB_CATEGORY_SPONSOR_COLOR = new StringSetting("sb_sponsor_color", "#00D400");
+ public static final StringSetting SB_CATEGORY_SELF_PROMO = new StringSetting("sb_selfpromo", SKIP_AUTOMATICALLY.reVancedKeyValue);
+ public static final StringSetting SB_CATEGORY_SELF_PROMO_COLOR = new StringSetting("sb_selfpromo_color", "#FFFF00");
+ public static final StringSetting SB_CATEGORY_INTERACTION = new StringSetting("sb_interaction", SKIP_AUTOMATICALLY.reVancedKeyValue);
+ public static final StringSetting SB_CATEGORY_INTERACTION_COLOR = new StringSetting("sb_interaction_color", "#CC00FF");
+ public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", SKIP_AUTOMATICALLY.reVancedKeyValue);
+ public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#00FFFF");
+ public static final StringSetting SB_CATEGORY_OUTRO = new StringSetting("sb_outro", SKIP_AUTOMATICALLY.reVancedKeyValue);
+ public static final StringSetting SB_CATEGORY_OUTRO_COLOR = new StringSetting("sb_outro_color", "#0202ED");
+ public static final StringSetting SB_CATEGORY_PREVIEW = new StringSetting("sb_preview", SKIP_AUTOMATICALLY.reVancedKeyValue);
+ public static final StringSetting SB_CATEGORY_PREVIEW_COLOR = new StringSetting("sb_preview_color", "#008FD6");
+ public static final StringSetting SB_CATEGORY_FILLER = new StringSetting("sb_filler", SKIP_AUTOMATICALLY.reVancedKeyValue);
+ public static final StringSetting SB_CATEGORY_FILLER_COLOR = new StringSetting("sb_filler_color", "#7300FF");
+ public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC = new StringSetting("sb_music_offtopic", SKIP_AUTOMATICALLY.reVancedKeyValue);
+ public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC_COLOR = new StringSetting("sb_music_offtopic_color", "#FF9900");
+
+ // SB settings not exported
+ public static final LongSetting SB_LAST_VIP_CHECK = new LongSetting("sb_last_vip_check", 0L, false, false);
+
+ static {
+ // region SB import/export callbacks
+
+ Setting.addImportExportCallback(SponsorBlockSettings.SB_IMPORT_EXPORT_CALLBACK);
+
+ // endregion
+ }
+
+ public static final String OPEN_DEFAULT_APP_SETTINGS = "revanced_default_app_settings";
+
+ /**
+ * If a setting path has this prefix, then remove it.
+ */
+ public static final String OPTIONAL_SPONSOR_BLOCK_SETTINGS_PREFIX = "sb_segments_";
+
+ /**
+ * Array of settings using intent
+ */
+ private static final String[] intentSettingArray = new String[]{
+ BYPASS_IMAGE_REGION_RESTRICTIONS_DOMAIN.key,
+ CHANGE_START_PAGE.key,
+ CUSTOM_FILTER_STRINGS.key,
+ CUSTOM_PLAYBACK_SPEEDS.key,
+ EXTERNAL_DOWNLOADER_PACKAGE_NAME.key,
+ HIDE_ACCOUNT_MENU_FILTER_STRINGS.key,
+ SB_API_URL.key,
+ SETTINGS_IMPORT_EXPORT.key,
+ SPOOF_APP_VERSION_TARGET.key,
+ SPOOF_CLIENT_TYPE.key,
+ SPOOF_STREAMING_DATA_TYPE.key,
+ RETURN_YOUTUBE_USERNAME_ABOUT.key,
+ RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT.key,
+ RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY.key,
+ OPEN_DEFAULT_APP_SETTINGS,
+ OPTIONAL_SPONSOR_BLOCK_SETTINGS_PREFIX
+ };
+
+ /**
+ * @return whether dataString contains settings that use Intent
+ */
+ public static boolean includeWithIntent(@NonNull String dataString) {
+ return Utils.containsAny(dataString, intentSettingArray);
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ExternalDownloaderPreference.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ExternalDownloaderPreference.java
new file mode 100644
index 000000000..0454f1424
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ExternalDownloaderPreference.java
@@ -0,0 +1,132 @@
+package app.revanced.extension.music.settings.preference;
+
+import static app.revanced.extension.music.utils.ExtendedUtils.getDialogBuilder;
+import static app.revanced.extension.shared.utils.StringRef.str;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.net.Uri;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.TypedValue;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+
+import java.util.Arrays;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.music.utils.ExtendedUtils;
+import app.revanced.extension.shared.settings.StringSetting;
+import app.revanced.extension.shared.utils.ResourceUtils;
+import app.revanced.extension.shared.utils.Utils;
+
+/**
+ * @noinspection all
+ */
+public class ExternalDownloaderPreference {
+
+ private static final StringSetting settings = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME;
+ private static final String[] mEntries = ResourceUtils.getStringArray("revanced_external_downloader_label");
+ private static final String[] mEntryValues = ResourceUtils.getStringArray("revanced_external_downloader_package_name");
+ private static final String[] mWebsiteEntries = ResourceUtils.getStringArray("revanced_external_downloader_website");
+ private static EditText mEditText;
+ private static String packageName;
+ private static int mClickedDialogEntryIndex;
+
+ private static final TextWatcher textWatcher = new TextWatcher() {
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ public void afterTextChanged(Editable s) {
+ packageName = s.toString();
+ mClickedDialogEntryIndex = Arrays.asList(mEntryValues).indexOf(packageName);
+ }
+ };
+
+ public static void showDialog(Activity mActivity) {
+ packageName = settings.get().toString();
+ mClickedDialogEntryIndex = Arrays.asList(mEntryValues).indexOf(packageName);
+
+ AlertDialog.Builder builder = getDialogBuilder(mActivity);
+
+ TableLayout table = new TableLayout(mActivity);
+ table.setOrientation(LinearLayout.HORIZONTAL);
+ table.setPadding(15, 0, 15, 0);
+
+ TableRow row = new TableRow(mActivity);
+
+ mEditText = new EditText(mActivity);
+ mEditText.setText(packageName);
+ mEditText.addTextChangedListener(textWatcher);
+ mEditText.setTextSize(TypedValue.COMPLEX_UNIT_PT, 9);
+ mEditText.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.WRAP_CONTENT, 1f));
+ row.addView(mEditText);
+
+ table.addView(row);
+ builder.setView(table);
+
+ builder.setTitle(str("revanced_external_downloader_dialog_title"));
+ builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex, (dialog, which) -> {
+ mClickedDialogEntryIndex = which;
+ mEditText.setText(mEntryValues[which].toString());
+ });
+ builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ final String packageName = mEditText.getText().toString().trim();
+ settings.save(packageName);
+ checkPackageIsValid(mActivity, packageName);
+ dialog.dismiss();
+ });
+ builder.setNeutralButton(str("revanced_extended_settings_reset"), (dialog, which) -> settings.resetToDefault());
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ builder.show();
+ }
+
+ private static boolean checkPackageIsValid(Activity mActivity, String packageName) {
+ String appName = "";
+ String website = "";
+ if (mClickedDialogEntryIndex >= 0) {
+ appName = mEntries[mClickedDialogEntryIndex].toString();
+ website = mWebsiteEntries[mClickedDialogEntryIndex].toString();
+ return showToastOrOpenWebsites(mActivity, appName, packageName, website);
+ } else {
+ return showToastOrOpenWebsites(mActivity, appName, packageName, website);
+ }
+ }
+
+ private static boolean showToastOrOpenWebsites(Activity mActivity, String appName, String packageName, String website) {
+ if (ExtendedUtils.isPackageEnabled(packageName))
+ return true;
+
+ if (website.isEmpty()) {
+ Utils.showToastShort(str("revanced_external_downloader_not_installed_warning", packageName));
+ return false;
+ }
+
+ getDialogBuilder(mActivity)
+ .setTitle(str("revanced_external_downloader_not_installed_dialog_title"))
+ .setMessage(str("revanced_external_downloader_not_installed_dialog_message", appName, appName))
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(website));
+ mActivity.startActivity(i);
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+
+ return false;
+ }
+
+ public static boolean checkPackageIsEnabled() {
+ final Activity mActivity = Utils.getActivity();
+ packageName = settings.get().toString();
+ mClickedDialogEntryIndex = Arrays.asList(mEntryValues).indexOf(packageName);
+ return checkPackageIsValid(mActivity, packageName);
+ }
+
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java
new file mode 100644
index 000000000..313588ef9
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java
@@ -0,0 +1,355 @@
+package app.revanced.extension.music.settings.preference;
+
+import static app.revanced.extension.music.settings.Settings.BYPASS_IMAGE_REGION_RESTRICTIONS_DOMAIN;
+import static app.revanced.extension.music.settings.Settings.CHANGE_START_PAGE;
+import static app.revanced.extension.music.settings.Settings.CUSTOM_FILTER_STRINGS;
+import static app.revanced.extension.music.settings.Settings.CUSTOM_PLAYBACK_SPEEDS;
+import static app.revanced.extension.music.settings.Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME;
+import static app.revanced.extension.music.settings.Settings.HIDE_ACCOUNT_MENU_FILTER_STRINGS;
+import static app.revanced.extension.music.settings.Settings.OPEN_DEFAULT_APP_SETTINGS;
+import static app.revanced.extension.music.settings.Settings.OPTIONAL_SPONSOR_BLOCK_SETTINGS_PREFIX;
+import static app.revanced.extension.music.settings.Settings.RETURN_YOUTUBE_USERNAME_ABOUT;
+import static app.revanced.extension.music.settings.Settings.SB_API_URL;
+import static app.revanced.extension.music.settings.Settings.SETTINGS_IMPORT_EXPORT;
+import static app.revanced.extension.music.settings.Settings.SPOOF_APP_VERSION_TARGET;
+import static app.revanced.extension.music.settings.Settings.SPOOF_CLIENT_TYPE;
+import static app.revanced.extension.music.utils.ExtendedUtils.getDialogBuilder;
+import static app.revanced.extension.music.utils.ExtendedUtils.getLayoutParams;
+import static app.revanced.extension.music.utils.RestartUtils.showRestartDialog;
+import static app.revanced.extension.shared.settings.BaseSettings.RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT;
+import static app.revanced.extension.shared.settings.BaseSettings.RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY;
+import static app.revanced.extension.shared.settings.BaseSettings.SPOOF_STREAMING_DATA_TYPE;
+import static app.revanced.extension.shared.settings.Setting.getSettingFromPath;
+import static app.revanced.extension.shared.utils.ResourceUtils.getStringArray;
+import static app.revanced.extension.shared.utils.StringRef.str;
+import static app.revanced.extension.shared.utils.Utils.isSDKAbove;
+import static app.revanced.extension.shared.utils.Utils.showToastShort;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.icu.text.SimpleDateFormat;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+import android.text.InputType;
+import android.util.TypedValue;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+
+import androidx.annotation.Nullable;
+
+import com.google.android.material.textfield.TextInputLayout;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.Objects;
+
+import app.revanced.extension.music.patches.utils.ReturnYouTubeDislikePatch;
+import app.revanced.extension.music.returnyoutubedislike.ReturnYouTubeDislike;
+import app.revanced.extension.music.settings.ActivityHook;
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.music.utils.ExtendedUtils;
+import app.revanced.extension.shared.settings.BooleanSetting;
+import app.revanced.extension.shared.settings.EnumSetting;
+import app.revanced.extension.shared.settings.Setting;
+import app.revanced.extension.shared.settings.StringSetting;
+import app.revanced.extension.shared.settings.preference.YouTubeDataAPIDialogBuilder;
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.Utils;
+
+@SuppressWarnings("all")
+public class ReVancedPreferenceFragment extends PreferenceFragment {
+
+ private static final String IMPORT_EXPORT_SETTINGS_ENTRY_KEY = "revanced_extended_settings_import_export_entries";
+ private static final int READ_REQUEST_CODE = 42;
+ private static final int WRITE_REQUEST_CODE = 43;
+
+ private static String existingSettings;
+
+
+ public ReVancedPreferenceFragment() {
+ }
+
+ /**
+ * Injection point.
+ */
+ public static void onPreferenceChanged(@Nullable String key, boolean newValue) {
+ if (key == null || key.isEmpty())
+ return;
+
+ if (key.equals(Settings.RESTORE_OLD_PLAYER_LAYOUT.key) && newValue) {
+ Settings.RESTORE_OLD_PLAYER_BACKGROUND.save(newValue);
+ } else if (key.equals(Settings.RYD_ENABLED.key)) {
+ ReturnYouTubeDislikePatch.onRYDStatusChange(newValue);
+ } else if (key.equals(Settings.RYD_DISLIKE_PERCENTAGE.key) || key.equals(Settings.RYD_COMPACT_LAYOUT.key)) {
+ ReturnYouTubeDislike.clearAllUICaches();
+ }
+
+ for (Setting> setting : Setting.allLoadedSettings()) {
+ if (key.equals(setting.key)) {
+ ((BooleanSetting) setting).save(newValue);
+ if (setting.rebootApp) {
+ showRebootDialog();
+ }
+ break;
+ }
+ }
+ }
+
+ public static void showRebootDialog() {
+ final Activity activity = ActivityHook.getActivity();
+ if (activity == null)
+ return;
+
+ showRestartDialog(activity);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ try {
+ final Activity baseActivity = this.getActivity();
+ final Activity mActivity = ActivityHook.getActivity();
+ final Intent savedInstanceStateIntent = baseActivity.getIntent();
+ if (savedInstanceStateIntent == null)
+ return;
+
+ final String dataString = savedInstanceStateIntent.getDataString();
+ if (dataString == null || dataString.isEmpty())
+ return;
+
+ if (dataString.startsWith(OPTIONAL_SPONSOR_BLOCK_SETTINGS_PREFIX)) {
+ SponsorBlockCategoryPreference.showDialog(baseActivity, dataString.replaceAll(OPTIONAL_SPONSOR_BLOCK_SETTINGS_PREFIX, ""));
+ return;
+ } else if (dataString.equals(OPEN_DEFAULT_APP_SETTINGS)) {
+ openDefaultAppSetting();
+ return;
+ }
+
+ final Setting> settings = getSettingFromPath(dataString);
+ if (settings instanceof StringSetting stringSetting) {
+ if (settings.equals(CHANGE_START_PAGE)) {
+ ResettableListPreference.showDialog(mActivity, stringSetting, 2);
+ } else if (settings.equals(BYPASS_IMAGE_REGION_RESTRICTIONS_DOMAIN)
+ || settings.equals(CUSTOM_FILTER_STRINGS)
+ || settings.equals(CUSTOM_PLAYBACK_SPEEDS)
+ || settings.equals(HIDE_ACCOUNT_MENU_FILTER_STRINGS)
+ || settings.equals(RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY)) {
+ ResettableEditTextPreference.showDialog(mActivity, stringSetting);
+ } else if (settings.equals(EXTERNAL_DOWNLOADER_PACKAGE_NAME)) {
+ ExternalDownloaderPreference.showDialog(mActivity);
+ } else if (settings.equals(SB_API_URL)) {
+ SponsorBlockApiUrlPreference.showDialog(mActivity);
+ } else if (settings.equals(SPOOF_APP_VERSION_TARGET)) {
+ ResettableListPreference.showDialog(mActivity, stringSetting, 0);
+ } else {
+ Logger.printDebug(() -> "Failed to find the right value: " + dataString);
+ }
+ } else if (settings instanceof BooleanSetting) {
+ if (settings.equals(SETTINGS_IMPORT_EXPORT)) {
+ importExportListDialogBuilder();
+ } else if (settings.equals(RETURN_YOUTUBE_USERNAME_ABOUT)) {
+ YouTubeDataAPIDialogBuilder.showDialog(mActivity);
+ } else {
+ Logger.printDebug(() -> "Failed to find the right value: " + dataString);
+ }
+ } else if (settings instanceof EnumSetting> enumSetting) {
+ if (settings.equals(RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT)
+ || settings.equals(SPOOF_CLIENT_TYPE)
+ || settings.equals(SPOOF_STREAMING_DATA_TYPE)) {
+ ResettableListPreference.showDialog(mActivity, enumSetting, 0);
+ }
+ }
+ } catch (Exception ex) {
+ Logger.printException(() -> "onCreate failure", ex);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+ private void openDefaultAppSetting() {
+ try {
+ Context context = getActivity();
+ final Uri uri = Uri.parse("package:" + context.getPackageName());
+ final Intent intent = isSDKAbove(31)
+ ? new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, uri)
+ : new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri);
+ context.startActivity(intent);
+ } catch (Exception exception) {
+ Logger.printException(() -> "openDefaultAppSetting failed");
+ }
+ }
+
+ /**
+ * Build a ListDialog for Import / Export settings
+ * When importing/exporting as file, {@link #onActivityResult} is used, so declare it here.
+ */
+ private void importExportListDialogBuilder() {
+ try {
+ final Activity activity = getActivity();
+ final String[] mEntries = getStringArray(IMPORT_EXPORT_SETTINGS_ENTRY_KEY);
+
+ getDialogBuilder(activity)
+ .setTitle(str("revanced_extended_settings_import_export_title"))
+ .setItems(mEntries, (dialog, index) -> {
+ switch (index) {
+ case 0 -> exportActivity();
+ case 1 -> importActivity();
+ case 2 -> importExportEditTextDialogBuilder();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ } catch (Exception ex) {
+ Logger.printException(() -> "importExportListDialogBuilder failure", ex);
+ }
+ }
+
+ /**
+ * Build a EditTextDialog for Import / Export settings
+ */
+ private void importExportEditTextDialogBuilder() {
+ try {
+ final Activity activity = getActivity();
+ final EditText textView = new EditText(activity);
+ existingSettings = Setting.exportToJson(null);
+ textView.setText(existingSettings);
+ textView.setInputType(textView.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+ textView.setTextSize(TypedValue.COMPLEX_UNIT_PT, 8); // Use a smaller font to reduce text wrap.
+
+ TextInputLayout textInputLayout = new TextInputLayout(activity);
+ textInputLayout.setLayoutParams(getLayoutParams());
+ textInputLayout.addView(textView);
+
+ FrameLayout container = new FrameLayout(activity);
+ container.addView(textInputLayout);
+
+ getDialogBuilder(activity)
+ .setTitle(str("revanced_extended_settings_import_export_title"))
+ .setView(container)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setNeutralButton(str("revanced_extended_settings_import_copy"), (dialog, which) -> Utils.setClipboard(textView.getText().toString(), str("revanced_share_copy_settings_success")))
+ .setPositiveButton(str("revanced_extended_settings_import"), (dialog, which) -> importSettings(activity, textView.getText().toString()))
+ .show();
+ } catch (Exception ex) {
+ Logger.printException(() -> "importExportEditTextDialogBuilder failure", ex);
+ }
+ }
+
+ /**
+ * Invoke the SAF(Storage Access Framework) to export settings
+ */
+ private void exportActivity() {
+ @SuppressLint("SimpleDateFormat")
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+
+ var appName = ExtendedUtils.getAppLabel();
+ var versionName = ExtendedUtils.getAppVersionName();
+ var formatDate = dateFormat.format(new Date(System.currentTimeMillis()));
+ var fileName = String.format("%s_v%s_%s.txt", appName, versionName, formatDate);
+
+ var intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("text/plain");
+ intent.putExtra(Intent.EXTRA_TITLE, fileName);
+ startActivityForResult(intent, WRITE_REQUEST_CODE);
+ }
+
+ /**
+ * Invoke the SAF(Storage Access Framework) to import settings
+ */
+ private void importActivity() {
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType(isSDKAbove(29) ? "text/plain" : "*/*");
+ startActivityForResult(intent, READ_REQUEST_CODE);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+ exportText(data.getData());
+ } else if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK && data != null) {
+ importText(data.getData());
+ }
+ }
+
+ private void exportText(Uri uri) {
+ try {
+ final Context context = this.getContext();
+
+ @SuppressLint("Recycle")
+ FileWriter jsonFileWriter =
+ new FileWriter(
+ Objects.requireNonNull(context.getApplicationContext()
+ .getContentResolver()
+ .openFileDescriptor(uri, "w"))
+ .getFileDescriptor()
+ );
+ PrintWriter printWriter = new PrintWriter(jsonFileWriter);
+ printWriter.write(Setting.exportToJson(null));
+ printWriter.close();
+ jsonFileWriter.close();
+
+ showToastShort(str("revanced_extended_settings_export_success"));
+ } catch (IOException e) {
+ showToastShort(str("revanced_extended_settings_export_failed"));
+ }
+ }
+
+ private void importText(Uri uri) {
+ final Context context = this.getContext();
+ StringBuilder sb = new StringBuilder();
+ String line;
+
+ try {
+ @SuppressLint("Recycle")
+ FileReader fileReader =
+ new FileReader(
+ Objects.requireNonNull(context.getApplicationContext()
+ .getContentResolver()
+ .openFileDescriptor(uri, "r"))
+ .getFileDescriptor()
+ );
+ BufferedReader bufferedReader = new BufferedReader(fileReader);
+ while ((line = bufferedReader.readLine()) != null) {
+ sb.append(line).append("\n");
+ }
+ bufferedReader.close();
+ fileReader.close();
+
+ final boolean restartNeeded = Setting.importFromJSON(context, sb.toString());
+ if (restartNeeded) {
+ ReVancedPreferenceFragment.showRebootDialog();
+ }
+ } catch (IOException e) {
+ showToastShort(str("revanced_extended_settings_import_failed"));
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void importSettings(Activity mActivity, String replacementSettings) {
+ try {
+ existingSettings = Setting.exportToJson(null);
+ if (replacementSettings.equals(existingSettings)) {
+ return;
+ }
+ final boolean restartNeeded = Setting.importFromJSON(mActivity, replacementSettings);
+ if (restartNeeded) {
+ ReVancedPreferenceFragment.showRebootDialog();
+ }
+ } catch (Exception ex) {
+ Logger.printException(() -> "importSettings failure", ex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ResettableEditTextPreference.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ResettableEditTextPreference.java
new file mode 100644
index 000000000..d94bfab37
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ResettableEditTextPreference.java
@@ -0,0 +1,50 @@
+package app.revanced.extension.music.settings.preference;
+
+import static app.revanced.extension.music.utils.ExtendedUtils.getDialogBuilder;
+import static app.revanced.extension.music.utils.ExtendedUtils.getLayoutParams;
+import static app.revanced.extension.shared.utils.StringRef.str;
+
+import android.app.Activity;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.material.textfield.TextInputLayout;
+
+import app.revanced.extension.shared.settings.Setting;
+import app.revanced.extension.shared.utils.Logger;
+
+public class ResettableEditTextPreference {
+
+ public static void showDialog(Activity mActivity, @NonNull Setting setting) {
+ try {
+ final EditText textView = new EditText(mActivity);
+ textView.setText(setting.get());
+
+ TextInputLayout textInputLayout = new TextInputLayout(mActivity);
+ textInputLayout.setLayoutParams(getLayoutParams());
+ textInputLayout.addView(textView);
+
+ FrameLayout container = new FrameLayout(mActivity);
+ container.addView(textInputLayout);
+
+ getDialogBuilder(mActivity)
+ .setTitle(str(setting.key + "_title"))
+ .setView(container)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setNeutralButton(str("revanced_extended_settings_reset"), (dialog, which) -> {
+ setting.resetToDefault();
+ ReVancedPreferenceFragment.showRebootDialog();
+ })
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ setting.save(textView.getText().toString().trim());
+ ReVancedPreferenceFragment.showRebootDialog();
+ })
+ .show();
+ } catch (Exception ex) {
+ Logger.printException(() -> "showDialog failure", ex);
+ }
+ }
+
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ResettableListPreference.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ResettableListPreference.java
new file mode 100644
index 000000000..b01f5bf2d
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ResettableListPreference.java
@@ -0,0 +1,81 @@
+package app.revanced.extension.music.settings.preference;
+
+import static app.revanced.extension.music.utils.ExtendedUtils.getDialogBuilder;
+import static app.revanced.extension.shared.utils.ResourceUtils.getStringArray;
+import static app.revanced.extension.shared.utils.StringRef.str;
+
+import android.app.Activity;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+
+import app.revanced.extension.shared.settings.EnumSetting;
+import app.revanced.extension.shared.settings.Setting;
+import app.revanced.extension.shared.utils.Logger;
+
+public class ResettableListPreference {
+ private static int mClickedDialogEntryIndex;
+
+ public static void showDialog(Activity mActivity, @NonNull Setting setting, int defaultIndex) {
+ try {
+ final String settingsKey = setting.key;
+
+ final String entryKey = settingsKey + "_entries";
+ final String entryValueKey = settingsKey + "_entry_values";
+ final String[] mEntries = getStringArray(entryKey);
+ final String[] mEntryValues = getStringArray(entryValueKey);
+
+ final int findIndex = Arrays.binarySearch(mEntryValues, setting.get());
+ mClickedDialogEntryIndex = findIndex >= 0 ? findIndex : defaultIndex;
+
+ getDialogBuilder(mActivity)
+ .setTitle(str(settingsKey + "_title"))
+ .setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
+ (dialog, id) -> mClickedDialogEntryIndex = id)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setNeutralButton(str("revanced_extended_settings_reset"), (dialog, which) -> {
+ setting.resetToDefault();
+ ReVancedPreferenceFragment.showRebootDialog();
+ })
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ setting.save(mEntryValues[mClickedDialogEntryIndex]);
+ ReVancedPreferenceFragment.showRebootDialog();
+ })
+ .show();
+ } catch (Exception ex) {
+ Logger.printException(() -> "showDialog failure", ex);
+ }
+ }
+
+ public static void showDialog(Activity mActivity, @NonNull EnumSetting> setting, int defaultIndex) {
+ try {
+ final String settingsKey = setting.key;
+
+ final String entryKey = settingsKey + "_entries";
+ final String entryValueKey = settingsKey + "_entry_values";
+ final String[] mEntries = getStringArray(entryKey);
+ final String[] mEntryValues = getStringArray(entryValueKey);
+
+ final int findIndex = Arrays.binarySearch(mEntryValues, setting.get().toString());
+ mClickedDialogEntryIndex = findIndex >= 0 ? findIndex : defaultIndex;
+
+ getDialogBuilder(mActivity)
+ .setTitle(str(settingsKey + "_title"))
+ .setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
+ (dialog, id) -> mClickedDialogEntryIndex = id)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setNeutralButton(str("revanced_extended_settings_reset"), (dialog, which) -> {
+ setting.resetToDefault();
+ ReVancedPreferenceFragment.showRebootDialog();
+ })
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ setting.saveValueFromString(mEntryValues[mClickedDialogEntryIndex]);
+ ReVancedPreferenceFragment.showRebootDialog();
+ })
+ .show();
+ } catch (Exception ex) {
+ Logger.printException(() -> "showDialog failure", ex);
+ }
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/SponsorBlockApiUrlPreference.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/SponsorBlockApiUrlPreference.java
new file mode 100644
index 000000000..9b6c9a1a7
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/SponsorBlockApiUrlPreference.java
@@ -0,0 +1,70 @@
+package app.revanced.extension.music.settings.preference;
+
+import static app.revanced.extension.music.utils.ExtendedUtils.getDialogBuilder;
+import static app.revanced.extension.music.utils.ExtendedUtils.getLayoutParams;
+import static app.revanced.extension.shared.utils.StringRef.str;
+
+import android.app.Activity;
+import android.util.Patterns;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.material.textfield.TextInputLayout;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.settings.StringSetting;
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.Utils;
+
+public class SponsorBlockApiUrlPreference {
+
+ public static void showDialog(Activity mActivity) {
+ try {
+ final StringSetting apiUrl = Settings.SB_API_URL;
+
+ final EditText textView = new EditText(mActivity);
+ textView.setText(apiUrl.get());
+
+ TextInputLayout textInputLayout = new TextInputLayout(mActivity);
+ textInputLayout.setLayoutParams(getLayoutParams());
+ textInputLayout.addView(textView);
+
+ FrameLayout container = new FrameLayout(mActivity);
+ container.addView(textInputLayout);
+
+ getDialogBuilder(mActivity)
+ .setTitle(str("revanced_sb_api_url"))
+ .setView(container)
+ .setNegativeButton(android.R.string.cancel, null)
+ .setNeutralButton(str("revanced_extended_settings_reset"), (dialog, which) -> {
+ apiUrl.resetToDefault();
+ Utils.showToastShort(str("revanced_sb_api_url_reset"));
+ })
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ String serverAddress = textView.getText().toString().trim();
+ if (!isValidSBServerAddress(serverAddress)) {
+ Utils.showToastShort(str("revanced_sb_api_url_invalid"));
+ } else if (!serverAddress.equals(Settings.SB_API_URL.get())) {
+ apiUrl.save(serverAddress);
+ Utils.showToastShort(str("revanced_sb_api_url_changed"));
+ }
+ })
+ .show();
+ } catch (Exception ex) {
+ Logger.printException(() -> "showDialog failure", ex);
+ }
+ }
+
+ public static boolean isValidSBServerAddress(@NonNull String serverAddress) {
+ if (!Patterns.WEB_URL.matcher(serverAddress).matches()) {
+ return false;
+ }
+ // Verify url is only the server address and does not contain a path such as: "https://sponsor.ajay.app/api/"
+ // Could use Patterns.compile, but this is simpler
+ final int lastDotIndex = serverAddress.lastIndexOf('.');
+ return lastDotIndex == -1 || !serverAddress.substring(lastDotIndex).contains("/");
+ }
+
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/SponsorBlockCategoryPreference.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/SponsorBlockCategoryPreference.java
new file mode 100644
index 000000000..14dda2c78
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/SponsorBlockCategoryPreference.java
@@ -0,0 +1,124 @@
+package app.revanced.extension.music.settings.preference;
+
+import static app.revanced.extension.music.utils.ExtendedUtils.getDialogBuilder;
+import static app.revanced.extension.shared.utils.StringRef.str;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.graphics.Color;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+import app.revanced.extension.music.sponsorblock.objects.CategoryBehaviour;
+import app.revanced.extension.music.sponsorblock.objects.SegmentCategory;
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.Utils;
+
+public class SponsorBlockCategoryPreference {
+ private static final String[] CategoryBehaviourEntries = {str("revanced_sb_skip_automatically"), str("revanced_sb_skip_ignore")};
+ private static final CategoryBehaviour[] CategoryBehaviourEntryValues = {CategoryBehaviour.SKIP_AUTOMATICALLY, CategoryBehaviour.IGNORE};
+ private static int mClickedDialogEntryIndex;
+
+
+ public static void showDialog(Activity baseActivity, String categoryString) {
+ try {
+ SegmentCategory category = Objects.requireNonNull(SegmentCategory.byCategoryKey(categoryString));
+ final AlertDialog.Builder builder = getDialogBuilder(baseActivity);
+ TableLayout table = new TableLayout(baseActivity);
+ table.setOrientation(LinearLayout.HORIZONTAL);
+ table.setPadding(70, 0, 150, 0);
+
+ TableRow row = new TableRow(baseActivity);
+
+ TextView colorTextLabel = new TextView(baseActivity);
+ colorTextLabel.setText(str("revanced_sb_color_dot_label"));
+ row.addView(colorTextLabel);
+
+ TextView colorDotView = new TextView(baseActivity);
+ colorDotView.setText(category.getCategoryColorDot());
+ colorDotView.setPadding(30, 0, 30, 0);
+ row.addView(colorDotView);
+
+ final EditText mEditText = new EditText(baseActivity);
+ mEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
+ mEditText.setText(category.colorString());
+ mEditText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ try {
+ String colorString = s.toString();
+ if (!colorString.startsWith("#")) {
+ s.insert(0, "#"); // recursively calls back into this method
+ return;
+ }
+ if (colorString.length() > 7) {
+ s.delete(7, colorString.length());
+ return;
+ }
+ final int color = Color.parseColor(colorString);
+ colorDotView.setText(SegmentCategory.getCategoryColorDot(color));
+ } catch (IllegalArgumentException ex) {
+ // ignore
+ }
+ }
+ });
+ mEditText.setLayoutParams(new TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 1f));
+ row.addView(mEditText);
+
+ table.addView(row);
+ builder.setView(table);
+ builder.setTitle(category.title.toString());
+
+ builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ category.behaviour = CategoryBehaviourEntryValues[mClickedDialogEntryIndex];
+ category.setBehaviour(category.behaviour);
+ SegmentCategory.updateEnabledCategories();
+
+ String colorString = mEditText.getText().toString();
+ try {
+ if (!colorString.equals(category.colorString())) {
+ category.setColor(colorString);
+ Utils.showToastShort(str("revanced_sb_color_changed"));
+ }
+ } catch (IllegalArgumentException ex) {
+ Utils.showToastShort(str("revanced_sb_color_invalid"));
+ }
+ });
+ builder.setNeutralButton(str("revanced_sb_reset_color"), (dialog, which) -> {
+ try {
+ category.resetColor();
+ Utils.showToastShort(str("revanced_sb_color_reset"));
+ } catch (Exception ex) {
+ Logger.printException(() -> "setNeutralButton failure", ex);
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+
+ final int index = Arrays.asList(CategoryBehaviourEntryValues).indexOf(category.behaviour);
+ mClickedDialogEntryIndex = Math.max(index, 0);
+
+ builder.setSingleChoiceItems(CategoryBehaviourEntries, mClickedDialogEntryIndex,
+ (dialog, id) -> mClickedDialogEntryIndex = id);
+ builder.show();
+ } catch (Exception ex) {
+ Logger.printException(() -> "dialogBuilder failure", ex);
+ }
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/shared/PlayerType.kt b/extensions/shared/src/main/java/app/revanced/extension/music/shared/PlayerType.kt
new file mode 100644
index 000000000..5ca6ba944
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/shared/PlayerType.kt
@@ -0,0 +1,54 @@
+package app.revanced.extension.music.shared
+
+import app.revanced.extension.shared.utils.Event
+import app.revanced.extension.shared.utils.Logger
+
+/**
+ * WatchWhile player type
+ */
+enum class PlayerType {
+ DISMISSED,
+ MINIMIZED,
+ MAXIMIZED_NOW_PLAYING,
+ MAXIMIZED_PLAYER_ADDITIONAL_VIEW,
+ FULLSCREEN,
+ SLIDING_VERTICALLY,
+ QUEUE_EXPANDING,
+ SLIDING_HORIZONTALLY;
+
+ companion object {
+
+ private val nameToPlayerType = values().associateBy { it.name }
+
+ @JvmStatic
+ fun setFromString(enumName: String) {
+ val newType = nameToPlayerType[enumName]
+ if (newType == null) {
+ Logger.printException { "Unknown PlayerType encountered: $enumName" }
+ } else if (current != newType) {
+ Logger.printDebug { "PlayerType changed to: $newType" }
+ current = newType
+ }
+ }
+
+ /**
+ * The current player type.
+ */
+ @JvmStatic
+ var current
+ get() = currentPlayerType
+ private set(value) {
+ currentPlayerType = value
+ onChange(currentPlayerType)
+ }
+
+ @Volatile // value is read/write from different threads
+ private var currentPlayerType = MINIMIZED
+
+ /**
+ * player type change listener
+ */
+ @JvmStatic
+ val onChange = Event()
+ }
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/shared/VideoInformation.java b/extensions/shared/src/main/java/app/revanced/extension/music/shared/VideoInformation.java
new file mode 100644
index 000000000..12ce65258
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/shared/VideoInformation.java
@@ -0,0 +1,319 @@
+package app.revanced.extension.music.shared;
+
+import static app.revanced.extension.shared.utils.ResourceUtils.getString;
+import static app.revanced.extension.shared.utils.Utils.getFormattedTimeStamp;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.Utils;
+
+/**
+ * Hooking class for the current playing video.
+ */
+@SuppressWarnings("unused")
+public final class VideoInformation {
+ private static final float DEFAULT_YOUTUBE_MUSIC_PLAYBACK_SPEED = 1.0f;
+ private static final int DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY = -2;
+ private static final String DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY_STRING = getString("quality_auto");
+ @NonNull
+ private static String videoId = "";
+
+ private static long videoLength = 0;
+ private static long videoTime = -1;
+
+ /**
+ * The current playback speed
+ */
+ private static float playbackSpeed = DEFAULT_YOUTUBE_MUSIC_PLAYBACK_SPEED;
+ /**
+ * The current video quality
+ */
+ private static int videoQuality = DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY;
+ /**
+ * The current video quality string
+ */
+ private static String videoQualityString = DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY_STRING;
+ /**
+ * The available qualities of the current video in human readable form: [1080, 720, 480]
+ */
+ @Nullable
+ private static List videoQualities;
+
+ /**
+ * Injection point.
+ */
+ public static void initialize() {
+ videoTime = -1;
+ videoLength = 0;
+ Logger.printDebug(() -> "Initialized Player");
+ }
+
+ /**
+ * Injection point.
+ */
+ public static void initializeMdx() {
+ Logger.printDebug(() -> "Initialized Mdx Player");
+ }
+
+ /**
+ * Id of the current video playing. Includes Shorts and YouTube Stories.
+ *
+ * @return The id of the video. Empty string if not set yet.
+ */
+ @NonNull
+ public static String getVideoId() {
+ return videoId;
+ }
+
+ /**
+ * Injection point.
+ *
+ * @param newlyLoadedVideoId id of the current video
+ */
+ public static void setVideoId(@NonNull String newlyLoadedVideoId) {
+ if (Objects.equals(newlyLoadedVideoId, videoId)) {
+ return;
+ }
+ Logger.printDebug(() -> "New video id: " + newlyLoadedVideoId);
+ videoId = newlyLoadedVideoId;
+ }
+
+ /**
+ * Seek on the current video.
+ * Does not function for playback of Shorts.
+ *
+ * Caution: If called from a videoTimeHook() callback,
+ * this will cause a recursive call into the same videoTimeHook() callback.
+ *
+ * @param seekTime The millisecond to seek the video to.
+ * @return if the seek was successful
+ */
+ public static boolean seekTo(final long seekTime) {
+ Utils.verifyOnMainThread();
+ try {
+ final long videoLength = getVideoLength();
+ final long videoTime = getVideoTime();
+ final long adjustedSeekTime = getAdjustedSeekTime(seekTime, videoLength);
+
+ if (videoTime <= 0 || videoLength <= 0) {
+ Logger.printDebug(() -> "Skipping seekTo as the video is not initialized");
+ return false;
+ }
+
+ Logger.printDebug(() -> "Seeking to: " + getFormattedTimeStamp(adjustedSeekTime));
+
+ // Try regular playback controller first, and it will not succeed if casting.
+ if (overrideVideoTime(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.
+ if (adjustedSeekTime / 1000 == videoTime / 1000) {
+ Logger.printDebug(() -> "Skipping seekTo for MDX because seek time is too small "
+ + "(" + (adjustedSeekTime - videoTime) + "ms)");
+ return false;
+ }
+
+ return overrideMDXVideoTime(adjustedSeekTime);
+ } catch (Exception ex) {
+ Logger.printException(() -> "Failed to seek", ex);
+ return false;
+ }
+ }
+
+ // Prevent issues such as play/pause button or autoplay not working.
+ private static long getAdjustedSeekTime(final long seekTime, final long videoLength) {
+ // If the user skips to a section that is 500 ms before the video length,
+ // it will get stuck in a loop.
+ if (videoLength - seekTime > 500) {
+ return seekTime;
+ } else {
+ // Otherwise, just skips to a time longer than the video length.
+ // Paradoxically, if user skips to a section much longer than the video length, does not get stuck in a loop.
+ return Integer.MAX_VALUE;
+ }
+ }
+
+ /**
+ * @return The current playback speed.
+ */
+ public static float getPlaybackSpeed() {
+ return playbackSpeed;
+ }
+
+ /**
+ * Injection point.
+ *
+ * @param newlyLoadedPlaybackSpeed The current playback speed.
+ */
+ public static void setPlaybackSpeed(float newlyLoadedPlaybackSpeed) {
+ playbackSpeed = newlyLoadedPlaybackSpeed;
+ }
+
+ /**
+ * @return The current video quality.
+ */
+ public static int getVideoQuality() {
+ return videoQuality;
+ }
+
+ /**
+ * @return The current video quality string.
+ */
+ public static String getVideoQualityString() {
+ return videoQualityString;
+ }
+
+ /**
+ * Injection point.
+ *
+ * @param newlyLoadedQuality The current video quality string.
+ */
+ public static void setVideoQuality(String newlyLoadedQuality) {
+ if (newlyLoadedQuality == null) {
+ return;
+ }
+ try {
+ String splitVideoQuality;
+ if (newlyLoadedQuality.contains("p")) {
+ splitVideoQuality = newlyLoadedQuality.split("p")[0];
+ videoQuality = Integer.parseInt(splitVideoQuality);
+ videoQualityString = splitVideoQuality + "p";
+ } else if (newlyLoadedQuality.contains("s")) {
+ splitVideoQuality = newlyLoadedQuality.split("s")[0];
+ videoQuality = Integer.parseInt(splitVideoQuality);
+ videoQualityString = splitVideoQuality + "s";
+ } else {
+ videoQuality = DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY;
+ videoQualityString = DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY_STRING;
+ }
+ } catch (NumberFormatException ignored) {
+ }
+ }
+
+ /**
+ * @return available video quality.
+ */
+ public static int getAvailableVideoQuality(int preferredQuality) {
+ if (videoQualities != null) {
+ int qualityToUse = videoQualities.get(0); // first element is automatic mode
+ for (Integer quality : videoQualities) {
+ if (quality <= preferredQuality && qualityToUse < quality) {
+ qualityToUse = quality;
+ }
+ }
+ preferredQuality = qualityToUse;
+ }
+ return preferredQuality;
+ }
+
+ /**
+ * Injection point.
+ *
+ * @param qualities Video qualities available, ordered from largest to smallest, with index 0 being the 'automatic' value of -2
+ */
+ public static void setVideoQualityList(Object[] qualities) {
+ try {
+ if (videoQualities == null || videoQualities.size() != qualities.length) {
+ videoQualities = new ArrayList<>(qualities.length);
+ for (Object streamQuality : qualities) {
+ for (Field field : streamQuality.getClass().getFields()) {
+ if (field.getType().isAssignableFrom(Integer.TYPE)
+ && field.getName().length() <= 2) {
+ videoQualities.add(field.getInt(streamQuality));
+ }
+ }
+ }
+ Logger.printDebug(() -> "videoQualities: " + videoQualities);
+ }
+ } catch (Exception ex) {
+ Logger.printException(() -> "Failed to set quality list", ex);
+ }
+ }
+
+ /**
+ * Length of the current video playing. Includes Shorts.
+ *
+ * @return The length of the video in milliseconds.
+ * If the video is not yet loaded, or if the video is playing in the background with no video visible,
+ * then this returns zero.
+ */
+ public static long getVideoLength() {
+ return videoLength;
+ }
+
+ /**
+ * Injection point.
+ *
+ * @param length The length of the video in milliseconds.
+ */
+ public static void setVideoLength(final long length) {
+ if (videoLength != length) {
+ videoLength = length;
+ }
+ }
+
+ /**
+ * Playback time of the current video playing. Includes Shorts.
+ *
+ * Value will lag behind the actual playback time by a variable amount based on the playback speed.
+ *
+ * If playback speed is 2.0x, this value may be up to 2000ms behind the actual playback time.
+ * If playback speed is 1.0x, this value may be up to 1000ms behind the actual playback time.
+ * If playback speed is 0.5x, this value may be up to 500ms behind the actual playback time.
+ * Etc.
+ *
+ * @return The time of the video in milliseconds. -1 if not set yet.
+ */
+ public static long getVideoTime() {
+ return videoTime;
+ }
+
+ /**
+ * Injection point.
+ * Called on the main thread every 1000ms.
+ *
+ * @param currentPlaybackTime The current playback time of the video in milliseconds.
+ */
+ public static void setVideoTime(final long currentPlaybackTime) {
+ videoTime = currentPlaybackTime;
+ }
+
+ /**
+ * Overrides the current quality.
+ * Rest of the implementation added by patch.
+ */
+ public static void overrideVideoQuality(int qualityOverride) {
+ Logger.printDebug(() -> "Overriding video quality to: " + qualityOverride);
+ }
+
+ /**
+ * Overrides the current video time by seeking.
+ * Rest of the implementation added by patch.
+ */
+ public static boolean overrideVideoTime(final long seekTime) {
+ // These instructions are ignored by patch.
+ Logger.printDebug(() -> "Seeking to " + seekTime);
+ return false;
+ }
+
+ /**
+ * Overrides the current video time by seeking. (MDX player)
+ * Rest of the implementation added by patch.
+ */
+ public static boolean overrideMDXVideoTime(final long seekTime) {
+ // These instructions are ignored by patch.
+ Logger.printDebug(() -> "Seeking to " + seekTime);
+ return false;
+ }
+
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/shared/VideoType.kt b/extensions/shared/src/main/java/app/revanced/extension/music/shared/VideoType.kt
new file mode 100644
index 000000000..87711a27e
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/shared/VideoType.kt
@@ -0,0 +1,63 @@
+package app.revanced.extension.music.shared
+
+import app.revanced.extension.shared.utils.Event
+import app.revanced.extension.shared.utils.Logger
+
+/**
+ * Music video type
+ */
+enum class VideoType {
+ MUSIC_VIDEO_TYPE_UNKNOWN,
+ MUSIC_VIDEO_TYPE_ATV,
+ MUSIC_VIDEO_TYPE_OMV,
+ MUSIC_VIDEO_TYPE_UGC,
+ MUSIC_VIDEO_TYPE_SHOULDER,
+ MUSIC_VIDEO_TYPE_OFFICIAL_SOURCE_MUSIC,
+ MUSIC_VIDEO_TYPE_PRIVATELY_OWNED_TRACK,
+ MUSIC_VIDEO_TYPE_LIVE_STREAM,
+ MUSIC_VIDEO_TYPE_PODCAST_EPISODE;
+
+ companion object {
+
+ private val nameToVideoType = values().associateBy { it.name }
+
+ @JvmStatic
+ fun setFromString(enumName: String) {
+ val newType = nameToVideoType[enumName]
+ if (newType == null) {
+ Logger.printException { "Unknown VideoType encountered: $enumName" }
+ } else if (current != newType) {
+ Logger.printDebug { "VideoType changed to: $newType" }
+ current = newType
+ }
+ }
+
+ /**
+ * The current video type.
+ */
+ @JvmStatic
+ var current
+ get() = currentVideoType
+ private set(value) {
+ currentVideoType = value
+ onChange(currentVideoType)
+ }
+
+ @Volatile // value is read/write from different threads
+ private var currentVideoType = MUSIC_VIDEO_TYPE_UNKNOWN
+
+ /**
+ * player type change listener
+ */
+ @JvmStatic
+ val onChange = Event()
+ }
+
+ fun isMusicVideo(): Boolean {
+ return this == MUSIC_VIDEO_TYPE_OMV
+ }
+
+ fun isPodCast(): Boolean {
+ return this == MUSIC_VIDEO_TYPE_PODCAST_EPISODE
+ }
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/SegmentPlaybackController.java b/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/SegmentPlaybackController.java
new file mode 100644
index 000000000..948a8a92e
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/SegmentPlaybackController.java
@@ -0,0 +1,472 @@
+package app.revanced.extension.music.sponsorblock;
+
+import static app.revanced.extension.shared.utils.StringRef.str;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Objects;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.music.shared.VideoInformation;
+import app.revanced.extension.music.sponsorblock.objects.CategoryBehaviour;
+import app.revanced.extension.music.sponsorblock.objects.SponsorSegment;
+import app.revanced.extension.music.sponsorblock.requests.SBRequester;
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.Utils;
+
+/**
+ * Handles showing, scheduling, and skipping of all {@link SponsorSegment} for the current video.
+ *
+ * Class is not thread safe. All methods must be called on the main thread unless otherwise specified.
+ */
+@SuppressWarnings("unused")
+public class SegmentPlaybackController {
+ @Nullable
+ private static String currentVideoId;
+ @Nullable
+ private static SponsorSegment[] segments;
+ /**
+ * Currently playing (non-highlight) segment that user can manually skip.
+ */
+ @Nullable
+ private static SponsorSegment segmentCurrentlyPlaying;
+ /**
+ * Currently playing manual skip segment that is scheduled to hide.
+ * This will always be NULL or equal to {@link #segmentCurrentlyPlaying}.
+ */
+ @Nullable
+ private static SponsorSegment scheduledHideSegment;
+ /**
+ * Upcoming segment that is scheduled to either autoskip or show the manual skip button.
+ */
+ @Nullable
+ private static SponsorSegment scheduledUpcomingSegment;
+ /**
+ * System time (in milliseconds) of when to hide the skip button of {@link #segmentCurrentlyPlaying}.
+ * Value is zero if playback is not inside a segment ({@link #segmentCurrentlyPlaying} is null),
+ */
+ private static long skipSegmentButtonEndTime;
+
+ private static int sponsorBarAbsoluteLeft;
+ private static int sponsorAbsoluteBarRight;
+ private static int sponsorBarThickness = 7;
+ private static SponsorSegment lastSegmentSkipped;
+ private static long lastSegmentSkippedTime;
+ private static int toastNumberOfSegmentsSkipped;
+ @Nullable
+ private static SponsorSegment toastSegmentSkipped;
+
+ private static void setSegments(@NonNull SponsorSegment[] videoSegments) {
+ Arrays.sort(videoSegments);
+ segments = videoSegments;
+ }
+
+ /**
+ * Clears all downloaded data.
+ */
+ private static void clearData() {
+ SponsorBlockSettings.initialize();
+ currentVideoId = null;
+ segments = null;
+ segmentCurrentlyPlaying = null;
+ scheduledUpcomingSegment = null;
+ scheduledHideSegment = null;
+ skipSegmentButtonEndTime = 0;
+ toastSegmentSkipped = null;
+ toastNumberOfSegmentsSkipped = 0;
+ }
+
+ /**
+ * Injection point.
+ */
+ public static void setVideoId(@NonNull String videoId) {
+ try {
+ if (Objects.equals(currentVideoId, videoId)) {
+ return;
+ }
+ clearData();
+ if (!Settings.SB_ENABLED.get()) {
+ return;
+ }
+ if (Utils.isNetworkNotConnected()) {
+ Logger.printDebug(() -> "Network not connected, ignoring video");
+ return;
+ }
+
+ currentVideoId = videoId;
+ Logger.printDebug(() -> "setCurrentVideoId: " + videoId);
+
+ Utils.runOnBackgroundThread(() -> {
+ try {
+ executeDownloadSegments(videoId);
+ } catch (Exception e) {
+ Logger.printException(() -> "Failed to download segments", e);
+ }
+ });
+ } catch (Exception ex) {
+ Logger.printException(() -> "setCurrentVideoId failure", ex);
+ }
+ }
+
+ /**
+ * Must be called off main thread
+ */
+ static void executeDownloadSegments(@NonNull String videoId) {
+ Objects.requireNonNull(videoId);
+ try {
+ SponsorSegment[] segments = SBRequester.getSegments(videoId);
+
+ Utils.runOnMainThread(() -> {
+ if (!videoId.equals(currentVideoId)) {
+ // user changed videos before get segments network call could complete
+ Logger.printDebug(() -> "Ignoring segments for prior video: " + videoId);
+ return;
+ }
+ setSegments(segments);
+
+ // check for any skips now, instead of waiting for the next update to setVideoTime()
+ setVideoTime(VideoInformation.getVideoTime());
+ });
+ } catch (Exception ex) {
+ Logger.printException(() -> "executeDownloadSegments failure", ex);
+ }
+ }
+
+ /**
+ * Injection point.
+ * Updates SponsorBlock every 1000ms.
+ * When changing videos, this is first called with value 0 and then the video is changed.
+ */
+ public static void setVideoTime(long millis) {
+ try {
+ if (!Settings.SB_ENABLED.get() || segments == null || segments.length == 0) {
+ return;
+ }
+ Logger.printDebug(() -> "setVideoTime: " + millis);
+
+ final float playbackSpeed = VideoInformation.getPlaybackSpeed();
+ // Amount of time to look ahead for the next segment,
+ // and the threshold to determine if a scheduled show/hide is at the correct video time when it's run.
+ //
+ // This value must be greater than largest time between calls to this method (1000ms),
+ // and must be adjusted for the video speed.
+ //
+ // To debug the stale skip logic, set this to a very large value (5000 or more)
+ // then try manually seeking just before playback reaches a segment skip.
+ final long speedAdjustedTimeThreshold = (long) (playbackSpeed * 1200);
+ final long startTimerLookAheadThreshold = millis + speedAdjustedTimeThreshold;
+
+ SponsorSegment foundSegmentCurrentlyPlaying = null;
+ SponsorSegment foundUpcomingSegment = null;
+
+ for (final SponsorSegment segment : segments) {
+ if (segment.category.behaviour == CategoryBehaviour.IGNORE) {
+ continue;
+ }
+ if (segment.end <= millis) {
+ continue; // past this segment
+ }
+
+ if (segment.start <= millis) {
+ // we are in the segment!
+ if (segment.shouldAutoSkip()) {
+ skipSegment(segment);
+ return; // must return, as skipping causes a recursive call back into this method
+ }
+
+ // first found segment, or it's an embedded segment and fully inside the outer segment
+ if (foundSegmentCurrentlyPlaying == null || foundSegmentCurrentlyPlaying.containsSegment(segment)) {
+ // If the found segment is not currently displayed, then do not show if the segment is nearly over.
+ // This check prevents the skip button text from rapidly changing when multiple segments end at nearly the same time.
+ // Also prevents showing the skip button if user seeks into the last 800ms of the segment.
+ final long minMillisOfSegmentRemainingThreshold = 800;
+ if (segmentCurrentlyPlaying == segment
+ || !segment.endIsNear(millis, minMillisOfSegmentRemainingThreshold)) {
+ foundSegmentCurrentlyPlaying = segment;
+ } else {
+ Logger.printDebug(() -> "Ignoring segment that ends very soon: " + segment);
+ }
+ }
+ // Keep iterating and looking. There may be an upcoming autoskip,
+ // or there may be another smaller segment nested inside this segment
+ continue;
+ }
+
+ // segment is upcoming
+ if (startTimerLookAheadThreshold < segment.start) {
+ break; // segment is not close enough to schedule, and no segments after this are of interest
+ }
+ if (segment.shouldAutoSkip()) { // upcoming autoskip
+ foundUpcomingSegment = segment;
+ break; // must stop here
+ }
+
+ // upcoming manual skip
+
+ // do not schedule upcoming segment, if it is not fully contained inside the current segment
+ if ((foundSegmentCurrentlyPlaying == null || foundSegmentCurrentlyPlaying.containsSegment(segment))
+ // use the most inner upcoming segment
+ && (foundUpcomingSegment == null || foundUpcomingSegment.containsSegment(segment))) {
+
+ // Only schedule, if the segment start time is not near the end time of the current segment.
+ // This check is needed to prevent scheduled hide and show from clashing with each other.
+ // Instead the upcoming segment will be handled when the current segment scheduled hide calls back into this method.
+ final long minTimeBetweenStartEndOfSegments = 1000;
+ if (foundSegmentCurrentlyPlaying == null
+ || !foundSegmentCurrentlyPlaying.endIsNear(segment.start, minTimeBetweenStartEndOfSegments)) {
+ foundUpcomingSegment = segment;
+ } else {
+ Logger.printDebug(() -> "Not scheduling segment (start time is near end of current segment): " + segment);
+ }
+ }
+ }
+
+ if (segmentCurrentlyPlaying != foundSegmentCurrentlyPlaying) {
+ setSegmentCurrentlyPlaying(foundSegmentCurrentlyPlaying);
+ } else if (foundSegmentCurrentlyPlaying != null
+ && skipSegmentButtonEndTime != 0 && skipSegmentButtonEndTime <= System.currentTimeMillis()) {
+ Logger.printDebug(() -> "Auto hiding skip button for segment: " + segmentCurrentlyPlaying);
+ skipSegmentButtonEndTime = 0;
+ }
+
+ // schedule a hide, only if the segment end is near
+ final SponsorSegment segmentToHide =
+ (foundSegmentCurrentlyPlaying != null && foundSegmentCurrentlyPlaying.endIsNear(millis, speedAdjustedTimeThreshold))
+ ? foundSegmentCurrentlyPlaying
+ : null;
+
+ if (scheduledHideSegment != segmentToHide) {
+ if (segmentToHide == null) {
+ Logger.printDebug(() -> "Clearing scheduled hide: " + scheduledHideSegment);
+ scheduledHideSegment = null;
+ } else {
+ scheduledHideSegment = segmentToHide;
+ Logger.printDebug(() -> "Scheduling hide segment: " + segmentToHide + " playbackSpeed: " + playbackSpeed);
+ final long delayUntilHide = (long) ((segmentToHide.end - millis) / playbackSpeed);
+ Utils.runOnMainThreadDelayed(() -> {
+ if (scheduledHideSegment != segmentToHide) {
+ Logger.printDebug(() -> "Ignoring old scheduled hide segment: " + segmentToHide);
+ return;
+ }
+ scheduledHideSegment = null;
+
+ final long videoTime = VideoInformation.getVideoTime();
+ if (!segmentToHide.endIsNear(videoTime, speedAdjustedTimeThreshold)) {
+ // current video time is not what's expected. User paused playback
+ Logger.printDebug(() -> "Ignoring outdated scheduled hide: " + segmentToHide
+ + " videoInformation time: " + videoTime);
+ return;
+ }
+ Logger.printDebug(() -> "Running scheduled hide segment: " + segmentToHide);
+ // Need more than just hide the skip button, as this may have been an embedded segment
+ // Instead call back into setVideoTime to check everything again.
+ // Should not use VideoInformation time as it is less accurate,
+ // but this scheduled handler was scheduled precisely so we can just use the segment end time
+ setSegmentCurrentlyPlaying(null);
+ setVideoTime(segmentToHide.end);
+ }, delayUntilHide);
+ }
+ }
+
+ if (scheduledUpcomingSegment != foundUpcomingSegment) {
+ if (foundUpcomingSegment == null) {
+ Logger.printDebug(() -> "Clearing scheduled segment: " + scheduledUpcomingSegment);
+ scheduledUpcomingSegment = null;
+ } else {
+ scheduledUpcomingSegment = foundUpcomingSegment;
+ final SponsorSegment segmentToSkip = foundUpcomingSegment;
+
+ Logger.printDebug(() -> "Scheduling segment: " + segmentToSkip + " playbackSpeed: " + playbackSpeed);
+ final long delayUntilSkip = (long) ((segmentToSkip.start - millis) / playbackSpeed);
+ Utils.runOnMainThreadDelayed(() -> {
+ if (scheduledUpcomingSegment != segmentToSkip) {
+ Logger.printDebug(() -> "Ignoring old scheduled segment: " + segmentToSkip);
+ return;
+ }
+ scheduledUpcomingSegment = null;
+
+ final long videoTime = VideoInformation.getVideoTime();
+ if (!segmentToSkip.startIsNear(videoTime, speedAdjustedTimeThreshold)) {
+ // current video time is not what's expected. User paused playback
+ Logger.printDebug(() -> "Ignoring outdated scheduled segment: " + segmentToSkip
+ + " videoInformation time: " + videoTime);
+ return;
+ }
+ if (segmentToSkip.shouldAutoSkip()) {
+ Logger.printDebug(() -> "Running scheduled skip segment: " + segmentToSkip);
+ skipSegment(segmentToSkip);
+ } else {
+ Logger.printDebug(() -> "Running scheduled show segment: " + segmentToSkip);
+ setSegmentCurrentlyPlaying(segmentToSkip);
+ }
+ }, delayUntilSkip);
+ }
+ }
+ } catch (Exception e) {
+ Logger.printException(() -> "setVideoTime failure", e);
+ }
+ }
+
+ private static void setSegmentCurrentlyPlaying(@Nullable SponsorSegment segment) {
+ if (segment == null) {
+ if (segmentCurrentlyPlaying != null)
+ Logger.printDebug(() -> "Hiding segment: " + segmentCurrentlyPlaying);
+ segmentCurrentlyPlaying = null;
+ skipSegmentButtonEndTime = 0;
+ return;
+ }
+ segmentCurrentlyPlaying = segment;
+ skipSegmentButtonEndTime = 0;
+ Logger.printDebug(() -> "Showing segment: " + segment);
+ }
+
+ private static void skipSegment(@NonNull SponsorSegment segmentToSkip) {
+ try {
+ // If trying to seek to end of the video, YouTube can seek just before of the actual end.
+ // (especially if the video does not end on a whole second boundary).
+ // This causes additional segment skip attempts, even though it cannot seek any closer to the desired time.
+ // Check for and ignore repeated skip attempts of the same segment over a small time period.
+ final long now = System.currentTimeMillis();
+ final long minimumMillisecondsBetweenSkippingSameSegment = 500;
+ if ((lastSegmentSkipped == segmentToSkip) && (now - lastSegmentSkippedTime < minimumMillisecondsBetweenSkippingSameSegment)) {
+ Logger.printDebug(() -> "Ignoring skip segment request (already skipped as close as possible): " + segmentToSkip);
+ return;
+ }
+
+ Logger.printDebug(() -> "Skipping segment: " + segmentToSkip);
+ lastSegmentSkipped = segmentToSkip;
+ lastSegmentSkippedTime = now;
+ setSegmentCurrentlyPlaying(null);
+ scheduledHideSegment = null;
+ scheduledUpcomingSegment = null;
+
+ // If the seek is successful, then the seek causes a recursive call back into this class.
+ final boolean seekSuccessful = VideoInformation.seekTo(segmentToSkip.end);
+ if (!seekSuccessful) {
+ // can happen when switching videos and is normal
+ Logger.printDebug(() -> "Could not skip segment (seek unsuccessful): " + segmentToSkip);
+ return;
+ }
+
+ // check for any smaller embedded segments, and count those as autoskipped
+ final boolean showSkipToast = Settings.SB_TOAST_ON_SKIP.get();
+ for (final SponsorSegment otherSegment : Objects.requireNonNull(segments)) {
+ if (segmentToSkip.end < otherSegment.start) {
+ break; // no other segments can be contained
+ }
+ if (otherSegment == segmentToSkip ||
+ segmentToSkip.containsSegment(otherSegment)) {
+ otherSegment.didAutoSkipped = true;
+ // Do not show a toast if the user is scrubbing thru a paused video.
+ // Cannot do this video state check in setTime or earlier in this method, as the video state may not be up to date.
+ // So instead, only hide toasts because all other skip logic done while paused causes no harm.
+ if (showSkipToast) {
+ showSkippedSegmentToast(otherSegment);
+ }
+ }
+ }
+ } catch (Exception ex) {
+ Logger.printException(() -> "skipSegment failure", ex);
+ }
+ }
+
+ private static void showSkippedSegmentToast(@NonNull SponsorSegment segment) {
+ Utils.verifyOnMainThread();
+ toastNumberOfSegmentsSkipped++;
+ if (toastNumberOfSegmentsSkipped > 1) {
+ return; // toast already scheduled
+ }
+ toastSegmentSkipped = segment;
+
+ final long delayToToastMilliseconds = 250; // also the maximum time between skips to be considered skipping multiple segments
+ Utils.runOnMainThreadDelayed(() -> {
+ try {
+ if (toastSegmentSkipped == null) { // video was changed just after skipping segment
+ Logger.printDebug(() -> "Ignoring old scheduled show toast");
+ return;
+ }
+ Utils.showToastShort(toastNumberOfSegmentsSkipped == 1
+ ? toastSegmentSkipped.getSkippedToastText()
+ : str("revanced_sb_skipped_multiple_segments"));
+ } catch (Exception ex) {
+ Logger.printException(() -> "showSkippedSegmentToast failure", ex);
+ } finally {
+ toastNumberOfSegmentsSkipped = 0;
+ toastSegmentSkipped = null;
+ }
+ }, delayToToastMilliseconds);
+ }
+
+ /**
+ * Injection point
+ */
+ public static void setSponsorBarRect(final Object self, final String fieldName) {
+ try {
+ Field field = self.getClass().getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Rect rect = (Rect) Objects.requireNonNull(field.get(self));
+ setSponsorBarAbsoluteLeft(rect);
+ setSponsorBarAbsoluteRight(rect);
+ } catch (Exception ex) {
+ Logger.printException(() -> "setSponsorBarRect failure", ex);
+ }
+ }
+
+ private static void setSponsorBarAbsoluteLeft(Rect rect) {
+ final int left = rect.left;
+ if (sponsorBarAbsoluteLeft != left) {
+ Logger.printDebug(() -> "setSponsorBarAbsoluteLeft: " + left);
+ sponsorBarAbsoluteLeft = left;
+ }
+ }
+
+ private static void setSponsorBarAbsoluteRight(Rect rect) {
+ final int right = rect.right;
+ if (sponsorAbsoluteBarRight != right) {
+ Logger.printDebug(() -> "setSponsorBarAbsoluteRight: " + right);
+ sponsorAbsoluteBarRight = right;
+ }
+ }
+
+ /**
+ * Injection point
+ */
+ public static void setSponsorBarThickness(int thickness) {
+ if (sponsorBarThickness != thickness) {
+ sponsorBarThickness = (int) Math.round(thickness * 1.2);
+ Logger.printDebug(() -> "setSponsorBarThickness: " + sponsorBarThickness);
+ }
+ }
+
+ /**
+ * Injection point.
+ */
+ public static void drawSponsorTimeBars(final Canvas canvas, final float posY) {
+ try {
+ if (segments == null) return;
+ final long videoLength = VideoInformation.getVideoLength();
+ if (videoLength <= 0) return;
+
+ final int thicknessDiv2 = sponsorBarThickness / 2; // rounds down
+ final float top = posY - (sponsorBarThickness - thicknessDiv2);
+ final float bottom = posY + thicknessDiv2;
+ final float videoMillisecondsToPixels = (1f / videoLength) * (sponsorAbsoluteBarRight - sponsorBarAbsoluteLeft);
+ final float leftPadding = sponsorBarAbsoluteLeft;
+
+ for (SponsorSegment segment : segments) {
+ final float left = leftPadding + segment.start * videoMillisecondsToPixels;
+ final float right = leftPadding + segment.end * videoMillisecondsToPixels;
+ canvas.drawRect(left, top, right, bottom, segment.category.paint);
+ }
+ } catch (Exception ex) {
+ Logger.printException(() -> "drawSponsorTimeBars failure", ex);
+ }
+ }
+
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/SponsorBlockSettings.java b/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/SponsorBlockSettings.java
new file mode 100644
index 000000000..eef25d14d
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/SponsorBlockSettings.java
@@ -0,0 +1,60 @@
+package app.revanced.extension.music.sponsorblock;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.UUID;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.music.sponsorblock.objects.SegmentCategory;
+import app.revanced.extension.shared.settings.Setting;
+
+public class SponsorBlockSettings {
+
+ public static final Setting.ImportExportCallback SB_IMPORT_EXPORT_CALLBACK = new Setting.ImportExportCallback() {
+ @Override
+ public void settingsImported(@Nullable Context context) {
+ SegmentCategory.loadAllCategoriesFromSettings();
+ }
+
+ @Override
+ public void settingsExported(@Nullable Context context) {
+ }
+ };
+
+ private static boolean initialized;
+
+ /**
+ * @return if the user has ever voted, created a segment, or imported existing SB settings.
+ */
+ public static boolean userHasSBPrivateId() {
+ return !Settings.SB_PRIVATE_USER_ID.get().isEmpty();
+ }
+
+ /**
+ * Use this only if a user id is required (creating segments, voting).
+ */
+ @NonNull
+ public static String getSBPrivateUserID() {
+ String uuid = Settings.SB_PRIVATE_USER_ID.get();
+ if (uuid.isEmpty()) {
+ uuid = (UUID.randomUUID().toString() +
+ UUID.randomUUID().toString() +
+ UUID.randomUUID().toString())
+ .replace("-", "");
+ Settings.SB_PRIVATE_USER_ID.save(uuid);
+ }
+ return uuid;
+ }
+
+ public static void initialize() {
+ if (initialized) {
+ return;
+ }
+ initialized = true;
+
+ SegmentCategory.updateEnabledCategories();
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/objects/CategoryBehaviour.java b/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/objects/CategoryBehaviour.java
new file mode 100644
index 000000000..bba2334dc
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/objects/CategoryBehaviour.java
@@ -0,0 +1,49 @@
+package app.revanced.extension.music.sponsorblock.objects;
+
+import static app.revanced.extension.shared.utils.StringRef.sf;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
+import app.revanced.extension.shared.utils.StringRef;
+
+public enum CategoryBehaviour {
+ SKIP_AUTOMATICALLY("skip", 2, true, sf("revanced_sb_skip_automatically")),
+ // ignored categories are not exported to json, and ignore is the default behavior when importing
+ IGNORE("ignore", -1, false, sf("revanced_sb_skip_ignore"));
+
+ /**
+ * ReVanced specific value.
+ */
+ @NonNull
+ public final String reVancedKeyValue;
+ /**
+ * Desktop specific value.
+ */
+ public final int desktopKeyValue;
+ /**
+ * If the segment should skip automatically
+ */
+ public final boolean skipAutomatically;
+ @NonNull
+ public final StringRef description;
+
+ CategoryBehaviour(String reVancedKeyValue, int desktopKeyValue, boolean skipAutomatically, StringRef description) {
+ this.reVancedKeyValue = Objects.requireNonNull(reVancedKeyValue);
+ this.desktopKeyValue = desktopKeyValue;
+ this.skipAutomatically = skipAutomatically;
+ this.description = Objects.requireNonNull(description);
+ }
+
+ @Nullable
+ public static CategoryBehaviour byReVancedKeyValue(@NonNull String keyValue) {
+ for (CategoryBehaviour behaviour : values()) {
+ if (behaviour.reVancedKeyValue.equals(keyValue)) {
+ return behaviour;
+ }
+ }
+ return null;
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/objects/SegmentCategory.java b/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/objects/SegmentCategory.java
new file mode 100644
index 000000000..d20827e6f
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/objects/SegmentCategory.java
@@ -0,0 +1,293 @@
+package app.revanced.extension.music.sponsorblock.objects;
+
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_FILLER;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_FILLER_COLOR;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_INTERACTION;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_INTERACTION_COLOR;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_INTRO;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_INTRO_COLOR;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_MUSIC_OFFTOPIC;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_MUSIC_OFFTOPIC_COLOR;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_OUTRO;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_OUTRO_COLOR;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_PREVIEW;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_PREVIEW_COLOR;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_SELF_PROMO;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_SELF_PROMO_COLOR;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_SPONSOR;
+import static app.revanced.extension.music.settings.Settings.SB_CATEGORY_SPONSOR_COLOR;
+import static app.revanced.extension.shared.utils.StringRef.sf;
+
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.text.Html;
+import android.text.Spanned;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import app.revanced.extension.shared.settings.StringSetting;
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.StringRef;
+import app.revanced.extension.shared.utils.Utils;
+
+public enum SegmentCategory {
+ SPONSOR("sponsor", sf("revanced_sb_segments_sponsor"), sf("revanced_sb_segments_sponsor_sum"), sf("revanced_sb_skip_button_sponsor"), sf("revanced_sb_skipped_sponsor"),
+ SB_CATEGORY_SPONSOR, SB_CATEGORY_SPONSOR_COLOR),
+ SELF_PROMO("selfpromo", sf("revanced_sb_segments_selfpromo"), sf("revanced_sb_segments_selfpromo_sum"), sf("revanced_sb_skip_button_selfpromo"), sf("revanced_sb_skipped_selfpromo"),
+ SB_CATEGORY_SELF_PROMO, SB_CATEGORY_SELF_PROMO_COLOR),
+ INTERACTION("interaction", sf("revanced_sb_segments_interaction"), sf("revanced_sb_segments_interaction_sum"), sf("revanced_sb_skip_button_interaction"), sf("revanced_sb_skipped_interaction"),
+ SB_CATEGORY_INTERACTION, SB_CATEGORY_INTERACTION_COLOR),
+ INTRO("intro", sf("revanced_sb_segments_intro"), sf("revanced_sb_segments_intro_sum"),
+ sf("revanced_sb_skip_button_intro_beginning"), sf("revanced_sb_skip_button_intro_middle"), sf("revanced_sb_skip_button_intro_end"),
+ sf("revanced_sb_skipped_intro_beginning"), sf("revanced_sb_skipped_intro_middle"), sf("revanced_sb_skipped_intro_end"),
+ SB_CATEGORY_INTRO, SB_CATEGORY_INTRO_COLOR),
+ OUTRO("outro", sf("revanced_sb_segments_outro"), sf("revanced_sb_segments_outro_sum"), sf("revanced_sb_skip_button_outro"), sf("revanced_sb_skipped_outro"),
+ SB_CATEGORY_OUTRO, SB_CATEGORY_OUTRO_COLOR),
+ PREVIEW("preview", sf("revanced_sb_segments_preview"), sf("revanced_sb_segments_preview_sum"),
+ sf("revanced_sb_skip_button_preview_beginning"), sf("revanced_sb_skip_button_preview_middle"), sf("revanced_sb_skip_button_preview_end"),
+ sf("revanced_sb_skipped_preview_beginning"), sf("revanced_sb_skipped_preview_middle"), sf("revanced_sb_skipped_preview_end"),
+ SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR),
+ FILLER("filler", sf("revanced_sb_segments_filler"), sf("revanced_sb_segments_filler_sum"), sf("revanced_sb_skip_button_filler"), sf("revanced_sb_skipped_filler"),
+ SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR),
+ MUSIC_OFFTOPIC("music_offtopic", sf("revanced_sb_segments_nomusic"), sf("revanced_sb_segments_nomusic_sum"), sf("revanced_sb_skip_button_nomusic"), sf("revanced_sb_skipped_nomusic"),
+ SB_CATEGORY_MUSIC_OFFTOPIC, SB_CATEGORY_MUSIC_OFFTOPIC_COLOR);
+
+ private static final SegmentCategory[] categoriesWithoutUnsubmitted = new SegmentCategory[]{
+ SPONSOR,
+ SELF_PROMO,
+ INTERACTION,
+ INTRO,
+ OUTRO,
+ PREVIEW,
+ FILLER,
+ MUSIC_OFFTOPIC,
+ };
+ private static final Map mValuesMap = new HashMap<>(2 * categoriesWithoutUnsubmitted.length);
+
+ /**
+ * Categories currently enabled, formatted for an API call
+ */
+ public static String sponsorBlockAPIFetchCategories = "[]";
+
+ static {
+ for (SegmentCategory value : categoriesWithoutUnsubmitted)
+ mValuesMap.put(value.keyValue, value);
+ }
+
+ @NonNull
+ public static SegmentCategory[] categoriesWithoutUnsubmitted() {
+ return categoriesWithoutUnsubmitted;
+ }
+
+ @Nullable
+ public static SegmentCategory byCategoryKey(@NonNull String key) {
+ return mValuesMap.get(key);
+ }
+
+ /**
+ * Must be called if behavior of any category is changed
+ */
+ public static void updateEnabledCategories() {
+ Utils.verifyOnMainThread();
+ Logger.printDebug(() -> "updateEnabledCategories");
+ SegmentCategory[] categories = categoriesWithoutUnsubmitted();
+ List enabledCategories = new ArrayList<>(categories.length);
+ for (SegmentCategory category : categories) {
+ if (category.behaviour != CategoryBehaviour.IGNORE) {
+ enabledCategories.add(category.keyValue);
+ }
+ }
+
+ //"[%22sponsor%22,%22outro%22,%22music_offtopic%22,%22intro%22,%22selfpromo%22,%22interaction%22,%22preview%22]";
+ if (enabledCategories.isEmpty())
+ sponsorBlockAPIFetchCategories = "[]";
+ else
+ sponsorBlockAPIFetchCategories = "[%22" + TextUtils.join("%22,%22", enabledCategories) + "%22]";
+ }
+
+ public static void loadAllCategoriesFromSettings() {
+ for (SegmentCategory category : values()) {
+ category.loadFromSettings();
+ }
+ updateEnabledCategories();
+ }
+
+ @NonNull
+ public final String keyValue;
+ @NonNull
+ private final StringSetting behaviorSetting;
+ @NonNull
+ private final StringSetting colorSetting;
+
+ @NonNull
+ public final StringRef title;
+ @NonNull
+ public final StringRef description;
+
+ /**
+ * Skip button text, if the skip occurs in the first quarter of the video
+ */
+ @NonNull
+ public final StringRef skipButtonTextBeginning;
+ /**
+ * Skip button text, if the skip occurs in the middle half of the video
+ */
+ @NonNull
+ public final StringRef skipButtonTextMiddle;
+ /**
+ * Skip button text, if the skip occurs in the last quarter of the video
+ */
+ @NonNull
+ public final StringRef skipButtonTextEnd;
+ /**
+ * Skipped segment toast, if the skip occurred in the first quarter of the video
+ */
+ @NonNull
+ public final StringRef skippedToastBeginning;
+ /**
+ * Skipped segment toast, if the skip occurred in the middle half of the video
+ */
+ @NonNull
+ public final StringRef skippedToastMiddle;
+ /**
+ * Skipped segment toast, if the skip occurred in the last quarter of the video
+ */
+ @NonNull
+ public final StringRef skippedToastEnd;
+
+ @NonNull
+ public final Paint paint;
+
+ /**
+ * Value must be changed using {@link #setColor(String)}.
+ */
+ public int color;
+
+ /**
+ * Value must be changed using {@link #setBehaviour(CategoryBehaviour)}.
+ * Caller must also {@link #updateEnabledCategories()}.
+ */
+ @NonNull
+ public CategoryBehaviour behaviour = CategoryBehaviour.SKIP_AUTOMATICALLY;
+
+ SegmentCategory(String keyValue, StringRef title, StringRef description,
+ StringRef skipButtonText,
+ StringRef skippedToastText,
+ StringSetting behavior, StringSetting color) {
+ this(keyValue, title, description,
+ skipButtonText, skipButtonText, skipButtonText,
+ skippedToastText, skippedToastText, skippedToastText,
+ behavior, color);
+ }
+
+ SegmentCategory(String keyValue, StringRef title, StringRef description,
+ StringRef skipButtonTextBeginning, StringRef skipButtonTextMiddle, StringRef skipButtonTextEnd,
+ StringRef skippedToastBeginning, StringRef skippedToastMiddle, StringRef skippedToastEnd,
+ StringSetting behavior, StringSetting color) {
+ this.keyValue = Objects.requireNonNull(keyValue);
+ this.title = Objects.requireNonNull(title);
+ this.description = Objects.requireNonNull(description);
+ this.skipButtonTextBeginning = Objects.requireNonNull(skipButtonTextBeginning);
+ this.skipButtonTextMiddle = Objects.requireNonNull(skipButtonTextMiddle);
+ this.skipButtonTextEnd = Objects.requireNonNull(skipButtonTextEnd);
+ this.skippedToastBeginning = Objects.requireNonNull(skippedToastBeginning);
+ this.skippedToastMiddle = Objects.requireNonNull(skippedToastMiddle);
+ this.skippedToastEnd = Objects.requireNonNull(skippedToastEnd);
+ this.behaviorSetting = Objects.requireNonNull(behavior);
+ this.colorSetting = Objects.requireNonNull(color);
+ this.paint = new Paint();
+ loadFromSettings();
+ }
+
+ private void loadFromSettings() {
+ String behaviorString = behaviorSetting.get();
+ CategoryBehaviour savedBehavior = CategoryBehaviour.byReVancedKeyValue(behaviorString);
+ if (savedBehavior == null) {
+ Logger.printException(() -> "Invalid behavior: " + behaviorString);
+ behaviorSetting.resetToDefault();
+ loadFromSettings();
+ return;
+ }
+ this.behaviour = savedBehavior;
+
+ String colorString = colorSetting.get();
+ try {
+ setColor(colorString);
+ } catch (Exception ex) {
+ Logger.printException(() -> "Invalid color: " + colorString, ex);
+ colorSetting.resetToDefault();
+ loadFromSettings();
+ }
+ }
+
+ public void setBehaviour(@NonNull CategoryBehaviour behaviour) {
+ this.behaviour = Objects.requireNonNull(behaviour);
+ this.behaviorSetting.save(behaviour.reVancedKeyValue);
+ }
+
+ /**
+ * @return HTML color format string
+ */
+ @NonNull
+ public String colorString() {
+ return String.format("#%06X", color);
+ }
+
+ public void setColor(@NonNull String colorString) throws IllegalArgumentException {
+ final int color = Color.parseColor(colorString) & 0xFFFFFF;
+ this.color = color;
+ paint.setColor(color);
+ paint.setAlpha(255);
+ colorSetting.save(colorString); // Save after parsing.
+ }
+
+ public void resetColor() {
+ setColor(colorSetting.defaultValue);
+ }
+
+ @NonNull
+ private static String getCategoryColorDotHTML(int color) {
+ color &= 0xFFFFFF;
+ return String.format("⬤", color);
+ }
+
+ /**
+ * @noinspection deprecation
+ */
+ @NonNull
+ public static Spanned getCategoryColorDot(int color) {
+ return Html.fromHtml(getCategoryColorDotHTML(color));
+ }
+
+ @NonNull
+ public Spanned getCategoryColorDot() {
+ return getCategoryColorDot(color);
+ }
+
+ /**
+ * @param segmentStartTime video time the segment category started
+ * @param videoLength length of the video
+ * @return 'skipped segment' toast message
+ */
+ @NonNull
+ StringRef getSkippedToastText(long segmentStartTime, long videoLength) {
+ if (videoLength == 0) {
+ return skippedToastBeginning; // video is still loading. Assume it's the beginning
+ }
+ final float position = segmentStartTime / (float) videoLength;
+ if (position < 0.25f) {
+ return skippedToastBeginning;
+ } else if (position < 0.75f) {
+ return skippedToastMiddle;
+ }
+ return skippedToastEnd;
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/objects/SponsorSegment.java b/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/objects/SponsorSegment.java
new file mode 100644
index 000000000..85c2e0c26
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/objects/SponsorSegment.java
@@ -0,0 +1,102 @@
+package app.revanced.extension.music.sponsorblock.objects;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
+import app.revanced.extension.music.shared.VideoInformation;
+
+public class SponsorSegment implements Comparable {
+ @NonNull
+ public final SegmentCategory category;
+ /**
+ * NULL if segment is unsubmitted
+ */
+ @Nullable
+ public final String UUID;
+ public final long start;
+ public final long end;
+ public final boolean isLocked;
+ public boolean didAutoSkipped = false;
+
+ public SponsorSegment(@NonNull SegmentCategory category, @Nullable String UUID, long start, long end, boolean isLocked) {
+ this.category = category;
+ this.UUID = UUID;
+ this.start = start;
+ this.end = end;
+ this.isLocked = isLocked;
+ }
+
+ public boolean shouldAutoSkip() {
+ return category.behaviour.skipAutomatically;
+ }
+
+ /**
+ * @param nearThreshold threshold to declare the time parameter is near this segment. Must be a positive number
+ */
+ public boolean startIsNear(long videoTime, long nearThreshold) {
+ return Math.abs(start - videoTime) <= nearThreshold;
+ }
+
+ /**
+ * @param nearThreshold threshold to declare the time parameter is near this segment. Must be a positive number
+ */
+ public boolean endIsNear(long videoTime, long nearThreshold) {
+ return Math.abs(end - videoTime) <= nearThreshold;
+ }
+
+ /**
+ * @return if the segment is completely contained inside this segment
+ */
+ public boolean containsSegment(SponsorSegment other) {
+ return start <= other.start && other.end <= end;
+ }
+
+ /**
+ * @return the length of this segment, in milliseconds. Always a positive number.
+ */
+ public long length() {
+ return end - start;
+ }
+
+ /**
+ * @return 'skipped segment' toast message
+ */
+ @NonNull
+ public String getSkippedToastText() {
+ return category.getSkippedToastText(start, VideoInformation.getVideoLength()).toString();
+ }
+
+ @Override
+ public int compareTo(SponsorSegment o) {
+ // If both segments start at the same time, then sort with the longer segment first.
+ // This keeps the seekbar drawing correct since it draws the segments using the sorted order.
+ return start == o.start ? Long.compare(o.length(), length()) : Long.compare(start, o.start);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof SponsorSegment other)) return false;
+ return Objects.equals(UUID, other.UUID)
+ && category == other.category
+ && start == other.start
+ && end == other.end;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(UUID);
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "SponsorSegment{"
+ + "category=" + category
+ + ", start=" + start
+ + ", end=" + end
+ + '}';
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/requests/SBRequester.java b/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/requests/SBRequester.java
new file mode 100644
index 000000000..0b520fbfc
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/sponsorblock/requests/SBRequester.java
@@ -0,0 +1,145 @@
+package app.revanced.extension.music.sponsorblock.requests;
+
+import static app.revanced.extension.shared.utils.StringRef.str;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.music.sponsorblock.SponsorBlockSettings;
+import app.revanced.extension.music.sponsorblock.objects.SegmentCategory;
+import app.revanced.extension.music.sponsorblock.objects.SponsorSegment;
+import app.revanced.extension.shared.requests.Requester;
+import app.revanced.extension.shared.requests.Route;
+import app.revanced.extension.shared.sponsorblock.requests.SBRoutes;
+import app.revanced.extension.shared.utils.Logger;
+import app.revanced.extension.shared.utils.Utils;
+
+public class SBRequester {
+ /**
+ * TCP timeout
+ */
+ private static final int TIMEOUT_TCP_DEFAULT_MILLISECONDS = 7000;
+
+ /**
+ * HTTP response timeout
+ */
+ private static final int TIMEOUT_HTTP_DEFAULT_MILLISECONDS = 10000;
+
+ /**
+ * Response code of a successful API call
+ */
+ private static final int HTTP_STATUS_CODE_SUCCESS = 200;
+
+ private SBRequester() {
+ }
+
+ private static void handleConnectionError(@NonNull String toastMessage, @Nullable Exception ex) {
+ if (Settings.SB_TOAST_ON_CONNECTION_ERROR.get()) {
+ Utils.showToastShort(toastMessage);
+ }
+ if (ex != null) {
+ Logger.printInfo(() -> toastMessage, ex);
+ }
+ }
+
+ @NonNull
+ public static SponsorSegment[] getSegments(@NonNull String videoId) {
+ Utils.verifyOffMainThread();
+ List segments = new ArrayList<>();
+ try {
+ HttpURLConnection connection = getConnectionFromRoute(SBRoutes.GET_SEGMENTS, videoId, SegmentCategory.sponsorBlockAPIFetchCategories);
+ final int responseCode = connection.getResponseCode();
+
+ if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
+ JSONArray responseArray = Requester.parseJSONArray(connection);
+ final long minSegmentDuration = 0;
+ for (int i = 0, length = responseArray.length(); i < length; i++) {
+ JSONObject obj = (JSONObject) responseArray.get(i);
+ JSONArray segment = obj.getJSONArray("segment");
+ final long start = (long) (segment.getDouble(0) * 1000);
+ final long end = (long) (segment.getDouble(1) * 1000);
+
+ String uuid = obj.getString("UUID");
+ final boolean locked = obj.getInt("locked") == 1;
+ String categoryKey = obj.getString("category");
+ SegmentCategory category = SegmentCategory.byCategoryKey(categoryKey);
+ if (category == null) {
+ Logger.printException(() -> "Received unknown category: " + categoryKey); // should never happen
+ } else if ((end - start) >= minSegmentDuration) {
+ segments.add(new SponsorSegment(category, uuid, start, end, locked));
+ }
+ }
+ Logger.printDebug(() -> {
+ StringBuilder builder = new StringBuilder("Downloaded segments:");
+ for (SponsorSegment segment : segments) {
+ builder.append('\n').append(segment);
+ }
+ return builder.toString();
+ });
+ runVipCheckInBackgroundIfNeeded();
+ } else if (responseCode == 404) {
+ // no segments are found. a normal response
+ Logger.printDebug(() -> "No segments found for video: " + videoId);
+ } else {
+ handleConnectionError(str("revanced_sb_sponsorblock_connection_failure_status", responseCode), null);
+ connection.disconnect(); // something went wrong, might as well disconnect
+ }
+ } catch (SocketTimeoutException ex) {
+ handleConnectionError(str("revanced_sb_sponsorblock_connection_failure_timeout"), ex);
+ } catch (IOException ex) {
+ handleConnectionError(str("revanced_sb_sponsorblock_connection_failure_generic"), ex);
+ } catch (Exception ex) {
+ // Should never happen
+ Logger.printException(() -> "getSegments failure", ex);
+ }
+
+ return segments.toArray(new SponsorSegment[0]);
+ }
+
+ public static void runVipCheckInBackgroundIfNeeded() {
+ if (!SponsorBlockSettings.userHasSBPrivateId()) {
+ return; // User cannot be a VIP. User has never voted, created any segments, or has imported a SB user id.
+ }
+ long now = System.currentTimeMillis();
+ if (now < (Settings.SB_LAST_VIP_CHECK.get() + TimeUnit.DAYS.toMillis(3))) {
+ return;
+ }
+ Utils.runOnBackgroundThread(() -> {
+ try {
+ JSONObject json = getJSONObject(SponsorBlockSettings.getSBPrivateUserID());
+ boolean vip = json.getBoolean("vip");
+ Settings.SB_USER_IS_VIP.save(vip);
+ Settings.SB_LAST_VIP_CHECK.save(now);
+ } catch (IOException ex) {
+ Logger.printInfo(() -> "Failed to check VIP (network error)", ex); // info, so no error toast is shown
+ } catch (Exception ex) {
+ Logger.printException(() -> "Failed to check VIP", ex); // should never happen
+ }
+ });
+ }
+
+ // helpers
+
+ private static HttpURLConnection getConnectionFromRoute(@NonNull Route route, String... params) throws IOException {
+ HttpURLConnection connection = Requester.getConnectionFromRoute(Settings.SB_API_URL.get(), route, params);
+ connection.setConnectTimeout(TIMEOUT_TCP_DEFAULT_MILLISECONDS);
+ connection.setReadTimeout(TIMEOUT_HTTP_DEFAULT_MILLISECONDS);
+ return connection;
+ }
+
+ private static JSONObject getJSONObject(String... params) throws IOException, JSONException {
+ return Requester.parseJSONObject(getConnectionFromRoute(SBRoutes.IS_USER_VIP, params));
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/utils/ExtendedUtils.java b/extensions/shared/src/main/java/app/revanced/extension/music/utils/ExtendedUtils.java
new file mode 100644
index 000000000..5de71744e
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/utils/ExtendedUtils.java
@@ -0,0 +1,41 @@
+package app.revanced.extension.music.utils;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.shared.utils.PackageUtils;
+
+public class ExtendedUtils extends PackageUtils {
+
+ public static boolean isSpoofingToLessThan(@NonNull String versionName) {
+ if (!Settings.SPOOF_APP_VERSION.get())
+ return false;
+
+ return isVersionToLessThan(Settings.SPOOF_APP_VERSION_TARGET.get(), versionName);
+ }
+
+ @SuppressWarnings("deprecation")
+ public static AlertDialog.Builder getDialogBuilder(@NonNull Context context) {
+ return new AlertDialog.Builder(context, isSDKAbove(22)
+ ? android.R.style.Theme_DeviceDefault_Dialog_Alert
+ : AlertDialog.THEME_DEVICE_DEFAULT_DARK
+ );
+ }
+
+ public static FrameLayout.LayoutParams getLayoutParams() {
+ FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ int left_margin = dpToPx(20);
+ int top_margin = dpToPx(10);
+ int right_margin = dpToPx(20);
+ int bottom_margin = dpToPx(4);
+ params.setMargins(left_margin, top_margin, right_margin, bottom_margin);
+
+ return params;
+ }
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/utils/RestartUtils.java b/extensions/shared/src/main/java/app/revanced/extension/music/utils/RestartUtils.java
new file mode 100644
index 000000000..a4ca37641
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/utils/RestartUtils.java
@@ -0,0 +1,36 @@
+package app.revanced.extension.music.utils;
+
+import static app.revanced.extension.music.utils.ExtendedUtils.getDialogBuilder;
+import static app.revanced.extension.shared.utils.StringRef.str;
+import static app.revanced.extension.shared.utils.Utils.runOnMainThreadDelayed;
+
+import android.app.Activity;
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+public class RestartUtils {
+
+ public static void restartApp(@NonNull Activity activity) {
+ final Intent intent = Objects.requireNonNull(activity.getPackageManager().getLaunchIntentForPackage(activity.getPackageName()));
+ final Intent mainIntent = Intent.makeRestartActivityTask(intent.getComponent());
+
+ activity.finishAffinity();
+ activity.startActivity(mainIntent);
+ Runtime.getRuntime().exit(0);
+ }
+
+ public static void showRestartDialog(@NonNull Activity activity) {
+ showRestartDialog(activity, "revanced_extended_restart_message", 0);
+ }
+
+ public static void showRestartDialog(@NonNull Activity activity, @NonNull String message, long delay) {
+ getDialogBuilder(activity)
+ .setMessage(str(message))
+ .setPositiveButton(android.R.string.ok, (dialog, id) -> runOnMainThreadDelayed(() -> restartApp(activity), delay))
+ .setNegativeButton(android.R.string.cancel, null)
+ .show();
+ }
+}
\ No newline at end of file
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/utils/VideoUtils.java b/extensions/shared/src/main/java/app/revanced/extension/music/utils/VideoUtils.java
new file mode 100644
index 000000000..059c311bd
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/utils/VideoUtils.java
@@ -0,0 +1,87 @@
+package app.revanced.extension.music.utils;
+
+import static app.revanced.extension.music.settings.preference.ExternalDownloaderPreference.checkPackageIsEnabled;
+import static app.revanced.extension.shared.utils.StringRef.str;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.media.AudioManager;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.music.shared.VideoInformation;
+import app.revanced.extension.shared.settings.StringSetting;
+import app.revanced.extension.shared.utils.IntentUtils;
+import app.revanced.extension.shared.utils.Logger;
+
+@SuppressWarnings("unused")
+public class VideoUtils extends IntentUtils {
+ private static final StringSetting externalDownloaderPackageName =
+ Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME;
+
+ public static void launchExternalDownloader() {
+ launchExternalDownloader(VideoInformation.getVideoId());
+ }
+
+ public static void launchExternalDownloader(@NonNull String videoId) {
+ try {
+ String downloaderPackageName = externalDownloaderPackageName.get().trim();
+
+ if (downloaderPackageName.isEmpty()) {
+ externalDownloaderPackageName.resetToDefault();
+ downloaderPackageName = externalDownloaderPackageName.defaultValue;
+ }
+
+ if (!checkPackageIsEnabled()) {
+ return;
+ }
+
+ final String content = String.format("https://music.youtube.com/watch?v=%s", videoId);
+ launchExternalDownloader(content, downloaderPackageName);
+ } catch (Exception ex) {
+ Logger.printException(() -> "launchExternalDownloader failure", ex);
+ }
+ }
+
+ @SuppressLint("IntentReset")
+ public static void openInYouTube() {
+ final String videoId = VideoInformation.getVideoId();
+ if (videoId.isEmpty()) {
+ showToastShort(str("revanced_replace_flyout_menu_dismiss_queue_watch_on_youtube_warning"));
+ return;
+ }
+
+ if (context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE) instanceof AudioManager audioManager) {
+ audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+ }
+
+ String url = String.format("vnd.youtube://%s", videoId);
+ if (Settings.REPLACE_FLYOUT_MENU_DISMISS_QUEUE_CONTINUE_WATCH.get()) {
+ long seconds = VideoInformation.getVideoTime() / 1000;
+ url += String.format("?t=%s", seconds);
+ }
+
+ launchView(url);
+ }
+
+ public static void openInYouTubeMusic(@NonNull String songId) {
+ final String url = String.format("vnd.youtube.music://%s", songId);
+ launchView(url, context.getPackageName());
+ }
+
+ /**
+ * Rest of the implementation added by patch.
+ */
+ public static void shuffleTracks() {
+ Log.d("Extended: VideoUtils", "Tracks are shuffled");
+ }
+
+ /**
+ * Rest of the implementation added by patch.
+ */
+ public static void showPlaybackSpeedFlyoutMenu() {
+ Logger.printDebug(() -> "Playback speed flyout menu opened");
+ }
+}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/reddit/patches/GeneralAdsPatch.java b/extensions/shared/src/main/java/app/revanced/extension/reddit/patches/GeneralAdsPatch.java
new file mode 100644
index 000000000..f108a49d7
--- /dev/null
+++ b/extensions/shared/src/main/java/app/revanced/extension/reddit/patches/GeneralAdsPatch.java
@@ -0,0 +1,42 @@
+package app.revanced.extension.reddit.patches;
+
+import com.reddit.domain.model.ILink;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import app.revanced.extension.reddit.settings.Settings;
+
+@SuppressWarnings("unused")
+public final class GeneralAdsPatch {
+
+ private static List> filterChildren(final Iterable> links) {
+ final List