mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-01-06 01:55:50 +01:00
chore: Merge branch dev
to main
(#665)
This commit is contained in:
commit
e5736fc27f
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -45,7 +45,7 @@ jobs:
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
fingerprint: ${{ env.GPG_FINGERPRINT }}
|
||||
fingerprint: ${{ vars.GPG_FINGERPRINT }}
|
||||
|
||||
- name: Release
|
||||
env:
|
||||
|
@ -7,7 +7,13 @@
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
[
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"releaseRules": [
|
||||
{ "type": "build", "scope": "Needs bump", "release": "patch" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"@semantic-release/release-notes-generator",
|
||||
"@semantic-release/changelog",
|
||||
"gradle-semantic-release-plugin",
|
||||
|
96
CHANGELOG.md
96
CHANGELOG.md
@ -1,3 +1,99 @@
|
||||
# [1.12.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.12.0-dev.9...v1.12.0-dev.10) (2024-08-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Return YouTube Dislike:** Fix dislikes not appearing due to new component name ([#674](https://github.com/ReVanced/revanced-integrations/issues/674)) ([509e151](https://github.com/ReVanced/revanced-integrations/commit/509e1516f817bd736c3b2cc75bb5b48ab7de404a))
|
||||
|
||||
# [1.12.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.12.0-dev.8...v1.12.0-dev.9) (2024-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Spoof client:** Restore livestream audio only playback with iOS spoofing ([#673](https://github.com/ReVanced/revanced-integrations/issues/673)) ([5bf5fbd](https://github.com/ReVanced/revanced-integrations/commit/5bf5fbd1a79389895991f6b672d87373e96b698c))
|
||||
|
||||
# [1.12.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.12.0-dev.7...v1.12.0-dev.8) (2024-08-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - SponsorBlock:** Improve create segment manual seek accuracy ([#671](https://github.com/ReVanced/revanced-integrations/issues/671)) ([34c02ae](https://github.com/ReVanced/revanced-integrations/commit/34c02aeb2a75bd95492e55958a446c9f99efdbb3))
|
||||
|
||||
# [1.12.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.12.0-dev.6...v1.12.0-dev.7) (2024-08-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Description components:** Add `Hide 'Key concepts' section` option ([#670](https://github.com/ReVanced/revanced-integrations/issues/670)) ([86b25ea](https://github.com/ReVanced/revanced-integrations/commit/86b25ea468a132bd01e3fb1e2972cc903dd46d0c))
|
||||
|
||||
# [1.12.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.12.0-dev.5...v1.12.0-dev.6) (2024-07-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Keyword filter:** Filter videos from new subscription layout ([2f2eeea](https://github.com/ReVanced/revanced-integrations/commit/2f2eeea5a722b6b7053eb2825d16fa37938b4e9e))
|
||||
|
||||
# [1.12.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.12.0-dev.4...v1.12.0-dev.5) (2024-07-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Client Spoof:** Restore missing high qualities by spoofing the iOS client user agent ([#668](https://github.com/ReVanced/revanced-integrations/issues/668)) ([fbf629f](https://github.com/ReVanced/revanced-integrations/commit/fbf629fd6278440e70b0f1fb07e4cb7c412f0949))
|
||||
|
||||
# [1.12.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.12.0-dev.3...v1.12.0-dev.4) (2024-07-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Spoof client:** Fix tracking history on brand accounts ([#669](https://github.com/ReVanced/revanced-integrations/issues/669)) ([4ac698f](https://github.com/ReVanced/revanced-integrations/commit/4ac698fd4bd493d3830009853454a8f6566362b5))
|
||||
|
||||
# [1.12.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.12.0-dev.2...v1.12.0-dev.3) (2024-07-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - SponsorBlock:** Correctly show minute timestamp when creating a new segment ([e71955d](https://github.com/ReVanced/revanced-integrations/commit/e71955d5bbe58c1c634e82262d0e67dc65eca078))
|
||||
|
||||
# [1.12.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.12.0-dev.1...v1.12.0-dev.2) (2024-07-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Disable auto captions:** Do not break Shorts captions menu ([0345a00](https://github.com/ReVanced/revanced-integrations/commit/0345a00d6095797e275bb31f92ccda2e861f44c4))
|
||||
|
||||
# [1.12.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.11.2-dev.3...v1.12.0-dev.1) (2024-07-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube:** Add `Bypass image region restrictions` patch ([#667](https://github.com/ReVanced/revanced-integrations/issues/667)) ([396ba77](https://github.com/ReVanced/revanced-integrations/commit/396ba77c207b438651ba6b83fb4b31e623544c00))
|
||||
|
||||
## [1.11.2-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.11.2-dev.2...v1.11.2-dev.3) (2024-07-14)
|
||||
|
||||
## [1.11.2-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.11.2-dev.1...v1.11.2-dev.2) (2024-07-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Alternative thumbnails:** Always use primary thumbnail domain for still captures ([#666](https://github.com/ReVanced/revanced-integrations/issues/666)) ([7cdaf8d](https://github.com/ReVanced/revanced-integrations/commit/7cdaf8df146fdc0da8254a27d9c125f1e3d34765))
|
||||
* **YouTube - Hide layout components:** Hide new type of horizontal shelf ([1fa59a6](https://github.com/ReVanced/revanced-integrations/commit/1fa59a62a17c63916808647331fa682d3de6aafb))
|
||||
|
||||
## [1.11.2-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.11.2-dev.1...v1.11.2-dev.2) (2024-07-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Alternative thumbnails:** Always use primary thumbnail domain for still captures ([#666](https://github.com/ReVanced/revanced-integrations/issues/666)) ([7cdaf8d](https://github.com/ReVanced/revanced-integrations/commit/7cdaf8df146fdc0da8254a27d9c125f1e3d34765))
|
||||
|
||||
## [1.11.2-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.11.1...v1.11.2-dev.1) (2024-07-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adjust blacklist ([d8d2a85](https://github.com/ReVanced/revanced-integrations/commit/d8d2a852d3879060bd95cc43d66c7cf195e82b43))
|
||||
* **YouTube - Hide keyword content:** Do not hide flyout menu ([cda1f31](https://github.com/ReVanced/revanced-integrations/commit/cda1f3160c12d239df1183799ead39526cbac20f))
|
||||
* **YouTube - Hide keyword content:** Do not hide flyout menu ([#664](https://github.com/ReVanced/revanced-integrations/issues/664)) ([120188d](https://github.com/ReVanced/revanced-integrations/commit/120188d6431b5500d6fde9cec136c752f8ee0ea4))
|
||||
|
||||
## [1.11.1](https://github.com/ReVanced/revanced-integrations/compare/v1.11.0...v1.11.1) (2024-07-11)
|
||||
|
||||
|
||||
|
@ -2,6 +2,7 @@ plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin)
|
||||
publishing
|
||||
signing
|
||||
}
|
||||
|
||||
android {
|
||||
@ -53,28 +54,27 @@ dependencies {
|
||||
compileOnly(project(":stub"))
|
||||
}
|
||||
|
||||
tasks {
|
||||
// Because the signing plugin doesn't support signing APKs, do it manually.
|
||||
register("sign") {
|
||||
group = "signing"
|
||||
|
||||
dependsOn(build)
|
||||
tasks {
|
||||
val assembleReleaseSignApk by registering {
|
||||
dependsOn("assembleRelease")
|
||||
|
||||
val apk = layout.buildDirectory.file("outputs/apk/release/${rootProject.name}-$version.apk")
|
||||
|
||||
inputs.file(apk).withPropertyName("input")
|
||||
outputs.file(apk.map { it.asFile.resolveSibling("${it.asFile.name}.asc") })
|
||||
|
||||
doLast {
|
||||
val outputDirectory = layout.buildDirectory.dir("outputs/apk/release").get().asFile
|
||||
val integrationsApk = outputDirectory.resolve("${rootProject.name}-$version.apk")
|
||||
|
||||
org.gradle.security.internal.gnupg.GnupgSignatoryFactory().createSignatory(project).sign(
|
||||
integrationsApk.inputStream(),
|
||||
outputDirectory.resolve("${integrationsApk.name}.asc").outputStream(),
|
||||
)
|
||||
signing {
|
||||
useGpgCmd()
|
||||
sign(*inputs.files.files.toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Needed by gradle-semantic-release-plugin.
|
||||
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435
|
||||
// Tracking: https://github.com/KengoTODA/gradle-semantic-release-plugin/issues/435.
|
||||
publish {
|
||||
dependsOn(build)
|
||||
dependsOn("sign")
|
||||
dependsOn(assembleReleaseSignApk)
|
||||
}
|
||||
}
|
||||
|
@ -273,7 +273,6 @@ public class Utils {
|
||||
@NonNull MatchFilter<View> filter) {
|
||||
for (int i = 0, childCount = viewGroup.getChildCount(); i < childCount; i++) {
|
||||
View childAt = viewGroup.getChildAt(i);
|
||||
Logger.printDebug(() -> "View id: " + childAt.getId() + " tag: " + childAt.getTag());
|
||||
|
||||
if (filter.matches(childAt)) {
|
||||
//noinspection unchecked
|
||||
@ -285,6 +284,7 @@ public class Utils {
|
||||
if (match != null) return match;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -190,16 +190,17 @@ public final class AlternativeThumbnailsPatch {
|
||||
* 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.
|
||||
* @return The alternative thumbnail url, or if not available NULL.
|
||||
*/
|
||||
@NonNull
|
||||
private static String buildYoutubeVideoStillURL(@NonNull DecodedThumbnailUrl decodedUrl,
|
||||
@Nullable
|
||||
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;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -284,14 +285,21 @@ public final class AlternativeThumbnailsPatch {
|
||||
final boolean includeTracking;
|
||||
if (option.useDeArrow && canUseDeArrowAPI()) {
|
||||
includeTracking = false; // Do not include view tracking parameters with API call.
|
||||
final String fallbackUrl = option.useStillImages
|
||||
? buildYoutubeVideoStillURL(decodedUrl, qualityToUse)
|
||||
: decodedUrl.sanitizedUrl;
|
||||
String fallbackUrl = null;
|
||||
if (option.useStillImages) {
|
||||
fallbackUrl = buildYouTubeVideoStillURL(decodedUrl, qualityToUse);
|
||||
}
|
||||
if (fallbackUrl == null) {
|
||||
fallbackUrl = decodedUrl.sanitizedUrl;
|
||||
}
|
||||
|
||||
sanitizedReplacementUrl = buildDeArrowThumbnailURL(decodedUrl.videoId, fallbackUrl);
|
||||
} else if (option.useStillImages) {
|
||||
includeTracking = true; // Include view tracking parameters if present.
|
||||
sanitizedReplacementUrl = buildYoutubeVideoStillURL(decodedUrl, qualityToUse);
|
||||
sanitizedReplacementUrl = buildYouTubeVideoStillURL(decodedUrl, qualityToUse);
|
||||
if (sanitizedReplacementUrl == null) {
|
||||
return originalUrl; // Still capture is not available. Return the untouched original url.
|
||||
}
|
||||
} else {
|
||||
return originalUrl; // Recently experienced DeArrow failure and video stills are not enabled.
|
||||
}
|
||||
@ -345,7 +353,7 @@ public final class AlternativeThumbnailsPatch {
|
||||
return; // Not a thumbnail.
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "handleCronetSuccess, image not available: " + url);
|
||||
Logger.printDebug(() -> "handleCronetSuccess, image not available: " + decodedUrl.sanitizedUrl);
|
||||
|
||||
ThumbnailQuality quality = ThumbnailQuality.altImageNameToQuality(decodedUrl.imageQuality);
|
||||
if (quality == null) {
|
||||
@ -627,14 +635,17 @@ public final class AlternativeThumbnailsPatch {
|
||||
* YouTube video thumbnail url, decoded into it's relevant parts.
|
||||
*/
|
||||
private static class DecodedThumbnailUrl {
|
||||
/**
|
||||
* YouTube thumbnail URL prefix. Can be '/vi/' or '/vi_webp/'
|
||||
*/
|
||||
private static final String YOUTUBE_THUMBNAIL_PREFIX = "https://i.ytimg.com/vi";
|
||||
private static final String YOUTUBE_THUMBNAIL_DOMAIN = "https://i.ytimg.com/";
|
||||
|
||||
@Nullable
|
||||
static DecodedThumbnailUrl decodeImageUrl(String url) {
|
||||
final int videoIdStartIndex = url.indexOf('/', YOUTUBE_THUMBNAIL_PREFIX.length()) + 1;
|
||||
final int urlPathStartIndex = url.indexOf('/', "https://".length()) + 1;
|
||||
if (urlPathStartIndex <= 0) return null;
|
||||
|
||||
final int urlPathEndIndex = url.indexOf('/', urlPathStartIndex);
|
||||
if (urlPathEndIndex < 0) return null;
|
||||
|
||||
final int videoIdStartIndex = url.indexOf('/', urlPathEndIndex) + 1;
|
||||
if (videoIdStartIndex <= 0) return null;
|
||||
|
||||
final int videoIdEndIndex = url.indexOf('/', videoIdStartIndex);
|
||||
@ -647,15 +658,15 @@ public final class AlternativeThumbnailsPatch {
|
||||
int imageExtensionEndIndex = url.indexOf('?', imageSizeEndIndex);
|
||||
if (imageExtensionEndIndex < 0) imageExtensionEndIndex = url.length();
|
||||
|
||||
return new DecodedThumbnailUrl(url, videoIdStartIndex, videoIdEndIndex,
|
||||
return new DecodedThumbnailUrl(url, urlPathStartIndex, urlPathEndIndex, 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. */
|
||||
final String urlPrefix;
|
||||
/** Url path, such as 'vi' or 'vi_webp' */
|
||||
final String urlPath;
|
||||
final String videoId;
|
||||
/** Quality, such as hq720 or sddefault. */
|
||||
final String imageQuality;
|
||||
@ -664,11 +675,11 @@ public final class AlternativeThumbnailsPatch {
|
||||
/** User view tracking parameters, only present on some images. */
|
||||
final String viewTrackingParameters;
|
||||
|
||||
DecodedThumbnailUrl(String fullUrl, int videoIdStartIndex, int videoIdEndIndex,
|
||||
DecodedThumbnailUrl(String fullUrl, int urlPathStartIndex, int urlPathEndIndex, int videoIdStartIndex, int videoIdEndIndex,
|
||||
int imageSizeStartIndex, int imageSizeEndIndex, int imageExtensionEndIndex) {
|
||||
originalFullUrl = fullUrl;
|
||||
sanitizedUrl = fullUrl.substring(0, imageExtensionEndIndex);
|
||||
urlPrefix = fullUrl.substring(0, videoIdStartIndex);
|
||||
urlPath = fullUrl.substring(urlPathStartIndex, urlPathEndIndex);
|
||||
videoId = fullUrl.substring(videoIdStartIndex, videoIdEndIndex);
|
||||
imageQuality = fullUrl.substring(imageSizeStartIndex, imageSizeEndIndex);
|
||||
imageExtension = fullUrl.substring(imageSizeEndIndex + 1, imageExtensionEndIndex);
|
||||
@ -681,9 +692,12 @@ public final class AlternativeThumbnailsPatch {
|
||||
// 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).
|
||||
// (as much as 4x slower network response has been observed, despite the alt webp image being a smaller file).
|
||||
StringBuilder builder = new StringBuilder(originalFullUrl.length() + 2);
|
||||
builder.append(urlPrefix);
|
||||
// Many different "i.ytimage.com" domains exist such as "i9.ytimg.com",
|
||||
// but still captures are frequently not available on the other domains (especially newly uploaded videos).
|
||||
// So always use the primary domain for a higher success rate.
|
||||
builder.append(YOUTUBE_THUMBNAIL_DOMAIN).append(urlPath).append('/');
|
||||
builder.append(videoId).append('/');
|
||||
builder.append(qualityToUse.getAltImageNameToUse());
|
||||
builder.append('.').append(imageExtension);
|
||||
|
@ -0,0 +1,46 @@
|
||||
package app.revanced.integrations.youtube.patches;
|
||||
|
||||
import static app.revanced.integrations.youtube.settings.Settings.BYPASS_IMAGE_REGION_RESTRICTIONS;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class BypassImageRegionRestrictionsPatch {
|
||||
|
||||
private static final boolean BYPASS_IMAGE_REGION_RESTRICTIONS_ENABLED = BYPASS_IMAGE_REGION_RESTRICTIONS.get();
|
||||
|
||||
private static final String REPLACEMENT_IMAGE_DOMAIN = "https://yt4.ggpht.com";
|
||||
|
||||
/**
|
||||
* YouTube static images domain. Includes user and channel avatar images and community post images.
|
||||
*/
|
||||
private static final Pattern YOUTUBE_STATIC_IMAGE_DOMAIN_PATTERN
|
||||
= Pattern.compile("^https://(yt3|lh[3-6]|play-lh)\\.(ggpht|googleusercontent)\\.com");
|
||||
|
||||
/**
|
||||
* Injection point. Called off the main thread and by multiple threads at the same time.
|
||||
*
|
||||
* @param originalUrl Image url for all image urls loaded.
|
||||
*/
|
||||
public static String overrideImageURL(String originalUrl) {
|
||||
try {
|
||||
if (BYPASS_IMAGE_REGION_RESTRICTIONS_ENABLED) {
|
||||
String replacement = YOUTUBE_STATIC_IMAGE_DOMAIN_PATTERN
|
||||
.matcher(originalUrl).replaceFirst(REPLACEMENT_IMAGE_DOMAIN);
|
||||
|
||||
if (Settings.DEBUG.get() && !replacement.equals(originalUrl)) {
|
||||
Logger.printDebug(() -> "Replaced: '" + originalUrl + "' with: '" + replacement + "'");
|
||||
}
|
||||
|
||||
return replacement;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "overrideImageURL failure", ex);
|
||||
}
|
||||
|
||||
return originalUrl;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package app.revanced.integrations.youtube.patches;
|
||||
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class DisableAutoCaptionsPatch {
|
||||
@ -11,7 +12,9 @@ public class DisableAutoCaptionsPatch {
|
||||
public static boolean captionsButtonDisabled;
|
||||
|
||||
public static boolean autoCaptionsEnabled() {
|
||||
return Settings.AUTO_CAPTIONS.get();
|
||||
return Settings.AUTO_CAPTIONS.get()
|
||||
// Do not use auto captions for Shorts.
|
||||
&& !PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -221,12 +221,12 @@ public class ReturnYouTubeDislikePatch {
|
||||
|
||||
String conversionContextString = conversionContext.toString();
|
||||
|
||||
if (isRollingNumber && !conversionContextString.contains("video_action_bar.eml|")) {
|
||||
if (isRollingNumber && !conversionContextString.contains("video_action_bar.eml")) {
|
||||
return original;
|
||||
}
|
||||
|
||||
final CharSequence replacement;
|
||||
if (conversionContextString.contains("|segmented_like_dislike_button.eml|")) {
|
||||
if (conversionContextString.contains("segmented_like_dislike_button.eml")) {
|
||||
// Regular video.
|
||||
ReturnYouTubeDislike videoData = currentVideoData;
|
||||
if (videoData == null) {
|
||||
|
@ -7,7 +7,6 @@ import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@ -15,17 +14,21 @@ import java.util.Objects;
|
||||
* @noinspection unused
|
||||
*/
|
||||
public final class VideoInformation {
|
||||
|
||||
public interface PlaybackController {
|
||||
// Methods are added to YT classes during patching.
|
||||
boolean seekTo(long videoTime);
|
||||
boolean seekToRelative(long videoTimeOffset);
|
||||
}
|
||||
|
||||
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<Object> playerControllerRef;
|
||||
private static WeakReference<Object> mdxPlayerDirectorRef;
|
||||
private static Method seekMethod;
|
||||
private static Method mdxSeekMethod;
|
||||
private static WeakReference<PlaybackController> playerControllerRef = new WeakReference<>(null);
|
||||
private static WeakReference<PlaybackController> mdxPlayerDirectorRef = new WeakReference<>(null);
|
||||
|
||||
@NonNull
|
||||
private static String videoId = "";
|
||||
@ -47,15 +50,12 @@ public final class VideoInformation {
|
||||
*
|
||||
* @param playerController player controller object.
|
||||
*/
|
||||
public static void initialize(@NonNull Object playerController) {
|
||||
public static void initialize(@NonNull PlaybackController playerController) {
|
||||
try {
|
||||
playerControllerRef = new WeakReference<>(Objects.requireNonNull(playerController));
|
||||
videoTime = -1;
|
||||
videoLength = 0;
|
||||
playbackSpeed = DEFAULT_YOUTUBE_PLAYBACK_SPEED;
|
||||
|
||||
seekMethod = playerController.getClass().getMethod(SEEK_METHOD_NAME, Long.TYPE);
|
||||
seekMethod.setAccessible(true);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed to initialize", ex);
|
||||
}
|
||||
@ -66,12 +66,9 @@ public final class VideoInformation {
|
||||
*
|
||||
* @param mdxPlayerDirector MDX player director object (casting mode).
|
||||
*/
|
||||
public static void initializeMdx(@NonNull Object mdxPlayerDirector) {
|
||||
public static void initializeMdx(@NonNull PlaybackController mdxPlayerDirector) {
|
||||
try {
|
||||
mdxPlayerDirectorRef = new WeakReference<>(Objects.requireNonNull(mdxPlayerDirector));
|
||||
|
||||
mdxSeekMethod = mdxPlayerDirector.getClass().getMethod(SEEK_METHOD_NAME, Long.TYPE);
|
||||
mdxSeekMethod.setAccessible(true);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed to initialize MDX", ex);
|
||||
}
|
||||
@ -195,42 +192,80 @@ public final class VideoInformation {
|
||||
return false;
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "Seeking to " + adjustedSeekTime);
|
||||
Logger.printDebug(() -> "Seeking to: " + adjustedSeekTime);
|
||||
|
||||
try {
|
||||
//noinspection DataFlowIssue
|
||||
if ((Boolean) seekMethod.invoke(playerControllerRef.get(), adjustedSeekTime)) {
|
||||
return true;
|
||||
} // Else the video is loading or changing videos, or video is casting to a different device.
|
||||
} catch (Exception ex) {
|
||||
Logger.printInfo(() -> "seekTo method call failed", ex);
|
||||
// Try regular playback controller first, and it will not succeed if casting.
|
||||
PlaybackController controller = playerControllerRef.get();
|
||||
if (controller == null) {
|
||||
Logger.printDebug(() -> "Cannot seekTo because player controller is null");
|
||||
} else {
|
||||
if (controller.seekTo(adjustedSeekTime)) return true;
|
||||
Logger.printDebug(() -> "seekTo did not succeeded. Trying MXD.");
|
||||
// Else the video is loading or changing videos, or video is casting to a different device.
|
||||
}
|
||||
|
||||
// Try calling the seekTo method of the MDX player director (called when casting).
|
||||
// The difference has to be a different second mark in order to avoid infinite skip loops
|
||||
// as the Lounge API only supports seconds.
|
||||
if ((adjustedSeekTime / 1000) == (videoTime / 1000)) {
|
||||
Logger.printDebug(() -> "Skipping seekTo for MDX because seek time is too small ("
|
||||
+ (adjustedSeekTime - videoTime) + "ms)");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
//noinspection DataFlowIssue
|
||||
return (Boolean) mdxSeekMethod.invoke(mdxPlayerDirectorRef.get(), adjustedSeekTime);
|
||||
} catch (Exception ex) {
|
||||
Logger.printInfo(() -> "seekTo (MDX) method call failed", ex);
|
||||
if (adjustedSeekTime / 1000 == videoTime / 1000) {
|
||||
Logger.printDebug(() -> "Skipping seekTo for MDX because seek time is too small "
|
||||
+ "(" + (adjustedSeekTime - videoTime) + "ms)");
|
||||
return false;
|
||||
}
|
||||
|
||||
controller = mdxPlayerDirectorRef.get();
|
||||
if (controller == null) {
|
||||
Logger.printDebug(() -> "Cannot seekTo MXD because player controller is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
return controller.seekTo(adjustedSeekTime);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed to seek", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @noinspection UnusedReturnValue*/
|
||||
public static boolean seekToRelative(long millisecondsRelative) {
|
||||
return seekTo(videoTime + millisecondsRelative);
|
||||
/**
|
||||
* Seeks a relative amount. Should always be used over {@link #seekTo(long)}
|
||||
* when the desired seek time is an offset of the current time.
|
||||
*
|
||||
* @noinspection UnusedReturnValue
|
||||
*/
|
||||
public static boolean seekToRelative(long seekTime) {
|
||||
Utils.verifyOnMainThread();
|
||||
try {
|
||||
Logger.printDebug(() -> "Seeking relative to: " + seekTime);
|
||||
|
||||
// Try regular playback controller first, and it will not succeed if casting.
|
||||
PlaybackController controller = playerControllerRef.get();
|
||||
if (controller == null) {
|
||||
Logger.printDebug(() -> "Cannot seek relative as player controller is null");
|
||||
} else {
|
||||
if (controller.seekToRelative(seekTime)) return true;
|
||||
Logger.printDebug(() -> "seekToRelative did not succeeded. Trying MXD.");
|
||||
}
|
||||
|
||||
// Adjust the fine adjustment function so it's at least 1 second before/after.
|
||||
// Otherwise the fine adjustment will do nothing when casting.
|
||||
final long adjustedSeekTime;
|
||||
if (seekTime < 0) {
|
||||
adjustedSeekTime = Math.min(seekTime, -1000);
|
||||
} else {
|
||||
adjustedSeekTime = Math.max(seekTime, 1000);
|
||||
}
|
||||
|
||||
controller = mdxPlayerDirectorRef.get();
|
||||
if (controller == null) {
|
||||
Logger.printDebug(() -> "Cannot seek relative as MXD player controller is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
return controller.seekToRelative(adjustedSeekTime);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed to seek relative", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,14 +1,19 @@
|
||||
package app.revanced.integrations.youtube.patches.components;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
import app.revanced.integrations.youtube.StringTrieSearch;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
final class DescriptionComponentsFilter extends Filter {
|
||||
|
||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||
|
||||
private final ByteArrayFilterGroupList macroMarkersCarouselGroupList = new ByteArrayFilterGroupList();
|
||||
|
||||
private final StringFilterGroup macroMarkersCarousel;
|
||||
|
||||
public DescriptionComponentsFilter() {
|
||||
exceptions.addPatterns(
|
||||
"compact_channel",
|
||||
@ -25,11 +30,6 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
"video_attributes_section"
|
||||
);
|
||||
|
||||
final StringFilterGroup chaptersSection = new StringFilterGroup(
|
||||
Settings.HIDE_CHAPTERS_SECTION,
|
||||
"macro_markers_carousel"
|
||||
);
|
||||
|
||||
final StringFilterGroup infoCardsSection = new StringFilterGroup(
|
||||
Settings.HIDE_INFO_CARDS_SECTION,
|
||||
"infocards_section"
|
||||
@ -45,21 +45,44 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
"transcript_section"
|
||||
);
|
||||
|
||||
macroMarkersCarousel = new StringFilterGroup(
|
||||
null,
|
||||
"macro_markers_carousel.eml"
|
||||
);
|
||||
|
||||
macroMarkersCarouselGroupList.addAll(
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_CHAPTERS_SECTION,
|
||||
"chapters_horizontal_shelf"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_KEY_CONCEPTS_SECTION,
|
||||
"learning_concept_macro_markers_carousel_shelf"
|
||||
)
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
attributesSection,
|
||||
chaptersSection,
|
||||
infoCardsSection,
|
||||
podcastSection,
|
||||
transcriptSection
|
||||
transcriptSection,
|
||||
macroMarkersCarousel
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (exceptions.matches(path)) return false;
|
||||
|
||||
if (matchedGroup == macroMarkersCarousel) {
|
||||
if (contentIndex == 0 && macroMarkersCarouselGroupList.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.youtube.ByteTrieSearch;
|
||||
import app.revanced.integrations.youtube.StringTrieSearch;
|
||||
import app.revanced.integrations.youtube.TrieSearch;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.youtube.shared.NavigationBar;
|
||||
@ -59,11 +60,15 @@ final class KeywordContentFilter extends Filter {
|
||||
*/
|
||||
private static final String[] STRINGS_IN_EVERY_BUFFER = {
|
||||
// Video playback data.
|
||||
"https://i.ytimg.com/vi/", // Thumbnail url.
|
||||
"sddefault.jpg", // More video sizes exist, but for most devices only these 2 are used.
|
||||
"hqdefault.webp",
|
||||
"googlevideo.com/initplayback?source=youtube", // Video url.
|
||||
"ANDROID", // Video url parameter.
|
||||
"https://i.ytimg.com/vi/", // Thumbnail url.
|
||||
"mqdefault.jpg",
|
||||
"hqdefault.jpg",
|
||||
"sddefault.jpg",
|
||||
"hq720.jpg",
|
||||
"webp",
|
||||
"_custom_", // Custom thumbnail set by video creator.
|
||||
// Video decoders.
|
||||
"OMX.ffmpeg.vp9.decoder",
|
||||
"OMX.Intel.sw_vd.vp9",
|
||||
@ -75,15 +80,22 @@ final class KeywordContentFilter extends Filter {
|
||||
"c2.android.av1-dav1d.decoder",
|
||||
"c2.android.vp9.decoder",
|
||||
"c2.mtk.sw.vp9.decoder",
|
||||
// User analytics.
|
||||
"https://ad.doubleclick.net/ddm/activity/",
|
||||
"DEVICE_ADVERTISER_ID_FOR_CONVERSION_TRACKING",
|
||||
"tag_for_child_directed_treatment", // Found in overflow menu such as 'Watch later'.
|
||||
// Litho components frequently found in the buffer that belong to the path filter items.
|
||||
// Analytics.
|
||||
"searchR",
|
||||
"browse-feed",
|
||||
"FEwhat_to_watch",
|
||||
"FEsubscriptions",
|
||||
"search_vwc_description_transition_key",
|
||||
"g-high-recZ",
|
||||
// Text and litho components found in the buffer that belong to path filters.
|
||||
"metadata.eml",
|
||||
"thumbnail.eml",
|
||||
"avatar.eml",
|
||||
"overflow_button.eml",
|
||||
"shorts-lockup-image",
|
||||
"shorts-lockup.overlay-metadata.secondary-text",
|
||||
"YouTubeSans-SemiBold",
|
||||
"sans-serif"
|
||||
};
|
||||
|
||||
/**
|
||||
@ -95,6 +107,7 @@ final class KeywordContentFilter extends Filter {
|
||||
"search_video_with_context.eml",
|
||||
"video_with_context.eml", // Subscription tab videos.
|
||||
"related_video_with_context.eml",
|
||||
"video_lockup_with_attachment.eml", // A/B test for subscribed video.
|
||||
"compact_video.eml",
|
||||
"inline_shorts",
|
||||
"shorts_video_cell",
|
||||
@ -108,10 +121,24 @@ final class KeywordContentFilter extends Filter {
|
||||
private final StringFilterGroup containsFilter = new StringFilterGroup(
|
||||
null,
|
||||
"modern_type_shelf_header_content.eml",
|
||||
"shorts_lockup_cell.eml", // Part of 'shorts_shelf_carousel.eml'
|
||||
"shorts_lockup_cell.eml", // Part of 'shorts_shelf_carousel.eml'
|
||||
"video_card.eml" // Shorts that appear in a horizontal shelf.
|
||||
);
|
||||
|
||||
/**
|
||||
* Path components to not filter. Cannot filter the buffer when these are present,
|
||||
* otherwise text in UI controls can be filtered as a keyword (such as using "Playlist" as a keyword).
|
||||
*
|
||||
* This is also a small performance improvement since
|
||||
* the buffer of the parent component was already searched and passed.
|
||||
*/
|
||||
private final StringTrieSearch exceptions = new StringTrieSearch(
|
||||
"metadata.eml",
|
||||
"thumbnail.eml",
|
||||
"avatar.eml",
|
||||
"overflow_button.eml"
|
||||
);
|
||||
|
||||
/**
|
||||
* Threshold for {@link #filteredVideosPercentage}
|
||||
* that indicates all or nearly all videos have been filtered.
|
||||
@ -121,7 +148,7 @@ final class KeywordContentFilter extends Filter {
|
||||
|
||||
private static final float ALL_VIDEOS_FILTERED_SAMPLE_SIZE = 50;
|
||||
|
||||
private static final long ALL_VIDEOS_FILTERED_TIMEOUT_MILLISECONDS = 60 * 1000; // 60 seconds
|
||||
private static final long ALL_VIDEOS_FILTERED_BACKOFF_MILLISECONDS = 60 * 1000; // 60 seconds
|
||||
|
||||
/**
|
||||
* Rolling average of how many videos were filtered by a keyword.
|
||||
@ -140,7 +167,7 @@ final class KeywordContentFilter extends Filter {
|
||||
|
||||
/**
|
||||
* If filtering is temporarily turned off, the time to resume filtering.
|
||||
* Field is zero if no timeout is in effect.
|
||||
* Field is zero if no backoff is in effect.
|
||||
*/
|
||||
private volatile long timeToResumeFiltering;
|
||||
|
||||
@ -339,7 +366,7 @@ final class KeywordContentFilter extends Filter {
|
||||
|
||||
// A keyword is hiding everything.
|
||||
// Inform the user, and temporarily turn off filtering.
|
||||
timeToResumeFiltering = System.currentTimeMillis() + ALL_VIDEOS_FILTERED_TIMEOUT_MILLISECONDS;
|
||||
timeToResumeFiltering = System.currentTimeMillis() + ALL_VIDEOS_FILTERED_BACKOFF_MILLISECONDS;
|
||||
|
||||
Logger.printDebug(() -> "Temporarily turning off filtering due to excessively broad filter: " + keyword);
|
||||
Utils.showToastLong(str("revanced_hide_keyword_toast_invalid_broad", keyword));
|
||||
@ -361,6 +388,10 @@ final class KeywordContentFilter extends Filter {
|
||||
|
||||
if (!hideKeywordSettingIsActive()) return false;
|
||||
|
||||
if (exceptions.matches(path)) {
|
||||
return false; // Do not update statistics.
|
||||
}
|
||||
|
||||
MutableReference<String> matchRef = new MutableReference<>();
|
||||
if (bufferSearch.matches(protobufBufferArray, matchRef)) {
|
||||
updateStats(true, matchRef.value);
|
||||
|
@ -250,6 +250,7 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
Settings.HIDE_HORIZONTAL_SHELVES,
|
||||
"horizontal_video_shelf.eml",
|
||||
"horizontal_shelf.eml",
|
||||
"horizontal_shelf_inline.eml",
|
||||
"horizontal_tile_shelf.eml"
|
||||
);
|
||||
|
||||
|
@ -1,19 +1,20 @@
|
||||
package app.revanced.integrations.youtube.patches.playback.quality;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
import static app.revanced.integrations.shared.Utils.NetworkType;
|
||||
|
||||
import app.revanced.integrations.shared.settings.IntegerSetting;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
import static app.revanced.integrations.shared.Utils.NetworkType;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.shared.settings.IntegerSetting;
|
||||
import app.revanced.integrations.youtube.patches.VideoInformation;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class RememberVideoQualityPatch {
|
||||
@ -158,7 +159,7 @@ public class RememberVideoQualityPatch {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void newVideoStarted(Object ignoredPlayerController) {
|
||||
public static void newVideoStarted(VideoInformation.PlaybackController ignoredPlayerController) {
|
||||
Logger.printDebug(() -> "newVideoStarted");
|
||||
qualityNeedsUpdating = true;
|
||||
videoQualities = null;
|
||||
|
@ -13,7 +13,7 @@ public final class RememberPlaybackSpeedPatch {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void newVideoStarted(Object ignoredPlayerController) {
|
||||
public static void newVideoStarted(VideoInformation.PlaybackController ignoredPlayerController) {
|
||||
Logger.printDebug(() -> "newVideoStarted");
|
||||
VideoInformation.overridePlaybackSpeed(Settings.PLAYBACK_SPEED_DEFAULT.get());
|
||||
}
|
||||
|
@ -5,12 +5,15 @@ import android.media.MediaCodecList;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.youtube.patches.BackgroundPlaybackPatch;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import org.chromium.net.ExperimentalUrlRequest;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SpoofClientPatch {
|
||||
private static final boolean SPOOF_CLIENT_ENABLED = Settings.SPOOF_CLIENT.get();
|
||||
private static final ClientType SPOOF_CLIENT_TYPE = Settings.SPOOF_CLIENT_USE_IOS.get() ? ClientType.IOS : ClientType.ANDROID_VR;
|
||||
private static final boolean SPOOFING_TO_IOS = SPOOF_CLIENT_ENABLED && SPOOF_CLIENT_TYPE == ClientType.IOS;
|
||||
|
||||
/**
|
||||
* Any unreachable ip address. Used to intentionally fail requests.
|
||||
@ -45,7 +48,7 @@ public class SpoofClientPatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* <p>
|
||||
* Blocks /initplayback requests.
|
||||
*/
|
||||
public static String blockInitPlaybackRequest(String originalUrlString) {
|
||||
@ -71,33 +74,29 @@ public class SpoofClientPatch {
|
||||
* Injection point.
|
||||
*/
|
||||
public static int getClientTypeId(int originalClientTypeId) {
|
||||
if (SPOOF_CLIENT_ENABLED) {
|
||||
return SPOOF_CLIENT_TYPE.id;
|
||||
}
|
||||
|
||||
return originalClientTypeId;
|
||||
return SPOOF_CLIENT_ENABLED ? SPOOF_CLIENT_TYPE.id : originalClientTypeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String getClientVersion(String originalClientVersion) {
|
||||
if (SPOOF_CLIENT_ENABLED) {
|
||||
return SPOOF_CLIENT_TYPE.version;
|
||||
}
|
||||
|
||||
return originalClientVersion;
|
||||
return SPOOF_CLIENT_ENABLED ? SPOOF_CLIENT_TYPE.version : originalClientVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String getClientModel(String originalClientModel) {
|
||||
if (SPOOF_CLIENT_ENABLED) {
|
||||
return SPOOF_CLIENT_TYPE.model;
|
||||
}
|
||||
return SPOOF_CLIENT_ENABLED ? SPOOF_CLIENT_TYPE.model : originalClientModel;
|
||||
}
|
||||
|
||||
return originalClientModel;
|
||||
/**
|
||||
* Injection point.
|
||||
* Fix video qualities missing, if spoofing to iOS by using the correct client OS version.
|
||||
*/
|
||||
public static String getOsVersion(String originalOsVersion) {
|
||||
return SPOOFING_TO_IOS ? ClientType.IOS.osVersion : originalOsVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,16 +119,41 @@ public class SpoofClientPatch {
|
||||
* Return true to force create the playback speed menu.
|
||||
*/
|
||||
public static boolean forceCreatePlaybackSpeedMenu(boolean original) {
|
||||
if (SPOOF_CLIENT_ENABLED && SPOOF_CLIENT_TYPE == ClientType.IOS) {
|
||||
return true;
|
||||
return SPOOFING_TO_IOS || original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* When spoofing the client to iOS, background audio only playback of livestreams fails.
|
||||
* Return true to force enable audio background play.
|
||||
*/
|
||||
public static boolean overrideBackgroundAudioPlayback() {
|
||||
return SPOOFING_TO_IOS && BackgroundPlaybackPatch.playbackIsNotShort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Fix video qualities missing, if spoofing to iOS by using the correct iOS user-agent.
|
||||
*/
|
||||
public static ExperimentalUrlRequest overrideUserAgent(ExperimentalUrlRequest.Builder builder, String url) {
|
||||
if (SPOOFING_TO_IOS) {
|
||||
String path = Uri.parse(url).getPath();
|
||||
if (path != null && path.contains("player")) {
|
||||
return builder.addHeader("User-Agent", ClientType.IOS.userAgent).build();
|
||||
}
|
||||
}
|
||||
|
||||
return original;
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private enum ClientType {
|
||||
// https://dumps.tadiphone.dev/dumps/oculus/eureka
|
||||
ANDROID_VR(28, "Quest 3", "1.56.21"),
|
||||
ANDROID_VR(28,
|
||||
"Quest 3",
|
||||
"1.56.21",
|
||||
"12",
|
||||
"com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12; GB) gzip"
|
||||
),
|
||||
// 11,4 = iPhone XS Max.
|
||||
// 16,2 = iPhone 15 Pro Max.
|
||||
// Since the 15 supports AV1 hardware decoding, only spoof that device if this
|
||||
@ -137,7 +161,12 @@ public class SpoofClientPatch {
|
||||
//
|
||||
// Version number should be a valid iOS release.
|
||||
// https://www.ipa4fun.com/history/185230
|
||||
IOS(5, deviceHasAV1HardwareDecoding() ? "iPhone16,2" : "iPhone11,4", "19.10.7");
|
||||
IOS(5,
|
||||
deviceHasAV1HardwareDecoding() ? "iPhone16,2" : "iPhone11,4",
|
||||
"19.10.7",
|
||||
"17.5.1.21F90",
|
||||
"com.google.ios.youtube/19.10.7 (iPhone; U; CPU iOS 17_5_1 like Mac OS X)"
|
||||
);
|
||||
|
||||
/**
|
||||
* YouTube
|
||||
@ -155,10 +184,22 @@ public class SpoofClientPatch {
|
||||
*/
|
||||
final String version;
|
||||
|
||||
ClientType(int id, String model, String version) {
|
||||
/**
|
||||
* Device OS version.
|
||||
*/
|
||||
final String osVersion;
|
||||
|
||||
/**
|
||||
* Player user-agent.
|
||||
*/
|
||||
final String userAgent;
|
||||
|
||||
ClientType(int id, String model, String version, String osVersion, String userAgent) {
|
||||
this.id = id;
|
||||
this.model = model;
|
||||
this.version = version;
|
||||
this.osVersion = osVersion;
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,6 +157,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE);
|
||||
public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE);
|
||||
public static final BooleanSetting HIDE_INFO_CARDS_SECTION = new BooleanSetting("revanced_hide_info_cards_section", TRUE);
|
||||
public static final BooleanSetting HIDE_KEY_CONCEPTS_SECTION = new BooleanSetting("revanced_hide_key_concepts_section", FALSE);
|
||||
public static final BooleanSetting HIDE_PODCAST_SECTION = new BooleanSetting("revanced_hide_podcast_section", TRUE);
|
||||
public static final BooleanSetting HIDE_TRANSCRIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
||||
|
||||
@ -189,6 +190,7 @@ public class Settings extends BaseSettings {
|
||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "17.33.42", true, parent(SPOOF_APP_VERSION));
|
||||
public static final BooleanSetting TABLET_LAYOUT = new BooleanSetting("revanced_tablet_layout", FALSE, true, "revanced_tablet_layout_user_dialog_message");
|
||||
public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true);
|
||||
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
|
||||
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
|
||||
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
|
||||
|
||||
|
@ -11,11 +11,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
@ -182,7 +178,7 @@ public class SegmentPlaybackController {
|
||||
* Injection point.
|
||||
* Initializes SponsorBlock when the video player starts playing a new video.
|
||||
*/
|
||||
public static void initialize(Object ignoredPlayerController) {
|
||||
public static void initialize(VideoInformation.PlaybackController ignoredPlayerController) {
|
||||
try {
|
||||
Utils.verifyOnMainThread();
|
||||
SponsorBlockSettings.initialize();
|
||||
@ -632,6 +628,7 @@ public class SegmentPlaybackController {
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void setSponsorBarRect(final Object self) {
|
||||
try {
|
||||
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
|
||||
@ -663,6 +660,7 @@ public class SegmentPlaybackController {
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void setSponsorBarThickness(int thickness) {
|
||||
if (sponsorBarThickness != thickness) {
|
||||
Logger.printDebug(() -> "setSponsorBarThickness: " + thickness);
|
||||
@ -673,6 +671,7 @@ public class SegmentPlaybackController {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static String appendTimeWithoutSegments(String totalTime) {
|
||||
try {
|
||||
if (Settings.SB_ENABLED.get() && Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get()
|
||||
@ -725,9 +724,9 @@ public class SegmentPlaybackController {
|
||||
final long minutes = (timeWithoutSegmentsValue / 60000) % 60;
|
||||
final long seconds = (timeWithoutSegmentsValue / 1000) % 60;
|
||||
if (hours > 0) {
|
||||
timeWithoutSegments = String.format("\u2009(%d:%02d:%02d)", hours, minutes, seconds);
|
||||
timeWithoutSegments = String.format(Locale.ENGLISH, "\u2009(%d:%02d:%02d)", hours, minutes, seconds);
|
||||
} else {
|
||||
timeWithoutSegments = String.format("\u2009(%d:%02d)", minutes, seconds);
|
||||
timeWithoutSegments = String.format(Locale.ENGLISH, "\u2009(%d:%02d)", minutes, seconds);
|
||||
}
|
||||
}
|
||||
|
||||
@ -744,6 +743,7 @@ public class SegmentPlaybackController {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void drawSponsorTimeBars(final Canvas canvas, final float posY) {
|
||||
try {
|
||||
if (segments == null) return;
|
||||
|
@ -234,9 +234,7 @@ public class SponsorBlockUtils {
|
||||
new AlertDialog.Builder(SponsorBlockViewController.getOverLaysViewGroupContext())
|
||||
.setTitle(str("revanced_sb_new_segment_title"))
|
||||
.setMessage(str("revanced_sb_new_segment_mark_time_as_question",
|
||||
newSponsorSegmentDialogShownMillis / 3600000,
|
||||
newSponsorSegmentDialogShownMillis / 1000 % 60,
|
||||
newSponsorSegmentDialogShownMillis % 1000))
|
||||
formatSegmentTime(newSponsorSegmentDialogShownMillis)))
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
.setNegativeButton(str("revanced_sb_new_segment_mark_start"), newSponsorSegmentDialogListener)
|
||||
.setPositiveButton(str("revanced_sb_new_segment_mark_end"), newSponsorSegmentDialogListener)
|
||||
@ -448,17 +446,20 @@ public class SponsorBlockUtils {
|
||||
Duration duration = Duration.ofSeconds(totalSecondsSaved);
|
||||
final long hours = duration.toHours();
|
||||
final long minutes = duration.toMinutes() % 60;
|
||||
|
||||
// Format all numbers so non-western numbers use a consistent appearance.
|
||||
String minutesFormatted = statsNumberFormatter.format(minutes);
|
||||
if (hours > 0) {
|
||||
String hoursFormatted = statsNumberFormatter.format(hours);
|
||||
return str("revanced_sb_stats_saved_hour_format", hoursFormatted, minutesFormatted);
|
||||
}
|
||||
|
||||
final long seconds = duration.getSeconds() % 60;
|
||||
String secondsFormatted = statsNumberFormatter.format(seconds);
|
||||
if (minutes > 0) {
|
||||
return str("revanced_sb_stats_saved_minute_format", minutesFormatted, secondsFormatted);
|
||||
}
|
||||
|
||||
return str("revanced_sb_stats_saved_second_format", secondsFormatted);
|
||||
}
|
||||
return "error"; // will never be reached. YouTube requires Android O or greater
|
||||
|
@ -1,4 +1,4 @@
|
||||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
android.useAndroidX = true
|
||||
version = 1.11.1
|
||||
version = 1.12.0-dev.10
|
||||
|
@ -0,0 +1,8 @@
|
||||
package org.chromium.net;
|
||||
|
||||
public abstract class ExperimentalUrlRequest {
|
||||
public abstract class Builder {
|
||||
public abstract ExperimentalUrlRequest.Builder addHeader(String name, String value);
|
||||
public abstract ExperimentalUrlRequest build();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user