chore: Merge branch dev to main (#3980)

This commit is contained in:
oSumAtrIX 2024-11-27 14:54:10 +01:00 committed by GitHub
commit 787860605d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
160 changed files with 1726 additions and 1026 deletions

View File

@ -1,3 +1,71 @@
# [5.2.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.2.0-dev.6...v5.2.0-dev.7) (2024-11-27)
### Bug Fixes
* **YouTube - Settings:** Do not clip settings menus when using an Android 15 device ([#3999](https://github.com/ReVanced/revanced-patches/issues/3999)) ([7382a02](https://github.com/ReVanced/revanced-patches/commit/7382a020b8322a7abc016a4569bc15f9caf05546))
# [5.2.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.2.0-dev.5...v5.2.0-dev.6) (2024-11-27)
### Features
* **YouTube:** Support version `19.46.42` ([#4010](https://github.com/ReVanced/revanced-patches/issues/4010)) ([122aac6](https://github.com/ReVanced/revanced-patches/commit/122aac6aee8ef0737f18564f11bbc2a6addf4a6b))
# [5.2.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.2.0-dev.4...v5.2.0-dev.5) (2024-11-27)
### Bug Fixes
* **YouTube - Spoof video streams:** Log out the iOS client to restore kids videos playback ([#4000](https://github.com/ReVanced/revanced-patches/issues/4000)) ([cc2ac4e](https://github.com/ReVanced/revanced-patches/commit/cc2ac4e4cd15ca2a23d60abd160d915bc98f99b4))
### Features
* **TikTok:** Add ReVanced settings about screen ([#4009](https://github.com/ReVanced/revanced-patches/issues/4009)) ([12ea26b](https://github.com/ReVanced/revanced-patches/commit/12ea26b10ddea5ad39da1d35e2b8fd0b48c15d88))
# [5.2.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.2.0-dev.3...v5.2.0-dev.4) (2024-11-26)
### Bug Fixes
* **YouTube - Playback speed:** Allow long press 2x speed when using custom playback speeds ([#3990](https://github.com/ReVanced/revanced-patches/issues/3990)) ([79a543a](https://github.com/ReVanced/revanced-patches/commit/79a543a57470638f983862c61270e046f3ac5cb7))
# [5.2.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.2.0-dev.2...v5.2.0-dev.3) (2024-11-26)
### Features
* **VSCO:** Remove non functional `Unlock pro` patch ([4fddb19](https://github.com/ReVanced/revanced-patches/commit/4fddb1930bc7adeee3b60ae9cd346b143e88bd42))
# [5.2.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.2.0-dev.1...v5.2.0-dev.2) (2024-11-26)
### Bug Fixes
* **YouTube - Settings:** Show navigation back button in setting sub menus ([#3991](https://github.com/ReVanced/revanced-patches/issues/3991)) ([e61686c](https://github.com/ReVanced/revanced-patches/commit/e61686c1039ae29e443273e4da4ec63956216841))
# [5.2.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.1.1-dev.2...v5.2.0-dev.1) (2024-11-25)
### Features
* **YouTube - Theme:** Apply custom seekbar color to splash screen animation ([#3978](https://github.com/ReVanced/revanced-patches/issues/3978)) ([98d57e2](https://github.com/ReVanced/revanced-patches/commit/98d57e28af7206099867474b7aa3760cd4fe333f))
## [5.1.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.1.1-dev.1...v5.1.1-dev.2) (2024-11-25)
### Bug Fixes
* **YouTube - Hide Shorts components:** Add missing options to patch ([65f62fc](https://github.com/ReVanced/revanced-patches/commit/65f62fcd5ac340616a96542c64faf2af2a60df28))
## [5.1.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.1.0...v5.1.1-dev.1) (2024-11-24)
### Bug Fixes
* **My Expenses - Unlock pro:** Constrain compatible version to working version ([#3974](https://github.com/ReVanced/revanced-patches/issues/3974)) ([ba3bf69](https://github.com/ReVanced/revanced-patches/commit/ba3bf69df07ec8dab46868c3940ebd56db0cd137))
# [5.1.0](https://github.com/ReVanced/revanced-patches/compare/v5.0.2...v5.1.0) (2024-11-24) # [5.1.0](https://github.com/ReVanced/revanced-patches/compare/v5.0.2...v5.1.0) (2024-11-24)

View File

@ -1,21 +1,35 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Insets;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.*; import android.preference.*;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.widget.TextView;
import android.widget.Toolbar;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.Objects;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.youtube.ThemeHelper;
import static app.revanced.extension.shared.StringRef.str;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public abstract class AbstractPreferenceFragment extends PreferenceFragment { public abstract class AbstractPreferenceFragment extends PreferenceFragment {
@ -71,6 +85,15 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
} }
}; };
@SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getBackButtonDrawable() {
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme()
? "yt_outline_arrow_left_white_24"
: "yt_outline_arrow_left_black_24",
"drawable");
return Utils.getContext().getResources().getDrawable(backButtonResource);
}
/** /**
* Initialize this instance, and do any custom behavior. * Initialize this instance, and do any custom behavior.
* <p> * <p>
@ -98,7 +121,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
showingUserDialogMessage = true; showingUserDialogMessage = true;
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setTitle(confirmDialogTitle) .setTitle(confirmDialogTitle)
.setMessage(setting.userDialogMessage.toString()) .setMessage(Objects.requireNonNull(setting.userDialogMessage).toString())
.setPositiveButton(android.R.string.ok, (dialog, id) -> { .setPositiveButton(android.R.string.ok, (dialog, id) -> {
if (setting.rebootApp) { if (setting.rebootApp) {
showRestartDialog(context); showRestartDialog(context);
@ -261,6 +284,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
// causes a callback to the listener even though nothing changed. // causes a callback to the listener even though nothing changed.
initialize(); initialize();
updateUIToSettingValues(); updateUIToSettingValues();
setPreferenceScreenToolbar(getPreferenceScreen());
preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(listener); preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
} catch (Exception ex) { } catch (Exception ex) {
@ -273,4 +297,56 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener); getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener);
super.onDestroy(); super.onDestroy();
} }
private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) {
for (int i = 0, preferenceCount = parentScreen.getPreferenceCount(); i < preferenceCount; i++) {
Preference childPreference = parentScreen.getPreference(i);
if (childPreference instanceof PreferenceScreen) {
// Recursively set sub preferences.
setPreferenceScreenToolbar((PreferenceScreen) childPreference);
childPreference.setOnPreferenceClickListener(
childScreen -> {
Dialog preferenceScreenDialog = ((PreferenceScreen) childScreen).getDialog();
ViewGroup rootView = (ViewGroup) preferenceScreenDialog
.findViewById(android.R.id.content)
.getParent();
// Fix required for Android 15 and YT 19.45+
// FIXME:
// On Android 15 the text layout is not aligned the same as the parent
// screen and it looks a little off. Otherwise this works.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
rootView.setOnApplyWindowInsetsListener((v, insets) -> {
Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars());
v.setPadding(0, statusInsets.top, 0, 0);
return insets;
});
}
Toolbar toolbar = new Toolbar(childScreen.getContext());
toolbar.setTitle(childScreen.getTitle());
toolbar.setNavigationIcon(getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
final int margin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()
);
toolbar.setTitleMargin(margin, 0, margin, 0);
}
TextView toolbarTextView = Utils.getChildView(toolbar,
true, TextView.class::isInstance);
if (toolbarTextView != null) {
toolbarTextView.setTextColor(ThemeHelper.getForegroundColor());
}
rootView.addView(toolbar, 0);
return false;
}
);
}
}
}
} }

View File

@ -1,6 +1,5 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.sf;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.youtube.requests.Route.Method.GET; import static app.revanced.extension.youtube.requests.Route.Method.GET;
@ -13,6 +12,8 @@ import android.content.res.Configuration;
import android.graphics.Color; import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.preference.Preference; import android.preference.Preference;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.Window; import android.view.Window;
@ -37,7 +38,7 @@ import app.revanced.extension.youtube.requests.Requester;
import app.revanced.extension.youtube.requests.Route; import app.revanced.extension.youtube.requests.Route;
/** /**
* Opens a dialog showing the links from {@link SocialLinksRoutes}. * Opens a dialog showing official links.
*/ */
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public class ReVancedAboutPreference extends Preference { public class ReVancedAboutPreference extends Preference {
@ -72,7 +73,16 @@ public class ReVancedAboutPreference extends Preference {
return Color.BLACK; return Color.BLACK;
} }
private String createDialogHtml(WebLink[] socialLinks) { /**
* Apps that do not support bundling resources must override this.
*
* @return A localized string to display for the key.
*/
protected String getString(String key, Object ... args) {
return str(key, args);
}
private String createDialogHtml(WebLink[] aboutLinks) {
final boolean isNetworkConnected = Utils.isNetworkConnected(); final boolean isNetworkConnected = Utils.isNetworkConnected();
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
@ -91,7 +101,7 @@ public class ReVancedAboutPreference extends Preference {
builder.append("<img style=\"width: 100px; height: 100px;\" " builder.append("<img style=\"width: 100px; height: 100px;\" "
// Hide the image if it does not load. // Hide the image if it does not load.
+ "onerror=\"this.style.display='none';\" " + "onerror=\"this.style.display='none';\" "
+ "src=\"https://revanced.app/favicon.ico\" />"); + "src=\"").append(AboutLinksRoutes.aboutLogoUrl).append("\" />");
} }
String patchesVersion = Utils.getPatchesReleaseVersion(); String patchesVersion = Utils.getPatchesReleaseVersion();
@ -103,29 +113,29 @@ public class ReVancedAboutPreference extends Preference {
builder.append("<p>") builder.append("<p>")
// Replace hyphens with non breaking dashes so the version number does not break lines. // Replace hyphens with non breaking dashes so the version number does not break lines.
.append(useNonBreakingHyphens(str("revanced_settings_about_links_body", patchesVersion))) .append(useNonBreakingHyphens(getString("revanced_settings_about_links_body", patchesVersion)))
.append("</p>"); .append("</p>");
// Add a disclaimer if using a dev release. // Add a disclaimer if using a dev release.
if (patchesVersion.contains("dev")) { if (patchesVersion.contains("dev")) {
builder.append("<h3>") builder.append("<h3>")
// English text 'Pre-release' can break lines. // English text 'Pre-release' can break lines.
.append(useNonBreakingHyphens(str("revanced_settings_about_links_dev_header"))) .append(useNonBreakingHyphens(getString("revanced_settings_about_links_dev_header")))
.append("</h3>"); .append("</h3>");
builder.append("<p>") builder.append("<p>")
.append(str("revanced_settings_about_links_dev_body")) .append(getString("revanced_settings_about_links_dev_body"))
.append("</p>"); .append("</p>");
} }
builder.append("<h2 style=\"margin-top: 30px;\">") builder.append("<h2 style=\"margin-top: 30px;\">")
.append(str("revanced_settings_about_links_header")) .append(getString("revanced_settings_about_links_header"))
.append("</h2>"); .append("</h2>");
builder.append("<div>"); builder.append("<div>");
for (WebLink social : socialLinks) { for (WebLink link : aboutLinks) {
builder.append("<div style=\"margin-bottom: 20px;\">"); builder.append("<div style=\"margin-bottom: 20px;\">");
builder.append(String.format("<a href=\"%s\">%s</a>", social.url, social.name)); builder.append(String.format("<a href=\"%s\">%s</a>", link.url, link.name));
builder.append("</div>"); builder.append("</div>");
} }
builder.append("</div>"); builder.append("</div>");
@ -137,25 +147,44 @@ public class ReVancedAboutPreference extends Preference {
{ {
setOnPreferenceClickListener(pref -> { setOnPreferenceClickListener(pref -> {
// Show a progress spinner if the social links are not fetched yet. // Show a progress spinner if the social links are not fetched yet.
if (!SocialLinksRoutes.hasFetchedLinks() && Utils.isNetworkConnected()) { if (!AboutLinksRoutes.hasFetchedLinks() && Utils.isNetworkConnected()) {
// Show a progress spinner, but only if the api fetch takes more than a half a second.
final long delayToShowProgressSpinner = 500;
ProgressDialog progress = new ProgressDialog(getContext()); ProgressDialog progress = new ProgressDialog(getContext());
progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progress.show();
Utils.runOnBackgroundThread(() -> fetchLinksAndShowDialog(progress)); Handler handler = new Handler(Looper.getMainLooper());
Runnable showDialogRunnable = progress::show;
handler.postDelayed(showDialogRunnable, delayToShowProgressSpinner);
Utils.runOnBackgroundThread(() ->
fetchLinksAndShowDialog(handler, showDialogRunnable, progress));
} else { } else {
// No network call required and can run now. // No network call required and can run now.
fetchLinksAndShowDialog(null); fetchLinksAndShowDialog(null, null, null);
} }
return false; return false;
}); });
} }
private void fetchLinksAndShowDialog(@Nullable ProgressDialog progress) { private void fetchLinksAndShowDialog(@Nullable Handler handler,
WebLink[] socialLinks = SocialLinksRoutes.fetchSocialLinks(); Runnable showDialogRunnable,
String htmlDialog = createDialogHtml(socialLinks); @Nullable ProgressDialog progress) {
WebLink[] links = AboutLinksRoutes.fetchAboutLinks();
String htmlDialog = createDialogHtml(links);
// Enable to randomly force a delay to debug the spinner logic.
final boolean debugSpinnerDelayLogic = false;
//noinspection ConstantConditions
if (debugSpinnerDelayLogic && handler != null && Math.random() < 0.5f) {
Utils.doNothingForDuration((long) (Math.random() * 4000));
}
Utils.runOnMainThreadNowOrLater(() -> { Utils.runOnMainThreadNowOrLater(() -> {
if (handler != null) {
handler.removeCallbacks(showDialogRunnable);
}
if (progress != null) { if (progress != null) {
progress.dismiss(); progress.dismiss();
} }
@ -224,7 +253,7 @@ class WebViewDialog extends Dialog {
class WebLink { class WebLink {
final boolean preferred; final boolean preferred;
final String name; String name;
final String url; final String url;
WebLink(JSONObject json) throws JSONException { WebLink(JSONObject json) throws JSONException {
@ -243,7 +272,7 @@ class WebLink {
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return "ReVancedSocialLink{" + return "WebLink{" +
"preferred=" + preferred + "preferred=" + preferred +
", name='" + name + '\'' + ", name='" + name + '\'' +
", url='" + url + '\'' + ", url='" + url + '\'' +
@ -251,25 +280,21 @@ class WebLink {
} }
} }
class SocialLinksRoutes { class AboutLinksRoutes {
/** /**
* Simple link to the website donate page, * Backup icon url if the API call fails.
* rather than fetching and parsing the donation links using the API.
*/ */
public static final WebLink DONATE_LINK = new WebLink(true, public static volatile String aboutLogoUrl = "https://revanced.app/favicon.ico";
sf("revanced_settings_about_links_donate").toString(),
"https://revanced.app/donate");
/** /**
* Links to use if fetch links api call fails. * Links to use if fetch links api call fails.
*/ */
private static final WebLink[] NO_CONNECTION_STATIC_LINKS = { private static final WebLink[] NO_CONNECTION_STATIC_LINKS = {
new WebLink(true, "ReVanced.app", "https://revanced.app"), new WebLink(true, "ReVanced.app", "https://revanced.app")
DONATE_LINK,
}; };
private static final String SOCIAL_LINKS_PROVIDER = "https://api.revanced.app/v2"; private static final String SOCIAL_LINKS_PROVIDER = "https://api.revanced.app/v4";
private static final Route.CompiledRoute GET_SOCIAL = new Route(GET, "/socials").compile(); private static final Route.CompiledRoute GET_SOCIAL = new Route(GET, "/about").compile();
@Nullable @Nullable
private static volatile WebLink[] fetchedLinks; private static volatile WebLink[] fetchedLinks;
@ -278,7 +303,7 @@ class SocialLinksRoutes {
return fetchedLinks != null; return fetchedLinks != null;
} }
static WebLink[] fetchSocialLinks() { static WebLink[] fetchAboutLinks() {
try { try {
if (hasFetchedLinks()) return fetchedLinks; if (hasFetchedLinks()) return fetchedLinks;
@ -298,11 +323,22 @@ class SocialLinksRoutes {
} }
JSONObject json = Requester.parseJSONObjectAndDisconnect(connection); JSONObject json = Requester.parseJSONObjectAndDisconnect(connection);
JSONArray socials = json.getJSONArray("socials"); aboutLogoUrl = json.getJSONObject("branding").getString("logo");
List<WebLink> links = new ArrayList<>(); List<WebLink> links = new ArrayList<>();
links.add(DONATE_LINK); // Show donate link first. JSONArray donations = json.getJSONObject("donations").getJSONArray("links");
for (int i = 0, length = donations.length(); i < length; i++) {
WebLink link = new WebLink(donations.getJSONObject(i));
if (link.preferred) {
// This could be localized, but TikTok does not support localized resources.
// All link names returned by the api are also non localized.
link.name = "Donate";
links.add(link);
}
}
JSONArray socials = json.getJSONArray("socials");
for (int i = 0, length = socials.length(); i < length; i++) { for (int i = 0, length = socials.length(); i < length; i++) {
WebLink link = new WebLink(socials.getJSONObject(i)); WebLink link = new WebLink(socials.getJSONObject(i));
links.add(link); links.add(link);

View File

@ -0,0 +1,56 @@
package app.revanced.extension.tiktok.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import java.util.Map;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
@SuppressWarnings("unused")
public class ReVancedTikTokAboutPreference extends ReVancedAboutPreference {
/**
* Because resources cannot be added to TikTok,
* these strings are copied from the shared strings.xml file.
*
* Changes here must also be made in strings.xml
*/
private final Map<String, String> aboutStrings = Map.of(
"revanced_settings_about_links_body", "You are using ReVanced Patches version <i>%s</i>",
"revanced_settings_about_links_dev_header", "Note",
"revanced_settings_about_links_dev_body", "This version is a pre-release and you may experience unexpected issues",
"revanced_settings_about_links_header", "Official links"
);
{
//noinspection deprecation
setTitle("About");
}
public ReVancedTikTokAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ReVancedTikTokAboutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ReVancedTikTokAboutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReVancedTikTokAboutPreference(Context context) {
super(context);
}
@Override
protected String getString(String key, Object ... args) {
String format = aboutStrings.get(key);
if (format == null) {
Logger.printException(() -> "Unknown key: " + key);
return "";
}
return String.format(format, args);
}
}

View File

@ -4,13 +4,14 @@ import android.content.Context;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.tiktok.settings.preference.ReVancedTikTokAboutPreference;
import app.revanced.extension.tiktok.settings.preference.TogglePreference; import app.revanced.extension.tiktok.settings.preference.TogglePreference;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class ExtensionPreferenceCategory extends ConditionalPreferenceCategory { public class ExtensionPreferenceCategory extends ConditionalPreferenceCategory {
public ExtensionPreferenceCategory(Context context, PreferenceScreen screen) { public ExtensionPreferenceCategory(Context context, PreferenceScreen screen) {
super(context, screen); super(context, screen);
setTitle("Extension"); setTitle("Miscellaneous");
} }
@Override @Override
@ -20,6 +21,8 @@ public class ExtensionPreferenceCategory extends ConditionalPreferenceCategory {
@Override @Override
public void addPreferences(Context context) { public void addPreferences(Context context) {
addPreference(new ReVancedTikTokAboutPreference(context));
addPreference(new TogglePreference(context, addPreference(new TogglePreference(context,
"Enable debug log", "Enable debug log",
"Show extension debug log.", "Show extension debug log.",

View File

@ -82,4 +82,12 @@ public class ThemeHelper {
} }
return Utils.getResourceColor(colorString); return Utils.getResourceColor(colorString);
} }
public static int getBackgroundColor() {
return isDarkTheme() ? getDarkThemeColor() : getLightThemeColor();
}
public static int getForegroundColor() {
return isDarkTheme() ? getLightThemeColor() : getDarkThemeColor();
}
} }

View File

@ -1,14 +1,14 @@
package app.revanced.extension.youtube.patches; package app.revanced.extension.youtube.patches;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import app.revanced.extension.youtube.patches.playback.speed.RememberPlaybackSpeedPatch;
import app.revanced.extension.youtube.shared.VideoState;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Objects; import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.shared.VideoState;
/** /**
* Hooking class for the current playing video. * Hooking class for the current playing video.
* @noinspection unused * @noinspection unused
@ -120,6 +120,16 @@ public final class VideoInformation {
} }
} }
/**
* Injection point.
*/
public static void videoSpeedChanged(float currentVideoSpeed) {
if (playbackSpeed != currentVideoSpeed) {
Logger.printDebug(() -> "Video speed changed: " + currentVideoSpeed);
playbackSpeed = currentVideoSpeed;
}
}
/** /**
* Injection point. * Injection point.
* Called when user selects a playback speed. * Called when user selects a playback speed.
@ -131,18 +141,6 @@ public final class VideoInformation {
playbackSpeed = userSelectedPlaybackSpeed; playbackSpeed = userSelectedPlaybackSpeed;
} }
/**
* Overrides the current playback speed.
* <p>
* <b> Used exclusively by {@link RememberPlaybackSpeedPatch} </b>
*/
public static void overridePlaybackSpeed(float speedOverride) {
if (playbackSpeed != speedOverride) {
Logger.printDebug(() -> "Overriding playback speed to: " + speedOverride);
playbackSpeed = speedOverride;
}
}
/** /**
* Injection point. * Injection point.
* *

View File

@ -12,6 +12,8 @@ public final class RememberPlaybackSpeedPatch {
private static final long TOAST_DELAY_MILLISECONDS = 750; private static final long TOAST_DELAY_MILLISECONDS = 750;
private static volatile boolean newVideoStarted;
private static long lastTimeSpeedChanged; private static long lastTimeSpeedChanged;
/** /**
@ -19,7 +21,7 @@ public final class RememberPlaybackSpeedPatch {
*/ */
public static void newVideoStarted(VideoInformation.PlaybackController ignoredPlayerController) { public static void newVideoStarted(VideoInformation.PlaybackController ignoredPlayerController) {
Logger.printDebug(() -> "newVideoStarted"); Logger.printDebug(() -> "newVideoStarted");
VideoInformation.overridePlaybackSpeed(Settings.PLAYBACK_SPEED_DEFAULT.get()); newVideoStarted = true;
} }
/** /**
@ -29,42 +31,56 @@ public final class RememberPlaybackSpeedPatch {
* @param playbackSpeed The playback speed the user selected * @param playbackSpeed The playback speed the user selected
*/ */
public static void userSelectedPlaybackSpeed(float playbackSpeed) { public static void userSelectedPlaybackSpeed(float playbackSpeed) {
if (Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED.get()) { try {
// With the 0.05x menu, if the speed is set by integrations to higher than 2.0x if (Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED.get()) {
// then the menu will allow increasing without bounds but the max speed is // With the 0.05x menu, if the speed is set by integrations to higher than 2.0x
// still capped to under 8.0x. // then the menu will allow increasing without bounds but the max speed is
playbackSpeed = Math.min(playbackSpeed, CustomPlaybackSpeedPatch.PLAYBACK_SPEED_MAXIMUM - 0.05f); // still capped to under 8.0x.
playbackSpeed = Math.min(playbackSpeed, CustomPlaybackSpeedPatch.PLAYBACK_SPEED_MAXIMUM - 0.05f);
// Prevent toast spamming if using the 0.05x adjustments. // Prevent toast spamming if using the 0.05x adjustments.
// Show exactly one toast after the user stops interacting with the speed menu. // Show exactly one toast after the user stops interacting with the speed menu.
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
lastTimeSpeedChanged = now; lastTimeSpeedChanged = now;
final float finalPlaybackSpeed = playbackSpeed; final float finalPlaybackSpeed = playbackSpeed;
Utils.runOnMainThreadDelayed(() -> { Utils.runOnMainThreadDelayed(() -> {
if (lastTimeSpeedChanged != now) { if (lastTimeSpeedChanged != now) {
// The user made additional speed adjustments and this call is outdated. // The user made additional speed adjustments and this call is outdated.
return; return;
} }
if (Settings.PLAYBACK_SPEED_DEFAULT.get() == finalPlaybackSpeed) { if (Settings.PLAYBACK_SPEED_DEFAULT.get() == finalPlaybackSpeed) {
// User changed to a different speed and immediately changed back. // User changed to a different speed and immediately changed back.
// Or the user is going past 8.0x in the glitched out 0.05x menu. // Or the user is going past 8.0x in the glitched out 0.05x menu.
return; return;
} }
Settings.PLAYBACK_SPEED_DEFAULT.save(finalPlaybackSpeed); Settings.PLAYBACK_SPEED_DEFAULT.save(finalPlaybackSpeed);
Utils.showToastLong(str("revanced_remember_playback_speed_toast", (finalPlaybackSpeed + "x"))); Utils.showToastLong(str("revanced_remember_playback_speed_toast", (finalPlaybackSpeed + "x")));
}, TOAST_DELAY_MILLISECONDS); }, TOAST_DELAY_MILLISECONDS);
}
} catch (Exception ex) {
Logger.printException(() -> "userSelectedPlaybackSpeed failure", ex);
} }
} }
/** /**
* Injection point. * Injection point.
* Overrides the video speed. Called after video loads, and immediately after user selects a different playback speed * Overrides the video speed. Called after video loads,
* and immediately after the user selects a different playback speed.
*/ */
public static float getPlaybackSpeedOverride() { public static float getPlaybackSpeedOverride() {
return VideoInformation.getPlaybackSpeed(); if (newVideoStarted) {
newVideoStarted = false;
final float defaultSpeed = Settings.PLAYBACK_SPEED_DEFAULT.get();
if (defaultSpeed > 0) {
return defaultSpeed;
}
}
return -2.0f;
} }
} }

View File

@ -8,6 +8,17 @@ import android.os.Build;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
public enum ClientType { public enum ClientType {
// Specific purpose for age restricted, or private videos, because the iOS client is not logged in.
ANDROID_VR(28,
"Quest 3",
"12",
"com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12; GB) gzip",
"32", // Android 12.1
"1.56.21",
"ANDROID_VR",
true
),
// Specific for kids videos.
// https://dumps.tadiphone.dev/dumps/oculus/eureka // https://dumps.tadiphone.dev/dumps/oculus/eureka
IOS(5, IOS(5,
// iPhone 15 supports AV1 hardware decoding. // iPhone 15 supports AV1 hardware decoding.
@ -25,14 +36,9 @@ public enum ClientType {
null, null,
// Version number should be a valid iOS release. // Version number should be a valid iOS release.
// https://www.ipa4fun.com/history/185230 // https://www.ipa4fun.com/history/185230
"19.10.7" "19.10.7",
), "IOS",
ANDROID_VR(28, false
"Quest 3",
"12",
"com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12; GB) gzip",
"32", // Android 12.1
"1.56.21"
); );
/** /**
@ -44,7 +50,7 @@ public enum ClientType {
/** /**
* Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model) * Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model)
*/ */
public final String model; public final String deviceModel;
/** /**
* Device OS version. * Device OS version.
@ -63,17 +69,37 @@ public enum ClientType {
@Nullable @Nullable
public final String androidSdkVersion; public final String androidSdkVersion;
/**
* Client name.
*/
public final String clientName;
/** /**
* App version. * App version.
*/ */
public final String appVersion; public final String clientVersion;
ClientType(int id, String model, String osVersion, String userAgent, @Nullable String androidSdkVersion, String appVersion) { /**
* If the client can access the API logged in.
*/
public final boolean canLogin;
ClientType(int id,
String deviceModel,
String osVersion,
String userAgent,
@Nullable String androidSdkVersion,
String clientVersion,
String clientName,
boolean canLogin
) {
this.id = id; this.id = id;
this.model = model; this.deviceModel = deviceModel;
this.osVersion = osVersion; this.osVersion = osVersion;
this.userAgent = userAgent; this.userAgent = userAgent;
this.androidSdkVersion = androidSdkVersion; this.androidSdkVersion = androidSdkVersion;
this.appVersion = appVersion; this.clientVersion = clientVersion;
this.clientName = clientName;
this.canLogin = canLogin;
} }
} }

View File

@ -12,15 +12,13 @@ import app.revanced.extension.youtube.requests.Requester;
import app.revanced.extension.youtube.requests.Route; import app.revanced.extension.youtube.requests.Route;
final class PlayerRoutes { final class PlayerRoutes {
private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
static final Route.CompiledRoute GET_STREAMING_DATA = new Route( static final Route.CompiledRoute GET_STREAMING_DATA = new Route(
Route.Method.POST, Route.Method.POST,
"player" + "player" +
"?fields=streamingData" + "?fields=streamingData" +
"&alt=proto" "&alt=proto"
).compile(); ).compile();
private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
/** /**
* TCP connection and HTTP read timeout * TCP connection and HTTP read timeout
*/ */
@ -30,15 +28,15 @@ final class PlayerRoutes {
} }
static String createInnertubeBody(ClientType clientType) { static String createInnertubeBody(ClientType clientType) {
JSONObject innerTubeBody = new JSONObject(); JSONObject innerTubeBody = new JSONObject();
try { try {
JSONObject context = new JSONObject(); JSONObject context = new JSONObject();
JSONObject client = new JSONObject(); JSONObject client = new JSONObject();
client.put("clientName", clientType.name()); client.put("clientName", clientType.name());
client.put("clientVersion", clientType.appVersion); client.put("clientVersion", clientType.clientVersion);
client.put("deviceModel", clientType.model); client.put("deviceModel", clientType.deviceModel);
client.put("osVersion", clientType.osVersion); client.put("osVersion", clientType.osVersion);
if (clientType.androidSdkVersion != null) { if (clientType.androidSdkVersion != null) {
client.put("androidSdkVersion", clientType.androidSdkVersion); client.put("androidSdkVersion", clientType.androidSdkVersion);
@ -57,7 +55,9 @@ final class PlayerRoutes {
return innerTubeBody.toString(); return innerTubeBody.toString();
} }
/** @noinspection SameParameterValue*/ /**
* @noinspection SameParameterValue
*/
static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route, ClientType clientType) throws IOException { static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route, ClientType clientType) throws IOException {
var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route); var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route);

View File

@ -28,7 +28,7 @@ import app.revanced.extension.youtube.settings.Settings;
/** /**
* Video streaming data. Fetching is tied to the behavior YT uses, * Video streaming data. Fetching is tied to the behavior YT uses,
* where this class fetches the streams only when YT fetches. * where this class fetches the streams only when YT fetches.
* * <p>
* Effectively the cache expiration of these fetches is the same as the stock app, * Effectively the cache expiration of these fetches is the same as the stock app,
* since the stock app would not use expired streams and therefor * since the stock app would not use expired streams and therefor
* the extension replace stream hook is called only if YT * the extension replace stream hook is called only if YT
@ -37,38 +37,20 @@ import app.revanced.extension.youtube.settings.Settings;
public class StreamingDataRequest { public class StreamingDataRequest {
private static final ClientType[] CLIENT_ORDER_TO_USE; private static final ClientType[] CLIENT_ORDER_TO_USE;
private static final String AUTHORIZATION_HEADER = "Authorization";
static {
ClientType[] allClientTypes = ClientType.values();
ClientType preferredClient = Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length];
CLIENT_ORDER_TO_USE[0] = preferredClient;
int i = 1;
for (ClientType c : allClientTypes) {
if (c != preferredClient) {
CLIENT_ORDER_TO_USE[i++] = c;
}
}
}
private static final String[] REQUEST_HEADER_KEYS = { private static final String[] REQUEST_HEADER_KEYS = {
"Authorization", // Available only to logged in users. AUTHORIZATION_HEADER, // Available only to logged-in users.
"X-GOOG-API-FORMAT-VERSION", "X-GOOG-API-FORMAT-VERSION",
"X-Goog-Visitor-Id" "X-Goog-Visitor-Id"
}; };
/** /**
* TCP connection and HTTP read timeout. * TCP connection and HTTP read timeout.
*/ */
private static final int HTTP_TIMEOUT_MILLISECONDS = 10 * 1000; private static final int HTTP_TIMEOUT_MILLISECONDS = 10 * 1000;
/** /**
* Any arbitrarily large value, but must be at least twice {@link #HTTP_TIMEOUT_MILLISECONDS} * Any arbitrarily large value, but must be at least twice {@link #HTTP_TIMEOUT_MILLISECONDS}
*/ */
private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000; private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000;
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap( private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
new LinkedHashMap<>(100) { new LinkedHashMap<>(100) {
/** /**
@ -86,8 +68,32 @@ public class StreamingDataRequest {
} }
}); });
static {
ClientType[] allClientTypes = ClientType.values();
ClientType preferredClient = Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length];
CLIENT_ORDER_TO_USE[0] = preferredClient;
int i = 1;
for (ClientType c : allClientTypes) {
if (c != preferredClient) {
CLIENT_ORDER_TO_USE[i++] = c;
}
}
}
private final String videoId;
private final Future<ByteBuffer> future;
private StreamingDataRequest(String videoId, Map<String, String> playerHeaders) {
Objects.requireNonNull(playerHeaders);
this.videoId = videoId;
this.future = Utils.submitOnBackgroundThread(() -> fetch(videoId, playerHeaders));
}
public static void fetchRequest(String videoId, Map<String, String> fetchHeaders) { public static void fetchRequest(String videoId, Map<String, String> fetchHeaders) {
// Always fetch, even if there is a existing request for the same video. // Always fetch, even if there is an existing request for the same video.
cache.put(videoId, new StreamingDataRequest(videoId, fetchHeaders)); cache.put(videoId, new StreamingDataRequest(videoId, fetchHeaders));
} }
@ -119,6 +125,10 @@ public class StreamingDataRequest {
connection.setReadTimeout(HTTP_TIMEOUT_MILLISECONDS); connection.setReadTimeout(HTTP_TIMEOUT_MILLISECONDS);
for (String key : REQUEST_HEADER_KEYS) { for (String key : REQUEST_HEADER_KEYS) {
if (!clientType.canLogin && key.equals(AUTHORIZATION_HEADER)) {
continue;
}
String value = playerHeaders.get(key); String value = playerHeaders.get(key);
if (value != null) { if (value != null) {
connection.setRequestProperty(key, value); connection.setRequestProperty(key, value);
@ -186,15 +196,6 @@ public class StreamingDataRequest {
return null; return null;
} }
private final String videoId;
private final Future<ByteBuffer> future;
private StreamingDataRequest(String videoId, Map<String, String> playerHeaders) {
Objects.requireNonNull(playerHeaders);
this.videoId = videoId;
this.future = Utils.submitOnBackgroundThread(() -> fetch(videoId, playerHeaders));
}
public boolean fetchCompleted() { public boolean fetchCompleted() {
return future.isDone(); return future.isDone();
} }

View File

@ -2,9 +2,12 @@ package app.revanced.extension.youtube.patches.theme;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.content.res.Resources;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.AnimatedVectorDrawable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
@ -16,7 +19,8 @@ public final class SeekbarColorPatch {
private static final boolean SEEKBAR_CUSTOM_COLOR_ENABLED = Settings.SEEKBAR_CUSTOM_COLOR.get(); private static final boolean SEEKBAR_CUSTOM_COLOR_ENABLED = Settings.SEEKBAR_CUSTOM_COLOR.get();
/** /**
* Default color of the seekbar. * Default color of the litho seekbar.
* Differs slightly from the default custom seekbar color setting.
*/ */
private static final int ORIGINAL_SEEKBAR_COLOR = 0xFFFF0000; private static final int ORIGINAL_SEEKBAR_COLOR = 0xFFFF0000;
@ -72,12 +76,76 @@ public final class SeekbarColorPatch {
return seekbarColor; return seekbarColor;
} }
/**
* Injection point
*/
public static boolean playerSeekbarGradientEnabled(boolean original) { public static boolean playerSeekbarGradientEnabled(boolean original) {
if (SEEKBAR_CUSTOM_COLOR_ENABLED) return false; if (SEEKBAR_CUSTOM_COLOR_ENABLED) return false;
return original; return original;
} }
/**
* Injection point
*/
public static boolean useLotteLaunchSplashScreen(boolean original) {
Logger.printDebug(() -> "useLotteLaunchSplashScreen original: " + original);
if (SEEKBAR_CUSTOM_COLOR_ENABLED) return false;
return original;
}
private static int colorChannelTo3Bits(int channel8Bits) {
final float channel3Bits = channel8Bits * 7 / 255f;
// If a color channel is near zero, then allow rounding up so values between
// 0x12 and 0x23 will show as 0x24. But always round down when the channel is
// near full saturation, otherwise rounding to nearest will cause all values
// between 0xEC and 0xFE to always show as full saturation (0xFF).
return channel3Bits < 6
? Math.round(channel3Bits)
: (int) channel3Bits;
}
private static String get9BitStyleIdentifier(int color24Bit) {
final int r3 = colorChannelTo3Bits(Color.red(color24Bit));
final int g3 = colorChannelTo3Bits(Color.green(color24Bit));
final int b3 = colorChannelTo3Bits(Color.blue(color24Bit));
return String.format(Locale.US, "splash_seekbar_color_style_%d_%d_%d", r3, g3, b3);
}
/**
* Injection point
*/
public static void setSplashAnimationDrawableTheme(AnimatedVectorDrawable vectorDrawable) {
// Alternatively a ColorMatrixColorFilter can be used to change the color of the drawable
// without using any styles, but a color filter cannot selectively change the seekbar
// while keeping the red YT logo untouched.
// Even if the seekbar color xml value is changed to a completely different color (such as green),
// a color filter still cannot be selectively applied when the drawable has more than 1 color.
try {
String seekbarStyle = get9BitStyleIdentifier(seekbarColor);
Logger.printDebug(() -> "Using splash seekbar style: " + seekbarStyle);
final int styleIdentifierDefault = Utils.getResourceIdentifier(
seekbarStyle,
"style"
);
if (styleIdentifierDefault == 0) {
throw new RuntimeException("Seekbar style not found: " + seekbarStyle);
}
Resources.Theme theme = Utils.getContext().getResources().newTheme();
theme.applyStyle(styleIdentifierDefault, true);
vectorDrawable.applyTheme(theme);
} catch (Exception ex) {
Logger.printException(() -> "setSplashAnimationDrawableTheme failure", ex);
}
}
/** /**
* Injection point. * Injection point.
* *
@ -189,4 +257,4 @@ public final class SeekbarColorPatch {
private static float clamp(float value, float lower, float upper) { private static float clamp(float value, float lower, float upper) {
return Math.max(lower, Math.min(value, upper)); return Math.max(lower, Math.min(value, upper));
} }
} }

View File

@ -7,6 +7,7 @@ import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment;
import app.revanced.extension.youtube.ThemeHelper; import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment; import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
import app.revanced.extension.youtube.settings.preference.ReturnYouTubeDislikePreferenceFragment; import app.revanced.extension.youtube.settings.preference.ReturnYouTubeDislikePreferenceFragment;
@ -39,7 +40,7 @@ public class LicenseActivityHook {
PreferenceFragment fragment; PreferenceFragment fragment;
String toolbarTitleResourceName; String toolbarTitleResourceName;
String dataString = licenseActivity.getIntent().getDataString(); String dataString = Objects.requireNonNull(licenseActivity.getIntent().getDataString());
switch (dataString) { switch (dataString) {
case "revanced_sb_settings_intent": case "revanced_sb_settings_intent":
toolbarTitleResourceName = "revanced_sb_settings_title"; toolbarTitleResourceName = "revanced_sb_settings_title";
@ -59,12 +60,14 @@ public class LicenseActivityHook {
} }
setToolbarTitle(licenseActivity, toolbarTitleResourceName); setToolbarTitle(licenseActivity, toolbarTitleResourceName);
//noinspection deprecation
licenseActivity.getFragmentManager() licenseActivity.getFragmentManager()
.beginTransaction() .beginTransaction()
.replace(getResourceIdentifier("revanced_settings_fragments", "id"), fragment) .replace(getResourceIdentifier("revanced_settings_fragments", "id"), fragment)
.commit(); .commit();
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "onCreate failure", ex); Logger.printException(() -> "initialize failure", ex);
} }
} }
@ -80,11 +83,7 @@ public class LicenseActivityHook {
ViewGroup toolbar = activity.findViewById(getToolbarResourceId()); ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
ImageButton imageButton = Objects.requireNonNull(getChildView(toolbar, false, ImageButton imageButton = Objects.requireNonNull(getChildView(toolbar, false,
view -> view instanceof ImageButton)); view -> view instanceof ImageButton));
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme() imageButton.setImageDrawable(AbstractPreferenceFragment.getBackButtonDrawable());
? "yt_outline_arrow_left_white_24"
: "yt_outline_arrow_left_black_24",
"drawable");
imageButton.setImageDrawable(activity.getResources().getDrawable(backButtonResource));
imageButton.setOnClickListener(view -> activity.onBackPressed()); imageButton.setOnClickListener(view -> activity.onBackPressed());
} }

View File

@ -20,7 +20,6 @@ import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillIm
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime;
import app.revanced.extension.youtube.patches.spoof.ClientType; import app.revanced.extension.youtube.patches.spoof.ClientType;
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
import app.revanced.extension.youtube.patches.spoof.SpoofVideoStreamsPatch; import app.revanced.extension.youtube.patches.spoof.SpoofVideoStreamsPatch;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings; import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
@ -380,7 +379,7 @@ public class Settings extends BaseSettings {
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER); migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER);
// Old spoof versions that no longer work reliably. // Old spoof versions that no longer work reliably.
if (SpoofAppVersionPatch.isSpoofingToLessThan(SPOOF_APP_VERSION_TARGET.defaultValue)) { if (SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0) {
Logger.printInfo(() -> "Resetting spoof app version target"); Logger.printInfo(() -> "Resetting spoof app version target");
SPOOF_APP_VERSION_TARGET.resetToDefault(); SPOOF_APP_VERSION_TARGET.resetToDefault();
} }

View File

@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true org.gradle.parallel = true
android.useAndroidX = true android.useAndroidX = true
kotlin.code.style = official kotlin.code.style = official
version = 5.1.0 version = 5.2.0-dev.7

View File

@ -1328,6 +1328,7 @@ public final class app/revanced/patches/youtube/misc/playservice/VersionCheckPat
public static final fun is_19_36_or_greater ()Z public static final fun is_19_36_or_greater ()Z
public static final fun is_19_41_or_greater ()Z public static final fun is_19_41_or_greater ()Z
public static final fun is_19_43_or_greater ()Z public static final fun is_19_43_or_greater ()Z
public static final fun is_19_46_or_greater ()Z
} }
public final class app/revanced/patches/youtube/misc/privacy/RemoveTrackingQueryParameterPatchKt { public final class app/revanced/patches/youtube/misc/privacy/RemoveTrackingQueryParameterPatchKt {
@ -1369,11 +1370,9 @@ public final class app/revanced/patches/youtube/shared/FingerprintsKt {
} }
public final class app/revanced/patches/youtube/video/information/VideoInformationPatchKt { public final class app/revanced/patches/youtube/video/information/VideoInformationPatchKt {
public static final fun getSetPlaybackSpeedClassFieldReference ()Ljava/lang/String;
public static final fun getSetPlaybackSpeedContainerClassFieldReference ()Ljava/lang/String;
public static final fun getSetPlaybackSpeedMethodReference ()Ljava/lang/String;
public static final fun getVideoInformationPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getVideoInformationPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun userSelectedPlaybackSpeedHook (Ljava/lang/String;Ljava/lang/String;)V public static final fun userSelectedPlaybackSpeedHook (Ljava/lang/String;Ljava/lang/String;)V
public static final fun videoSpeedChangedHook (Ljava/lang/String;Ljava/lang/String;)V
public static final fun videoTimeHook (Ljava/lang/String;Ljava/lang/String;)V public static final fun videoTimeHook (Ljava/lang/String;Ljava/lang/String;)V
} }

View File

@ -7,7 +7,7 @@ import app.revanced.patcher.patch.bytecodePatch
val unlockProPatch = bytecodePatch( val unlockProPatch = bytecodePatch(
name = "Unlock pro", name = "Unlock pro",
) { ) {
compatibleWith("org.totschnig.myexpenses") compatibleWith("org.totschnig.myexpenses"("3.4.9"))
execute { execute {
isEnabledFingerprint.method.addInstructions( isEnabledFingerprint.method.addInstructions(

View File

@ -3,9 +3,9 @@ package app.revanced.patches.vsco.misc.pro
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
@Deprecated("This patch is deprecated because it does not work anymore and will be removed in the future.")
@Suppress("unused") @Suppress("unused")
val unlockProPatch = bytecodePatch( val unlockProPatch = bytecodePatch(
name = "Unlock pro",
description = "Unlocks pro features.", description = "Unlocks pro features.",
) { ) {
compatibleWith("com.vsco.cam"("345")) compatibleWith("com.vsco.cam"("345"))

View File

@ -76,6 +76,7 @@ val hideAdsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -32,6 +32,7 @@ val hideGetPremiumPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -30,6 +30,7 @@ val videoAdsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -60,6 +60,7 @@ val copyVideoUrlPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -31,6 +31,7 @@ val removeViewerDiscretionDialogPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -75,6 +75,7 @@ val downloadsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -30,6 +30,7 @@ val disablePreciseSeekingGesturePatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -33,6 +33,7 @@ val enableSeekbarTappingPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -43,6 +43,7 @@ val enableSlideToSeekPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -36,6 +36,7 @@ val seekbarThumbnailsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
) )
) )

View File

@ -72,6 +72,7 @@ val swipeControlsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -29,6 +29,7 @@ val autoCaptionsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -50,6 +50,7 @@ val customBrandingPatch = resourcePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -48,6 +48,7 @@ val changeHeaderPatch = resourcePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
) )
) )

View File

@ -29,6 +29,7 @@ val hideButtonsPatch = resourcePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -43,6 +43,7 @@ val navigationButtonsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -61,6 +61,7 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -63,6 +63,7 @@ val hideEndscreenCardsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -36,6 +36,7 @@ val disableFullscreenAmbientModePatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -129,6 +129,7 @@ val hideLayoutComponentsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -64,6 +64,7 @@ val hideInfoCardsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -31,6 +31,7 @@ val hidePlayerFlyoutMenuPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -36,6 +36,7 @@ val disableRollingNumberAnimationPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -32,6 +32,7 @@ val hideSeekbarPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -189,9 +189,13 @@ val hideShortsComponentsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )
hideShortsAppShortcutOption()
hideShortsWidgetOption()
execute { execute {
// region Hide the Shorts shelf. // region Hide the Shorts shelf.

View File

@ -61,6 +61,7 @@ val disableSuggestedVideoEndScreenPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -28,6 +28,7 @@ val hideTimestampPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -135,8 +135,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/
@Suppress("unused") @Suppress("unused")
val miniplayerPatch = bytecodePatch( val miniplayerPatch = bytecodePatch(
name = "Miniplayer", name = "Miniplayer",
description = "Adds options to change the in app minimized player. " + description = "Adds options to change the in app minimized player."
"Patching target 19.16+ adds modern miniplayers.",
) { ) {
dependsOn( dependsOn(
sharedExtensionPatch, sharedExtensionPatch,
@ -170,6 +169,7 @@ val miniplayerPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -28,6 +28,7 @@ val playerPopupPanelsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -19,6 +19,7 @@ val playerControlsBackgroundPatch = resourcePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -59,6 +59,7 @@ val customPlayerOverlayOpacityPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -60,6 +60,7 @@ val returnYouTubeDislikePatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -36,6 +36,7 @@ val wideSearchbarPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -48,3 +48,17 @@ internal val lithoLinearGradientFingerprint = fingerprint {
returns("Landroid/graphics/LinearGradient;") returns("Landroid/graphics/LinearGradient;")
parameters("F", "F", "F", "F", "[I", "[F") parameters("F", "F", "F", "F", "[I", "[F")
} }
internal const val launchScreenLayoutTypeLotteFeatureFlag = 268507948L
internal val launchScreenLayoutTypeFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
returns("V")
custom { method, _ ->
val firstParameter = method.parameterTypes.firstOrNull()
// 19.25 - 19.45
(firstParameter == "Lcom/google/android/apps/youtube/app/watchwhile/MainActivity;"
|| firstParameter == "Landroid/app/Activity;") // 19.46+
&& method.containsLiteralInstruction(launchScreenLayoutTypeLotteFeatureFlag)
}
}

View File

@ -13,15 +13,24 @@ import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.youtube.layout.theme.lithoColorHookPatch import app.revanced.patches.youtube.layout.theme.lithoColorHookPatch
import app.revanced.patches.youtube.layout.theme.lithoColorOverrideHook import app.revanced.patches.youtube.layout.theme.lithoColorOverrideHook
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_23_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.misc.playservice.is_19_46_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
import app.revanced.util.copyXmlNode
import app.revanced.util.findElementByAttributeValueOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import app.revanced.util.inputStreamFromBundledResource
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import org.w3c.dom.Element import org.w3c.dom.Element
import java.io.ByteArrayInputStream
import kotlin.use
internal var reelTimeBarPlayedColorId = -1L internal var reelTimeBarPlayedColorId = -1L
private set private set
@ -30,6 +39,8 @@ internal var inlineTimeBarColorizedBarPlayedColorDarkId = -1L
internal var inlineTimeBarPlayedNotHighlightedColorId = -1L internal var inlineTimeBarPlayedNotHighlightedColorId = -1L
private set private set
internal const val splashSeekbarColorAttributeName = "splash_custom_seekbar_color"
private val seekbarColorResourcePatch = resourcePatch { private val seekbarColorResourcePatch = resourcePatch {
dependsOn( dependsOn(
settingsPatch, settingsPatch,
@ -51,9 +62,8 @@ private val seekbarColorResourcePatch = resourcePatch {
"inline_time_bar_played_not_highlighted_color", "inline_time_bar_played_not_highlighted_color",
] ]
// Edit the resume playback drawable and replace the progress bar with a custom drawable // Modify the resume playback drawable and replace the progress bar with a custom drawable.
document("res/drawable/resume_playback_progressbar_drawable.xml").use { document -> document("res/drawable/resume_playback_progressbar_drawable.xml").use { document ->
val layerList = document.getElementsByTagName("layer-list").item(0) as Element val layerList = document.getElementsByTagName("layer-list").item(0) as Element
val progressNode = layerList.getElementsByTagName("item").item(1) as Element val progressNode = layerList.getElementsByTagName("item").item(1) as Element
if (!progressNode.getAttributeNode("android:id").value.endsWith("progress")) { if (!progressNode.getAttributeNode("android:id").value.endsWith("progress")) {
@ -66,9 +76,102 @@ private val seekbarColorResourcePatch = resourcePatch {
) )
scaleNode.replaceChild(replacementNode, shapeNode) scaleNode.replaceChild(replacementNode, shapeNode)
} }
if (!is_19_25_or_greater) {
return@execute
}
// Add attribute and styles for splash screen custom color.
// Using a style is the only way to selectively change just the seekbar fill color.
//
// Because the style colors must be hard coded for all color possibilities,
// instead of allowing 24 bit color the style is restricted to 9-bit (3 bits per color channel)
// and the style color closest to the users custom color is used for the splash screen.
arrayOf(
inputStreamFromBundledResource("seekbar/values", "attrs.xml")!! to "res/values/attrs.xml",
ByteArrayInputStream(create9BitSeekbarColorStyles().toByteArray()) to "res/values/styles.xml"
).forEach { (source, destination) ->
"resources".copyXmlNode(
document(source),
document(destination),
).close()
}
fun setSplashDrawablePathFillColor(xmlFileNames: Iterable<String>, vararg resourceNames: String) {
xmlFileNames.forEach { xmlFileName ->
document(xmlFileName).use { document ->
resourceNames.forEach { elementId ->
val element = document.childNodes.findElementByAttributeValueOrThrow(
"android:name",
elementId
)
val attribute = "android:fillColor"
if (!element.hasAttribute(attribute)) {
throw PatchException("Could not find $attribute for $elementId")
}
element.setAttribute(attribute, "?attr/$splashSeekbarColorAttributeName")
}
}
}
}
setSplashDrawablePathFillColor(
listOf(
"res/drawable/\$startup_animation_light__0.xml",
"res/drawable/\$startup_animation_dark__0.xml"
),
"_R_G_L_10_G_D_0_P_0"
)
if (!is_19_46_or_greater) {
// Resources removed in 19.46+
setSplashDrawablePathFillColor(
listOf(
"res/drawable/\$buenos_aires_animation_light__0.xml",
"res/drawable/\$buenos_aires_animation_dark__0.xml"
),
"_R_G_L_8_G_D_0_P_0"
)
}
} }
} }
/**
* Generate a style xml with all combinations of 9-bit colors.
*/
private fun create9BitSeekbarColorStyles(): String = StringBuilder().apply {
append("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
append("<resources>\n")
for (red in 0..7) {
for (green in 0..7) {
for (blue in 0..7) {
val name = "${red}_${green}_${blue}"
fun roundTo3BitHex(channel8Bits: Int) =
(channel8Bits * 255 / 7).toString(16).padStart(2, '0')
val r = roundTo3BitHex(red)
val g = roundTo3BitHex(green)
val b = roundTo3BitHex(blue)
val color = "#ff$r$g$b"
append(
"""
<style name="splash_seekbar_color_style_$name">
<item name="$splashSeekbarColorAttributeName">$color</item>
</style>
"""
)
}
}
}
append("</resources>")
}.toString()
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/theme/SeekbarColorPatch;" private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/theme/SeekbarColorPatch;"
val seekbarColorPatch = bytecodePatch( val seekbarColorPatch = bytecodePatch(
@ -117,27 +220,73 @@ val seekbarColorPatch = bytecodePatch(
} }
} }
if (is_19_23_or_greater) { lithoColorOverrideHook(EXTENSION_CLASS_DESCRIPTOR, "getLithoColor")
playerSeekbarGradientConfigFingerprint.method.apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG) if (!is_19_25_or_greater) {
return@execute
}
// 19.25+ changes
playerSeekbarGradientConfigFingerprint.method.apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG)
val resultIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
val register = getInstruction<OneRegisterInstruction>(resultIndex).registerA
addInstructions(
resultIndex + 1,
"""
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->playerSeekbarGradientEnabled(Z)Z
move-result v$register
"""
)
}
lithoLinearGradientFingerprint.method.addInstruction(
0,
"invoke-static/range { p4 .. p5 }, $EXTENSION_CLASS_DESCRIPTOR->setLinearGradient([I[F)V"
)
// region apply seekbar custom color to splash screen animation.
// Don't use the lotte splash screen layout if using custom seekbar.
arrayOf(
launchScreenLayoutTypeFingerprint,
mainActivityOnCreateFingerprint
).forEach { fingerprint ->
fingerprint.method.apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(launchScreenLayoutTypeLotteFeatureFlag)
val resultIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT) val resultIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
val register = getInstruction<OneRegisterInstruction>(resultIndex).registerA val register = getInstruction<OneRegisterInstruction>(resultIndex).registerA
addInstructions( addInstructions(
resultIndex + 1, resultIndex + 1,
""" """
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->playerSeekbarGradientEnabled(Z)Z invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->useLotteLaunchSplashScreen(Z)Z
move-result v$register move-result v$register
""", """
) )
} }
}
lithoLinearGradientFingerprint.method.addInstruction( // Hook the splash animation drawable to set the a seekbar color theme.
0, mainActivityOnCreateFingerprint.method.apply {
"invoke-static/range { p4 .. p5 }, $EXTENSION_CLASS_DESCRIPTOR->setLinearGradient([I[F)V", val drawableIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/widget/ImageView;"
&& reference.name == "getDrawable"
}
val checkCastIndex = indexOfFirstInstructionOrThrow(drawableIndex, Opcode.CHECK_CAST)
val drawableRegister = getInstruction<OneRegisterInstruction>(checkCastIndex).registerA
addInstruction(
checkCastIndex + 1,
"invoke-static { v$drawableRegister }, $EXTENSION_CLASS_DESCRIPTOR->" +
"setSplashAnimationDrawableTheme(Landroid/graphics/drawable/AnimatedVectorDrawable;)V"
) )
} }
lithoColorOverrideHook(EXTENSION_CLASS_DESCRIPTOR, "getLithoColor") // endregion
} }
} }

View File

@ -38,6 +38,7 @@ val shortsAutoplayPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )
@ -56,7 +57,7 @@ val shortsAutoplayPatch = bytecodePatch(
// Main activity is used to check if app is in pip mode. // Main activity is used to check if app is in pip mode.
mainActivityOnCreateFingerprint.method.addInstructions( mainActivityOnCreateFingerprint.method.addInstructions(
0, 1,
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" + "invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->" +
"setMainActivity(Landroid/app/Activity;)V", "setMainActivity(Landroid/app/Activity;)V",
) )

View File

@ -118,6 +118,7 @@ val sponsorBlockPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -39,6 +39,7 @@ val spoofAppVersionPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -36,6 +36,7 @@ val changeStartPagePatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -38,6 +38,7 @@ val disableResumingShortsOnStartupPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -33,6 +33,7 @@ val enableTabletLayoutPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -201,6 +201,7 @@ val themePatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -40,6 +40,7 @@ val alternativeThumbnailsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -34,6 +34,7 @@ val bypassImageRegionRestrictionsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -30,6 +30,7 @@ val announcementsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -31,6 +31,7 @@ val autoRepeatPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -55,6 +55,7 @@ val backgroundPlaybackPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -36,6 +36,7 @@ val enableDebuggingPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -31,6 +31,7 @@ val spoofDeviceDimensionsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -24,6 +24,7 @@ val checkWatchHistoryDomainNameResolutionPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -44,6 +44,7 @@ val spoofVideoStreamsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -42,6 +42,7 @@ val gmsCoreSupportPatch = gmsCoreSupportPatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )
} }

View File

@ -37,6 +37,7 @@ val bypassURLRedirectsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -48,6 +48,7 @@ val openLinksExternallyPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -37,6 +37,8 @@ var is_19_41_or_greater = false
private set private set
var is_19_43_or_greater = false var is_19_43_or_greater = false
private set private set
var is_19_46_or_greater = false
private set
val versionCheckPatch = resourcePatch( val versionCheckPatch = resourcePatch(
description = "Uses the Play Store service version to find the major/minor version of the YouTube target app.", description = "Uses the Play Store service version to find the major/minor version of the YouTube target app.",
@ -68,5 +70,6 @@ val versionCheckPatch = resourcePatch(
is_19_36_or_greater = 243705000 <= playStoreServicesVersion is_19_36_or_greater = 243705000 <= playStoreServicesVersion
is_19_41_or_greater = 244305000 <= playStoreServicesVersion is_19_41_or_greater = 244305000 <= playStoreServicesVersion
is_19_43_or_greater = 244405000 <= playStoreServicesVersion is_19_43_or_greater = 244405000 <= playStoreServicesVersion
is_19_46_or_greater = 244705000 <= playStoreServicesVersion
} }
} }

View File

@ -37,6 +37,7 @@ val removeTrackingQueryParameterPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -28,6 +28,7 @@ val zoomHapticsPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -20,13 +20,13 @@ import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
@ -57,12 +57,16 @@ private lateinit var speedSelectionInsertMethod: MutableMethod
private var speedSelectionInsertIndex = -1 private var speedSelectionInsertIndex = -1
private var speedSelectionValueRegister = -1 private var speedSelectionValueRegister = -1
// Change playback speed method.
private lateinit var setPlaybackSpeedMethod: MutableMethod
private var setPlaybackSpeedMethodIndex = -1
// Used by other patches. // Used by other patches.
lateinit var setPlaybackSpeedContainerClassFieldReference: String internal lateinit var setPlaybackSpeedContainerClassFieldReference: FieldReference
private set private set
lateinit var setPlaybackSpeedClassFieldReference: String internal lateinit var setPlaybackSpeedClassFieldReference: FieldReference
private set private set
lateinit var setPlaybackSpeedMethodReference: String internal lateinit var setPlaybackSpeedMethodReference: MethodReference
private set private set
val videoInformationPatch = bytecodePatch( val videoInformationPatch = bytecodePatch(
@ -164,24 +168,27 @@ val videoInformationPatch = bytecodePatch(
videoTimeHook(EXTENSION_CLASS_DESCRIPTOR, "setVideoTime") videoTimeHook(EXTENSION_CLASS_DESCRIPTOR, "setVideoTime")
/* /*
* Hook the user playback speed selection * Hook the user playback speed selection.
*/ */
onPlaybackSpeedItemClickFingerprint.method.apply { onPlaybackSpeedItemClickFingerprint.method.apply {
val speedSelectionMethodInstructions = implementation!!.instructions val speedSelectionValueInstructionIndex = indexOfFirstInstructionOrThrow(Opcode.IGET)
val speedSelectionValueInstructionIndex = speedSelectionMethodInstructions.indexOfFirst {
it.opcode == Opcode.IGET
}
legacySpeedSelectionInsertMethod = this legacySpeedSelectionInsertMethod = this
legacySpeedSelectionInsertIndex = speedSelectionValueInstructionIndex + 1 legacySpeedSelectionInsertIndex = speedSelectionValueInstructionIndex + 1
legacySpeedSelectionValueRegister = legacySpeedSelectionValueRegister =
getInstruction<TwoRegisterInstruction>(speedSelectionValueInstructionIndex).registerA getInstruction<TwoRegisterInstruction>(speedSelectionValueInstructionIndex).registerA
setPlaybackSpeedClassFieldReference =
getInstruction<ReferenceInstruction>(speedSelectionValueInstructionIndex + 1).reference.toString()
setPlaybackSpeedMethodReference = setPlaybackSpeedMethodReference =
getInstruction<ReferenceInstruction>(speedSelectionValueInstructionIndex + 2).reference.toString() getInstruction<ReferenceInstruction>(speedSelectionValueInstructionIndex + 2).reference as MethodReference
setPlaybackSpeedClassFieldReference =
getInstruction<ReferenceInstruction>(speedSelectionValueInstructionIndex + 1).reference as FieldReference
setPlaybackSpeedContainerClassFieldReference = setPlaybackSpeedContainerClassFieldReference =
getReference(speedSelectionMethodInstructions, -1, Opcode.IF_EQZ) getInstruction<ReferenceInstruction>(indexOfFirstInstructionOrThrow(Opcode.IF_EQZ) - 1).reference as FieldReference
setPlaybackSpeedMethod =
proxy(classes.first { it.type == setPlaybackSpeedMethodReference.definingClass })
.mutableClass.methods.first { it.name == setPlaybackSpeedMethodReference.name }
setPlaybackSpeedMethodIndex = 0
} }
// Handle new playback speed menu. // Handle new playback speed menu.
@ -195,6 +202,7 @@ val videoInformationPatch = bytecodePatch(
speedSelectionValueRegister = getInstruction<TwoRegisterInstruction>(index).registerA speedSelectionValueRegister = getInstruction<TwoRegisterInstruction>(index).registerA
} }
videoSpeedChangedHook(EXTENSION_CLASS_DESCRIPTOR, "videoSpeedChanged")
userSelectedPlaybackSpeedHook(EXTENSION_CLASS_DESCRIPTOR, "userSelectedPlaybackSpeed") userSelectedPlaybackSpeedHook(EXTENSION_CLASS_DESCRIPTOR, "userSelectedPlaybackSpeed")
} }
} }
@ -295,9 +303,14 @@ fun videoTimeHook(targetMethodClass: String, targetMethodName: String) =
"$targetMethodClass->$targetMethodName(J)V", "$targetMethodClass->$targetMethodName(J)V",
) )
private fun getReference(instructions: List<BuilderInstruction>, offset: Int, opcode: Opcode) = /**
(instructions[instructions.indexOfFirst { it.opcode == opcode } + offset] as ReferenceInstruction) * Hook when the video speed is changed for any reason _except when the user manually selects a new speed_.
.reference.toString() */
fun videoSpeedChangedHook(targetMethodClass: String, targetMethodName: String) =
setPlaybackSpeedMethod.addInstruction(
setPlaybackSpeedMethodIndex++,
"invoke-static { p1 }, $targetMethodClass->$targetMethodName(F)V"
)
/** /**
* Hook the video speed selected by the user. * Hook the video speed selected by the user.
@ -312,4 +325,4 @@ fun userSelectedPlaybackSpeedHook(targetMethodClass: String, targetMethodName: S
speedSelectionInsertIndex++, speedSelectionInsertIndex++,
"invoke-static { v$speedSelectionValueRegister }, $targetMethodClass->$targetMethodName(F)V", "invoke-static { v$speedSelectionValueRegister }, $targetMethodClass->$targetMethodName(F)V",
) )
} }

View File

@ -42,6 +42,7 @@ val rememberVideoQualityPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -26,6 +26,7 @@ val playbackSpeedPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )
} }

View File

@ -81,6 +81,7 @@ val restoreOldVideoQualityMenuPatch = bytecodePatch(
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.45.38", "19.45.38",
"19.46.42",
), ),
) )

View File

@ -114,7 +114,6 @@ fun String.copyXmlNode(
target: Document, target: Document,
): AutoCloseable { ): AutoCloseable {
val hostNodes = source.getElementsByTagName(this).item(0).childNodes val hostNodes = source.getElementsByTagName(this).item(0).childNodes
val destinationNode = target.getElementsByTagName(this).item(0) val destinationNode = target.getElementsByTagName(this).item(0)
for (index in 0 until hostNodes.length) { for (index in 0 until hostNodes.length) {

View File

@ -36,6 +36,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch> </patch>
<patch id="misc.settings.settingsResourcePatch"> <patch id="misc.settings.settingsResourcePatch">
<!-- Settings about dialog. --> <!-- Settings about dialog. -->
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->

View File

@ -36,6 +36,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch> </patch>
<patch id="misc.settings.settingsResourcePatch"> <patch id="misc.settings.settingsResourcePatch">
<!-- Settings about dialog. --> <!-- Settings about dialog. -->
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->

View File

@ -60,7 +60,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_about_links_dev_header">ملاحظة</string> <string name="revanced_settings_about_links_dev_header">ملاحظة</string>
<string name="revanced_settings_about_links_dev_body">هذا الإصدار هو إصدار مسبق، وقد تواجه مشاكل غير متوقعة</string> <string name="revanced_settings_about_links_dev_body">هذا الإصدار هو إصدار مسبق، وقد تواجه مشاكل غير متوقعة</string>
<string name="revanced_settings_about_links_header">الروابط الرسمية</string> <string name="revanced_settings_about_links_header">الروابط الرسمية</string>
<string name="revanced_settings_about_links_donate">تبرع</string> <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@ -1204,9 +1205,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">لا يحتوي جهازك على فك تشفير الأجهزة VP9، وهذا الإعداد يعمل دائما عند تمكين تزييف العميل</string> <string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">لا يحتوي جهازك على فك تشفير الأجهزة VP9، وهذا الإعداد يعمل دائما عند تمكين تزييف العميل</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">قد يؤدي تمكين هذا إلى تحسين عمر البطارية وإصلاح مشكلة تقطيع التشغيل.\n\nيتمتع تنسيق AVC بدقة قصوى تبلغ 1080P، وسيستخدم تشغيل الفيديو المزيد من بيانات الإنترنت مقارنةً بتنسيق VP9 أو AV1.</string> <string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">قد يؤدي تمكين هذا إلى تحسين عمر البطارية وإصلاح مشكلة تقطيع التشغيل.\n\nيتمتع تنسيق AVC بدقة قصوى تبلغ 1080P، وسيستخدم تشغيل الفيديو المزيد من بيانات الإنترنت مقارنةً بتنسيق VP9 أو AV1.</string>
<string name="revanced_spoof_video_streams_about_ios_title">التأثيرات الجانبية لمحاكاة iOS</string> <string name="revanced_spoof_video_streams_about_ios_title">التأثيرات الجانبية لمحاكاة iOS</string>
<string name="revanced_spoof_video_streams_about_ios_summary">• قد لا يتم تشغيل الأفلام أو الفيديوهات المدفوعة\n• تبدأ البثوث المباشرة من البداية\n• قد تنتهي الفيديوهات قبل النهاية بثانية واحدة\n• لا يوجد ترميز الصوت Opus</string> <string name="revanced_spoof_video_streams_about_ios_summary">• قد لا تشغل مقاطع فيديو الأطفال الخاصون\n• تبدأ Livestreams من البداية\n• قد تنتهي مقاطع الفيديو ثانية واحدة في أوائل\n• لا يوجد رمز صوتي opus</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">التأثيرات الجانبية لمحاكاة Android VR</string> <string name="revanced_spoof_video_streams_about_android_vr_title">التأثيرات الجانبية لمحاكاة Android VR</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">• قائمة المقطع الصوتي مفقودة\n• مستوى الصوت الثابت غير متوفر</string> <string name="revanced_spoof_video_streams_about_android_vr_summary">• قد لا تشغل مقاطع فيديو الأطفال\n• قائمة المسار الصوتي مفقودة\n• مستوى الصوت المستقر غير متوفر</string>
</patch> </patch>
</app> </app>
<app id="twitch"> <app id="twitch">

View File

@ -36,6 +36,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch> </patch>
<patch id="misc.settings.settingsResourcePatch"> <patch id="misc.settings.settingsResourcePatch">
<!-- Settings about dialog. --> <!-- Settings about dialog. -->
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->

View File

@ -60,7 +60,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_about_links_dev_header">Qeyd</string> <string name="revanced_settings_about_links_dev_header">Qeyd</string>
<string name="revanced_settings_about_links_dev_body">Bu versiya ilkin buraxılışdır və gözlənilməz problemlərlə üzləşə bilərsiniz</string> <string name="revanced_settings_about_links_dev_body">Bu versiya ilkin buraxılışdır və gözlənilməz problemlərlə üzləşə bilərsiniz</string>
<string name="revanced_settings_about_links_header">Rəsmi bağlantılar</string> <string name="revanced_settings_about_links_header">Rəsmi bağlantılar</string>
<string name="revanced_settings_about_links_donate">İanə ver</string> <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@ -1012,6 +1013,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_miniplayer_type_title">Kiçik oynadıcı növü</string> <string name="revanced_miniplayer_type_title">Kiçik oynadıcı növü</string>
<string name="revanced_miniplayer_type_entry_0">Qeyri-aktivdir</string> <string name="revanced_miniplayer_type_entry_0">Qeyri-aktivdir</string>
<string name="revanced_miniplayer_type_entry_1">Orijinal</string> <string name="revanced_miniplayer_type_entry_1">Orijinal</string>
<string name="revanced_miniplayer_type_entry_2">Ən kiçik</string>
<string name="revanced_miniplayer_type_entry_3">Planşet</string> <string name="revanced_miniplayer_type_entry_3">Planşet</string>
<string name="revanced_miniplayer_type_entry_4">Müasir 1</string> <string name="revanced_miniplayer_type_entry_4">Müasir 1</string>
<string name="revanced_miniplayer_type_entry_5">Müasir 2</string> <string name="revanced_miniplayer_type_entry_5">Müasir 2</string>
@ -1203,9 +1205,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Cihazınızın VP9 hardware decoding\'i yoxdur və bu seçim, \"Qəbuledicini saxtalaşdırma\" aktivləşdikdə həmişəlikdir</string> <string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Cihazınızın VP9 hardware decoding\'i yoxdur və bu seçim, \"Qəbuledicini saxtalaşdırma\" aktivləşdikdə həmişəlikdir</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Bunu aktivləşdirmə batareya ömrünü yaxşılaşdıra və oynatma donmasını düzəldə bilər.\n\nAVC maksimum 1080p görüntü imkanına malikdir və video oynadılması VP9 və ya AV1-dən daha çox internet məlumatı istifadə edəcək.</string> <string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Bunu aktivləşdirmə batareya ömrünü yaxşılaşdıra və oynatma donmasını düzəldə bilər.\n\nAVC maksimum 1080p görüntü imkanına malikdir və video oynadılması VP9 və ya AV1-dən daha çox internet məlumatı istifadə edəcək.</string>
<string name="revanced_spoof_video_streams_about_ios_title">iOS saxtakarlığı yan təsirləri</string> <string name="revanced_spoof_video_streams_about_ios_title">iOS saxtakarlığı yan təsirləri</string>
<string name="revanced_spoof_video_streams_about_ios_summary">• Filmlər və ya ödənişli videolar oynadılmaya bilər\n• Canlı yayımlar əvvəldən başlayır\n• Videolar 1 saniyə tez bitə bilər\n• Opus səs kodlama yoxdur</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">Android VR saxtakarlığı yan təsirləri</string> <string name="revanced_spoof_video_streams_about_android_vr_title">Android VR saxtakarlığı yan təsirləri</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">• Səs axını menyusu əskikdir\n• Stabil səs səviyyəsi əlçatan deyil</string>
</patch> </patch>
</app> </app>
<app id="twitch"> <app id="twitch">

View File

@ -51,6 +51,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_about_links_dev_header">Нататка</string> <string name="revanced_settings_about_links_dev_header">Нататка</string>
<string name="revanced_settings_about_links_dev_body">Гэтая версія з\"яўляецца папярэдняй версіяй, і вы можаце сутыкнуцца з непрадбачанымі праблемамі</string> <string name="revanced_settings_about_links_dev_body">Гэтая версія з\"яўляецца папярэдняй версіяй, і вы можаце сутыкнуцца з непрадбачанымі праблемамі</string>
<string name="revanced_settings_about_links_header">Афіцыйныя спасылкі</string> <string name="revanced_settings_about_links_header">Афіцыйныя спасылкі</string>
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->

View File

@ -60,7 +60,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_about_links_dev_header">Забележка</string> <string name="revanced_settings_about_links_dev_header">Забележка</string>
<string name="revanced_settings_about_links_dev_body">Тази версия е предварителна, така че може да срещнете неочаквани проблеми</string> <string name="revanced_settings_about_links_dev_body">Тази версия е предварителна, така че може да срещнете неочаквани проблеми</string>
<string name="revanced_settings_about_links_header">Официални линкове</string> <string name="revanced_settings_about_links_header">Официални линкове</string>
<string name="revanced_settings_about_links_donate">Дарение</string> <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@ -1012,6 +1013,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_miniplayer_type_title">Минимизиран тип екран за гледане</string> <string name="revanced_miniplayer_type_title">Минимизиран тип екран за гледане</string>
<string name="revanced_miniplayer_type_entry_0">Деактивирано</string> <string name="revanced_miniplayer_type_entry_0">Деактивирано</string>
<string name="revanced_miniplayer_type_entry_1">Оригинал</string> <string name="revanced_miniplayer_type_entry_1">Оригинал</string>
<string name="revanced_miniplayer_type_entry_2">Минимално</string>
<string name="revanced_miniplayer_type_entry_3">Таблет</string> <string name="revanced_miniplayer_type_entry_3">Таблет</string>
<string name="revanced_miniplayer_type_entry_4">Модерен 1</string> <string name="revanced_miniplayer_type_entry_4">Модерен 1</string>
<string name="revanced_miniplayer_type_entry_5">Модерен 2</string> <string name="revanced_miniplayer_type_entry_5">Модерен 2</string>
@ -1203,9 +1205,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Вашето устройство няма хардуерно VP9 декодиране и тази настройка винаги е активирана, когато е активно подправяне на клиента</string> <string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Вашето устройство няма хардуерно VP9 декодиране и тази настройка винаги е активирана, когато е активно подправяне на клиента</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Активирането на това може да подобри живота на батерията и да коригира прекъсванията при възпроизвеждане.\n\nAVC има максимална разделителна способност от 1080p и възпроизвеждането на видео ще използва повече интернет данни от VP9 или AV1.</string> <string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Активирането на това може да подобри живота на батерията и да коригира прекъсванията при възпроизвеждане.\n\nAVC има максимална разделителна способност от 1080p и възпроизвеждането на видео ще използва повече интернет данни от VP9 или AV1.</string>
<string name="revanced_spoof_video_streams_about_ios_title">ранични ефекти от подмяната на iOS</string> <string name="revanced_spoof_video_streams_about_ios_title">ранични ефекти от подмяната на iOS</string>
<string name="revanced_spoof_video_streams_about_ios_summary">• Филми или платени видеоклипове може да не се възпроизвеждат\n• Потоците на живо започват отначало\n• Видеоклиповете може да завършват 1 секунда по-рано\n• Няма аудиокодек Opus</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">Странични ефекти от подправяне на Android VR</string> <string name="revanced_spoof_video_streams_about_android_vr_title">Странични ефекти от подправяне на Android VR</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">• Липсва менюто за избор аудио\n• Не е налична стабилна сила на звука</string>
</patch> </patch>
</app> </app>
<app id="twitch"> <app id="twitch">

View File

@ -59,7 +59,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_about_links_dev_header">দ্রষ্টব্য</string> <string name="revanced_settings_about_links_dev_header">দ্রষ্টব্য</string>
<string name="revanced_settings_about_links_dev_body">এই সংস্করণ একটি প্রাক-প্রকাশনা এবং এতে আপনি অনাকাঙ্খিত সমস্যার সম্মুখিন হতে পারেন</string> <string name="revanced_settings_about_links_dev_body">এই সংস্করণ একটি প্রাক-প্রকাশনা এবং এতে আপনি অনাকাঙ্খিত সমস্যার সম্মুখিন হতে পারেন</string>
<string name="revanced_settings_about_links_header">অফিশ্যাল লিংকসমূহ</string> <string name="revanced_settings_about_links_header">অফিশ্যাল লিংকসমূহ</string>
<string name="revanced_settings_about_links_donate">দান করুন</string> <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->

View File

@ -36,6 +36,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch> </patch>
<patch id="misc.settings.settingsResourcePatch"> <patch id="misc.settings.settingsResourcePatch">
<!-- Settings about dialog. --> <!-- Settings about dialog. -->
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->

View File

@ -37,6 +37,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<patch id="misc.settings.settingsResourcePatch"> <patch id="misc.settings.settingsResourcePatch">
<string name="revanced_settings_reset">Restablir</string> <string name="revanced_settings_reset">Restablir</string>
<!-- Settings about dialog. --> <!-- Settings about dialog. -->
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->

View File

@ -61,7 +61,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_about_links_dev_header">Poznámka</string> <string name="revanced_settings_about_links_dev_header">Poznámka</string>
<string name="revanced_settings_about_links_dev_body">Tato verze je předběžná verze a můžete zaznamenat neočekávané problémy</string> <string name="revanced_settings_about_links_dev_body">Tato verze je předběžná verze a můžete zaznamenat neočekávané problémy</string>
<string name="revanced_settings_about_links_header">Oficiální odkazy</string> <string name="revanced_settings_about_links_header">Oficiální odkazy</string>
<string name="revanced_settings_about_links_donate">Přispět</string> <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@ -1205,9 +1206,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Vaše zařízení nemá hardwarové dekódování VP9 a toto nastavení je vždy zapnuto, když je aktivní spoofování klienta</string> <string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Vaše zařízení nemá hardwarové dekódování VP9 a toto nastavení je vždy zapnuto, když je aktivní spoofování klienta</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Povolení této funkce může zlepšit životnost baterie a opravit stahování přehrávání.\n\nAVC má maximální rozlišení 1080p a video přehrávání bude používat více dat než VP9 nebo AV1.</string> <string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Povolení této funkce může zlepšit životnost baterie a opravit stahování přehrávání.\n\nAVC má maximální rozlišení 1080p a video přehrávání bude používat více dat než VP9 nebo AV1.</string>
<string name="revanced_spoof_video_streams_about_ios_title">Boční efekty iOS</string> <string name="revanced_spoof_video_streams_about_ios_title">Boční efekty iOS</string>
<string name="revanced_spoof_video_streams_about_ios_summary">Filmy nebo placená videa nemusí přehrávat\n• Živočišné rekony začínající od začátku\n• Videa mohou skončit 1 sekundu na začátku\n• Žádný opus audio kodek</string> <string name="revanced_spoof_video_streams_about_ios_summary">Soukromá dětská videa nemusí přehrávat\n• Živočišná zvířata začínající od začátku\n• Videa mohou skončit 1 sekundu na začátku\n• Žádný opus zvukový kodek</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">Boční efekty Android VR</string> <string name="revanced_spoof_video_streams_about_android_vr_title">Boční efekty Android VR</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">• Menu zvukové stopy chybí\n• Stabilní hlasitost není k dispozici</string> <string name="revanced_spoof_video_streams_about_android_vr_summary">Dětská videa nemusí přehrát\n• Menu zvukové stopy chybí\n• Stabilní hlasitost není k dispozici</string>
</patch> </patch>
</app> </app>
<app id="twitch"> <app id="twitch">

View File

@ -61,7 +61,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_about_links_dev_header">Bemærkning</string> <string name="revanced_settings_about_links_dev_header">Bemærkning</string>
<string name="revanced_settings_about_links_dev_body">Denne version er en pre-release og du kan opleve uventede problemer</string> <string name="revanced_settings_about_links_dev_body">Denne version er en pre-release og du kan opleve uventede problemer</string>
<string name="revanced_settings_about_links_header">Officielle links</string> <string name="revanced_settings_about_links_header">Officielle links</string>
<string name="revanced_settings_about_links_donate">Donér</string> <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@ -1187,9 +1188,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Din enhed har ikke VP9 hardwareafkodning, og denne indstilling er altid tændt, når Client spoofing er aktiveret</string> <string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Din enhed har ikke VP9 hardwareafkodning, og denne indstilling er altid tændt, når Client spoofing er aktiveret</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Aktivering af dette kan forbedre batteriets levetid og rette afspilningsstuttering.\n\nAVC har en maksimal opløsning på 1080p, og videoafspilning vil bruge flere internetdata end VP9 eller AV1.</string> <string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Aktivering af dette kan forbedre batteriets levetid og rette afspilningsstuttering.\n\nAVC har en maksimal opløsning på 1080p, og videoafspilning vil bruge flere internetdata end VP9 eller AV1.</string>
<string name="revanced_spoof_video_streams_about_ios_title">iOS forfalskning bivirkninger</string> <string name="revanced_spoof_video_streams_about_ios_title">iOS forfalskning bivirkninger</string>
<string name="revanced_spoof_video_streams_about_ios_summary">Film eller betalte videoer kan ikke afspille\n• Livestreams starter fra begyndelsen\n• Videoer kan ende 1 sekund tidlig\n• Ingen opus lydkode</string> <string name="revanced_spoof_video_streams_about_ios_summary">Private børn videoer kan ikke afspille\n• Livestreams starter fra begyndelsen\n• Videoer kan ende 1 sekund tidlig\n• Ingen opus audio codec</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">Android VR spoofing bivirkninger</string> <string name="revanced_spoof_video_streams_about_android_vr_title">Android VR spoofing bivirkninger</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">Menuen Lydspor mangler\n• Stabil lydstyrke er ikke tilgængelig</string> <string name="revanced_spoof_video_streams_about_android_vr_summary">Kids videoer afspiller muligvis ikke\n• Menuen for lydspor mangler\n• Stabil lydstyrke er ikke tilgængelig</string>
</patch> </patch>
</app> </app>
<app id="twitch"> <app id="twitch">

View File

@ -33,7 +33,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<resources> <resources>
<app id="shared"> <app id="shared">
<patch id="misc.checks.checkEnvironmentPatch"> <patch id="misc.checks.checkEnvironmentPatch">
<string name="revanced_check_environment_failed_title">Überprüfungen fehlgeschlagen</string> <string name="revanced_check_environment_failed_title">Überprüfung fehlgeschlagen</string>
<string name="revanced_check_environment_dialog_open_official_source_button">Offizielle Webseite öffnen</string> <string name="revanced_check_environment_dialog_open_official_source_button">Offizielle Webseite öffnen</string>
<string name="revanced_check_environment_dialog_ignore_button">Ignorieren</string> <string name="revanced_check_environment_dialog_ignore_button">Ignorieren</string>
<string name="revanced_check_environment_failed_message">&lt;h5&gt;Diese App wurde offenbar nicht von Ihnen gepatcht.&lt;/h5&gt;&lt;br&gt;Diese App funktioniert möglicherweise nicht richtig, &lt;b&gt;könnte schädlich oder sogar gefährlich in der Verwendung sein&lt;/b&gt;.&lt; br&gt;&lt;br&gt;Diese Prüfungen deuten darauf hin, dass diese App vorab gepatcht wurde oder von jemandem bezogen wurde sonst:&lt;br&gt;&lt;br&gt;&lt;small&gt;%1$s&lt;/small&gt;&lt;br&gt;Es wird dringend empfohlen, &lt;b&gt;diese App zu deinstallieren und selbst zu patchen&lt;/b&gt; um sicherzustellen, dass Sie eine validierte und sichere App verwenden.&lt;p&gt;&lt;br&gt;Wenn Sie diese Warnung ignorieren, wird sie nur zweimal angezeigt.</string> <string name="revanced_check_environment_failed_message">&lt;h5&gt;Diese App wurde offenbar nicht von Ihnen gepatcht.&lt;/h5&gt;&lt;br&gt;Diese App funktioniert möglicherweise nicht richtig, &lt;b&gt;könnte schädlich oder sogar gefährlich in der Verwendung sein&lt;/b&gt;.&lt; br&gt;&lt;br&gt;Diese Prüfungen deuten darauf hin, dass diese App vorab gepatcht wurde oder von jemandem bezogen wurde sonst:&lt;br&gt;&lt;br&gt;&lt;small&gt;%1$s&lt;/small&gt;&lt;br&gt;Es wird dringend empfohlen, &lt;b&gt;diese App zu deinstallieren und selbst zu patchen&lt;/b&gt; um sicherzustellen, dass Sie eine validierte und sichere App verwenden.&lt;p&gt;&lt;br&gt;Wenn Sie diese Warnung ignorieren, wird sie nur zweimal angezeigt.</string>
@ -61,7 +61,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_about_links_dev_header">Notiz</string> <string name="revanced_settings_about_links_dev_header">Notiz</string>
<string name="revanced_settings_about_links_dev_body">Diese Version ist eine Vorabversion und du könntest unerwartete Probleme haben</string> <string name="revanced_settings_about_links_dev_body">Diese Version ist eine Vorabversion und du könntest unerwartete Probleme haben</string>
<string name="revanced_settings_about_links_header">Offizielle Links</string> <string name="revanced_settings_about_links_header">Offizielle Links</string>
<string name="revanced_settings_about_links_donate">Spenden</string> <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@ -1205,9 +1206,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Ihr Gerät hat keine VP9-Hardware-Dekodierung, und diese Einstellung ist immer aktiviert, wenn Client-Spoofing aktiviert ist</string> <string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Ihr Gerät hat keine VP9-Hardware-Dekodierung, und diese Einstellung ist immer aktiviert, wenn Client-Spoofing aktiviert ist</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Das Aktivieren kann die Akkulaufzeit verbessern und die Wiedergabe-Stutting beheben.\n\nAVC hat eine maximale Auflösung von 1080p, und die Videowiedergabe wird mehr Internet-Daten als VP9 oder AV1 verwenden.</string> <string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Das Aktivieren kann die Akkulaufzeit verbessern und die Wiedergabe-Stutting beheben.\n\nAVC hat eine maximale Auflösung von 1080p, und die Videowiedergabe wird mehr Internet-Daten als VP9 oder AV1 verwenden.</string>
<string name="revanced_spoof_video_streams_about_ios_title">iOS Spoofing Nebeneffekte</string> <string name="revanced_spoof_video_streams_about_ios_title">iOS Spoofing Nebeneffekte</string>
<string name="revanced_spoof_video_streams_about_ios_summary">Filme oder bezahlte Videos werden möglicherweise nicht abgespielt\n• Livestreams starten von Anfang an\n• Videos enden möglicherweise 1 Sekunde früher\n• kein Opus-Audiocodec</string> <string name="revanced_spoof_video_streams_about_ios_summary">Videos für Privatkinder dürfen nicht\nabspielen • Livestreams beginnen von Anfang an\n• Videos können 1 Sekunde früher\n• Kein opus Audio Codec</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">Android VR Spoofing Nebeneffekte</string> <string name="revanced_spoof_video_streams_about_android_vr_title">Android VR Spoofing Nebeneffekte</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">Audio Track Menü fehlt\n• Stabile Lautstärke ist nicht verfügbar</string> <string name="revanced_spoof_video_streams_about_android_vr_summary">Kindervideos dürfen nicht\nabspielen • Audiospurmenü fehlt\n• Stabile Lautstärke ist nicht verfügbar</string>
</patch> </patch>
</app> </app>
<app id="twitch"> <app id="twitch">

View File

@ -60,7 +60,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_about_links_dev_header">Σημείωση</string> <string name="revanced_settings_about_links_dev_header">Σημείωση</string>
<string name="revanced_settings_about_links_dev_body">Αυτή η έκδοση είναι σε πρώιμο στάδιο, επομένως πιθανότατα να αντιμετωπίσετε απρόοπτα προβλήματα</string> <string name="revanced_settings_about_links_dev_body">Αυτή η έκδοση είναι σε πρώιμο στάδιο, επομένως πιθανότατα να αντιμετωπίσετε απρόοπτα προβλήματα</string>
<string name="revanced_settings_about_links_header">Επίσημοι σύνδεσμοι</string> <string name="revanced_settings_about_links_header">Επίσημοι σύνδεσμοι</string>
<string name="revanced_settings_about_links_donate">Δωρεά</string> <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@ -1010,13 +1011,13 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_miniplayer_screen_title">Ελαχιστοποιημένη οθόνη αναπαραγωγής</string> <string name="revanced_miniplayer_screen_title">Ελαχιστοποιημένη οθόνη αναπαραγωγής</string>
<string name="revanced_miniplayer_screen_summary">Αλλάξτε το στυλ της ελαχιστοποιημένης οθόνης αναπαραγωγής</string> <string name="revanced_miniplayer_screen_summary">Αλλάξτε το στυλ της ελαχιστοποιημένης οθόνης αναπαραγωγής</string>
<string name="revanced_miniplayer_type_title">Τύπος ελαχιστοποιημένης οθόνης αναπαραγωγής</string> <string name="revanced_miniplayer_type_title">Τύπος ελαχιστοποιημένης οθόνης αναπαραγωγής</string>
<string name="revanced_miniplayer_type_entry_0">Ανενεργό</string> <string name="revanced_miniplayer_type_entry_0">Ανενεργή</string>
<string name="revanced_miniplayer_type_entry_1">Αρχικός</string> <string name="revanced_miniplayer_type_entry_1">Αρχικός</string>
<string name="revanced_miniplayer_type_entry_2">Ελάχιστα</string> <string name="revanced_miniplayer_type_entry_2">Ελάχιστη</string>
<string name="revanced_miniplayer_type_entry_3">Ταμπλετ</string> <string name="revanced_miniplayer_type_entry_3">Ταμπλετ</string>
<string name="revanced_miniplayer_type_entry_4">Μοντέρνος 1</string> <string name="revanced_miniplayer_type_entry_4">Μοντέρνα 1</string>
<string name="revanced_miniplayer_type_entry_5">Μοντέρνος 2</string> <string name="revanced_miniplayer_type_entry_5">Μοντέρνα 2</string>
<string name="revanced_miniplayer_type_entry_6">Μοντέρνος 3</string> <string name="revanced_miniplayer_type_entry_6">Μοντέρνα 3</string>
<string name="revanced_miniplayer_rounded_corners_title">Στρογγυλεμένες γωνίες</string> <string name="revanced_miniplayer_rounded_corners_title">Στρογγυλεμένες γωνίες</string>
<string name="revanced_miniplayer_rounded_corners_summary_on">Οι γωνίες είναι στρογγυλεμένες</string> <string name="revanced_miniplayer_rounded_corners_summary_on">Οι γωνίες είναι στρογγυλεμένες</string>
<string name="revanced_miniplayer_rounded_corners_summary_off">Οι γωνίες είναι τετράγωνες</string> <string name="revanced_miniplayer_rounded_corners_summary_off">Οι γωνίες είναι τετράγωνες</string>
@ -1204,9 +1205,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Η συσκευή σας δεν διαθέτει αποκωδικοποίηση υλικού VP9, και αυτή η ρύθμιση είναι πάντα ενεργή όταν είναι ενεργοποιημένη η παραποίηση προγράμματος πελάτη</string> <string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Η συσκευή σας δεν διαθέτει αποκωδικοποίηση υλικού VP9, και αυτή η ρύθμιση είναι πάντα ενεργή όταν είναι ενεργοποιημένη η παραποίηση προγράμματος πελάτη</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Ενεργοποιώντας αυτόν τον κωδικοποιητή ίσως να βελτιώσει κατανάλωση ενέργειας και ίσως διορθώσει κολλήματα αναπαραγωγής.\n\nΟ AVC έχει μέγιστη ανάλυση 1080p, και καταναλώνει περισσότερα δεδομένα internet από τον VP9 ή τον AV1.</string> <string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Ενεργοποιώντας αυτόν τον κωδικοποιητή ίσως να βελτιώσει κατανάλωση ενέργειας και ίσως διορθώσει κολλήματα αναπαραγωγής.\n\nΟ AVC έχει μέγιστη ανάλυση 1080p, και καταναλώνει περισσότερα δεδομένα internet από τον VP9 ή τον AV1.</string>
<string name="revanced_spoof_video_streams_about_ios_title">Παρενέργειες παραποίησης σε iOS</string> <string name="revanced_spoof_video_streams_about_ios_title">Παρενέργειες παραποίησης σε iOS</string>
<string name="revanced_spoof_video_streams_about_ios_summary">Οι ταινίες ή τα επί πληρωμή βίντεο ενδέχεται να μην αναπαράγονται\n• Οι ζωντανές μεταδόσεις ξεκινούν από την αρχή κατά την αναπαραγωγή\n• Τα βίντεο μπορεί να τελειώνουν 1 δευτερόλεπτο νωρίτερα\n• Ο κωδικοποιητής ήχου opus δεν είναι διαθέσιμος</string> <string name="revanced_spoof_video_streams_about_ios_summary">Τα ιδιωτικά βίντεο για παιδιά μπορεί να μην παίζουν\n• Livestreams ξεκινούν από την αρχή\n• Τα βίντεο μπορεί να λήξουν 1 δευτερόλεπτο\n• Δεν υπάρχει κωδικοποιητής ήχου opus</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">Παρενέργειες παραποίησης σε Android VR</string> <string name="revanced_spoof_video_streams_about_android_vr_title">Παρενέργειες παραποίησης σε Android VR</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">Το μενού «Κομμάτι ήχου» λείπει\n• Η λειτουργία «Σταθερή ένταση» δεν είναι διαθέσιμη</string> <string name="revanced_spoof_video_streams_about_android_vr_summary">Τα βίντεο για παιδιά μπορεί να μην αναπαράγονται\n• Το μενού κομματιών ήχου λείπει\n• Η σταθερή ένταση δεν είναι διαθέσιμη</string>
</patch> </patch>
</app> </app>
<app id="twitch"> <app id="twitch">

View File

@ -60,7 +60,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_about_links_dev_header">Nota</string> <string name="revanced_settings_about_links_dev_header">Nota</string>
<string name="revanced_settings_about_links_dev_body">Esta versión es un pre-lanzamiento y puede que experimentes problemas inesperados</string> <string name="revanced_settings_about_links_dev_body">Esta versión es un pre-lanzamiento y puede que experimentes problemas inesperados</string>
<string name="revanced_settings_about_links_header">Enlaces oficiales</string> <string name="revanced_settings_about_links_header">Enlaces oficiales</string>
<string name="revanced_settings_about_links_donate">Donar</string> <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@ -1204,9 +1205,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Su dispositivo no tiene decodificación de hardware VP9, y esta configuración siempre está encendida cuando el cliente spoofing está habilitado</string> <string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Su dispositivo no tiene decodificación de hardware VP9, y esta configuración siempre está encendida cuando el cliente spoofing está habilitado</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Activar esto puede mejorar la vida de la batería y corregir el retraso en la reproducción.\n\nAVC tiene una resolución máxima de 1080p, y la reproducción de vídeo utilizará más datos de Internet que VP9 o AV1.</string> <string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Activar esto puede mejorar la vida de la batería y corregir el retraso en la reproducción.\n\nAVC tiene una resolución máxima de 1080p, y la reproducción de vídeo utilizará más datos de Internet que VP9 o AV1.</string>
<string name="revanced_spoof_video_streams_about_ios_title">Efectos secundarios para la falsificación de iOS</string> <string name="revanced_spoof_video_streams_about_ios_title">Efectos secundarios para la falsificación de iOS</string>
<string name="revanced_spoof_video_streams_about_ios_summary">• Las películas o vídeos de pago pueden no reproducirse\n• Los directos empiezan desde el principio\n• Los vídeos pueden terminar 1 segundo antes\n• No hay códec de audio de Opus</string> <string name="revanced_spoof_video_streams_about_ios_summary">• Los vídeos de niños privados no pueden reproducir\n• Livestreams comienzan desde el principio\n• Los vídeos pueden terminar 1 segundo antes de\n• No hay código de audio de opus</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">Efectos secundarios para la falsificación de Android RV</string> <string name="revanced_spoof_video_streams_about_android_vr_title">Efectos secundarios para la falsificación de Android RV</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">• Falta el menú de pista de audio\n• El volumen estable no está disponible</string> <string name="revanced_spoof_video_streams_about_android_vr_summary">Los vídeos infantiles no pueden reproducir\n• Falta el menú de pista de audio\n• El volumen estable no está disponible</string>
</patch> </patch>
</app> </app>
<app id="twitch"> <app id="twitch">

View File

@ -36,6 +36,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch> </patch>
<patch id="misc.settings.settingsResourcePatch"> <patch id="misc.settings.settingsResourcePatch">
<!-- Settings about dialog. --> <!-- Settings about dialog. -->
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->

View File

@ -36,6 +36,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch> </patch>
<patch id="misc.settings.settingsResourcePatch"> <patch id="misc.settings.settingsResourcePatch">
<!-- Settings about dialog. --> <!-- Settings about dialog. -->
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->

View File

@ -36,6 +36,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
</patch> </patch>
<patch id="misc.settings.settingsResourcePatch"> <patch id="misc.settings.settingsResourcePatch">
<!-- Settings about dialog. --> <!-- Settings about dialog. -->
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->

View File

@ -61,7 +61,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_about_links_dev_header">Huomautus</string> <string name="revanced_settings_about_links_dev_header">Huomautus</string>
<string name="revanced_settings_about_links_dev_body">Tämä versio on ennakkojulkaisuversio, joten voit kokea odottamattomia ongelmia</string> <string name="revanced_settings_about_links_dev_body">Tämä versio on ennakkojulkaisuversio, joten voit kokea odottamattomia ongelmia</string>
<string name="revanced_settings_about_links_header">Viralliset linkit</string> <string name="revanced_settings_about_links_header">Viralliset linkit</string>
<string name="revanced_settings_about_links_donate">Lahjoita</string> <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
@ -293,7 +294,7 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_hide_keyword_content_subscriptions_title">Piilota tilaukset-videoita avainsanojen mukaan</string> <string name="revanced_hide_keyword_content_subscriptions_title">Piilota tilaukset-videoita avainsanojen mukaan</string>
<string name="revanced_hide_keyword_content_subscriptions_summary_on">Tilaukset-välilehden videoita suodatetaan avainsanojen mukaan</string> <string name="revanced_hide_keyword_content_subscriptions_summary_on">Tilaukset-välilehden videoita suodatetaan avainsanojen mukaan</string>
<string name="revanced_hide_keyword_content_subscriptions_summary_off">Tilaukset-välilehden videoita ei suodateta avainsanojen mukaan</string> <string name="revanced_hide_keyword_content_subscriptions_summary_off">Tilaukset-välilehden videoita ei suodateta avainsanojen mukaan</string>
<string name="revanced_hide_keyword_content_search_title">Piilota hakutuloksia avainsanojen muakan</string> <string name="revanced_hide_keyword_content_search_title">Piilota hakutuloksia avainsanojen mukaan</string>
<string name="revanced_hide_keyword_content_search_summary_on">Hakutuloksia suodatetaan avainsanojen mukaan</string> <string name="revanced_hide_keyword_content_search_summary_on">Hakutuloksia suodatetaan avainsanojen mukaan</string>
<string name="revanced_hide_keyword_content_search_summary_off">Hakutuloksia ei suodateta avainsanojen mukaan</string> <string name="revanced_hide_keyword_content_search_summary_off">Hakutuloksia ei suodateta avainsanojen mukaan</string>
<string name="revanced_hide_keyword_content_phrases_title">Piilotettavat avainsanat</string> <string name="revanced_hide_keyword_content_phrases_title">Piilotettavat avainsanat</string>
@ -1166,11 +1167,11 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_playback_speed_dialog_button_summary_off">Painiketta ei näytetä</string> <string name="revanced_playback_speed_dialog_button_summary_off">Painiketta ei näytetä</string>
</patch> </patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch"> <patch id="video.speed.custom.customPlaybackSpeedPatch">
<string name="revanced_custom_speed_menu_title">Mukautettu soiton nopeusvalikko</string> <string name="revanced_custom_speed_menu_title">Mukautetun toistonopeuden valikko</string>
<string name="revanced_custom_speed_menu_summary_on">Mukautettu nopeusvalikko näkyy</string> <string name="revanced_custom_speed_menu_summary_on">Mukautettu nopeusvalikko näkyy</string>
<string name="revanced_custom_speed_menu_summary_off">Mukautettua nopeusvalikkoa ei näytetä</string> <string name="revanced_custom_speed_menu_summary_off">Mukautettua nopeusvalikkoa ei näytetä</string>
<string name="revanced_custom_playback_speeds_title">Mukautetut toistonopeudet</string> <string name="revanced_custom_playback_speeds_title">Mukautetut toistonopeudet</string>
<string name="revanced_custom_playback_speeds_summary">Lisää tai muuta mukautettuja soiton nopeuksia</string> <string name="revanced_custom_playback_speeds_summary">Lisää tai muuta mukautettuja toistonopeuksia</string>
<string name="revanced_custom_playback_speeds_invalid">Mukautettujen nopeuksien on oltava alle %s. Käytetään oletusarvoja.</string> <string name="revanced_custom_playback_speeds_invalid">Mukautettujen nopeuksien on oltava alle %s. Käytetään oletusarvoja.</string>
<string name="revanced_custom_playback_speeds_parse_exception">Virheelliset mukautetut toistonopeudet. Käytetään oletusarvoja.</string> <string name="revanced_custom_playback_speeds_parse_exception">Virheelliset mukautetut toistonopeudet. Käytetään oletusarvoja.</string>
<string name="revanced_custom_playback_speeds_auto">Automaattinen</string> <string name="revanced_custom_playback_speeds_auto">Automaattinen</string>
@ -1206,9 +1207,9 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Laitteessasi ei ole VP9-laitteiston dekoodausta, ja tämä asetus on aina päällä, kun asiakkaan spoofing on käytössä</string> <string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Laitteessasi ei ole VP9-laitteiston dekoodausta, ja tämä asetus on aina päällä, kun asiakkaan spoofing on käytössä</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Tämän käyttöönotto voi parantaa akun kestoa ja korjata toistoa stuttering.\n\nAVC on suurin resoluutio 1080p, ja videon toisto käyttää enemmän internet-tietoja kuin VP9 tai AV1.</string> <string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Tämän käyttöönotto voi parantaa akun kestoa ja korjata toistoa stuttering.\n\nAVC on suurin resoluutio 1080p, ja videon toisto käyttää enemmän internet-tietoja kuin VP9 tai AV1.</string>
<string name="revanced_spoof_video_streams_about_ios_title">iOS-naamioinnin haittavaikutukset</string> <string name="revanced_spoof_video_streams_about_ios_title">iOS-naamioinnin haittavaikutukset</string>
<string name="revanced_spoof_video_streams_about_ios_summary">Elokuvat tai maksetut videot eivät välttämättä toistu\n• Suoratoistot alkavat alusta\n• Videot saattavat päättyä 1 sekuntia etuajassa\n• Ei Opus-äänikoodekkia</string> <string name="revanced_spoof_video_streams_about_ios_summary">Yksityiset lapset videot eivät välttämättä pelaa\n• Livestreams alusta alkaen\n• Videot saattavat päättyä 1 sekunnin alkupuolella\n• Ei opus-äänikoodekkia</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">Android VR -naamioinnin haittavaikutukset</string> <string name="revanced_spoof_video_streams_about_android_vr_title">Android VR -naamioinnin haittavaikutukset</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">Ääniraitavalikko puuttuu\n• Tasainen äänenvoimakkuus ei ole käytettävissä</string> <string name="revanced_spoof_video_streams_about_android_vr_summary">Lapset videot eivät välttämättä soita\n• Ääniraidan valikko puuttuu\n• Vakaa äänenvoimakkuus ei ole käytettävissä</string>
</patch> </patch>
</app> </app>
<app id="twitch"> <app id="twitch">

View File

@ -51,6 +51,8 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_settings_about_links_dev_header">Tandaan</string> <string name="revanced_settings_about_links_dev_header">Tandaan</string>
<string name="revanced_settings_about_links_dev_body">Ang bersyon na ito ay isang pre-release at maaari kang makaranas ng mga hindi inaasahang isyu</string> <string name="revanced_settings_about_links_dev_body">Ang bersyon na ito ay isang pre-release at maaari kang makaranas ng mga hindi inaasahang isyu</string>
<string name="revanced_settings_about_links_header">Mga opisyal na link</string> <string name="revanced_settings_about_links_header">Mga opisyal na link</string>
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->

Some files were not shown because too many files have changed in this diff Show More