From 3d660e1b5eeab9771f96bd2d26a222b835e2485c Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 2 Dec 2023 17:56:08 +0100 Subject: [PATCH 01/27] fix(YouTube - SponsorBlock): Allow autoplay when skipping to the end of the video --- .../app/revanced/integrations/patches/VideoInformation.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java b/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java index b2a42378..2b9bea41 100644 --- a/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java +++ b/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java @@ -135,10 +135,8 @@ public final class VideoInformation { public static boolean seekTo(final long millisecond) { final long videoLength = getVideoLength(); - // Don't seek more than the video length to prevent issues such as - // Play pause button or autoplay not working. - // TODO: These are arbitrarily chosen values and should be subject to be adjusted. - final long seekToMilliseconds = millisecond <= videoLength - 500 ? millisecond : millisecond - 100; + // Prevent issues such as play/ pause button or autoplay not working. + final long seekToMilliseconds = millisecond > videoLength ? Integer.MAX_VALUE : millisecond; ReVancedUtils.verifyOnMainThread(); try { From 3b4f0206ad2ead652b582a49c636fd7b0f02f707 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 2 Dec 2023 21:31:15 +0000 Subject: [PATCH 02/27] chore(release): 0.125.1-dev.1 [skip ci] ## [0.125.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.125.0...v0.125.1-dev.1) (2023-12-02) ### Bug Fixes * **YouTube - SponsorBlock:** Allow autoplay when skipping to the end of the video ([3d660e1](https://github.com/ReVanced/revanced-integrations/commit/3d660e1b5eeab9771f96bd2d26a222b835e2485c)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33453ab2..36d5f482 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.125.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.125.0...v0.125.1-dev.1) (2023-12-02) + + +### Bug Fixes + +* **YouTube - SponsorBlock:** Allow autoplay when skipping to the end of the video ([3d660e1](https://github.com/ReVanced/revanced-integrations/commit/3d660e1b5eeab9771f96bd2d26a222b835e2485c)) + # [0.125.0](https://github.com/ReVanced/revanced-integrations/compare/v0.124.1...v0.125.0) (2023-12-02) diff --git a/gradle.properties b/gradle.properties index 850afc82..181eaae8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 0.125.0 +version = 0.125.1-dev.1 From fba7181e70d695d7fb13c530754dc1db99b87216 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 2 Dec 2023 22:51:39 +0100 Subject: [PATCH 03/27] feat: Allow choosing the vendor of GmsCore via patch options (#529) BREAKING CHANGE: The class `MicroGSupport` has been renamed to `GmsCoreSupport` --- .../integrations/patches/GmsCoreSupport.java | 74 +++++++++++++++++++ .../integrations/patches/MicroGSupport.java | 53 ------------- 2 files changed, 74 insertions(+), 53 deletions(-) create mode 100644 app/src/main/java/app/revanced/integrations/patches/GmsCoreSupport.java delete mode 100644 app/src/main/java/app/revanced/integrations/patches/MicroGSupport.java diff --git a/app/src/main/java/app/revanced/integrations/patches/GmsCoreSupport.java b/app/src/main/java/app/revanced/integrations/patches/GmsCoreSupport.java new file mode 100644 index 00000000..633b01c2 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/patches/GmsCoreSupport.java @@ -0,0 +1,74 @@ +package app.revanced.integrations.patches; + +import android.app.SearchManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import androidx.annotation.RequiresApi; +import app.revanced.integrations.utils.LogHelper; +import app.revanced.integrations.utils.ReVancedUtils; + +import java.util.Objects; + +import static app.revanced.integrations.utils.StringRef.str; + +/** + * @noinspection unused + */ +public class GmsCoreSupport { + private static final String GMS_CORE_PACKAGE_NAME + = getGmsCoreVendor() + ".android.gms"; + private static final String DONT_KILL_MY_APP_LINK + = "https://dontkillmyapp.com"; + private static final Uri GMS_CORE_PROVIDER + = Uri.parse("content://" + getGmsCoreVendor() + ".android.gsf.gservices/prefix"); + + private static void search(Context context, String uriString, String message) { + ReVancedUtils.showToastLong(message); + + var intent = new Intent(Intent.ACTION_WEB_SEARCH); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(SearchManager.QUERY, uriString); + context.startActivity(intent); + } + + @RequiresApi(api = Build.VERSION_CODES.N) + public static void checkAvailability() { + var context = Objects.requireNonNull(ReVancedUtils.getContext()); + + try { + context.getPackageManager().getPackageInfo(GMS_CORE_PACKAGE_NAME, PackageManager.GET_ACTIVITIES); + } catch (PackageManager.NameNotFoundException exception) { + LogHelper.printInfo(() -> "GmsCore was not found", exception); + search(context, getGmsCoreDownloadLink(), str("gms_core_not_installed_warning")); + + // Gracefully exit the app, so it does not crash. + System.exit(0); + } + + try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) { + if (client != null) return; + LogHelper.printInfo(() -> "GmsCore is not running in the background"); + search(context, DONT_KILL_MY_APP_LINK, str("gms_core_not_running_warning")); + } + } + + private static String getGmsCoreDownloadLink() { + final var vendor = getGmsCoreVendor(); + switch (vendor) { + case "com.mgoogle": + return "https://github.com/TeamVanced/VancedMicroG/releases/latest"; + case "app.revanced": + return "https://github.com/revanced/gmscore/releases/latest"; + default: + return vendor + ".android.gms"; + } + } + + // Modified by a patch. Do not touch. + private static String getGmsCoreVendor() { + return "app.revanced"; + } +} diff --git a/app/src/main/java/app/revanced/integrations/patches/MicroGSupport.java b/app/src/main/java/app/revanced/integrations/patches/MicroGSupport.java deleted file mode 100644 index ba242d30..00000000 --- a/app/src/main/java/app/revanced/integrations/patches/MicroGSupport.java +++ /dev/null @@ -1,53 +0,0 @@ -package app.revanced.integrations.patches; - -import static app.revanced.integrations.utils.StringRef.str; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; - -import java.util.Objects; - -import app.revanced.integrations.utils.LogHelper; -import app.revanced.integrations.utils.ReVancedUtils; - -public class MicroGSupport { - private static final String MICROG_VENDOR = "com.mgoogle"; - private static final String MICROG_PACKAGE_NAME = MICROG_VENDOR + ".android.gms"; - private static final String VANCED_MICROG_DOWNLOAD_LINK = "https://github.com/TeamVanced/VancedMicroG/releases/latest"; - private static final String DONT_KILL_MY_APP_LINK = "https://dontkillmyapp.com"; - private static final Uri VANCED_MICROG_PROVIDER = Uri.parse("content://" + MICROG_VENDOR + ".android.gsf.gservices/prefix"); - - private static void startIntent(Context context, String uriString, String message) { - ReVancedUtils.showToastLong(message); - - var intent = new Intent(Intent.ACTION_VIEW); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setData(Uri.parse(uriString)); - context.startActivity(intent); - } - - @TargetApi(26) - public static void checkAvailability() { - var context = Objects.requireNonNull(ReVancedUtils.getContext()); - - try { - context.getPackageManager().getPackageInfo(MICROG_PACKAGE_NAME, PackageManager.GET_ACTIVITIES); - } catch (PackageManager.NameNotFoundException exception) { - LogHelper.printInfo(() -> "Vanced MicroG was not found", exception); - startIntent(context, VANCED_MICROG_DOWNLOAD_LINK, str("microg_not_installed_warning")); - - // Gracefully exit the app, so it does not crash. - System.exit(0); - } - - - try (var client = context.getContentResolver().acquireContentProviderClient(VANCED_MICROG_PROVIDER)) { - if (client != null) return; - LogHelper.printInfo(() -> "Vanced MicroG is not running in the background"); - startIntent(context, DONT_KILL_MY_APP_LINK, str("microg_not_running_warning")); - } - } -} \ No newline at end of file From 3a56431a28e4b7f796474457e41726c0c6234efc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 2 Dec 2023 21:54:20 +0000 Subject: [PATCH 04/27] chore(release): 1.0.0-dev.1 [skip ci] # [1.0.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.125.1-dev.1...v1.0.0-dev.1) (2023-12-02) ### Features * Allow choosing the vendor of GmsCore via patch options ([#529](https://github.com/ReVanced/revanced-integrations/issues/529)) ([fba7181](https://github.com/ReVanced/revanced-integrations/commit/fba7181e70d695d7fb13c530754dc1db99b87216)) ### BREAKING CHANGES * The class `MicroGSupport` has been renamed to `GmsCoreSupport` --- CHANGELOG.md | 12 ++++++++++++ gradle.properties | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36d5f482..2567db92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# [1.0.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.125.1-dev.1...v1.0.0-dev.1) (2023-12-02) + + +### Features + +* Allow choosing the vendor of GmsCore via patch options ([#529](https://github.com/ReVanced/revanced-integrations/issues/529)) ([fba7181](https://github.com/ReVanced/revanced-integrations/commit/fba7181e70d695d7fb13c530754dc1db99b87216)) + + +### BREAKING CHANGES + +* The class `MicroGSupport` has been renamed to `GmsCoreSupport` + ## [0.125.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.125.0...v0.125.1-dev.1) (2023-12-02) diff --git a/gradle.properties b/gradle.properties index 181eaae8..462534e8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 0.125.1-dev.1 +version = 1.0.0-dev.1 From 5d4c8b0a1b77e97c7c0c02288927e92f3c9765ce Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sun, 3 Dec 2023 20:24:37 +0200 Subject: [PATCH 05/27] fix(YouTube - Return YouTube Dislike): Fix dislikes sometimes not showing for non English language --- .../patches/ReturnYouTubeDislikePatch.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java index f69340d2..154088da 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -318,8 +318,12 @@ public class ReturnYouTubeDislikePatch { try { if (SettingsEnum.RYD_ENABLED.getBoolean() && !SettingsEnum.RYD_COMPACT_LAYOUT.getBoolean()) { if (ReturnYouTubeDislike.isPreviouslyCreatedSegmentedSpan(text)) { + // +1 pixel is needed for some foreign languages that measure + // the text different from what is used for layout (Greek in particular). + // Probably a bug in Android, but who knows. + // Single line mode is also used as an additional fix for this issue. return measuredTextWidth + ReturnYouTubeDislike.leftSeparatorBounds.right - + ReturnYouTubeDislike.leftSeparatorShapePaddingPixels; + + ReturnYouTubeDislike.leftSeparatorShapePaddingPixels + 1; } } } catch (Exception ex) { @@ -342,6 +346,10 @@ public class ReturnYouTubeDislikePatch { } else { view.setCompoundDrawables(separator, null, null, null); } + // Single line mode does not clip words if the span is larger than the view bounds. + // The styled span applied to the view should always have the same bounds, + // but use this feature just in case the measurements are somehow off by a few pixels. + view.setSingleLine(true); } } @@ -354,6 +362,7 @@ public class ReturnYouTubeDislikePatch { LogHelper.printDebug(() -> "Removing rolling number TextView changes"); view.setCompoundDrawablePadding(0); view.setCompoundDrawables(null, null, null, null); + view.setSingleLine(false); } } From 9394c512ba216da3819d9370daa7429afedfb162 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 3 Dec 2023 18:27:44 +0000 Subject: [PATCH 06/27] chore(release): 1.0.0-dev.2 [skip ci] # [1.0.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.1...v1.0.0-dev.2) (2023-12-03) ### Bug Fixes * **YouTube - Return YouTube Dislike:** Fix dislikes sometimes not showing for non English language ([5d4c8b0](https://github.com/ReVanced/revanced-integrations/commit/5d4c8b0a1b77e97c7c0c02288927e92f3c9765ce)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2567db92..5a9930db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.1...v1.0.0-dev.2) (2023-12-03) + + +### Bug Fixes + +* **YouTube - Return YouTube Dislike:** Fix dislikes sometimes not showing for non English language ([5d4c8b0](https://github.com/ReVanced/revanced-integrations/commit/5d4c8b0a1b77e97c7c0c02288927e92f3c9765ce)) + # [1.0.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v0.125.1-dev.1...v1.0.0-dev.1) (2023-12-02) diff --git a/gradle.properties b/gradle.properties index 462534e8..da3653e7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.0.0-dev.1 +version = 1.0.0-dev.2 From f4e2d56b181fee4d693dea1dfe81974237e4eff7 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 3 Dec 2023 19:30:56 +0100 Subject: [PATCH 07/27] fix(YouTube - SponsorBlock): Prevent autoplay from stopping to work --- .../app/revanced/integrations/patches/VideoInformation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java b/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java index 2b9bea41..81ae8ce2 100644 --- a/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java +++ b/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java @@ -136,7 +136,7 @@ public final class VideoInformation { final long videoLength = getVideoLength(); // Prevent issues such as play/ pause button or autoplay not working. - final long seekToMilliseconds = millisecond > videoLength ? Integer.MAX_VALUE : millisecond; + final long seekToMilliseconds = Math.min(millisecond, VideoInformation.getVideoLength() - 250); ReVancedUtils.verifyOnMainThread(); try { From d484f35127083f4a9d2f1bacb48af0f82e7efaaa Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 3 Dec 2023 18:33:33 +0000 Subject: [PATCH 08/27] chore(release): 1.0.0-dev.3 [skip ci] # [1.0.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.2...v1.0.0-dev.3) (2023-12-03) ### Bug Fixes * **YouTube - SponsorBlock:** Prevent autoplay from stopping to work ([f4e2d56](https://github.com/ReVanced/revanced-integrations/commit/f4e2d56b181fee4d693dea1dfe81974237e4eff7)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a9930db..80b4e5eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.2...v1.0.0-dev.3) (2023-12-03) + + +### Bug Fixes + +* **YouTube - SponsorBlock:** Prevent autoplay from stopping to work ([f4e2d56](https://github.com/ReVanced/revanced-integrations/commit/f4e2d56b181fee4d693dea1dfe81974237e4eff7)) + # [1.0.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.1...v1.0.0-dev.2) (2023-12-03) diff --git a/gradle.properties b/gradle.properties index da3653e7..2fdf1726 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.0.0-dev.2 +version = 1.0.0-dev.3 From 0bb86694e24a6a41edee62f5ef1bb80fe7bc3f19 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:47:29 +0200 Subject: [PATCH 09/27] fix(YouTube - Return YouTube Dislike): Prevent the first Short opened from freezing the UI (#532) --- .../patches/ReturnYouTubeDislikePatch.java | 61 +++++++++++++------ .../patches/VideoInformation.java | 46 ++++++++++++-- .../ReturnYouTubeDislikeFilterPatch.java | 6 +- .../patches/spoof/SpoofSignaturePatch.java | 9 +-- .../requests/ReturnYouTubeDislikeApi.java | 4 +- .../ReturnYouTubeDislikeSettingsFragment.java | 6 +- .../integrations/shared/PlayerType.kt | 7 ++- 7 files changed, 99 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java index 154088da..38e4e54d 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -9,6 +9,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import app.revanced.integrations.patches.components.ReturnYouTubeDislikeFilterPatch; +import app.revanced.integrations.patches.spoof.SpoofAppVersionPatch; import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.shared.PlayerType; @@ -27,19 +28,25 @@ import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislik * Handles all interaction of UI patch components. * * Known limitation: - * Litho based Shorts player can experience temporarily frozen video playback if the RYD fetch takes too long. + * The implementation of Shorts litho requires blocking the loading the first Short until RYD has completed. + * This is because it modifies the dislikes text synchronously, and if the RYD fetch has + * not completed yet then the UI will be temporarily frozen. * - * Temporary work around: - * Enable app spoofing to version 18.33.40 or older, as that uses a non litho Shorts player. - * - * Permanent fix (yet to be implemented), either of: - * - Modify patch to hook onto the Shorts Litho TextView, and update the dislikes asynchronously. - * - Find a way to force Litho to rebuild it's component tree - * (and use that hook to force the shorts dislikes to update after the fetch is completed). + * A (yet to be implemented) solution that fixes this problem. Any one of: + * - Modify patch to hook onto the Shorts Litho TextView, and update the dislikes text asynchronously. + * - Find a way to force Litho to rebuild it's component tree, + * and use that hook to force the shorts dislikes to update after the fetch is completed. + * - Hook into the dislikes button image view, and replace the dislikes thumb down image with a + * generated image of the number of dislikes, then update the image asynchronously. This Could + * also be used for the regular video player to give a better UI layout and completely remove + * the need for the Rolling Number patches. */ @SuppressWarnings("unused") public class ReturnYouTubeDislikePatch { + public static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER = + SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("18.33.40"); + /** * RYD data for the current video on screen. */ @@ -549,26 +556,46 @@ public class ReturnYouTubeDislikePatch { // Video Id and voting hooks (all players). // + private static volatile boolean lastPlayerResponseWasShort; + /** * Injection point. Uses 'playback response' video id hook to preload RYD. */ - public static void preloadVideoId(@NonNull String videoId, boolean videoIsOpeningOrPlaying) { + public static void preloadVideoId(@NonNull String videoId, boolean isShortAndOpeningOrPlaying) { try { - // Shorts shelf in home and subscription feed causes player response hook to be called, - // and the 'is opening/playing' parameter will be false. - // This hook will be called again when the Short is actually opened. - if (!videoIsOpeningOrPlaying || !SettingsEnum.RYD_ENABLED.getBoolean()) { - return; - } - if (!SettingsEnum.RYD_SHORTS.getBoolean() && PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized()) { + if (!SettingsEnum.RYD_ENABLED.getBoolean()) { return; } if (videoId.equals(lastPrefetchedVideoId)) { return; } + + final boolean videoIdIsShort = VideoInformation.lastVideoIdIsShort(); + // Shorts shelf in home and subscription feed causes player response hook to be called, + // and the 'is opening/playing' parameter will be false. + // This hook will be called again when the Short is actually opened. + if (videoIdIsShort && (!isShortAndOpeningOrPlaying || !SettingsEnum.RYD_SHORTS.getBoolean())) { + return; + } + final boolean waitForFetchToComplete = !IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER + && videoIdIsShort && !lastPlayerResponseWasShort; + lastPlayerResponseWasShort = videoIdIsShort; lastPrefetchedVideoId = videoId; + LogHelper.printDebug(() -> "Prefetching RYD for video: " + videoId); - ReturnYouTubeDislike.getFetchForVideoId(videoId); + ReturnYouTubeDislike fetch = ReturnYouTubeDislike.getFetchForVideoId(videoId); + if (waitForFetchToComplete && !fetch.fetchCompleted()) { + // This call is off the main thread, so wait until the RYD fetch completely finishes, + // otherwise if this returns before the fetch completes then the UI can + // become frozen when the main thread tries to modify the litho Shorts dislikes and + // it must wait for the fetch. + // Only need to do this for the first Short opened, as the next Short to swipe to + // are preloaded in the background. + // + // If an asynchronous litho Shorts solution is found, then this blocking call should be removed. + LogHelper.printDebug(() -> "Waiting for prefetch to complete: " + videoId); + fetch.getFetchData(10000); // Use any arbitrarily large max wait time. + } } catch (Exception ex) { LogHelper.printException(() -> "preloadVideoId failure", ex); } diff --git a/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java b/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java index 81ae8ce2..a6ba5649 100644 --- a/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java +++ b/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java @@ -17,6 +17,10 @@ import java.util.Objects; public final class VideoInformation { private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f; private static final String SEEK_METHOD_NAME = "seekTo"; + /** + * Prefix present in all Short player parameters signature. + */ + private static final String SHORTS_PLAYER_PARAMETERS = "8AEB"; private static WeakReference playerControllerRef; private static Method seekMethod; @@ -28,6 +32,7 @@ public final class VideoInformation { @NonNull private static volatile String playerResponseVideoId = ""; + private static volatile boolean videoIdIsShort; /** * The current playback speed @@ -65,12 +70,33 @@ public final class VideoInformation { } } + /** + * @return If the player parameters are for a Short. + */ + public static boolean playerParametersAreShort(@NonNull String parameters) { + return parameters.startsWith(SHORTS_PLAYER_PARAMETERS); + } + + /** + * Injection point. + */ + public static String newPlayerResponseSignature(@NonNull String signature, boolean isShortAndOpeningOrPlaying) { + final boolean isShort = playerParametersAreShort(signature); + if (!isShort || isShortAndOpeningOrPlaying) { + if (videoIdIsShort != isShort) { + videoIdIsShort = isShort; + LogHelper.printDebug(() -> "videoIdIsShort: " + isShort); + } + } + return signature; // Return the original value since we are observing and not modifying. + } + /** * Injection point. Called off the main thread. * * @param videoId The id of the last video loaded. */ - public static void setPlayerResponseVideoId(@NonNull String videoId, boolean videoIsOpeningOrPlaying) { + public static void setPlayerResponseVideoId(@NonNull String videoId, boolean isShortAndOpeningOrPlaying) { if (!playerResponseVideoId.equals(videoId)) { LogHelper.printDebug(() -> "New player response video id: " + videoId); playerResponseVideoId = videoId; @@ -155,9 +181,9 @@ public final class VideoInformation { } /** - * Id of the current video playing. Includes Shorts. + * Id of the last video opened. Includes Shorts. * - * @return The id of the video. Empty string if not set yet. + * @return The id of the video, or an empty string if no videos have been opened yet. */ @NonNull public static String getVideoId() { @@ -166,20 +192,30 @@ public final class VideoInformation { /** * Differs from {@link #videoId} as this is the video id for the - * last player response received, which may not be the current video playing. + * last player response received, which may not be the last video opened. *

* If Shorts are loading the background, this commonly will be * different from the Short that is currently on screen. *

* For most use cases, you should instead use {@link #getVideoId()}. * - * @return The id of the last video loaded. Empty string if not set yet. + * @return The id of the last video loaded, or an empty string if no videos have been loaded yet. */ @NonNull public static String getPlayerResponseVideoId() { return playerResponseVideoId; } + /** + * @return If the last player response video id _that was opened_ was a Short. + *

+ * Note: This value returned may not match the status of {@link #getPlayerResponseVideoId()} + * since that includes player responses for videos not opened. + */ + public static boolean lastVideoIdIsShort() { + return videoIdIsShort; + } + /** * @return The current playback speed. */ diff --git a/app/src/main/java/app/revanced/integrations/patches/components/ReturnYouTubeDislikeFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/ReturnYouTubeDislikeFilterPatch.java index 48c44f36..d6b7d151 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/ReturnYouTubeDislikeFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/ReturnYouTubeDislikeFilterPatch.java @@ -53,14 +53,14 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter { /** * Injection point. */ - public static void newPlayerResponseVideoId(String videoId, boolean videoIsOpeningOrPlaying) { + public static void newPlayerResponseVideoId(String videoId, boolean isShortAndOpeningOrPlaying) { try { - if (!videoIsOpeningOrPlaying || !SettingsEnum.RYD_SHORTS.getBoolean()) { + if (!isShortAndOpeningOrPlaying || !SettingsEnum.RYD_SHORTS.getBoolean()) { return; } synchronized (lastVideoIds) { if (lastVideoIds.put(videoId, Boolean.TRUE) == null) { - LogHelper.printDebug(() -> "New video id: " + videoId); + LogHelper.printDebug(() -> "New Short video id: " + videoId); } } } catch (Exception ex) { diff --git a/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java b/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java index 93ca4c4d..ed29e44d 100644 --- a/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java @@ -37,11 +37,6 @@ public class SpoofSignaturePatch { */ private static final String SCRIM_PARAMETER = "SAFgAXgB"; - /** - * Parameters used in YouTube Shorts. - */ - private static final String SHORTS_PLAYER_PARAMETERS = "8AEB"; - /** * Last video id loaded. Used to prevent reloading the same spec multiple times. */ @@ -62,7 +57,7 @@ public class SpoofSignaturePatch { * * @param parameters Original protobuf parameter value. */ - public static String spoofParameter(String parameters) { + public static String spoofParameter(String parameters, boolean isShortAndOpeningOrPlaying) { try { LogHelper.printDebug(() -> "Original protobuf parameter value: " + parameters); @@ -74,7 +69,7 @@ public class SpoofSignaturePatch { if (useOriginalStoryboardRenderer = parameters.length() > 150) return parameters; // Shorts do not need to be spoofed. - if (useOriginalStoryboardRenderer = parameters.startsWith(SHORTS_PLAYER_PARAMETERS)) { + if (useOriginalStoryboardRenderer = VideoInformation.playerParametersAreShort(parameters)) { isPlayingShorts = true; return parameters; } diff --git a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java index e08b48db..1712d40a 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/requests/ReturnYouTubeDislikeApi.java @@ -62,12 +62,12 @@ public class ReturnYouTubeDislikeApi { * How long to wait until API calls are resumed, if the API requested a back off. * No clear guideline of how long to wait until resuming. */ - private static final int BACKOFF_RATE_LIMIT_MILLISECONDS = 4 * 60 * 1000; // 4 Minutes. + private static final int BACKOFF_RATE_LIMIT_MILLISECONDS = 5 * 60 * 1000; // 5 Minutes. /** * How long to wait until API calls are resumed, if any connection error occurs. */ - private static final int BACKOFF_CONNECTION_ERROR_MILLISECONDS = 60 * 1000; // 60 Seconds. + private static final int BACKOFF_CONNECTION_ERROR_MILLISECONDS = 2 * 60 * 1000; // 2 Minutes. /** * If non zero, then the system time of when API calls can resume. diff --git a/app/src/main/java/app/revanced/integrations/settingsmenu/ReturnYouTubeDislikeSettingsFragment.java b/app/src/main/java/app/revanced/integrations/settingsmenu/ReturnYouTubeDislikeSettingsFragment.java index e269c932..5ee90efa 100644 --- a/app/src/main/java/app/revanced/integrations/settingsmenu/ReturnYouTubeDislikeSettingsFragment.java +++ b/app/src/main/java/app/revanced/integrations/settingsmenu/ReturnYouTubeDislikeSettingsFragment.java @@ -13,7 +13,6 @@ import android.preference.PreferenceScreen; import android.preference.SwitchPreference; import app.revanced.integrations.patches.ReturnYouTubeDislikePatch; -import app.revanced.integrations.patches.spoof.SpoofAppVersionPatch; import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike; import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi; import app.revanced.integrations.settings.SettingsEnum; @@ -21,9 +20,6 @@ import app.revanced.integrations.settings.SharedPrefCategory; public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment { - private static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER = - SpoofAppVersionPatch.isSpoofingToEqualOrLessThan("18.33.40"); - /** * If dislikes are shown on Shorts. */ @@ -79,7 +75,7 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment { shortsPreference.setChecked(SettingsEnum.RYD_SHORTS.getBoolean()); shortsPreference.setTitle(str("revanced_ryd_shorts_title")); String shortsSummary = str("revanced_ryd_shorts_summary_on", - IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER + ReturnYouTubeDislikePatch.IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER ? "" : "\n\n" + str("revanced_ryd_shorts_summary_disclaimer")); shortsPreference.setSummaryOn(shortsSummary); diff --git a/app/src/main/java/app/revanced/integrations/shared/PlayerType.kt b/app/src/main/java/app/revanced/integrations/shared/PlayerType.kt index e2777c86..b74a4d63 100644 --- a/app/src/main/java/app/revanced/integrations/shared/PlayerType.kt +++ b/app/src/main/java/app/revanced/integrations/shared/PlayerType.kt @@ -1,10 +1,11 @@ package app.revanced.integrations.shared +import app.revanced.integrations.patches.VideoInformation import app.revanced.integrations.utils.Event import app.revanced.integrations.utils.LogHelper /** - * WatchWhile player type + * WatchWhile player type. */ enum class PlayerType { /** @@ -83,6 +84,8 @@ enum class PlayerType { * Does not include the first moment after a short is opened when a regular video is minimized on screen, * or while watching a short with a regular video present on a spoofed 16.x version of YouTube. * To include those situations instead use [isNoneHiddenOrMinimized]. + * + * @see VideoInformation */ fun isNoneOrHidden(): Boolean { return this == NONE || this == HIDDEN @@ -99,6 +102,7 @@ enum class PlayerType { * though a Short is being opened or is on screen (see [isNoneHiddenOrMinimized]). * * @return If nothing, a Short, or a regular video is sliding off screen to a dismissed or hidden state. + * @see VideoInformation */ fun isNoneHiddenOrSlidingMinimized(): Boolean { return isNoneOrHidden() || this == WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED @@ -117,6 +121,7 @@ enum class PlayerType { * * @return If nothing, a Short, a regular video is sliding off screen to a dismissed or hidden state, * a regular video is minimized (and a new video is not being opened). + * @see VideoInformation */ fun isNoneHiddenOrMinimized(): Boolean { return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED From aa6f591141c42eeb86e1be841bc71cabbabb0287 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 4 Dec 2023 08:49:53 +0000 Subject: [PATCH 10/27] chore(release): 1.0.0-dev.4 [skip ci] # [1.0.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.3...v1.0.0-dev.4) (2023-12-04) ### Bug Fixes * **YouTube - Return YouTube Dislike:** Prevent the first Short opened from freezing the UI ([#532](https://github.com/ReVanced/revanced-integrations/issues/532)) ([0bb8669](https://github.com/ReVanced/revanced-integrations/commit/0bb86694e24a6a41edee62f5ef1bb80fe7bc3f19)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80b4e5eb..2364019d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.3...v1.0.0-dev.4) (2023-12-04) + + +### Bug Fixes + +* **YouTube - Return YouTube Dislike:** Prevent the first Short opened from freezing the UI ([#532](https://github.com/ReVanced/revanced-integrations/issues/532)) ([0bb8669](https://github.com/ReVanced/revanced-integrations/commit/0bb86694e24a6a41edee62f5ef1bb80fe7bc3f19)) + # [1.0.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.2...v1.0.0-dev.3) (2023-12-03) diff --git a/gradle.properties b/gradle.properties index 2fdf1726..12fcfca0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.0.0-dev.3 +version = 1.0.0-dev.4 From fb433da6ad652aee48fc92794de82bb914ab80ca Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Tue, 5 Dec 2023 03:47:31 +0400 Subject: [PATCH 11/27] fix(YouTube - Minimized playback): Fix PIP incorrectly shown for some Shorts playback (#533) --- .../patches/MinimizedPlaybackPatch.java | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/MinimizedPlaybackPatch.java b/app/src/main/java/app/revanced/integrations/patches/MinimizedPlaybackPatch.java index 6fcae6bf..43bf49fa 100644 --- a/app/src/main/java/app/revanced/integrations/patches/MinimizedPlaybackPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/MinimizedPlaybackPatch.java @@ -2,12 +2,39 @@ package app.revanced.integrations.patches; import app.revanced.integrations.shared.PlayerType; +@SuppressWarnings("unused") public class MinimizedPlaybackPatch { - public static boolean isPlaybackNotShort() { - return !PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized(); + /** + * Injection point. + */ + public static boolean playbackIsNotShort() { + // Steps to verify most edge cases: + // 1. Open a regular video + // 2. Minimize app (PIP should appear) + // 3. Reopen app + // 4. Open a Short (without closing the regular video) + // (try opening both Shorts in the video player suggestions AND Shorts from the home feed) + // 5. Minimize the app (PIP should not appear) + // 6. Reopen app + // 7. Close the Short + // 8. Resume playing the regular video + // 9. Minimize the app (PIP should appear) + + if (!VideoInformation.lastVideoIdIsShort()) { + return true; // Definitely is not a Short. + } + + // Might be a Short, or might be a prior regular video on screen again after a Short was closed. + // This incorrectly prevents PIP if player is in WATCH_WHILE_MINIMIZED after closing a Short, + // But there's no way around this unless an additional hook is added to definitively detect + // the Shorts player is on screen. This use case is unusual anyways so it's not a huge concern. + return !PlayerType.getCurrent().isNoneHiddenOrMinimized(); } + /** + * Injection point. + */ public static boolean overrideMinimizedPlaybackAvailable() { // This could be done entirely in the patch, // but having a unique method to search for makes manually inspecting the patched apk much easier. From e7758a7ac4c614a797855371f08e6eb5523ecc8a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 4 Dec 2023 23:50:11 +0000 Subject: [PATCH 12/27] chore(release): 1.0.0-dev.5 [skip ci] # [1.0.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2023-12-04) ### Bug Fixes * **YouTube - Minimized playback:** Fix PIP incorrectly shown for some Shorts playback ([#533](https://github.com/ReVanced/revanced-integrations/issues/533)) ([fb433da](https://github.com/ReVanced/revanced-integrations/commit/fb433da6ad652aee48fc92794de82bb914ab80ca)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2364019d..8cd02e86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2023-12-04) + + +### Bug Fixes + +* **YouTube - Minimized playback:** Fix PIP incorrectly shown for some Shorts playback ([#533](https://github.com/ReVanced/revanced-integrations/issues/533)) ([fb433da](https://github.com/ReVanced/revanced-integrations/commit/fb433da6ad652aee48fc92794de82bb914ab80ca)) + # [1.0.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.3...v1.0.0-dev.4) (2023-12-04) diff --git a/gradle.properties b/gradle.properties index 12fcfca0..8cbd1fce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.0.0-dev.4 +version = 1.0.0-dev.5 From f9102fa83bdb2b147543882cb8ebb80b5985ad3e Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 7 Dec 2023 02:18:09 +0100 Subject: [PATCH 13/27] fix(YouTube - Client spoof): Do not break clips --- .../patches/spoof/SpoofSignaturePatch.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java b/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java index ed29e44d..8e0e61d3 100644 --- a/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java @@ -1,19 +1,17 @@ package app.revanced.integrations.patches.spoof; -import static app.revanced.integrations.utils.ReVancedUtils.containsAny; - import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; - import androidx.annotation.Nullable; - import app.revanced.integrations.patches.VideoInformation; import app.revanced.integrations.patches.spoof.requests.StoryboardRendererRequester; import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.shared.PlayerType; import app.revanced.integrations.utils.LogHelper; +import static app.revanced.integrations.utils.ReVancedUtils.containsAny; + /** @noinspection unused*/ public class SpoofSignaturePatch { /** @@ -23,6 +21,11 @@ public class SpoofSignaturePatch { */ private static final String INCOGNITO_PARAMETERS = "CgIQBg=="; + /** + * Parameters used when playing clips. + */ + private static final String CLIPS_PARAMETERS = "kAIB"; + /** * Parameters causing playback issues. */ @@ -61,14 +64,20 @@ public class SpoofSignaturePatch { try { LogHelper.printDebug(() -> "Original protobuf parameter value: " + parameters); - if (!SettingsEnum.SPOOF_SIGNATURE.getBoolean()) return parameters; + if (!SettingsEnum.SPOOF_SIGNATURE.getBoolean()) { + return parameters; + } // Clip's player parameters contain a lot of information (e.g. video start and end time or whether it loops) // For this reason, the player parameters of a clip are usually very long (150~300 characters). // Clips are 60 seconds or less in length, so no spoofing. - if (useOriginalStoryboardRenderer = parameters.length() > 150) return parameters; + //noinspection AssignmentUsedAsCondition + if (useOriginalStoryboardRenderer = parameters.length() > 150 || containsAny(parameters, CLIPS_PARAMETERS)) { + return parameters; + } // Shorts do not need to be spoofed. + //noinspection AssignmentUsedAsCondition if (useOriginalStoryboardRenderer = VideoInformation.playerParametersAreShort(parameters)) { isPlayingShorts = true; return parameters; @@ -78,6 +87,7 @@ public class SpoofSignaturePatch { boolean isPlayingFeed = PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL && containsAny(parameters, AUTOPLAY_PARAMETERS); if (isPlayingFeed) { + //noinspection AssignmentUsedAsCondition if (useOriginalStoryboardRenderer = !SettingsEnum.SPOOF_SIGNATURE_IN_FEED.getBoolean()) { // Don't spoof the feed video playback. This will cause video playback issues, // but only if user continues watching for more than 1 minute. From 9c635e5f91e7eec49b709ff6cfb5db3a68c61e91 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 7 Dec 2023 19:03:21 +0000 Subject: [PATCH 14/27] chore(release): 1.0.0-dev.6 [skip ci] # [1.0.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2023-12-07) ### Bug Fixes * **YouTube - Client spoof:** Do not break clips ([f9102fa](https://github.com/ReVanced/revanced-integrations/commit/f9102fa83bdb2b147543882cb8ebb80b5985ad3e)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cd02e86..e02b4703 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2023-12-07) + + +### Bug Fixes + +* **YouTube - Client spoof:** Do not break clips ([f9102fa](https://github.com/ReVanced/revanced-integrations/commit/f9102fa83bdb2b147543882cb8ebb80b5985ad3e)) + # [1.0.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.4...v1.0.0-dev.5) (2023-12-04) diff --git a/gradle.properties b/gradle.properties index 8cbd1fce..57599768 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.0.0-dev.5 +version = 1.0.0-dev.6 From 92e8619cd7bbcf82f27e9407e18c30d65214e31c Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Sat, 9 Dec 2023 14:31:20 +0400 Subject: [PATCH 15/27] fix(YouTube - Spoof signature): Wait until storyboard fetch is done (#535) --- .../patches/spoof/SpoofSignaturePatch.java | 61 ++++++++++++++----- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java b/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java index 8e0e61d3..58ef4675 100644 --- a/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/spoof/SpoofSignaturePatch.java @@ -1,16 +1,24 @@ package app.revanced.integrations.patches.spoof; +import static app.revanced.integrations.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer; +import static app.revanced.integrations.utils.ReVancedUtils.containsAny; + import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; + import androidx.annotation.Nullable; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import app.revanced.integrations.patches.VideoInformation; -import app.revanced.integrations.patches.spoof.requests.StoryboardRendererRequester; import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.shared.PlayerType; import app.revanced.integrations.utils.LogHelper; - -import static app.revanced.integrations.utils.ReVancedUtils.containsAny; +import app.revanced.integrations.utils.ReVancedUtils; /** @noinspection unused*/ public class SpoofSignaturePatch { @@ -47,12 +55,30 @@ public class SpoofSignaturePatch { private static volatile String lastPlayerResponseVideoId; @Nullable - private static volatile StoryboardRenderer videoRenderer; + private static volatile Future rendererFuture; private static volatile boolean useOriginalStoryboardRenderer; private static volatile boolean isPlayingShorts; + @Nullable + private static StoryboardRenderer getRenderer(boolean waitForCompletion) { + Future future = rendererFuture; + if (future != null) { + try { + if (waitForCompletion || future.isDone()) { + return future.get(20000, TimeUnit.MILLISECONDS); // Any arbitrarily large timeout. + } // else, return null. + } catch (TimeoutException ex) { + LogHelper.printDebug(() -> "Could not get renderer (get timed out)"); + } catch (ExecutionException | InterruptedException ex) { + // Should never happen. + LogHelper.printException(() -> "Could not get renderer", ex); + } + } + return null; + } + /** * Injection point. * @@ -108,27 +134,32 @@ public class SpoofSignaturePatch { private static void fetchStoryboardRenderer() { if (!SettingsEnum.SPOOF_STORYBOARD_RENDERER.getBoolean()) { lastPlayerResponseVideoId = null; - videoRenderer = null; + rendererFuture = null; return; } String videoId = VideoInformation.getPlayerResponseVideoId(); if (!videoId.equals(lastPlayerResponseVideoId)) { + rendererFuture = ReVancedUtils.submitOnBackgroundThread(() -> getStoryboardRenderer(videoId)); lastPlayerResponseVideoId = videoId; - // This will block starting video playback until the fetch completes. - // This is desired because if this returns without finishing the fetch, - // then video will start playback but the image will be frozen - // while the main thread call for the renderer waits for the fetch to complete. - videoRenderer = StoryboardRendererRequester.getStoryboardRenderer(videoId); } + // Block until the renderer fetch completes. + // This is desired because if this returns without finishing the fetch + // then video will start playback but the storyboard is not ready yet. + getRenderer(true); } private static String getStoryboardRendererSpec(String originalStoryboardRendererSpec, boolean returnNullIfLiveStream) { if (SettingsEnum.SPOOF_SIGNATURE.getBoolean() && !useOriginalStoryboardRenderer) { - StoryboardRenderer renderer = videoRenderer; + StoryboardRenderer renderer = getRenderer(false); if (renderer != null) { - if (returnNullIfLiveStream && renderer.isLiveStream()) return null; - return renderer.getSpec(); + if (returnNullIfLiveStream && renderer.isLiveStream()) { + return null; + } + String spec = renderer.getSpec(); + if (spec != null) { + return spec; + } } } @@ -159,7 +190,7 @@ public class SpoofSignaturePatch { */ public static int getRecommendedLevel(int originalLevel) { if (SettingsEnum.SPOOF_SIGNATURE.getBoolean() && !useOriginalStoryboardRenderer) { - StoryboardRenderer renderer = videoRenderer; + StoryboardRenderer renderer = getRenderer(false); if (renderer != null) { Integer recommendedLevel = renderer.getRecommendedLevel(); if (recommendedLevel != null) return recommendedLevel; @@ -177,7 +208,7 @@ public class SpoofSignaturePatch { if (!SettingsEnum.SPOOF_SIGNATURE.getBoolean()) { return false; } - StoryboardRenderer renderer = videoRenderer; + StoryboardRenderer renderer = getRenderer(false); if (renderer == null) { // Spoof storyboard renderer is turned off, // video is paid, or the storyboard fetch timed out. From fa5713b4a0297d89c0d07bb1500459d570f78668 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 9 Dec 2023 10:33:48 +0000 Subject: [PATCH 16/27] chore(release): 1.0.0-dev.7 [skip ci] # [1.0.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.6...v1.0.0-dev.7) (2023-12-09) ### Bug Fixes * **YouTube - Spoof signature:** Wait until storyboard fetch is done ([#535](https://github.com/ReVanced/revanced-integrations/issues/535)) ([92e8619](https://github.com/ReVanced/revanced-integrations/commit/92e8619cd7bbcf82f27e9407e18c30d65214e31c)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e02b4703..dd159ffc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.6...v1.0.0-dev.7) (2023-12-09) + + +### Bug Fixes + +* **YouTube - Spoof signature:** Wait until storyboard fetch is done ([#535](https://github.com/ReVanced/revanced-integrations/issues/535)) ([92e8619](https://github.com/ReVanced/revanced-integrations/commit/92e8619cd7bbcf82f27e9407e18c30d65214e31c)) + # [1.0.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.5...v1.0.0-dev.6) (2023-12-07) diff --git a/gradle.properties b/gradle.properties index 57599768..dd03ee2a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.0.0-dev.6 +version = 1.0.0-dev.7 From fb56e9a36272cb66869a51b24c7f1ea0c29dcc53 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sun, 10 Dec 2023 21:57:09 +0100 Subject: [PATCH 17/27] build: Simplify enabling local build cache --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 4715a048..b06d31df 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ rootProject.name = "revanced-integrations" buildCache { local { - isEnabled = !System.getenv().containsKey("CI") + isEnabled = "CI" !in System.getenv() } } From 0ce92c284d08a1c6bffba976e9cf208e82288ddf Mon Sep 17 00:00:00 2001 From: nullptr <107796137+johnconner122@users.noreply.github.com> Date: Mon, 11 Dec 2023 03:44:10 +0500 Subject: [PATCH 18/27] fix(YouTube - Announcements): Don't show error toast if there is no internet connection (#537) --- .../integrations/patches/announcements/AnnouncementsPatch.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/app/revanced/integrations/patches/announcements/AnnouncementsPatch.java b/app/src/main/java/app/revanced/integrations/patches/announcements/AnnouncementsPatch.java index e68d10c1..9961c4be 100644 --- a/app/src/main/java/app/revanced/integrations/patches/announcements/AnnouncementsPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/announcements/AnnouncementsPatch.java @@ -32,6 +32,9 @@ public final class AnnouncementsPatch { public static void showAnnouncement(final Activity context) { if (!SettingsEnum.ANNOUNCEMENTS.getBoolean()) return; + // Check if there is internet connection + if (!ReVancedUtils.isNetworkConnected()) return; + ReVancedUtils.runOnBackgroundThread(() -> { try { HttpURLConnection connection = AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT, CONSUMER); From 46dbbf5f869d8353d3c924598dc14dc407e26727 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 10 Dec 2023 22:46:37 +0000 Subject: [PATCH 19/27] chore(release): 1.0.0-dev.8 [skip ci] # [1.0.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.7...v1.0.0-dev.8) (2023-12-10) ### Bug Fixes * **YouTube - Announcements:** Don't show error toast if there is no internet connection ([#537](https://github.com/ReVanced/revanced-integrations/issues/537)) ([0ce92c2](https://github.com/ReVanced/revanced-integrations/commit/0ce92c284d08a1c6bffba976e9cf208e82288ddf)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd159ffc..8476deb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.7...v1.0.0-dev.8) (2023-12-10) + + +### Bug Fixes + +* **YouTube - Announcements:** Don't show error toast if there is no internet connection ([#537](https://github.com/ReVanced/revanced-integrations/issues/537)) ([0ce92c2](https://github.com/ReVanced/revanced-integrations/commit/0ce92c284d08a1c6bffba976e9cf208e82288ddf)) + # [1.0.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.6...v1.0.0-dev.7) (2023-12-09) diff --git a/gradle.properties b/gradle.properties index dd03ee2a..1b0ddae0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.0.0-dev.7 +version = 1.0.0-dev.8 From c4ee6ca4dde13ab8ce6f9cf94f1910455f9d9ecc Mon Sep 17 00:00:00 2001 From: Chris <52449218+shadow578@users.noreply.github.com> Date: Mon, 11 Dec 2023 02:06:55 +0100 Subject: [PATCH 20/27] feat(YouTube - Alternative Thumbnails): Add option to use DeArrow (#534) Co-authored-by: oSumAtrIX Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> --- .../patches/AlternativeThumbnailsPatch.java | 419 +++++++++++++----- .../announcements/AnnouncementsPatch.java | 4 +- .../patches/components/LithoFilterPatch.java | 2 +- .../speed/CustomPlaybackSpeedPatch.java | 2 +- .../patches/theme/SeekbarColorPatch.java | 2 +- .../integrations/settings/SettingsEnum.java | 21 +- ...ativeThumbnailsAboutDeArrowPreference.java | 35 ++ ...AlternativeThumbnailsStatusPreference.java | 85 ++++ .../SponsorBlockSettingsFragment.java | 6 +- .../java/org/chromium/net/UrlRequest.java | 4 + .../chromium/net/impl/CronetUrlRequest.java | 11 + 11 files changed, 469 insertions(+), 122 deletions(-) create mode 100644 app/src/main/java/app/revanced/integrations/settingsmenu/AlternativeThumbnailsAboutDeArrowPreference.java create mode 100644 app/src/main/java/app/revanced/integrations/settingsmenu/AlternativeThumbnailsStatusPreference.java create mode 100644 dummy/src/main/java/org/chromium/net/UrlRequest.java create mode 100644 dummy/src/main/java/org/chromium/net/impl/CronetUrlRequest.java diff --git a/app/src/main/java/app/revanced/integrations/patches/AlternativeThumbnailsPatch.java b/app/src/main/java/app/revanced/integrations/patches/AlternativeThumbnailsPatch.java index 1e4d3f05..49d84103 100644 --- a/app/src/main/java/app/revanced/integrations/patches/AlternativeThumbnailsPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/AlternativeThumbnailsPatch.java @@ -1,11 +1,17 @@ package app.revanced.integrations.patches; +import android.net.Uri; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - +import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.utils.LogHelper; +import app.revanced.integrations.utils.ReVancedUtils; +import org.chromium.net.UrlRequest; import org.chromium.net.UrlResponseInfo; +import org.chromium.net.impl.CronetUrlRequest; +import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.util.HashMap; @@ -13,30 +19,289 @@ import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ExecutionException; -import app.revanced.integrations.settings.SettingsEnum; -import app.revanced.integrations.utils.LogHelper; -import app.revanced.integrations.utils.ReVancedUtils; +import static app.revanced.integrations.utils.StringRef.str; /** - * Alternative YouTube thumbnails, showing the beginning/middle/end of the video. + * Alternative YouTube thumbnails. + *

+ * Can show YouTube provided screen captures of beginning/middle/end of the video. * (ie: sd1.jpg, sd2.jpg, sd3.jpg). - * - * Has an additional option to use 'fast' thumbnails, + *

+ * Or can show crowdsourced thumbnails provided by DeArrow (...). + *

+ * Or can use DeArrow and fall back to screen captures if DeArrow is not available. + *

+ * Has an additional option to use 'fast' video still thumbnails, * where it forces sd thumbnail quality and skips verifying if the alt thumbnail image exists. - * The UI loading time will be the same or better than using the the original thumbnails, + * The UI loading time will be the same or better than using original thumbnails, * but thumbnails will initially fail to load for all live streams, unreleased, and occasionally very old videos. * If a failed thumbnail load is reloaded (ie: scroll off, then on screen), then the original thumbnail * is reloaded instead. Fast thumbnails requires using SD or lower thumbnail resolution, - * because a noticeable number of videos do not have hq720 and too many fail to load. - * + * because a noticeable number of videos do not have hq720 and too much fail to load. + *

* Ideas for improvements: * - Selectively allow using original thumbnails in some situations, * such as videos subscription feed, watch history, or in search results. * - Save to a temporary file the video id's verified to have alt thumbnails. * This would speed up loading the watch history and users saved playlists. */ +@SuppressWarnings("unused") public final class AlternativeThumbnailsPatch { + private static final Uri dearrowApiUri; + + /** + * The scheme and host of {@link #dearrowApiUri}. + */ + private static final String deArrowApiUrlPrefix; + + /** + * How long to temporarily turn off DeArrow if it fails for any reason. + */ + private static final long DEARROW_FAILURE_API_BACKOFF_MILLISECONDS = 5 * 60 * 1000; // 5 Minutes. + + /** + * If non zero, then the system time of when DeArrow API calls can resume. + */ + private static volatile long timeToResumeDeArrowAPICalls; + + static { + dearrowApiUri = validateSettings(); + final int port = dearrowApiUri.getPort(); + String portString = port == -1 ? "" : (":" + port); + deArrowApiUrlPrefix = dearrowApiUri.getScheme() + "://" + dearrowApiUri.getHost() + portString + "/"; + LogHelper.printDebug(() -> "Using DeArrow API address: " + deArrowApiUrlPrefix); + } + + /** + * Fix any bad imported data. + */ + private static Uri validateSettings() { + final int altThumbnailType = SettingsEnum.ALT_THUMBNAIL_STILLS_TIME.getInt(); + if (altThumbnailType < 1 || altThumbnailType > 3) { + ReVancedUtils.showToastLong("Invalid Alternative still thumbnail type: " + + altThumbnailType + ". Using default"); + SettingsEnum.ALT_THUMBNAIL_STILLS_TIME.resetToDefault(); + } + + Uri apiUri = Uri.parse(SettingsEnum.ALT_THUMBNAIL_DEARROW_API_URL.getString()); + // Cannot use unsecured 'http', otherwise the connections fail to start and no callbacks hooks are made. + String scheme = apiUri.getScheme(); + if (scheme == null || scheme.equals("http") || apiUri.getHost() == null) { + ReVancedUtils.showToastLong("Invalid DeArrow API URL. Using default"); + SettingsEnum.ALT_THUMBNAIL_DEARROW_API_URL.resetToDefault(); + return validateSettings(); + } + return apiUri; + } + + private static boolean usingDeArrow() { + return SettingsEnum.ALT_THUMBNAIL_DEARROW.getBoolean(); + } + + private static boolean usingVideoStills() { + return SettingsEnum.ALT_THUMBNAIL_STILLS.getBoolean(); + } + + /** + * Build the alternative thumbnail url using YouTube provided still video captures. + * + * @param decodedUrl Decoded original thumbnail request url. + * @return The alternative thumbnail url, or the original url. Both without tracking parameters. + */ + @NonNull + private static String buildYoutubeVideoStillURL(@NonNull DecodedThumbnailUrl decodedUrl, + @NonNull ThumbnailQuality qualityToUse) { + String sanitizedReplacement = decodedUrl.createStillsUrl(qualityToUse, false); + if (VerifiedQualities.verifyAltThumbnailExist(decodedUrl.videoId, qualityToUse, sanitizedReplacement)) { + return sanitizedReplacement; + } + return decodedUrl.sanitizedUrl; + } + + /** + * Build the alternative thumbnail url using DeArrow thumbnail cache. + * + * @param videoId ID of the video to get a thumbnail of. Can be any video (regular or Short). + * @param fallbackUrl URL to fall back to in case. + * @return The alternative thumbnail url, without tracking parameters. + */ + @NonNull + private static String buildDeArrowThumbnailURL(String videoId, String fallbackUrl) { + // Build thumbnail request url. + // See https://github.com/ajayyy/DeArrowThumbnailCache/blob/29eb4359ebdf823626c79d944a901492d760bbbc/app.py#L29. + return dearrowApiUri + .buildUpon() + .appendQueryParameter("videoID", videoId) + .appendQueryParameter("redirectUrl", fallbackUrl) + .build() + .toString(); + } + + private static boolean urlIsDeArrow(@NonNull String imageUrl) { + return imageUrl.startsWith(deArrowApiUrlPrefix); + } + + /** + * @return If this client has not recently experienced any DeArrow API errors. + */ + private static boolean canUseDeArrowAPI() { + if (timeToResumeDeArrowAPICalls == 0) { + return true; + } + if (timeToResumeDeArrowAPICalls < System.currentTimeMillis()) { + LogHelper.printDebug(() -> "Resuming DeArrow API calls"); + timeToResumeDeArrowAPICalls = 0; + return true; + } + return false; + } + + private static void handleDeArrowError(@NonNull String url, int statusCode) { + LogHelper.printDebug(() -> "Encountered DeArrow error. Url: " + url); + final long now = System.currentTimeMillis(); + if (timeToResumeDeArrowAPICalls < now) { + timeToResumeDeArrowAPICalls = now + DEARROW_FAILURE_API_BACKOFF_MILLISECONDS; + if (SettingsEnum.ALT_THUMBNAIL_DEARROW_CONNECTION_TOAST.getBoolean()) { + String toastMessage = (statusCode != 0) + ? str("revanced_alt_thumbnail_dearrow_error", statusCode) + : str("revanced_alt_thumbnail_dearrow_error_generic"); + ReVancedUtils.showToastLong(toastMessage); + } + } + } + + /** + * Injection point. Called off the main thread and by multiple threads at the same time. + * + * @param originalUrl Image url for all url images loaded, including video thumbnails. + */ + public static String overrideImageURL(String originalUrl) { + try { + final boolean usingDeArrow = usingDeArrow(); + final boolean usingVideoStills = usingVideoStills(); + if (!usingDeArrow && !usingVideoStills) { + return originalUrl; + } + + final var decodedUrl = DecodedThumbnailUrl.decodeImageUrl(originalUrl); + if (decodedUrl == null) { + return originalUrl; // Not a thumbnail. + } + + LogHelper.printDebug(() -> "Original url: " + decodedUrl.sanitizedUrl); + + ThumbnailQuality qualityToUse = ThumbnailQuality.getQualityToUse(decodedUrl.imageQuality); + if (qualityToUse == null) { + // Thumbnail is a Short or a Storyboard image used for seekbar thumbnails (must not replace these). + return originalUrl; + } + + String sanitizedReplacementUrl; + final boolean includeTracking; + if (usingDeArrow && canUseDeArrowAPI()) { + includeTracking = false; // Do not include view tracking parameters with API call. + final String fallbackUrl = usingVideoStills + ? buildYoutubeVideoStillURL(decodedUrl, qualityToUse) + : decodedUrl.sanitizedUrl; + + sanitizedReplacementUrl = buildDeArrowThumbnailURL(decodedUrl.videoId, fallbackUrl); + } else if (usingVideoStills) { + includeTracking = true; // Include view tracking parameters if present. + sanitizedReplacementUrl = buildYoutubeVideoStillURL(decodedUrl, qualityToUse); + } else { + return originalUrl; // Recently experienced DeArrow failure and video stills are not enabled. + } + + // Do not log any tracking parameters. + LogHelper.printDebug(() -> "Replacement url: " + sanitizedReplacementUrl); + + return includeTracking + ? sanitizedReplacementUrl + decodedUrl.viewTrackingParameters + : sanitizedReplacementUrl; + } catch (Exception ex) { + LogHelper.printException(() -> "overrideImageURL failure", ex); + return originalUrl; + } + } + + /** + * Injection point. + *

+ * Cronet considers all completed connections as a success, even if the response is 404 or 5xx. + */ + public static void handleCronetSuccess(UrlRequest request, @NonNull UrlResponseInfo responseInfo) { + try { + final int statusCode = responseInfo.getHttpStatusCode(); + if (statusCode != 200) { + String url = responseInfo.getUrl(); + + if (usingDeArrow() && urlIsDeArrow(url)) { + LogHelper.printDebug(() -> "handleCronetSuccess, statusCode: " + statusCode); + handleDeArrowError(url, statusCode); + return; + } + + if (usingVideoStills() && statusCode == 404) { + // Fast alt thumbnails is enabled and the thumbnail is not available. + // The video is: + // - live stream + // - upcoming unreleased video + // - very old + // - very low view count + // Take note of this, so if the image reloads the original thumbnail will be used. + DecodedThumbnailUrl decodedUrl = DecodedThumbnailUrl.decodeImageUrl(url); + if (decodedUrl == null) { + return; // Not a thumbnail. + } + + LogHelper.printDebug(() -> "handleCronetSuccess, image not available: " + url); + + ThumbnailQuality quality = ThumbnailQuality.altImageNameToQuality(decodedUrl.imageQuality); + if (quality == null) { + // Video is a short or a seekbar thumbnail, but somehow did not load. Should not happen. + LogHelper.printDebug(() -> "Failed to recognize image quality of url: " + decodedUrl.sanitizedUrl); + return; + } + + VerifiedQualities.setAltThumbnailDoesNotExist(decodedUrl.videoId, quality); + } + } + } catch (Exception ex) { + LogHelper.printException(() -> "Callback success error", ex); + } + } + + /** + * Injection point. + *

+ * To test failure cases, try changing the API URL to each of: + * - A non-existent domain. + * - A url path of something incorrect (ie: /v1/nonExistentEndPoint). + *

+ * Known limitation: YT uses an infinite timeout, so this hook is never called if a host never responds. + * But this does not appear to be a problem, as the DeArrow API has not been observed to 'go silent' + * Instead if there's a problem it returns an error code status response, which is handled in this patch. + */ + public static void handleCronetFailure(UrlRequest request, + @Nullable UrlResponseInfo responseInfo, + IOException exception) { + try { + if (usingDeArrow()) { + String url = ((CronetUrlRequest) request).getHookedUrl(); + if (urlIsDeArrow(url)) { + LogHelper.printDebug(() -> "handleCronetFailure, exception: " + exception); + final int statusCode = (responseInfo != null) + ? responseInfo.getHttpStatusCode() + : 0; + handleDeArrowError(url, statusCode); + } + } + } catch (Exception ex) { + LogHelper.printException(() -> "Callback failure error", ex); + } + } + private enum ThumbnailQuality { // In order of lowest to highest resolution. DEFAULT("default", ""), // effective alt name is 1.jpg, 2.jpg, 3.jpg @@ -61,6 +326,11 @@ public final class AlternativeThumbnailsPatch { originalNameToEnum.put(quality.originalName, quality); for (int i = 1; i <= 3; i++) { + // 'custom' thumbnails set by the content creator. + // These show up in place of regular thumbnails + // and seem to be limited to [1, 3] range. + originalNameToEnum.put(quality.originalName + "_custom_" + i, quality); + altNameToEnum.put(quality.altImageName + i, quality); } } @@ -86,7 +356,7 @@ public final class AlternativeThumbnailsPatch { return null; // Not a thumbnail for a regular video. } - final boolean useFastQuality = SettingsEnum.ALT_THUMBNAIL_FAST_QUALITY.getBoolean(); + final boolean useFastQuality = SettingsEnum.ALT_THUMBNAIL_STILLS_FAST.getBoolean(); switch (quality) { case SDDEFAULT: // SD alt images have somewhat worse quality with washed out color and poor contrast. @@ -121,7 +391,7 @@ public final class AlternativeThumbnailsPatch { } String getAltImageNameToUse() { - return altImageName + SettingsEnum.ALT_THUMBNAIL_TYPE.getInt(); + return altImageName + SettingsEnum.ALT_THUMBNAIL_STILLS_TIME.getInt(); } } @@ -146,7 +416,7 @@ public final class AlternativeThumbnailsPatch { @Override protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > CACHE_LIMIT; // Evict oldest entry if over the cache limit. + return size() > CACHE_LIMIT; // Evict the oldest entry if over the cache limit. } }; @@ -166,13 +436,14 @@ public final class AlternativeThumbnailsPatch { static boolean verifyAltThumbnailExist(@NonNull String videoId, @NonNull ThumbnailQuality quality, @NonNull String imageUrl) { - VerifiedQualities verified = getVerifiedQualities(videoId, SettingsEnum.ALT_THUMBNAIL_FAST_QUALITY.getBoolean()); + VerifiedQualities verified = getVerifiedQualities(videoId, SettingsEnum.ALT_THUMBNAIL_STILLS_FAST.getBoolean()); if (verified == null) return true; // Fast alt thumbnails is enabled. return verified.verifyYouTubeThumbnailExists(videoId, quality, imageUrl); } static void setAltThumbnailDoesNotExist(@NonNull String videoId, @NonNull ThumbnailQuality quality) { VerifiedQualities verified = getVerifiedQualities(videoId, false); + //noinspection ConstantConditions verified.setQualityVerified(videoId, quality, false); } @@ -180,20 +451,20 @@ public final class AlternativeThumbnailsPatch { * Highest quality verified as existing. */ @Nullable - ThumbnailQuality highestQualityVerified; + private ThumbnailQuality highestQualityVerified; /** * Lowest quality verified as not existing. */ @Nullable - ThumbnailQuality lowestQualityNotAvailable; + private ThumbnailQuality lowestQualityNotAvailable; /** * System time, of when to invalidate {@link #lowestQualityNotAvailable}. * Used only if fast mode is not enabled. */ - long timeToReVerifyLowestQuality; + private long timeToReVerifyLowestQuality; - synchronized void setQualityVerified(String videoId, ThumbnailQuality quality, boolean isVerified) { + private synchronized void setQualityVerified(String videoId, ThumbnailQuality quality, boolean isVerified) { if (isVerified) { if (highestQualityVerified == null || highestQualityVerified.ordinal() < quality.ordinal()) { highestQualityVerified = quality; @@ -216,7 +487,7 @@ public final class AlternativeThumbnailsPatch { return true; // Previously verified as existing. } - final boolean fastQuality = SettingsEnum.ALT_THUMBNAIL_FAST_QUALITY.getBoolean(); + final boolean fastQuality = SettingsEnum.ALT_THUMBNAIL_STILLS_FAST.getBoolean(); if (lowestQualityNotAvailable != null && lowestQualityNotAvailable.ordinal() <= quality.ordinal()) { if (fastQuality || System.currentTimeMillis() < timeToReVerifyLowestQuality) { return false; // Previously verified as not existing. @@ -279,131 +550,61 @@ public final class AlternativeThumbnailsPatch { static DecodedThumbnailUrl decodeImageUrl(String url) { final int videoIdStartIndex = url.indexOf('/', YOUTUBE_THUMBNAIL_PREFIX.length()) + 1; if (videoIdStartIndex <= 0) return null; + final int videoIdEndIndex = url.indexOf('/', videoIdStartIndex); if (videoIdEndIndex < 0) return null; + final int imageSizeStartIndex = videoIdEndIndex + 1; final int imageSizeEndIndex = url.indexOf('.', imageSizeStartIndex); if (imageSizeEndIndex < 0) return null; + int imageExtensionEndIndex = url.indexOf('?', imageSizeEndIndex); if (imageExtensionEndIndex < 0) imageExtensionEndIndex = url.length(); + return new DecodedThumbnailUrl(url, videoIdStartIndex, videoIdEndIndex, imageSizeStartIndex, imageSizeEndIndex, imageExtensionEndIndex); } + final String originalFullUrl; /** Full usable url, but stripped of any tracking information. */ final String sanitizedUrl; - /** Url up to the video id. */ + /** Url up to the video ID. */ final String urlPrefix; final String videoId; /** Quality, such as hq720 or sddefault. */ final String imageQuality; - /** jpg or webp */ + /** JPG or WEBP */ final String imageExtension; /** User view tracking parameters, only present on some images. */ - final String urlTrackingParameters; + final String viewTrackingParameters; - private DecodedThumbnailUrl(String fullUrl, int videoIdStartIndex, int videoIdEndIndex, - int imageSizeStartIndex, int imageSizeEndIndex, int imageExtensionEndIndex) { + DecodedThumbnailUrl(String fullUrl, int videoIdStartIndex, int videoIdEndIndex, + int imageSizeStartIndex, int imageSizeEndIndex, int imageExtensionEndIndex) { + originalFullUrl = fullUrl; sanitizedUrl = fullUrl.substring(0, imageExtensionEndIndex); urlPrefix = fullUrl.substring(0, videoIdStartIndex); videoId = fullUrl.substring(videoIdStartIndex, videoIdEndIndex); imageQuality = fullUrl.substring(imageSizeStartIndex, imageSizeEndIndex); imageExtension = fullUrl.substring(imageSizeEndIndex + 1, imageExtensionEndIndex); - urlTrackingParameters = (imageExtensionEndIndex == fullUrl.length()) + viewTrackingParameters = (imageExtensionEndIndex == fullUrl.length()) ? "" : fullUrl.substring(imageExtensionEndIndex); } - } - - static { - // Fix any bad imported data. - final int altThumbnailType = SettingsEnum.ALT_THUMBNAIL_TYPE.getInt(); - if (altThumbnailType < 1 || altThumbnailType > 3) { - LogHelper.printException(() -> "Invalid alt thumbnail type: " + altThumbnailType); - SettingsEnum.ALT_THUMBNAIL_TYPE.saveValue(SettingsEnum.ALT_THUMBNAIL_TYPE.defaultValue); - } - } - - /** - * Injection point. Called off the main thread and by multiple threads at the same time. - * - * @param originalUrl Image url for all url images loaded, including video thumbnails. - */ - public static String overrideImageURL(String originalUrl) { - try { - if (!SettingsEnum.ALT_THUMBNAIL.getBoolean()) { - return originalUrl; - } - DecodedThumbnailUrl decodedUrl = DecodedThumbnailUrl.decodeImageUrl(originalUrl); - if (decodedUrl == null) { - return originalUrl; // Not a thumbnail. - } - - // Keep any tracking parameters out of the logs, and log only the base url. - LogHelper.printDebug(() -> "Original url: " + decodedUrl.sanitizedUrl); - - ThumbnailQuality qualityToUse = ThumbnailQuality.getQualityToUse(decodedUrl.imageQuality); - if (qualityToUse == null) return originalUrl; // Video is a short. + /** @noinspection SameParameterValue*/ + String createStillsUrl(@NonNull ThumbnailQuality qualityToUse, boolean includeViewTracking) { // Images could be upgraded to webp if they are not already, but this fails quite often, // especially for new videos uploaded in the last hour. // And even if alt webp images do exist, sometimes they can load much slower than the original jpg alt images. // (as much as 4x slower has been observed, despite the alt webp image being a smaller file). - - StringBuilder builder = new StringBuilder(originalUrl.length() + 2); - builder.append(decodedUrl.urlPrefix); - builder.append(decodedUrl.videoId).append('/'); + StringBuilder builder = new StringBuilder(originalFullUrl.length() + 2); + builder.append(urlPrefix); + builder.append(videoId).append('/'); builder.append(qualityToUse.getAltImageNameToUse()); - builder.append('.').append(decodedUrl.imageExtension); - - String sanitizedReplacement = builder.toString(); - if (!VerifiedQualities.verifyAltThumbnailExist(decodedUrl.videoId, qualityToUse, sanitizedReplacement)) { - return originalUrl; + builder.append('.').append(imageExtension); + if (includeViewTracking) { + builder.append(viewTrackingParameters); } - - LogHelper.printDebug(() -> "Replaced url: " + sanitizedReplacement); - - // URL tracking parameters. Presumably they are to determine if a user has viewed a thumbnail. - // This likely is used for recommendations, so they are retained if present. - builder.append(decodedUrl.urlTrackingParameters); return builder.toString(); - } catch (Exception ex) { - LogHelper.printException(() -> "Alt thumbnails failure", ex); - return originalUrl; } } - - /** - * Injection point. - * - * Cronet considers all completed connections as a success, even if the response is 404 or 5xx. - */ - public static void handleCronetSuccess(@NonNull UrlResponseInfo responseInfo) { - try { - if (responseInfo.getHttpStatusCode() == 404 && SettingsEnum.ALT_THUMBNAIL.getBoolean()) { - // Fast alt thumbnails is enabled and the thumbnail is not available. - // The video is: - // - live stream - // - upcoming unreleased video - // - very old - // - very low view count - // Take note of this, so if the image reloads the original thumbnail will be used. - DecodedThumbnailUrl decodedUrl = DecodedThumbnailUrl.decodeImageUrl(responseInfo.getUrl()); - if (decodedUrl == null) { - return; // Not a thumbnail. - } - - ThumbnailQuality quality = ThumbnailQuality.altImageNameToQuality(decodedUrl.imageQuality); - if (quality == null) { - // Video is a short or unknown quality, but the url returned 404. Should never happen. - LogHelper.printDebug(() -> "Failed to load unknown url: " + decodedUrl.sanitizedUrl); - return; - } - - VerifiedQualities.setAltThumbnailDoesNotExist(decodedUrl.videoId, quality); - } - } catch (Exception ex) { - LogHelper.printException(() -> "Alt thumbnails callback failure", ex); - } - } - } diff --git a/app/src/main/java/app/revanced/integrations/patches/announcements/AnnouncementsPatch.java b/app/src/main/java/app/revanced/integrations/patches/announcements/AnnouncementsPatch.java index 9961c4be..81148c1f 100644 --- a/app/src/main/java/app/revanced/integrations/patches/announcements/AnnouncementsPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/announcements/AnnouncementsPatch.java @@ -46,7 +46,7 @@ public final class AnnouncementsPatch { if (connection.getResponseCode() != 200) { if (SettingsEnum.ANNOUNCEMENT_LAST_HASH.getString().isEmpty()) return; - SettingsEnum.ANNOUNCEMENT_LAST_HASH.saveValue(""); + SettingsEnum.ANNOUNCEMENT_LAST_HASH.resetToDefault(); ReVancedUtils.showToastLong("Failed to get announcement"); return; @@ -121,7 +121,7 @@ public final class AnnouncementsPatch { */ private static boolean emptyLastAnnouncementHash() { if (SettingsEnum.ANNOUNCEMENT_LAST_HASH.getString().isEmpty()) return true; - SettingsEnum.ANNOUNCEMENT_LAST_HASH.saveValue(""); + SettingsEnum.ANNOUNCEMENT_LAST_HASH.resetToDefault(); return false; } diff --git a/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java b/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java index 8712a551..96b0976e 100644 --- a/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/components/LithoFilterPatch.java @@ -133,7 +133,7 @@ final class CustomFilterGroup extends StringFilterGroup { for (String pattern : patterns) { if (!StringTrieSearch.isValidPattern(pattern)) { ReVancedUtils.showToastLong("Invalid custom filter, resetting to default"); - setting.saveValue(setting.defaultValue); + setting.resetToDefault(); return getFilterPatterns(setting); } } diff --git a/app/src/main/java/app/revanced/integrations/patches/playback/speed/CustomPlaybackSpeedPatch.java b/app/src/main/java/app/revanced/integrations/patches/playback/speed/CustomPlaybackSpeedPatch.java index 3c281722..12e962d0 100644 --- a/app/src/main/java/app/revanced/integrations/patches/playback/speed/CustomPlaybackSpeedPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/playback/speed/CustomPlaybackSpeedPatch.java @@ -43,7 +43,7 @@ public class CustomPlaybackSpeedPatch { private static void resetCustomSpeeds(@NonNull String toastMessage) { ReVancedUtils.showToastLong(toastMessage); - SettingsEnum.CUSTOM_PLAYBACK_SPEEDS.saveValue(SettingsEnum.CUSTOM_PLAYBACK_SPEEDS.defaultValue); + SettingsEnum.CUSTOM_PLAYBACK_SPEEDS.resetToDefault(); } private static void loadCustomSpeeds() { diff --git a/app/src/main/java/app/revanced/integrations/patches/theme/SeekbarColorPatch.java b/app/src/main/java/app/revanced/integrations/patches/theme/SeekbarColorPatch.java index 942ff3c6..41fa877d 100644 --- a/app/src/main/java/app/revanced/integrations/patches/theme/SeekbarColorPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/theme/SeekbarColorPatch.java @@ -48,7 +48,7 @@ public final class SeekbarColorPatch { Color.colorToHSV(seekbarColor, customSeekbarColorHSV); } catch (Exception ex) { ReVancedUtils.showToastShort("Invalid seekbar color value. Using default value."); - SettingsEnum.SEEKBAR_CUSTOM_COLOR_VALUE.saveValue(SettingsEnum.SEEKBAR_CUSTOM_COLOR_VALUE.defaultValue); + SettingsEnum.SEEKBAR_CUSTOM_COLOR_VALUE.resetToDefault(); loadCustomSeekbarColor(); } } diff --git a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java index e4b9550a..2b18ab78 100644 --- a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java +++ b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java @@ -56,9 +56,13 @@ public enum SettingsEnum { HIDE_WEB_SEARCH_RESULTS("revanced_hide_web_search_results", BOOLEAN, TRUE), // Layout - ALT_THUMBNAIL("revanced_alt_thumbnail", BOOLEAN, FALSE), - ALT_THUMBNAIL_TYPE("revanced_alt_thumbnail_type", INTEGER, 2, parents(ALT_THUMBNAIL)), - ALT_THUMBNAIL_FAST_QUALITY("revanced_alt_thumbnail_fast_quality", BOOLEAN, FALSE, parents(ALT_THUMBNAIL)), + ALT_THUMBNAIL_STILLS("revanced_alt_thumbnail_stills", BOOLEAN, FALSE), + ALT_THUMBNAIL_STILLS_TIME("revanced_alt_thumbnail_stills_time", INTEGER, 2, parents(ALT_THUMBNAIL_STILLS)), + ALT_THUMBNAIL_STILLS_FAST("revanced_alt_thumbnail_stills_fast", BOOLEAN, FALSE, parents(ALT_THUMBNAIL_STILLS)), + ALT_THUMBNAIL_DEARROW("revanced_alt_thumbnail_dearrow", BOOLEAN, false), + ALT_THUMBNAIL_DEARROW_API_URL("revanced_alt_thumbnail_dearrow_api_url", STRING, + "https://dearrow-thumb.ajay.app/api/v1/getThumbnail", true, parents(ALT_THUMBNAIL_DEARROW)), + ALT_THUMBNAIL_DEARROW_CONNECTION_TOAST("revanced_alt_thumbnail_dearrow_connection_toast", BOOLEAN, TRUE, parents(ALT_THUMBNAIL_DEARROW)), CUSTOM_FILTER("revanced_custom_filter", BOOLEAN, FALSE), CUSTOM_FILTER_STRINGS("revanced_custom_filter_strings", STRING, "", true, parents(CUSTOM_FILTER)), DISABLE_FULLSCREEN_AMBIENT_MODE("revanced_disable_fullscreen_ambient_mode", BOOLEAN, TRUE, true), @@ -430,7 +434,7 @@ public enum SettingsEnum { LogHelper.printInfo(() -> "Migrating old setting of '" + oldSetting.value + "' from: " + oldSetting + " into replacement setting: " + newSetting); newSetting.saveValue(oldSetting.value); - oldSetting.saveValue(oldSetting.defaultValue); // reset old value + oldSetting.resetToDefault(); } } @@ -522,6 +526,13 @@ public enum SettingsEnum { } } + /** + * Identical to calling {@link #saveValue(Object)} using {@link #defaultValue}. + */ + public void resetToDefault() { + saveValue(defaultValue); + } + /** * @return if this setting can be configured and used. *

@@ -694,7 +705,7 @@ public enum SettingsEnum { } else if (setting.includeWithImportExport() && !setting.isSetToDefault()) { LogHelper.printDebug(() -> "Resetting to default: " + setting); rebootSettingChanged |= setting.rebootApp; - setting.saveValue(setting.defaultValue); + setting.resetToDefault(); } } numberOfSettingsImported += SponsorBlockSettings.importCategoriesFromFlatJson(json); diff --git a/app/src/main/java/app/revanced/integrations/settingsmenu/AlternativeThumbnailsAboutDeArrowPreference.java b/app/src/main/java/app/revanced/integrations/settingsmenu/AlternativeThumbnailsAboutDeArrowPreference.java new file mode 100644 index 00000000..353f40cc --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/settingsmenu/AlternativeThumbnailsAboutDeArrowPreference.java @@ -0,0 +1,35 @@ +package app.revanced.integrations.settingsmenu; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.preference.Preference; +import android.util.AttributeSet; + +/** + * Allows tapping the DeArrow about preference to open the DeArrow website. + */ +@SuppressWarnings("unused") +public class AlternativeThumbnailsAboutDeArrowPreference extends Preference { + { + setOnPreferenceClickListener(pref -> { + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse("https://dearrow.ajay.app")); + pref.getContext().startActivity(i); + return false; + }); + } + + public AlternativeThumbnailsAboutDeArrowPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + public AlternativeThumbnailsAboutDeArrowPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + public AlternativeThumbnailsAboutDeArrowPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + public AlternativeThumbnailsAboutDeArrowPreference(Context context) { + super(context); + } +} diff --git a/app/src/main/java/app/revanced/integrations/settingsmenu/AlternativeThumbnailsStatusPreference.java b/app/src/main/java/app/revanced/integrations/settingsmenu/AlternativeThumbnailsStatusPreference.java new file mode 100644 index 00000000..97dc6d53 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/settingsmenu/AlternativeThumbnailsStatusPreference.java @@ -0,0 +1,85 @@ +package app.revanced.integrations.settingsmenu; + +import static app.revanced.integrations.utils.StringRef.str; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.Preference; +import android.preference.PreferenceManager; +import android.util.AttributeSet; + +import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.settings.SharedPrefCategory; +import app.revanced.integrations.utils.LogHelper; +import app.revanced.integrations.utils.ReVancedUtils; + +/** + * Shows what thumbnails will be used based on the current settings. + */ +@SuppressWarnings("unused") +public class AlternativeThumbnailsStatusPreference extends Preference { + + private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { + // Because this listener may run before the ReVanced settings fragment updates SettingsEnum, + // this could show the prior config and not the current. + // + // Push this call to the end of the main run queue, + // so all other listeners are done and SettingsEnum is up to date. + ReVancedUtils.runOnMainThread(this::updateUI); + }; + + public AlternativeThumbnailsStatusPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + public AlternativeThumbnailsStatusPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + public AlternativeThumbnailsStatusPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + public AlternativeThumbnailsStatusPreference(Context context) { + super(context); + } + + private void addChangeListener() { + LogHelper.printDebug(() -> "addChangeListener"); + SharedPrefCategory.YOUTUBE.preferences.registerOnSharedPreferenceChangeListener(listener); + } + + private void removeChangeListener() { + LogHelper.printDebug(() -> "removeChangeListener"); + SharedPrefCategory.YOUTUBE.preferences.unregisterOnSharedPreferenceChangeListener(listener); + } + + @Override + protected void onAttachedToHierarchy(PreferenceManager preferenceManager) { + super.onAttachedToHierarchy(preferenceManager); + updateUI(); + addChangeListener(); + } + + @Override + protected void onPrepareForRemoval() { + super.onPrepareForRemoval(); + removeChangeListener(); + } + + private void updateUI() { + LogHelper.printDebug(() -> "updateUI"); + final boolean usingDeArrow = SettingsEnum.ALT_THUMBNAIL_DEARROW.getBoolean(); + final boolean usingVideoStills = SettingsEnum.ALT_THUMBNAIL_STILLS.getBoolean(); + + final String summaryTextKey; + if (usingDeArrow && usingVideoStills) { + summaryTextKey = "revanced_alt_thumbnail_about_status_dearrow_stills"; + } else if (usingDeArrow) { + summaryTextKey = "revanced_alt_thumbnail_about_status_dearrow"; + } else if (usingVideoStills) { + summaryTextKey = "revanced_alt_thumbnail_about_status_stills"; + } else { + summaryTextKey = "revanced_alt_thumbnail_about_status_disabled"; + } + + setSummary(str(summaryTextKey)); + } +} diff --git a/app/src/main/java/app/revanced/integrations/settingsmenu/SponsorBlockSettingsFragment.java b/app/src/main/java/app/revanced/integrations/settingsmenu/SponsorBlockSettingsFragment.java index 456bcdc8..e25ae7a5 100644 --- a/app/src/main/java/app/revanced/integrations/settingsmenu/SponsorBlockSettingsFragment.java +++ b/app/src/main/java/app/revanced/integrations/settingsmenu/SponsorBlockSettingsFragment.java @@ -351,7 +351,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { DialogInterface.OnClickListener urlChangeListener = (dialog, buttonPressed) -> { if (buttonPressed == DialogInterface.BUTTON_NEUTRAL) { - SettingsEnum.SB_API_URL.saveValue(SettingsEnum.SB_API_URL.defaultValue); + SettingsEnum.SB_API_URL.resetToDefault(); ReVancedUtils.showToastLong(str("sb_api_url_reset")); } else if (buttonPressed == DialogInterface.BUTTON_POSITIVE) { String serverAddress = editText.getText().toString(); @@ -583,8 +583,8 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { new AlertDialog.Builder(preference1.getContext()) .setTitle(str("sb_stats_self_saved_reset_title")) .setPositiveButton(android.R.string.yes, (dialog, whichButton) -> { - SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.saveValue(SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.defaultValue); - SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.saveValue(SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.defaultValue); + SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.resetToDefault(); + SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.resetToDefault(); updateStatsSelfSaved.run(); }) .setNegativeButton(android.R.string.no, null).show(); diff --git a/dummy/src/main/java/org/chromium/net/UrlRequest.java b/dummy/src/main/java/org/chromium/net/UrlRequest.java new file mode 100644 index 00000000..565fc222 --- /dev/null +++ b/dummy/src/main/java/org/chromium/net/UrlRequest.java @@ -0,0 +1,4 @@ +package org.chromium.net; + +public abstract class UrlRequest { +} diff --git a/dummy/src/main/java/org/chromium/net/impl/CronetUrlRequest.java b/dummy/src/main/java/org/chromium/net/impl/CronetUrlRequest.java new file mode 100644 index 00000000..fa0dcacd --- /dev/null +++ b/dummy/src/main/java/org/chromium/net/impl/CronetUrlRequest.java @@ -0,0 +1,11 @@ +package org.chromium.net.impl; + +import org.chromium.net.UrlRequest; + +public abstract class CronetUrlRequest extends UrlRequest { + + /** + * Method is added by patch. + */ + public abstract String getHookedUrl(); +} From 6f7a7b825e208a723b60351cd78b1cca6c5ed76f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 11 Dec 2023 01:09:29 +0000 Subject: [PATCH 21/27] chore(release): 1.0.0-dev.9 [skip ci] # [1.0.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.8...v1.0.0-dev.9) (2023-12-11) ### Features * **YouTube - Alternative Thumbnails:** Add option to use DeArrow ([#534](https://github.com/ReVanced/revanced-integrations/issues/534)) ([c4ee6ca](https://github.com/ReVanced/revanced-integrations/commit/c4ee6ca4dde13ab8ce6f9cf94f1910455f9d9ecc)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8476deb6..724a96eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.8...v1.0.0-dev.9) (2023-12-11) + + +### Features + +* **YouTube - Alternative Thumbnails:** Add option to use DeArrow ([#534](https://github.com/ReVanced/revanced-integrations/issues/534)) ([c4ee6ca](https://github.com/ReVanced/revanced-integrations/commit/c4ee6ca4dde13ab8ce6f9cf94f1910455f9d9ecc)) + # [1.0.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.7...v1.0.0-dev.8) (2023-12-10) diff --git a/gradle.properties b/gradle.properties index 1b0ddae0..942b3ce4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.0.0-dev.8 +version = 1.0.0-dev.9 From 1c9c51ca5f7970774d4e0b5aad5ebcd064cac716 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Mon, 11 Dec 2023 22:30:36 +0400 Subject: [PATCH 22/27] fix(YouTube - Return YouTube Dislike): Wait until fetch is complete before allowing the first Short to start playback (#538) --- .../integrations/patches/ReturnYouTubeDislikePatch.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java index 38e4e54d..9a14366e 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -579,8 +579,6 @@ public class ReturnYouTubeDislikePatch { } final boolean waitForFetchToComplete = !IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER && videoIdIsShort && !lastPlayerResponseWasShort; - lastPlayerResponseWasShort = videoIdIsShort; - lastPrefetchedVideoId = videoId; LogHelper.printDebug(() -> "Prefetching RYD for video: " + videoId); ReturnYouTubeDislike fetch = ReturnYouTubeDislike.getFetchForVideoId(videoId); @@ -594,8 +592,11 @@ public class ReturnYouTubeDislikePatch { // // If an asynchronous litho Shorts solution is found, then this blocking call should be removed. LogHelper.printDebug(() -> "Waiting for prefetch to complete: " + videoId); - fetch.getFetchData(10000); // Use any arbitrarily large max wait time. + fetch.getFetchData(20000); // Any arbitrarily large max wait time. } + // Set the fields after the fetch completes, so any concurrent calls will also wait. + lastPlayerResponseWasShort = videoIdIsShort; + lastPrefetchedVideoId = videoId; } catch (Exception ex) { LogHelper.printException(() -> "preloadVideoId failure", ex); } From 8870f7ab0ce4466d470a2d2175cd5601ca75d1a5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 11 Dec 2023 18:33:07 +0000 Subject: [PATCH 23/27] chore(release): 1.0.0-dev.10 [skip ci] # [1.0.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.9...v1.0.0-dev.10) (2023-12-11) ### Bug Fixes * **YouTube - Return YouTube Dislike:** Wait until fetch is complete before allowing the first Short to start playback ([#538](https://github.com/ReVanced/revanced-integrations/issues/538)) ([1c9c51c](https://github.com/ReVanced/revanced-integrations/commit/1c9c51ca5f7970774d4e0b5aad5ebcd064cac716)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 724a96eb..7ef419c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.9...v1.0.0-dev.10) (2023-12-11) + + +### Bug Fixes + +* **YouTube - Return YouTube Dislike:** Wait until fetch is complete before allowing the first Short to start playback ([#538](https://github.com/ReVanced/revanced-integrations/issues/538)) ([1c9c51c](https://github.com/ReVanced/revanced-integrations/commit/1c9c51ca5f7970774d4e0b5aad5ebcd064cac716)) + # [1.0.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.8...v1.0.0-dev.9) (2023-12-11) diff --git a/gradle.properties b/gradle.properties index 942b3ce4..d8397dc9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.0.0-dev.9 +version = 1.0.0-dev.10 From 792dc0c52210dc9b1560290b0cc2afad572c584c Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 12 Dec 2023 00:34:20 +0100 Subject: [PATCH 24/27] feat(YouTube): Add `Change start page` patch --- .../patches/ChangeStartPagePatch.java | 16 ++++++++++++++++ .../integrations/settings/SettingsEnum.java | 2 ++ 2 files changed, 18 insertions(+) create mode 100644 app/src/main/java/app/revanced/integrations/patches/ChangeStartPagePatch.java diff --git a/app/src/main/java/app/revanced/integrations/patches/ChangeStartPagePatch.java b/app/src/main/java/app/revanced/integrations/patches/ChangeStartPagePatch.java new file mode 100644 index 00000000..ede239a3 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/patches/ChangeStartPagePatch.java @@ -0,0 +1,16 @@ +package app.revanced.integrations.patches; + +import android.content.Intent; +import app.revanced.integrations.settings.SettingsEnum; +import app.revanced.integrations.utils.LogHelper; + +@SuppressWarnings("unused") +public final class ChangeStartPagePatch { + public static void changeIntent(Intent intent) { + final var startPage = SettingsEnum.START_PAGE.getString(); + if (startPage.isEmpty()) return; + + LogHelper.printDebug(() -> "Changing start page to " + startPage); + intent.setAction("com.google.android.youtube.action." + startPage); + } +} diff --git a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java index 2b18ab78..154c0c4b 100644 --- a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java +++ b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java @@ -129,6 +129,8 @@ public enum SettingsEnum { TABLET_LAYOUT("revanced_tablet_layout", BOOLEAN, FALSE, true, "revanced_tablet_layout_user_dialog_message"), USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true), WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true), + START_PAGE("revanced_start_page", STRING, ""), + // Description HIDE_CHAPTERS("revanced_hide_chapters", BOOLEAN, TRUE), HIDE_INFO_CARDS_SECTION("revanced_hide_info_cards_section", BOOLEAN, TRUE), From e79ebb26ec3679875aa5b0dda5b57fb71946de04 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 11 Dec 2023 23:44:14 +0000 Subject: [PATCH 25/27] chore(release): 1.0.0-dev.11 [skip ci] # [1.0.0-dev.11](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2023-12-11) ### Features * **YouTube:** Add `Change start page` patch ([792dc0c](https://github.com/ReVanced/revanced-integrations/commit/792dc0c52210dc9b1560290b0cc2afad572c584c)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ef419c9..fcac8390 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.11](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2023-12-11) + + +### Features + +* **YouTube:** Add `Change start page` patch ([792dc0c](https://github.com/ReVanced/revanced-integrations/commit/792dc0c52210dc9b1560290b0cc2afad572c584c)) + # [1.0.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.9...v1.0.0-dev.10) (2023-12-11) diff --git a/gradle.properties b/gradle.properties index d8397dc9..7b3ce32d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.0.0-dev.10 +version = 1.0.0-dev.11 From 10a1e168d062adc3c979de17738d8cf1b8f25d63 Mon Sep 17 00:00:00 2001 From: d4rkk3y <43563783+d4rkk3y@users.noreply.github.com> Date: Tue, 12 Dec 2023 07:07:08 +0700 Subject: [PATCH 26/27] feat(Tiktok): Bump compatibility to `32.5.3` (#536) Co-authored-by: oSumAtrIX --- .../revanced/tiktok/settingsmenu/SettingsMenu.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsMenu.java b/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsMenu.java index fc9bcfab..4521409a 100644 --- a/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsMenu.java +++ b/app/src/main/java/app/revanced/tiktok/settingsmenu/SettingsMenu.java @@ -7,13 +7,15 @@ import android.preference.PreferenceFragment; import android.view.View; import android.widget.FrameLayout; import android.widget.LinearLayout; -import app.revanced.tiktok.utils.LogHelper; -import app.revanced.tiktok.utils.ReVancedUtils; + import com.bytedance.ies.ugc.aweme.commercialize.compliance.personalization.AdPersonalizationActivity; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import app.revanced.tiktok.utils.LogHelper; +import app.revanced.tiktok.utils.ReVancedUtils; + public class SettingsMenu { public static Object createSettingsEntry(String entryClazzName, String entryInfoClazzName) { @@ -22,10 +24,9 @@ public class SettingsMenu { Class entryInfoClazz = Class.forName(entryInfoClazzName); Constructor entryConstructor = entryClazz.getConstructor(entryInfoClazz); Constructor entryInfoConstructor = entryInfoClazz.getDeclaredConstructors()[0]; - Object buttonInfo = entryInfoConstructor.newInstance("Revanced settings", null, (View.OnClickListener) view -> startSettingsActivity()); + Object buttonInfo = entryInfoConstructor.newInstance("ReVanced settings", null, (View.OnClickListener) view -> startSettingsActivity(), "revanced"); return entryConstructor.newInstance(buttonInfo); - } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | - InstantiationException e) { + } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) { throw new RuntimeException(e); } } From 30f2c4f4c946cfad233fa8ece92207d09436267c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 12 Dec 2023 00:09:33 +0000 Subject: [PATCH 27/27] chore(release): 1.0.0-dev.12 [skip ci] # [1.0.0-dev.12](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.11...v1.0.0-dev.12) (2023-12-12) ### Features * **Tiktok:** Bump compatibility to `32.5.3` ([#536](https://github.com/ReVanced/revanced-integrations/issues/536)) ([10a1e16](https://github.com/ReVanced/revanced-integrations/commit/10a1e168d062adc3c979de17738d8cf1b8f25d63)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcac8390..396e31d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.0.0-dev.12](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.11...v1.0.0-dev.12) (2023-12-12) + + +### Features + +* **Tiktok:** Bump compatibility to `32.5.3` ([#536](https://github.com/ReVanced/revanced-integrations/issues/536)) ([10a1e16](https://github.com/ReVanced/revanced-integrations/commit/10a1e168d062adc3c979de17738d8cf1b8f25d63)) + # [1.0.0-dev.11](https://github.com/ReVanced/revanced-integrations/compare/v1.0.0-dev.10...v1.0.0-dev.11) (2023-12-11) diff --git a/gradle.properties b/gradle.properties index 7b3ce32d..e6e5296a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true android.useAndroidX = true -version = 1.0.0-dev.11 +version = 1.0.0-dev.12