Compare commits

..

12 Commits

Author SHA1 Message Date
semantic-release-bot
dd400ac2a0 chore: Release v5.3.0-dev.6 [skip ci]
# [5.3.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.5...v5.3.0-dev.6) (2024-12-09)

### Features

* **YouTube - Spoof video streams:** Allow picking a default audio language track ([#4050](https://github.com/ReVanced/revanced-patches/issues/4050)) ([538ed6d](538ed6d876))
2024-12-09 01:14:21 +00:00
LisoUseInAIKyrios
538ed6d876 feat(YouTube - Spoof video streams): Allow picking a default audio language track (#4050) 2024-12-09 05:11:00 +04:00
semantic-release-bot
5ff94dc34a chore: Release v5.3.0-dev.5 [skip ci]
# [5.3.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.4...v5.3.0-dev.5) (2024-12-09)

### Bug Fixes

* **Change package name:** Prevent applying the patch to known incompatible apps ([#3943](https://github.com/ReVanced/revanced-patches/issues/3943)) ([b04a11a](b04a11a885))
* **YouTube Music - Permanent shuffle:** Remove obsolete and non functional patch ([#4073](https://github.com/ReVanced/revanced-patches/issues/4073)) ([4983e02](4983e021f9))

### Features

* **YouTube:** Add `Open videos fullscreen` patch ([#4069](https://github.com/ReVanced/revanced-patches/issues/4069)) ([bee917f](bee917f4ed))
2024-12-09 00:50:48 +00:00
LisoUseInAIKyrios
b04a11a885 fix(Change package name): Prevent applying the patch to known incompatible apps (#3943) 2024-12-09 04:46:47 +04:00
LisoUseInAIKyrios
4983e021f9 fix(YouTube Music - Permanent shuffle): Remove obsolete and non functional patch (#4073) 2024-12-09 04:44:12 +04:00
LisoUseInAIKyrios
bee917f4ed feat(YouTube): Add Open videos fullscreen patch (#4069)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2024-12-09 04:43:20 +04:00
semantic-release-bot
c94376bc4c chore: Release v5.3.0-dev.4 [skip ci]
# [5.3.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.3...v5.3.0-dev.4) (2024-12-09)

### Features

* **Nyx:** Remove broken `Unlock pro` patch ([87fe83a](87fe83aacf))
2024-12-09 00:36:16 +00:00
oSumAtrIX
87fe83aacf feat(Nyx): Remove broken Unlock pro patch 2024-12-09 01:32:41 +01:00
semantic-release-bot
92d282e963 chore: Release v5.3.0-dev.3 [skip ci]
# [5.3.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.2...v5.3.0-dev.3) (2024-12-09)

### Bug Fixes

* **YouTube - Spoof video streams:** Update `Force AVC` client data ([#4064](https://github.com/ReVanced/revanced-patches/issues/4064)) ([4a88f65](4a88f650c2))
2024-12-09 00:16:04 +00:00
LisoUseInAIKyrios
4a88f650c2 fix(YouTube - Spoof video streams): Update Force AVC client data (#4064) 2024-12-09 04:12:36 +04:00
semantic-release-bot
8b67716506 chore: Release v5.3.0-dev.2 [skip ci]
# [5.3.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.1...v5.3.0-dev.2) (2024-12-08)

### Bug Fixes

* **Reddit:** Fix patches by using correct extension class ([95d56b1](95d56b1529))
2024-12-08 22:01:18 +00:00
LisoUseInAIKyrios
95d56b1529 fix(Reddit): Fix patches by using correct extension class 2024-12-09 01:58:31 +04:00
28 changed files with 563 additions and 245 deletions

View File

@@ -1,3 +1,44 @@
# [5.3.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.5...v5.3.0-dev.6) (2024-12-09)
### Features
* **YouTube - Spoof video streams:** Allow picking a default audio language track ([#4050](https://github.com/ReVanced/revanced-patches/issues/4050)) ([ede666b](https://github.com/ReVanced/revanced-patches/commit/ede666b5cb64fcbaa1334ad8bef79e2634ced113))
# [5.3.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.4...v5.3.0-dev.5) (2024-12-09)
### Bug Fixes
* **Change package name:** Prevent applying the patch to known incompatible apps ([#3943](https://github.com/ReVanced/revanced-patches/issues/3943)) ([44936e7](https://github.com/ReVanced/revanced-patches/commit/44936e71e846f72f7279950232a5dba37765ceb3))
* **YouTube Music - Permanent shuffle:** Remove obsolete and non functional patch ([#4073](https://github.com/ReVanced/revanced-patches/issues/4073)) ([fbc6ab6](https://github.com/ReVanced/revanced-patches/commit/fbc6ab6a357b351f02d4d486ddc2072cf53199c3))
### Features
* **YouTube:** Add `Open videos fullscreen` patch ([#4069](https://github.com/ReVanced/revanced-patches/issues/4069)) ([296d63b](https://github.com/ReVanced/revanced-patches/commit/296d63bd42c338a01efbcb2df702e5822d05a5f1))
# [5.3.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.3...v5.3.0-dev.4) (2024-12-09)
### Features
* **Nyx:** Remove broken `Unlock pro` patch ([1fe8b16](https://github.com/ReVanced/revanced-patches/commit/1fe8b164eab0c4fa80ab2da2581977f5111a2858))
# [5.3.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.2...v5.3.0-dev.3) (2024-12-09)
### Bug Fixes
* **YouTube - Spoof video streams:** Update `Force AVC` client data ([#4064](https://github.com/ReVanced/revanced-patches/issues/4064)) ([7d537dd](https://github.com/ReVanced/revanced-patches/commit/7d537ddff4bb5421fa320741275131a66ef5c7bb))
# [5.3.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.1...v5.3.0-dev.2) (2024-12-08)
### Bug Fixes
* **Reddit:** Fix patches by using correct extension class ([70bdc68](https://github.com/ReVanced/revanced-patches/commit/70bdc6840d465399625aa1ae0259f49e72711955))
# [5.3.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.2.4-dev.3...v5.3.0-dev.1) (2024-12-08)

View File

@@ -1,12 +1,16 @@
package app.revanced.extension.patches;
package app.revanced.extension.reddit.patches;
import com.reddit.domain.model.ILink;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings("unused")
public final class FilterPromotedLinksPatch {
/**
* Injection point.
*
* Filters list from promoted links.
**/
public static List<?> filterChildren(final Iterable<?> links) {

View File

@@ -1,11 +1,12 @@
package app.revanced.extension.shared.settings;
import app.revanced.extension.shared.spoof.ClientType;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.settings.Setting.parent;
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.ForceiOSAVCAvailability;
import app.revanced.extension.shared.spoof.AudioStreamLanguage;
import app.revanced.extension.shared.spoof.ClientType;
/**
* Settings shared across multiple apps.
@@ -21,8 +22,9 @@ public class BaseSettings {
public static final IntegerSetting CHECK_ENVIRONMENT_WARNINGS_ISSUED = new IntegerSetting("revanced_check_environment_warnings_issued", 0, true, false);
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
public static final EnumSetting<AudioStreamLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AudioStreamLanguage.DEFAULT);
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofVideoStreamsPatch.ForceiOSAVCAvailability());
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new ForceiOSAVCAvailability());
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS));
}

View File

@@ -0,0 +1,102 @@
package app.revanced.extension.shared.spoof;
import java.util.Locale;
public enum AudioStreamLanguage {
DEFAULT,
// Language codes found in locale_config.xml
// Region specific variants of Chinese/English/Spanish/French have been removed.
AF,
AM,
AR,
AS,
AZ,
BE,
BG,
BN,
BS,
CA,
CS,
DA,
DE,
EL,
EN,
ES,
ET,
EU,
FA,
FI,
FR,
GL,
GU,
HI,
HE, // App uses obsolete 'IW' and 'HE' is modern ISO code.
HR,
HU,
HY,
ID,
IS,
IT,
JA,
KA,
KK,
KM,
KN,
KO,
KY,
LO,
LT,
LV,
MK,
ML,
MN,
MR,
MS,
MY,
NE,
NL,
NB,
OR,
PA,
PL,
PT_BR,
PT_PT,
RO,
RU,
SI,
SK,
SL,
SQ,
SR,
SV,
SW,
TA,
TE,
TH,
TL,
TR,
UK,
UR,
UZ,
VI,
ZH,
ZU;
private final String iso639_1;
AudioStreamLanguage() {
iso639_1 = name().replace('_', '-');
}
public String getIso639_1() {
// Changing the app language does not force the app to completely restart,
// so the default needs to be the current language and not a static field.
if (this == DEFAULT) {
// Android VR requires uppercase language code.
return Locale.getDefault().toLanguageTag().toUpperCase(Locale.US);
}
return iso639_1;
}
}

View File

@@ -1,12 +1,11 @@
package app.revanced.extension.shared.spoof;
import static app.revanced.extension.shared.spoof.DeviceHardwareSupport.allowAV1;
import static app.revanced.extension.shared.spoof.DeviceHardwareSupport.allowVP9;
import android.os.Build;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.settings.BaseSettings;
public enum ClientType {
// Specific purpose for age restricted, or private videos, because the iOS client is not logged in.
// https://dumps.tadiphone.dev/dumps/oculus/eureka
@@ -16,31 +15,35 @@ public enum ClientType {
"com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12; GB) gzip",
"32", // Android 12.1
"1.56.21",
"ANDROID_VR",
true
),
// Specific for kids videos.
IOS(5,
// iPhone 15 supports AV1 hardware decoding.
// Only use if this Android device also has hardware decoding.
allowAV1()
? "iPhone16,2" // 15 Pro Max
: "iPhone11,4", // XS Max
// iOS 14+ forces VP9.
allowVP9()
? "17.5.1.21F90"
: "13.7.17H35",
allowVP9()
? "com.google.ios.youtube/19.47.7 (iPhone; U; CPU iOS 17_5_1 like Mac OS X)"
: "com.google.ios.youtube/19.47.7 (iPhone; U; CPU iOS 13_7 like Mac OS X)",
forceAVC()
? "iPhone12,5" // 11 Pro Max (last device with iOS 13)
: "iPhone16,2", // 15 Pro Max
// iOS 13 and earlier uses only AVC. 14+ adds VP9 and AV1.
forceAVC()
? "13.7.17H35" // Last release of iOS 13.
: "17.5.1.21F90",
forceAVC()
? "com.google.ios.youtube/17.40.5 (iPhone; U; CPU iOS 13_7 like Mac OS X)"
: "com.google.ios.youtube/19.47.7 (iPhone; U; CPU iOS 17_5_1 like Mac OS X)",
null,
// Version number should be a valid iOS release.
// https://www.ipa4fun.com/history/185230
"19.47.7",
"IOS",
forceAVC()
// Some newer versions can also force AVC,
// but 17.40 is the last version that supports iOS 13.
? "17.40.5"
: "19.47.7",
false
);
private static boolean forceAVC() {
return BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
}
/**
* YouTube
* <a href="https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients">client type</a>
@@ -69,11 +72,6 @@ public enum ClientType {
@Nullable
public final String androidSdkVersion;
/**
* Client name.
*/
public final String clientName;
/**
* App version.
*/
@@ -90,7 +88,6 @@ public enum ClientType {
String userAgent,
@Nullable String androidSdkVersion,
String clientVersion,
String clientName,
boolean canLogin
) {
this.id = id;
@@ -99,7 +96,6 @@ public enum ClientType {
this.userAgent = userAgent;
this.androidSdkVersion = androidSdkVersion;
this.clientVersion = clientVersion;
this.clientName = clientName;
this.canLogin = canLogin;
}
}

View File

@@ -1,53 +0,0 @@
package app.revanced.extension.shared.spoof;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.os.Build;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings;
public class DeviceHardwareSupport {
public static final boolean DEVICE_HAS_HARDWARE_DECODING_VP9;
public static final boolean DEVICE_HAS_HARDWARE_DECODING_AV1;
static {
boolean vp9found = false;
boolean av1found = false;
MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
final boolean deviceIsAndroidTenOrLater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
for (MediaCodecInfo codecInfo : codecList.getCodecInfos()) {
final boolean isHardwareAccelerated = deviceIsAndroidTenOrLater
? codecInfo.isHardwareAccelerated()
: !codecInfo.getName().startsWith("OMX.google"); // Software decoder.
if (isHardwareAccelerated && !codecInfo.isEncoder()) {
for (String type : codecInfo.getSupportedTypes()) {
if (type.equalsIgnoreCase("video/x-vnd.on2.vp9")) {
vp9found = true;
} else if (type.equalsIgnoreCase("video/av01")) {
av1found = true;
}
}
}
}
DEVICE_HAS_HARDWARE_DECODING_VP9 = vp9found;
DEVICE_HAS_HARDWARE_DECODING_AV1 = av1found;
Logger.printDebug(() -> DEVICE_HAS_HARDWARE_DECODING_AV1
? "Device supports AV1 hardware decoding\n"
: "Device does not support AV1 hardware decoding\n"
+ (DEVICE_HAS_HARDWARE_DECODING_VP9
? "Device supports VP9 hardware decoding"
: "Device does not support VP9 hardware decoding"));
}
public static boolean allowVP9() {
return DEVICE_HAS_HARDWARE_DECODING_VP9 && !BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
}
public static boolean allowAV1() {
return allowVP9() && DEVICE_HAS_HARDWARE_DECODING_AV1;
}
}

View File

@@ -13,7 +13,6 @@ import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
import app.revanced.extension.shared.settings.BaseSettings;
@SuppressWarnings("unused")
public class SpoofVideoStreamsPatch {

View File

@@ -7,9 +7,9 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.spoof.ClientType;
final class PlayerRoutes {
@@ -25,9 +25,6 @@ final class PlayerRoutes {
*/
private static final int CONNECTION_TIMEOUT_MILLISECONDS = 10 * 1000; // 10 Seconds.
private static final String LOCALE_LANGUAGE = Utils.getContext().getResources()
.getConfiguration().locale.getLanguage();
private PlayerRoutes() {
}
@@ -38,8 +35,7 @@ final class PlayerRoutes {
JSONObject context = new JSONObject();
JSONObject client = new JSONObject();
// Required to use correct default audio channel with iOS.
client.put("hl", LOCALE_LANGUAGE);
client.put("hl", BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get().getIso639_1());
client.put("clientName", clientType.name());
client.put("clientVersion", clientType.clientVersion);
client.put("deviceModel", clientType.deviceModel);

View File

@@ -0,0 +1,14 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class OpenVideosFullscreen {
/**
* Injection point.
*/
public static boolean openVideoFullscreenPortrait(boolean original) {
return Settings.OPEN_VIDEOS_FULLSCREEN_PORTRAIT.get();
}
}

View File

@@ -55,7 +55,7 @@ public class ThemePatch {
/**
* Injection point.
*/
public static boolean gradientLoadingScreenEnabled() {
public static boolean gradientLoadingScreenEnabled(boolean original) {
return GRADIENT_LOADING_SCREEN_ENABLED;
}
}

View File

@@ -140,6 +140,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE);
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true);
public static final BooleanSetting OPEN_VIDEOS_FULLSCREEN_PORTRAIT = new BooleanSetting("revanced_open_videos_fullscreen_portrait", FALSE);
// Miniplayer
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.ORIGINAL, true);
private static final Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4);

View File

@@ -1,64 +0,0 @@
package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.spoof.DeviceHardwareSupport.DEVICE_HAS_HARDWARE_DECODING_VP9;
import android.content.Context;
import android.preference.SwitchPreference;
import android.util.AttributeSet;
@SuppressWarnings({"unused", "deprecation"})
public class ForceAVCSpoofingPreference extends SwitchPreference {
{
if (!DEVICE_HAS_HARDWARE_DECODING_VP9) {
setSummaryOn(str("revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on"));
}
}
public ForceAVCSpoofingPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ForceAVCSpoofingPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ForceAVCSpoofingPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ForceAVCSpoofingPreference(Context context) {
super(context);
}
private void updateUI() {
if (DEVICE_HAS_HARDWARE_DECODING_VP9) {
return;
}
// Temporarily remove the preference key to allow changing this preference without
// causing the settings UI listeners from showing reboot dialogs by the changes made here.
String key = getKey();
setKey(null);
// This setting cannot be changed by the user.
super.setEnabled(false);
super.setChecked(true);
setKey(key);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
updateUI();
}
@Override
public void setChecked(boolean checked) {
super.setChecked(checked);
updateUI();
}
}

View File

@@ -10,6 +10,7 @@ import android.os.Build;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceScreen;
import android.util.Pair;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.view.WindowInsets;
@@ -18,6 +19,10 @@ import android.widget.Toolbar;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment;
@@ -41,6 +46,46 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
return Utils.getContext().getResources().getDrawable(backButtonResource);
}
/**
* Sorts a preference list by menu entries, but preserves the first value as the first entry.
*/
private static void sortListPreferenceByValues(ListPreference listPreference) {
CharSequence[] entries = listPreference.getEntries();
CharSequence[] entryValues = listPreference.getEntryValues();
final int entrySize = entries.length;
if (entrySize != entryValues.length) {
throw new IllegalStateException();
}
// Ensure the first entry remains the first after sorting.
CharSequence firstEntry = entries[0];
CharSequence firstEntryValue = entryValues[0];
List<Pair<String, String>> entryPairs = new ArrayList<>(entrySize);
for (int i = 1; i < entrySize; i++) {
entryPairs.add(new Pair<>(entries[i].toString(), entryValues[i].toString()));
}
Collections.sort(entryPairs, (pair1, pair2) -> pair1.first.compareToIgnoreCase(pair2.first));
CharSequence[] sortedEntries = new CharSequence[entrySize];
CharSequence[] sortedEntryValues = new CharSequence[entrySize];
sortedEntries[0] = firstEntry;
sortedEntryValues[0] = firstEntryValue;
int i = 1;
for (Pair<String, String> pair : entryPairs) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
i++;
}
listPreference.setEntries(sortedEntries);
listPreference.setEntryValues(sortedEntryValues);
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
protected void initialize() {
@@ -50,9 +95,14 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
setPreferenceScreenToolbar(getPreferenceScreen());
// If the preference was included, then initialize it based on the available playback speed.
Preference defaultSpeedPreference = findPreference(Settings.PLAYBACK_SPEED_DEFAULT.key);
if (defaultSpeedPreference instanceof ListPreference) {
CustomPlaybackSpeedPatch.initializeListPreference((ListPreference) defaultSpeedPreference);
Preference preference = findPreference(Settings.PLAYBACK_SPEED_DEFAULT.key);
if (preference instanceof ListPreference playbackPreference) {
CustomPlaybackSpeedPatch.initializeListPreference(playbackPreference);
}
preference = findPreference(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE.key);
if (preference instanceof ListPreference languagePreference) {
sortListPreferenceByValues(languagePreference);
}
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);

View File

@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true
android.useAndroidX = true
kotlin.code.style = official
version = 5.3.0-dev.1
version = 5.3.0-dev.6

View File

@@ -1164,6 +1164,10 @@ public final class app/revanced/patches/youtube/layout/player/background/PlayerC
public static final fun getPlayerControlsBackgroundPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenKt {
public static final fun getOpenVideosFullscreenPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/layout/player/overlay/CustomPlayerOverlayOpacityPatchKt {
public static final fun getCustomPlayerOverlayOpacityPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}

View File

@@ -4,6 +4,7 @@ import app.revanced.patcher.patch.Option
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import org.w3c.dom.Element
import java.util.logging.Logger
lateinit var packageNameOption: Option<String>
@@ -41,18 +42,38 @@ val changePackageNamePatch = resourcePatch(
it == "Default" || it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$"))
}
/**
* Apps that are confirmed to not work correctly with this patch.
* This is not an exhaustive list, and is only the apps with
* ReVanced specific patches and are confirmed incompatible with this patch.
*/
val incompatibleAppPackages = setOf(
// Cannot login, settings menu is broken.
"com.reddit.frontpage",
// Patches and installs but crashes on launch.
"com.duolingo",
"com.twitter.android",
"tv.twitch.android.app",
)
finalize {
document("AndroidManifest.xml").use { document ->
val manifest = document.getElementsByTagName("manifest").item(0) as Element
val originalPackageName = manifest.getAttribute("package")
if (incompatibleAppPackages.contains(originalPackageName)) {
return@finalize Logger.getLogger(this::class.java.name).severe(
"'$originalPackageName' does not work correctly with \"Change package name\"")
}
val replacementPackageName = packageNameOption.value
val manifest = document.getElementsByTagName("manifest").item(0) as Element
manifest.setAttribute(
"package",
if (replacementPackageName != packageNameOption.default) {
replacementPackageName
} else {
"${manifest.getAttribute("package")}.revanced"
"${originalPackageName}.revanced"
},
)
}

View File

@@ -3,9 +3,9 @@ package app.revanced.patches.music.interaction.permanentshuffle
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch
@Deprecated("This patch no longer works and will be removed in the future.")
@Suppress("unused")
val permanentShufflePatch = bytecodePatch(
name = "Permanent shuffle",
description = "Permanently remember your shuffle preference " +
"even if the playlist ends or another track is played.",
use = false,

View File

@@ -3,10 +3,9 @@ package app.revanced.patches.nyx.misc.pro
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
@Deprecated("This patch will be removed in the future.")
@Suppress("unused")
val unlockProPatch = bytecodePatch(
name = "Unlock pro",
) {
val unlockProPatch = bytecodePatch {
compatibleWith("com.awedea.nyx")
execute {

View File

@@ -240,14 +240,14 @@ val miniplayerPatch = bytecodePatch(
),
)
fun MutableMethod.insertBooleanOverride(index: Int, methodName: String) {
fun MutableMethod.insertMiniplayerBooleanOverride(index: Int, methodName: String) {
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index,
"""
invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->$methodName(Z)Z
move-result v$register
""",
invoke-static {v$register}, $EXTENSION_CLASS_DESCRIPTOR->$methodName(Z)Z
move-result v$register
"""
)
}
@@ -257,29 +257,25 @@ val miniplayerPatch = bytecodePatch(
* Adds an override to force legacy tablet miniplayer to be used or not used.
*/
fun MutableMethod.insertLegacyTabletMiniplayerOverride(index: Int) {
insertBooleanOverride(index, "getLegacyTabletMiniplayerOverride")
insertMiniplayerBooleanOverride(index, "getLegacyTabletMiniplayerOverride")
}
/**
* Adds an override to force modern miniplayer to be used or not used.
*/
fun MutableMethod.insertModernMiniplayerOverride(index: Int) {
insertBooleanOverride(index, "getModernMiniplayerOverride")
insertMiniplayerBooleanOverride(index, "getModernMiniplayerOverride")
}
fun Fingerprint.insertLiteralValueBooleanOverride(
fun Fingerprint.insertMiniplayerFeatureFlagBooleanOverride(
literal: Long,
extensionMethod: String,
) {
method.apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(literal)
val targetIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
) = method.insertFeatureFlagBooleanOverride(
literal,
"$EXTENSION_CLASS_DESCRIPTOR->$extensionMethod(Z)Z"
)
insertBooleanOverride(targetIndex + 1, extensionMethod)
}
}
fun Fingerprint.insertLiteralValueFloatOverride(
fun Fingerprint.insertMiniplayerFeatureFlagFloatOverride(
literal: Long,
extensionMethod: String,
) {
@@ -370,24 +366,24 @@ val miniplayerPatch = bytecodePatch(
}
if (is_19_23_or_greater) {
miniplayerModernConstructorFingerprint.insertLiteralValueBooleanOverride(
miniplayerModernConstructorFingerprint.insertMiniplayerFeatureFlagBooleanOverride(
MINIPLAYER_DRAG_DROP_FEATURE_KEY,
"enableMiniplayerDragAndDrop",
)
}
if (is_19_25_or_greater) {
miniplayerModernConstructorFingerprint.insertLiteralValueBooleanOverride(
miniplayerModernConstructorFingerprint.insertMiniplayerFeatureFlagBooleanOverride(
MINIPLAYER_MODERN_FEATURE_LEGACY_KEY,
"getModernMiniplayerOverride",
)
miniplayerModernConstructorFingerprint.insertLiteralValueBooleanOverride(
miniplayerModernConstructorFingerprint.insertMiniplayerFeatureFlagBooleanOverride(
MINIPLAYER_MODERN_FEATURE_KEY,
"getModernFeatureFlagsActiveOverride",
)
miniplayerModernConstructorFingerprint.insertLiteralValueBooleanOverride(
miniplayerModernConstructorFingerprint.insertMiniplayerFeatureFlagBooleanOverride(
MINIPLAYER_DOUBLE_TAP_FEATURE_KEY,
"enableMiniplayerDoubleTapAction",
)
@@ -426,19 +422,19 @@ val miniplayerPatch = bytecodePatch(
}
if (is_19_36_or_greater) {
miniplayerModernConstructorFingerprint.insertLiteralValueBooleanOverride(
miniplayerModernConstructorFingerprint.insertMiniplayerFeatureFlagBooleanOverride(
MINIPLAYER_ROUNDED_CORNERS_FEATURE_KEY,
"setRoundedCorners",
)
}
if (is_19_43_or_greater) {
miniplayerOnCloseHandlerFingerprint.insertLiteralValueBooleanOverride(
miniplayerOnCloseHandlerFingerprint.insertMiniplayerFeatureFlagBooleanOverride(
MINIPLAYER_DISABLED_FEATURE_KEY,
"getMiniplayerOnCloseHandler"
)
miniplayerModernConstructorFingerprint.insertLiteralValueBooleanOverride(
miniplayerModernConstructorFingerprint.insertMiniplayerFeatureFlagBooleanOverride(
MINIPLAYER_HORIZONTAL_DRAG_FEATURE_KEY,
"setHorizontalDrag",
)

View File

@@ -0,0 +1,16 @@
package app.revanced.patches.youtube.layout.player.fullscreen
import app.revanced.patcher.fingerprint
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
internal const val OPEN_VIDEOS_FULLSCREEN_PORTRAIT_FEATURE_FLAG = 45666112L
internal val openVideosFullscreenPortraitFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters("L", "Lj\$/util/Optional;")
literal {
OPEN_VIDEOS_FULLSCREEN_PORTRAIT_FEATURE_FLAG
}
}

View File

@@ -0,0 +1,46 @@
package app.revanced.patches.youtube.layout.player.fullscreen
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.insertFeatureFlagBooleanOverride
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/OpenVideosFullscreen;"
@Suppress("unused")
val openVideosFullscreenPatch = bytecodePatch(
name = "Open videos fullscreen",
description = "Adds an option to open videos in full screen portrait mode.",
) {
dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.youtube"(
"19.46.42",
)
)
execute {
openVideosFullscreenPortraitFingerprint.method.insertFeatureFlagBooleanOverride(
OPEN_VIDEOS_FULLSCREEN_PORTRAIT_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->openVideoFullscreenPortrait(Z)Z"
)
// Add resources and setting last, in case the user force patches an old incompatible version.
addResources("youtube", "layout.player.fullscreen.openVideosFullscreen")
PreferenceScreen.PLAYER.addPreferences(
SwitchPreference("revanced_open_videos_fullscreen_portrait")
)
}
}

View File

@@ -24,6 +24,7 @@ import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import app.revanced.util.inputStreamFromBundledResource
import app.revanced.util.insertFeatureFlagBooleanOverride
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
@@ -228,19 +229,10 @@ val seekbarColorPatch = bytecodePatch(
// 19.25+ changes
playerSeekbarGradientConfigFingerprint.method.apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG)
val resultIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
val register = getInstruction<OneRegisterInstruction>(resultIndex).registerA
addInstructions(
resultIndex + 1,
"""
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->playerSeekbarGradientEnabled(Z)Z
move-result v$register
"""
)
}
playerSeekbarGradientConfigFingerprint.method.insertFeatureFlagBooleanOverride(
PLAYER_SEEKBAR_GRADIENT_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->playerSeekbarGradientEnabled(Z)Z"
)
lithoLinearGradientFingerprint.method.addInstruction(
0,
@@ -255,19 +247,10 @@ val seekbarColorPatch = bytecodePatch(
launchScreenLayoutTypeFingerprint,
mainActivityOnCreateFingerprint
).forEach { fingerprint ->
fingerprint.method.apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(launchScreenLayoutTypeLotteFeatureFlag)
val resultIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
val register = getInstruction<OneRegisterInstruction>(resultIndex).registerA
addInstructions(
resultIndex + 1,
"""
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->useLotteLaunchSplashScreen(Z)Z
move-result v$register
"""
)
}
fingerprint.method.insertFeatureFlagBooleanOverride(
launchScreenLayoutTypeLotteFeatureFlag,
"$EXTENSION_CLASS_DESCRIPTOR->useLotteLaunchSplashScreen(Z)Z"
)
}
// Hook the splash animation drawable to set the a seekbar color theme.

View File

@@ -1,7 +1,6 @@
package app.revanced.patches.youtube.layout.theme
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
@@ -17,10 +16,7 @@ import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.forEachChildElement
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import app.revanced.util.insertFeatureFlagBooleanOverride
import org.w3c.dom.Element
private const val EXTENSION_CLASS_DESCRIPTOR =
@@ -212,19 +208,10 @@ val themePatch = bytecodePatch(
SwitchPreference("revanced_gradient_loading_screen"),
)
useGradientLoadingScreenFingerprint.method.apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(GRADIENT_LOADING_SCREEN_AB_CONSTANT)
val isEnabledIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
val isEnabledRegister = getInstruction<OneRegisterInstruction>(isEnabledIndex).registerA
addInstructions(
isEnabledIndex + 1,
"""
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->gradientLoadingScreenEnabled()Z
move-result v$isEnabledRegister
""",
)
}
useGradientLoadingScreenFingerprint.method.insertFeatureFlagBooleanOverride(
GRADIENT_LOADING_SCREEN_AB_CONSTANT,
"$EXTENSION_CLASS_DESCRIPTOR->gradientLoadingScreenEnabled(Z)Z"
)
mapOf(
themeHelperLightColorFingerprint to lightThemeBackgroundColor,

View File

@@ -34,10 +34,7 @@ internal val disableCairoSettingsPatch = bytecodePatch(
* <a href="https://github.com/qnblackcat/uYouPlus/issues/1468">uYouPlus#1468</a>.
*/
cairoFragmentConfigFingerprint.method.apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(
CAIRO_CONFIG_LITERAL_VALUE,
)
val literalIndex = indexOfFirstLiteralInstructionOrThrow(CAIRO_CONFIG_LITERAL_VALUE)
val resultIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
val register = getInstruction<OneRegisterInstruction>(resultIndex).registerA

View File

@@ -40,10 +40,7 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
"revanced_spoof_video_streams_client",
summaryKey = null,
),
SwitchPreference(
"revanced_spoof_video_streams_ios_force_avc",
tag = "app.revanced.extension.youtube.settings.preference.ForceAVCSpoofingPreference",
),
SwitchPreference("revanced_spoof_video_streams_ios_force_avc"),
NonInteractivePreference("revanced_spoof_video_streams_about_android_vr"),
NonInteractivePreference("revanced_spoof_video_streams_about_ios"),
),

View File

@@ -17,6 +17,7 @@ import app.revanced.patches.shared.misc.mapping.resourceMappings
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
import com.android.tools.smali.dexlib2.iface.reference.Reference
@@ -402,6 +403,20 @@ fun Method.findInstructionIndicesReversedOrThrow(opcode: Opcode): List<Int> {
return instructions
}
internal fun MutableMethod.insertFeatureFlagBooleanOverride(literal: Long, extensionsMethod: String) {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(literal)
val index = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstructions(
index + 1,
"""
invoke-static { v$register }, $extensionsMethod
move-result v$register
"""
)
}
/**
* Called for _all_ instructions with the given literal value.
*/

View File

@@ -11,6 +11,116 @@
<item>ANDROID_VR</item>
<item>IOS</item>
</string-array>
<string-array name="revanced_spoof_video_streams_language_entries">
<item>@string/revanced_spoof_video_streams_language_DEFAULT</item>
<item>@string/revanced_spoof_video_streams_language_AR</item>
<item>@string/revanced_spoof_video_streams_language_AZ</item>
<item>@string/revanced_spoof_video_streams_language_BG</item>
<item>@string/revanced_spoof_video_streams_language_BN</item>
<item>@string/revanced_spoof_video_streams_language_CA</item>
<item>@string/revanced_spoof_video_streams_language_CS</item>
<item>@string/revanced_spoof_video_streams_language_DA</item>
<item>@string/revanced_spoof_video_streams_language_DE</item>
<item>@string/revanced_spoof_video_streams_language_EL</item>
<item>@string/revanced_spoof_video_streams_language_EN</item>
<item>@string/revanced_spoof_video_streams_language_ES</item>
<item>@string/revanced_spoof_video_streams_language_ET</item>
<item>@string/revanced_spoof_video_streams_language_FA</item>
<item>@string/revanced_spoof_video_streams_language_FI</item>
<item>@string/revanced_spoof_video_streams_language_FR</item>
<item>@string/revanced_spoof_video_streams_language_GU</item>
<item>@string/revanced_spoof_video_streams_language_HI</item>
<item>@string/revanced_spoof_video_streams_language_HR</item>
<item>@string/revanced_spoof_video_streams_language_HU</item>
<item>@string/revanced_spoof_video_streams_language_ID</item>
<item>@string/revanced_spoof_video_streams_language_IT</item>
<item>@string/revanced_spoof_video_streams_language_JA</item>
<item>@string/revanced_spoof_video_streams_language_KK</item>
<item>@string/revanced_spoof_video_streams_language_KO</item>
<item>@string/revanced_spoof_video_streams_language_LT</item>
<item>@string/revanced_spoof_video_streams_language_LV</item>
<item>@string/revanced_spoof_video_streams_language_MK</item>
<item>@string/revanced_spoof_video_streams_language_MN</item>
<item>@string/revanced_spoof_video_streams_language_MR</item>
<item>@string/revanced_spoof_video_streams_language_MS</item>
<item>@string/revanced_spoof_video_streams_language_MY</item>
<item>@string/revanced_spoof_video_streams_language_NL</item>
<item>@string/revanced_spoof_video_streams_language_OR</item>
<item>@string/revanced_spoof_video_streams_language_PA</item>
<item>@string/revanced_spoof_video_streams_language_PL</item>
<item>@string/revanced_spoof_video_streams_language_PT_BR</item>
<item>@string/revanced_spoof_video_streams_language_PT_PT</item>
<item>@string/revanced_spoof_video_streams_language_RO</item>
<item>@string/revanced_spoof_video_streams_language_RU</item>
<item>@string/revanced_spoof_video_streams_language_SK</item>
<item>@string/revanced_spoof_video_streams_language_SL</item>
<item>@string/revanced_spoof_video_streams_language_SR</item>
<item>@string/revanced_spoof_video_streams_language_SV</item>
<item>@string/revanced_spoof_video_streams_language_SW</item>
<item>@string/revanced_spoof_video_streams_language_TA</item>
<item>@string/revanced_spoof_video_streams_language_TE</item>
<item>@string/revanced_spoof_video_streams_language_TH</item>
<item>@string/revanced_spoof_video_streams_language_TR</item>
<item>@string/revanced_spoof_video_streams_language_UK</item>
<item>@string/revanced_spoof_video_streams_language_UR</item>
<item>@string/revanced_spoof_video_streams_language_VI</item>
<item>@string/revanced_spoof_video_streams_language_ZH</item>
</string-array>
<string-array name="revanced_spoof_video_streams_language_entry_values">
<item>DEFAULT</item>
<item>AR</item>
<item>AZ</item>
<item>BG</item>
<item>BN</item>
<item>CA</item>
<item>CS</item>
<item>DA</item>
<item>DE</item>
<item>EL</item>
<item>EN</item>
<item>ES</item>
<item>ET</item>
<item>FA</item>
<item>FI</item>
<item>FR</item>
<item>GU</item>
<item>HI</item>
<item>HR</item>
<item>HU</item>
<item>ID</item>
<item>IT</item>
<item>JA</item>
<item>KK</item>
<item>KO</item>
<item>LT</item>
<item>LV</item>
<item>MK</item>
<item>MN</item>
<item>MR</item>
<item>MS</item>
<item>MY</item>
<item>NL</item>
<item>OR</item>
<item>PA</item>
<item>PL</item>
<item>PT_BR</item>
<item>PT_PT</item>
<item>RO</item>
<item>RU</item>
<item>SK</item>
<item>SL</item>
<item>SR</item>
<item>SV</item>
<item>SW</item>
<item>TA</item>
<item>TE</item>
<item>TH</item>
<item>TR</item>
<item>UK</item>
<item>UR</item>
<item>VI</item>
<item>ZH</item>
</string-array>
</patch>
<patch id="layout.spoofappversion.spoofAppVersionPatch">
<string-array name="revanced_spoof_app_version_target_entries">

View File

@@ -715,6 +715,11 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_hide_player_popup_panels_summary_on">Player popup panels are hidden</string>
<string name="revanced_hide_player_popup_panels_summary_off">Player popup panels are shown</string>
</patch>
<patch id="layout.player.fullscreen.openVideosFullscreen">
<string name="revanced_open_videos_fullscreen_portrait_title">Open videos in fullscreen portrait</string>
<string name="revanced_open_videos_fullscreen_portrait_summary_on">Videos open fullscreen</string>
<string name="revanced_open_videos_fullscreen_portrait_summary_off">Videos do not open fullscreen</string>
</patch>
<patch id="layout.player.overlay.customPlayerOverlayOpacityResourcePatch">
<string name="revanced_player_overlay_opacity_title">Player overlay opacity</string>
<string name="revanced_player_overlay_opacity_summary">Opacity value between 0-100, where 0 is transparent</string>
@@ -1220,14 +1225,68 @@ This is because Crowdin requires temporarily flattening this file and removing t
<string name="revanced_spoof_video_streams_user_dialog_message">Turning off this setting may cause video playback issues.</string>
<string name="revanced_spoof_video_streams_client_title">Default client</string>
<string name="revanced_spoof_video_streams_ios_force_avc_title">Force AVC (H.264)</string>
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">Video codec is AVC (H.264)</string>
<string name="revanced_spoof_video_streams_ios_force_avc_summary_off">Video codec is VP9 or AV1</string>
<string name="revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on">Your device does not have VP9 hardware decoding, and this setting is always on when Client spoofing is enabled</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Enabling this might improve battery life and fix playback stuttering.\n\nAVC has a maximum resolution of 1080p, and video playback will use more internet data than VP9 or AV1.</string>
<string name="revanced_spoof_video_streams_ios_force_avc_summary_on">Video codec is forced to AVC (H.264)</string>
<string name="revanced_spoof_video_streams_ios_force_avc_summary_off">Video codec is determined automatically</string>
<string name="revanced_spoof_video_streams_ios_force_avc_user_dialog_message">Enabling this might improve battery life and fix playback stuttering.\n\nAVC has a maximum resolution of 1080p, Opus audio codec is not available, and video playback will use more internet data than VP9 or AV1.</string>
<string name="revanced_spoof_video_streams_about_ios_title">iOS spoofing side effects</string>
<string name="revanced_spoof_video_streams_about_ios_summary">• Private kids videos may not play\n• Livestreams start from the beginning\n• Videos may end 1 second early</string>
<string name="revanced_spoof_video_streams_about_android_vr_title">Android VR spoofing side effects</string>
<string name="revanced_spoof_video_streams_about_android_vr_summary">• Kids videos may not play\n• Audio track menu is missing\n• Stable volume is not available</string>
<string name="revanced_spoof_video_streams_language_">Video streams are spoofed</string>
<string name="revanced_spoof_video_streams_language_title">Preferred audio stream language</string>
<string name="revanced_spoof_video_streams_language_DEFAULT">App language</string>
<string name="revanced_spoof_video_streams_language_AR">Arabic</string>
<string name="revanced_spoof_video_streams_language_AZ">Azerbaijani</string>
<string name="revanced_spoof_video_streams_language_BG">Bulgarian</string>
<string name="revanced_spoof_video_streams_language_BN">Bengali</string>
<string name="revanced_spoof_video_streams_language_CA">Catalan</string>
<string name="revanced_spoof_video_streams_language_CS">Czech</string>
<string name="revanced_spoof_video_streams_language_DA">Danish</string>
<string name="revanced_spoof_video_streams_language_DE">German</string>
<string name="revanced_spoof_video_streams_language_EL">Greek</string>
<string name="revanced_spoof_video_streams_language_EN">English</string>
<string name="revanced_spoof_video_streams_language_ES">Spanish</string>
<string name="revanced_spoof_video_streams_language_ET">Estonian</string>
<string name="revanced_spoof_video_streams_language_FA">Persian</string>
<string name="revanced_spoof_video_streams_language_FI">Finnish</string>
<string name="revanced_spoof_video_streams_language_FR">French</string>
<string name="revanced_spoof_video_streams_language_GU">Gujarati</string>
<string name="revanced_spoof_video_streams_language_HI">Hindi</string>
<string name="revanced_spoof_video_streams_language_HR">Croatian</string>
<string name="revanced_spoof_video_streams_language_HU">Hungarian</string>
<string name="revanced_spoof_video_streams_language_ID">Indonesian</string>
<string name="revanced_spoof_video_streams_language_IT">Italian</string>
<string name="revanced_spoof_video_streams_language_JA">Japanese</string>
<string name="revanced_spoof_video_streams_language_KK">Kazakh</string>
<string name="revanced_spoof_video_streams_language_KO">Korean</string>
<string name="revanced_spoof_video_streams_language_LT">Lithuanian</string>
<string name="revanced_spoof_video_streams_language_LV">Latvian</string>
<string name="revanced_spoof_video_streams_language_MK">Macedonian</string>
<string name="revanced_spoof_video_streams_language_MN">Mongolian</string>
<string name="revanced_spoof_video_streams_language_MR">Marathi</string>
<string name="revanced_spoof_video_streams_language_MS">Malay</string>
<string name="revanced_spoof_video_streams_language_MY">Burmese</string>
<string name="revanced_spoof_video_streams_language_NL">Dutch</string>
<string name="revanced_spoof_video_streams_language_OR">Odia</string>
<string name="revanced_spoof_video_streams_language_PA">Punjabi</string>
<string name="revanced_spoof_video_streams_language_PL">Polish</string>
<string name="revanced_spoof_video_streams_language_PT_BR">Portuguese (Brazil)</string>
<string name="revanced_spoof_video_streams_language_PT_PT">Portuguese (Portugal)</string>
<string name="revanced_spoof_video_streams_language_RO">Romanian</string>
<string name="revanced_spoof_video_streams_language_RU">Russian</string>
<string name="revanced_spoof_video_streams_language_SK">Slovak</string>
<string name="revanced_spoof_video_streams_language_SL">Slovene</string>
<string name="revanced_spoof_video_streams_language_SR">Serbian</string>
<string name="revanced_spoof_video_streams_language_SV">Swedish</string>
<string name="revanced_spoof_video_streams_language_SW">Swahili</string>
<string name="revanced_spoof_video_streams_language_TA">Tamil</string>
<string name="revanced_spoof_video_streams_language_TE">Telugu</string>
<string name="revanced_spoof_video_streams_language_TH">Thai</string>
<string name="revanced_spoof_video_streams_language_TR">Turkish</string>
<string name="revanced_spoof_video_streams_language_UK">Ukrainian</string>
<string name="revanced_spoof_video_streams_language_UR">Urdu</string>
<string name="revanced_spoof_video_streams_language_VI">Vietnamese</string>
<string name="revanced_spoof_video_streams_language_ZH">Chinese</string>
</patch>
</app>
<app id="twitch">