Merge remote-tracking branch 'origin/dev' into feat/update-youtube-patches
# Conflicts: # extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/YouTubeAndMusicSettings.java
This commit is contained in:
commit
cc019ec78b
173 changed files with 30882 additions and 17056 deletions
|
|
@ -1,14 +1,7 @@
|
|||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
defaultConfig {
|
||||
minSdk = 23
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,7 @@
|
|||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,10 @@ public class InternalDataDocumentsProvider extends DocumentsProvider {
|
|||
private static final String[] directoryColumns =
|
||||
{"document_id", "mime_type", "_display_name", "last_modified", "flags",
|
||||
"_size", "full_path", "lstat_info"};
|
||||
private static final int S_IFLNK = 0x8000;
|
||||
@SuppressWarnings("OctalInteger")
|
||||
private static final int S_IFMT = 0170000;
|
||||
@SuppressWarnings("OctalInteger")
|
||||
private static final int S_IFLNK = 0120000;
|
||||
|
||||
private String packageName;
|
||||
private File dataDirectory;
|
||||
|
|
@ -47,7 +50,7 @@ public class InternalDataDocumentsProvider extends DocumentsProvider {
|
|||
if (root.isDirectory()) {
|
||||
try {
|
||||
// Only delete recursively if the directory is not a symlink
|
||||
if ((Os.lstat(root.getPath()).st_mode & S_IFLNK) != S_IFLNK) {
|
||||
if ((Os.lstat(root.getPath()).st_mode & S_IFMT) != S_IFLNK) {
|
||||
File[] files = root.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
|
|
@ -324,7 +327,7 @@ public class InternalDataDocumentsProvider extends DocumentsProvider {
|
|||
sb.append(";");
|
||||
sb.append(lstat.st_gid);
|
||||
// Append symlink target if it is a symlink
|
||||
if ((lstat.st_mode & S_IFLNK) == S_IFLNK) {
|
||||
if ((lstat.st_mode & S_IFMT) == S_IFLNK) {
|
||||
sb.append(";");
|
||||
sb.append(Os.readlink(path));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,8 @@
|
|||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
aidl = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package app.revanced.extension.playintegrity;
|
||||
package app.revanced.extension.play;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
|
@ -1,14 +1,7 @@
|
|||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,7 @@
|
|||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
|||
|
|
@ -3,3 +3,9 @@ dependencies {
|
|||
compileOnly(libs.annotation)
|
||||
compileOnly(libs.okhttp)
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 22
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,3 +4,9 @@ dependencies {
|
|||
compileOnly(libs.annotation)
|
||||
compileOnly(libs.okhttp)
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:cricbuzz:stub"))
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package app.revanced.extension.music.patches.spoof;
|
||||
|
||||
import static app.revanced.extension.music.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_NO_SDK;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_REEL;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
|
||||
|
|
@ -18,8 +18,8 @@ public class SpoofVideoStreamsPatch {
|
|||
*/
|
||||
public static void setClientOrderToUse() {
|
||||
List<ClientType> availableClients = List.of(
|
||||
ANDROID_REEL,
|
||||
ANDROID_VR_1_43_32,
|
||||
ANDROID_NO_SDK,
|
||||
VISIONOS,
|
||||
ANDROID_VR_1_61_48
|
||||
);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||
|
||||
// Miscellaneous
|
||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type",
|
||||
ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
|
||||
ClientType.ANDROID_REEL, true, parent(SPOOF_VIDEO_STREAMS));
|
||||
|
||||
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", TRUE, true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,6 @@ dependencies {
|
|||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
minSdk = 23
|
||||
}
|
||||
}
|
||||
|
|
@ -2,3 +2,9 @@ dependencies {
|
|||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:nunl:stub"))
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,3 +2,9 @@ dependencies {
|
|||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:primevideo:stub"))
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
dependencies {
|
||||
compileOnly(project(":extensions:reddit:stub"))
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 28
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,3 +2,9 @@ dependencies {
|
|||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:samsung:radio:stub"))
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ dependencies {
|
|||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
minSdk = 23
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -19,4 +19,6 @@ android {
|
|||
dependencies {
|
||||
compileOnly(libs.annotation)
|
||||
compileOnly(libs.okhttp)
|
||||
compileOnly(libs.protobuf.javalite)
|
||||
implementation(project(":extensions:shared:protobuf", configuration = "shadowRuntimeElements"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,11 +32,7 @@ import android.view.Window;
|
|||
import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
|
|
@ -65,7 +61,6 @@ import app.revanced.extension.shared.settings.BooleanSetting;
|
|||
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
|
||||
@SuppressWarnings("NewApi")
|
||||
public class Utils {
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
|
|
@ -139,6 +134,7 @@ public class Utils {
|
|||
return versionName;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static String getApplicationName() {
|
||||
if (applicationLabel == null) {
|
||||
try {
|
||||
|
|
@ -185,24 +181,13 @@ public class Utils {
|
|||
* @param view The view to hide.
|
||||
*/
|
||||
public static void hideViewBy0dp(View view) {
|
||||
if (view instanceof LinearLayout) {
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams);
|
||||
} else if (view instanceof FrameLayout) {
|
||||
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams2);
|
||||
} else if (view instanceof RelativeLayout) {
|
||||
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams3);
|
||||
} else if (view instanceof Toolbar) {
|
||||
Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams4);
|
||||
} else {
|
||||
ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||
params.width = 0;
|
||||
params.height = 0;
|
||||
view.setLayoutParams(params);
|
||||
}
|
||||
ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||
if (params == null)
|
||||
params = new ViewGroup.LayoutParams(0, 0);
|
||||
|
||||
params.width = 0;
|
||||
params.height = 0;
|
||||
view.setLayoutParams(params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -514,6 +499,7 @@ public class Utils {
|
|||
return str != null && !str.isEmpty();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static boolean isTablet() {
|
||||
return context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
|
||||
}
|
||||
|
|
@ -553,6 +539,7 @@ public class Utils {
|
|||
return getTextDirectionString(isRightToLeftLocale());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static String getTextDirectionString(Locale locale) {
|
||||
return getTextDirectionString(isRightToLeftLocale(locale));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
|||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.text.Html;
|
||||
import android.util.Pair;
|
||||
import android.view.Gravity;
|
||||
|
|
@ -19,6 +19,7 @@ import android.widget.ImageView;
|
|||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ import app.revanced.extension.shared.Utils;
|
|||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.ui.CustomDialog;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
abstract class Check {
|
||||
private static final int NUMBER_OF_TIMES_TO_IGNORE_WARNING_BEFORE_DISABLING = 2;
|
||||
|
||||
|
|
@ -76,7 +78,6 @@ abstract class Check {
|
|||
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
static void issueWarning(Activity activity, Collection<Check> failedChecks) {
|
||||
final var reasons = new StringBuilder();
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import android.util.Base64;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
|
|
@ -29,6 +31,7 @@ import static app.revanced.extension.shared.checks.PatchInfo.Build.*;
|
|||
* <br>
|
||||
* Various indicators help to detect if the app was patched by the user.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@SuppressWarnings("unused")
|
||||
public final class CheckEnvironmentPatch {
|
||||
private static final boolean DEBUG_ALWAYS_SHOW_CHECK_FAILED_DIALOG = debugAlwaysShowWarning();
|
||||
|
|
@ -121,7 +124,7 @@ public final class CheckEnvironmentPatch {
|
|||
* If the build properties are different, the app was likely downloaded pre-patched or patched on another device.
|
||||
*/
|
||||
private static class CheckWasPatchedOnSameDevice extends Check {
|
||||
@SuppressLint({"NewApi", "HardwareIds"})
|
||||
@SuppressLint("HardwareIds")
|
||||
@Override
|
||||
protected Boolean check() {
|
||||
if (PATCH_BOARD.isEmpty()) {
|
||||
|
|
@ -195,7 +198,7 @@ public final class CheckEnvironmentPatch {
|
|||
PackageManager packageManager = context.getPackageManager();
|
||||
PackageInfo packageInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
|
||||
|
||||
// Duration since initial install or last update, which ever is sooner.
|
||||
// Duration since initial install or last update, whichever is sooner.
|
||||
durationBetweenPatchingAndInstallation = packageInfo.lastUpdateTime - PatchInfo.PATCH_TIME;
|
||||
Logger.printInfo(() -> "App was installed/updated: "
|
||||
+ (durationBetweenPatchingAndInstallation / (60 * 1000) + " minutes after patching"));
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ public class CustomBrandingPatch {
|
|||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* <p>
|
||||
* The total number of app name aliases, including dummy aliases.
|
||||
*/
|
||||
private static int numberOfPresetAppNames() {
|
||||
|
|
@ -146,13 +146,13 @@ public class CustomBrandingPatch {
|
|||
public static int getDefaultAppNameIndex() {
|
||||
return userProvidedCustomName()
|
||||
? numberOfPresetAppNames()
|
||||
: 1;
|
||||
: 2;
|
||||
}
|
||||
|
||||
public static BrandingTheme getDefaultIconStyle() {
|
||||
return userProvidedCustomIcon()
|
||||
? BrandingTheme.CUSTOM
|
||||
: BrandingTheme.ORIGINAL;
|
||||
: BrandingTheme.ROUNDED;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ public final class LithoFilterPatch {
|
|||
}
|
||||
builder.append(" Path: ");
|
||||
builder.append(path);
|
||||
if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
|
||||
if (YouTubeAndMusicSettings.DEBUG_PROTOCOLBUFFER.get()) {
|
||||
builder.append(" BufferStrings: ");
|
||||
findAsciiStrings(builder, buffer);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,15 @@ import android.annotation.SuppressLint;
|
|||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.ResourceType;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
|
|
@ -22,7 +25,8 @@ import app.revanced.extension.shared.ui.Dim;
|
|||
* Base class for hooking activities to inject a custom PreferenceFragment with a toolbar.
|
||||
* Provides common logic for initializing the activity and setting up the toolbar.
|
||||
*/
|
||||
@SuppressWarnings({"deprecation", "NewApi"})
|
||||
@SuppressWarnings("deprecation")
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public abstract class BaseActivityHook extends Activity {
|
||||
|
||||
private static final int ID_REVANCED_SETTINGS_FRAGMENTS =
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ public class BaseSettings {
|
|||
//
|
||||
|
||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
||||
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_video_streams_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
||||
|
||||
public static final BooleanSetting SANITIZE_SHARING_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE);
|
||||
public static final BooleanSetting REPLACE_MUSIC_LINKS_WITH_YOUTUBE = new BooleanSetting("revanced_replace_music_with_youtube", FALSE);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ public class YouTubeAndMusicSettings extends BaseSettings {
|
|||
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
||||
|
||||
// Miscellaneous
|
||||
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false,
|
||||
"revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG));
|
||||
|
||||
public static final BooleanSetting DEBUG_PROTOCOLBUFFER = new BooleanSetting("revanced_debug_protocolbuffer", FALSE, false,
|
||||
"revanced_debug_protocolbuffer_user_dialog_message", parent(BaseSettings.DEBUG));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,15 +15,16 @@ import android.widget.TextView;
|
|||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.ResourceType;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.BaseActivityHook;
|
||||
import app.revanced.extension.shared.ui.Dim;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
|
||||
@SuppressWarnings({"deprecation", "NewApi"})
|
||||
@SuppressWarnings("deprecation")
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -9,9 +9,34 @@ import java.util.Locale;
|
|||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
|
||||
@SuppressWarnings("ConstantLocale")
|
||||
public enum ClientType {
|
||||
/**
|
||||
* Video not playable: Paid, Movie, Private, Age-restricted.
|
||||
* Uses non-adaptive bitrate.
|
||||
* AV1 codec available.
|
||||
*/
|
||||
ANDROID_REEL(
|
||||
3,
|
||||
"ANDROID",
|
||||
"com.google.android.youtube",
|
||||
Build.MANUFACTURER,
|
||||
Build.MODEL,
|
||||
"Android",
|
||||
Build.VERSION.RELEASE,
|
||||
String.valueOf(Build.VERSION.SDK_INT),
|
||||
Build.ID,
|
||||
"20.44.38",
|
||||
// This client has been used by most open-source YouTube stream extraction tools since 2024, including NewPipe Extractor, SmartTube, and Grayjay.
|
||||
// This client can log in, but if an access token is used in the request, GVS can more easily identify the request as coming from ReVanced.
|
||||
// This means that the GVS server can strengthen its validation of the ANDROID_REEL client.
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
"Android Reel"
|
||||
),
|
||||
/**
|
||||
* Video not playable: Kids / Paid / Movie / Private / Age-restricted.
|
||||
* This client can only be used when logged out.
|
||||
|
|
@ -28,10 +53,10 @@ public enum ClientType {
|
|||
// Android 12.1
|
||||
"32",
|
||||
"SQ3A.220605.009.A1",
|
||||
"132.0.6808.3",
|
||||
"1.61.48",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
"Android VR 1.61"
|
||||
),
|
||||
/**
|
||||
|
|
@ -48,39 +73,12 @@ public enum ClientType {
|
|||
ANDROID_VR_1_61_48.osVersion,
|
||||
Objects.requireNonNull(ANDROID_VR_1_61_48.androidSdkVersion),
|
||||
Objects.requireNonNull(ANDROID_VR_1_61_48.buildId),
|
||||
"107.0.5284.2",
|
||||
"1.43.32",
|
||||
ANDROID_VR_1_61_48.useAuth,
|
||||
ANDROID_VR_1_61_48.supportsMultiAudioTracks,
|
||||
ANDROID_VR_1_61_48.usePlayerEndpoint,
|
||||
"Android VR 1.43"
|
||||
),
|
||||
/**
|
||||
* Video not playable: Paid / Movie / Private / Age-restricted.
|
||||
* Note: The 'Authorization' key must be excluded from the header.
|
||||
*
|
||||
* According to TeamNewPipe in 2022, if the 'androidSdkVersion' field is missing,
|
||||
* the GVS did not return a valid response:
|
||||
* [NewPipe#8713 (comment)](https://github.com/TeamNewPipe/NewPipe/issues/8713#issuecomment-1207443550).
|
||||
*
|
||||
* According to the latest commit in yt-dlp, the GVS returns a valid response
|
||||
* even if the 'androidSdkVersion' field is missing:
|
||||
* [yt-dlp#14693](https://github.com/yt-dlp/yt-dlp/pull/14693).
|
||||
*
|
||||
* For some reason, PoToken is not required.
|
||||
*/
|
||||
ANDROID_NO_SDK(
|
||||
3,
|
||||
"ANDROID",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
Build.VERSION.RELEASE,
|
||||
"20.05.46",
|
||||
"com.google.android.youtube/20.05.46 (Linux; U; Android " + Build.VERSION.RELEASE + ") gzip",
|
||||
false,
|
||||
true,
|
||||
"Android No SDK"
|
||||
),
|
||||
/**
|
||||
* Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
|
||||
* <a href="https://dumps.tadiphone.dev/dumps/google/barbet">Google Pixel 9 Pro Fold</a>
|
||||
|
|
@ -95,10 +93,10 @@ public enum ClientType {
|
|||
"15",
|
||||
"35",
|
||||
"AP3A.241005.015.A2",
|
||||
"132.0.6779.0",
|
||||
"23.47.101",
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
"Android Studio"
|
||||
),
|
||||
/**
|
||||
|
|
@ -114,32 +112,8 @@ public enum ClientType {
|
|||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15",
|
||||
false,
|
||||
false,
|
||||
"visionOS"
|
||||
),
|
||||
/**
|
||||
* The device machine id for the iPad 6th Gen (iPad7,6).
|
||||
* AV1 hardware decoding is not supported.
|
||||
* See [this GitHub Gist](https://gist.github.com/adamawolf/3048717) for more information.
|
||||
*
|
||||
* Based on Google's actions to date, PoToken may not be required on devices with very low specs.
|
||||
* For example, suppose the User-Agent for a PlayStation 3 (with 256MB of RAM) is used.
|
||||
* Accessing 'Web' (https://www.youtube.com) will redirect to 'TV' (https://www.youtube.com/tv).
|
||||
* 'TV' target devices with very low specs, such as embedded devices, game consoles, and blu-ray players, so PoToken is not required.
|
||||
*
|
||||
* For this reason, the device machine id for the iPad 6th Gen (with 2GB of RAM),
|
||||
* the lowest spec device capable of running iPadOS 17, was used.
|
||||
*/
|
||||
IPADOS(5,
|
||||
"IOS",
|
||||
"Apple",
|
||||
"iPad7,6",
|
||||
"iPadOS",
|
||||
"17.7.10.21H450",
|
||||
"19.22.3",
|
||||
"com.google.ios.youtube/19.22.3 (iPad7,6; U; CPU iPadOS 17_7_10 like Mac OS X; " + Locale.getDefault() + ")",
|
||||
false,
|
||||
true,
|
||||
"iPadOS"
|
||||
"visionOS"
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -195,13 +169,6 @@ public enum ClientType {
|
|||
@Nullable
|
||||
private final String buildId;
|
||||
|
||||
/**
|
||||
* Cronet release version, as found in decompiled client apk.
|
||||
* Field is null if not applicable.
|
||||
*/
|
||||
@Nullable
|
||||
private final String cronetVersion;
|
||||
|
||||
/**
|
||||
* App version.
|
||||
*/
|
||||
|
|
@ -217,6 +184,11 @@ public enum ClientType {
|
|||
*/
|
||||
public final boolean supportsMultiAudioTracks;
|
||||
|
||||
/**
|
||||
* If the client should use the player endpoint for stream extraction.
|
||||
*/
|
||||
public final boolean usePlayerEndpoint;
|
||||
|
||||
/**
|
||||
* Friendly name displayed in stats for nerds.
|
||||
*/
|
||||
|
|
@ -234,10 +206,10 @@ public enum ClientType {
|
|||
String osVersion,
|
||||
@NonNull String androidSdkVersion,
|
||||
@NonNull String buildId,
|
||||
@NonNull String cronetVersion,
|
||||
String clientVersion,
|
||||
boolean useAuth,
|
||||
boolean supportsMultiAudioTracks,
|
||||
boolean usePlayerEndpoint,
|
||||
String friendlyName) {
|
||||
this.id = id;
|
||||
this.clientName = clientName;
|
||||
|
|
@ -248,21 +220,20 @@ public enum ClientType {
|
|||
this.osVersion = osVersion;
|
||||
this.androidSdkVersion = androidSdkVersion;
|
||||
this.buildId = buildId;
|
||||
this.cronetVersion = cronetVersion;
|
||||
this.clientVersion = clientVersion;
|
||||
this.useAuth = useAuth;
|
||||
this.supportsMultiAudioTracks = supportsMultiAudioTracks;
|
||||
this.usePlayerEndpoint = usePlayerEndpoint;
|
||||
this.friendlyName = friendlyName;
|
||||
|
||||
Locale defaultLocale = Locale.getDefault();
|
||||
this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s; %s; Build/%s; Cronet/%s)",
|
||||
this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s; %s; Build/%s)",
|
||||
packageName,
|
||||
clientVersion,
|
||||
osVersion,
|
||||
defaultLocale,
|
||||
deviceModel,
|
||||
Objects.requireNonNull(buildId),
|
||||
Objects.requireNonNull(cronetVersion)
|
||||
buildId
|
||||
);
|
||||
Logger.printDebug(() -> "userAgent: " + this.userAgent);
|
||||
}
|
||||
|
|
@ -278,6 +249,7 @@ public enum ClientType {
|
|||
String userAgent,
|
||||
boolean useAuth,
|
||||
boolean supportsMultiAudioTracks,
|
||||
boolean usePlayerEndpoint,
|
||||
String friendlyName) {
|
||||
this.id = id;
|
||||
this.clientName = clientName;
|
||||
|
|
@ -289,10 +261,10 @@ public enum ClientType {
|
|||
this.userAgent = userAgent;
|
||||
this.useAuth = useAuth;
|
||||
this.supportsMultiAudioTracks = supportsMultiAudioTracks;
|
||||
this.usePlayerEndpoint = usePlayerEndpoint;
|
||||
this.friendlyName = friendlyName;
|
||||
this.packageName = null;
|
||||
this.androidSdkVersion = null;
|
||||
this.buildId = null;
|
||||
this.cronetVersion = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import android.text.TextUtils;
|
|||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
|
@ -39,7 +38,7 @@ public class SpoofVideoStreamsPatch {
|
|||
@Nullable
|
||||
private static volatile AppLanguage languageOverride;
|
||||
|
||||
private static volatile ClientType preferredClient = ClientType.ANDROID_VR_1_43_32;
|
||||
private static volatile ClientType preferredClient = ClientType.ANDROID_REEL;
|
||||
|
||||
/**
|
||||
* @return If this patch was included during patching.
|
||||
|
|
@ -250,7 +249,7 @@ public class SpoofVideoStreamsPatch {
|
|||
* Called after {@link #fetchStreams(String, Map)}.
|
||||
*/
|
||||
@Nullable
|
||||
public static ByteBuffer getStreamingData(String videoId) {
|
||||
public static byte[] getStreamingData(String videoId) {
|
||||
if (SPOOF_STREAMING_DATA) {
|
||||
try {
|
||||
StreamingDataRequest request = StreamingDataRequest.getRequestForVideoId(videoId);
|
||||
|
|
|
|||
|
|
@ -15,13 +15,20 @@ import app.revanced.extension.shared.spoof.ClientType;
|
|||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||
|
||||
final class PlayerRoutes {
|
||||
static final Route.CompiledRoute GET_STREAMING_DATA = new Route(
|
||||
static final Route.CompiledRoute GET_PLAYER_STREAMING_DATA = new Route(
|
||||
Route.Method.POST,
|
||||
"player" +
|
||||
"?fields=streamingData" +
|
||||
"&alt=proto"
|
||||
).compile();
|
||||
|
||||
static final Route.CompiledRoute GET_REEL_STREAMING_DATA = new Route(
|
||||
Route.Method.POST,
|
||||
"reel/reel_item_watch" +
|
||||
"?fields=playerResponse.playabilityStatus,playerResponse.streamingData" +
|
||||
"&alt=proto"
|
||||
).compile();
|
||||
|
||||
private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
|
||||
|
||||
/**
|
||||
|
|
@ -47,6 +54,7 @@ final class PlayerRoutes {
|
|||
Locale streamLocale = language.getLocale();
|
||||
|
||||
JSONObject client = new JSONObject();
|
||||
|
||||
client.put("deviceMake", clientType.deviceMake);
|
||||
client.put("deviceModel", clientType.deviceModel);
|
||||
client.put("clientName", clientType.clientName);
|
||||
|
|
@ -61,9 +69,19 @@ final class PlayerRoutes {
|
|||
context.put("client", client);
|
||||
|
||||
innerTubeBody.put("context", context);
|
||||
innerTubeBody.put("contentCheckOk", true);
|
||||
innerTubeBody.put("racyCheckOk", true);
|
||||
innerTubeBody.put("videoId", videoId);
|
||||
|
||||
if (clientType.usePlayerEndpoint) {
|
||||
innerTubeBody.put("contentCheckOk", true);
|
||||
innerTubeBody.put("racyCheckOk", true);
|
||||
innerTubeBody.put("videoId", videoId);
|
||||
} else {
|
||||
JSONObject playerRequest = new JSONObject();
|
||||
playerRequest.put("contentCheckOk", true);
|
||||
playerRequest.put("racyCheckOk", true);
|
||||
playerRequest.put("videoId", videoId);
|
||||
innerTubeBody.put("playerRequest", playerRequest);
|
||||
innerTubeBody.put("disablePlayerResponse", false);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Logger.printException(() -> "Failed to create innerTubeBody", e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
package app.revanced.extension.shared.spoof.requests;
|
||||
|
||||
import static app.revanced.extension.shared.ByteTrieSearch.convertStringsToBytes;
|
||||
import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_STREAMING_DATA;
|
||||
import static app.revanced.extension.shared.Utils.isNotEmpty;
|
||||
import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_PLAYER_STREAMING_DATA;
|
||||
import static app.revanced.extension.shared.spoof.requests.PlayerRoutes.GET_REEL_STREAMING_DATA;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
|
@ -27,6 +26,11 @@ import java.util.concurrent.TimeoutException;
|
|||
import app.revanced.extension.shared.ByteTrieSearch;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.innertube.PlayerResponseOuterClass;
|
||||
import app.revanced.extension.shared.innertube.PlayerResponseOuterClass.PlayerResponse;
|
||||
import app.revanced.extension.shared.innertube.PlayerResponseOuterClass.StreamingData;
|
||||
import app.revanced.extension.shared.innertube.ReelItemWatchResponseOuterClass.ReelItemWatchResponse;
|
||||
import app.revanced.extension.shared.requests.Route;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.shared.spoof.ClientType;
|
||||
|
||||
|
|
@ -41,7 +45,7 @@ import app.revanced.extension.shared.spoof.ClientType;
|
|||
*/
|
||||
public class StreamingDataRequest {
|
||||
|
||||
private static volatile ClientType[] clientOrderToUse = ClientType.values();
|
||||
private static volatile ClientType[] clientOrderToUse = ClientType.values();
|
||||
|
||||
public static void setClientOrderToUse(List<ClientType> availableClients, ClientType preferredClient) {
|
||||
Objects.requireNonNull(preferredClient);
|
||||
|
|
@ -111,7 +115,7 @@ public class StreamingDataRequest {
|
|||
|
||||
private final String videoId;
|
||||
|
||||
private final Future<ByteBuffer> future;
|
||||
private final Future<byte[]> future;
|
||||
|
||||
private StreamingDataRequest(String videoId, Map<String, String> playerHeaders) {
|
||||
Objects.requireNonNull(playerHeaders);
|
||||
|
|
@ -134,6 +138,12 @@ public class StreamingDataRequest {
|
|||
Logger.printInfo(() -> toastMessage, ex);
|
||||
}
|
||||
|
||||
private static void handleDebugToast(String toastMessage, ClientType clientType) {
|
||||
if (BaseSettings.DEBUG.get() && BaseSettings.DEBUG_TOAST_ON_ERROR.get()) {
|
||||
Utils.showToastShort(String.format(toastMessage, clientType));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static HttpURLConnection send(ClientType clientType,
|
||||
String videoId,
|
||||
|
|
@ -146,7 +156,10 @@ public class StreamingDataRequest {
|
|||
final long startTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_STREAMING_DATA, clientType);
|
||||
Route.CompiledRoute route = clientType.usePlayerEndpoint ?
|
||||
GET_PLAYER_STREAMING_DATA : GET_REEL_STREAMING_DATA;
|
||||
|
||||
HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(route, clientType);
|
||||
connection.setConnectTimeout(HTTP_TIMEOUT_MILLISECONDS);
|
||||
connection.setReadTimeout(HTTP_TIMEOUT_MILLISECONDS);
|
||||
|
||||
|
|
@ -203,7 +216,7 @@ public class StreamingDataRequest {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static ByteBuffer fetch(String videoId, Map<String, String> playerHeaders) {
|
||||
private static byte[] fetch(String videoId, Map<String, String> playerHeaders) {
|
||||
final boolean debugEnabled = BaseSettings.DEBUG.get();
|
||||
|
||||
// Retry with different client if empty response body is received.
|
||||
|
|
@ -214,33 +227,11 @@ public class StreamingDataRequest {
|
|||
|
||||
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
|
||||
if (connection != null) {
|
||||
try {
|
||||
// gzip encoding doesn't response with content length (-1),
|
||||
// but empty response body does.
|
||||
if (connection.getContentLength() == 0) {
|
||||
if (BaseSettings.DEBUG.get() && BaseSettings.DEBUG_TOAST_ON_ERROR.get()) {
|
||||
Utils.showToastShort("Debug: Ignoring empty spoof stream client " + clientType);
|
||||
}
|
||||
} else {
|
||||
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||
byte[] playerResponseBuffer = buildPlayerResponseBuffer(clientType, connection);
|
||||
if (playerResponseBuffer != null) {
|
||||
lastSpoofedClientType = clientType;
|
||||
|
||||
byte[] buffer = new byte[2048];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) >= 0) {
|
||||
baos.write(buffer, 0, bytesRead);
|
||||
}
|
||||
if (clientType == ClientType.ANDROID_CREATOR && liveStreamBufferSearch.matches(buffer)) {
|
||||
Logger.printDebug(() -> "Skipping Android Studio as video is a livestream: " + videoId);
|
||||
} else {
|
||||
lastSpoofedClientType = clientType;
|
||||
|
||||
return ByteBuffer.wrap(baos.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Logger.printException(() -> "Fetch failed while processing response data", ex);
|
||||
return playerResponseBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -250,12 +241,61 @@ public class StreamingDataRequest {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] buildPlayerResponseBuffer(ClientType clientType,
|
||||
HttpURLConnection connection) {
|
||||
// gzip encoding doesn't response with content length (-1),
|
||||
// but empty response body does.
|
||||
if (connection.getContentLength() == 0) {
|
||||
handleDebugToast("Debug: Ignoring empty spoof stream client (%s)", clientType);
|
||||
return null;
|
||||
}
|
||||
|
||||
try (InputStream inputStream = connection.getInputStream()) {
|
||||
PlayerResponse playerResponse = clientType.usePlayerEndpoint
|
||||
? PlayerResponse.parseFrom(inputStream)
|
||||
: ReelItemWatchResponse.parseFrom(inputStream).getPlayerResponse();
|
||||
|
||||
var playabilityStatus = playerResponse.getPlayabilityStatus();
|
||||
if (playabilityStatus.getStatus() != PlayerResponseOuterClass.Status.OK) {
|
||||
handleDebugToast("Debug: Ignoring unplayable video (%s)", clientType);
|
||||
String reason = playabilityStatus.getReason();
|
||||
if (isNotEmpty(reason)) {
|
||||
Logger.printDebug(() -> String.format("Debug: Ignoring unplayable video (%s), reason: %s", clientType, reason));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
PlayerResponse.Builder responseBuilder = playerResponse.toBuilder();
|
||||
if (!playerResponse.hasStreamingData()) {
|
||||
handleDebugToast("Debug: Ignoring empty streaming data (%s)", clientType);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Android Studio only supports the HLS protocol for live streams.
|
||||
// HLS protocol can theoretically be played with ExoPlayer,
|
||||
// but the related code has not yet been implemented.
|
||||
// If DASH protocol is not available, the client will be skipped.
|
||||
StreamingData streamingData = playerResponse.getStreamingData();
|
||||
if (streamingData.getAdaptiveFormatsCount() == 0) {
|
||||
handleDebugToast("Debug: Ignoring empty adaptiveFormat (%s)", clientType);
|
||||
return null;
|
||||
}
|
||||
|
||||
return responseBuilder.build().toByteArray();
|
||||
} catch (IOException ex) {
|
||||
Logger.printException(() -> "Failed to write player response to buffer array", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean fetchCompleted() {
|
||||
return future.isDone();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ByteBuffer getStream() {
|
||||
public byte[] getStream() {
|
||||
try {
|
||||
return future.get(MAX_MILLISECONDS_TO_WAIT_FOR_FETCH, TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException ex) {
|
||||
|
|
|
|||
55
extensions/shared/protobuf/build.gradle.kts
Normal file
55
extensions/shared/protobuf/build.gradle.kts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
alias(libs.plugins.protobuf)
|
||||
alias(libs.plugins.shadow)
|
||||
}
|
||||
|
||||
val shade: Configuration by configurations.creating {
|
||||
configurations.getByName("compileClasspath").extendsFrom(this)
|
||||
configurations.getByName("runtimeClasspath").extendsFrom(this)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly(libs.annotation)
|
||||
compileOnly(libs.okhttp)
|
||||
shade(libs.protobuf.javalite)
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
// Make sure generated proto sources are compiled and end up in the shaded jar
|
||||
main {
|
||||
java.srcDir("$buildDir/generated/source/proto/main/java")
|
||||
}
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = libs.protobuf.protoc.get().toString()
|
||||
}
|
||||
|
||||
generateProtoTasks {
|
||||
all().forEach { task ->
|
||||
task.builtins {
|
||||
named("java") {
|
||||
option("lite")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val shadowJar = tasks.named<ShadowJar>("shadowJar") {
|
||||
configurations = listOf(shade)
|
||||
relocate("com.google.protobuf", "app.revanced.com.google.protobuf")
|
||||
}
|
||||
|
||||
configurations.named("runtimeElements") {
|
||||
isCanBeConsumed = true
|
||||
isCanBeResolved = false
|
||||
|
||||
outgoing.artifacts.clear()
|
||||
outgoing.artifact(shadowJar)
|
||||
}!!.let { artifacts { add(it.name, shadowJar) } }
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package app.revanced.extension.shared.innertube;
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
option java_package = "app.revanced.extension.shared.innertube";
|
||||
|
||||
message PlayerResponse {
|
||||
oneof data {
|
||||
PlayabilityStatus playability_status = 2;
|
||||
StreamingData streaming_data = 4;
|
||||
}
|
||||
}
|
||||
|
||||
message PlayabilityStatus {
|
||||
Status status = 1;
|
||||
string reason = 2;
|
||||
}
|
||||
|
||||
enum Status {
|
||||
OK = 0;
|
||||
ERROR = 1;
|
||||
UNPLAYABLE = 2;
|
||||
LOGIN_REQUIRED = 3;
|
||||
CONTENT_CHECK_REQUIRED = 4;
|
||||
AGE_CHECK_REQUIRED = 5;
|
||||
LIVE_STREAM_OFFLINE = 6;
|
||||
FULLSCREEN_ONLY = 7;
|
||||
GL_PLAYBACK_REQUIRED = 8;
|
||||
AGE_VERIFICATION_REQUIRED = 9;
|
||||
}
|
||||
|
||||
message StreamingData {
|
||||
repeated Format formats = 2;
|
||||
repeated Format adaptiveFormats = 3;
|
||||
string serverAbrStreamingUrl = 15;
|
||||
}
|
||||
|
||||
message Format {
|
||||
string url = 2;
|
||||
string signatureCipher = 48;
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
syntax = "proto3";
|
||||
|
||||
import "app/revanced/extension/shared/innertube/player_response.proto";
|
||||
|
||||
package app.revanced.extension.shared.innertube;
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
option java_package = "app.revanced.extension.shared.innertube";
|
||||
|
||||
message ReelItemWatchResponse {
|
||||
oneof data {
|
||||
PlayerResponse player_response = 4;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,6 @@ dependencies {
|
|||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
minSdk = 24
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ android {
|
|||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
minSdk = 24
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,3 +3,9 @@ dependencies {
|
|||
compileOnly(project(":extensions:strava:stub"))
|
||||
compileOnly(libs.okhttp)
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
package app.revanced.extension.strava;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
|
|
@ -28,7 +26,6 @@ import java.util.stream.Stream;
|
|||
|
||||
import app.revanced.extension.shared.Utils;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public final class AddMediaDownloadPatch {
|
||||
public static final int ACTION_DOWNLOAD = -1;
|
||||
public static final int ACTION_OPEN_LINK = -2;
|
||||
|
|
@ -85,7 +82,7 @@ public final class AddMediaDownloadPatch {
|
|||
} finally {
|
||||
values.clear();
|
||||
values.put(MediaStore.Images.Media.IS_PENDING, 0);
|
||||
resolver.update(row, values, null);
|
||||
resolver.update(row, values, null, null);
|
||||
}
|
||||
showInfoToast("yis_2024_local_save_image_success", "✔️");
|
||||
} catch (IOException e) {
|
||||
|
|
@ -151,7 +148,7 @@ public final class AddMediaDownloadPatch {
|
|||
} finally {
|
||||
values.clear();
|
||||
values.put(MediaStore.Video.Media.IS_PENDING, 0);
|
||||
resolver.update(row, values, null);
|
||||
resolver.update(row, values, null, null);
|
||||
}
|
||||
showInfoToast("yis_2024_local_save_video_success", "✔️");
|
||||
} catch (IOException e) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package app.revanced.extension.strava;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import com.strava.modularframework.data.Destination;
|
||||
import com.strava.modularframework.data.GenericLayoutModule;
|
||||
import com.strava.modularframework.data.GenericModuleField;
|
||||
|
|
@ -21,7 +19,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public class HideDistractionsPatch {
|
||||
public static boolean upselling;
|
||||
public static boolean promo;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,6 @@ android {
|
|||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
minSdk = 26
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,3 +4,9 @@ dependencies {
|
|||
compileOnly(libs.annotation)
|
||||
compileOnly(libs.okhttp)
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 23
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,4 @@ android {
|
|||
defaultConfig {
|
||||
minSdk = 22
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import java.lang.reflect.InvocationTargetException;
|
|||
/**
|
||||
* Hooks AdPersonalizationActivity to inject a custom {@link TikTokPreferenceFragment}.
|
||||
*/
|
||||
@SuppressWarnings({"deprecation", "NewApi", "unused"})
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class TikTokActivityHook {
|
||||
public static Object createSettingsEntry(String entryClazzName, String entryInfoClazzName) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
dependencies {
|
||||
compileOnly(libs.appcompat)
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 22
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,9 +11,4 @@ android {
|
|||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import tv.twitch.android.settings.SettingsActivity;
|
|||
/**
|
||||
* Hooks AppCompatActivity to inject a custom {@link TwitchPreferenceFragment}.
|
||||
*/
|
||||
@SuppressWarnings({"deprecation", "NewApi", "unused"})
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class TwitchActivityHook {
|
||||
private static final int REVANCED_SETTINGS_MENU_ITEM_ID = 0x7;
|
||||
private static final String EXTRA_REVANCED_SETTINGS = "app.revanced.twitch.settings";
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
}
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,8 +238,8 @@ public final class AlternativeThumbnailsPatch {
|
|||
// See https://github.com/ajayyy/DeArrowThumbnailCache/blob/29eb4359ebdf823626c79d944a901492d760bbbc/app.py#L29.
|
||||
return dearrowAPIURI
|
||||
.buildUpon()
|
||||
.appendQueryParameter("videoId", videoId)
|
||||
.appendQueryParameter("redirectURL", fallbackURL)
|
||||
.appendQueryParameter("videoID", videoId)
|
||||
.appendQueryParameter("redirectUrl", fallbackURL)
|
||||
.build()
|
||||
.toString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ import android.widget.LinearLayout;
|
|||
import android.widget.TextView;
|
||||
|
||||
import app.revanced.extension.shared.ui.CustomDialog;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
|
@ -29,9 +31,20 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||
|
||||
@SuppressWarnings("unused")
|
||||
public final class AnnouncementsPatch {
|
||||
private static final String[] ANNOUNCEMENT_TAGS = {
|
||||
"\uD83C\uDF9E\uFE0F YouTube",
|
||||
};
|
||||
|
||||
private AnnouncementsPatch() {
|
||||
}
|
||||
|
||||
private static boolean hasSupportedTag(String wrapperTag) {
|
||||
if (wrapperTag == null) return false;
|
||||
for (var tag : ANNOUNCEMENT_TAGS) if (tag.equals(wrapperTag)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isLatestAlready() throws IOException {
|
||||
HttpURLConnection connection =
|
||||
AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT_IDS);
|
||||
|
|
@ -56,19 +69,30 @@ public final class AnnouncementsPatch {
|
|||
|
||||
var jsonString = Requester.parseStringAndDisconnect(connection);
|
||||
|
||||
// Parse the ID. Fall-back to raw string if it fails.
|
||||
int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
||||
var id = -1;
|
||||
try {
|
||||
final var announcementIDs = new JSONArray(jsonString);
|
||||
if (announcementIDs.length() == 0) return true;
|
||||
|
||||
id = announcementIDs.getJSONObject(0).getInt("id");
|
||||
final var announcementIDTagPairs = new JSONArray(jsonString);
|
||||
if (announcementIDTagPairs.length() == 0) return true;
|
||||
|
||||
JSONObject latest = null;
|
||||
for (int i = 0, entryCount = announcementIDTagPairs.length(); i < entryCount; i++) {
|
||||
var pair = announcementIDTagPairs.optJSONObject(i);
|
||||
if (pair != null && hasSupportedTag(pair.optString("tag", null))) {
|
||||
latest = pair;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (latest == null || latest.isNull("id")) return true;
|
||||
|
||||
id = latest.getInt("id");
|
||||
} catch (Throwable ex) {
|
||||
Logger.printException(() -> "Failed to parse announcement ID", ex);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do not show the announcement, if the last announcement id is the same as the current one.
|
||||
return Settings.ANNOUNCEMENT_LAST_ID.get() == id;
|
||||
return Settings.ANNOUNCEMENT_LAST_ID.get().equals(id);
|
||||
}
|
||||
|
||||
public static void showAnnouncement(final Activity context) {
|
||||
|
|
@ -95,7 +119,22 @@ public final class AnnouncementsPatch {
|
|||
LocalDateTime archivedAt = LocalDateTime.MAX;
|
||||
Level level = Level.INFO;
|
||||
try {
|
||||
final var announcement = new JSONArray(jsonString).getJSONObject(0);
|
||||
final var announcements = new JSONArray(jsonString);
|
||||
JSONObject latestAnnouncement = null;
|
||||
for (int i = 0, entryCount = announcements.length(); i < entryCount; i++) {
|
||||
var announcementTagPair = announcements.optJSONObject(i);
|
||||
if (announcementTagPair != null && hasSupportedTag(announcementTagPair.optString("tag", null))) {
|
||||
latestAnnouncement = announcementTagPair;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (latestAnnouncement == null || latestAnnouncement.isNull("announcement")) {
|
||||
Logger.printDebug(() -> "No YouTube announcement found in latest announcements response");
|
||||
return;
|
||||
}
|
||||
|
||||
final var announcement = latestAnnouncement.getJSONObject("announcement");
|
||||
|
||||
id = announcement.getInt("id");
|
||||
title = announcement.getString("title");
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import java.net.HttpURLConnection;
|
|||
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
||||
|
||||
public class AnnouncementsRoutes {
|
||||
private static final String ANNOUNCEMENTS_PROVIDER = "https://api.revanced.app/v4";
|
||||
public static final Route GET_LATEST_ANNOUNCEMENT_IDS = new Route(GET, "/announcements/latest/id?tag=\uD83C\uDF9E\uFE0F%20YouTube");
|
||||
public static final Route GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest?tag=\uD83C\uDF9E\uFE0F%20YouTube");
|
||||
private static final String ANNOUNCEMENTS_PROVIDER = "https://api.revanced.app/v5";
|
||||
public static final Route GET_LATEST_ANNOUNCEMENT_IDS = new Route(GET, "/announcements/latest/id");
|
||||
public static final Route GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest");
|
||||
|
||||
private AnnouncementsRoutes() {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
package app.revanced.extension.youtube.patches.spoof;
|
||||
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_CREATOR;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_NO_SDK;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.IPADOS;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_REEL;
|
||||
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -44,11 +43,11 @@ public class SpoofVideoStreamsPatch {
|
|||
}
|
||||
|
||||
List<ClientType> availableClients = List.of(
|
||||
VISIONOS,
|
||||
ANDROID_CREATOR,
|
||||
ANDROID_REEL,
|
||||
ANDROID_VR_1_43_32,
|
||||
ANDROID_NO_SDK,
|
||||
IPADOS);
|
||||
VISIONOS,
|
||||
ANDROID_CREATOR
|
||||
);
|
||||
|
||||
app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse(
|
||||
availableClients, client);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package app.revanced.extension.youtube.patches.theme;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
|
|
@ -10,7 +12,6 @@ import androidx.annotation.Nullable;
|
|||
|
||||
import app.revanced.extension.shared.ResourceType;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
|
||||
/**
|
||||
* Dynamic drawable that is either the regular or bolded ReVanced preference icon.
|
||||
|
|
@ -82,4 +83,19 @@ public class ReVancedSettingsIconDynamicDrawable extends Drawable {
|
|||
super.onBoundsChange(bounds);
|
||||
icon.setBounds(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTint(int tintColor) {
|
||||
icon.setTint(tintColor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTintList(@Nullable ColorStateList tint) {
|
||||
icon.setTintList(tint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
|
||||
icon.setTintMode(tintMode);
|
||||
}
|
||||
}
|
||||
|
|
@ -392,7 +392,7 @@ public class Settings extends YouTubeAndMusicSettings {
|
|||
public static final BooleanSetting EXTERNAL_BROWSER = new BooleanSetting("revanced_external_browser", TRUE, true);
|
||||
public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true,
|
||||
"revanced_spoof_device_dimensions_user_dialog_message");
|
||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
|
||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_REEL, true, parent(SPOOF_VIDEO_STREAMS));
|
||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS_AV1 = new BooleanSetting("revanced_spoof_video_streams_av1", FALSE, true,
|
||||
"revanced_spoof_video_streams_av1_user_dialog_message", new SpoofClientAv1Availability());
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import app.revanced.extension.shared.spoof.ClientType;
|
|||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
||||
public class SpoofVideoStreamsSideEffectsPreference extends Preference {
|
||||
|
||||
@Nullable
|
||||
private ClientType currentClientType;
|
||||
|
|
@ -33,19 +33,19 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
|||
Utils.runOnMainThread(this::updateUI);
|
||||
};
|
||||
|
||||
public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
public SpoofVideoStreamsSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
public SpoofVideoStreamsSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs) {
|
||||
public SpoofVideoStreamsSideEffectsPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SpoofStreamingDataSideEffectsPreference(Context context) {
|
||||
public SpoofVideoStreamsSideEffectsPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
|
|
@ -88,27 +88,23 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
|||
+ '\n' + str("revanced_spoof_video_streams_about_no_stable_volume")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_av1")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_force_original_audio");
|
||||
case ANDROID_REEL ->
|
||||
summary = str("revanced_spoof_video_streams_about_playback_failure");
|
||||
// VR 1.61 is not exposed in the UI and should never be reached here.
|
||||
case ANDROID_VR_1_43_32, ANDROID_VR_1_61_48 ->
|
||||
summary = str("revanced_spoof_video_streams_about_no_audio_tracks")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_stable_volume");
|
||||
case ANDROID_NO_SDK ->
|
||||
summary = str("revanced_spoof_video_streams_about_playback_failure");
|
||||
case IPADOS ->
|
||||
summary = str("revanced_spoof_video_streams_about_playback_failure")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_av1");
|
||||
case VISIONOS ->
|
||||
summary = str("revanced_spoof_video_streams_about_experimental")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_audio_tracks")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_av1");
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_stable_volume");
|
||||
case VISIONOS -> summary = str("revanced_spoof_video_streams_about_experimental")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_playback_failure")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_audio_tracks")
|
||||
+ '\n' + str("revanced_spoof_video_streams_about_no_av1");
|
||||
default -> Logger.printException(() -> "Unknown client: " + clientType);
|
||||
}
|
||||
|
||||
// Only iPadOS can play children videos in incognito, but it commonly fails at 1 minute
|
||||
// or doesn't start playback at all. List the side effect for other clients
|
||||
// since they will fall over to iPadOS.
|
||||
if (clientType != ClientType.IPADOS && clientType != ClientType.ANDROID_NO_SDK) {
|
||||
summary += '\n' + str("revanced_spoof_video_streams_about_kids_videos");
|
||||
// Only Android Reel and Android VR supports 360° VR immersive mode.
|
||||
if (!clientType.name().startsWith("ANDROID_VR") && clientType != ClientType.ANDROID_REEL) {
|
||||
summary += '\n' + str("revanced_spoof_video_streams_about_no_immersive_mode");
|
||||
}
|
||||
|
||||
// Use better formatting for bullet points.
|
||||
|
|
@ -3,7 +3,6 @@ package app.revanced.extension.youtube.sponsorblock;
|
|||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
|
|
@ -51,7 +50,6 @@ import kotlin.Unit;
|
|||
* <p>
|
||||
* Class is not thread safe. All methods must be called on the main thread unless otherwise specified.
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
public class SegmentPlaybackController {
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour;
|
|||
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory;
|
||||
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
|
||||
|
||||
@SuppressWarnings("NewApi")
|
||||
public class SponsorBlockSettings {
|
||||
/**
|
||||
* Minimum length an SB user ID must be, as set by SB API.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue