feat(YouTube): Add Reload video patch & reduce patches bundle size

Co-Authored-By: Eric Ahn <w@chill.dev>
Co-Authored-By: inotia00 <108592928+inotia00@users.noreply.github.com>
This commit is contained in:
Aaron Veil 2026-03-24 15:31:15 +03:00
parent f81b531ce9
commit 33fdf51520
124 changed files with 1785 additions and 5892 deletions

View file

@ -6,23 +6,15 @@
-keep class app.morphe.** {
*;
}
-keep class com.google.** {
*;
}
-keep class com.aurora.** {
*;
}
-keep class com.dragons.** {
*;
}
-keep class com.eclipsesource.v8.** {
*;
}
-keep class com.liskovsoft.** {
*;
}
## Rules for OkHttp. Copy pasted from https://github.com/square/okhttp
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn java.lang.reflect.AnnotatedType
-dontwarn javax.lang.model.element.Modifier

View file

@ -1,5 +1,3 @@
import java.lang.Boolean.TRUE
plugins {
alias(libs.plugins.protobuf)
}
@ -10,21 +8,10 @@ extension {
android {
namespace = "app.morphe.extension"
compileSdk = 34
compileSdk = 35
defaultConfig {
minSdk = 24
}
buildTypes {
release {
isMinifyEnabled = TRUE
// 'libj2v8.so' is already included in the patch.
ndk {
abiFilters.add("")
}
}
minSdk = 26
}
compileOptions {
@ -37,15 +24,13 @@ dependencies {
compileOnly(libs.annotation)
compileOnly(libs.preference)
implementation(libs.androidx.javascriptengine)
implementation(libs.collections4)
implementation(libs.gson)
implementation(libs.lang3)
implementation(libs.okhttp3)
implementation(libs.protobuf.javalite)
//noinspection UseTomlInstead
implementation("com.github.ynab:J2V8:6.2.1-16kb.2@aar")
implementation(libs.nanohttpd)
implementation(libs.protobuf.javalite)

View file

@ -13,9 +13,6 @@
-keep class com.eclipsesource.v8.** {
*;
}
-keep class com.liskovsoft.** {
*;
}
## Rules for OkHttp. Copy pasted from https://github.com/square/okhttp
-dontwarn okhttp3.**

View file

@ -1,8 +1,9 @@
package app.morphe.extension.music.patches.spoof;
import static app.morphe.extension.music.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE;
import static app.morphe.extension.shared.spoof.ClientType.ANDROID_VR_1_47_48;
import static app.morphe.extension.shared.spoof.ClientType.ANDROID_VR_1_54_20;
import static app.morphe.extension.shared.spoof.ClientType.ANDROID_REEL;
import static app.morphe.extension.shared.spoof.ClientType.ANDROID_VR_1_64;
import static app.morphe.extension.shared.spoof.ClientType.ANDROID_VR_1_65;
import static app.morphe.extension.shared.spoof.ClientType.TV;
import static app.morphe.extension.shared.spoof.ClientType.VISIONOS;
@ -19,10 +20,11 @@ public class SpoofVideoStreamsPatch {
public static void setClientOrderToUse() {
// For some users No SDK can fail at 1 minute. Only use it if the user has explicitly set it.
List<ClientType> availableClients = List.of(
ANDROID_REEL,
TV,
ANDROID_VR_1_47_48,
ANDROID_VR_1_64,
VISIONOS,
ANDROID_VR_1_54_20
ANDROID_VR_1_65
);
app.morphe.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse(

View file

@ -31,7 +31,7 @@ import app.morphe.extension.shared.utils.Utils;
public class Settings extends SharedYouTubeSettings {
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE =
new EnumSetting<>("morphe_spoof_video_streams_client_type",
ClientType.ANDROID_VR_1_47_48, true, parent(SPOOF_VIDEO_STREAMS));
ClientType.ANDROID_REEL, true, parent(SPOOF_VIDEO_STREAMS));
// PreferenceScreen: Account
public static final BooleanSetting HIDE_ACCOUNT_MENU = new BooleanSetting("revanced_hide_account_menu", FALSE);
@ -323,7 +323,7 @@ public class Settings extends SharedYouTubeSettings {
SPOOF_APP_VERSION_FOR_LYRICS_TARGET.key,
SPOOF_APP_VERSION_TARGET.key,
SPOOF_VIDEO_STREAMS_CLIENT_TYPE.key,
SPOOF_VIDEO_STREAMS_PLAYER_JS_HASH.key,
SPOOF_VIDEO_STREAMS_PLAYER_JS_HASH_VALUE.key,
SPOOF_VIDEO_STREAMS_SIGN_IN_ANDROID_VR_ABOUT.key,
WATCH_HISTORY_TYPE.key,
};

View file

@ -29,7 +29,7 @@ import static app.morphe.extension.shared.patches.PatchStatus.PatchVersion;
import static app.morphe.extension.shared.settings.BaseSettings.RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT;
import static app.morphe.extension.shared.settings.BaseSettings.RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY;
import static app.morphe.extension.shared.settings.Setting.getSettingFromPath;
import static app.morphe.extension.shared.settings.SharedYouTubeSettings.SPOOF_VIDEO_STREAMS_PLAYER_JS_HASH;
import static app.morphe.extension.shared.settings.SharedYouTubeSettings.SPOOF_VIDEO_STREAMS_PLAYER_JS_HASH_VALUE;
import static app.morphe.extension.shared.utils.ResourceUtils.getStringArray;
import static app.morphe.extension.shared.utils.StringRef.str;
import static app.morphe.extension.shared.utils.Utils.isSDKAbove;
@ -152,7 +152,7 @@ public class ReVancedPreferenceFragment extends PreferenceFragment {
|| settings.equals(ENABLE_CUSTOM_NAVIGATION_BAR_COLOR_VALUE)
|| settings.equals(HIDE_ACCOUNT_MENU_FILTER_STRINGS)
|| settings.equals(RETURN_YOUTUBE_USERNAME_YOUTUBE_DATA_API_V3_DEVELOPER_KEY)
|| settings.equals(SPOOF_VIDEO_STREAMS_PLAYER_JS_HASH)) {
|| settings.equals(SPOOF_VIDEO_STREAMS_PLAYER_JS_HASH_VALUE)) {
ResettableEditTextPreference.showDialog(mActivity, stringSetting);
} else if (settings.equals(EXTERNAL_DOWNLOADER_PACKAGE_NAME)) {
ExternalDownloaderPreference.showDialog(mActivity);

View file

@ -1,10 +1,12 @@
package app.morphe.extension.shared.innertube.client
import android.os.Build
import app.morphe.extension.music.settings.Settings as MusicSettings
import app.morphe.extension.shared.innertube.utils.J2V8Support.supportJ2V8
import app.morphe.extension.shared.patches.AppCheckPatch.IS_YOUTUBE
import app.morphe.extension.shared.settings.BaseSettings
import app.morphe.extension.shared.utils.PackageUtils
import app.morphe.extension.youtube.settings.Settings as YouTubeSettings
import org.apache.commons.lang3.ArrayUtils
import java.util.Locale
@ -291,7 +293,11 @@ object YouTubeClient {
}
return clientToUse.filterNotNull().toTypedArray()
} else {
BaseSettings.SPOOF_STREAMING_DATA_DEFAULT_CLIENT.resetToDefault()
if (IS_YOUTUBE) {
YouTubeSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.resetToDefault()
} else {
MusicSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.resetToDefault()
}
return availableClientTypes
}
}

View file

@ -5,14 +5,14 @@ import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.liskovsoft.sharedutils.helpers.Helpers;
import com.liskovsoft.youtubeapi.app.playerdata.PlayerDataExtractor;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONObject;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.SocketTimeoutException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
@ -21,6 +21,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import app.morphe.extension.shared.innertube.client.YouTubeClient;
import app.morphe.extension.shared.spoof.js.PlayerDataExtractor;
import app.morphe.extension.shared.utils.Logger;
import app.morphe.extension.shared.utils.Utils;
import okhttp3.OkHttpClient;
@ -187,7 +188,7 @@ public class ThrottlingParameterUtils {
String signatureTimestamp = matcher.group(1);
if (StringUtils.isNotEmpty(signatureTimestamp)) {
Logger.printDebug(() -> "signatureTimestamp: " + signatureTimestamp);
return Helpers.parseInt(signatureTimestamp);
return Integer.parseInt(signatureTimestamp);
}
}
}
@ -413,9 +414,15 @@ public class ThrottlingParameterUtils {
String urlParam = paramUrlMatcher.group(1);
if (StringUtils.isNotEmpty(sParam) && StringUtils.isNotEmpty(urlParam)) {
// The 'sig' parameter converted by javascript rules.
String decodedSigParm = extractor.extractSig(Helpers.decode(sParam));
var results = extractor.bulkSigExtract(
null,
Collections.singletonList(decodeUrl(sParam))
);
String decodedSigParm = results.second.isEmpty()
? null
: results.second.get(0);
if (StringUtils.isNotEmpty(decodedSigParm)) {
String decodedUriParm = Helpers.decode(urlParam);
String decodedUriParm = decodeUrl(urlParam);
Logger.printDebug(() -> "Converted signatureCipher to obfuscatedUrl, videoId: " + videoId);
return decodedUriParm + "&sig=" + decodedSigParm;
}
@ -526,7 +533,13 @@ public class ThrottlingParameterUtils {
PlayerDataExtractor extractor = getExtractor();
if (extractor != null) {
// The 'n' parameter deobfuscated by javascript rules.
String deObfuscatedNParams = extractor.extractNSig(obfuscatedNParams);
var results = extractor.bulkSigExtract(
Collections.singletonList(obfuscatedNParams),
null
);
String deObfuscatedNParams = results.first.isEmpty()
? null
: results.first.get(0);
if (StringUtils.isNotEmpty(deObfuscatedNParams)) {
String deObfuscatedUrl = replaceNParam(obfuscatedUrl, obfuscatedNParams, deObfuscatedNParams);
return new Pair<>(deObfuscatedUrl, deObfuscatedNParams);
@ -538,4 +551,15 @@ public class ThrottlingParameterUtils {
return new Pair<>(obfuscatedUrl, "");
}
@NonNull
private static String decodeUrl(@NonNull String value) {
try {
//noinspection CharsetObjectCanBeUsed
return URLDecoder.decode(value, "UTF-8");
} catch (UnsupportedEncodingException ex) {
Logger.printException(() -> "Failed to decode url", ex);
return value;
}
}
}

View file

@ -18,7 +18,7 @@ final class OAuth2Routes {
private static final String OAUTH2_GOOGLE_API_URL = "https://oauth2.googleapis.com/";
private static final String OAUTH2_YOUTUBE_API_URL = "https://www.youtube.com/o/oauth2/";
private static final String USER_AGENT =
"com.google.android.apps.youtube.vr.oculus/1.47.48(Linux; U; Android 10; en_US; Quest Build/QQ3A.200805.001) gzip";
"com.google.android.apps.youtube.vr.oculus/1.64.34(Linux; U; Android 10; en_US; Quest Build/QQ3A.200805.001) gzip";
/**
* TCP connection and HTTP read timeout.
@ -56,4 +56,4 @@ final class OAuth2Routes {
applyCommonPostRequestSettings(connection);
return connection;
}
}
}

View file

@ -1,6 +1,6 @@
package app.morphe.extension.shared.patches.spoof;
import static app.morphe.extension.shared.spoof.js.J2V8Support.supportJ2V8;
import static app.morphe.extension.shared.spoof.js.JavaScriptEngineSupport.supportsJavaScriptEngine;
import android.net.Uri;
@ -21,11 +21,12 @@ import app.morphe.extension.shared.spoof.ClientType;
import app.morphe.extension.shared.spoof.SpoofVideoStreamsPatch;
import app.morphe.extension.shared.spoof.js.JavaScriptManager;
import app.morphe.extension.shared.utils.Logger;
import app.morphe.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class SpoofStreamingDataPatch {
private static final boolean J2V8_LIBRARY_AVAILABILITY = supportJ2V8();
// Keep the old name for existing availability classes and settings that still reference it.
private static final boolean J2V8_LIBRARY_AVAILABILITY = supportsJavaScriptEngine();
private static final boolean JAVASCRIPT_ENGINE_AVAILABILITY = supportsJavaScriptEngine();
public static final boolean SPOOF_STREAMING_DATA =
BaseSettings.SPOOF_STREAMING_DATA.get() && PatchStatus.SpoofStreamingData();
@ -34,6 +35,15 @@ public class SpoofStreamingDataPatch {
return SPOOF_STREAMING_DATA;
}
@NonNull
private static ClientType getSelectedClientType() {
if (AppCheckPatch.IS_YOUTUBE) {
return app.morphe.extension.youtube.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
}
return app.morphe.extension.music.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
}
public static String blockGetAttRequest(String originalUrlString) {
return SpoofVideoStreamsPatch.blockGetAttRequest(originalUrlString);
}
@ -114,8 +124,8 @@ public class SpoofStreamingDataPatch {
public static void initializeJavascript() {
if (isSpoofingEnabled()
&& Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get().requireJS
&& J2V8_LIBRARY_AVAILABILITY) {
&& getSelectedClientType().requireJS
&& JAVASCRIPT_ENGINE_AVAILABILITY) {
app.morphe.extension.shared.utils.Utils.runOnBackgroundThread(JavaScriptManager::getSignatureTimestamp);
}
}
@ -132,21 +142,7 @@ public class SpoofStreamingDataPatch {
@Override
public boolean isAvailable() {
return BaseSettings.SPOOF_STREAMING_DATA.get()
&& BaseSettings.SPOOF_STREAMING_DATA_DEFAULT_CLIENT.get().name().startsWith("ANDROID_VR");
}
@Override
public List<Setting<?>> getParentSettings() {
return List.of(BaseSettings.SPOOF_STREAMING_DATA);
}
}
public static final class ClientJSAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
return BaseSettings.SPOOF_STREAMING_DATA.get()
&& J2V8_LIBRARY_AVAILABILITY
&& Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get().requireJS;
&& getSelectedClientType().name().startsWith("ANDROID_VR");
}
@Override
@ -175,24 +171,4 @@ public class SpoofStreamingDataPatch {
return BaseSettings.SPOOF_STREAMING_DATA.get() && J2V8_LIBRARY_AVAILABILITY;
}
}
public static final class ShowReloadVideoButtonAvailability implements Setting.Availability {
private static final boolean AVAILABLE_ON_LAUNCH = BaseSettings.SPOOF_STREAMING_DATA.get()
&& BaseSettings.SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON.get();
@Override
public boolean isAvailable() {
return AVAILABLE_ON_LAUNCH
&& BaseSettings.SPOOF_STREAMING_DATA.get()
&& BaseSettings.SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON.get();
}
@Override
public List<Setting<?>> getParentSettings() {
return List.of(
BaseSettings.SPOOF_STREAMING_DATA,
BaseSettings.SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON
);
}
}
}

View file

@ -2,17 +2,13 @@ package app.morphe.extension.shared.settings;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static app.morphe.extension.shared.patches.AppCheckPatch.IS_YOUTUBE;
import static app.morphe.extension.shared.settings.Setting.parent;
import app.morphe.extension.shared.patches.ReturnYouTubeUsernamePatch.DisplayFormat;
import app.morphe.extension.shared.patches.WatchHistoryPatch.WatchHistoryType;
import app.morphe.extension.shared.patches.spoof.SpoofStreamingDataPatch;
import app.morphe.extension.shared.patches.spoof.SpoofStreamingDataPatch.ClientAndroidVRAvailability;
import app.morphe.extension.shared.patches.spoof.SpoofStreamingDataPatch.ClientJSAvailability;
import app.morphe.extension.shared.patches.spoof.SpoofStreamingDataPatch.J2V8Availability;
import app.morphe.extension.shared.patches.spoof.SpoofStreamingDataPatch.ShowReloadVideoButtonAvailability;
import app.morphe.extension.shared.spoof.ClientType;
/**
* Settings shared across multiple apps.
@ -31,23 +27,12 @@ public class BaseSettings {
public static final EnumSetting<AppLanguage> REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true);
public static final BooleanSetting SPOOF_STREAMING_DATA = new BooleanSetting("revanced_spoof_streaming_data", TRUE, true, "revanced_spoof_streaming_data_user_dialog_message");
public static final BooleanSetting SPOOF_STREAMING_DATA_PRIORITIZE_VIDEO_QUALITY = new BooleanSetting("revanced_spoof_streaming_data_prioritize_video_quality", FALSE, true,
"revanced_spoof_streaming_data_prioritize_video_quality_user_dialog_message", parent(SPOOF_STREAMING_DATA));
public static final BooleanSetting SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON = new BooleanSetting("revanced_spoof_streaming_data_reload_video_button", FALSE, true, parent(SPOOF_STREAMING_DATA));
public static final BooleanSetting SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON_ALWAYS_SHOW = new BooleanSetting("revanced_spoof_streaming_data_reload_video_button_always_show", TRUE, true, new ShowReloadVideoButtonAvailability());
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_STREAMING_DATA));
public static final BooleanSetting SPOOF_STREAMING_DATA_ANDROID_VR_ENABLE_AV1_CODEC = new BooleanSetting("revanced_spoof_streaming_data_android_vr_enable_av1_codec", FALSE, true,
"revanced_spoof_streaming_data_android_vr_enable_av1_codec_user_dialog_message", new ClientAndroidVRAvailability());
public static final BooleanSetting SPOOF_STREAMING_DATA_USE_JS = new BooleanSetting("revanced_spoof_streaming_data_use_js", TRUE, true,
"revanced_spoof_streaming_data_use_js_user_dialog_message", new J2V8Availability());
public static final BooleanSetting SPOOF_STREAMING_DATA_USE_JS_ALL = new BooleanSetting("revanced_spoof_streaming_data_use_js_all", FALSE, true, new ClientJSAvailability());
public static final BooleanSetting SPOOF_STREAMING_DATA_USE_JS_BYPASS_FAKE_BUFFERING = new BooleanSetting("revanced_spoof_streaming_data_use_js_bypass_fake_buffering", FALSE, true, new ClientJSAvailability());
// Client type must be last spoof setting due to cyclic references.
public static final EnumSetting<ClientType> SPOOF_STREAMING_DATA_DEFAULT_CLIENT = new EnumSetting<>("revanced_spoof_streaming_data_default_client",
IS_YOUTUBE ? ClientType.TV : ClientType.ANDROID_VR_1_47_48, true, parent(SPOOF_STREAMING_DATA));
public static final BooleanSetting ENABLE_COMMENTS_SCROLL_TOP = new BooleanSetting("revanced_enable_comments_scroll_top", FALSE, true);
public static final BooleanSetting DISABLE_AUTO_AUDIO_TRACKS = new BooleanSetting("revanced_disable_auto_audio_tracks", TRUE);

View file

@ -25,12 +25,12 @@ public class SharedYouTubeSettings extends BaseSettings {
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("morphe_spoof_video_streams", TRUE, true, "morphe_spoof_video_streams_user_dialog_message");
public static final BooleanSetting SPOOF_VIDEO_STREAMS_STATS_FOR_NERDS = new BooleanSetting("morphe_spoof_video_streams_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
public static final EnumSetting<JavaScriptVariant> SPOOF_VIDEO_STREAMS_PLAYER_JS_VARIANT = new EnumSetting<>("morphe_spoof_video_streams_player_js_variant", JavaScriptVariant.PHONE, true,
public static final EnumSetting<JavaScriptVariant> SPOOF_VIDEO_STREAMS_PLAYER_JS_VARIANT = new EnumSetting<>("morphe_spoof_video_streams_player_js_variant", JavaScriptVariant.HOUSE_BRAND, true,
new JavaScriptClientAvailability());
public static final BooleanSetting SPOOF_VIDEO_STREAMS_DISABLE_PLAYER_JS_UPDATE = new BooleanSetting("morphe_spoof_video_streams_disable_player_js_update", FALSE, true,
"morphe_spoof_video_streams_disable_player_js_update_user_dialog_message", new JavaScriptClientAvailability());
public static final StringSetting SPOOF_VIDEO_STREAMS_PLAYER_JS_HASH = new StringSetting("morphe_spoof_video_streams_player_js_hash", "", true,
public static final StringSetting SPOOF_VIDEO_STREAMS_PLAYER_JS_HASH_VALUE = new StringSetting("morphe_spoof_video_streams_player_js_hash_value", "", true,
new JavaScriptHashAvailability());
public static final LongSetting SPOOF_VIDEO_STREAMS_PLAYER_JS_SAVED_MILLISECONDS = new LongSetting("morphe_spoof_video_streams_player_js_saved_milliseconds", -1L, false, false);
public static final StringSetting OAUTH2_REFRESH_TOKEN = new StringSetting("morphe_oauth2_refresh_token", "", false, false);

View file

@ -83,7 +83,7 @@ public enum ClientType {
* AV1 codec available.
*/
// https://dumps.tadiphone.dev/dumps/oculus/eureka
ANDROID_VR_1_54_20(
ANDROID_VR_1_65(
28,
"ANDROID_VR",
"com.google.android.apps.youtube.vr.oculus",
@ -93,7 +93,7 @@ public enum ClientType {
"14",
"34",
"UP1A.231005.007.A1",
"1.54.20",
"1.65.10",
null,
false,
false,
@ -101,32 +101,32 @@ public enum ClientType {
true,
false,
true,
"Android VR 1.54"
"Android VR 1.65"
),
/**
* Uses non adaptive bitrate.
* AV1 codec not available.
*/
// https://dumps.tadiphone.dev/dumps/oculus/monterey
ANDROID_VR_1_47_48(
ANDROID_VR_1_54_20.id,
ANDROID_VR_1_54_20.clientName,
Objects.requireNonNull(ANDROID_VR_1_54_20.packageName),
ANDROID_VR_1_54_20.deviceMake,
ANDROID_VR_1_64(
ANDROID_VR_1_65.id,
ANDROID_VR_1_65.clientName,
Objects.requireNonNull(ANDROID_VR_1_65.packageName),
ANDROID_VR_1_65.deviceMake,
"Quest",
ANDROID_VR_1_54_20.osName,
ANDROID_VR_1_65.osName,
"10",
"29",
"QQ3A.200805.001",
"1.47.48",
ANDROID_VR_1_54_20.clientPlatform,
ANDROID_VR_1_54_20.canLogin,
ANDROID_VR_1_54_20.requireLogin,
ANDROID_VR_1_54_20.supportsMultiAudioTracks,
ANDROID_VR_1_54_20.supportsOAuth2,
ANDROID_VR_1_54_20.requireJS,
ANDROID_VR_1_54_20.usePlayerEndpoint,
"Android VR 1.47"
"1.64.34",
ANDROID_VR_1_65.clientPlatform,
ANDROID_VR_1_65.canLogin,
ANDROID_VR_1_65.requireLogin,
ANDROID_VR_1_65.supportsMultiAudioTracks,
ANDROID_VR_1_65.supportsOAuth2,
ANDROID_VR_1_65.requireJS,
ANDROID_VR_1_65.usePlayerEndpoint,
"Android VR 1.64"
),
/**
* Video not playable: Livestream.

View file

@ -71,7 +71,7 @@ public class SpoofVideoStreamsPatch {
@Nullable
private static volatile AppLanguage languageOverride;
private static volatile ClientType preferredClient = ClientType.ANDROID_VR_1_47_48;
private static volatile ClientType preferredClient = ClientType.ANDROID_REEL;
private static WeakReference<Application> mainActivityRef = new WeakReference<>(null);

View file

@ -1,39 +0,0 @@
package app.morphe.extension.shared.spoof.js;
import java.io.File;
import app.morphe.extension.shared.Logger;
import app.morphe.extension.shared.Utils;
/**
* In general, there is no error in 'System.loadLibrary()' because the patched app contains the J2V8 native library (libj2v8.so).
* If the app is installed via mount, it cannot find the native library, causing a runtime exception and a fatal crash.
* Therefore, before loading the library, the app first checks whether the native library exists.
* If the native library does not exist, the JavaScript client is skipped.
*/
public class J2V8Support {
private static final boolean DEVICE_SUPPORT_J2V8;
static {
boolean j2v8Support = false;
try {
String libraryDir = Utils.getContext()
.getApplicationContext()
.getApplicationInfo()
.nativeLibraryDir;
File j2v8File = new File(libraryDir + "/libj2v8.so");
j2v8Support = j2v8File.exists();
} catch (Exception ex) {
Logger.printException(() -> "J2V8 native library not found", ex);
}
DEVICE_SUPPORT_J2V8 = j2v8Support;
Logger.printDebug(() -> DEVICE_SUPPORT_J2V8
? "Device supports J2V8"
: "Device does not support J2V8");
}
public static boolean supportJ2V8() {
return DEVICE_SUPPORT_J2V8;
}
}

View file

@ -0,0 +1,33 @@
package app.morphe.extension.shared.spoof.js;
import android.webkit.CookieManager;
import androidx.javascriptengine.JavaScriptSandbox;
import app.morphe.extension.shared.Logger;
/**
* Checks whether the device supports the JavaScriptEngine (backed by the system WebView's V8).
*/
public class JavaScriptEngineSupport {
private static final boolean DEVICE_SUPPORTS_JS_ENGINE;
static {
boolean sandBoxSupport = false;
try {
CookieManager.getInstance();
sandBoxSupport = JavaScriptSandbox.isSupported();
} catch (Exception ex) {
Logger.printException(() -> "JavaScriptSandbox support check failed", ex);
}
DEVICE_SUPPORTS_JS_ENGINE = sandBoxSupport;
Logger.printDebug(() -> DEVICE_SUPPORTS_JS_ENGINE
? "Device supports JavaScriptEngine"
: "Device does not support JavaScriptEngine");
}
public static boolean supportsJavaScriptEngine() {
return DEVICE_SUPPORTS_JS_ENGINE;
}
}

View file

@ -84,7 +84,7 @@ public final class JavaScriptManager {
* JavaScript hash setting.
*/
private static final StringSetting PLAYER_JS_HASH =
SharedYouTubeSettings.SPOOF_VIDEO_STREAMS_PLAYER_JS_HASH;
SharedYouTubeSettings.SPOOF_VIDEO_STREAMS_PLAYER_JS_HASH_VALUE;
/**
* Variant of JavaScript url.
*/
@ -336,22 +336,18 @@ public final class JavaScriptManager {
streamingDataBuilder.clearFormats();
// Deobfuscate formats.
boolean deobfuscateResult = deobfuscateFormat(
deobfuscateFormat(
streamingDataBuilder,
streamingData.getFormatsList(),
serverAbrStreamingUrl,
false
);
if (!deobfuscateResult) {
return null;
}
// Initialize streamingDataBuilder before adding adaptiveFormats.
streamingDataBuilder.clearAdaptiveFormats();
// Deobfuscate adaptiveFormats.
deobfuscateResult = deobfuscateFormat(
boolean deobfuscateResult = deobfuscateFormat(
streamingDataBuilder,
streamingData.getAdaptiveFormatsList(),
serverAbrStreamingUrl,
@ -371,6 +367,10 @@ public final class JavaScriptManager {
boolean isAdaptiveFormats) {
PlayerDataExtractor playerDataExtractor = getPlayerDataExtractor();
if (!isAdaptiveFormats) {
streamingDataBuilder.addFormats(Format.newBuilder().build());
return true;
}
if (playerDataExtractor != null && formats != null && !formats.isEmpty()) {
// In streamingData, all n-parameters have the same value.
String obfuscatedNParameter = null;
@ -444,11 +444,7 @@ public final class JavaScriptManager {
formatBuilder.clearSignatureCipher();
Format newFormat = formatBuilder.build();
if (isAdaptiveFormats) {
streamingDataBuilder.addAdaptiveFormats(newFormat);
} else {
streamingDataBuilder.addFormats(newFormat);
}
streamingDataBuilder.addAdaptiveFormats(newFormat);
i++;
}
@ -459,11 +455,6 @@ public final class JavaScriptManager {
return true;
}
} else if (!isAdaptiveFormats) {
// The TV client may not have formats in live streams, which is normal.
// Since formats aren't used for playback, there's no problem using the default instance.
streamingDataBuilder.addFormats(Format.newBuilder().build());
return true;
}
return false;
@ -512,4 +503,4 @@ public final class JavaScriptManager {
Logger.printException(() -> "Failed to save file", ex);
}
}
}
}

View file

@ -7,7 +7,7 @@ package app.morphe.extension.shared.spoof.js;
public enum JavaScriptVariant {
// Used in Google Drive, this variant is the only one that does not have player ad-related code.
// In theory, it seems like the best variant, but it hasn't been tested enough.
// In theory, it seems like the best variant.
HOUSE_BRAND("https://youtube.googleapis.com/s/player/%s/house_brand_player.vflset/en_US/base.js"),
PHONE("https://m.youtube.com/s/player/%s/player-plasma-ias-phone-en_US.vflset/base.js"),
TV_ES6("https://www.youtube.com/s/player/%s/tv-player-es6.vflset/tv-player-es6.js"),

View file

@ -1,5 +1,7 @@
package app.morphe.extension.shared.spoof.js;
import static app.morphe.extension.shared.spoof.js.nsigsolver.impl.JsEngineChallengeProvider.getInstance;
import android.util.Pair;
import androidx.annotation.NonNull;
@ -9,7 +11,6 @@ import java.util.Collections;
import java.util.List;
import app.morphe.extension.shared.Logger;
import app.morphe.extension.shared.spoof.js.nsigsolver.impl.V8ChallengeProvider;
import app.morphe.extension.shared.spoof.js.nsigsolver.provider.*;
/**
@ -19,8 +20,8 @@ import app.morphe.extension.shared.spoof.js.nsigsolver.provider.*;
public class PlayerDataExtractor {
public PlayerDataExtractor(String playerJS, String playerJSHash) {
V8ChallengeProvider.getInstance().setPlayerJS(playerJS, playerJSHash);
V8ChallengeProvider.getInstance().warmup();
getInstance().setPlayerJS(playerJS, playerJSHash);
getInstance().warmup();
checkAllData();
}
@ -30,7 +31,7 @@ public class PlayerDataExtractor {
List<String> sProcessed = new ArrayList<>();
List<JsChallengeRequest> requests = getJsChallengeRequests(nParams, sParams);
List<JsChallengeProviderResponse> result = V8ChallengeProvider.getInstance().bulkSolve(requests);
List<JsChallengeProviderResponse> result = getInstance().bulkSolve(requests);
for (JsChallengeProviderResponse item : result) {
var response = item.getResponse();
@ -95,9 +96,9 @@ public class PlayerDataExtractor {
requests.add(new JsChallengeRequest(JsChallengeType.N, new ChallengeInput(param)));
requests.add(new JsChallengeRequest(JsChallengeType.SIG, new ChallengeInput(param)));
V8ChallengeProvider.getInstance().bulkSolve(requests);
getInstance().bulkSolve(requests);
} catch (Exception ex) {
Logger.printException(() -> "Deobfuscation test failed", ex);
}
}
}
}

View file

@ -0,0 +1,169 @@
/*
* Copyright 2026 Morphe.
* https://github.com/MorpheApp/morphe-patches
*
* See the included NOTICE file for GPLv3 §7(b) and §7(c) terms that apply to this code.
*/
package app.morphe.extension.shared.spoof.js.nsigsolver.impl;
import androidx.javascriptengine.EvaluationFailedException;
import androidx.javascriptengine.IsolateStartupParameters;
import androidx.javascriptengine.JavaScriptIsolate;
import androidx.javascriptengine.JavaScriptSandbox;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import app.morphe.extension.shared.Logger;
import app.morphe.extension.shared.Utils;
import app.morphe.extension.shared.spoof.js.nsigsolver.common.CacheError;
import app.morphe.extension.shared.spoof.js.nsigsolver.common.ScriptUtils;
import app.morphe.extension.shared.spoof.js.nsigsolver.provider.JsChallengeProviderError;
import app.morphe.extension.shared.spoof.js.nsigsolver.runtime.JsRuntimeChalBaseJCP;
import app.morphe.extension.shared.spoof.js.nsigsolver.runtime.Script;
import app.morphe.extension.shared.spoof.js.nsigsolver.runtime.ScriptSource;
import app.morphe.extension.shared.spoof.js.nsigsolver.runtime.ScriptType;
import app.morphe.extension.shared.spoof.js.nsigsolver.runtime.ScriptVariant;
public class JsEngineChallengeProvider extends JsRuntimeChalBaseJCP {
private static final JsEngineChallengeProvider INSTANCE = new JsEngineChallengeProvider();
private final List<String> npmLibFilenames = Arrays.asList(
LIB_PREFIX + "polyfill.js",
LIB_PREFIX + "meriyah-6.1.4.min.js",
LIB_PREFIX + "astring-1.9.0.min.js"
);
private ExecutorService jsExecutor = Executors.newSingleThreadExecutor();
private JavaScriptSandbox jsSandbox;
private JavaScriptIsolate jsIsolate;
private int executeCount = 0;
private JsEngineChallengeProvider() {}
public static JsEngineChallengeProvider getInstance() {
return INSTANCE;
}
@Override
protected Script builtinSource(ScriptType scriptType) {
if (scriptType == ScriptType.LIB) {
Script npmScript = npmSource(scriptType);
if (npmScript != null) return npmScript;
}
return super.builtinSource(scriptType);
}
private Script npmSource(ScriptType scriptType) {
try {
String code = ScriptUtils.loadScript(npmLibFilenames, "Failed to read js challenge solver lib script");
return new Script(scriptType, ScriptVariant.V8_NPM, ScriptSource.BUILTIN, SCRIPT_VERSION, code);
} catch (ScriptUtils.ScriptLoaderError e) {
Logger.printException(() -> "Failed to read npm source", e);
return null;
}
}
private void ensureIsolate() throws Exception {
if (jsSandbox == null) {
jsSandbox = JavaScriptSandbox.createConnectedInstanceAsync(
Utils.getContext().getApplicationContext()
).get();
}
if (jsIsolate == null) {
if (jsSandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)) {
IsolateStartupParameters params = new IsolateStartupParameters();
params.setMaxHeapSizeBytes(128 * 1024 * 1024);
jsIsolate = jsSandbox.createIsolate(params);
} else {
jsIsolate = jsSandbox.createIsolate();
}
}
}
private void resetIsolate() {
if (jsIsolate != null) {
try {
jsIsolate.close();
} catch (Exception e) {
// Ignore close errors.
}
jsIsolate = null;
}
executeCount = 0;
Logger.printDebug(() -> "Closed the JavaScript isolate");
}
@Override
protected String runJsRuntime(String stdin) throws JsChallengeProviderError {
warmup();
return runJS(stdin, false);
}
private String runJS(String stdin, boolean warmup) throws JsChallengeProviderError {
try {
String results = jsExecutor.submit(() -> {
ensureIsolate();
String result = jsIsolate.evaluateJavaScriptAsync(stdin).get();
executeCount++;
return result;
}).get();
if (warmup) {
return "";
}
if (Utils.isNotEmpty(results)) {
return results;
} else {
var message = "JavaScript engine error: empty response";
Logger.printException(() -> message);
throw new JsChallengeProviderError(message);
}
} catch (InterruptedException | ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof ExecutionException innerExec) {
cause = innerExec.getCause();
}
if (cause instanceof EvaluationFailedException jsError) {
if (jsError.getMessage() != null && jsError.getMessage().contains("Invalid or unexpected token")) {
try {
cacheService.clear(CACHE_SECTION);
} catch (CacheError ce) {
// ignore
}
}
Logger.printException(() -> "JavaScript engine error, warmup: " + warmup, jsError);
throw new JsChallengeProviderError("JavaScript engine error: " + jsError.getMessage(), jsError);
}
Logger.printException(() -> "Execution failed, warmup: " + warmup, e);
throw new JsChallengeProviderError("Execution failed", e);
}
}
public void warmup() {
if (jsExecutor.isShutdown() || jsExecutor.isTerminated()) {
try {
jsExecutor = Executors.newSingleThreadExecutor();
executeCount = 0;
resetLoadedPlayerState();
resetIsolate();
} catch (Exception ex) {
Logger.printException(() -> "Failed to create JS executor", ex);
}
}
if (executeCount == 0) {
try {
String commonStdin = constructCommonStdin();
runJS(commonStdin, true);
} catch (Exception e) {
// ignore warmup errors
}
}
}
}

View file

@ -1,152 +0,0 @@
/*
* Copyright 2026 Morphe.
* https://github.com/MorpheApp/morphe-patches
*/
package app.morphe.extension.shared.spoof.js.nsigsolver.impl;
import com.eclipsesource.v8.V8;
import com.eclipsesource.v8.V8ScriptExecutionException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import app.morphe.extension.shared.Logger;
import app.morphe.extension.shared.Utils;
import app.morphe.extension.shared.spoof.js.nsigsolver.common.CacheError;
import app.morphe.extension.shared.spoof.js.nsigsolver.common.ScriptUtils;
import app.morphe.extension.shared.spoof.js.nsigsolver.provider.JsChallengeProviderError;
import app.morphe.extension.shared.spoof.js.nsigsolver.runtime.JsRuntimeChalBaseJCP;
import app.morphe.extension.shared.spoof.js.nsigsolver.runtime.Script;
import app.morphe.extension.shared.spoof.js.nsigsolver.runtime.ScriptSource;
import app.morphe.extension.shared.spoof.js.nsigsolver.runtime.ScriptType;
import app.morphe.extension.shared.spoof.js.nsigsolver.runtime.ScriptVariant;
public class V8ChallengeProvider extends JsRuntimeChalBaseJCP {
private static final V8ChallengeProvider INSTANCE = new V8ChallengeProvider();
private final List<String> v8NpmLibFilename = Arrays.asList(
LIB_PREFIX + "polyfill.js",
LIB_PREFIX + "meriyah-6.1.4.min.js",
LIB_PREFIX + "astring-1.9.0.min.js"
);
private ExecutorService v8Executor = Executors.newSingleThreadExecutor();
private V8 v8Runtime;
private int executeCount = 0;
private V8ChallengeProvider() {}
public static V8ChallengeProvider getInstance() {
return INSTANCE;
}
// Override builtinSource to inject V8 specific logic
@Override
protected Script builtinSource(ScriptType scriptType) {
// Try V8 specific source first for LIB
if (scriptType == ScriptType.LIB) {
Script v8Script = v8NpmSource(scriptType);
if (v8Script != null) return v8Script;
}
return super.builtinSource(scriptType);
}
private Script v8NpmSource(ScriptType scriptType) {
try {
String code = ScriptUtils.loadScript(v8NpmLibFilename, "Failed to read v8 challenge solver lib script");
return new Script(scriptType, ScriptVariant.V8_NPM, ScriptSource.BUILTIN, SCRIPT_VERSION, code);
} catch (ScriptUtils.ScriptLoaderError e) {
Logger.printException(() -> "Failed to read v8 npm source", e);
return null;
}
}
@Override
protected String runJsRuntime(String stdin) throws JsChallengeProviderError {
warmup();
return runJS(stdin, false);
}
private String runJS(String stdin, boolean warmup) throws JsChallengeProviderError {
try {
String results = v8Executor.submit(() -> {
// Null checking and setting in the v8 runtime are done on the same thread
if (v8Runtime == null) {
v8Runtime = V8.createV8Runtime();
}
// Run js to get decipher results
String result = v8Runtime.executeStringScript(stdin);
// The decipher function and global functions are remembered by the V8 runtime's Bytecode Caching
// This ensures that the V8 runtime is faster than other runtimes, such as QuickJS,
// when deciphering dozens of formats simultaneously with yt-dlp-ejs
//
// To prevent memory leaks, the V8 runtime's cached bytecode must be periodically flushed
// The V8 runtime is reset every time the execution count exceeds 12
//
// Note: There is a delay of approximately 100-200ms when the runtime is regenerated
if (executeCount > 12 && !warmup && !v8Runtime.isReleased()) {
v8Runtime.lowMemoryNotification();
v8Runtime.release(false);
v8Runtime = null;
executeCount = 0;
Logger.printDebug(() -> "Close the V8 runtime");
}
executeCount++;
return result;
}).get();
// The results of the warmup are not used anywhere
if (warmup) {
return "";
}
if (Utils.isNotEmpty(results)) {
return results;
} else {
var message = "V8 runtime error: empty response";
Logger.printException(() -> message);
throw new JsChallengeProviderError(message);
}
} catch (InterruptedException | ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof V8ScriptExecutionException v8e) {
if (v8e.getMessage() != null && v8e.getMessage().contains("Invalid or unexpected token")) {
try {
cacheService.clear(CACHE_SECTION);
} catch (CacheError ce) {
// ignore
}
}
Logger.printException(() -> "V8 runtime error, warmup: " + warmup, v8e);
throw new JsChallengeProviderError("V8 runtime error: " + v8e.getMessage(), v8e);
}
Logger.printException(() -> "Execution failed, warmup: " + warmup, e);
throw new JsChallengeProviderError("Execution failed", e);
}
}
public void warmup() {
// If v8Executor terminates for an unexpected reason, it will be recreated
if (v8Executor.isShutdown() || v8Executor.isTerminated()) {
try {
v8Executor = Executors.newSingleThreadExecutor();
} catch (Exception ex) {
Logger.printException(() -> "Failed to create V8 executor", ex);
}
}
try {
// Declare a global function
runJS(constructCommonStdin(), true);
} catch (Exception e) {
// ignore warmup errors
}
}
}

View file

@ -35,6 +35,8 @@ public abstract class JsRuntimeChalBaseJCP extends JsChallengeProvider {
private Script libScript;
private Script coreScript;
private Script wrapperScript;
private String loadedPlayerHash = "";
// LRU Cache equivalent
protected final Map<String, String> cache = Collections.synchronizedMap(
@ -52,11 +54,13 @@ public abstract class JsRuntimeChalBaseJCP extends JsChallengeProvider {
Map<ScriptType, String> sMap = new HashMap<>();
sMap.put(ScriptType.LIB, LIB_PREFIX + "yt.solver.lib.js");
sMap.put(ScriptType.CORE, LIB_PREFIX + "yt.solver.core.js");
sMap.put(ScriptType.WRAPPER, LIB_PREFIX + "yt.solver.wrapper.js");
scriptFilenames = Collections.unmodifiableMap(sMap);
Map<ScriptType, String> mMap = new HashMap<>();
mMap.put(ScriptType.LIB, "yt.solver.lib.min.js");
mMap.put(ScriptType.CORE, "yt.solver.core.min.js");
mMap.put(ScriptType.WRAPPER, "yt.solver.wrapper.min.js");
minScriptFilenames = Collections.unmodifiableMap(mMap);
}
@ -72,23 +76,43 @@ public abstract class JsRuntimeChalBaseJCP extends JsChallengeProvider {
this.playerJSHash = playerJSHash;
}
protected void resetLoadedPlayerState() {
this.loadedPlayerHash = "";
}
@Override
protected List<JsChallengeProviderResponse> realBulkSolve(List<JsChallengeRequest> requests) {
List<JsChallengeProviderResponse> responses = new ArrayList<>();
try {
CachedData data = cacheService.get(CACHE_SECTION, "player:" + playerJSHash);
String player = (data != null) ? data.getCode() : null;
boolean cached;
boolean playerChanged = !playerJSHash.equals(loadedPlayerHash);
if (player != null) {
cached = true;
} else {
player = playerJS;
cached = false;
if (playerChanged) {
CachedData data = cacheService.get(CACHE_SECTION, "player:" + playerJSHash);
String player = (data != null) ? data.getCode() : null;
boolean preprocessed = (player != null);
if (!preprocessed) {
player = playerJS;
}
String loadStdin = constructPlayerLoadStdin(player, playerJSHash, preprocessed);
String loadResult = runJsRuntime(loadStdin);
Gson gson = new Gson();
try {
SolverOutput loadOutput = gson.fromJson(loadResult, SOLVER_OUTPUT_TYPE);
if (loadOutput != null && loadOutput.getPreprocessedPlayer() != null) {
cacheService.save(CACHE_SECTION, "player:" + playerJSHash, new CachedData(loadOutput.getPreprocessedPlayer()));
}
} catch (JsonSyntaxException ex) {
Logger.printDebug(() -> "Could not parse player load result (non-fatal)");
}
loadedPlayerHash = playerJSHash;
}
String stdin = constructStdin(player, cached, requests);
String stdin = constructWrapperStdin(requests);
String stdout = runJsRuntime(stdin);
Gson gson = new Gson();
@ -105,11 +129,6 @@ public abstract class JsRuntimeChalBaseJCP extends JsChallengeProvider {
throw new JsChallengeProviderError(message);
}
String preprocessed = output.getPreprocessedPlayer();
if (preprocessed != null) {
cacheService.save(CACHE_SECTION, "player:" + playerJSHash, new CachedData(preprocessed));
}
List<ResponseData> outputResponses = output.getResponses();
if (outputResponses != null && outputResponses.size() == requests.size()) {
for (int i = 0; i < requests.size(); i++) {
@ -142,7 +161,30 @@ public abstract class JsRuntimeChalBaseJCP extends JsChallengeProvider {
return responses;
}
private String constructStdin(String playerJS, boolean preprocessed, List<JsChallengeRequest> requests) {
private String constructPlayerLoadStdin(String playerJS, String playerHash, boolean preprocessed) {
Gson gson = new Gson();
String escapedPlayer = gson.toJson(playerJS);
String escapedHash = gson.toJson(playerHash);
StringBuilder sb = new StringBuilder();
if (preprocessed) {
sb.append(String.format("setPreprocessedPlayer(%s, %s);\n", escapedPlayer, escapedHash));
sb.append("JSON.stringify({type: 'result', responses: []});\n");
} else {
sb.append(String.format("setPlayer(%s, %s);\n", escapedPlayer, escapedHash));
sb.append("JSON.stringify((function() {\n");
sb.append(" var result = jscw({requests: [{type: 'n', challenges: []}]});\n");
sb.append(" var pp = getPreprocessedPlayer();\n");
sb.append(" if (pp) result.preprocessed_player = pp;\n");
sb.append(" return result;\n");
sb.append("})());\n");
}
return sb.toString();
}
private String constructWrapperStdin(List<JsChallengeRequest> requests) {
List<Map<String, Object>> jsonRequests = new ArrayList<>();
for (JsChallengeRequest request : requests) {
Map<String, Object> reqMap = new HashMap<>();
@ -152,20 +194,11 @@ public abstract class JsRuntimeChalBaseJCP extends JsChallengeProvider {
}
Map<String, Object> data = new HashMap<>();
if (preprocessed) {
data.put("type", "preprocessed");
data.put("preprocessed_player", playerJS);
data.put("requests", jsonRequests);
} else {
data.put("type", "player");
data.put("player", playerJS);
data.put("requests", jsonRequests);
data.put("output_preprocessed", true);
}
data.put("requests", jsonRequests);
Gson gson = new Gson();
String jsonData = gson.toJson(data);
return String.format("\nJSON.stringify(jsc(%s));\n", jsonData);
return String.format("\nJSON.stringify(jscw(%s));\n", jsonData);
}
protected String constructCommonStdin() {
@ -173,6 +206,7 @@ public abstract class JsRuntimeChalBaseJCP extends JsChallengeProvider {
return String.join("\n",
getLibScript().getCode(),
getCoreScript().getCode(),
getWrapperScript().getCode(),
"\"\";"
);
} catch (JsChallengeProviderRejectedRequest ex) {
@ -195,6 +229,13 @@ public abstract class JsRuntimeChalBaseJCP extends JsChallengeProvider {
return coreScript;
}
private Script getWrapperScript() throws JsChallengeProviderRejectedRequest {
if (wrapperScript == null) {
wrapperScript = getScript(ScriptType.WRAPPER);
}
return wrapperScript;
}
private interface ScriptSourceProvider {
Script get(ScriptType type);
}
@ -295,4 +336,4 @@ public abstract class JsRuntimeChalBaseJCP extends JsChallengeProvider {
return null;
}
}
}
}

View file

@ -2,7 +2,8 @@ package app.morphe.extension.shared.spoof.js.nsigsolver.runtime;
public enum ScriptType {
LIB("lib"),
CORE("core");
CORE("core"),
WRAPPER("wrapper");
private final String value;

View file

@ -10,7 +10,7 @@ package app.morphe.extension.shared.spoof.requests;
import static app.morphe.extension.shared.StringRef.str;
import static app.morphe.extension.shared.Utils.isNotEmpty;
import static app.morphe.extension.shared.spoof.js.J2V8Support.supportJ2V8;
import static app.morphe.extension.shared.spoof.js.JavaScriptEngineSupport.supportsJavaScriptEngine;
import static app.morphe.extension.shared.spoof.js.JavaScriptManager.getDeobfuscatedStreamingData;
import static app.morphe.extension.shared.spoof.js.JavaScriptManager.getJavaScriptHash;
import static app.morphe.extension.shared.spoof.js.JavaScriptManager.getJavaScriptVariant;
@ -72,8 +72,8 @@ public class StreamingDataRequest {
int i = 1;
for (ClientType c : availableClients) {
if (c.requireJS && !supportJ2V8()) {
Logger.printDebug(() -> "Could not find J2V8 runtime. Skipping JavaScript client: " + c.name());
if (c.requireJS && !supportsJavaScriptEngine()) {
Logger.printDebug(() -> "Could not find JavaScript engine. Skipping JavaScript client: " + c.name());
continue;
}
@ -342,7 +342,7 @@ public class StreamingDataRequest {
handleConnectionError(str("morphe_spoof_video_streams_no_clients_toast"), null, true);
var preferredClient = clientOrderToUse[0];
if (preferredClient != ClientType.ANDROID_VR_1_47_48 && preferredClient != ClientType.ANDROID_VR_1_54_20
if (preferredClient != ClientType.ANDROID_VR_1_64 && preferredClient != ClientType.ANDROID_VR_1_65
&& !SharedYouTubeSettings.OAUTH2_REFRESH_TOKEN.get().isBlank()) {
handleConnectionError(str("morphe_spoof_video_streams_no_clients_suggest_vr_toast"), null, true);
}

View file

@ -0,0 +1,124 @@
/*
* Copyright 2026 Morphe.
* https://github.com/MorpheApp/morphe-patches
*
* See the included NOTICE file for GPLv3 §7(b) and §7(c) terms that apply to Morphe contributions.
*/
package app.morphe.extension.youtube.patches;
import static app.morphe.extension.shared.utils.StringRef.str;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import androidx.annotation.NonNull;
import java.lang.ref.WeakReference;
import java.util.Objects;
import app.morphe.extension.shared.utils.Logger;
import app.morphe.extension.shared.utils.Utils;
import app.morphe.extension.youtube.shared.VideoInformation;
@SuppressWarnings("unused")
public final class ReloadVideoPatch {
/**
* Interface to use obfuscated methods.
*/
public interface PlayerInterface {
// Method is added during patching.
void patch_dismissPlayer();
}
private static WeakReference<Activity> activityRef = new WeakReference<>(null);
private static WeakReference<PlayerInterface> playerInterfaceRef = new WeakReference<>(null);
/**
* Injection point.
*/
public static void setMainActivity(Activity mainActivity) {
activityRef = new WeakReference<>(mainActivity);
}
/**
* Injection point.
*/
public static void initialize(@NonNull PlayerInterface playerInterface) {
playerInterfaceRef = new WeakReference<>(Objects.requireNonNull(playerInterface));
}
/**
* If the player is not active, the layout may break.
* Use it only when it is guaranteed to be used in situations where the player is active.
*/
public static void reloadVideo() {
try {
PlayerInterface playerInterface = playerInterfaceRef.get();
if (playerInterface == null) {
Utils.showToastShort(str("revanced_dismiss_player_not_available_toast"));
} else {
String videoId = VideoInformation.getVideoId();
String playlistId = VideoInformation.getPlaylistId();
// Dismiss the player.
playerInterface.patch_dismissPlayer();
// Reopens the video after 500ms.
// If the video was opened from a playlist, the playlist ID is also used.
Utils.runOnMainThreadDelayed(() -> openVideo(playlistId, videoId), 500);
}
} catch (Exception ex) {
Logger.printException(() -> "Failed to reload video", ex);
}
}
@SuppressWarnings("ExtractMethodRecommender")
private static void openVideo(String playlistId, String videoId) {
try {
String parameterSeparator = "?";
StringBuilder builder = new StringBuilder("https://youtu.be/");
builder.append(videoId);
if (!playlistId.isEmpty()) {
builder.append(parameterSeparator);
parameterSeparator = "&";
builder.append("list=");
builder.append(playlistId);
}
long currentVideoTimeInSeconds = VideoInformation.getVideoTime() / 1000;
if (currentVideoTimeInSeconds > 0) {
builder.append(parameterSeparator);
builder.append("t=");
builder.append(currentVideoTimeInSeconds);
}
Uri content = Uri.parse(builder.toString());
// If possible, use the main activity as the context.
// Otherwise, fall back on using the application context.
Context context = activityRef.get();
boolean isActivityContext = true;
if (context == null) {
// Utils context is the application context, and not an activity context.
//
// Edit: This check may no longer be needed since YT can now
// only be launched from the main Activity (embedded usage in other apps no longer works).
context = Utils.getContext();
isActivityContext = false;
}
Intent intent = new Intent("android.intent.action.VIEW", content);
intent.setPackage(context.getPackageName());
if (!isActivityContext) {
Logger.printDebug(() -> "Using new task intent");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
context.startActivity(intent);
} catch (Exception e) {
Logger.printException(() -> "Failed to open video", e);
}
}
}

View file

@ -1,29 +0,0 @@
package app.morphe.extension.youtube.patches.spoof;
import app.morphe.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class ReloadVideoPatch {
private static final boolean SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON =
Settings.SPOOF_STREAMING_DATA.get() &&
Settings.SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON.get();
private static final boolean SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON_ALWAYS_SHOW =
Settings.SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON_ALWAYS_SHOW.get();
private static volatile int progressBarVisibility = 0;
/**
* Injection point.
* Hooks the visibility of the loading circle (progress bar) that appears when buffering occurs.
*/
public static void setProgressBarVisibility(int visibility) {
if (SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON &&
!SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON_ALWAYS_SHOW) {
progressBarVisibility = visibility;
}
}
public static boolean isProgressBarVisible() {
return progressBarVisibility == 0;
}
}

View file

@ -1,8 +1,9 @@
package app.morphe.extension.youtube.patches.spoof;
import static app.morphe.extension.shared.spoof.ClientType.ANDROID_CREATOR;
import static app.morphe.extension.shared.spoof.ClientType.ANDROID_VR_1_47_48;
import static app.morphe.extension.shared.spoof.ClientType.ANDROID_VR_1_54_20;
import static app.morphe.extension.shared.spoof.ClientType.ANDROID_REEL;
import static app.morphe.extension.shared.spoof.ClientType.ANDROID_VR_1_64;
import static app.morphe.extension.shared.spoof.ClientType.ANDROID_VR_1_65;
import static app.morphe.extension.shared.spoof.ClientType.TV;
import static app.morphe.extension.shared.spoof.ClientType.VISIONOS;
@ -19,7 +20,7 @@ public class SpoofVideoStreamsPatch {
@Override
public boolean isAvailable() {
return Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.isAvailable()
&& Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ANDROID_VR_1_47_48;
&& Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ANDROID_VR_1_64;
}
@Override
@ -34,18 +35,19 @@ public class SpoofVideoStreamsPatch {
public static void setClientOrderToUse() {
ClientType client = Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
// Use VR 1.54 client that has AV1 if user settings allow it.
// AVC cannot be forced with VR 1.54 because it uses VP9 and AV1.
// If both settings are on, then force AVC takes priority and VR 1.47 is used.
if (client == ANDROID_VR_1_47_48 && Settings.SPOOF_VIDEO_STREAMS_AV1.get()
// Use VR 1.65 client that has AV1 if user settings allow it.
// AVC cannot be forced with VR 1.65 because it uses VP9 and AV1.
// If both settings are on, then force AVC takes priority and VR 1.64 is used.
if (client == ANDROID_VR_1_64 && Settings.SPOOF_VIDEO_STREAMS_AV1.get()
&& !Settings.FORCE_AVC_CODEC.get()) {
client = ANDROID_VR_1_54_20;
client = ANDROID_VR_1_65;
}
// For some users No SDK can fail at 1 minute. Only use it if the user has explicitly set it.
List<ClientType> availableClients = List.of(
ANDROID_REEL,
TV,
ANDROID_VR_1_47_48,
ANDROID_VR_1_64,
VISIONOS,
ANDROID_CREATOR
);
@ -53,4 +55,4 @@ public class SpoofVideoStreamsPatch {
app.morphe.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse(
availableClients, client);
}
}
}

View file

@ -1,68 +0,0 @@
package app.morphe.extension.youtube.patches.spoof.ui
import android.view.View
import app.morphe.extension.shared.utils.Logger
import app.morphe.extension.youtube.patches.spoof.ReloadVideoPatch
import app.morphe.extension.youtube.settings.Settings
import app.morphe.extension.youtube.shared.PlayerControlButton
import app.morphe.extension.youtube.shared.RootView.isAdProgressTextVisible
import app.morphe.extension.youtube.utils.VideoUtils
@Suppress("unused")
object ReloadVideoButton {
private var instance: PlayerControlButton? = null
/**
* injection point
*/
@JvmStatic
fun initializeButton(controlsView: View) {
try {
instance = PlayerControlButton(
controlsViewGroup = controlsView,
imageViewButtonId = "revanced_reload_video_button",
hasPlaceholder = false,
buttonVisibility = { isButtonEnabled() },
onClickListener = { view: View -> onClick(view) }
)
} catch (ex: Exception) {
Logger.printException({ "initializeButton failure" }, ex)
}
}
/**
* injection point
*/
@JvmStatic
fun setVisibilityNegatedImmediate() {
instance?.setVisibilityNegatedImmediate()
}
/**
* injection point
*/
@JvmStatic
fun setVisibilityImmediate(visible: Boolean) {
instance?.setVisibilityImmediate(visible)
}
/**
* injection point
*/
@JvmStatic
fun setVisibility(visible: Boolean, animated: Boolean) {
instance?.setVisibility(visible, animated)
}
private fun isButtonEnabled(): Boolean {
return Settings.SPOOF_STREAMING_DATA.get()
&& Settings.SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON.get()
&& !isAdProgressTextVisible()
&& (Settings.SPOOF_STREAMING_DATA_RELOAD_VIDEO_BUTTON_ALWAYS_SHOW.get() || ReloadVideoPatch.isProgressBarVisible())
}
private fun onClick(view: View) {
VideoUtils.reloadVideo()
}
}

View file

@ -62,7 +62,7 @@ import app.morphe.extension.youtube.swipecontrols.SwipeControlsConfigurationProv
@SuppressWarnings("unused")
public class Settings extends SharedYouTubeSettings {
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE =
new EnumSetting<>("morphe_spoof_video_streams_client_type", ClientType.ANDROID_VR_1_47_48, true, parent(SPOOF_VIDEO_STREAMS));
new EnumSetting<>("morphe_spoof_video_streams_client_type", ClientType.ANDROID_REEL, true, parent(SPOOF_VIDEO_STREAMS));
public static final BooleanSetting FORCE_AVC_CODEC = new BooleanSetting(
"morphe_force_avc_codec",
FALSE,
@ -588,6 +588,9 @@ public class Settings extends SharedYouTubeSettings {
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_SPEED_DIALOG = new BooleanSetting("revanced_shorts_custom_actions_speed_dialog", FALSE, true);
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_GEMINI = new BooleanSetting("revanced_shorts_custom_actions_gemini", FALSE, true);
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_REPEAT_STATE = new BooleanSetting("revanced_shorts_custom_actions_repeat_state", FALSE, true);
// PreferenceScreen: Player - Buttons
public static final BooleanSetting RELOAD_VIDEO = new BooleanSetting("morphe_reload_video", FALSE);
public static final EnumSetting<PlaybackSpeedMenuType> SHORTS_CUSTOM_ACTIONS_SPEED_DIALOG_TYPE = new EnumSetting<>("revanced_shorts_custom_actions_speed_dialog_type", IS_19_34_OR_GREATER ? PlaybackSpeedMenuType.CUSTOM_MODERN : PlaybackSpeedMenuType.CUSTOM_LEGACY,
true, IS_19_34_OR_GREATER ? null : "revanced_shorts_custom_actions_speed_dialog_type_user_dialog_message", parent(SHORTS_CUSTOM_ACTIONS_SPEED_DIALOG));
@ -822,8 +825,8 @@ public class Settings extends SharedYouTubeSettings {
SPOOF_APP_VERSION_TARGET.resetToDefault();
}
// VR 1.54 is not selectable in the settings, and it's selected by spoof stream patch if needed.
if (SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_VR_1_54_20) {
// VR 1.65 is not selectable in the settings, and it's selected by spoof stream patch if needed.
if (SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_VR_1_65) {
SPOOF_VIDEO_STREAMS_CLIENT_TYPE.resetToDefault();
}

View file

@ -98,8 +98,8 @@ public class SpoofVideoStreamsSideEffectsPreference extends Preference {
+ '\n' + str("morphe_spoof_video_streams_about_no_force_original_audio");
case ANDROID_REEL ->
summary = str("morphe_spoof_video_streams_about_playback_failure");
// VR 1.54 is not exposed in the UI and should never be reached here.
case ANDROID_VR_1_47_48, ANDROID_VR_1_54_20 ->
// VR 1.65 is not exposed in the UI and should never be reached here.
case ANDROID_VR_1_64, ANDROID_VR_1_65 ->
summary = str("morphe_spoof_video_streams_about_no_audio_tracks")
+ '\n' + str("morphe_spoof_video_streams_about_no_stable_volume");
case TV ->

View file

@ -0,0 +1,56 @@
package app.morphe.extension.youtube.videoplayer;
import android.view.View;
import androidx.annotation.Nullable;
import app.morphe.extension.shared.utils.Logger;
import app.morphe.extension.youtube.patches.ReloadVideoPatch;
import app.morphe.extension.youtube.settings.Settings;
import app.morphe.extension.youtube.shared.PlayerControlButton;
import app.morphe.extension.youtube.shared.RootView;
@SuppressWarnings("unused")
public class ReloadVideoButton {
@Nullable
private static PlayerControlButton instance;
/**
* Injection point.
*/
public static void initializeButton(View controlsView) {
try {
instance = new PlayerControlButton(
controlsView,
"morphe_reload_video_button",
false,
() -> Settings.RELOAD_VIDEO.get() && !RootView.isAdProgressTextVisible(),
v -> ReloadVideoPatch.reloadVideo(),
null
);
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
/**
* Injection point.
*/
public static void setVisibilityNegatedImmediate() {
if (instance != null) instance.setVisibilityNegatedImmediate();
}
/**
* Injection point.
*/
public static void setVisibilityImmediate(boolean visible) {
if (instance != null) instance.setVisibilityImmediate(visible);
}
/**
* Injection point.
*/
public static void setVisibility(boolean visible, boolean animated) {
if (instance != null) instance.setVisibility(visible, animated);
}
}

View file

@ -1,50 +0,0 @@
package com.liskovsoft.googlecommon.common.helpers;
import androidx.annotation.NonNull;
import com.liskovsoft.sharedutils.helpers.Helpers;
import java.util.Random;
/**
* Generates a random string from a predefined alphabet.
*/
public final class RandomStringFromAlphabetGenerator {
private static final String CONTENT_PLAYBACK_NONCE_ALPHABET =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
/**
* Generate a random string from an alphabet.
*
* @param alphabet the characters' alphabet to use
* @param length the length of the returned string (greater than 0)
* @param random {@link Random} (or better {@link java.security.SecureRandom}) used for
* generating the random string
* @return a random string of the requested length made of only characters from the provided
* alphabet
*/
@NonNull
public static String generate(
final String alphabet,
final int length,
final Random random) {
final StringBuilder stringBuilder = new StringBuilder(length);
for (int i = 0; i < length; i++) {
stringBuilder.append(alphabet.charAt(random.nextInt(alphabet.length())));
}
return stringBuilder.toString();
}
/**
* Generate a random string from an alphabet.
*
* @param length the length of the returned string (greater than 0)
* @return a random string of the requested length made of only characters from the provided
* alphabet
*/
@NonNull
public static String generate(
final int length) {
return generate(CONTENT_PLAYBACK_NONCE_ALPHABET, length, Helpers.getRandom());
}
}

View file

@ -1,29 +0,0 @@
package com.liskovsoft.googlecommon.common.helpers;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public final class YouTubeHelper {
/**
* Try to generate a {@code t} parameter, sent by mobile clients as a query of the player
* request.
*
* <p>
* Some researches needs to be done to know how this parameter, unique at each request, is
* generated.
* </p>
*
* @return a 12 characters string to try to reproduce the {@code} parameter
*/
@NonNull
public static String generateTParameter() {
return RandomStringFromAlphabetGenerator.generate(12);
}
@NonNull
public static String generateTParameter(@Nullable String tParameter) {
return tParameter != null && tParameter.length() == 12
? tParameter
: generateTParameter();
}
}

View file

@ -1,44 +0,0 @@
package com.liskovsoft.sharedutils.helpers;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Random;
import app.morphe.extension.shared.utils.Logger;
public final class Helpers {
private static Random sRandom;
public static String decode(String urlDecoded) {
try {
urlDecoded = URLDecoder.decode(urlDecoded, "UTF-8");
} catch (UnsupportedEncodingException e) {
Logger.printException(() -> "decode failed", e);
}
return urlDecoded;
}
public static Random getRandom() {
if (sRandom == null) {
sRandom = new Random();
}
return sRandom;
}
public static boolean isInteger(String s) {
return s != null && s.matches("^[-+]?\\d+$");
}
public static int parseInt(String numString) {
return parseInt(numString, -1);
}
public static int parseInt(String numString, int defaultValue) {
if (!isInteger(numString)) {
return defaultValue;
}
return Integer.parseInt(numString);
}
}

View file

@ -1,120 +0,0 @@
package com.liskovsoft.sharedutils.prefs;
import android.content.Context;
import android.preference.PreferenceManager;
import android.content.SharedPreferences;
import java.io.File;
import app.morphe.extension.shared.utils.Logger;
import app.morphe.extension.shared.utils.Utils;
public class SharedPreferencesBase {
private static final long PREF_MAX_SIZE_MB = 5;
private final SharedPreferences mPrefs;
public SharedPreferencesBase(String prefName) {
this(prefName, -1, false);
}
public SharedPreferencesBase(String prefName, boolean limitMaxSize) {
this(prefName, -1, limitMaxSize);
}
public SharedPreferencesBase(String prefName, int defValResId) {
this(prefName, defValResId, false);
}
public SharedPreferencesBase() {
this(null, -1, false);
}
public SharedPreferencesBase(int defValResId) {
this(null, defValResId, false);
}
public SharedPreferencesBase(String prefName, int defValResId, boolean limitMaxSize) {
Context context = Utils.getContext();
if (limitMaxSize) {
limitMaxSize(context, prefName);
}
if (prefName != null) {
mPrefs = context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
} else {
prefName = context.getPackageName() + "_preferences";
mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
}
if (defValResId != -1) {
try {
PreferenceManager.setDefaultValues(context, prefName, Context.MODE_PRIVATE, defValResId, true);
} catch (NoSuchMethodError e) {
// NoSuchMethodError: No interface method putBoolean in class SharedPreferences$Editor (Android 7.0)
e.printStackTrace();
}
}
}
/**
* Delete prefs which size exceeds a limit to prevent unconditional behavior
*/
private void limitMaxSize(Context context, String prefName) {
File sharedPrefs = new File(context.getApplicationInfo().dataDir, "shared_prefs" + "/" + prefName + ".xml");
if (sharedPrefs.exists() && sharedPrefs.isFile()) {
long sizeMB = sharedPrefs.length() / 1024 / 1024;
if (sizeMB > PREF_MAX_SIZE_MB) {
Logger.printException(() -> "Shared preference max size exceeded. Deleting...");
sharedPrefs.delete();
}
}
}
public void putLong(String key, long val) {
mPrefs.edit()
.putLong(key, val)
.apply();
}
public long getLong(String key, long defVal) {
return mPrefs.getLong(key, defVal);
}
public void putInt(String key, int val) {
mPrefs.edit()
.putInt(key, val)
.apply();
}
public int getInt(String key, int defVal) {
return mPrefs.getInt(key, defVal);
}
public void putBoolean(String key, boolean val) {
mPrefs.edit()
.putBoolean(key, val)
.apply();
}
public boolean getBoolean(String key, boolean defVal) {
return mPrefs.getBoolean(key, defVal);
}
public void putString(String key, String val) {
mPrefs.edit()
.putString(key, val)
.apply();
}
public String getString(String key, String defVal) {
return mPrefs.getString(key, defVal);
}
public void clear() {
mPrefs.edit()
.clear()
.apply();
}
}

View file

@ -1,66 +0,0 @@
package com.liskovsoft.youtubeapi.app.nsigsolver.common
import com.liskovsoft.sharedutils.prefs.SharedPreferencesBase
import java.lang.ref.WeakReference
internal class CacheError(message: String, cause: Exception? = null): Exception(message, cause)
internal data class CachedData(
val code: String,
val version: String? = null,
val variant: String? = null
)
internal object CacheService {
private const val PREF_NAME = "yt_cache_service2"
private const val KEY_DELIM = "%KEY%"
private val prefs: MutableMap<String, WeakReference<SharedPreferencesBase>> = mutableMapOf()
fun load(section: String, key: String): CachedData? {
return loadFromStorage(section, key)
}
fun store(section: String, key: String, content: CachedData) {
persistToStorage(section, key, content)
}
fun clear(section: String) {
val prefs = getSharedPrefs(getPrefsName(section))
prefs.clear()
}
private fun loadFromStorage(section: String, key: String): CachedData? {
val prefs = getSharedPrefs(getPrefsName(section))
val code: String? = prefs.getString(getCodeKey(key), null)
val version: String? = prefs.getString(getVersionKey(key), null)
val variant: String? = prefs.getString(getVariantKey(key), null)
return code?.let { CachedData(it, version, variant) }
}
private fun persistToStorage(section: String, key: String, content: CachedData) {
val prefs = getSharedPrefs(getPrefsName(section))
prefs.clear() // free some RAM (one value per file)
prefs.putString(getCodeKey(key), content.code)
prefs.putString(getVersionKey(key), content.version)
prefs.putString(getVariantKey(key), content.variant)
}
private fun getSharedPrefs(name: String): SharedPreferencesBase {
// Use standalone prefs per section to preserve RAM
return prefs[name]?.get() ?: SharedPreferencesBase(name)
.also {
prefs[name] = WeakReference(it)
}
}
private fun getCodeKey(key: String) = "$key${KEY_DELIM}code"
private fun getVersionKey(key: String) = "$key${KEY_DELIM}version"
private fun getVariantKey(key: String) = "$key${KEY_DELIM}variant"
private fun getPrefsName(section: String) =
if (section.contains("/"))
throw CacheError("Slashes aren't allowed inside the pref name: $section")
else "$PREF_NAME$KEY_DELIM$section"
}

View file

@ -1,29 +0,0 @@
package com.liskovsoft.youtubeapi.app.nsigsolver.common
import app.morphe.extension.shared.utils.ResourceUtils.getRawResource
import app.morphe.extension.shared.utils.Utils
import com.liskovsoft.youtubeapi.app.nsigsolver.impl.V8ChallengeProvider.libPrefix
internal open class ScriptLoaderError(message: String, cause: Exception? = null) :
Exception(message, cause)
internal fun loadScript(filename: String, errorMsg: String? = null): String {
Utils.getContext() ?: throw ScriptLoaderError(formatError(errorMsg, "Context isn't available"))
val fixedFilename =
filename.replace(libPrefix, "")
.replace(".js", "")
return getRawResource(fixedFilename)
}
internal fun loadScript(filenames: List<String>, errorMsg: String? = null): String {
return buildString {
for (filename in filenames) {
append(loadScript(filename, errorMsg))
}
}
}
internal fun formatError(firstMsg: String?, secondMsg: String) =
firstMsg?.let { "$it: $secondMsg" } ?: secondMsg

View file

@ -1,5 +0,0 @@
package com.liskovsoft.youtubeapi.app.nsigsolver.common
internal object YouTubeInfoExtractor {
val cache: CacheService = CacheService
}

View file

@ -1,81 +0,0 @@
package com.liskovsoft.youtubeapi.app.nsigsolver.impl
import com.eclipsesource.v8.V8
import com.eclipsesource.v8.V8ScriptExecutionException
import com.liskovsoft.youtubeapi.app.nsigsolver.common.loadScript
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeProviderError
import com.liskovsoft.youtubeapi.app.nsigsolver.runtime.JsRuntimeChalBaseJCP
import com.liskovsoft.youtubeapi.app.nsigsolver.runtime.Script
import com.liskovsoft.youtubeapi.app.nsigsolver.runtime.ScriptSource
import com.liskovsoft.youtubeapi.app.nsigsolver.runtime.ScriptType
import com.liskovsoft.youtubeapi.app.nsigsolver.runtime.ScriptVariant
import java.util.concurrent.Callable
import java.util.concurrent.Executors
internal object V8ChallengeProvider : JsRuntimeChalBaseJCP() {
private val v8NpmLibFilename = listOf(
"${libPrefix}polyfill.js",
"${libPrefix}meriyah-6.1.4.min.js",
"${libPrefix}astring-1.9.0.min.js"
)
private val v8Executor = Executors.newSingleThreadExecutor()
private var v8Runtime: V8? = null
override fun iterScriptSources(): Sequence<Pair<ScriptSource, (ScriptType) -> Script?>> =
sequence {
for ((source, func) in super.iterScriptSources()) {
if (source == ScriptSource.WEB || source == ScriptSource.BUILTIN)
yield(Pair(ScriptSource.BUILTIN, ::v8NpmSource))
yield(Pair(source, func))
}
}
private fun v8NpmSource(scriptType: ScriptType): Script? {
if (scriptType != ScriptType.LIB)
return null
// V8-specific lib scripts that uses Deno NPM imports
val code = loadScript(v8NpmLibFilename, "Failed to read v8 challenge solver lib script")
return Script(scriptType, ScriptVariant.V8_NPM, ScriptSource.BUILTIN, scriptVersion, code)
}
override fun runJsRuntime(stdin: String): String {
warmup()
return runJS(stdin)
}
private fun runJS(stdin: String, warmup: Boolean = false): String {
try {
val result = v8Executor.submit(
Callable { v8Runtime?.executeStringScript(stdin) }
).get()
return if (warmup) "" else if (result.isNullOrEmpty())
throw JsChallengeProviderError("V8 runtime error: empty response")
else result
} catch (e: V8ScriptExecutionException) {
if (e.message?.contains("Invalid or unexpected token") ?: false)
ie.cache.clear(cacheSection) // cached data broken?
if (!warmup) {
shutDown()
}
throw JsChallengeProviderError("V8 runtime error: ${e.message}", e)
}
}
fun shutDown() {
v8Executor.submit {
v8Runtime?.release(false)
v8Runtime = null
}
v8Executor.shutdown()
}
fun warmup() {
if (v8Runtime != null) return
v8Executor.submit {
v8Runtime = V8.createV8Runtime()
}
runJS(constructCommonStdin(), true)
}
}

View file

@ -1,37 +0,0 @@
package com.liskovsoft.youtubeapi.app.nsigsolver.provider
import com.liskovsoft.youtubeapi.app.nsigsolver.common.YouTubeInfoExtractor
internal abstract class JsChallengeProvider {
protected val ie = YouTubeInfoExtractor
protected abstract val supportedTypes: List<JsChallengeType>
private fun validateRequest(request: JsChallengeRequest) {
// Validate request using built-in settings
if (request.type !in supportedTypes) {
throw JsChallengeProviderRejectedRequest("JS Challenge type ${request.type} is not supported by the provider ${this::class.simpleName}")
}
}
/**
* Solve multiple JS challenges and return the results
*/
fun bulkSolve(requests: List<JsChallengeRequest>): Sequence<JsChallengeProviderResponse> =
sequence {
val validatedRequests: MutableList<JsChallengeRequest> = mutableListOf()
for (request in requests) {
try {
validateRequest(request)
validatedRequests.add(request)
} catch (e: JsChallengeProviderRejectedRequest) {
yield(JsChallengeProviderResponse(request = request, error = e))
}
}
yieldAll(realBulkSolve(validatedRequests))
}
/**
* Subclasses can override this method to handle bulk solving
*/
protected abstract fun realBulkSolve(requests: List<JsChallengeRequest>): Sequence<JsChallengeProviderResponse>
}

View file

@ -1,31 +0,0 @@
package com.liskovsoft.youtubeapi.app.nsigsolver.provider
internal enum class JsChallengeType(val value: String) {
N("n"),
SIG("sig");
}
internal data class JsChallengeRequest(
val type: JsChallengeType,
val input: ChallengeInput,
val videoId: String? = null
)
internal data class ChallengeInput(
val challenge: String
)
internal data class ChallengeOutput(
val results: Map<String, String>
)
internal data class JsChallengeProviderResponse(
val request: JsChallengeRequest,
val response: JsChallengeResponse? = null,
val error: Exception? = null
)
internal data class JsChallengeResponse(
val type: JsChallengeType,
val output: ChallengeOutput
)

View file

@ -1,19 +0,0 @@
package com.liskovsoft.youtubeapi.app.nsigsolver.provider
internal open class InfoExtractorError(message: String, cause: Exception? = null) :
Exception(message, cause)
internal open class ContentProviderError(message: String, cause: Exception? = null) :
Exception(message, cause)
/**
* Reject the JsChallengeRequest (cannot handle the request)
*/
internal class JsChallengeProviderRejectedRequest(message: String, cause: Exception? = null) :
ContentProviderError(message, cause)
/**
* An error occurred while solving the challenge
*/
internal class JsChallengeProviderError(message: String, cause: Exception? = null) :
ContentProviderError(message, cause)

View file

@ -1,248 +0,0 @@
package com.liskovsoft.youtubeapi.app.nsigsolver.runtime
import androidx.annotation.GuardedBy
import app.morphe.extension.shared.innertube.utils.ThrottlingParameterUtils
import app.morphe.extension.shared.utils.Logger
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.liskovsoft.youtubeapi.app.nsigsolver.common.CachedData
import com.liskovsoft.youtubeapi.app.nsigsolver.common.loadScript
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.ChallengeOutput
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeProvider
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeProviderError
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeProviderRejectedRequest
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeProviderResponse
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeRequest
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeResponse
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeType
import java.util.Collections
internal abstract class JsRuntimeChalBaseJCP : JsChallengeProvider() {
private val tag = JsRuntimeChalBaseJCP::class.simpleName
val cacheSection = "challenge-solver"
private var playerJS = ""
private var playerJSIdentifier = ""
private val jcpGuideUrl = "https://github.com/yt-dlp/yt-dlp/wiki/YouTube-JS-Challenges"
private val repository = "yt-dlp/ejs"
override val supportedTypes = listOf(JsChallengeType.N, JsChallengeType.SIG)
protected val scriptVersion = "0.0.1"
internal val libPrefix = "nsigsolver/"
private val scriptFilenames = mapOf(
ScriptType.LIB to "${libPrefix}yt.solver.lib.js",
ScriptType.CORE to "${libPrefix}yt.solver.core.js"
)
private val minScriptFilenames = mapOf(
ScriptType.LIB to "yt.solver.lib.min.js",
ScriptType.CORE to "yt.solver.core.min.js"
)
protected abstract fun runJsRuntime(stdin: String): String
fun setPlayerJS(
jsCode: String,
jsIdentifier: String,
) {
playerJS = jsCode
playerJSIdentifier = jsIdentifier
}
override fun realBulkSolve(requests: List<JsChallengeRequest>): Sequence<JsChallengeProviderResponse> =
sequence {
val data = ie.cache.load(cacheSection, "player:$playerJSIdentifier")
var player = data?.code
val cached = if (player != null) {
true
} else {
player = playerJS
false
}
val stdin = constructStdin(player, cached, requests)
val stdout = runJsRuntime(stdin)
val gson = Gson()
val output: SolverOutput = try {
gson.fromJson(stdout, solverOutputType)
} catch (e: JsonSyntaxException) {
throw JsChallengeProviderError("Cannot parse solver output", e)
}
if (output.type == "error")
throw JsChallengeProviderError(output.error ?: "Unknown solver output error")
val preprocessed = output.preprocessed_player
if (preprocessed != null)
ie.cache.store(cacheSection, "player:$playerJSIdentifier", CachedData(preprocessed))
for ((request, responseData) in requests.zip(output.responses)) {
if (responseData.type == "error") {
yield(
JsChallengeProviderResponse(
request,
null,
JsChallengeProviderError(
responseData.error ?: "Unknown solver output error"
)
)
)
} else {
yield(
JsChallengeProviderResponse(
request,
JsChallengeResponse(request.type, ChallengeOutput(responseData.data))
)
)
}
}
}
private fun constructStdin(
playerJS: String,
preprocessed: Boolean,
requests: List<JsChallengeRequest>
): String {
val jsonRequests = requests.map { request ->
mapOf(
// TODO: i despise nsig name
//"type" to if (request.type.value == "n") "nsig" else request.type.value,
"type" to request.type.value,
"challenges" to listOf(request.input.challenge)
)
}
val data = if (preprocessed) {
mapOf(
"type" to "preprocessed",
"preprocessed_player" to playerJS,
"requests" to jsonRequests
)
} else {
mapOf(
"type" to "player",
"player" to playerJS,
"requests" to jsonRequests,
"output_preprocessed" to true
)
}
val gson = Gson()
val jsonData = gson.toJson(data)
return """
JSON.stringify(jsc($jsonData));
"""
}
protected fun constructCommonStdin(): String {
return """
${libScript.code}
${coreScript.code}
"";
"""
}
// region: challenge solver script
private val libScript: Script by lazy {
getScript(ScriptType.LIB)
}
private val coreScript: Script by lazy {
getScript(ScriptType.CORE)
}
private fun getScript(scriptType: ScriptType): Script {
for ((_, fromSource) in iterScriptSources()) {
val script = fromSource(scriptType) ?: continue
if (script.version != scriptVersion)
Logger.printWarn {
"Challenge solver ${scriptType.value} script version ${script.version} " +
"is not supported (source: ${script.source.value}, supported version: $scriptVersion)"
}
Logger.printDebug {
"Using challenge solver ${script.type.value} script v${script.version} " +
"(source: ${script.source.value}, variant: ${script.variant.value})"
}
return script
}
throw JsChallengeProviderRejectedRequest("No usable challenge solver ${scriptType.value} script available")
}
protected open fun iterScriptSources(): Sequence<Pair<ScriptSource, (scriptType: ScriptType) -> Script?>> =
sequence {
yieldAll(
listOf(
Pair(ScriptSource.CACHE, ::cachedSource),
Pair(ScriptSource.BUILTIN, ::builtinSource),
Pair(ScriptSource.WEB, ::webReleaseSource)
)
)
}
private fun cachedSource(scriptType: ScriptType): Script? {
val data = ie.cache.load(cacheSection, scriptType.value) ?: return null
return Script(
scriptType,
ScriptVariant.valueOf(data.variant ?: "unknown"),
ScriptSource.CACHE,
data.version ?: "unknown",
data.code
)
}
private fun builtinSource(scriptType: ScriptType): Script? {
val fileName = scriptFilenames[scriptType] ?: return null
val code =
loadScript(fileName, "Failed to read builtin challenge solver ${scriptType.value}")
return Script(
scriptType,
ScriptVariant.UNMINIFIED,
ScriptSource.BUILTIN,
scriptVersion,
code
)
}
@GuardedBy("itself")
val cache: MutableMap<String, String> = Collections.synchronizedMap(
object : LinkedHashMap<String, String>(30) {
private val CACHE_LIMIT = 15
override fun removeEldestEntry(eldest: Map.Entry<String, String>): Boolean {
return size > CACHE_LIMIT // Evict the oldest entry if over the cache limit.
}
})
private fun webReleaseSource(scriptType: ScriptType): Script? {
val fileName = minScriptFilenames[scriptType] ?: return null
if (fileName.isNotEmpty()) {
synchronized(cache) {
var code = cache[fileName]
if (code == null) {
val url =
"https://github.com/$repository/releases/download/$scriptVersion/$fileName"
code = ThrottlingParameterUtils.fetch(url)
Logger.printDebug { "[${tag}] Downloading challenge solver ${scriptType.value} script from $url" }
if (code.isNullOrEmpty()) {
return null
} else {
cache[fileName] = code
ie.cache.store(cacheSection, scriptType.value, CachedData(code))
}
}
return Script(
scriptType,
ScriptVariant.MINIFIED,
ScriptSource.WEB,
scriptVersion,
code
)
}
}
return null
}
// endregion: challenge solver script
}

View file

@ -1,18 +0,0 @@
package com.liskovsoft.youtubeapi.app.nsigsolver.runtime
import com.google.gson.reflect.TypeToken
internal val solverOutputType = object : TypeToken<SolverOutput>() {}.type
internal data class SolverOutput(
val type: String,
val error: String?,
val preprocessed_player: String?,
val responses: List<ResponseData>
)
internal data class ResponseData(
val type: String,
val error: String?,
val data: Map<String, String>
)

View file

@ -1,47 +0,0 @@
package com.liskovsoft.youtubeapi.app.nsigsolver.runtime
import java.security.MessageDigest
internal enum class ScriptType(val value: String) {
LIB("lib"),
CORE("core");
}
internal enum class ScriptVariant(val value: String) {
UNKNOWN("unknown"),
MINIFIED("minified"),
UNMINIFIED("unminified"),
DENO_NPM("deno_npm"),
BUN_NPM("bun_npm"),
V8_NPM("v8_npm");
}
internal enum class ScriptSource(val value: String) {
PYPACKAGE("python package"),
BINARY("binary"),
CACHE("cache"),
WEB("web"),
BUILTIN("builtin");
}
internal data class Script(
val type: ScriptType,
val variant: ScriptVariant,
val source: ScriptSource,
val version: String,
val code: String,
) {
val hash: String by lazy {
val digest = MessageDigest.getInstance("SHA3-512")
val bytes = digest.digest(code.toByteArray(Charsets.UTF_8))
bytes.joinToString("") { "%02x".format(it) }
}
override fun toString(): String {
return "<Script ${type.value} v${version} (source: ${source.value}) variant=${variant.value} size=${code.length} hash=${
hash.take(
7
)
}...>"
}
}

View file

@ -1,64 +0,0 @@
package com.liskovsoft.youtubeapi.app.playerdata
import app.morphe.extension.shared.utils.Logger
import com.liskovsoft.youtubeapi.app.nsigsolver.impl.V8ChallengeProvider
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.ChallengeInput
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeRequest
import com.liskovsoft.youtubeapi.app.nsigsolver.provider.JsChallengeType
internal class PlayerDataExtractor(playerJS: String, playerJSIdentifier: String) {
private var nSigTmp: Pair<String, String?>? = null
private var initialized: Boolean = false
init {
if (!initialized) {
initialized = true
V8ChallengeProvider.setPlayerJS(playerJS, playerJSIdentifier)
V8ChallengeProvider.warmup()
checkAllData()
}
}
fun extractNSig(nParam: String): String? {
if (nSigTmp?.first == nParam) return nSigTmp?.second
val nSig = extractNSigReal(nParam)
nSigTmp = Pair(nParam, nSig)
return nSig
}
fun extractSig(sParam: String): String? {
val result = V8ChallengeProvider.bulkSolve(
listOfNotNull(JsChallengeRequest(JsChallengeType.SIG, ChallengeInput(sParam)))
)
return result.toList().firstOrNull()?.response?.output?.results?.get(sParam)
}
private fun extractNSigReal(nParam: String): String? {
val result = V8ChallengeProvider.bulkSolve(
listOfNotNull(JsChallengeRequest(JsChallengeType.N, ChallengeInput(nParam)))
)
return result.toList().firstOrNull()?.response?.output?.results?.get(nParam)
}
private fun checkAllData() {
val param = "5cNpZqIJ7ixNqU68Y7S"
try {
V8ChallengeProvider.bulkSolve(
listOf(
JsChallengeRequest(JsChallengeType.N, ChallengeInput(param)),
JsChallengeRequest(JsChallengeType.SIG, ChallengeInput(param)),
)
)
} catch (e: Exception) {
Logger.printException({ "checkAllData failed" }, e)
}
}
}

View file

@ -4,19 +4,21 @@ morphe-patcher = "1.2.0"
smali = "b6365a84f4"
gson = "2.13.2"
agp = "8.2.2"
annotation = "1.9.1"
annotation = "1.8.1"
androidx-javascriptengine = "1.1.0-alpha01"
collections4 = "4.5.0"
desugar = "2.1.5"
nanohttpd = "2.3.1"
hiddenapi = "6.1"
lang3 = "3.20.0"
okhttp3 = "4.12.0"
protobuf = "0.9.5"
protoc = "3.25.2"
protobuf = "0.9.6"
protoc = "4.34.0"
preference = "1.2.1"
[libraries]
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
androidx-javascriptengine = { module = "androidx.javascriptengine:javascriptengine", version.ref = "androidx-javascriptengine" }
collections4 = { module = "org.apache.commons:commons-collections4", version.ref = "collections4" }
desugar-jdk-libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }

View file

@ -327,7 +327,7 @@ val intentIcon = intentKey.associateWith { "${it}_icon" }
private val emptyTitles = setOf(
"morphe_spoof_video_streams_client_type",
"morphe_spoof_video_streams_disable_player_js_update",
"morphe_spoof_video_streams_player_js_hash",
"morphe_spoof_video_streams_player_js_hash_value",
"morphe_spoof_video_streams_sign_in_android_vr_about",
"revanced_custom_filter_strings",
"revanced_custom_playback_speeds",
@ -341,7 +341,6 @@ private val emptyTitles = setOf(
"revanced_replace_flyout_menu_dismiss_queue_continue_watch",
"revanced_replace_flyout_menu_report_only_player",
"revanced_spoof_client_type",
"revanced_spoof_streaming_data_default_client",
"revanced_spoof_streaming_data_no_sdk_auth_token_about",
"revanced_spoof_streaming_data_sign_in_android_no_sdk_about",
"revanced_spoof_streaming_data_sign_in_android_vr_about",

View file

@ -63,7 +63,7 @@ val spoofStreamingDataPatch = spoofVideoStreamsPatch(
)
addPreferenceWithIntent(
CategoryType.MISC,
"morphe_spoof_video_streams_player_js_hash",
"morphe_spoof_video_streams_player_js_hash_value",
"morphe_spoof_video_streams_disable_player_js_update"
)
},

View file

@ -17,7 +17,7 @@ import app.morphe.patcher.extensions.InstructionExtensions.getInstruction
import app.morphe.patcher.patch.BytecodePatchBuilder
import app.morphe.patcher.patch.BytecodePatchContext
import app.morphe.patcher.patch.bytecodePatch
import app.morphe.patcher.patch.rawResourcePatch
import app.morphe.patcher.patch.resourcePatch
import app.morphe.patcher.util.proxy.mutableTypes.MutableMethod
import app.morphe.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.morphe.patches.shared.misc.fix.proto.fixProtoLibraryPatch
@ -29,7 +29,6 @@ import app.morphe.util.findInstructionIndicesReversedOrThrow
import app.morphe.util.getReference
import app.morphe.util.indexOfFirstInstructionOrThrow
import app.morphe.util.indexOfFirstInstructionReversedOrThrow
import app.morphe.util.inputStreamFromBundledResource
import app.morphe.util.insertLiteralOverride
import app.morphe.util.setExtensionIsPatchIncluded
import com.android.tools.smali.dexlib2.AccessFlags
@ -42,9 +41,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
import org.w3c.dom.Element
import java.lang.ref.WeakReference
import java.nio.file.Files
internal const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/morphe/extension/shared/spoof/SpoofVideoStreamsPatch;"
@ -52,34 +49,9 @@ internal const val EXTENSION_CLASS_DESCRIPTOR =
private lateinit var buildRequestMethodRef : WeakReference<MutableMethod>
private var buildRequestMethodURLRegister = -1
private val spoofVideoStreamsRawResourcePatch = rawResourcePatch {
private val spoofVideoStreamsResourcePatch = resourcePatch {
execute {
// region copy the j2v8 library.
setOf(
"arm64-v8a",
"armeabi-v7a",
"x86",
"x86_64"
).forEach { arch ->
val architectureDirectory = get("lib/$arch")
// For YouTube Music, there is only one architecture in the app.
// Copy only if the architecture folder exists.
if (architectureDirectory.exists()) {
val inputStream = inputStreamFromBundledResource(
"spoof/jniLibs",
"$arch/libj2v8.so"
)
if (inputStream != null) {
Files.copy(
inputStream,
architectureDirectory.resolve("libj2v8.so").toPath(),
)
}
}
}
// region copy the ejs wrapper.
copyResources(
"spoof",
@ -88,19 +60,11 @@ private val spoofVideoStreamsRawResourcePatch = rawResourcePatch {
"astring-1.9.0.min.js",
"meriyah-6.1.4.min.js",
"polyfill.js",
"yt.solver.core.js", // yt-dlp-ejs 0.5.1: https://github.com/MorpheApp/ejs/releases/tag/0.5.1
"yt.solver.core.js", // yt-dlp-ejs 0.8.0: https://github.com/yt-dlp/ejs/releases/tag/0.8.0
"yt.solver.wrapper.js",
)
)
// Fix compile error in YouTube Music.
document("AndroidManifest.xml").use { document ->
val applicationNode =
document
.getElementsByTagName("application")
.item(0) as Element
applicationNode.setAttribute("android:extractNativeLibs", "true")
}
// endregion
}
}
@ -123,7 +87,7 @@ internal fun spoofVideoStreamsPatch(
dependsOn(
fixProtoLibraryPatch,
spoofVideoStreamsRawResourcePatch,
spoofVideoStreamsResourcePatch,
)
execute {

View file

@ -72,13 +72,6 @@ const val EXTENSION_STREAMING_DATA_OUTER_CLASS_DESCRIPTOR =
const val EXTENSION_STREAMING_DATA_INTERFACE =
"$SPOOF_PATH/StreamingDataOuterClassPatch${'$'}StreamingDataMessage;"
const val EXTENSION_YOUTUBE_SPOOF_PATH =
app.morphe.patches.youtube.utils.extension.Constants.SPOOF_PATH
const val EXTENSION_RELOAD_VIDEO_CLASS_DESCRIPTOR =
"$EXTENSION_YOUTUBE_SPOOF_PATH/ReloadVideoPatch;"
const val EXTENSION_RELOAD_VIDEO_BUTTON_CLASS_DESCRIPTOR =
"$EXTENSION_YOUTUBE_SPOOF_PATH/ui/ReloadVideoButton;"
fun spoofStreamingDataPatch(
block: BytecodePatchBuilder.() -> Unit = {},
isYouTube: BytecodePatchBuilder.() -> Boolean = { false },

View file

@ -0,0 +1,31 @@
/*
* Copyright 2026 Morphe.
* https://github.com/MorpheApp/morphe-patches
*
* See the included NOTICE file for GPLv3 §7(b) and §7(c) terms that apply to Morphe contributions.
*/
package app.morphe.patches.youtube.interaction.reload
import app.morphe.patcher.Fingerprint
import app.morphe.patcher.methodCall
import app.morphe.patcher.opcode
import app.morphe.patcher.string
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal object MiniAppOpenYtContentCommandEndpointFingerprint : Fingerprint(
accessFlags = listOf(AccessFlags.PUBLIC, AccessFlags.FINAL),
returnType = "V",
parameters = listOf("L"),
filters = listOf(
string("no error message"),
opcode(Opcode.IF_EQZ),
methodCall(
opcode = Opcode.INVOKE_VIRTUAL,
parameters = listOf(),
returnType = "V"
),
string("InvalidProtocolBufferException while decoding MiniAppMetadata for MiniAppOpenYTContentCommand: ")
)
)

View file

@ -0,0 +1,143 @@
/*
* Copyright 2026 Morphe.
* https://github.com/MorpheApp/morphe-patches
*
* See the included NOTICE file for GPLv3 §7(b) and §7(c) terms that apply to Morphe contributions.
*/
package app.morphe.patches.youtube.interaction.reload
import app.morphe.patcher.extensions.InstructionExtensions.addInstruction
import app.morphe.patcher.extensions.InstructionExtensions.addInstructions
import app.morphe.patcher.extensions.InstructionExtensions.getInstruction
import app.morphe.patcher.patch.bytecodePatch
import app.morphe.patcher.patch.resourcePatch
import app.morphe.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.morphe.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.morphe.patches.youtube.utils.mainactivity.mainActivityFingerprint
import app.morphe.patches.youtube.utils.playercontrols.addTopControl
import app.morphe.patches.youtube.utils.playercontrols.injectControl
import app.morphe.patches.youtube.utils.playercontrols.playerControlsPatch
import app.morphe.patches.youtube.utils.settings.ResourceUtils.addPreference
import app.morphe.patches.youtube.utils.settings.settingsPatch
import app.morphe.patches.youtube.video.information.videoInformationPatch
import app.morphe.util.ResourceGroup
import app.morphe.util.copyResources
import app.morphe.util.fingerprint.methodOrThrow
import app.morphe.util.getReference
import app.morphe.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.util.MethodUtil
private val reloadVideoResourcePatch = resourcePatch {
dependsOn(
settingsPatch,
playerControlsPatch,
)
execute {
addPreference(
arrayOf(
"PREFERENCE_SCREEN: PLAYER",
"PREFERENCE_SCREENS: PLAYER_BUTTONS",
"SETTINGS: RELOAD_VIDEO",
)
)
copyResources(
"youtube/reloadbutton/default",
ResourceGroup(
resourceDirectoryName = "drawable",
"morphe_reload_video.xml",
),
)
}
}
private const val EXTENSION_BUTTON_DESCRIPTOR =
"Lapp/morphe/extension/youtube/videoplayer/ReloadVideoButton;"
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/morphe/extension/youtube/patches/ReloadVideoPatch;"
private const val EXTENSION_PLAYER_INTERFACE =
"Lapp/morphe/extension/youtube/patches/ReloadVideoPatch\$PlayerInterface;"
@Suppress("unused")
val reloadVideoPatch = bytecodePatch(
name = "Reload video",
description = "Adds an option to display a button in the video player to reload the current video.",
) {
dependsOn(
reloadVideoResourcePatch,
playerControlsPatch,
videoInformationPatch,
)
compatibleWith(COMPATIBLE_PACKAGE)
execute {
injectControl(EXTENSION_BUTTON_DESCRIPTOR)
// Main activity is used to launch downloader intent.
mainActivityFingerprint.methodOrThrow().addInstruction(
0,
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->setMainActivity(Landroid/app/Activity;)V"
)
val dismissPlayerInnerMethod = MiniAppOpenYtContentCommandEndpointFingerprint
.instructionMatches[2]
.getInstruction<ReferenceInstruction>()
.getReference<MethodReference>()!!
mutableClassDefBy(dismissPlayerInnerMethod.definingClass).apply {
// Add interface and helper methods to allow extension code to call obfuscated methods.
interfaces.add(EXTENSION_PLAYER_INTERFACE)
// Add methods to access obfuscated player methods.
methods.add(
ImmutableMethod(
type,
"patch_dismissPlayer",
listOf(),
"V",
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
null,
null,
MutableMethodImplementation(2),
).toMutable().apply {
addInstructions(
0,
"""
invoke-virtual { p0 }, $dismissPlayerInnerMethod
return-void
"""
)
}
)
methods.single { method ->
MethodUtil.isConstructor(method)
}.apply {
val index = indexOfFirstInstructionReversedOrThrow(Opcode.RETURN_VOID)
addInstruction(
index,
"invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->initialize($EXTENSION_PLAYER_INTERFACE)V"
)
}
}
}
finalize {
addTopControl(
"youtube/reloadbutton/shared",
"@+id/morphe_reload_video_button",
"@+id/morphe_reload_video_button"
)
}
}

View file

@ -83,7 +83,7 @@
<string-array name="morphe_spoof_video_streams_client_type_entry_values">
<item>ANDROID_REEL</item>
<item>ANDROID_MUSIC_NO_SDK</item>
<item>ANDROID_VR_1_47_48</item>
<item>ANDROID_VR_1_64</item>
<item>TV</item>
<item>VISIONOS</item>
</string-array>

View file

@ -52,8 +52,8 @@ Tap the continue button and allow optimization changes."</string>
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Player JavaScript hashes are valid for approximately one month, and outdated hashes may be revoked by GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Try enabling: Spoof video streams &gt; Android VR sign in</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Could not fetch any client streams</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Enter a valid player JavaScript hash</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Player JavaScript hash</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Enter a valid player JavaScript hash</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Player JavaScript hash</string>
<string name="morphe_spoof_video_streams_screen_summary">Spoof the client video streams to prevent playback issues</string>
<string name="morphe_spoof_video_streams_screen_title">Spoof video streams</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Not signed in to Android VR. Tap here to sign in</string>
@ -628,7 +628,6 @@ Click to see how to issue an API key."</string>
Side effect (Block request):
• App may be forced to close when using a VPN or DNS."</string>
<string name="revanced_spoof_client_type_title">Default client</string>
<string name="revanced_spoof_streaming_data_default_client_title">Default client</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">Click to see how to get an authentication token.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">About Android No SDK Auth</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Click here to see how to sign in to Android No SDK.</string>
@ -673,8 +672,6 @@ Side effect (Block request):
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Failed to issue an authentication token.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Click Reset and try again.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Authentication token and activation code reset.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Shows the client used to fetch streaming data in Stats for nerds.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Show in Stats for nerds</string>
<string name="revanced_spoof_streaming_data_summary">Spoof the client video streams to prevent playback issues</string>
<string name="revanced_spoof_streaming_data_title">Spoof video streams</string>
<string name="revanced_spoof_streaming_data_vr_auth_token_about_summary">Click to see how to get an authentication token.</string>
@ -693,10 +690,6 @@ Side effect (Block request):
• Replace domain: Follows the watch history settings of Google account.
• Block watch history: Watch history is blocked."</string>
<string name="revanced_watch_history_type_title">Watch history type</string>
<string name="revanced_webview_contextual_menu_get_cookies">Get Cookies</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Get Data Sync Id</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Get Visitor Data</string>
<string name="revanced_webview_contextual_menu_refresh">Refresh</string>
<string name="revanced_webview_cookies">Cookies</string>
<string name="revanced_webview_copy">Copy</string>
<string name="revanced_webview_data_sync_id">Data Sync Id</string>

View file

@ -53,8 +53,8 @@
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">تكون تجزئات مشغل JavaScript صالحة لمدة شهر تقريبًا، وقد يتم إلغاء التجزئات القديمة بواسطة GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">جرب تمكين: تضمين تدفقات الفيديو &gt; تسجيل الدخول إلى Android VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">تعذر جلب أي تدفقات عميل</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">أدخل تجزئة مشغل JavaScript صالحة</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">تجزئة مشغل JavaScript</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">أدخل تجزئة مشغل JavaScript صالحة</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">تجزئة مشغل JavaScript</string>
<string name="morphe_spoof_video_streams_screen_summary">تزييف بثوث الفيديو الخاصة بالعميل لمنع مشكلات التشغيل</string>
<string name="morphe_spoof_video_streams_screen_title">Spoof Video Streams</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">لم يتم تسجيل الدخول إلى Android VR. انقر هنا لتسجيل الدخول</string>
@ -84,6 +84,7 @@
<string name="morphe_spoof_video_streams_sign_in_android_vr_success_dialog_title">تم تسجيل الدخول بنجاح</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_toast_get_activation_code_failed">فشل في إصدار رمز التفعيل</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_toast_get_authorization_code_failed">فشل في إصدار رمز المصادقة</string>
<string name="morphe_spoof_video_streams_summary">تزوير تدفقات فيديو العميل لتفادي مشاكل التشغيل</string>
<string name="morphe_spoof_video_streams_summary_off">"لا يتم تزييف تدفقات الفيديو
قد لا يعمل تشغيل الفيديو"</string>
@ -92,8 +93,27 @@
إذا كنت مستخدمًا لـ YouTube Premium، فقد لا يكون هذا الإعداد مطلوبًا"</string>
<string name="morphe_spoof_video_streams_title">Spoof Video Streams</string>
<string name="morphe_spoof_video_streams_user_dialog_message">قد يؤدي إيقاف تشغيل هذا الإعداد إلى مشكلات في التشغيل.</string>
<string name="revanced_add_miniplayer_next_button_summary">إضافة زر المسار التالي إلى المشغل المصغّر</string>
<string name="revanced_add_miniplayer_next_button_title">إضافة زر المسار التالي إلى المشغل المصغّر</string>
<string name="revanced_add_miniplayer_previous_button_summary">إضافة زر المسار السابق إلى المشغل المصغّر</string>
<string name="revanced_add_miniplayer_previous_button_title">إضافة زر المسار السابق إلى المشغل المصغّر</string>
<string name="revanced_app_info_dialog_message">&lt;b&gt;اسم التطبيق&lt;/b&gt;&lt;br&gt;&lt;small&gt;%1$s&lt;/small&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;إصدار التطبيق&lt;/b&gt;&lt;br&gt;&lt;small&gt;%2$s&lt;/small&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;تصحيحات ReVanced&lt;/b&gt;&lt;br&gt;&lt;small&gt;%3$s&lt;/small&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;تاريخ التصحيح&lt;/b&gt;&lt;br&gt;&lt;small&gt;%4$s&lt;/small&gt;</string>
<string name="revanced_app_info_dialog_title">معلومات التطبيق</string>
<string name="revanced_app_info_summary">انقر لعرض اسم التطبيق وإصدار التطبيق وإصدار التصحيح وتاريخ التصحيح</string>
<string name="revanced_app_info_title">معلومات التطبيق</string>
<string name="revanced_bypass_image_region_restrictions_summary">تجاوز النطاق المحظور في بعض المناطق لاستلام الصور المصغّرة للقوائم وصور رمز القنوات وغيرها</string>
<string name="revanced_bypass_image_region_restrictions_title">تجاوز القيود على منطقة الصورة</string>
<string name="revanced_change_action_bar_position_summary">نقل شريط الإجراءات إلى أسفل زر التشغيل</string>
<string name="revanced_change_action_bar_position_title">تغيير موضع شريط الإجراءات</string>
<string name="revanced_change_miniplayer_color_summary">تغيير لون المشغل المصغّر ليتطابق مع لون المشغل بملء الشاشة</string>
<string name="revanced_change_miniplayer_color_title">تغيير لون المشغل المصغّر</string>
<string name="revanced_change_player_background_color_summary">تغيير لون خلفية المشغل إلى لون مخصص</string>
<string name="revanced_change_player_background_color_title">تغيير لون خلفية المشغل</string>
<string name="revanced_change_seekbar_position_summary">نقل شريط التقديم إلى أسفل زر التشغيل</string>
<string name="revanced_change_seekbar_position_title">تغيير موضع شريط التقديم</string>
<string name="revanced_change_share_sheet_summary">تغيير قائمة المشاركة داخل التطبيق إلى قائمة مشاركة النظام</string>
<string name="revanced_change_share_sheet_title">تغيير لوح مشاركة</string>
<string name="revanced_change_start_page_entry_chart">القوائم الموسيقية</string>
<string name="revanced_change_start_page_entry_charts">الرسوم البيانية</string>
<string name="revanced_change_start_page_entry_default">الافتراضية</string>
<string name="revanced_change_start_page_entry_episodes_for_later">حلقات للمشاهدة لاحقًا</string>
@ -102,80 +122,437 @@
<string name="revanced_change_start_page_entry_library">المكتبة</string>
<string name="revanced_change_start_page_entry_liked_music">موسيقى أعجبتني</string>
<string name="revanced_change_start_page_entry_podcasts">البودكاست</string>
<string name="revanced_change_start_page_entry_samples">عينات</string>
<string name="revanced_change_start_page_entry_search">البحث</string>
<string name="revanced_change_start_page_entry_subscription">الاشتراكات</string>
<string name="revanced_change_start_page_entry_subscriptions">الاشتراكات</string>
<string name="revanced_change_start_page_summary">تحديد الصفحة التي يفتح فيها التطبيق</string>
<string name="revanced_change_start_page_title">تغيير صفحة البداية</string>
<string name="revanced_credits_title">المساهمون
</string>
<string name="revanced_custom_filter_strings_summary">قائمة سلاسل منشئ مسار المكونات المراد تصفيتها، مفصولة بسطور جديدة.</string>
<string name="revanced_custom_filter_strings_title">فلتر مخصص</string>
<string name="revanced_custom_filter_summary">تمكين عامل التصفية المخصص لإخفاء مكونات الواجهة</string>
<string name="revanced_custom_filter_title">تمكين الفلتر المخصص</string>
<string name="revanced_custom_filter_toast_invalid_syntax">فلتر مخصص غير صالح: %s.</string>
<string name="revanced_custom_navigation_bar_color_value_invalid_invalid_toast">قيمة لون شريط التنقل غير صالحة</string>
<string name="revanced_custom_navigation_bar_color_value_summary">اكتب الرمز السداسي العشري (hex) للون شريط التنقل</string>
<string name="revanced_custom_navigation_bar_color_value_title">قيمة لون شريط التنقل المخصصة</string>
<string name="revanced_custom_playback_speeds_invalid">يجب أن تكون السرعات المخصصة أقل من %sx.</string>
<string name="revanced_custom_playback_speeds_parse_exception">سرعات التشغيل المخصصة غير صالحة.</string>
<string name="revanced_custom_playback_speeds_summary">إضافة أو تغيير سرعات التشغيل المتاحة.</string>
<string name="revanced_custom_playback_speeds_title">تعديل سرعة التشغيل المخصصة</string>
<string name="revanced_custom_player_background_color_primary_summary">اكتب الرمز السداسي العشري (hex) للون الخلفية الأساسي للمشغل.
استخدم الألوان الداكنة إن أمكن، لأن التطبيق لا يدعم السمات الفاتحة.</string>
<string name="revanced_custom_player_background_color_primary_title">اللون الأساسي لخلفية المشغل</string>
<string name="revanced_custom_player_background_color_secondary_summary">أدخل رمز اللون (Hex) للخلفية الثانوية للمشغل. استخدم ألواناً داكنة إن أمكن، حيث أن التطبيق لا يدعم المظاهر الفاتحة.
</string>
<string name="revanced_custom_player_background_color_secondary_title">اللون الثانوي لخلفية المشغل
</string>
<string name="revanced_custom_player_background_invalid_toast">لون خلفية المشغل غير صالح.
</string>
<string name="revanced_debug_protobuffer_summary">تضمين الـ Proto Buffer في سجل التصحيح.
</string>
<string name="revanced_debug_protobuffer_title">سجل بروتوكول التخزين المؤقت</string>
<string name="revanced_debug_spannable_summary">تضمين الـ Spannable في سجل التصحيح.
</string>
<string name="revanced_debug_spannable_title">سجل قابل للامتداد</string>
<string name="revanced_debug_summary">طباعة سجل التصحيح.
</string>
<string name="revanced_debug_title">تمكين سجلات التصحيح</string>
<string name="revanced_default_app_settings_summary">لفتح روابط YouTube Music في RVX Music، قم بتمكين خيار فتح الروابط المدعومة وتمكين جميع عناوين الويب المدعومة.</string>
<string name="revanced_default_app_settings_title">فتح إعدادات التطبيق الافتراضية</string>
<string name="revanced_disable_auto_audio_tracks_summary">تعطيل المسارات الصوتية التلقائية المفروضة للبودكاست.
قيد: في حال تفعيل "تزييف بيانات البث" (Spoof streaming data) وكان العميل الافتراضي هو "Android VR Auth"، فقد لا تعمل هذه الميزة.
</string>
<string name="revanced_disable_auto_audio_tracks_title">تعطيل المقطع الصوتي التلقائي المفروض</string>
<string name="revanced_disable_auto_captions_summary">تعطيل الفرض التلقائي للترجمات والشروحات (Auto captions).
</string>
<string name="revanced_disable_auto_captions_title">تعطيل الترجمة التلقائية المفروضة</string>
<string name="revanced_disable_cairo_splash_animation_summary">تعطيل الرسوم المتحركة لشاشة البدء (Cairo) عند تشغيل التطبيق.
</string>
<string name="revanced_disable_cairo_splash_animation_title">تعطيل رسوم البداية المتحركة (Cairo)
</string>
<string name="revanced_disable_dislike_redirection_summary">تعطيل الانتقال التلقائي للمقطع التالي عند الضغط على زر عدم الإعجاب (Dislike).
</string>
<string name="revanced_disable_dislike_redirection_title">تعطيل إعادة التوجيه عند عدم الإعجاب
</string>
<string name="revanced_disable_drc_audio_summary">تعطيل ضغط النطاق الديناميكي (DRC) المطبق على الصوت.
</string>
<string name="revanced_disable_drc_audio_title">تعطيل صوت DRC</string>
<string name="revanced_disable_miniplayer_gesture_summary">تعطيل السحب لتغيير المقاطع في المشغل المصغر.
</string>
<string name="revanced_disable_miniplayer_gesture_title">تعطيل إيماءة المشغل المصغر
</string>
<string name="revanced_disable_music_video_in_album_redirect_type_entry_on_click">النقر للتبديل بين أغنية / فيديو
</string>
<string name="revanced_disable_music_video_in_album_redirect_type_entry_on_long_click">النقر مطولاً للتبديل بين أغنية / فيديو
</string>
<string name="revanced_disable_music_video_in_album_redirect_type_entry_redirect">إعادة توجيه
</string>
<string name="revanced_disable_music_video_in_album_redirect_type_summary">تحديد كيفية إعادة التوجيه إلى الأغنية الرسمية.
</string>
<string name="revanced_disable_music_video_in_album_redirect_type_title">نوع إعادة التوجيه
</string>
<string name="revanced_disable_music_video_in_album_summary">عندما يقوم مستخدم غير مشترك (Non-premium) بتشغيل أغنية ضمن ألبوم، يتم أحياناً تشغيل الفيديو كليب بدلاً من الأغنية الرسمية.
البحث عن الأغنية الرسمية في حال تم اكتشاف تشغيل فيديو كليب من ألبوم.
القيود: قد لا يتم إعادة توجيه فيديوهات الأطفال.
</string>
<string name="revanced_disable_music_video_in_album_title">تعطيل فيديو كليب الألبومات
</string>
<string name="revanced_disable_player_gesture_summary">تعطيل السحب لتغيير المقاطع في المشغل.
</string>
<string name="revanced_disable_player_gesture_title">تعطيل إيماءة المشغل
</string>
<string name="revanced_disable_quic_protocol_summary">"يُعطل بروتوكول QUIC الخاص بـ CronetEngine."</string>
<string name="revanced_disable_quic_protocol_title">تعطيل بروتوكول QUIC</string>
<string name="revanced_disable_trim_silence_summary">تعطيل مفتاح "تخطي الصمت" في قائمة سرعة التشغيل.
معلومات:
• هذه الميزة مخصصة للبودكاست.
• هذه الميزة لا تزال قيد التطوير، لذا قد لا تكون مستقرة.
</string>
<string name="revanced_disable_trim_silence_title">تعطيل مفتاح تخطي الصمت
</string>
<string name="revanced_enable_comments_scroll_top_summary">"عند النقر على عنوان لوحة التعليقات ('التعليقات')، ينتقل إلى الأعلى.
القيود:
• لا ينطبق على ردود التعليقات."</string>
<string name="revanced_enable_comments_scroll_top_title">تمكين التمرير إلى الأعلى في التعليقات</string>
<string name="revanced_enable_compact_dialog_summary">تفعيل القائمة المدمجة للهواتف.
القيود:
• يقل حجم غلاف الألبوم في تبويب المكتبة عند عرضها كشبكة.
• قد يظهر مظهر مؤقت النوم بشكل غير مألوف.
</string>
<string name="revanced_enable_compact_dialog_title">تفعيل الحوار المدمج
</string>
<string name="revanced_enable_custom_navigation_bar_color_summary">تعيين لون شريط التنقل.
</string>
<string name="revanced_enable_custom_navigation_bar_color_title">تفعيل لون مخصص لشريط التنقل
</string>
<string name="revanced_enable_forced_miniplayer_summary">تفعيل المشغل المصغر الإجباري عند الانتقال لمقطع جديد.
</string>
<string name="revanced_enable_forced_miniplayer_title">تفعيل المشغل المصغر الإجباري
</string>
<string name="revanced_enable_landscape_mode_summary">تفعيل الوضع الأفقي عند تدوير الشاشة في الهواتف.
</string>
<string name="revanced_enable_landscape_mode_title">تفعيل الوضع الأفقي
</string>
<string name="revanced_enable_mini_player_next_button_summary">إضافة زر المقطع التالي إلى المشغل المصغر.
</string>
<string name="revanced_enable_mini_player_next_button_title">إضافة زر "التالي" للمشغل المصغر</string>
<string name="revanced_enable_mini_player_previous_button_summary">إضافة زر المقطع السابق إلى المشغل المصغر.
</string>
<string name="revanced_enable_mini_player_previous_button_title">إضافة زر "السابق" للمشغل المصغر
</string>
<string name="revanced_enable_opus_codec_summary">تفعيل ترميز OPUS إذا كان استجابة المشغل تتضمنه.
معلومات:
• تستخدم أحدث إصدارات YouTube Music ترميز الصوت OPUS بشكل افتراضي.
• قد يكون هذا الخيار ضرورياً في حال تم تزييف (Spoofing) بيانات التطبيق أو بث الفيديو.
</string>
<string name="revanced_enable_opus_codec_title">تمكين ترميز OPUS</string>
<string name="revanced_enable_smooth_transition_animation_summary">تفعيل الرسوم المتحركة السلسة عند تكبير المشغل المصغر من حالته المصغرة.
</string>
<string name="revanced_enable_smooth_transition_animation_title">تفعيل رسوم الانتقال السلسة
</string>
<string name="revanced_enable_swipe_to_dismiss_mini_player_summary">تفعيل السحب للأسفل لإغلاق المشغل المصغر.
</string>
<string name="revanced_enable_swipe_to_dismiss_mini_player_title">تفعيل السحب لإغلاق المشغل المصغر
</string>
<string name="revanced_enable_swipe_to_dismiss_miniplayer_summary">تفعيل السحب لأسفل لإغلاق المشغل المصغر.
</string>
<string name="revanced_enable_swipe_to_dismiss_miniplayer_title">تفعيل السحب لإغلاق المشغل المصغر
</string>
<string name="revanced_enable_thick_seekbar_summary">تفعيل شريط التمرير العريض.
القيود: لن تظهر أجزاء SponsorBlock في شريط التمرير.
</string>
<string name="revanced_enable_thick_seekbar_title">تفعيل شريط التمرير العريض
</string>
<string name="revanced_enable_zen_mode_podcast_summary">تفعيل وضع الزن (Zen) في البودكاست
</string>
<string name="revanced_enable_zen_mode_podcast_title">تفعيل وضع الزن (Zen) في البودكاست
</string>
<string name="revanced_enable_zen_mode_summary">تفعيل اللون الرمادي الفاتح لخلفية المشغل لتقليل إجهاد العين.
</string>
<string name="revanced_enable_zen_mode_title">تفعيل وضع الزن (Zen)
</string>
<string name="revanced_external_downloader_action_summary">زر التنزيل يفتح برنامج التنزيل الخارجي الخاص بك.
• يتخطى فقط زر إجراء "التنزيل" الموجود في المشغل.
• لا يتخطى زر التنزيل في القائمة المنبثقة أو تبويب المكتبة.
</string>
<string name="revanced_external_downloader_action_title">تخطي زر إجراء التنزيل
</string>
<string name="revanced_external_downloader_dialog_title">التنزيل الخارجي</string>
<string name="revanced_external_downloader_not_installed_dialog_message">"لم يتم تثبيت %1$s.
الرجاء تنزيل %2$s من الموقع."</string>
<string name="revanced_external_downloader_not_installed_dialog_title">تحذير</string>
<string name="revanced_external_downloader_not_installed_warning">%s لم يتم تثبيته. الرجاء تثبيته.</string>
<string name="revanced_external_downloader_package_name_summary">اسم الحزمة لتطبيق التنزيل الخارجي المثبت لديك، مثل NewPipe أو YTDLnis.
</string>
<string name="revanced_external_downloader_package_name_title">اسم الحزمة لتطبيق التنزيل الخارجي
</string>
<string name="revanced_fullscreen_ads_closed_toast">تم إغلاق الإعلانات على كامل الشاشة.</string>
<string name="revanced_gms_show_dialog_summary">يعرض مربع حوار تحسين GMSCore عند كل بدء تشغيل للتطبيق.</string>
<string name="revanced_gms_show_dialog_title">إظهار مربع حوار تحسين GMSCore</string>
<string name="revanced_hide_account_menu_empty_component_summary">إخفاء المكونات الفارغة في قائمة الحساب.
</string>
<string name="revanced_hide_account_menu_empty_component_title">إخفاء المكونات الفارغة
</string>
<string name="revanced_hide_account_menu_filter_strings_summary">قائمة بأسماء قائمة الحسابات المراد تصفيتها، مفصولة بسطور جديدة.</string>
<string name="revanced_hide_account_menu_filter_strings_title">تعديل فلتر قائمة الحساب</string>
<string name="revanced_hide_account_menu_summary">إخفاء عناصر قائمة الحساب باستخدام الفلتر المخصص.
</string>
<string name="revanced_hide_account_menu_title">إخفاء قائمة الحساب</string>
<string name="revanced_hide_action_button_add_to_playlist_summary">إخفاء زر الحفظ.
</string>
<string name="revanced_hide_action_button_add_to_playlist_title">إخفاء زر الحفظ</string>
<string name="revanced_hide_action_button_comment_summary">إخفاء زر التعليقات.
</string>
<string name="revanced_hide_action_button_comment_title">إخفاء زر التعليقات</string>
<string name="revanced_hide_action_button_disabled_summary">إخفاء الأزرار المعطلة، مثل زر التعليقات المعطل.
</string>
<string name="revanced_hide_action_button_disabled_title">إخفاء الزر المعطل
</string>
<string name="revanced_hide_action_button_download_summary">إخفاء زر التنزيل.
</string>
<string name="revanced_hide_action_button_download_title">إخفاء زر التنزيل</string>
<string name="revanced_hide_action_button_label_summary">إخفاء تسميات أزرار الإجراء.
</string>
<string name="revanced_hide_action_button_label_title">إخفاء تسميات أزرار الإجراء
</string>
<string name="revanced_hide_action_button_like_dislike_summary">إخفاء زري الإعجاب وعدم الإعجاب. لا يعمل في تخطيط المشغل القديم.
</string>
<string name="revanced_hide_action_button_like_dislike_title">إخفاء أزرار أعجبني ولم يعجبني</string>
<string name="revanced_hide_action_button_radio_summary">إخفاء زر الراديو.
</string>
<string name="revanced_hide_action_button_radio_title">إخفاء زر الراديو
</string>
<string name="revanced_hide_action_button_share_summary">إخفاء زر المشاركة.
</string>
<string name="revanced_hide_action_button_share_title">إخفاء زر مشاركة</string>
<string name="revanced_hide_action_button_song_video_summary">إخفاء زر الأغنية / الفيديو. (هذا الزر متاح لبعض المستخدمين فقط)
</string>
<string name="revanced_hide_action_button_song_video_title">إخفاء زر الأغنية / الفيديو
</string>
<string name="revanced_hide_audio_video_switch_toggle_summary">إخفاء مفتاح تبديل الأغنية / الفيديو في المشغل.
</string>
<string name="revanced_hide_audio_video_switch_toggle_title">إخفاء مفتاح تبديل الأغنية / الفيديو
</string>
<string name="revanced_hide_button_shelf_summary">إخفاء رف الأزرار في الخلاصات.
</string>
<string name="revanced_hide_button_shelf_title">إخفاء رف الأزرار
</string>
<string name="revanced_hide_carousel_shelf_summary">إخفاء رف العرض الدوار في الخلاصات.
</string>
<string name="revanced_hide_carousel_shelf_title">إخفاء رف العرض الدوار
</string>
<string name="revanced_hide_cast_button_summary">إخفاء زر الإرسال.
</string>
<string name="revanced_hide_cast_button_title">إخفاء زر البث</string>
<string name="revanced_hide_category_bar_summary">إخفاء شريط الفئات.
</string>
<string name="revanced_hide_category_bar_title">إخفاء شريط الفئات
</string>
<string name="revanced_hide_comment_channel_guidelines_summary">إخفاء إرشادات القناة في أعلى قسم التعليقات.
</string>
<string name="revanced_hide_comment_channel_guidelines_title">إخفاء إرشادات القناة</string>
<string name="revanced_hide_comment_timestamp_and_emoji_buttons_summary">إخفاء أزرار الرموز التعبيرية والطابع الزمني عند كتابة التعليقات.
</string>
<string name="revanced_hide_comment_timestamp_and_emoji_buttons_title">إخفاء أزرار الرموز التعبيرية والطوابع الزمنية</string>
<string name="revanced_hide_comments_information_button_summary">إخفاء زر المعلومات في لوحة التعليقات.
</string>
<string name="revanced_hide_comments_information_button_title">إخفاء زر المعلومات</string>
<string name="revanced_hide_double_tap_overlay_filter_summary">إخفاء التراكب المظلم الذي يظهر عند النقر المزدوج لتقديم/تأخير الفيديو.
</string>
<string name="revanced_hide_double_tap_overlay_filter_title">إخفاء فلتر واجهة النقر المزدوج</string>
<string name="revanced_hide_floating_button_summary">إخفاء الزر العائم في تبويب المكتبة.
</string>
<string name="revanced_hide_floating_button_title">إخفاء الزر العائم</string>
<string name="revanced_hide_flyout_menu_3_column_component_title">إخفاء مكون الثلاثة أعمدة
</string>
<string name="revanced_hide_flyout_menu_add_to_queue_title">إخفاء قائمة "إضافة إلى قائمة الانتظار"
</string>
<string name="revanced_hide_flyout_menu_captions_title">إخفاء قائمة الترجمة</string>
<string name="revanced_hide_flyout_menu_delete_playlist_title">إخفاء قائمة "حذف قائمة التشغيل"
</string>
<string name="revanced_hide_flyout_menu_dismiss_queue_title">إخفاء قائمة "تجاهل قائمة الانتظار"
</string>
<string name="revanced_hide_flyout_menu_download_title">إخفاء قائمة التنزيل
</string>
<string name="revanced_hide_flyout_menu_edit_playlist_title">إخفاء قائمة تعديل قائمة التشغيل
</string>
<string name="revanced_hide_flyout_menu_go_to_album_title">إخفاء قائمة "الانتقال إلى الألبوم"
</string>
<string name="revanced_hide_flyout_menu_go_to_artist_title">إخفاء قائمة "الانتقال إلى الفنان"
</string>
<string name="revanced_hide_flyout_menu_go_to_episode_title">إخفاء قائمة "الانتقال إلى الحلقة"
</string>
<string name="revanced_hide_flyout_menu_go_to_podcast_title">إخفاء قائمة "الانتقال إلى البودكاست"
</string>
<string name="revanced_hide_flyout_menu_help_title">إخفاء قائمة المساعدة &amp; الملاحظات</string>
<string name="revanced_hide_flyout_menu_like_dislike_title">إخفاء أزرار أعجبني ولم يعجبني</string>
<string name="revanced_hide_flyout_menu_not_interested_title">إخفاء قائمة "غير مهتم"
</string>
<string name="revanced_hide_flyout_menu_pin_to_speed_dial_title">إخفاء قائمة "التثبيت في الطلب السريع"
</string>
<string name="revanced_hide_flyout_menu_play_next_title">إخفاء قائمة "تشغيل التالي"
</string>
<string name="revanced_hide_flyout_menu_quality_title">إخفاء قائمة الجودة
</string>
<string name="revanced_hide_flyout_menu_remove_from_library_title">إخفاء قائمة "الإزالة من المكتبة"
</string>
<string name="revanced_hide_flyout_menu_remove_from_playlist_title">إخفاء قائمة "الإزالة من قائمة التشغيل"
</string>
<string name="revanced_hide_flyout_menu_report_title">إخفاء قائمة الإبلاغ</string>
<string name="revanced_hide_flyout_menu_save_episode_for_later_save_to_library_title">إخفاء قائمتي "حفظ الحلقة لوقت لاحق" و"الحفظ في المكتبة"
</string>
<string name="revanced_hide_flyout_menu_save_episode_for_later_title">إخفاء قائمة "حفظ الحلقة لوقت لاحق"
</string>
<string name="revanced_hide_flyout_menu_save_to_playlist_title">إخفاء قائمة "الحفظ في قائمة التشغيل"
</string>
<string name="revanced_hide_flyout_menu_share_title">إخفاء قائمة المشاركة
</string>
<string name="revanced_hide_flyout_menu_shuffle_play_title">إخفاء قائمة "التشغيل العشوائي"
</string>
<string name="revanced_hide_flyout_menu_sleep_timer_title">إخفاء قائمة مؤقت النوم</string>
<string name="revanced_hide_flyout_menu_start_radio_title">إخفاء قائمة "بدء الراديو"
</string>
<string name="revanced_hide_flyout_menu_stats_for_nerds_title">إخفاء قائمة إحصاءات تقنية</string>
<string name="revanced_hide_flyout_menu_subscribe_title">إخفاء قائمة "الاشتراك / إلغاء الاشتراك"
</string>
<string name="revanced_hide_flyout_menu_taste_match_title">إخفاء قائمة "مطابقة الذوق"
</string>
<string name="revanced_hide_flyout_menu_unpin_from_speed_dial_title">إخفاء قائمة "إلغاء التثبيت من الطلب السريع"
</string>
<string name="revanced_hide_flyout_menu_view_song_credit_title">إخفاء قائمة "عرض بيانات الأغنية"
</string>
<string name="revanced_hide_fullscreen_ads_summary">إخفاء الإعلانات بملء الشاشة
</string>
<string name="revanced_hide_fullscreen_ads_title">إخفاء إعلانات ملء الشاشة</string>
<string name="revanced_hide_fullscreen_share_button_summary">إخفاء زر المشاركة في مشغل ملء الشاشة
</string>
<string name="revanced_hide_fullscreen_share_button_title">إخفاء زر المشاركة بملء الشاشة
</string>
<string name="revanced_hide_general_ads_summary">إخفاء الإعلانات العامة
</string>
<string name="revanced_hide_general_ads_title">إخفاء الإعلانات العامة</string>
<string name="revanced_hide_handle_summary">إخفاء اسم المعرّف في قائمة الحساب
</string>
<string name="revanced_hide_handle_title">إخفاء الاسم المعرِّف</string>
<string name="revanced_hide_history_button_summary">إخفاء زر السجل في شريط الأدوات
</string>
<string name="revanced_hide_history_button_title">إخفاء زر السجل
</string>
<string name="revanced_hide_lyrics_share_button_summary">إخفاء زر المشاركة في كلمات الأغاني
</string>
<string name="revanced_hide_lyrics_share_button_title">إخفاء زر مشاركة كلمات الأغاني
</string>
<string name="revanced_hide_music_ads_summary">إخفاء الإعلانات التي تظهر أثناء تشغيل الوسائط
</string>
<string name="revanced_hide_music_ads_title">إخفاء إعلانات الوسائط
</string>
<string name="revanced_hide_navigation_bar_summary">إخفاء شريط التنقل
</string>
<string name="revanced_hide_navigation_bar_title">إخفاء شريط التنقل</string>
<string name="revanced_hide_navigation_explore_button_summary">إخفاء زر استكشاف
</string>
<string name="revanced_hide_navigation_explore_button_title">إخفاء زر استكشاف
</string>
<string name="revanced_hide_navigation_home_button_summary">إخفاء زر الصفحة الرئيسية
</string>
<string name="revanced_hide_navigation_home_button_title">إخفاء زر الصفحة الرئيسية</string>
<string name="revanced_hide_navigation_label_summary">إخفاء التسمية أسفل كل زر تنقل
</string>
<string name="revanced_hide_navigation_label_title">إخفاء تسميات شريط التنقل</string>
<string name="revanced_hide_navigation_library_button_summary">إخفاء زر المكتبة
</string>
<string name="revanced_hide_navigation_library_button_title">إخفاء زر المكتبة</string>
<string name="revanced_hide_navigation_samples_button_summary">إخفاء زر العينات
</string>
<string name="revanced_hide_navigation_samples_button_title">إخفاء زر العينات
</string>
<string name="revanced_hide_navigation_upgrade_button_summary">إخفاء زر الترقية
</string>
<string name="revanced_hide_navigation_upgrade_button_title">إخفاء زر الترقية
</string>
<string name="revanced_hide_notification_button_summary">إخفاء زر الإشعارات في شريط الأدوات
</string>
<string name="revanced_hide_notification_button_title">إخفاء زر الإشعارات</string>
<string name="revanced_hide_paid_promotion_label_summary">إخفاء تسمية الترويج المدفوع
</string>
<string name="revanced_hide_paid_promotion_label_title">إخفاء تصنيف الترويج المدفوع</string>
<string name="revanced_hide_playlist_card_shelf_summary">إخفاء رف بطاقات قوائم التشغيل في الخلاصة
</string>
<string name="revanced_hide_playlist_card_shelf_title">إخفاء رف بطاقات قوائم التشغيل
</string>
<string name="revanced_hide_premium_promotion_closed_toast">إغلاق النوافذ المنبثقة لترقية بريميوم
</string>
<string name="revanced_hide_premium_promotion_summary">إخفاء النوافذ المنبثقة لترقية بريميوم
</string>
<string name="revanced_hide_premium_promotion_title">إخفاء النوافذ المنبثقة لترقية بريميوم
</string>
<string name="revanced_hide_premium_renewal_summary">إخفاء لافتة تجديد اشتراك بريميوم
</string>
<string name="revanced_hide_premium_renewal_title">إخفاء لافتة تجديد اشتراك بريميوم
</string>
<string name="revanced_hide_promotion_alert_banner_summary">إخفاء لافتة تنبيه العروض الترويجية
</string>
<string name="revanced_hide_promotion_alert_banner_title">إخفاء لافتة تنبيه الترقية</string>
<string name="revanced_hide_samples_shelf_summary">إخفاء رف العينات في الخلاصة
</string>
<string name="revanced_hide_samples_shelf_title">إخفاء رف العينات
</string>
<string name="revanced_hide_search_button_summary">إخفاء زر البحث في شريط الأدوات
</string>
<string name="revanced_hide_search_button_title">إخفاء زر البحث</string>
<string name="revanced_hide_settings_menu_about_title">إخفاء قائمة لمحة</string>
<string name="revanced_hide_settings_menu_data_saving_title">إخفاء قائمة توفير البيانات</string>
<string name="revanced_hide_settings_menu_downloads_and_storage_title">إخفاء قائمة التنزيلات والتخزين
</string>
<string name="revanced_hide_settings_menu_general_title">إخفاء قائمة عامة</string>
<string name="revanced_hide_settings_menu_notification_title">إخفاء قائمة الإشعارات</string>
<string name="revanced_hide_settings_menu_paid_memberships_title">إخفاء قائمة الحصول على موسيقى بريميوم
</string>
<string name="revanced_hide_settings_menu_parent_tools_title">إخفاء قائمة مركز العائلة</string>
<string name="revanced_hide_settings_menu_playback_title">إخفاء قائمة التشغيل
</string>
<string name="revanced_hide_settings_menu_privacy_and_location_title">إخفاء قائمة الخصوصية والبيانات
</string>
<string name="revanced_hide_settings_menu_recommendations_title">إخفاء قائمة التوصيات
</string>
<string name="revanced_hide_settings_menu_summary">إخفاء عناصر قائمة الإعدادات. يؤدي هذا إلى إخفاء قائمة إعدادات YT Music فحسب، بل وأيضاً قائمة إعدادات ReVanced Extended.
</string>
<string name="revanced_hide_settings_menu_title">إخفاء قائمة إعدادات</string>
<string name="revanced_hide_song_video_toggle_summary">إخفاء مفتاح التبديل بين الأغنية والفيديو في المشغل
</string>
<string name="revanced_hide_song_video_toggle_title">إخفاء مفتاح التبديل بين الأغنية والفيديو
</string>
<string name="revanced_hide_sound_search_button_summary">إخفاء زر البحث الصوتي في شريط البحث
</string>
<string name="revanced_hide_sound_search_button_title">إخفاء زر البحث الصوتي
</string>
<string name="revanced_hide_tap_to_update_button_summary">إخفاء زر "انقر للتحديث"
</string>
<string name="revanced_hide_tap_to_update_button_title">إخفاء زر انقر للتحديث
</string>
<string name="revanced_hide_terms_container_summary">إخفاء حاوية شروط الخدمة
</string>
<string name="revanced_hide_terms_container_title">إخفاء حاوية الشروط
</string>
<string name="revanced_hide_voice_search_button_summary">إخفاء زر البحث الصوتي في شريط البحث
</string>
<string name="revanced_hide_voice_search_button_title">إخفاء زر البحث الصوتي</string>
<string name="revanced_preference_screen_account_title">الحساب
</string>
<string name="revanced_preference_screen_action_bar_title">شريط الإجراءات
</string>
<string name="revanced_preference_screen_ads_title">الإعلانات</string>
<string name="revanced_preference_screen_flyout_title">القائمة المنبثقة</string>
<string name="revanced_preference_screen_general_title">عام</string>
@ -187,20 +564,106 @@
<string name="revanced_preference_screen_sb_title">SponsorBlock</string>
<string name="revanced_preference_screen_settings_title">قائمة الإعدادات</string>
<string name="revanced_preference_screen_video_title">الفيديو</string>
<string name="revanced_remember_playback_speed_last_selected_summary">تذكر سرعة التشغيل المحددة مسبقاً
</string>
<string name="revanced_remember_playback_speed_last_selected_title">تذكر التغيرات في سرعة التشغيل</string>
<string name="revanced_remember_playback_speed_last_selected_toast_summary">إظهار إشعار مؤقت (Toast) عند تغيير سرعة التشغيل الافتراضية
</string>
<string name="revanced_remember_playback_speed_last_selected_toast_title">عرض ملاحظة</string>
<string name="revanced_remember_playback_speed_toast">تغيير سرعة التشغيل الافتراضية إلى %s.</string>
<string name="revanced_remember_repeat_state_summary">تذكر حالة زر التكرار
</string>
<string name="revanced_remember_repeat_state_title">تذكر حالة التكرار
</string>
<string name="revanced_remember_shuffle_state_summary">تذكر حالة زر الترتيب العشوائي
</string>
<string name="revanced_remember_shuffle_state_title">تذكر حالة الترتيب العشوائي
</string>
<string name="revanced_remember_video_quality_auto_mobile">تغيير جودة بيانات الجوال الافتراضية إلى تلقائي
</string>
<string name="revanced_remember_video_quality_auto_wifi">تغيير جودة "واي فاي" الافتراضية إلى تلقائي
</string>
<string name="revanced_remember_video_quality_last_selected_summary">تذكر جودة الفيديو المحددة مسبقاً
</string>
<string name="revanced_remember_video_quality_last_selected_title">تذكر تغييرات جودة الفيديو</string>
<string name="revanced_remember_video_quality_last_selected_toast_summary">إظهار إشعار مؤقت (Toast) عند تغيير جودة الفيديو الافتراضية
</string>
<string name="revanced_remember_video_quality_last_selected_toast_title">عرض ملاحظة</string>
<string name="revanced_remember_video_quality_mobile">تغيير جودة بيانات الجوال الافتراضية إلى %s
</string>
<string name="revanced_remember_video_quality_none">فشل تعيين الجودة
</string>
<string name="revanced_remember_video_quality_wifi">تغيير جودة "واي فاي" الافتراضية إلى %s
</string>
<string name="revanced_remove_viewer_discretion_dialog_summary">"يزيل مربع حوار تقدير المشاهد. هذا لا يتجاوز القيود العمرية. إنه يقبل ذلك تلقائيًا."</string>
<string name="revanced_remove_viewer_discretion_dialog_title">إزالة مربع حوار تقدير المشاهد</string>
<string name="revanced_replace_action_button_like_summary">استخدام أيقونة ورسوم متحركة مختلفة لزر الإعجاب. • قد لا تكون هناك حاجة لهذا الإعداد بمجرد إصلاح المشكلة من جانب الخادم (Server)
</string>
<string name="revanced_replace_action_button_like_title">استبدال زر الإعجاب
</string>
<string name="revanced_replace_action_button_like_type_summary">استخدام زر إعجاب أصغر. • لدى بعض المستخدمين شريط إجراءات سفلي (اختبار A/B). • هذا الإعداد مخصص لهؤلاء المستخدمين، لكنه لم يختبر بعد.
</string>
<string name="revanced_replace_action_button_like_type_title">استخدام أيقونة أصغر
</string>
<string name="revanced_replace_flyout_menu_dismiss_queue_continue_watch_summary">مواصلة الفيديو من الوقت الحالي عند الانتقال إلى YouTube
</string>
<string name="revanced_replace_flyout_menu_dismiss_queue_continue_watch_title">مواصلة المشاهدة
</string>
<string name="revanced_replace_flyout_menu_dismiss_queue_summary">استبدال قائمة "إغلاق قائمة الانتظار" بقائمة "المشاهدة على YouTube"
</string>
<string name="revanced_replace_flyout_menu_dismiss_queue_title">استبدال قائمة "إغلاق قائمة الانتظار"
</string>
<string name="revanced_replace_flyout_menu_dismiss_queue_watch_on_youtube_label">المشاهدة على YouTube
</string>
<string name="revanced_replace_flyout_menu_dismiss_queue_watch_on_youtube_warning">رابط فيديو غير صالح
</string>
<string name="revanced_replace_flyout_menu_report_only_player_summary">الإبقاء على قائمة "الإبلاغ" في قسم التعليقات كما هي
</string>
<string name="revanced_replace_flyout_menu_report_only_player_title">الإبقاء على "الإبلاغ" في التعليقات
</string>
<string name="revanced_replace_flyout_menu_report_summary">استبدال قائمة "الإبلاغ" بقائمة "سرعة التشغيل"
</string>
<string name="revanced_replace_flyout_menu_report_title">استبدال قائمة "الإبلاغ"
</string>
<string name="revanced_replace_navigation_button_about_summary">هذه الميزة تجريبية. توجد قيود هيكلية في الترقية (Patch)، حيث أن أنشطة مثل "البحث" و"الإعدادات" في YouTube Music ليست عامة. المشكلات المعروفة: • عند إغلاق نشاط مستبدل مثل "البحث" أو "الإعدادات"، تفتح صفحة البداية. انقر لفتح إعدادات "تغيير صفحة البداية".
</string>
<string name="revanced_replace_navigation_button_about_title">حول زر الاستبدال
</string>
<string name="revanced_replace_navigation_samples_button_summary">استبدال زر "عينات" (Samples) بزر "البحث"
</string>
<string name="revanced_replace_navigation_samples_button_title">استبدال زر "عينات" (Samples)
</string>
<string name="revanced_replace_navigation_upgrade_button_summary">استبدال زر "الترقية" (Upgrade) بزر "الإعدادات"
</string>
<string name="revanced_replace_navigation_upgrade_button_title">استبدال زر "الترقية" (Upgrade)
</string>
<string name="revanced_reset_to_default_toast">إعادة التعيين إلى القيم الافتراضية.</string>
<string name="revanced_restart_first_run">إعادة التشغيل لإنهاء الإعداد</string>
<string name="revanced_restart_message">تحديث وإعادة تشغيل
</string>
<string name="revanced_restore_old_comments_popup_panels_summary">استعادة النمط القديم للوحات التعليقات المنبثقة
</string>
<string name="revanced_restore_old_comments_popup_panels_title">استعادة اللوحات المنبثقة القديمة للتعليقات
</string>
<string name="revanced_restore_old_player_background_summary">استعادة النمط القديم لخلفية المشغل
</string>
<string name="revanced_restore_old_player_background_title">استعادة خلفية المشغل القديمة
</string>
<string name="revanced_restore_old_player_layout_summary">استعادة النمط القديم لتخطيط المشغل. قد لا تعمل بعض الميزات بشكل صحيح في هذا التخطيط القديم.
</string>
<string name="revanced_restore_old_player_layout_title">استعادة تخطيط المشغل القديم</string>
<string name="revanced_restore_old_style_library_shelf_summary">استعادة النمط القديم لتبويب "المكتبة". (تجريبي)
</string>
<string name="revanced_restore_old_style_library_shelf_title">استعادة تبويب "المكتبة" القديم
</string>
<string name="revanced_return_youtube_username_display_format_handle_username">(اسم المستخدم) Handle@</string>
<string name="revanced_return_youtube_username_display_format_summary">اختيار تنسيق عرض اسم المستخدم
</string>
<string name="revanced_return_youtube_username_display_format_title">شكل العرض</string>
<string name="revanced_return_youtube_username_display_format_username_handle">اسم المستخدم (Handle@)</string>
<string name="revanced_return_youtube_username_display_format_username_only">اسم المستخدم</string>
<string name="revanced_return_youtube_username_enabled_summary">استبدال المعرّفات (Handles) بأسماء المستخدمين في التعليقات
</string>
<string name="revanced_return_youtube_username_enabled_title">تمكين إعادة اسم مستخدم YouTube</string>
<string name="revanced_return_youtube_username_youtube_data_api_v3_about_summary">"مطلوب مفتاح مطور بيانات YouTube API v3 لاستبدال الاسم المعرِّف بـ اسم المستخدم.
@ -210,18 +673,30 @@
<string name="revanced_return_youtube_username_youtube_data_api_v3_about_title">لمحة عن مفتاح YouTube Data API</string>
<string name="revanced_return_youtube_username_youtube_data_api_v3_developer_key_summary">مفتاح المطور لاستخدام بيانات YouTube API v3.</string>
<string name="revanced_return_youtube_username_youtube_data_api_v3_developer_key_title">مفتاح API لبيانات YouTube</string>
<string name="revanced_return_youtube_username_youtube_data_api_v3_dialog_message">1. انتقل إلى &lt;a href=%1$s&gt;إنشاء مشروع جديد&lt;/a&gt;.&lt;br&gt;2. انقر على زر &lt;b&gt;CREATE&lt;/b&gt; (إنشاء).&lt;br&gt;3. انتقل إلى &lt;a href=%2$s&gt;YouTube Data API v3&lt;/a&gt;.&lt;br&gt;4. انقر على زر &lt;b&gt;ENABLE&lt;/b&gt; (تفعيل).&lt;br&gt;5. انقر على زر &lt;b&gt;CREATE CREDENTIALS&lt;/b&gt; (إنشاء مفاتيح اعتماد).&lt;br&gt;6. اختر خيار &lt;b&gt;Public data&lt;/b&gt; (البيانات العامة).&lt;br&gt;7. انقر على زر &lt;b&gt;NEXT&lt;/b&gt; (التالي).&lt;br&gt;8. انسخ مفتاح API.&lt;br&gt;&lt;br&gt;※ يجب عدم مشاركة مفتاح API مع الآخرين أبداً، لذا لا يتم تضمينه في إعدادات الاستيراد والتصدير.
</string>
<string name="revanced_return_youtube_username_youtube_data_api_v3_dialog_title">إصدار مفتاح مطور YouTube Data API v3</string>
<string name="revanced_ryd_about">لمحة</string>
<string name="revanced_ryd_attribution_summary">يتم توفير بيانات لم يعجبني بفيديوهات YouTube بواسطة the Return YouTube Dislike API. اضغط هنا لمعرفة المزيد.</string>
<string name="revanced_ryd_attribution_title">ReturnYouTubeDislike.com</string>
<string name="revanced_ryd_compact_layout_summary">إخفاء فاصل زر الإعجاب
</string>
<string name="revanced_ryd_compact_layout_title">مقاس زر أعجبني</string>
<string name="revanced_ryd_dislike_percentage_summary">عرض نسبة عدم الإعجاب بدلاً من عدد عدم الإعجاب
</string>
<string name="revanced_ryd_dislike_percentage_title">لم يعجبني كــ نسبة مئوية</string>
<string name="revanced_ryd_enabled_summary">إظهار عدد عدم الإعجاب في الفيديوهات
</string>
<string name="revanced_ryd_enabled_title">تمكين Return YouTube Dislike</string>
<string name="revanced_ryd_estimated_like_summary">إظهار عدد الإعجابات المقدر للفيديوهات
</string>
<string name="revanced_ryd_estimated_like_title">عرض الإعجابات المقدرة</string>
<string name="revanced_ryd_failure_client_rate_limit_requested">لم يعجبني غير متوفر (تم الوصول إلى حد API العميل).</string>
<string name="revanced_ryd_failure_connection_status_code">لم يعجبني غير متوفر (الحالة %d).</string>
<string name="revanced_ryd_failure_connection_timeout">لم يعجبني غير متوفر مؤقتًا (انتهت مهلة API).</string>
<string name="revanced_ryd_failure_generic">لم يعجبني غير متوفر (%s).</string>
<string name="revanced_ryd_toast_on_connection_error_summary">إظهار إشعار (Toast) في حال عدم توفر واجهة Return YouTube Dislike API.
</string>
<string name="revanced_ryd_toast_on_connection_error_title">عرض ملاحظة إذا كان API غير متوفر</string>
<string name="revanced_ryd_video_likes_hidden_by_video_owner">مخفي</string>
<string name="revanced_sanitize_sharing_links_summary">يزيل معلمات استعلام التتبع من عناوين URL عند مشاركة الروابط.</string>
@ -252,8 +727,10 @@
<string name="revanced_sb_segments_intro_sum">فاصل زمني بدون محتوى فعلي. يمكن أن يكون توقفًا مؤقتًا أو إطارًا ثابتًا أو تكرار الرسوم المتحركة. لا يتضمن انتقالات تحتوي على معلومات.</string>
<string name="revanced_sb_segments_nomusic">الموسيقى: مقطع بدون موسيقى</string>
<string name="revanced_sb_segments_nomusic_sum">فقط للاستخدام في مقاطع الفيديو الموسيقية. أقسام مقاطع الفيديو الموسيقية بدون موسيقى، والتي لم يتم تغطيتها بالفعل من قبل فئة أخرى.</string>
<string name="revanced_sb_segments_outro">الخاتمة / تترات النهاية</string>
<string name="revanced_sb_segments_outro_sum">تتر النهاية أو عندما تظهر بطاقات نهاية YouTube، نهايات غير منطوقة. ليس للاستنتاجات مع المعلومات.</string>
<string name="revanced_sb_segments_outro">شاشات النهاية / المساهمون
</string>
<string name="revanced_sb_segments_outro_sum">المساهمون أو وقت ظهور شاشات نهاية YouTube. ليس لخواتيم المعلومات.
</string>
<string name="revanced_sb_segments_preview">معاينة / ملخص</string>
<string name="revanced_sb_segments_preview_sum">مجموعة من المقاطع التي توضح ما هو قادم أو ما حدث في الفيديو أو في مقاطع فيديو أخرى من السلسلة، حيث تتكرر جميع المعلومات في مكان آخر.</string>
<string name="revanced_sb_segments_selfpromo">ترويج شخصي / غير مدفوع الأجر</string>
@ -280,14 +757,28 @@
<string name="revanced_sb_sponsorblock_connection_failure_status">SponsorBlock غير متوفر مؤقتًا (الحالة %d).</string>
<string name="revanced_sb_sponsorblock_connection_failure_timeout">SponsorBlock غير متوفر مؤقتًا (انتهت مهلة API).</string>
<string name="revanced_sb_toast_on_connection_error">عرض ملاحظة إذا كان API غير متوفر</string>
<string name="revanced_sb_toast_on_connection_error_sum">إظهار إشعار (Toast) في حال عدم توفر واجهة SponsorBlock API.
</string>
<string name="revanced_sb_toast_on_skip">إظهار إشعار (Toast) عند التخطي التلقائي
</string>
<string name="revanced_sb_toast_on_skip_sum">إظهار إشعار (Toast) عند تخطي مقطع تلقائياً
</string>
<string name="revanced_settings_export_as_file">تصدير الإعدادات إلى ملف
</string>
<string name="revanced_settings_export_failed">فشل تصدير الإعدادات.</string>
<string name="revanced_settings_export_success">تم تصدير الإعدادات بنجاح.</string>
<string name="revanced_settings_import">استيراد</string>
<string name="revanced_settings_import_as_file">استيراد الإعدادات من ملف
</string>
<string name="revanced_settings_import_copy">نسخ</string>
<string name="revanced_settings_import_export_as_text">استيراد \ تصدير الإعدادات كنص</string>
<string name="revanced_settings_import_export_summary">إستيراد \ تصدير إعدادات RVX Music.</string>
<string name="revanced_settings_import_export_title">إستيراد \ تصدير إعدادات</string>
<string name="revanced_settings_import_failed">فشل الاستيراد: %s.
</string>
<string name="revanced_settings_import_reset">إعادة تعيين الإعدادات إلى الافتراضي.</string>
<string name="revanced_settings_import_success">تم استيراد %d من الإعدادات.
</string>
<string name="revanced_settings_reset">إعادة تعيين</string>
<string name="revanced_settings_search_clear_history">مسح سجل البحث</string>
<string name="revanced_settings_search_clear_history_message">هل أنت متأكد من أنك تريد مسح سجل البحث بالكامل؟</string>
@ -309,17 +800,68 @@
<string name="revanced_settings_search_title">بحث %s</string>
<string name="revanced_settings_title">RVX</string>
<string name="revanced_share_copy_settings_success">تم نسخ الإعدادات إلى الحافظة.</string>
<string name="revanced_spoof_app_version_for_lyrics_summary">تمويه إصدار العميل المستخدم في كلمات الأغاني إلى إصدار أقدم.
</string>
<string name="revanced_spoof_app_version_for_lyrics_target_entry_6_42_55">6.42.55 - لا توجد كلمات أغانٍ فورية (Real-time)
</string>
<string name="revanced_spoof_app_version_for_lyrics_target_entry_7_12_52">7.12.52 - كلمات أغانٍ فورية (Real-time)
</string>
<string name="revanced_spoof_app_version_for_lyrics_target_entry_8_06_52">8.06.52 - كلمات أغانٍ فورية (Real-time) + زر المشاركة
</string>
<string name="revanced_spoof_app_version_for_lyrics_target_entry_8_33_54">8.33.54 - كلمات أغانٍ فورية (Real-time) + زر المشاركة + زر الترجمة (لمشتركي Premium فقط)
</string>
<string name="revanced_spoof_app_version_for_lyrics_target_summary">اختر إصدار التطبيق (Spoof) المستهدف لكلمات الأغاني
</string>
<string name="revanced_spoof_app_version_for_lyrics_target_title">إصدار التطبيق (Spoof) المستهدف لكلمات الأغاني
</string>
<string name="revanced_spoof_app_version_for_lyrics_title">إصدار التطبيق (Spoof) لكلمات الأغاني
</string>
<string name="revanced_spoof_app_version_summary">تمويه إصدار العميل إلى إصدار أقدم.
• سيؤدي هذا إلى تغيير مظهر التطبيق، ولكن قد تحدث آثار جانبية غير معروفة.
• إذا تم تمكينه ولم يعمل، فحاول التبديل إلى حساب آخر.
• في حال تعطيله لاحقاً، قد تظل الواجهة القديمة قائمة حتى يتم مسح بيانات التطبيق.
</string>
<string name="revanced_spoof_app_version_target_entry_4_27_53">4.27.53 - تعطيل وضع الراديو في المناطق الكندية
</string>
<string name="revanced_spoof_app_version_target_entry_6_11_52">6.11.52 - تعطيل كلمات الأغاني فورية (Real-time)
</string>
<string name="revanced_spoof_app_version_target_entry_6_35_52">6.35.52 - استعادة لوحة القائمة المنبثقة القديمة (Flyout panel)
</string>
<string name="revanced_spoof_app_version_target_entry_6_42_55">6.42.55 - تعطيل كلمات الأغاني الفورية (Real-time)
</string>
<string name="revanced_spoof_app_version_target_entry_7_16_53">7.16.53 - استعادة شريط الإجراءات (Action bar) القديم
</string>
<string name="revanced_spoof_app_version_target_entry_7_17_52">7.17.52 - قسم التعليقات لا يظهر في خلاصة الصفحة الرئيسية
</string>
<string name="revanced_spoof_app_version_target_invalid_toast">إصدار التطبيق الوهمي غير صالح: %s.</string>
<string name="revanced_spoof_app_version_target_summary">اكتب هدف إصدار التطبيق الوهمي.</string>
<string name="revanced_spoof_app_version_target_title">الهدف من خِداع إصدار التطبيق</string>
<string name="revanced_spoof_app_version_title">إصدار تطبيق وهمي</string>
<string name="revanced_spoof_client_summary">تمويه العميل لمنع مشاكل التشغيل.
</string>
<string name="revanced_spoof_client_title">تمويه العميل (Spoof client)
</string>
<string name="revanced_spoof_client_type_summary">الأعراض الجانبية:
• قد لا تظهر أزرار الإجراءات أو قوائم المشغل المنبثقة بشكل صحيح.
• يمكن حل هذه الأعراض الجانبية من خلال مسح بيانات التطبيق وتسجيل الدخول مرة أخرى.
أثر جانبي (حظر الطلبات):
• قد يتوقف التطبيق إجبارياً عند استخدام VPN أو DNS.
</string>
<string name="revanced_spoof_client_type_title">العميل الافتراضي</string>
<string name="revanced_spoof_streaming_data_default_client_title">العميل الافتراضي</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">انقر لمعرفة كيفية الحصول على رمز المصادقة.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">لمحة عن مصادقة Android No SDK</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">انقر هنا لمعرفة كيفية تسجيل الدخول إلى Android No SDK.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_title">تسجيل الدخول إلى Android No SDK</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_dialog_get_authorization_token_text">الحصول على رمز المصادقة</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_dialog_message">تسجيل الدخول إلى (Android No SDK (YouTube يتبع الإرشادات المقدمة من Google (تفويض حساب Google - OAuth 2.0 لتطبيقات Android):
1. انقر فوق "الحصول على رمز التفويض" لفتح صفحة تسجيل الدخول في WebView.
2. قم بتسجيل الدخول إلى حساب Google الخاص بك.
3. ستظهر نتائج تسجيل الدخول في غضون 5 ثوانٍ.
※ انقر فوق "إعادة ضبط" لإزالة رمز المصادقة.
</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_dialog_reset_text">إعادة تعيين</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_dialog_title">تسجيل الدخول إلى Android No SDK</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_success_dialog_message">"تم حفظ رمز المصادقة.
@ -328,6 +870,8 @@
- رمز المصادقة غير مستخدم لأكثر من 6 أشهر سيتم إلغاؤه من خوادم Google.
- إذا قمت بتغيير كلمة المرور الخاصة بك، أو ألغيت تسجيل جهاز في إعدادات حسابك، أو إلغاء تثبيت التطبيق، سوف تحتاج إلى إعادة إصدار رمز المصادقة."</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_success_dialog_title">تم تسجيل الدخول</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_toast_reset">إعادة ضبط رمز المصادقة.
</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_about_summary">انقر هنا لمعرفة كيفية تسجيل الدخول إلى Android VR.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_about_title">تسجيل الدخول إلى Android VR</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_activation_code_dialog_message">"رمز التفعيل:
@ -338,6 +882,8 @@
3. سجّل الدخول عند الطلب. إذا كان لديك حسابات جوجل متعددة، فحدد الحساب الذي تستخدمه مع YouTube. إذا كنت قد سجّلت الدخول بالفعل، فانتقل إلى الخطوة التالية.
4. بعد تسجيل الدخول، ارجع إلى RVX وانقر على 'تسجيل الدخول إلى Android VR' مرة أخرى.
5. انقر على 'الحصول على رمز المصادقة' للحصول على رمز التفويض."</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_activation_code_dialog_open_website">فتح الموقع الإلكتروني
</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_activation_code_dialog_open_website_text">فتح الموقع</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_activation_code_dialog_title">رمز التفعيل</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_activation_code_toast_copy">تم نسخ رمز التفعيل إلى الحافظة: %s.</string>
@ -350,20 +896,26 @@
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">فشل في إصدار رمز المصادقة.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">انقر فوق إعادة تعيين ثم حاول مرة أخرى.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">إعادة تعيين رمز المصادقة و رمز التفعيل.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">عرض في إحصاءات تقنية</string>
<string name="revanced_spoof_streaming_data_summary">تمويه تدفقات فيديو العميل لمنع مشاكل التشغيل.
</string>
<string name="revanced_spoof_streaming_data_title">Spoof video streams
</string>
<string name="revanced_spoof_streaming_data_vr_auth_token_about_summary">انقر لمعرفة كيفية الحصول على رمز المصادقة.</string>
<string name="revanced_spoof_streaming_data_vr_auth_token_about_title">لمحة عن مصادقة Android VR</string>
<string name="revanced_spoof_video_streams_default_client_summary">الآثار الجانبية:
• قد يتم إغلاق التطبيق قسراً عند استخدام VPN أو DNS.
• قد يتشوش الصوت بشكل متقطع أثناء التشغيل على عملاء البث ذوي معدل البت التكيفي (جميع العملاء باستثناء Android VR 1.43.32).</string>
<string name="revanced_spoof_video_streams_default_client_title">العميل الافتراضي</string>
<string name="revanced_spoof_video_streams_summary">انتحال بث الفيديو الخاص بالعميل لمنع مشاكل التشغيل.</string>
<string name="revanced_spoof_video_streams_title">Spoof Video Streams</string>
<string name="revanced_translations_title">الترجمات</string>
<string name="revanced_watch_history_type_entry_1">الأصلي</string>
<string name="revanced_watch_history_type_entry_2">استبدال النطاق</string>
<string name="revanced_watch_history_type_entry_3">حظر سجل المشاهدة</string>
<string name="revanced_watch_history_type_summary">• الأصلي: يتبع إعدادات سجل المشاهدة لحساب Google، ولكن قد لا يعمل سجل المشاهدة بسبب DNS أو VPN.
• استبدال النطاق: يتبع إعدادات سجل المشاهدة لحساب Google.
• حظر سجل المشاهدة: تم حظر سجل المشاهدة.</string>
<string name="revanced_watch_history_type_title">نوع سجل المشاهدة</string>
<string name="revanced_webview_contextual_menu_get_cookies">الحصول على ملفات تعريف الارتباط</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">الحصول على معرف مزامنة البيانات</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">الحصول على بيانات الزائر</string>
<string name="revanced_webview_contextual_menu_refresh">تحديث</string>
<string name="revanced_webview_cookies">ملفات تعريف الارتباط</string>
<string name="revanced_webview_copy">نسخ</string>
<string name="revanced_webview_data_sync_id">معرف مزامنة البيانات</string>

View file

@ -36,8 +36,8 @@
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">JavaScript хешовете на плейъра са валидни приблизително един месец и остарелите хешове може да бъдат отменени от GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Пробвайте да вкл. Подправяне на видеопотокове &gt; Вход в Android VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Не може да се изтеглят клиентски потоци</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Въведете валиден JavaScript хеш на плейъра</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">JavaScript хеш на играча</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Въведете валиден JavaScript хеш на плейъра</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">JavaScript хеш на играча</string>
<string name="morphe_spoof_video_streams_screen_summary">Подправете клиентските видео потоци, за да предотвратите проблеми с възпроизвеждането</string>
<string name="morphe_spoof_video_streams_screen_title">Подправяне на видео потоци</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Несте влезли в Android VR. Натиснете тук за да се влезете.</string>

View file

@ -51,8 +51,8 @@
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Τα JavaScript hashes για τον παίκτη είναι έγκυρα για περίπου ένα μήνα, και τα устаревшая hashes ενδέχεται να ανακληθούν από το GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Δοκιμάστε να ενεργοποιήσετε: Μιμήση ροής βίντεο &gt; Είσοδος Android VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Αδυναμία λήψης ροής του προγράμματος πελάτη</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Εισάγετε ένα έγκυρο hash JavaScript παίκτη</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Hash JavaScript παίκτη</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Εισάγετε ένα έγκυρο hash JavaScript παίκτη</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Hash JavaScript παίκτη</string>
<string name="morphe_spoof_video_streams_screen_summary">Παραποίηση ροών βίντεο του προγράμματος πελάτη για την αποφυγή προβλημάτων αναπαραγωγής</string>
<string name="morphe_spoof_video_streams_screen_title">Παραποίηση ροών βίντεο</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Δεν είστε συνδεδεμένοι στο Android VR. Πατήστε εδώ για να συνδεθείτε</string>
@ -626,7 +626,6 @@
Παρενέργεια αποκλεισμού αιτήματος:
Η εφαρμογή μπορεί να κλείνει απότομα όταν χρησιμοποιείτε VPN ή εναλλακτικό DNS."</string>
<string name="revanced_spoof_client_type_title">Προεπιλεγμένο πρόγραμμα πελάτη</string>
<string name="revanced_spoof_streaming_data_default_client_title">Προεπιλεγμένο πρόγραμμα πελάτη</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">Πατήστε για να δείτε πώς να αποκτήσετε ένα διακριτικό εξουσιοδότησης.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">Σχετικά με την εξουσιοδότηση Android No SDK </string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Πατήστε για να δείτε πώς να συνδεθείτε στο Android No SDK.</string>
@ -670,8 +669,6 @@
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Αποτυχία έκδοσης διακριτικού εξουσιοδότησης.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Πατήστε «Επαναφορά» και προσπαθήστε ξανά.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Το διακριτικό και ο κωδικός επαναφέρθηκαν.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Εμφάνιση του προγράμματος πελάτη που χρησιμοποιείται για τη λήψη δεδομένων ροής στο μενού «Στατιστικά για σπασίκλες».</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Εμφάνιση στο «Στατιστικά για σπασίκλες»</string>
<string name="revanced_spoof_streaming_data_summary">"Τα δεδομένα ροής παραποιούνται.
Αν είστε χρήστης του YouTube Premium, αυτή η ρύθμιση μπορεί να μην είναι απαραίτητη."</string>
@ -692,10 +689,6 @@
• Αντικατάσταση του domain: Ακολουθεί τις ρυθμίσεις ιστορικού παρακολούθησης του λογαριασμού Google σας.
• Αποκλεισμός ιστορικού παρακολούθησης: Το ιστορικό παρακολούθησης είναι αποκλεισμένο."</string>
<string name="revanced_watch_history_type_title">Τύπος ιστορικού παρακολούθησης</string>
<string name="revanced_webview_contextual_menu_get_cookies">Λήψη των Cookies</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Λήψη του Data Sync ID</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Λήψη των Visitor Data</string>
<string name="revanced_webview_contextual_menu_refresh">Ανανέωση</string>
<string name="revanced_webview_cookies">Cookies</string>
<string name="revanced_webview_copy">Αντιγραφή</string>
<string name="revanced_webview_data_sync_id">Data Sync ID</string>

View file

@ -49,8 +49,8 @@ Pulsa el botón de continuar y desactiva las optimizaciones de la batería."</st
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Los hashes JavaScript del reproductor son válidos aproximadamente un mes, y los hashes obsoletos pueden ser revocados por GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Prueba a activar: Falsificar transmisiones de vídeo &gt; Inicio de sesión de Android VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">No se ha podido obtener ninguna transmisión del cliente</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Introduzca un hash JavaScript del reproductor válido</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Hash JavaScript del reproductor</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Introduzca un hash JavaScript del reproductor válido</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Hash JavaScript del reproductor</string>
<string name="morphe_spoof_video_streams_screen_summary">Falsifica las transmisiones de vídeo del cliente para evitar problemas de reproducción</string>
<string name="morphe_spoof_video_streams_screen_title">Falsificar transmisiones de vídeo</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">No has iniciado sesión en Android VR. Pulsa aquí para iniciar sesión</string>
@ -610,7 +610,6 @@ Toca para ver cómo crear una clave de API."</string>
<string name="revanced_spoof_client_title">Falsificar cliente</string>
<string name="revanced_spoof_client_type_summary">"Define un cliente de por defecto para el falsificado."</string>
<string name="revanced_spoof_client_type_title">Cliente por defecto</string>
<string name="revanced_spoof_streaming_data_default_client_title">Cliente predeterminado</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">Clic para ver como obtener un token de autentificación.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">Acerca de la Autentificación de Android No Sdk</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Clic aquí para ver como iniciar sesión en Android No SDK</string>
@ -654,8 +653,6 @@ Toca para ver cómo crear una clave de API."</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Error al emitir un token de autenticación.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Pulsa en \"Restablecer\" e inténtalo de nuevo.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Token de autenticación y código de activación restablecidos.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Muestra el cliente utilizado para obtener datos de streaming en Estadísticas para nerds.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Mostrar en estadísticas para nerds</string>
<string name="revanced_spoof_streaming_data_summary">"Falsifica las transmisiones de video para prevenir problemas de reproducción.
Si eres un usuario de YouTube Premium, esta opción puede ser necesaria."</string>
@ -676,10 +673,6 @@ Si eres un usuario de YouTube Premium, esta opción puede ser necesaria."</strin
• Reemplazar dominio: Sigue la configuración del registro del historial de su cuenta de Google.
• Bloquear el registro del historial: El registro de historial está bloqueado."</string>
<string name="revanced_watch_history_type_title">Tipo de vista del historial</string>
<string name="revanced_webview_contextual_menu_get_cookies">Obtener cookies</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Obtener ID de sincronización de datos</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Obtener datos de visitante</string>
<string name="revanced_webview_contextual_menu_refresh">Actualizar</string>
<string name="revanced_webview_cookies">Cookies</string>
<string name="revanced_webview_copy">Copiar</string>
<string name="revanced_webview_data_sync_id">ID de sincronización de datos</string>

View file

@ -50,8 +50,8 @@ Cliquez sur le bouton Continuer et autorisez les modifications d'optimisations."
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Les hachages JavaScript du lecteur sont valides pendant environ un mois, et les hachages obsolètes peuvent être révoqués par GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Essayez d\'activer : Falsifier les flux vidéo &gt; Connexion à Android VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Impossible de récupérer les flux du client.</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Entrez un hachage JavaScript valide du lecteur</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Hachage JavaScript du lecteur</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Entrez un hachage JavaScript valide du lecteur</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Hachage JavaScript du lecteur</string>
<string name="morphe_spoof_video_streams_screen_summary">Falsifiez les flux vidéo du client afin déviter les problèmes de lecture</string>
<string name="morphe_spoof_video_streams_screen_title">Falsifier les flux vidéo</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Non connecté à Android VR. Appuyez pour vous connecter</string>
@ -619,7 +619,6 @@ Cliquez ici pour découvrir comment créer une clé API."</string>
<string name="revanced_spoof_client_title">Usurpation de client</string>
<string name="revanced_spoof_client_type_summary">"Définit un client par défaut pour l'usurpation d'identité."</string>
<string name="revanced_spoof_client_type_title">Client par défaut</string>
<string name="revanced_spoof_streaming_data_default_client_title">Client par défaut</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">Cliquez ici pour savoir comment obtenir un gage d\'authentification.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">À propos d\'Android sans authentification SDK</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Cliquez ici pour savoir comment vous connecter à Android No SDK.</string>
@ -663,8 +662,6 @@ Cliquez ici pour découvrir comment créer une clé API."</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Échec de l\'émission d\'un jeton d\'authentification.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Cliquez sur \'Réinitialiser\' et réessayez.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Réinitialisation du jeton d\'authentification et du code d\'activation.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Affiche le client utilisé pour récupérer les données de diffusion en direct dans \'Statistiques avancées\'</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Afficher dans les statistiques avancées</string>
<string name="revanced_spoof_streaming_data_summary">"Les données de lecture en direct sont falsifiées.
Si vous êtes un utilisateur YouTube Premium, ce paramètre n'est peut-être pas nécessaire."</string>
@ -685,10 +682,6 @@ Si vous êtes un utilisateur YouTube Premium, ce paramètre n'est peut-être pas
• Remplacer le domaine : Respecte les paramètres d'historique des vidéos regardées du compte Google.
• Bloquer l'historique des vidéos regardées : L'historique des vidéos regardées est bloqué."</string>
<string name="revanced_watch_history_type_title">Type d\'historique de visionnage</string>
<string name="revanced_webview_contextual_menu_get_cookies">Obtenir les cookies</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Obtenir l\'identifiant de synchronisation des données</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Obtenir les données des visiteurs</string>
<string name="revanced_webview_contextual_menu_refresh">Rafraîchir</string>
<string name="revanced_webview_cookies">Cookies</string>
<string name="revanced_webview_copy">Copier</string>
<string name="revanced_webview_data_sync_id">Identifiant de synchronisation des données</string>

View file

@ -51,8 +51,8 @@ Nyomj a folytatás gombra, és engedélyezd az optimalizálási módosításokat
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">A lejátszó JavaScript hash-ai kb. egy hónapig érvényesek, és a GVS visszavonhatja a lejárt hash-okat.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Próbálja meg a következők engedélyezését: Spoof video streams &gt; Android VR bejelentkezés</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Nem találtunk ügyfelek által megosztott folyamokat</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Adjon meg egy érvényes játékos JavaScript hasht</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Játékos JavaScript hash</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Adjon meg egy érvényes játékos JavaScript hasht</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Játékos JavaScript hash</string>
<string name="morphe_spoof_video_streams_screen_summary">A kliens videó streamek hamisítása a lejátszási problémák elkerülése érdekében</string>
<string name="morphe_spoof_video_streams_screen_title">Videó streamek hamisítása</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Nem vagy bejelentkezve az Android VR-ben. Vágja le a kurzort itt a bejelentkezéshez</string>
@ -620,7 +620,6 @@ Kattintson ide az API-kulcs kiadásának megtekintéséhez."</string>
<string name="revanced_spoof_client_title">Kliens hamisítása</string>
<string name="revanced_spoof_client_type_summary">"Meghatároz egy alapértelmezett klienst a hamisításhoz."</string>
<string name="revanced_spoof_client_type_title">Alapértelmezett kliens</string>
<string name="revanced_spoof_streaming_data_default_client_title">Alapértelmezett kliens</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">Kattintson ide, ha szeretné megtudni, hogyan szerezhet be hitelesítési tokent.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">Az Android No SDK Auth-ról</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Kattints ide az Android No SDK-ba való bejelentkezéshez.</string>
@ -664,8 +663,6 @@ Kattintson ide az API-kulcs kiadásának megtekintéséhez."</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Nem sikerült hitelesítési tokent kiállítani.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Kattintson a Visszaállítás gombra, és próbálja újra.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Hitelesítési token és aktiválási kód visszaállítása.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Megmutatja a streaming adatok lekérdezésére használt klienst a Statisztikák kockáknakban.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Megjelenik a statisztikák kockáknakban</string>
<string name="revanced_spoof_streaming_data_summary">"A lejátszási problémák megelőzése érdekében hamisítja a streaming-adatokat.
Ha YouTube Premium felhasználó vagy, akkor ez a beállítás nem feltétlenül szükséges."</string>
@ -686,10 +683,6 @@ Ha YouTube Premium felhasználó vagy, akkor ez a beállítás nem feltétlenül
• Domain cseréje: Követi a Google-fiók megtekintési előzmények beállításait.
• Megtekintési előzmények blokkolása: A megtekintési előzmények blokkolva vannak."</string>
<string name="revanced_watch_history_type_title">Megtekintési előzmények típusa</string>
<string name="revanced_webview_contextual_menu_get_cookies">Sütik fogadása</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Adatszinkron azonosító lekérése</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Látogató adatok lekérése</string>
<string name="revanced_webview_contextual_menu_refresh">Frissít</string>
<string name="revanced_webview_cookies">Sütik</string>
<string name="revanced_webview_copy">Másol</string>
<string name="revanced_webview_data_sync_id">Adatszinkron azonosító</string>

View file

@ -51,8 +51,8 @@ Ketuk tombol lanjutkan dan izinkan perubahan pengoptimalan."</string>
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Pemutar hash JavaScript berlaku selama kurang lebih satu bulan, dan hash yang kedaluwarsa mungkin dicabut oleh GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Cobalah mengaktifkan: Palsukan aliran video &gt; Masuk Android VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Tidak dapat mengambil aliran klien apa pun</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Masukkan hash pemutar JavaScript yang sah</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Hash pemutar JavaScript</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Masukkan hash pemutar JavaScript yang sah</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Hash pemutar JavaScript</string>
<string name="morphe_spoof_video_streams_screen_summary">Palsukan klien aliran video untuk mencegah masalah pemutaran</string>
<string name="morphe_spoof_video_streams_screen_title">Palsukan aliran video</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Tidak masuk ke Android VR. Ketuk disini untuk masuk</string>

View file

@ -51,8 +51,8 @@ Tocca il pulsante Continua e consenti le modifiche di ottimizzazione."</string>
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Gli hash JavaScript del lettore sono validi per circa un mese e gli hash obsoleti potrebbero essere revocati da GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Prova ad abilitare: Falsificare video streams &gt; accesso Android VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Impossibile recuperare qualsiasi flusso client</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Inserisci un hash JavaScript del lettore valido</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Hash JavaScript del giocatore</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Inserisci un hash JavaScript del lettore valido</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Hash JavaScript del giocatore</string>
<string name="morphe_spoof_video_streams_screen_summary">Falsifica i flussi video del client per prevenire problemi di riproduzione</string>
<string name="morphe_spoof_video_streams_screen_title">Falsifica i flussi video</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Non sei connesso ad Android VR. Premere qui per accedere</string>
@ -620,7 +620,6 @@ Fai clic per vedere come emettere una chiave API."</string>
<string name="revanced_spoof_client_title">Falsifica client</string>
<string name="revanced_spoof_client_type_summary">"Definisce un client predefinito per la falsificazione."</string>
<string name="revanced_spoof_client_type_title">Client predefinito</string>
<string name="revanced_spoof_streaming_data_default_client_title">Client predefinito</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">Clicca per vedere come ottenere un token di autenticazione.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">Informazioni su Android No Sdk Auth</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Clicca qui per scoprire come accedere ad Android No SDK.</string>
@ -665,8 +664,6 @@ Note:
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Emissione di un token di accesso non riuscita</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Tocca \"Ripristina\" e riprova</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Token di accesso e codice di attivazione ripristinati</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Mostra il client utilizzato per recuperare i dati in streaming in statistiche per nerd.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Mostra in statistiche per nerd</string>
<string name="revanced_spoof_streaming_data_summary">"Falsifica i flussi video del client per evitare problemi di riproduzione.
Se sei un utente YouTube Premium, questa impostazione potrebbe non essere necessaria."</string>
@ -687,10 +684,6 @@ Se sei un utente YouTube Premium, questa impostazione potrebbe non essere necess
• Sostituisci dominio: segue le impostazioni della cronologia delle visualizzazioni dell'account Google.
• Blocca cronologia delle visualizzazioni: la cronologia delle visualizzazioni è bloccata."</string>
<string name="revanced_watch_history_type_title">Cambia il tipo di cronologia</string>
<string name="revanced_webview_contextual_menu_get_cookies">Ottieni i cookie</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Ottieni ID di sincronizzazione dati</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Ottieni dati dei visitatori</string>
<string name="revanced_webview_contextual_menu_refresh">Aggiorna</string>
<string name="revanced_webview_cookies">Cookie</string>
<string name="revanced_webview_copy">Copia</string>
<string name="revanced_webview_data_sync_id">ID di sincronizzazione dati</string>

View file

@ -40,8 +40,8 @@
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">JavaScript hashes של השחקן תקפים לתקופה של כחודש, ו-hashes מיושנים עשויים להתבטל על ידי GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Попробуйте включить: Фальшивый поток &gt; Войти в Android VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Не удалось получить доступ к потокам клиента</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">\"הזן hash JavaScript תקף של השחקן</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">‌Ԓ԰՞ԑ JavaScript ԲՈԴ</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">\"הזן hash JavaScript תקף של השחקן</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">‌Ԓ԰՞ԑ JavaScript ԲՈԴ</string>
<string name="morphe_spoof_video_streams_screen_summary">זייף את זרמי הווידאו של הלקוח כדי למנוע בעיות השמעה</string>
<string name="morphe_spoof_video_streams_screen_title">זיוף זרמי וידאו</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Вы не вошли в Android VR. Нажмите здесь, чтобы войти</string>

View file

@ -45,8 +45,8 @@
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">JavaScript 版プレーヤーのハッシュはおよそ1ヶ月間有効です。古くなったハッシュは GVS 側で無効にされる可能性があります。</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">有効にしてみてください: [動画ストリームを偽装] &gt; [Android VR (ログイン)]</string>
<string name="morphe_spoof_video_streams_no_clients_toast">クライアントの動画ストリームを取得できませんでした</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">有効な「JavaScript 版プレーヤーのハッシュ」を入力します</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">JavaScript 版プレーヤーのハッシュ</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">有効な「JavaScript 版プレーヤーのハッシュ」を入力します</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">JavaScript 版プレーヤーのハッシュ</string>
<string name="morphe_spoof_video_streams_screen_summary">動画再生の失敗を回避するために、クライアントの動画ストリームを偽装します</string>
<string name="morphe_spoof_video_streams_screen_title">動画ストリームを偽装</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Android VR にログインしていません。ログインするには、ここをタップしてください</string>
@ -590,7 +590,6 @@ API キーの発行方法については、ここをタップしてください
<string name="revanced_spoof_client_title">クライアントを偽装</string>
<string name="revanced_spoof_client_type_summary">"偽装するデフォルトのクライアントを定義します。"</string>
<string name="revanced_spoof_client_type_title">既定のクライアント</string>
<string name="revanced_spoof_streaming_data_default_client_title">デフォルトのクライアント</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Android No SDK にログインする方法については、ここをタップしてください</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_title">Android No SDK にログイン</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_dialog_get_authorization_token_text">認証トークンを取得</string>
@ -622,8 +621,6 @@ API キーの発行方法については、ここをタップしてください
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">認証トークンを発行できませんでした。</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">[リセット] をタップして再試行してください。</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">認証トークンとアクティベーションコードをリセットします。</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Stats for nerds でストリーミングデータの取得に使用されるクライアントを定義する。</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">マニア向けの統計を表示</string>
<string name="revanced_spoof_streaming_data_summary">"クライアントの動画ストリームを偽装し再生の問題を防止します。
YouTube Premiumを利用の場合この設定は不要かもしれません。"</string>
@ -646,10 +643,6 @@ YouTube Premiumを利用の場合この設定は不要かもしれません。"<
・ドメインを置き換えます:  Google アカウントの視聴履歴設定に従います。
・視聴履歴をブロックします:  視聴履歴をブロックします。"</string>
<string name="revanced_watch_history_type_title">再生履歴の種類</string>
<string name="revanced_webview_contextual_menu_get_cookies">Cookie を取得</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">データ同期 ID を取得</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Visitor Data を取得</string>
<string name="revanced_webview_contextual_menu_refresh">更新</string>
<string name="revanced_webview_cookies">Cookie</string>
<string name="revanced_webview_copy">コピー</string>
<string name="revanced_webview_data_sync_id">データ同期 ID</string>

View file

@ -50,8 +50,8 @@
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">플레이어 자바스크립트 해시는 약 한 달 동안 유효하며, 오래된 해시는 GVS에 의해 취소될 수 있습니다.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">다음을 활성화해 보세요: 동영상 스트림 속이기 &gt; Android VR에 로그인하기</string>
<string name="morphe_spoof_video_streams_no_clients_toast">클라이언트 스트림을 가져올 수 없습니다</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">유효한 플레이어 자바스크립트 해시를 입력하세요</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">플레이어 자바스크립트 해시</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">유효한 플레이어 자바스크립트 해시를 입력하세요</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">플레이어 자바스크립트 해시</string>
<string name="morphe_spoof_video_streams_screen_summary">클라이언트 동영상 스트림을 속여서 재생 문제를 방지할 수 있습니다</string>
<string name="morphe_spoof_video_streams_screen_title">동영상 스트림 속이기</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Android VR에 로그인되어 있지 않습니다 \n로그인하려면 여기를 탭하세요</string>
@ -628,7 +628,6 @@ API Key를 발급받는 방법을 보려면 여기를 탭하세요."</string>
부작용 (요청 차단):
• VPN 또는 DNS를 사용할 경우에 앱이 강제로 종료될 수 있습니다."</string>
<string name="revanced_spoof_client_type_title">기본 클라이언트</string>
<string name="revanced_spoof_streaming_data_default_client_title">기본 클라이언트</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">인증 토큰을 받는 방법을 보려면 여기를 탭하세요.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">Android SDK 없음 인증 있음에 대한 정보</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Android SDK 없음에 로그인하는 방법을 보려면 여기를 탭하세요.</string>
@ -673,8 +672,6 @@ API Key를 발급받는 방법을 보려면 여기를 탭하세요."</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">인증 토큰을 발급할 수 없습니다.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">초기화를 탭하고 다시 시도하세요.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">인증 토큰과 활성화 코드를 초기화하였습니다.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">\'스트리밍 데이터를 가져오는 데 사용되는 클라이언트\'가 동영상 통계에서 표시됩니다.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">동영상 통계에서 표시</string>
<string name="revanced_spoof_streaming_data_summary">"클라이언트 동영상 스트림을 속여서 재생 문제를 방지할 수 있습니다.
YouTube Premium 사용자라면 이 설정은 필요하지 않을 수 있습니다."</string>
@ -695,10 +692,6 @@ YouTube Premium 사용자라면 이 설정은 필요하지 않을 수 있습니
• 도메인 변경: Google 계정의 시청 기록 설정을 따릅니다.
• 시청 기록 차단: 시청 기록이 차단됩니다."</string>
<string name="revanced_watch_history_type_title">시청 기록 유형</string>
<string name="revanced_webview_contextual_menu_get_cookies">Cookies 가져오기</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Data Sync ID 가져오기</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Visitor Data 가져오기</string>
<string name="revanced_webview_contextual_menu_refresh">새로고침</string>
<string name="revanced_webview_cookies">Cookies</string>
<string name="revanced_webview_copy">복사</string>
<string name="revanced_webview_data_sync_id">Data Sync ID</string>

View file

@ -51,8 +51,8 @@ Stuknij przycisk kontynuacji i zezwól na zmiany w optymalizacji."</string>
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Hashe JavaScript odtwarzacza są ważne przez około miesiąc, a nieaktualne hashe mogą zostać unieważnione przez GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Spróbuj włączyć: Oszukuj strumienie filmów i Logowanie Android VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Nie można przechwycić żadnego ze strumieni klienta.</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Wprowadź poprawny hash JavaScript odtwarzacza</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Hash JavaScript odtwarzacza</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Wprowadź poprawny hash JavaScript odtwarzacza</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Hash JavaScript odtwarzacza</string>
<string name="morphe_spoof_video_streams_screen_summary">Oszukuje strumień filmów klienta, aby zapobiec problemom z odtwarzaniem</string>
<string name="morphe_spoof_video_streams_screen_title">Oszukuj strumienie filmów</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Nie jesteś zalogowany do Android VR. Stuknij tutaj, aby się zalogować</string>
@ -625,7 +625,6 @@ Kliknij, by zobaczyć, jak zgłosić klucz API."</string>
Efekt uboczny (blokada żądania):
• aplikacja może wymusić zamknięcie się podczas korzystania z VPN lub DNS"</string>
<string name="revanced_spoof_client_type_title">Domyślny klient</string>
<string name="revanced_spoof_streaming_data_default_client_title">Domyślny klient</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">Kliknij, by zobaczyć jak uzyskać token uwierzytelniający.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">O Androidzie bez autoryzacji SDK</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Kliknij tutaj, by zobaczyć jak zalogować się do Android bez SDK.</string>
@ -669,8 +668,6 @@ Efekt uboczny (blokada żądania):
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Nie udało się wygenerować tokenu uwierzytelniającego.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Kliknij przycisk zresetuj i spróbuj ponownie</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Zresetowano token uwierzytelniający i kod aktywacyjny.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Pokazuje używanego klienta do przechwytywania strumienia danych w statystykach dla nerdów.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Informacja w statystykach dla nerdów</string>
<string name="revanced_spoof_streaming_data_summary">"Oszukuje strumień filmu klienta, by zapobiec problemom z odtwarzaniem.
Jeśli jesteś użytkownikiem YouTube Premium, to ustawienie te nie jest konieczne."</string>
@ -691,10 +688,6 @@ Jeśli jesteś użytkownikiem YouTube Premium, to ustawienie te nie jest koniecz
• Zastąpienie domeny: Stosuje się do ustawień historii oglądania konta Google
• Zablokowanie historii oglądania: Historia oglądania jest zablokowana"</string>
<string name="revanced_watch_history_type_title">Typ historii oglądania</string>
<string name="revanced_webview_contextual_menu_get_cookies">Pobierz pliki cookie</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Pobierz ID synchronizacji danych</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Pobierz Visitor Data</string>
<string name="revanced_webview_contextual_menu_refresh">Odśwież</string>
<string name="revanced_webview_cookies">Pliki cookie</string>
<string name="revanced_webview_copy">Kopiuj</string>
<string name="revanced_webview_data_sync_id">ID synchronizacji danych</string>

View file

@ -51,8 +51,8 @@ Toque no botão continuar e permita as alterações de otimização."</string>
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Hashes JavaScript do player são válidos por aproximadamente um mês, e hashes desatualizados podem ser revogados pelo GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Tente habilitar: Simular fluxos de vídeo &gt; Android VR sign in</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Não foi possível obter nenhum fluxo de cliente</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Insira um hash JavaScript do jogador válido</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Hash JavaScript do jogador</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Insira um hash JavaScript do jogador válido</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Hash JavaScript do jogador</string>
<string name="morphe_spoof_video_streams_screen_summary">Simular os fluxos de vídeo do cliente para evitar problemas de reprodução</string>
<string name="morphe_spoof_video_streams_screen_title">Simular fluxos de vídeo</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Não conectado ao Android VR. Toque aqui para conectar</string>
@ -620,7 +620,6 @@ Clique para ver como emitir uma chave de API."</string>
<string name="revanced_spoof_client_title">Falsificar cliente</string>
<string name="revanced_spoof_client_type_summary">"Define um cliente padrão para falsificar."</string>
<string name="revanced_spoof_client_type_title">Cliente padrão</string>
<string name="revanced_spoof_streaming_data_default_client_title">Cliente padrão</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">Clique para ver como obter um token de autenticação.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">Sobre o Android: Sem autenticação do SDK</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Clique aqui para ver como iniciar sessão no Android sem SDK.</string>
@ -664,8 +663,6 @@ Clique para ver como emitir uma chave de API."</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Falha ao emitir um token de autenticação.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Clique em Reiniciar e tente novamente.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Redefinição do token de autenticação e do código de ativação.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Exibir o cliente usado para buscar dados de streaming em estatísticas para nerds.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Exibir em Estatísticas para nerds</string>
<string name="revanced_spoof_streaming_data_summary">"Falsificar o cliente de reprodução de vídeo para prevenir problemas de reprodução.
Se você é um usuário de YouTube Premium, essa configuração pode não ser necessária."</string>
@ -686,10 +683,6 @@ Se você é um usuário de YouTube Premium, essa configuração pode não ser ne
• Substituir domínio: segue as configurações do histórico de exibição da conta do Google.
• Bloquear histórico de exibição: o histórico de exibição é bloqueado."</string>
<string name="revanced_watch_history_type_title">Tipo de histórico de exibição</string>
<string name="revanced_webview_contextual_menu_get_cookies">Obter Cookies</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Obter Id de sincronização de dados</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Obter Dados do visitante</string>
<string name="revanced_webview_contextual_menu_refresh">Atualizar</string>
<string name="revanced_webview_cookies">Cookies</string>
<string name="revanced_webview_copy">Copiar</string>
<string name="revanced_webview_data_sync_id">Id de sincronização de dados</string>

View file

@ -51,8 +51,8 @@
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Хеши JavaScript плеера действительны примерно один месяц, а устаревший хеш может быть отменен GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Попробуйте включить: Поддельные видео потоки &gt; Вход в Android VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Не удалось получить никаких клиентских потоков</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Введите допустимый хеш JavaScript плеера</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Хеш JavaScript плеера</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Введите допустимый хеш JavaScript плеера</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Хеш JavaScript плеера</string>
<string name="morphe_spoof_video_streams_screen_summary">Подделка видео потоков клиента для предотвращения проблем с воспроизведением</string>
<string name="morphe_spoof_video_streams_screen_title">Подмена видеопотоков</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Вы не вошли в Android VR. Нажмите здесь, чтобы войти</string>
@ -618,7 +618,6 @@
<string name="revanced_spoof_client_title">Подмена клиента</string>
<string name="revanced_spoof_client_type_summary">"Определяет клиент по умолчанию для подмены."</string>
<string name="revanced_spoof_client_type_title">Клиент по умолчанию</string>
<string name="revanced_spoof_streaming_data_default_client_title">Клиент по умолчанию</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">Нажмите, чтобы посмотреть, как получить токен авторизации.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">О Android No SDK авторизации</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Нажмите здесь, чтобы узнать, как войти в Android No SDK.</string>
@ -662,8 +661,6 @@
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Не удалось присвоить токен аутентификации.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Нажмите Сброс и попробуйте еще раз.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Токен аутентификации и код активации сброшены.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Показывает клиент, используемый для получения потоковых данных в Статистике для сисадминов.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Показывать в \"Статистике для сисадминов\"</string>
<string name="revanced_spoof_streaming_data_summary">"Подменяет клиентские видеопотоки для предотвращения проблем с воспроизведением.
Если вы являетесь пользователем YouTube Premium, эта настройка может быть не нужна."</string>
@ -686,10 +683,6 @@
• Заменить домен: Настройка истории просмотров аккаунта Google.
• Блокировка истории просмотров: История просмотров заблокирована."</string>
<string name="revanced_watch_history_type_title">Тип истории просмотра</string>
<string name="revanced_webview_contextual_menu_get_cookies">Получить куки</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Получить ID синхр. данных</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Получить данные посещаемости</string>
<string name="revanced_webview_contextual_menu_refresh">Обновить</string>
<string name="revanced_webview_cookies">Куки</string>
<string name="revanced_webview_copy">Копия</string>
<string name="revanced_webview_data_sync_id">ID синхр. данных</string>

View file

@ -50,8 +50,8 @@ Devam düğmesine dokunun ve pil optimizasyonlarını devre dışı bırakın."<
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Oynatıcı JavaScript hash\'leri yaklaşık bir ay süreyle geçerlidir ve süresi geçmiş hash\'ler GVS tarafından iptal edilebilir.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Şunu etkinleştirmeyi dene: Video akışlarını taklit et (Spoof) &gt; Android VR oturum açma</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Hiçbir istemci akışı alınamadı.</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Geçerli bir oynatıcı JavaScript hash değeri girin</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Oynatıcı JavaScript hash</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Geçerli bir oynatıcı JavaScript hash değeri girin</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Oynatıcı JavaScript hash</string>
<string name="morphe_spoof_video_streams_screen_summary">Oynatma sorunlarını önlemek için istemci video akışlarını taklit et</string>
<string name="morphe_spoof_video_streams_screen_title">Video akışlarını taklit et</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Android VR oturumu açılmadı. Oturum açmak için buraya dokunun.</string>
@ -630,7 +630,6 @@ APl anahtarının nasıl verileceğini görmek için tıklayın."</string>
<string name="revanced_spoof_client_title">Sahte istemci</string>
<string name="revanced_spoof_client_type_summary">"Taklit etmek için varsayılan bir istemci tanımlar."</string>
<string name="revanced_spoof_client_type_title">Varsayılan istemci</string>
<string name="revanced_spoof_streaming_data_default_client_title">Varsayılan istemci</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">Kimlik doğrulama belirtecinin nasıl alınacağını görmek için tıklayın.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">Android No SDK Kimlik Doğrulama Hakkında</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Android SDK olmadan nasıl giriş yapacağınızı görmek için buraya tıklayın.</string>
@ -683,8 +682,6 @@ APl anahtarının nasıl verileceğini görmek için tıklayın."</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Kimlik doğrulama anahtarı oluşturulamadı.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Sıfırla butonuna tıklayın ve tekrar deneyin.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Kimlik doğrulama anahtarı ve etkinleştirme kodu sıfırlandı.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Stats for nerds\'te akış verilerini almak için kullanılan istemciyi gösterir.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">İstatistikleri inekler için göster</string>
<string name="revanced_spoof_streaming_data_summary">"Oynatma sorunlarını önlemek için istemci video akışlarını taklit edin.
YouTube Premium kullanıcısıysanız bu ayar gerekli olmayabilir."</string>
@ -705,10 +702,6 @@ YouTube Premium kullanıcısıysanız bu ayar gerekli olmayabilir."</string>
- Alan adını değiştir: Google hesabının izleme geçmişi ayarlarını takip eder.
- İzleme geçmişini engelle: İzleme geçmişi engellenir."</string>
<string name="revanced_watch_history_type_title">İzleme geçmişi türü</string>
<string name="revanced_webview_contextual_menu_get_cookies">Çerezleri getir</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Veri Senkronizasyon Kimliğini getir</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Ziyaretçi Verilerini getir</string>
<string name="revanced_webview_contextual_menu_refresh">Yenile</string>
<string name="revanced_webview_cookies">Çerezler</string>
<string name="revanced_webview_copy">Kopyala</string>
<string name="revanced_webview_data_sync_id">Veri Senkronizasyon Kimliği</string>

View file

@ -51,8 +51,8 @@
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Хеші JavaScript плеєра дійсні приблизно один місяць, а застарілі хеші можуть бути скасовані GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Спробуйте увімкнути: Підміна відеопотоків &gt; Android VR з входом</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Не вдалося отримати жодних клієнтських потоків</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Введіть дійсний хеш JavaScript плеєра</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Хеш JavaScript плеєра</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Введіть дійсний хеш JavaScript плеєра</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Хеш JavaScript плеєра</string>
<string name="morphe_spoof_video_streams_screen_summary">Підміняти відеопотоки клієнта, щоб уникнути проблем із відтворенням</string>
<string name="morphe_spoof_video_streams_screen_title">Підміна відеопотоків</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Ви не ввійшли в Android VR. Натисніть тут, щоб увійти</string>
@ -623,7 +623,6 @@
<string name="revanced_spoof_client_title">Підмінити клієнт</string>
<string name="revanced_spoof_client_type_summary">"Визначає клієнт за замовчуванням для підробки."</string>
<string name="revanced_spoof_client_type_title">Клієнт за замовчуванням</string>
<string name="revanced_spoof_streaming_data_default_client_title">Клієнт за замовчуванням</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">Натисніть, щоб переглянути, як отримати токен авторизації.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">Про авторизацію Android No SDK</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Натисніть тут, щоб побачити як увійти в Android VR.</string>
@ -667,8 +666,6 @@
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Не вдалося видати токен автентифікації.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Натисніть Скинути та спробуйте ще раз.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Токен автентифікації та код активації скинуто.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Показує клієнт, який використовується для отримання потокових даних у \"Статистиці для сисадмінів\".</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Показувати у \"Статистиці для сисадмінів\"</string>
<string name="revanced_spoof_streaming_data_summary">"Підміняє клієнтські відеопотоки, щоб запобігти відтворенню проблем.
Якщо ви користувач YouTube Premium, цей параметр може бути не потрібен."</string>
@ -691,10 +688,6 @@
• Замінити домен: Дотримується налаштувань історії переглядів облікового запису Google.
• Блокувати історію переглядів: Історію переглядів заблоковано."</string>
<string name="revanced_watch_history_type_title">Тип історії перегляду</string>
<string name="revanced_webview_contextual_menu_get_cookies">Отримати куки</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Отримати ID синхр. даних</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Отримати дані відвідуваності</string>
<string name="revanced_webview_contextual_menu_refresh">Оновити</string>
<string name="revanced_webview_cookies">Куки</string>
<string name="revanced_webview_copy">Копія</string>
<string name="revanced_webview_data_sync_id">ID синхр. даних</string>

View file

@ -51,8 +51,8 @@ Nhấn vào Tiếp tục và cho phép thay đổi lựa chọn tối ưu hoá p
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Mã băm JavaScript của trình phát thường có hiệu lực khoảng một tháng, và các mã băm quá hạn có thể bị GVS thu hồi.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Hãy thử bật: Giả mạo luồng phát video &gt; Đăng nhập Android VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Không thể lấy bất kỳ luồng phát nào</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Nhập mã băm JavaScript hợp lệ của trình phát</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Mã băm JavaScript của trình phát</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Nhập mã băm JavaScript hợp lệ của trình phát</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Mã băm JavaScript của trình phát</string>
<string name="morphe_spoof_video_streams_screen_summary">Giả mạo luồng phát video của client nhằm ngăn chặn lỗi khi phát</string>
<string name="morphe_spoof_video_streams_screen_title">Giả mạo luồng phát video</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">Chưa đăng nhập Android VR. Bấm vào đây để đăng nhập</string>
@ -626,7 +626,6 @@ Nhấp vào đây để xem các bước phát hành khóa API."</string>
Hạn chế (Chặn truy vấn):
• Ứng dụng có thể bị buộc đóng khi đang dùng VPN hoặc DNS."</string>
<string name="revanced_spoof_client_type_title">Loại client mặc định</string>
<string name="revanced_spoof_streaming_data_default_client_title">Client mặc định</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">Nhấn để xem cách lấy token xác thực.</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_title">Về xác thực Android No SDK</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_about_summary">Nhấn vào đây để xem hướng dẫn đăng nhập vào Android No SDK.</string>
@ -673,8 +672,6 @@ Chi tiết:
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Không thể tạo mã xác thực.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Nhấn Đặt lại và thử lại.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Đặt lại mã xác thực và mã kích hoạt.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary">Hiển thị ứng dụng khách được sử dụng để nạp luồng dữ liệu trực tuyến trong Thống kê chi tiết.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Hiển thị trong Thống kê chi tiết</string>
<string name="revanced_spoof_streaming_data_summary">"Giả mạo client phát video để tránh lỗi khi phát.
Nếu bạn đã đăng ký YouTube Premium, thì có thể không cần thiết phải bật cài đặt này."</string>
@ -695,10 +692,6 @@ Nếu bạn đã đăng ký YouTube Premium, thì có thể không cần thiết
• Thay thế miền: Vẫn tuân theo cài đặt nhật ký xem của tài khoản Google.
• Chặn nhật ký: Nhật ký xem bị chặn hoàn toàn."</string>
<string name="revanced_watch_history_type_title">Kiểu nhật ký</string>
<string name="revanced_webview_contextual_menu_get_cookies">Lấy Cookie</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Lấy ID đồng bộ dữ liệu</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Lấy dữ liệu khách</string>
<string name="revanced_webview_contextual_menu_refresh">Làm mới</string>
<string name="revanced_webview_cookies">Cookie</string>
<string name="revanced_webview_copy">Sao chép</string>
<string name="revanced_webview_data_sync_id">ID đồng bộ dữ liệu</string>

View file

@ -51,8 +51,8 @@
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Player JavaScript 哈希有效期约为一个月,过期的哈希可能由 GVS 撤销。</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">请尝试启用:欺骗视频流 &gt; 登录 Google VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">无法获取任何客户端流</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">输入有效的 Player JavaScript 哈希</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Player JavaScript hash</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">输入有效的 Player JavaScript 哈希</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Player JavaScript hash</string>
<string name="morphe_spoof_video_streams_screen_summary">欺骗客户端视频流以防止播放问题</string>
<string name="morphe_spoof_video_streams_screen_title">欺骗视频流</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">尚未登录 Google VR。点击继续登录</string>
@ -543,7 +543,6 @@
<string name="revanced_spoof_client_title">伪装客户端</string>
<string name="revanced_spoof_client_type_summary">"定义用于伪装的默认客户端。"</string>
<string name="revanced_spoof_client_type_title">默认客户端</string>
<string name="revanced_spoof_streaming_data_default_client_title">默认客户端</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">点击查看如何获取认证令牌。</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_dialog_get_authorization_token_text">获取授权令牌</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_dialog_reset_text">重置</string>
@ -575,7 +574,6 @@
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">无法颁发身份验证令牌。</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">点击重置并重试。</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">身份验证令牌和激活码已重置。</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">显示统计信息</string>
<string name="revanced_spoof_streaming_data_title">伪装流媒体数据</string>
<string name="revanced_spoof_streaming_data_vr_auth_token_about_summary">点击查看如何获取认证令牌。</string>
<string name="revanced_spoof_streaming_data_vr_auth_token_about_title">关于 Android VR 认证</string>
@ -589,10 +587,6 @@
• 替换网域:遵循 Google 账号的观看历史记录设置。
• 屏蔽观看历史记录:观看历史记录被屏蔽。"</string>
<string name="revanced_watch_history_type_title">历史记录类型</string>
<string name="revanced_webview_contextual_menu_get_cookies">获取 Cookie</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">获取数据同步 ID</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">获取访客数据</string>
<string name="revanced_webview_contextual_menu_refresh">刷新</string>
<string name="revanced_webview_cookies">Cookie</string>
<string name="revanced_webview_copy">复制</string>
<string name="revanced_webview_data_sync_id">数据同步 ID</string>

View file

@ -51,8 +51,8 @@
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">播放器 JavaScript 雜湊值有效期約一個月,且過期的雜湊值可能被 GVS 撤銷。</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">試試啟用:偽裝視訊串流 &gt; Android VR 登入</string>
<string name="morphe_spoof_video_streams_no_clients_toast">未能擷取任何用戶端串流</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">請輸入有效的播放器 JavaScript 雜湊值</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">播放器 JavaScript 雜湊值</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">請輸入有效的播放器 JavaScript 雜湊值</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">播放器 JavaScript 雜湊值</string>
<string name="morphe_spoof_video_streams_screen_summary">偽裝用戶端影片串流以避免播放問題</string>
<string name="morphe_spoof_video_streams_screen_title">偽裝影片串流</string>
<string name="morphe_spoof_video_streams_sign_in_android_vr_about_summary">尚未登入 Android VR。輕觸這裡即可登入</string>
@ -589,7 +589,6 @@
<string name="revanced_spoof_client_summary">偽裝客戶端以避免播放問題。</string>
<string name="revanced_spoof_client_title">偽裝客戶端</string>
<string name="revanced_spoof_client_type_title">預設客戶端</string>
<string name="revanced_spoof_streaming_data_default_client_title">預設用戶端</string>
<string name="revanced_spoof_streaming_data_no_sdk_auth_token_about_summary">點擊查看如何取得身份驗證令牌。</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_dialog_get_authorization_token_text">取得授權令牌</string>
<string name="revanced_spoof_streaming_data_sign_in_android_no_sdk_dialog_reset_text">重置</string>
@ -621,7 +620,6 @@
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">頒發身份驗證令牌失敗。</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">點選重置按鈕然後重試。</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">身份驗證令牌和啟動碼重置。</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">顯示統計資料</string>
<string name="revanced_spoof_streaming_data_summary">"串流資料已偽裝。
如果您是 YouTube Premium 用戶,則可能無需進行此設定。"</string>
@ -638,10 +636,6 @@
・更換域:關注谷歌帳戶的手錶歷史設定。
・封鎖觀看記錄:觀看記錄被封鎖。"</string>
<string name="revanced_watch_history_type_title">觀看歷史類型</string>
<string name="revanced_webview_contextual_menu_get_cookies">取得 Cookies</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">取得資料同步ID</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">取得訪客資料</string>
<string name="revanced_webview_contextual_menu_refresh">重新整理</string>
<string name="revanced_webview_cookies">Cookies</string>
<string name="revanced_webview_copy">複製</string>
<string name="revanced_webview_data_sync_id">資料同步ID</string>

View file

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="revanced_ads_category">إعلانات</string>
<string name="revanced_credits_title">المساهمون
</string>
<string name="revanced_disable_screenshot_popup_summary">يعطّل النافذة المنبثقة التي تظهر عند التقاط لقطة شاشة.</string>
<string name="revanced_disable_screenshot_popup_title">تعطيل النافذة المنبثقة عند التقاط لقطة شاشة.</string>
<string name="revanced_hide_chat_button_summary">يُخفي زر الدردشة في شريط التنقّل.</string>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,53 +0,0 @@
if (!Object.hasOwn) {
Object.hasOwn = function (obj, key) {
if (obj == null) {
throw new TypeError("Object.hasOwn called on null or undefined");
}
return Object.prototype.hasOwnProperty.call(obj, key);
};
}
if (typeof URL === 'undefined') {
globalThis.URL = function (url) {
this.href = url;
// naive parsing for hostname
const match = url.match(/^(\w+):\/\/([^\/?#]+)(?:[\/?#]|$)/);
this.protocol = match ? match[1] + ':' : '';
this.hostname = match ? match[2] : '';
this.port = ''; // optional
this.pathname = url.replace(/^(\w+:\/\/[^\/?#]+)?/, '') || '/';
this.search = '';
this.hash = '';
};
}
if (typeof window === 'undefined') {
globalThis.window = {};
}
if (!window.location) {
window.location = {
_url: '',
set href(url) {
this._url = url;
// parse the URL so hostname works
this._parsed = new URL(url);
},
get href() {
return this._url;
},
get hostname() {
return this._parsed ? this._parsed.hostname : '';
}
};
}
if (typeof navigator === 'undefined') {
globalThis.navigator = {
userAgent: 'V8/Standalone',
platform: 'V8',
language: 'en-US',
// add more properties if needed
};
}

View file

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical"
android:transitionGroup="true">
<!-- Parent container for Toolbar -->
<FrameLayout
android:id="@+id/revanced_toolbar_parent"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:background="@android:color/white"
android:elevation="0dp">
<!-- Toolbar -->
<android.support.v7.widget.Toolbar
android:id="@+id/revanced_toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:background="@android:color/white"
app:navigationIcon="@drawable/revanced_settings_toolbar_arrow_left"
app:title="@string/revanced_settings_title" />
</FrameLayout>
<!-- Container for WebView -->
<WebView
android:id="@+id/revanced_webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/revanced_webview_get_cookies"
android:title="@string/revanced_webview_contextual_menu_get_cookies" />
<item
android:id="@+id/revanced_webview_get_data_sync_id"
android:title="@string/revanced_webview_contextual_menu_get_data_sync_id" />
<item
android:id="@+id/revanced_webview_get_visitor_data"
android:title="@string/revanced_webview_contextual_menu_get_visitor_data" />
<item
android:id="@+id/revanced_webview_refresh"
android:title="@string/revanced_webview_contextual_menu_refresh" />
</menu>

View file

@ -12,33 +12,60 @@ if (typeof URL === 'undefined') {
this.href = url;
// naive parsing for hostname
const match = url.match(/^(\w+):\/\/([^\/?#]+)(?:[\/?#]|$)/);
this.protocol = match ? match[1] + ':' : '';
const match = url.match(/^(https?:)\/\/([^\/?#:]+)(?::(\d+))?([^?#]*)?(\?[^#]*)?(#.*)?$/);
this.protocol = match ? match[1] : '';
this.hostname = match ? match[2] : '';
this.port = ''; // optional
this.pathname = url.replace(/^(\w+:\/\/[^\/?#]+)?/, '') || '/';
this.search = '';
this.hash = '';
this.port = match && match[3] ? match[3] : '';
this.pathname = match && match[4] ? match[4] : '/';
this.search = match && match[5] ? match[5] : '';
this.hash = match && match[6] ? match[6] : '';
};
}
if (typeof window === 'undefined') {
globalThis.window = {};
globalThis.window = globalThis;
}
if (typeof self === 'undefined') {
globalThis.self = globalThis;
}
if (!window.location) {
const defaultHost = "https://localhost/";
window.location = {
_url: '',
_url: defaultHost,
_parsed: new URL(defaultHost), // default, so hostname works
set href(url) {
this._url = url;
// parse the URL so hostname works
this._parsed = new URL(url);
},
get href() {
return this._url;
},
get protocol() {
return this._parsed.protocol;
},
get hostname() {
return this._parsed ? this._parsed.hostname : '';
return this._parsed.hostname;
},
get port() {
return this._parsed.port;
},
get host() {
return this._parsed.hostname + (this._parsed.port ? ':' + this._parsed.port : '');
},
get origin() {
return this._parsed.protocol + '//' + this.host;
},
get pathname() {
return this._parsed.pathname;
},
get search() {
return this._parsed.search;
},
get hash() {
return this._parsed.hash;
}
};
}
@ -50,4 +77,4 @@ if (typeof navigator === 'undefined') {
language: 'en-US',
// add more properties if needed
};
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,155 @@
/*
* Copyright 2026 Morphe.
* https://github.com/MorpheApp/morphe-patches
*
* See the included NOTICE file for GPLv3 §7(b) and §7(c) terms that apply to this code.
*/
/*!
* Player JS caching wrapper for yt.solver.core.js
*
* This wrapper maintains player JS state in memory to avoid:
* 1. Repeated JSON serialization/deserialization of large player JS
* 2. Repeated V8 parsing and bytecode generation for the same player
* 3. Repeated preprocessing of player JS by jsc()
*/
var _playerCache = {
playerHash: null,
preprocessedPlayer: null,
isPreprocessed: false
};
/**
* Set player JS code with a hash for cache validation.
* Call this before calling jscw() to ensure the player is available.
*
* @param {string} playerJS - The raw player JavaScript code
* @param {string} playerHash - A hash of the player JS for cache validation
*/
function setPlayer(playerJS, playerHash) {
if (_playerCache.playerHash !== playerHash) {
_playerCache.playerHash = playerHash;
_playerCache.preprocessedPlayer = playerJS;
_playerCache.isPreprocessed = false;
}
}
/**
* Set preprocessed player JS code.
* Use this when you already have preprocessed player JS from cache.
*
* @param {string} preprocessedPlayer - The preprocessed player JavaScript code
* @param {string} playerHash - A hash of the original player JS for cache validation
*/
function setPreprocessedPlayer(preprocessedPlayer, playerHash) {
if (_playerCache.playerHash !== playerHash) {
_playerCache.playerHash = playerHash;
_playerCache.preprocessedPlayer = preprocessedPlayer;
_playerCache.isPreprocessed = true;
}
}
/**
* Clear the player cache. Call this when switching players or cleaning up.
*/
function clearPlayerCache() {
_playerCache.playerHash = null;
_playerCache.preprocessedPlayer = null;
_playerCache.isPreprocessed = false;
}
/**
* Get the current player hash, or null if no player is loaded.
*
* @returns {string|null} The current player hash
*/
function getPlayerHash() {
return _playerCache.playerHash;
}
/**
* Check if the player with the given hash is already loaded and preprocessed.
*
* @param {string} playerHash - The hash to check
* @returns {boolean} True if the player is loaded and preprocessed
*/
function isPlayerReady(playerHash) {
return _playerCache.playerHash === playerHash && _playerCache.isPreprocessed;
}
/**
* Wrapped jsc() function that uses the cached player JS.
*
* Input format:
* {
* "requests": [
* {"type": "n" | "sig", "challenges": ["challenge1", "challenge2", ...]}
* ],
* "output_preprocessed": boolean (optional, defaults to true if player not preprocessed)
* }
*
* @param {object} input - The input object containing requests
* @returns {object} The solver output with responses and optionally preprocessed_player
*/
function jscw(input) {
if (!_playerCache.preprocessedPlayer) {
return {
type: "error",
error: "No player loaded. Call setPlayer() or setPreprocessedPlayer() first."
};
}
// Build the input for the underlying jsc() function
var jscInput;
if (_playerCache.isPreprocessed) {
jscInput = {
type: "preprocessed",
preprocessed_player: _playerCache.preprocessedPlayer,
requests: input.requests
};
} else {
jscInput = {
type: "player",
player: _playerCache.preprocessedPlayer,
requests: input.requests,
output_preprocessed: true
};
}
// Call the underlying jsc() function
var output = jsc(jscInput);
// If we got preprocessed player back, cache it
if (output.preprocessed_player && !_playerCache.isPreprocessed) {
_playerCache.preprocessedPlayer = output.preprocessed_player;
_playerCache.isPreprocessed = true;
}
return output;
}
/**
* Get the preprocessed player if available.
* This can be used by Java code to cache the preprocessed player.
*
* @returns {string|null} The preprocessed player JS, or null if not available
*/
function getPreprocessedPlayer() {
if (_playerCache.isPreprocessed) {
return _playerCache.preprocessedPlayer;
}
return null;
}
// Export for testing
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
setPlayer: setPlayer,
setPreprocessedPlayer: setPreprocessedPlayer,
clearPlayerCache: clearPlayerCache,
getPlayerHash: getPlayerHash,
isPlayerReady: isPlayerReady,
jscw: jscw,
getPreprocessedPlayer: getPreprocessedPlayer
};
}

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://github.com/google/material-design-icons/blob/c4daeb98d119754cc058ef366d69150fed76ec2d/symbols/android/restart_alt/materialsymbolsoutlined/restart_alt_gradN25_24px.xml
Changes made: Icon has been resized.
Copyright 2022 Google
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M449.04,813.96Q335.5,803.61 259.14,719.13Q182.77,634.65 182.77,520.31Q182.77,451.58 214,390.62Q245.23,329.66 300.69,288.69L332.19,319.89Q281.54,353.27 254.08,406.64Q226.62,460 226.62,520.31Q226.62,617.42 289.87,687.94Q353.12,758.46 449.04,770.11L449.04,813.96ZM512.88,814.73L512.88,770.88Q608.54,757.58 671.52,687.21Q734.5,616.85 734.5,520.31Q734.5,414.23 660.85,340.33Q587.19,266.42 480.62,266.42L453.46,266.42L519.85,332.81L488.65,364.69L368.81,244.85L488.65,125L519.85,156.19L453.46,222.58L480.62,222.58Q604.92,222.58 691.63,309.79Q778.34,397 778.34,520.31Q778.34,634.96 702.04,719.1Q625.73,803.23 512.88,814.73Z"/>
</vector>

View file

@ -1,6 +1,6 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android">
<com.google.android.libraries.youtube.common.ui.TouchImageView
android:id="@+id/revanced_reload_video_button"
android:id="@+id/morphe_reload_video_button"
style="@style/YouTubePlayerButton"
android:layout_width="@dimen/controls_overlay_action_button_size"
android:layout_height="@dimen/controls_overlay_action_button_size"
@ -10,5 +10,5 @@
android:layout_marginEnd="4dp"
android:layout_toStartOf="@id/music_app_deeplink_button"
android:padding="@dimen/controls_overlay_action_button_padding"
android:src="@drawable/revanced_reload_video" />
android:src="@drawable/morphe_reload_video" />
</RelativeLayout>

View file

@ -509,7 +509,7 @@
<string-array name="morphe_spoof_video_streams_client_type_entry_values">
<item>ANDROID_REEL</item>
<item>ANDROID_CREATOR</item>
<item>ANDROID_VR_1_47_48</item>
<item>ANDROID_VR_1_64</item>
<item>TV</item>
<item>VISIONOS</item>
</string-array>

View file

@ -38,6 +38,9 @@ Tap the continue button and allow optimization changes."</string>
<string name="morphe_oauth2_connection_failure_timeout">OAuth2 is temporarily not available (API timed out)</string>
<string name="morphe_oauth2_toast_invalid">Invalid VR sign in token, try signing in to VR again</string>
<string name="morphe_oauth2_toast_reset">Logged out of Android VR</string>
<string name="morphe_reload_video_summary_off">Reload video button is hidden.</string>
<string name="morphe_reload_video_summary_on">Reload video button is shown.</string>
<string name="morphe_reload_video_title">Show reload video button</string>
<string name="morphe_replace_music_with_youtube_summary_off">Shared links use music.youtube.com</string>
<string name="morphe_replace_music_with_youtube_summary_on">Shared links use youtube.com</string>
<string name="morphe_replace_music_with_youtube_title">Change share links to youtube.com</string>
@ -71,8 +74,8 @@ Video playback with AV1 may stutter or drop frames."</string>
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">Player JavaScript hashes are valid for approximately one month, and outdated hashes may be revoked by GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">Try enabling: Spoof video streams &gt; Android VR sign in</string>
<string name="morphe_spoof_video_streams_no_clients_toast">Could not fetch any client streams</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">Enter a valid player JavaScript hash</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">Player JavaScript hash</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">Enter a valid player JavaScript hash</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">Player JavaScript hash</string>
<string name="morphe_spoof_video_streams_player_js_variant_title">Player JavaScript variant</string>
<string name="morphe_spoof_video_streams_screen_summary">Spoof the client video streams to prevent playback issues</string>
<string name="morphe_spoof_video_streams_screen_title">Spoof video streams</string>
@ -2480,47 +2483,6 @@ Playback may stutter or drop frames"</string>
<string name="revanced_spoof_streaming_data_android_vr_enable_av1_codec_user_dialog_message">"Enabling this setting may use software AV1 decoding.
Video playback with AV1 may stutter or drop frames."</string>
<string name="revanced_spoof_streaming_data_default_client_title">Default client</string>
<string name="revanced_spoof_streaming_data_prioritize_video_quality_summary_off">Video codecs are prioritized.</string>
<string name="revanced_spoof_streaming_data_prioritize_video_quality_summary_on">Video quality is prioritized.</string>
<string name="revanced_spoof_streaming_data_prioritize_video_quality_title">Prioritize video quality</string>
<string name="revanced_spoof_streaming_data_prioritize_video_quality_user_dialog_message">"Some videos have the following video quality and codecs:
• 1080p AVC
• 720p AVC
• 360p VP9
If the device supports VP9, '1080p AVC' and '720p AVC' are ignored.
In other words, '360p VP9' is used as the highest video quality, and '1080p AVC' and '720p AVC' are not visible in the video quality flyout menu.
This is the intended behavior of the Android YouTube app, and this is why the video quality flyout menu was unavailable for some videos.
Although VP9 is a more advanced codec than AVC, some users may prefer '1080p AVC' over '360p VP9'.
If this setting is enabled, the highest available video quality from the server response is used, regardless of the video codec.
Example 1:
• 1080p AVC
• 720p AVC
• 360p VP9
→ 1080p AVC is used.
Example 2:
• 1080p AVC
• 1080p VP9
• 720p VP9
→ 1080p VP9 is used."</string>
<string name="revanced_spoof_streaming_data_reload_video_button_always_show_summary_off">Reload video button is shown when loading spinner showing.</string>
<string name="revanced_spoof_streaming_data_reload_video_button_always_show_summary_on">Reload video button is always shown.</string>
<string name="revanced_spoof_streaming_data_reload_video_button_always_show_title">Always show reload video button</string>
<string name="revanced_spoof_streaming_data_reload_video_button_summary_off">Reload video button is hidden.</string>
<string name="revanced_spoof_streaming_data_reload_video_button_summary_on">Reload video button is shown.</string>
<string name="revanced_spoof_streaming_data_reload_video_button_title">Show reload video button</string>
<string name="revanced_spoof_streaming_data_side_effects_android">"• Audio track menu is missing.
• Disable forced auto audio tracks is not available.
• Stable volume is not available.
@ -2598,9 +2560,6 @@ Example 2:
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">Failed to issue an authentication token.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">Click Reset and try again.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">Authentication token and activation code reset.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_off">Client is hidden in Stats for nerds</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_on">Client type is shown in Stats for nerds</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">Show in Stats for nerds</string>
<string name="revanced_spoof_streaming_data_summary_off">"Video streams are not spoofed
Playback may not work"</string>
@ -2609,15 +2568,6 @@ Playback may not work"</string>
If you are a YouTube Premium user, this setting may not be required"</string>
<string name="revanced_spoof_streaming_data_title">Spoof video streams</string>
<string name="revanced_spoof_streaming_data_type_entry_android_creator">Android Creator</string>
<string name="revanced_spoof_streaming_data_use_js_all_summary_off">"JavaScript client is not used for some videos that require fast playback:
• Autoplay in feed
• Clips
• Shorts"</string>
<string name="revanced_spoof_streaming_data_use_js_all_summary_on">"JavaScript client is used regardless of video type.
• JavaScript client still cannot play some videos."</string>
<string name="revanced_spoof_streaming_data_use_js_all_title">Use JavaScript client for all videos</string>
<string name="revanced_spoof_streaming_data_use_js_bypass_fake_buffering_dialog_message">"One reason videos start too late in the JavaScript client is due to YouTube's new anti-adblock measures.
When a video ad is blocked, GVS (Google Video Server) generate fake buffering proportional to the ad duration (A/B testing).
@ -2627,12 +2577,6 @@ uBlock Origin has found a workaround for this issue, which can be enabled via th
Click 'Open' to read the article."</string>
<string name="revanced_spoof_streaming_data_use_js_bypass_fake_buffering_dialog_open_text">Open</string>
<string name="revanced_spoof_streaming_data_use_js_bypass_fake_buffering_dialog_title">"YouTube's new anti-adblock measures"</string>
<string name="revanced_spoof_streaming_data_use_js_bypass_fake_buffering_summary_off">Bypass fake buffering is disabled.</string>
<string name="revanced_spoof_streaming_data_use_js_bypass_fake_buffering_summary_on">"Bypass fake buffering is enabled.
Limitations:
• Due to server-side changes, this may stop working at any time."</string>
<string name="revanced_spoof_streaming_data_use_js_bypass_fake_buffering_title">Bypass fake buffering</string>
<string name="revanced_spoof_streaming_data_use_js_summary_off">JavaScript client not added to available clients.</string>
<string name="revanced_spoof_streaming_data_use_js_summary_on">JavaScript client added to the available clients.</string>
<string name="revanced_spoof_streaming_data_use_js_title">Use JavaScript client</string>
@ -2826,10 +2770,6 @@ Disclaimer: Cookies issued without signing in may be invalid or may take time to
<string name="revanced_watch_history_type_title">Watch history type</string>
<string name="revanced_watch_next_processing_delay_summary">The amount of milliseconds delay in processing the watch next response.</string>
<string name="revanced_watch_next_processing_delay_title">Watch next processing delay</string>
<string name="revanced_webview_contextual_menu_get_cookies">Get Cookies</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">Get Data Sync Id</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">Get Visitor Data</string>
<string name="revanced_webview_contextual_menu_refresh">Refresh</string>
<string name="revanced_webview_cookies">Cookies</string>
<string name="revanced_webview_copy">Copy</string>
<string name="revanced_webview_data_sync_id">Data Sync Id</string>

View file

@ -332,6 +332,9 @@
<SwitchPreference android:title="@string/revanced_hide_player_youtube_music_button_title" android:key="revanced_hide_player_youtube_music_button" android:summaryOn="@string/revanced_hide_player_youtube_music_button_summary_on" android:summaryOff="@string/revanced_hide_player_youtube_music_button_summary_off" />
<SwitchPreference android:title="@string/revanced_hide_player_control_buttons_background_title" android:key="revanced_hide_player_control_buttons_background" android:summaryOn="@string/revanced_hide_player_control_buttons_background_summary_on" android:summaryOff="@string/revanced_hide_player_control_buttons_background_summary_off" />SETTINGS: HIDE_PLAYER_BUTTONS -->
<!-- SETTINGS: RELOAD_VIDEO
<SwitchPreference android:title="@string/morphe_reload_video_title" android:key="morphe_reload_video" android:summaryOn="@string/morphe_reload_video_summary_on" android:summaryOff="@string/morphe_reload_video_summary_off" />SETTINGS: RELOAD_VIDEO -->
<!-- SETTINGS: OVERLAY_BUTTONS
<PreferenceCategory android:title="@string/revanced_preference_category_overlay_buttons" android:layout="@layout/revanced_settings_preferences_category" />
<SwitchPreference android:title="@string/revanced_overlay_button_gemini_summarize_title" android:key="revanced_overlay_button_gemini_summarize" android:summary="@string/revanced_overlay_button_gemini_summarize_summary" />
@ -946,7 +949,7 @@
<PreferenceCategory android:title="@string/revanced_preference_category_js" android:layout="@layout/revanced_settings_preferences_category"/>
<app.morphe.extension.shared.settings.preference.SortedListPreference android:entries="@array/morphe_spoof_video_streams_player_js_variant_entries" android:title="@string/morphe_spoof_video_streams_player_js_variant_title" android:key="morphe_spoof_video_streams_player_js_variant" android:entryValues="@array/morphe_spoof_video_streams_player_js_variant_entry_values" />
<SwitchPreference android:title="@string/morphe_spoof_video_streams_disable_player_js_update_title" android:key="morphe_spoof_video_streams_disable_player_js_update" android:summaryOn="@string/morphe_spoof_video_streams_disable_player_js_update_summary_on" android:summaryOff="@string/morphe_spoof_video_streams_disable_player_js_update_summary_off" />
<EditTextPreference android:title="@string/morphe_spoof_video_streams_player_js_hash_title" android:key="morphe_spoof_video_streams_player_js_hash" android:summary="@string/morphe_spoof_video_streams_player_js_hash_summary" />
<EditTextPreference android:title="@string/morphe_spoof_video_streams_player_js_hash_value_title" android:key="morphe_spoof_video_streams_player_js_hash_value" android:summary="@string/morphe_spoof_video_streams_player_js_hash_value_summary" />
</PreferenceScreen>SETTINGS: SPOOF_VIDEO_STREAMS -->
<!-- SETTINGS: WATCH_HISTORY

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="#FFFFFF">
<path
android:fillColor="#FFFFFF"
android:pathData="M427,847.23Q306.46,827.61 228.23,735.08Q150,642.54 150,520Q150,462 167.58,407.62Q185.16,353.23 218.54,304.92L279.31,365.69Q257.46,400.31 246.73,439.73Q236,479.15 236,520Q236,607 289.19,674.12Q342.38,741.23 427,761.23L427,847.23ZM533,848L533,762Q617.62,741.46 671.31,674.54Q725,607.62 725,520Q725,420 658,347.5Q591,275 492,275L480.85,275L522.85,317L462.69,378.15L316.54,232L462.69,85.85L522.85,147L480.85,189L492,189Q626.54,189 718.77,286.23Q811,383.46 811,520Q811,642.92 732.58,735.08Q654.15,827.23 533,848Z"/>
</vector>

View file

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="#FFFFFF">
<path
android:fillColor="#FFFFFF"
android:pathData="M449.04,813.96Q335.5,803.61 259.14,719.13Q182.77,634.65 182.77,520.31Q182.77,451.58 214,390.62Q245.23,329.66 300.69,288.69L332.19,319.89Q281.54,353.27 254.08,406.64Q226.62,460 226.62,520.31Q226.62,617.42 289.87,687.94Q353.12,758.46 449.04,770.11L449.04,813.96ZM512.88,814.73L512.88,770.88Q608.54,757.58 671.52,687.21Q734.5,616.85 734.5,520.31Q734.5,414.23 660.85,340.33Q587.19,266.42 480.62,266.42L453.46,266.42L519.85,332.81L488.65,364.69L368.81,244.85L488.65,125L519.85,156.19L453.46,222.58L480.62,222.58Q604.92,222.58 691.63,309.79Q778.34,397 778.34,520.31Q778.34,634.96 702.04,719.1Q625.73,803.23 512.88,814.73Z"/>
</vector>

View file

@ -71,8 +71,8 @@
<string name="morphe_spoof_video_streams_disable_player_js_update_user_dialog_message">تكون تجزئات مشغل JavaScript صالحة لمدة شهر تقريبًا، وقد يتم إلغاء التجزئات القديمة بواسطة GVS.</string>
<string name="morphe_spoof_video_streams_no_clients_suggest_vr_toast">جرب تمكين: تضمين تدفقات الفيديو &gt; تسجيل الدخول إلى Android VR</string>
<string name="morphe_spoof_video_streams_no_clients_toast">تعذر جلب أي تدفقات عميل</string>
<string name="morphe_spoof_video_streams_player_js_hash_summary">أدخل تجزئة مشغل JavaScript صالحة</string>
<string name="morphe_spoof_video_streams_player_js_hash_title">تجزئة مشغل JavaScript</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_summary">أدخل تجزئة مشغل JavaScript صالحة</string>
<string name="morphe_spoof_video_streams_player_js_hash_value_title">تجزئة مشغل JavaScript</string>
<string name="morphe_spoof_video_streams_player_js_variant_title">مشغل JavaScript المتغير</string>
<string name="morphe_spoof_video_streams_screen_summary">تزييف بثوث الفيديو الخاصة بالعميل لمنع مشكلات التشغيل</string>
<string name="morphe_spoof_video_streams_screen_title">Spoof Video Streams</string>
@ -2486,47 +2486,6 @@
<string name="revanced_spoof_streaming_data_android_vr_enable_av1_codec_user_dialog_message">"قد يؤدي تمكين هذا الإعداد إلى استخدام برنامج فك التشفير AV1.
قد يتقطع التشغيل أو تنخفض الإطارات."</string>
<string name="revanced_spoof_streaming_data_default_client_title">العميل الافتراضي</string>
<string name="revanced_spoof_streaming_data_prioritize_video_quality_summary_off">يتم إعطاء الأولوية لترميزات الفيديو.</string>
<string name="revanced_spoof_streaming_data_prioritize_video_quality_summary_on">يتم إعطاء الأولوية لجودة الفيديو.</string>
<string name="revanced_spoof_streaming_data_prioritize_video_quality_title">إعطاء الأولوية لجودة الفيديو</string>
<string name="revanced_spoof_streaming_data_prioritize_video_quality_user_dialog_message">"تحتوي بعض الفيديوهات على جودة الفيديو وبرامج الترميز التالية:
• 1080p AVC
• 720p AVC
• 360p VP9
إذا كان الجهاز يدعم VP9، فسيتم تجاهل '1080p AVC' و'720p AVC'.
بمعنى آخر، يُستخدم '360p VP9' كأعلى جودة فيديو، ولا يظهر '1080p AVC' و'720p AVC' في قائمة جودة الفيديو المنبثقة.
هذا هو السلوك المقصود لتطبيق YouTube على نظام Android، ولهذا السبب لم تكن قائمة جودة الفيديو المنبثقة متاحة لبعض مقاطع الفيديو.
على الرغم من أن VP9 برنامج ترميز أكثر تقدمًا من AVC، إلا أن بعض المستخدمين قد يفضلون '1080p AVC' على 360p' VP9'.
عند تفعيل هذا الإعداد، يتم استخدام أعلى جودة فيديو متاحة من استجابة الخادم، بغض النظر عن برنامج ترميز الفيديو.
المثال 1:
• 1080p AVC
• 720p AVC
• 360p VP9
← تم استخدام 1080p AVC.
المثال 2:
• 1080p AVC
• 1080p VP9
• 720p VP9
← تم استخدام 1080p VP9."</string>
<string name="revanced_spoof_streaming_data_reload_video_button_always_show_summary_off">يتم عرض زر إعادة تحميل الفيديو عندما يظهر الدوران.</string>
<string name="revanced_spoof_streaming_data_reload_video_button_always_show_summary_on">يتم عرض زر إعادة تحميل الفيديو دائما.</string>
<string name="revanced_spoof_streaming_data_reload_video_button_always_show_title">عرض زر إعادة تحميل الفيديو دائمًا</string>
<string name="revanced_spoof_streaming_data_reload_video_button_summary_off">تم إخفاء زر إعادة تحميل الفيديو.</string>
<string name="revanced_spoof_streaming_data_reload_video_button_summary_on">يتم عرض زر إعادة تحميل الفيديو.</string>
<string name="revanced_spoof_streaming_data_reload_video_button_title">عرض زر إعادة تحميل الفيديو</string>
<string name="revanced_spoof_streaming_data_side_effects_android">"• قائمة المقطع الصوتي مفقودة.
• لا يتوفر تعطيل المقطع الصوتي التلقائي المفروض.
• مستوى الصوت الثابت غير متاح.
@ -2605,24 +2564,12 @@
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed">فشل في إصدار رمز المصادقة.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_failed_suggestion">انقر فوق إعادة تعيين ثم حاول مرة أخرى.</string>
<string name="revanced_spoof_streaming_data_sign_in_android_vr_toast_reset">إعادة تعيين رمز المصادقة و رمز التفعيل.</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_off">تم إخفاء نوع العميل في إحصاءات تقنية</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_summary_on">يتم عرض نوع العميل في إحصاءات تقنية</string>
<string name="revanced_spoof_streaming_data_stats_for_nerds_title">عرض في إحصاءات تقنية</string>
<string name="revanced_spoof_streaming_data_summary_off">"لا يتم تزييف بيانات البث. قد لا يعمل تشغيل الفيديو."</string>
<string name="revanced_spoof_streaming_data_summary_on">"يتم تزييف بيانات البث.
إذا كنت أحد مستخدمي YouTube Premium، فقد لا يكون هذا الإعداد ضروريًا."</string>
<string name="revanced_spoof_streaming_data_title">Spoof Streaming Data</string>
<string name="revanced_spoof_streaming_data_type_entry_android_creator">Android Creator</string>
<string name="revanced_spoof_streaming_data_use_js_all_summary_off">"لا يُستخدم عميل JavaScript لبعض الفيديوهات التي تتطلب تشغيلًا سريعًا:
• التشغيل التلقائي في الموجز
• المقاطع
• فيديوهات Shorts"</string>
<string name="revanced_spoof_streaming_data_use_js_all_summary_on">"يتم استخدام عميل JavaScript بغض النظر عن نوع الفيديو.
• لا يزال عميل JavaScript غير قادر على تشغيل بعض الفيديوهات."</string>
<string name="revanced_spoof_streaming_data_use_js_all_title">استخدام عميل JavaScript لجميع الفيديوهات</string>
<string name="revanced_spoof_streaming_data_use_js_bypass_fake_buffering_dialog_message">"أحد أسباب تأخر بدء تشغيل الفيديوهات في عميل JavaScript هو إجراءات YouTube الجديدة لمكافحة حظر الإعلانات.
عند حظر إعلان فيديو، يُنشئ GVS (خادم فيديو Google) تخزينًا مؤقتًا زائفًا يتناسب مع مدة الإعلان (اختبار A/B).
@ -2632,12 +2579,6 @@
انقر على 'فتح' لقراءة المقال."</string>
<string name="revanced_spoof_streaming_data_use_js_bypass_fake_buffering_dialog_open_text">فتح</string>
<string name="revanced_spoof_streaming_data_use_js_bypass_fake_buffering_dialog_title">"إجراءات جديدة لمكافحة حظر الإعلانات على YouTube"</string>
<string name="revanced_spoof_streaming_data_use_js_bypass_fake_buffering_summary_off">تم تعطيل تجاوز التخزين المؤقت الزائف.</string>
<string name="revanced_spoof_streaming_data_use_js_bypass_fake_buffering_summary_on">"تم تمكين تجاوز التخزين المؤقت الزائف.
القيود:
• بسبب تغييرات من جانب الخادم، قد يتوقف هذا عن العمل في أي وقت."</string>
<string name="revanced_spoof_streaming_data_use_js_bypass_fake_buffering_title">تجاوز التخزين المؤقت الزائف</string>
<string name="revanced_spoof_streaming_data_use_js_summary_off">لم يتم إضافة عميل JavaScript إلى العملاء المتاحين.</string>
<string name="revanced_spoof_streaming_data_use_js_summary_on">تم إضافة عميل JavaScript إلى العملاء المتاحين.</string>
<string name="revanced_spoof_streaming_data_use_js_title">استخدام عميل JavaScript</string>
@ -2834,10 +2775,6 @@
<string name="revanced_watch_history_type_title">نوع سجل المشاهدة</string>
<string name="revanced_watch_next_processing_delay_summary">مقدار التأخير بجزء الثانية في معالجة استجابة Watch Next.</string>
<string name="revanced_watch_next_processing_delay_title">تأخير معالجة Watch Next</string>
<string name="revanced_webview_contextual_menu_get_cookies">الحصول على ملفات تعريف الارتباط</string>
<string name="revanced_webview_contextual_menu_get_data_sync_id">الحصول على معرف مزامنة البيانات</string>
<string name="revanced_webview_contextual_menu_get_visitor_data">الحصول على بيانات الزائر</string>
<string name="revanced_webview_contextual_menu_refresh">تحديث</string>
<string name="revanced_webview_cookies">ملفات تعريف الارتباط</string>
<string name="revanced_webview_copy">نسخ</string>
<string name="revanced_webview_data_sync_id">معرف مزامنة البيانات</string>

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