diff --git a/extensions/music/build.gradle.kts b/extensions/music/build.gradle.kts
new file mode 100644
index 0000000000..a12d4c1f12
--- /dev/null
+++ b/extensions/music/build.gradle.kts
@@ -0,0 +1,4 @@
+dependencies {
+ compileOnly(project(":extensions:shared:library"))
+ compileOnly(libs.annotation)
+}
diff --git a/extensions/music/src/main/AndroidManifest.xml b/extensions/music/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..9b65eb06cf
--- /dev/null
+++ b/extensions/music/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+
diff --git a/extensions/music/src/main/java/app/revanced/extension/music/announcements/AnnouncementsPatch.java b/extensions/music/src/main/java/app/revanced/extension/music/announcements/AnnouncementsPatch.java
new file mode 100644
index 0000000000..7366dee1a2
--- /dev/null
+++ b/extensions/music/src/main/java/app/revanced/extension/music/announcements/AnnouncementsPatch.java
@@ -0,0 +1,20 @@
+package app.revanced.extension.music.announcements;
+
+import android.app.Activity;
+import android.os.Build;
+import androidx.annotation.RequiresApi;
+import app.revanced.extension.shared.announcements.BaseAnnouncementsPatch;
+
+@SuppressWarnings("unused")
+public class AnnouncementsPatch extends BaseAnnouncementsPatch {
+ private static final AnnouncementsPatch INSTANCE = new AnnouncementsPatch();
+
+ private AnnouncementsPatch() {
+ super("music");
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ public static void showAnnouncement(final Activity context) {
+ INSTANCE._showAnnouncement(context);
+ }
+}
diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/announcements/BaseAnnouncementsPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/announcements/BaseAnnouncementsPatch.java
new file mode 100644
index 0000000000..4e23214a88
--- /dev/null
+++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/announcements/BaseAnnouncementsPatch.java
@@ -0,0 +1,172 @@
+package app.revanced.extension.shared.announcements;
+
+import static android.text.Html.FROM_HTML_MODE_COMPACT;
+import static app.revanced.extension.shared.StringRef.str;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.os.Build;
+import android.text.Html;
+import android.text.method.LinkMovementMethod;
+import android.widget.TextView;
+
+import androidx.annotation.RequiresApi;
+
+import org.json.JSONArray;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.time.LocalDateTime;
+
+import app.revanced.extension.shared.Logger;
+import app.revanced.extension.shared.Utils;
+import app.revanced.extension.shared.requests.Requester;
+import app.revanced.extension.shared.announcements.requests.AnnouncementsRoutes;
+import app.revanced.extension.shared.settings.BaseSettings;
+
+public abstract class BaseAnnouncementsPatch {
+ private final AnnouncementsRoutes announcementsRoutes;
+
+ public BaseAnnouncementsPatch(String tag) {
+ this.announcementsRoutes = new AnnouncementsRoutes(tag);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private boolean isLatestAlready() throws IOException {
+ HttpURLConnection connection =
+ announcementsRoutes.getAnnouncementsConnectionFromRoute(announcementsRoutes.GET_LATEST_ANNOUNCEMENT_IDS);
+
+ Logger.printDebug(() -> "Get latest announcement IDs route connection url: " + connection.getURL());
+
+ try {
+ // Do not show the announcement if the request failed.
+ if (connection.getResponseCode() != 200) {
+ if (BaseSettings.ANNOUNCEMENT_LAST_ID.isSetToDefault())
+ return true;
+
+ BaseSettings.ANNOUNCEMENT_LAST_ID.resetToDefault();
+ Utils.showToastLong(str("revanced_announcements_connection_failed"));
+
+ return true;
+ }
+ } catch (IOException ex) {
+ Logger.printException(() -> "Could not connect to announcements provider", ex);
+ return true;
+ }
+
+ var jsonString = Requester.parseStringAndDisconnect(connection);
+
+ // Parse the ID. Fall-back to raw string if it fails.
+ int id = BaseSettings.ANNOUNCEMENT_LAST_ID.defaultValue;
+ try {
+ final var announcementIds = new JSONArray(jsonString);
+ id = announcementIds.getJSONObject(0).getInt("id");
+
+ } catch (Throwable ex) {
+ Logger.printException(() -> "Failed to parse announcement IDs", ex);
+ }
+
+ // Do not show the announcement, if the last announcement id is the same as the current one.
+ return BaseSettings.ANNOUNCEMENT_LAST_ID.get() == id;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ public void _showAnnouncement(final Activity context) {
+ if (!BaseSettings.ANNOUNCEMENTS.get()) return;
+
+ // Check if there is internet connection
+ if (!Utils.isNetworkConnected()) return;
+
+ Utils.runOnBackgroundThread(() -> {
+ try {
+ if (isLatestAlready()) return;
+
+ HttpURLConnection connection = announcementsRoutes
+ .getAnnouncementsConnectionFromRoute(announcementsRoutes.GET_LATEST_ANNOUNCEMENTS);
+
+ Logger.printDebug(() -> "Get latest announcements route connection url: " + connection.getURL());
+
+ var jsonString = Requester.parseStringAndDisconnect(connection);
+
+ // Parse the announcement. Fall-back to raw string if it fails.
+ int id = BaseSettings.ANNOUNCEMENT_LAST_ID.defaultValue;
+ String title;
+ String message;
+ LocalDateTime archivedAt = LocalDateTime.MAX;
+ Level level = Level.INFO;
+ try {
+ final var announcement = new JSONArray(jsonString).getJSONObject(0);
+
+ id = announcement.getInt("id");
+ title = announcement.getString("title");
+ message = announcement.getString("content");
+ if (!announcement.isNull("archived_at")) {
+ archivedAt = LocalDateTime.parse(announcement.getString("archived_at"));
+ }
+ if (!announcement.isNull("level")) {
+ level = Level.fromInt(announcement.getInt("level"));
+ }
+ } catch (Throwable ex) {
+ Logger.printException(() -> "Failed to parse announcement. Fall-backing to raw string", ex);
+
+ title = "Announcement";
+ message = jsonString;
+ }
+
+ // If the announcement is archived, do not show it.
+ if (archivedAt.isBefore(LocalDateTime.now())) {
+ BaseSettings.ANNOUNCEMENT_LAST_ID.save(id);
+ return;
+ }
+
+ int finalId = id;
+ final var finalTitle = title;
+ final var finalMessage = Html.fromHtml(message, FROM_HTML_MODE_COMPACT);
+ final Level finalLevel = level;
+
+ Utils.runOnMainThread(() -> {
+ // Show the announcement.
+ var alert = new AlertDialog.Builder(context)
+ .setTitle(finalTitle)
+ .setMessage(finalMessage)
+ .setIcon(finalLevel.icon)
+ .setPositiveButton(android.R.string.ok, (dialog, which) -> {
+ BaseSettings.ANNOUNCEMENT_LAST_ID.save(finalId);
+ dialog.dismiss();
+ }).setNegativeButton(str("revanced_announcements_dialog_dismiss"), (dialog, which) -> {
+ dialog.dismiss();
+ })
+ .setCancelable(false)
+ .create();
+
+ Utils.showDialog(context, alert, false, (AlertDialog dialog) -> {
+ // Make links clickable.
+ ((TextView) dialog.findViewById(android.R.id.message))
+ .setMovementMethod(LinkMovementMethod.getInstance());
+ });
+ });
+ } catch (Exception e) {
+ final var message = "Failed to get announcement";
+
+ Logger.printException(() -> message, e);
+ }
+ });
+ }
+
+ // TODO: Use better icons.
+ private enum Level {
+ INFO(android.R.drawable.ic_dialog_info),
+ WARNING(android.R.drawable.ic_dialog_alert),
+ SEVERE(android.R.drawable.ic_dialog_alert);
+
+ public final int icon;
+
+ Level(int icon) {
+ this.icon = icon;
+ }
+
+ public static Level fromInt(int value) {
+ return values()[Math.min(value, values().length - 1)];
+ }
+ }
+}
diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/announcements/requests/AnnouncementsRoutes.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/announcements/requests/AnnouncementsRoutes.java
new file mode 100644
index 0000000000..f0fb205535
--- /dev/null
+++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/announcements/requests/AnnouncementsRoutes.java
@@ -0,0 +1,23 @@
+package app.revanced.extension.shared.announcements.requests;
+
+import app.revanced.extension.shared.requests.Requester;
+import app.revanced.extension.shared.requests.Route;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+
+import static app.revanced.extension.shared.requests.Route.Method.GET;
+
+public class AnnouncementsRoutes {
+ public final Route GET_LATEST_ANNOUNCEMENTS;
+ public final Route GET_LATEST_ANNOUNCEMENT_IDS;
+
+ public AnnouncementsRoutes(String tag) {
+ this.GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest?tag=" + tag);
+ this.GET_LATEST_ANNOUNCEMENT_IDS = new Route(GET, "/announcements/latest/ids?tag=" + tag);
+ }
+
+ public HttpURLConnection getAnnouncementsConnectionFromRoute(Route route, String... params) throws IOException {
+ return Requester.getConnectionFromRoute("https://api.revanced.app/v4", route, params);
+ }
+}
diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java
index 13be9547ca..80bef81315 100644
--- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java
+++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseSettings.java
@@ -16,4 +16,7 @@ public class BaseSettings {
public static final BooleanSetting DEBUG_TOAST_ON_ERROR = new BooleanSetting("revanced_debug_toast_on_error", TRUE, "revanced_debug_toast_on_error_user_dialog_message");
public static final IntegerSetting CHECK_ENVIRONMENT_WARNINGS_ISSUED = new IntegerSetting("revanced_check_environment_warnings_issued", 0, true, false);
+
+ public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
+ public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false);
}
diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/announcements/AnnouncementsPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/announcements/AnnouncementsPatch.java
index 92490aabd7..5012820f6f 100644
--- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/announcements/AnnouncementsPatch.java
+++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/announcements/AnnouncementsPatch.java
@@ -1,172 +1,20 @@
package app.revanced.extension.youtube.patches.announcements;
-import static android.text.Html.FROM_HTML_MODE_COMPACT;
-import static app.revanced.extension.shared.StringRef.str;
-import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENTS;
-import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENT_IDS;
-
import android.app.Activity;
-import android.app.AlertDialog;
import android.os.Build;
-import android.text.Html;
-import android.text.method.LinkMovementMethod;
-import android.widget.TextView;
-
import androidx.annotation.RequiresApi;
-
-import org.json.JSONArray;
-
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.time.LocalDateTime;
-
-import app.revanced.extension.shared.Logger;
-import app.revanced.extension.shared.Utils;
-import app.revanced.extension.shared.requests.Requester;
-import app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes;
-import app.revanced.extension.youtube.settings.Settings;
+import app.revanced.extension.shared.announcements.BaseAnnouncementsPatch;
@SuppressWarnings("unused")
-public final class AnnouncementsPatch {
- private AnnouncementsPatch() {
- }
-
- @RequiresApi(api = Build.VERSION_CODES.O)
- private static boolean isLatestAlready() throws IOException {
- HttpURLConnection connection =
- AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT_IDS);
+public class AnnouncementsPatch extends BaseAnnouncementsPatch {
+ private static final AnnouncementsPatch INSTANCE = new AnnouncementsPatch();
- Logger.printDebug(() -> "Get latest announcement IDs route connection url: " + connection.getURL());
-
- try {
- // Do not show the announcement if the request failed.
- if (connection.getResponseCode() != 200) {
- if (Settings.ANNOUNCEMENT_LAST_ID.isSetToDefault())
- return true;
-
- Settings.ANNOUNCEMENT_LAST_ID.resetToDefault();
- Utils.showToastLong(str("revanced_announcements_connection_failed"));
-
- return true;
- }
- } catch (IOException ex) {
- Logger.printException(() -> "Could not connect to announcements provider", ex);
- return true;
- }
-
- var jsonString = Requester.parseStringAndDisconnect(connection);
-
- // Parse the ID. Fall-back to raw string if it fails.
- int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
- try {
- final var announcementIds = new JSONArray(jsonString);
- id = announcementIds.getJSONObject(0).getInt("id");
-
- } catch (Throwable ex) {
- Logger.printException(() -> "Failed to parse announcement IDs", ex);
- }
-
- // Do not show the announcement, if the last announcement id is the same as the current one.
- return Settings.ANNOUNCEMENT_LAST_ID.get() == id;
+ private AnnouncementsPatch() {
+ super("youtube");
}
@RequiresApi(api = Build.VERSION_CODES.O)
public static void showAnnouncement(final Activity context) {
- if (!Settings.ANNOUNCEMENTS.get()) return;
-
- // Check if there is internet connection
- if (!Utils.isNetworkConnected()) return;
-
- Utils.runOnBackgroundThread(() -> {
- try {
- if (isLatestAlready()) return;
-
- HttpURLConnection connection = AnnouncementsRoutes
- .getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENTS);
-
- Logger.printDebug(() -> "Get latest announcements route connection url: " + connection.getURL());
-
- var jsonString = Requester.parseStringAndDisconnect(connection);
-
- // Parse the announcement. Fall-back to raw string if it fails.
- int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
- String title;
- String message;
- LocalDateTime archivedAt = LocalDateTime.MAX;
- Level level = Level.INFO;
- try {
- final var announcement = new JSONArray(jsonString).getJSONObject(0);
-
- id = announcement.getInt("id");
- title = announcement.getString("title");
- message = announcement.getString("content");
- if (!announcement.isNull("archived_at")) {
- archivedAt = LocalDateTime.parse(announcement.getString("archived_at"));
- }
- if (!announcement.isNull("level")) {
- level = Level.fromInt(announcement.getInt("level"));
- }
- } catch (Throwable ex) {
- Logger.printException(() -> "Failed to parse announcement. Fall-backing to raw string", ex);
-
- title = "Announcement";
- message = jsonString;
- }
-
- // If the announcement is archived, do not show it.
- if (archivedAt.isBefore(LocalDateTime.now())) {
- Settings.ANNOUNCEMENT_LAST_ID.save(id);
- return;
- }
-
- int finalId = id;
- final var finalTitle = title;
- final var finalMessage = Html.fromHtml(message, FROM_HTML_MODE_COMPACT);
- final Level finalLevel = level;
-
- Utils.runOnMainThread(() -> {
- // Show the announcement.
- var alert = new AlertDialog.Builder(context)
- .setTitle(finalTitle)
- .setMessage(finalMessage)
- .setIcon(finalLevel.icon)
- .setPositiveButton(android.R.string.ok, (dialog, which) -> {
- Settings.ANNOUNCEMENT_LAST_ID.save(finalId);
- dialog.dismiss();
- }).setNegativeButton(str("revanced_announcements_dialog_dismiss"), (dialog, which) -> {
- dialog.dismiss();
- })
- .setCancelable(false)
- .create();
-
- Utils.showDialog(context, alert, false, (AlertDialog dialog) -> {
- // Make links clickable.
- ((TextView) dialog.findViewById(android.R.id.message))
- .setMovementMethod(LinkMovementMethod.getInstance());
- });
- });
- } catch (Exception e) {
- final var message = "Failed to get announcement";
-
- Logger.printException(() -> message, e);
- }
- });
- }
-
- // TODO: Use better icons.
- private enum Level {
- INFO(android.R.drawable.ic_dialog_info),
- WARNING(android.R.drawable.ic_dialog_alert),
- SEVERE(android.R.drawable.ic_dialog_alert);
-
- public final int icon;
-
- Level(int icon) {
- this.icon = icon;
- }
-
- public static Level fromInt(int value) {
- return values()[Math.min(value, values().length - 1)];
- }
+ INSTANCE._showAnnouncement(context);
}
}
diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/announcements/requests/AnnouncementsRoutes.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/announcements/requests/AnnouncementsRoutes.java
deleted file mode 100644
index 6f52610567..0000000000
--- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/announcements/requests/AnnouncementsRoutes.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package app.revanced.extension.youtube.patches.announcements.requests;
-
-import app.revanced.extension.shared.requests.Requester;
-import app.revanced.extension.shared.requests.Route;
-
-import java.io.IOException;
-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=youtube");
- public static final Route GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest?tag=youtube");
-
- private AnnouncementsRoutes() {
- }
-
- public static HttpURLConnection getAnnouncementsConnectionFromRoute(Route route, String... params) throws IOException {
- return Requester.getConnectionFromRoute(ANNOUNCEMENTS_PROVIDER, route, params);
- }
-}
\ No newline at end of file
diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java
index 4f11de9284..eab36ea3ff 100644
--- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java
+++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java
@@ -271,12 +271,10 @@ public class Settings extends BaseSettings {
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 BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
- public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
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_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofVideoStreamsPatch.ForceiOSAVCAvailability());
public static final EnumSetting SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS));
- public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false);
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE);
diff --git a/patches/api/patches.api b/patches/api/patches.api
index 1c601b5515..c3a1942f69 100644
--- a/patches/api/patches.api
+++ b/patches/api/patches.api
@@ -2,6 +2,11 @@ public final class app/revanced/patches/all/misc/activity/exportall/ExportAllAct
public static final fun getExportAllActivitiesPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
+public final class app/revanced/patches/all/misc/announcements/AnnouncementsPatchKt {
+ public static final fun announcementsPatch (Lapp/revanced/patcher/Fingerprint;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
+ public static synthetic fun announcementsPatch$default (Lapp/revanced/patcher/Fingerprint;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
+}
+
public final class app/revanced/patches/all/misc/build/BaseSpoofBuildInfoPatchKt {
public static final fun baseSpoofBuildInfoPatch (Lkotlin/jvm/functions/Function0;)Lapp/revanced/patcher/patch/BytecodePatch;
}
@@ -304,6 +309,10 @@ public final class app/revanced/patches/music/misc/androidauto/BypassCertificate
public static final fun getBypassCertificateChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
+public final class app/revanced/patches/music/misc/announcements/AnnouncementsPatchKt {
+ public static final fun getAnnouncementsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
+}
+
public final class app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatchKt {
public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/announcements/AnnouncementsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/announcements/AnnouncementsPatch.kt
new file mode 100644
index 0000000000..712286b1fc
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/announcements/AnnouncementsPatch.kt
@@ -0,0 +1,36 @@
+package app.revanced.patches.all.misc.announcements
+
+import app.revanced.patcher.Fingerprint
+import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
+import app.revanced.patcher.patch.*
+import app.revanced.patches.all.misc.resources.addResources
+import app.revanced.patches.all.misc.resources.addResourcesPatch
+
+fun announcementsPatch(
+ mainActivityOnCreateFingerprint: Fingerprint,
+ extensionPatch: Patch<*>,
+ extensionClassDescriptor: String,
+ block: BytecodePatchBuilder.() -> Unit = {},
+ executeBlock: BytecodePatchContext.() -> Unit = {},
+) = bytecodePatch(
+ name = "Announcements",
+ description = "Shows announcements from ReVanced on app startup.",
+) {
+ block()
+
+ dependsOn(
+ extensionPatch,
+ addResourcesPatch,
+ )
+
+ execute {
+ addResources("shared", "misc.announcements.announcementsPatch")
+
+ mainActivityOnCreateFingerprint.method.addInstructions(
+ 0,
+ "invoke-static/range { p0 .. p0 }, $extensionClassDescriptor->showAnnouncement(Landroid/app/Activity;)V",
+ )
+
+ executeBlock()
+ }
+}
diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/announcements/AnnouncementsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/announcements/AnnouncementsPatch.kt
new file mode 100644
index 0000000000..8ac3422245
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/announcements/AnnouncementsPatch.kt
@@ -0,0 +1,14 @@
+package app.revanced.patches.music.misc.announcements
+
+import app.revanced.patches.all.misc.announcements.announcementsPatch
+import app.revanced.patches.music.misc.extension.sharedExtensionPatch
+import app.revanced.patches.music.shared.musicActivityOnCreateFingerprint
+
+val announcementsPatch = announcementsPatch(
+ musicActivityOnCreateFingerprint,
+ sharedExtensionPatch,
+ "Lapp/revanced/extension/music/announcements/AnnouncementsPatch;",
+ {
+ compatibleWith("com.google.android.apps.music")
+ },
+)
diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/extension/SharedExtensionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/extension/SharedExtensionPatch.kt
index 5b25a006a4..9351b600ee 100644
--- a/patches/src/main/kotlin/app/revanced/patches/music/misc/extension/SharedExtensionPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/extension/SharedExtensionPatch.kt
@@ -4,5 +4,6 @@ import app.revanced.patches.music.misc.extension.hooks.applicationInitHook
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
val sharedExtensionPatch = sharedExtensionPatch(
+ "music",
applicationInitHook,
)
diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt
index e38ca6015c..d2441951e5 100644
--- a/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/GmsCoreSupportPatch.kt
@@ -4,6 +4,7 @@ import app.revanced.patcher.patch.Option
import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.gms.Constants.MUSIC_PACKAGE_NAME
import app.revanced.patches.music.misc.gms.Constants.REVANCED_MUSIC_PACKAGE_NAME
+import app.revanced.patches.music.shared.musicActivityOnCreateFingerprint
import app.revanced.patches.shared.castContextFetchFingerprint
import app.revanced.patches.shared.misc.gms.gmsCoreSupportPatch
import app.revanced.patches.shared.primeMethodFingerprint
diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/shared/Fingerprints.kt
similarity index 86%
rename from patches/src/main/kotlin/app/revanced/patches/music/misc/gms/Fingerprints.kt
rename to patches/src/main/kotlin/app/revanced/patches/music/shared/Fingerprints.kt
index 7131e143df..9321a3551a 100644
--- a/patches/src/main/kotlin/app/revanced/patches/music/misc/gms/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/music/shared/Fingerprints.kt
@@ -1,4 +1,4 @@
-package app.revanced.patches.music.misc.gms
+package app.revanced.patches.music.shared
import app.revanced.patcher.fingerprint
diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/announcements/AnnouncementsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/announcements/AnnouncementsPatch.kt
index f318d838a1..23e378ea56 100644
--- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/announcements/AnnouncementsPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/announcements/AnnouncementsPatch.kt
@@ -1,51 +1,27 @@
package app.revanced.patches.youtube.misc.announcements
-import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
-import app.revanced.patcher.patch.bytecodePatch
+import app.revanced.patches.all.misc.announcements.announcementsPatch
import app.revanced.patches.all.misc.resources.addResources
-import app.revanced.patches.all.misc.resources.addResourcesPatch
+import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
-private const val EXTENSION_CLASS_DESCRIPTOR =
- "Lapp/revanced/extension/youtube/patches/announcements/AnnouncementsPatch;"
+val announcementsPatch = announcementsPatch(
+ mainActivityOnCreateFingerprint,
+ sharedExtensionPatch,
+ "Lapp/revanced/extension/youtube/patches/announcements/AnnouncementsPatch;",
+ {
+ dependsOn(settingsPatch)
-val announcementsPatch = bytecodePatch(
- name = "Announcements",
- description = "Adds an option to show announcements from ReVanced on app startup.",
-) {
- dependsOn(
- settingsPatch,
- addResourcesPatch,
- )
-
- compatibleWith(
- "com.google.android.youtube"(
- "18.38.44",
- "18.49.37",
- "19.16.39",
- "19.25.37",
- "19.34.42",
- "19.43.41",
- "19.45.38",
- "19.46.42",
- ),
- )
-
- execute {
+ compatibleWith("com.google.android.youtube")
+ },
+ {
addResources("youtube", "misc.announcements.announcementsPatch")
PreferenceScreen.MISC.addPreferences(
SwitchPreference("revanced_announcements"),
)
-
- mainActivityOnCreateFingerprint.method.addInstructions(
- // Insert index must be greater than the insert index used by GmsCoreSupport,
- // as both patch the same method and GmsCore check should be first.
- 1,
- "invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->showAnnouncement(Landroid/app/Activity;)V",
- )
- }
-}
+ },
+)
diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml
index 51b21c2a11..fd6d9714fe 100644
--- a/patches/src/main/resources/addresources/values/strings.xml
+++ b/patches/src/main/resources/addresources/values/strings.xml
@@ -31,13 +31,23 @@ This is because Crowdin requires temporarily flattening this file and removing t
-->
+
+ Failed connecting to announcements provider
+ Dismiss
+
Checks failed
Open official website
Ignore
- <h5>This app does not appear to be patched by you.</h5><br>This app may not function correctly, <b>could be harmful or even dangerous to use</b>.<br><br>These checks imply this app is pre-patched or obtained from someone else:<br><br><small>%1$s</small><br>It is strongly recommended to <b>uninstall this app and patch it yourself</b> to ensure you are using a validated and secure app.<p><br>If ignored, this warning will only be shown twice.
+ <h5>This app does not appear to be patched by
+ you.</h5><br>This app may not function correctly, <b>could be harmful or even dangerous to use</b>.<br><br>These
+ checks imply this app is pre-patched or obtained from someone else:<br><br><small>%1$s</small><br>It
+ is strongly recommended to <b>uninstall this app and patch it yourself</b> to ensure you are using
+ a validated and secure app.<p><br>If ignored, this warning will only be shown twice.
+
Patched on a different device
- Not installed by ReVanced Manager
+ Not installed by ReVanced Manager
+
Patched more than 10 minutes ago
Patched %s days ago
APK build date is corrupted
@@ -56,9 +66,12 @@ This is because Crowdin requires temporarily flattening this file and removing t
Import / Export
Import / Export ReVanced settings
- You are using ReVanced Patches version <i>%s</i>
+ You are using ReVanced Patches version <i>%s</i>
+
Note
- This version is a pre-release and you may experience unexpected issues
+ This version is a pre-release and you may experience
+ unexpected issues
+
Official links
@@ -67,9 +80,15 @@ This is because Crowdin requires temporarily flattening this file and removing t
MicroG GmsCore is not installed. Install it.
Action needed
- MicroG GmsCore does not have permission to run in the background.\n\nFollow the \"Don\'t kill my app\" guide for your phone, and apply the instructions to your MicroG installation.\n\nThis is required for the app to work.
+ MicroG GmsCore does not
+ have permission to run in the background.\n\nFollow the \"Don\'t kill my app\" guide for your phone, and
+ apply the instructions to your MicroG installation.\n\nThis is required for the app to work.
+
Open website
- MicroG GmsCore battery optimizations must be disabled to prevent issues.\n\nTap on the continue button and disable battery optimizations.
+ MicroG GmsCore battery
+ optimizations must be disabled to prevent issues.\n\nTap on the continue button and disable battery
+ optimizations.
+
Continue
@@ -89,8 +108,10 @@ This is because Crowdin requires temporarily flattening this file and removing t
Disable Shorts background play
- Shorts background play is disabled
- Shorts background play is enabled
+ Shorts background play is disabled
+
+ Shorts background play is enabled
+
Debugging
@@ -107,12 +128,18 @@ This is because Crowdin requires temporarily flattening this file and removing t
Show toast on ReVanced error
Toast shown if error occurs
Toast not shown if error occurs
- Turning off error toasts hides all ReVanced error notifications.\n\nYou will not be notified of any unexpected events.
+ Turning off error toasts hides all ReVanced
+ error notifications.\n\nYou will not be notified of any unexpected events.
+
Disable like / subscribe button glow
- Like and subscribe button will not glow when mentioned
- Like and subscribe button will glow when mentioned
+ Like and subscribe button will not glow when
+ mentioned
+
+ Like and subscribe button will glow when
+ mentioned
+
Hide album cards
Album cards are hidden
Album cards are shown
@@ -126,7 +153,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
Watermark is hidden
Watermark is shown
Hide horizontal shelves
- Shelves are hidden such as:\n• Breaking news\n• Continue watching\n• Explore more channels\n• Shopping\n• Watch it again
+ Shelves are hidden such as:\n• Breaking news\n•
+ Continue watching\n• Explore more channels\n• Shopping\n• Watch it again
+
Shelves are shown
@@ -143,7 +172,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
Button is hidden
Button is shown
- Hide \'People also watched\' recommendations
+ Hide \'People also watched\'
+ recommendations
+
Recommendations are hidden
Recommendations are shown
Hide YouTube Doodles
Search bar Doodles are hidden
Search bar Doodles are shown
- YouTube Doodles show up a few days each year.\n\nIf a Doodle is currently showing in your region and this hide setting is on, then the filter bar below the search bar will also be hidden.
+ YouTube Doodles show up a few days each year.\n\nIf
+ a Doodle is currently showing in your region and this hide setting is on, then the filter bar below the
+ search bar will also be hidden.
+
Custom filter
Hide components using custom filters
@@ -287,34 +345,64 @@ This is because Crowdin requires temporarily flattening this file and removing t
Custom filter is disabled
Custom filter
- List of component path builder strings to filter separated by new line
+ List of component path builder strings to filter
+ separated by new line
+
Invalid custom filter: %s
Hide keyword content
- Hide search and feed videos using keyword filters
+ Hide search and feed videos using keyword
+ filters
+
Hide home videos by keywords
- Videos in the home tab are filtered by keywords
- Videos in the home tab are not filtered by keywords
- Hide subscription videos by keywords
- Videos in the subscriptions tab are filtered by keywords
- Videos in the subscriptions tab are not filtered by keywords
+ Videos in the home tab are filtered by
+ keywords
+
+ Videos in the home tab are not filtered by
+ keywords
+
+ Hide subscription videos by keywords
+
+ Videos in the subscriptions tab are
+ filtered by keywords
+
+ Videos in the subscriptions tab are
+ not filtered by keywords
+
Hide search results by keywords
- Search results are filtered by keywords
- Search results are not filtered by keywords
+ Search results are filtered by keywords
+
+ Search results are not filtered by
+ keywords
+
Keywords to hide
- Keywords and phrases to hide, separated by new lines\n\nKeywords can be channel names or any text shown in video titles\n\nWords with uppercase letters in the middle must be entered with the casing (ie: iPhone, TikTok, LeBlanc)
+ Keywords and phrases to hide, separated by new
+ lines\n\nKeywords can be channel names or any text shown in video titles\n\nWords with uppercase letters
+ in the middle must be entered with the casing (ie: iPhone, TikTok, LeBlanc)
+
About keyword filtering
- Home/Subscription/Search results are filtered to hide content that matches keyword phrases\n\nLimitations\n• Shorts cannot be hidden by channel name\n• Some UI components may not be hidden\n• Searching for a keyword may show no results
+ Home/Subscription/Search results are filtered to
+ hide content that matches keyword phrases\n\nLimitations\n• Shorts cannot be hidden by channel name\n•
+ Some UI components may not be hidden\n• Searching for a keyword may show no results
+
Match whole words
- Surrounding a keyword/phrase with double-quotes will prevent partial matches of video titles and channel names<br><br>For example,<br><b>\"ai\"</b> will hide the video: <b>How does AI work?</b><br>but will not hide: <b>What does fair use mean?</b>
+ Surrounding a keyword/phrase with
+ double-quotes will prevent partial matches of video titles and channel names<br><br>For example,<br><b>\"ai\"</b>
+ will hide the video: <b>How does AI work?</b><br>but will not hide: <b>What does fair use
+ mean?</b>
+
Cannot use keyword: %s
- Add quotes to use keyword: %s
- Keyword has conflicting declarations: %s
- Keyword is too short and requires quotes: %s
+ Add quotes to use keyword:
+ %s
+
+ Keyword has conflicting declarations: %s
+
+ Keyword is too short and requires quotes: %s
+
Keyword will hide all videos: %s
@@ -322,7 +410,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
General ads are hidden
General ads are shown
Hide fullscreen ads
- Fullscreen ads are hidden\n\nThis feature is only available for older devices
+ Fullscreen ads are hidden\n\nThis feature is only
+ available for older devices
+
Fullscreen ads are shown
Hide buttoned ads
Buttoned ads are hidden
@@ -343,7 +433,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
Shopping links are hidden
Shopping links are shown
- Hide the \'Visit store\' button on channel pages
+ Hide the \'Visit store\' button on channel pages
+
Button is hidden
Button is shown
Hide web search results
@@ -353,12 +444,18 @@ This is because Crowdin requires temporarily flattening this file and removing t
Merchandise banners are hidden
Merchandise banners are shown
- Hide fullscreen ads only works with older devices
+ Hide fullscreen ads only works with
+ older devices
+
Hide YouTube Premium promotions
- YouTube Premium promotions under video player are hidden
- YouTube Premium promotions under video player are shown
+ YouTube Premium promotions under video player are
+ hidden
+
+ YouTube Premium promotions under video player are
+ shown
+
Hide video ads
@@ -369,31 +466,45 @@ This is because Crowdin requires temporarily flattening this file and removing t
URL copied to clipboard
URL with timestamp copied
Show copy video URL button
- Button is shown. Tap to copy video URL. Tap and hold to copy video URL with timestamp
+ Button is shown. Tap to copy video URL. Tap and hold to
+ copy video URL with timestamp
+
Button is not shown
Show copy timestamp URL button
- Button is shown. Tap to copy video URL with timestamp. Tap and hold to copy video without timestamp
+ Button is shown. Tap to copy video URL with
+ timestamp. Tap and hold to copy video without timestamp
+
Button is not shown
Remove viewer discretion dialog
Dialog will be removed
Dialog will be shown
- This does not bypass the age restriction. It just accepts it automatically.
+ This does not bypass the age
+ restriction. It just accepts it automatically.
+
External downloads
- Settings for using an external downloader
+ Settings for using an external downloader
+
Show external download button
Download button shown in player
Download button not shown in player
Override download action button
- Download button opens your external downloader
- Download button opens the native in-app downloader
+ Download button opens your external
+ downloader
+
+ Download button opens the native
+ in-app downloader
+
Downloader package name
- Package name of your installed external downloader app, such as NewPipe or Seal
- %s is not installed. Please install it.
+ Package name of your installed external downloader
+ app, such as NewPipe or Seal
+
+ %s is not installed. Please install it.
+
Disable precise seeking gesture
@@ -419,18 +530,29 @@ This is because Crowdin requires temporarily flattening this file and removing t
Haptic feedback is enabled
Haptic feedback is disabled
Save and restore brightness
- Save and restore brightness when exiting or entering fullscreen
- Do not save and restore brightness when exiting or entering fullscreen
- Enable auto-brightness gesture
- Swiping down to the lowest value of the brightness gesture enable auto-brightness
- Swiping down to the lowest value does not enable auto-brightness
+ Save and restore brightness when
+ exiting or entering fullscreen
+
+ Do not save and restore brightness
+ when exiting or entering fullscreen
+
+ Enable auto-brightness gesture
+
+ Swiping down to the lowest
+ value of the brightness gesture enable auto-brightness
+
+ Swiping down to the lowest
+ value does not enable auto-brightness
+
Auto
Swipe overlay timeout
- The amount of milliseconds the overlay is visible
+ The amount of milliseconds the overlay is visible
+
Swipe overlay text size
The text size for swipe overlay
Swipe background visibility
- The visibility of swipe overlay background
+ The visibility of swipe overlay background
+
Swipe magnitude threshold
The amount of threshold for swipe to occur
@@ -477,7 +599,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
Navigation buttons
- Hide or change buttons in the navigation bar
+ Hide or change buttons in the navigation bar
+
Hide Home
Home button is hidden
@@ -495,9 +618,14 @@ This is because Crowdin requires temporarily flattening this file and removing t
Subscriptions button is hidden
Subscriptions button is shown
- Switch Create with Notifications
- Create button is switched with Notifications button\n\nNote: Enabling this also forcibly hides video ads
- Create button is not switched with Notifications button
+ Switch Create with Notifications
+
+ Create button is switched with
+ Notifications button\n\nNote: Enabling this also forcibly hides video ads
+
+ Create button is not switched
+ with Notifications button
+
Hide navigation button labels
Labels are hidden
Labels are shown
@@ -511,8 +639,12 @@ This is because Crowdin requires temporarily flattening this file and removing t
Captions menu is shown
Hide Additional settings
- Additional settings menu is hidden
- Additional settings menu is shown
+ Additional settings menu is
+ hidden
+
+ Additional settings menu is
+ shown
+
Hide Sleep timer
Sleep timer menu is hidden
@@ -553,12 +685,18 @@ This is because Crowdin requires temporarily flattening this file and removing t
Hide Watch in VR
Watch in VR menu is hidden
Watch in VR menu is shown
- Hide video quality menu footer
- Video quality menu footer is hidden
- Video quality menu footer is shown
+ Hide video quality menu footer
+
+ Video quality menu footer is
+ hidden
+
+ Video quality menu footer is
+ shown
+
- Hide previous & next video buttons
+ Hide previous & next video buttons
+
Buttons are hidden
Buttons are shown
Hide cast button
@@ -589,7 +727,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
Disable rolling number animations
- Rolling numbers are not animated
+ Rolling numbers are not animated
+
Rolling numbers are animated
@@ -623,8 +762,10 @@ This is because Crowdin requires temporarily flattening this file and removing t
Subscribe button is hidden
Subscribe button is shown
Hide paused overlay buttons
- Paused overlay buttons are hidden
- Paused overlay buttons are shown
+ Paused overlay buttons are hidden
+
+ Paused overlay buttons are shown
+
Hide shop button
Shop button is hidden
Shop button is shown
@@ -659,8 +800,10 @@ This is because Crowdin requires temporarily flattening this file and removing t
Stickers are hidden
Stickers are shown
Hide like fountain
- Like button fountain animation is hidden
- Like button fountain animation is shown
+ Like button fountain animation is hidden
+
+ Like button fountain animation is shown
+
Hide like button
Like button is hidden
Like button is shown
@@ -702,8 +845,10 @@ This is because Crowdin requires temporarily flattening this file and removing t
Disable suggested video end screen
- Suggested videos will be disabled
- Suggested videos will be shown
+ Suggested videos will be disabled
+
+ Suggested videos will be shown
+
Hide video timestamp
@@ -717,24 +862,33 @@ This is because Crowdin requires temporarily flattening this file and removing t
Player overlay opacity
- Opacity value between 0-100, where 0 is transparent
- Player overlay opacity must be between 0-100
+ Opacity value between 0-100, where 0 is transparent
+
+ Player overlay opacity must be between 0-100
+
Return YouTube Dislike
- Dislikes temporarily not available (API timed out)
+ Dislikes temporarily not available (API timed out)
+
Dislikes not available (status %d)
- Dislikes not available (client API limit reached)
+ Dislikes not available (client API limit
+ reached)
+
Dislikes not available (%s)
- Reload video to vote using Return YouTube Dislike
+ Reload video to vote
+ using Return YouTube Dislike
+
Return YouTube Dislike
Dislikes are shown
Dislikes are not shown
Show dislikes on Shorts
Dislikes shown on Shorts
- Dislikes shown on Shorts\n\nLimitation: Dislikes may not appear in incognito mode
+ Dislikes shown on Shorts\n\nLimitation: Dislikes
+ may not appear in incognito mode
+
Dislikes hidden on Shorts
Dislikes as percentage
Dislikes shown as percentage
@@ -744,27 +898,50 @@ This is because Crowdin requires temporarily flattening this file and removing t
Like button styled for minimum width
Like button styled for best appearance
Show a toast if API is not available
- Toast is shown if Return YouTube Dislike is not available
- Toast is not shown if Return YouTube Dislike is not available
+ Toast is shown if Return YouTube Dislike is
+ not available
+
+ Toast is not shown if Return YouTube
+ Dislike is not available
+
About
ReturnYouTubeDislike.com
- Data is provided by the Return YouTube Dislike API. Tap here to learn more
+ Data is provided by the Return YouTube Dislike API. Tap here
+ to learn more
+
- ReturnYouTubeDislike API statistics of this device
- API response time, average
+ ReturnYouTubeDislike API statistics of this device
+
+ API response time, average
+
API response time, minimum
API response time, maximum
- API response time, last video
- Dislikes temporarily not available - Client API rate limit in effect
+ API response time, last video
+
+ Dislikes temporarily
+ not available - Client API rate limit in effect
+
API fetch votes, number of calls
No network calls made
%d network calls made
- API fetch votes, number of timeouts
- No network calls timed out
- %d network calls timed out
- API client rate limits
- No client rate limits encountered
- Client rate limit encountered %d times
+ API fetch votes, number of
+ timeouts
+
+ No network calls timed
+ out
+
+ %d network calls timed
+ out
+
+ API client rate
+ limits
+
+ No client rate
+ limits encountered
+
+ Client rate
+ limit encountered %d times
+
%d milliseconds
@@ -774,19 +951,35 @@ This is because Crowdin requires temporarily flattening this file and removing t
Enable high quality thumbnails
- Seekbar thumbnails are high quality
- Seekbar thumbnails are medium quality
- Fullscreen seekbar thumbnails are high quality
- Fullscreen seekbar thumbnails are medium quality
- This will also restore thumbnails on livestreams that do not have seekbar thumbnails.\n\nSeekbar thumbnails will use the same quality as the current video.\n\nThis feature works best with a video quality of 720p or lower and when using a very fast internet connection.
+ Seekbar thumbnails are high quality
+
+ Seekbar thumbnails are medium quality
+
+ Fullscreen seekbar thumbnails are
+ high quality
+
+ Fullscreen seekbar thumbnails are
+ medium quality
+
+ This will also restore thumbnails on
+ livestreams that do not have seekbar thumbnails.\n\nSeekbar thumbnails will use the same quality as the
+ current video.\n\nThis feature works best with a video quality of 720p or lower and when using a very
+ fast internet connection.
+
Restore old seekbar thumbnails
- Seekbar thumbnails will appear above the seekbar
- Seekbar thumbnails will appear in fullscreen
+ Seekbar thumbnails will appear above the
+ seekbar
+
+ Seekbar thumbnails will appear in
+ fullscreen
+
Spoof app version
Version spoofed
Version not spoofed
- App version will be spoofed to an older version of YouTube.\n\nThis will change the appearance and features of the app, but unknown side effects may occur.\n\nIf later turned off, it is recommended to clear the app data to prevent UI bugs.
+ App version will be spoofed to an older
+ version of YouTube.\n\nThis will change the appearance and features of the app, but unknown side effects
+ may occur.\n\nIf later turned off, it is recommended to clear the app data to prevent UI bugs.
+
Spoof app version target
19.35.36 - Restore old Shorts player icons
- 18.33.40 - Restore RYD on Shorts incognito mode
- 18.20.39 - Restore wide video speed & quality menu
+ 18.33.40 - Restore RYD on Shorts incognito
+ mode
+
+ 18.20.39 - Restore wide video speed &
+ quality menu
+
18.09.39 - Restore library tab
- 17.33.42 - Restore old playlist shelf
+ 17.33.42 - Restore old playlist shelf
+
Set start page
@@ -1001,8 +1279,12 @@ This is because Crowdin requires temporarily flattening this file and removing t
Disable resuming Shorts player
- Shorts player will not resume on app startup
- Shorts player will resume on app startup
+ Shorts player will not resume on app
+ startup
+
+ Shorts player will resume on app
+ startup
+
Autoplay Shorts
@@ -1016,8 +1298,10 @@ This is because Crowdin requires temporarily flattening this file and removing t
Enable tablet layout
Tablet layout is enabled
Tablet layout is disabled
- Community posts do not show up on tablet layouts
- x
+ Community posts do not show up on tablet layouts
+
+
+ x
Miniplayer
Change the style of the in app minimized player
@@ -1033,20 +1317,31 @@ This is because Crowdin requires temporarily flattening this file and removing t
Corners are rounded
Corners are square
Enable double-tap and pinch to resize
- Double-tap action and pinch to resize is enabled\n\n• Double tap to increase miniplayer size\n• Double tap again to restore original size
- Double-tap action and pinch to resize is disabled
+ Double-tap action and pinch to resize is
+ enabled\n\n• Double tap to increase miniplayer size\n• Double tap again to restore original size
+
+ Double-tap action and pinch to resize is
+ disabled
+
Enable drag and drop
- Drag and drop is enabled\n\nMiniplayer can be dragged to any corner of the screen
+ Drag and drop is enabled\n\nMiniplayer can be
+ dragged to any corner of the screen
+
Drag and drop is disabled
Enable horizontal drag gesture
- Horizontal drag gesture enabled\n\nMiniplayer can be dragged off screen to the left or right
+ Horizontal drag gesture enabled\n\nMiniplayer
+ can be dragged off screen to the left or right
+
Horizontal drag gesture disabled
Hide close button
Close button is hidden
Close button is shown
Hide expand and close buttons
- Buttons are hidden\n\nSwipe to expand or close
- Expand and close buttons are shown
+ Buttons are hidden\n\nSwipe to expand
+ or close
+
+ Expand and close buttons are shown
+
Hide subtexts
Subtexts are hidden
Subtexts are shown
@@ -1057,13 +1352,17 @@ This is because Crowdin requires temporarily flattening this file and removing t
Initial on screen size, in pixels
Pixel size must be between %1$s and %2$s
Overlay opacity
- Opacity value between 0-100, where 0 is transparent
- Miniplayer overlay opacity must be between 0-100
+ Opacity value between 0-100, where 0 is transparent
+
+ Miniplayer overlay opacity must be between 0-100
+
Enable gradient loading screen
- Loading screen will have a gradient background
- Loading screen will have a solid background
+ Loading screen will have a gradient background
+
+ Loading screen will have a solid background
+
Enable custom seekbar color
@@ -1076,7 +1375,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
Bypass image region restrictions
Using image host yt4.ggpht.com
- Using original image host\n\nEnabling this can fix missing images that are blocked in some regions
+ Using original image host\n\nEnabling
+ this can fix missing images that are blocked in some regions
+
@@ -1092,23 +1393,39 @@ This is because Crowdin requires temporarily flattening this file and removing t
DeArrow & Still captures
Still captures
DeArrow
- DeArrow provides crowd-sourced thumbnails for YouTube videos. These thumbnails are often more relevant than those provided by YouTube\n\nIf enabled, video URLs will be sent to the API server and no other data is sent. If a video does not have DeArrow thumbnails, then the original or still captures are shown\n\nTap here to learn more about DeArrow
- Show a toast if API is not available
- Toast is shown if DeArrow is not available
- Toast is not shown if DeArrow is not available
+ DeArrow provides crowd-sourced thumbnails for
+ YouTube videos. These thumbnails are often more relevant than those provided by YouTube\n\nIf enabled,
+ video URLs will be sent to the API server and no other data is sent. If a video does not have DeArrow
+ thumbnails, then the original or still captures are shown\n\nTap here to learn more about DeArrow
+
+ Show a toast if API is not available
+
+ Toast is shown if DeArrow is not
+ available
+
+ Toast is not shown if DeArrow is
+ not available
+
DeArrow API endpoint
- The URL of the DeArrow thumbnail cache endpoint
+ The URL of the DeArrow thumbnail cache
+ endpoint
+
Still video captures
- Still captures are taken from the beginning/middle/end of each video. These images are built into YouTube and no external API is used
+ Still captures are taken from the
+ beginning/middle/end of each video. These images are built into YouTube and no external API is used
+
Use fast still captures
- Using medium quality still captures. Thumbnails will load faster, but live streams, unreleased, or very old videos may show blank thumbnails
+ Using medium quality still captures. Thumbnails
+ will load faster, but live streams, unreleased, or very old videos may show blank thumbnails
+
Using high quality still captures
Video time to take still captures from
Beginning of video
Middle of video
End of video
- DeArrow temporarily not available (status code: %s)
+ DeArrow temporarily not available (status code: %s)
+
DeArrow temporarily not available
@@ -1116,12 +1433,13 @@ This is because Crowdin requires temporarily flattening this file and removing t
Announcements are shown on startup
Announcements are not shown on startup
Show announcements on startup
- Failed connecting to announcements provider
- Dismiss
Warning
- Your watch history is not being saved.<br><br>This most likely is caused by a DNS ad blocker or network proxy.<br><br>To fix this, whitelist <b>s.youtube.com</b> or turn off all DNS blockers and proxies.
+ Your watch history is not being
+ saved.<br><br>This most likely is caused by a DNS ad blocker or network proxy.<br><br>To fix
+ this, whitelist <b>s.youtube.com</b> or turn off all DNS blockers and proxies.
+
Do not show again
@@ -1131,9 +1449,16 @@ This is because Crowdin requires temporarily flattening this file and removing t
Spoof device dimensions
- Device dimensions spoofed\n\nHigher video qualities might be unlocked but you may experience video playback stuttering, worse battery life, and unknown side effects
- Device dimensions not spoofed\n\nEnabling this can unlock higher video qualities
- Enabling this can cause video playback stuttering, worse battery life, and unknown side effects.
+ Device dimensions spoofed\n\nHigher video
+ qualities might be unlocked but you may experience video playback stuttering, worse battery life, and
+ unknown side effects
+
+ Device dimensions not spoofed\n\nEnabling this
+ can unlock higher video qualities
+
+ Enabling this can cause video playback
+ stuttering, worse battery life, and unknown side effects.
+
GmsCore Settings
@@ -1151,8 +1476,12 @@ This is because Crowdin requires temporarily flattening this file and removing t
Remove tracking query parameter
- Tracking query parameter is removed from links
- Tracking query parameter is not removed from links
+ Tracking query parameter is removed from
+ links
+
+ Tracking query parameter is not removed
+ from links
+
Disable zoom haptics
@@ -1171,8 +1500,12 @@ This is because Crowdin requires temporarily flattening this file and removing t
240p
144p
Remember video quality changes
- Quality changes apply to all videos
- Quality changes only apply to the current video
+ Quality changes apply to all
+ videos
+
+ Quality changes only apply to the
+ current video
+
Default video quality on Wi-Fi network
Default video quality on mobile network
mobile
@@ -1190,21 +1523,30 @@ This is because Crowdin requires temporarily flattening this file and removing t
Custom speed menu is not shown
Custom playback speeds
Add or change the custom playback speeds
- Custom speeds must be less than %s. Using default values.
- Invalid custom playback speeds. Using default values.
+ Custom speeds must be less than %s. Using default
+ values.
+
+ Invalid custom playback speeds. Using default
+ values.
+
Auto
Remember playback speed changes
- Playback speed changes apply to all videos
- Playback speed changes only apply to the current video
+ Playback speed changes apply to all
+ videos
+
+ Playback speed changes only apply
+ to the current video
+
Default playback speed
Changed default speed to: %s
Enable slide to seek
@@ -1213,21 +1555,36 @@ This is because Crowdin requires temporarily flattening this file and removing t
Spoof video streams
- Spoof the client video streams to prevent playback issues
+ Spoof the client video streams to prevent
+ playback issues
+
Spoof video streams
Video streams are spoofed
- Video streams are not spoofed\n\nVideo playback may not work
- Turning off this setting may cause video playback issues.
+ Video streams are not spoofed\n\nVideo playback may
+ not work
+
+ Turning off this setting may cause video
+ playback issues.
+
Default client
Force AVC (H.264)
Video codec is AVC (H.264)
Video codec is VP9 or AV1
- Your device does not have VP9 hardware decoding, and this setting is always on when Client spoofing is enabled
- Enabling this might improve battery life and fix playback stuttering.\n\nAVC has a maximum resolution of 1080p, and video playback will use more internet data than VP9 or AV1.
+ Your device does not
+ have VP9 hardware decoding, and this setting is always on when Client spoofing is enabled
+
+ Enabling this might improve
+ battery life and fix playback stuttering.\n\nAVC has a maximum resolution of 1080p, and video playback
+ will use more internet data than VP9 or AV1.
+
iOS spoofing side effects
- • Private kids videos may not play\n• Livestreams start from the beginning\n• Videos may end 1 second early
+ • Private kids videos may not play\n•
+ Livestreams start from the beginning\n• Videos may end 1 second early
+
Android VR spoofing side effects
- • Kids videos may not play\n• Audio track menu is missing\n• Stable volume is not available
+ • Kids videos may not play\n• Audio
+ track menu is missing\n• Stable volume is not available
+
@@ -1237,8 +1594,12 @@ This is because Crowdin requires temporarily flattening this file and removing t
Audio ads are unblocked
- %s is unavailable. Ads may show. Try switching to another ad block service in settings.
- %s server returned an error. Ads may show. Try switching to another ad block service in settings.
+ %s is unavailable. Ads may show. Try switching to
+ another ad block service in settings.
+
+ %s server returned an error. Ads may show. Try switching
+ to another ad block service in settings.
+
Block embedded video ads
Disabled
Luminous proxy
@@ -1258,8 +1619,10 @@ This is because Crowdin requires temporarily flattening this file and removing t
Automatically claim Channel Points
- Channel Points are claimed automatically
- Channel Points are not claimed automatically
+ Channel Points are claimed automatically
+
+ Channel Points are not claimed automatically
+