Skip to content

Commit

Permalink
feat(YouTube): Add Force original audio patch (#4122)
Browse files Browse the repository at this point in the history
  • Loading branch information
LisoUseInAIKyrios authored Dec 15, 2024
1 parent 1e18634 commit f4aa440
Show file tree
Hide file tree
Showing 14 changed files with 297 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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 static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.SpoofiOSAvailability;

import app.revanced.extension.shared.spoof.AudioStreamLanguage;
import app.revanced.extension.shared.spoof.ClientType;
Expand All @@ -22,9 +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, parent(SPOOF_VIDEO_STREAMS));
public static final EnumSetting<AudioStreamLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AudioStreamLanguage.DEFAULT, new SpoofiOSAvailability());
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 ForceiOSAVCAvailability());
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
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));

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
import java.util.Locale;

public enum AudioStreamLanguage {
/**
* YouTube default.
* Can be the original language or can be app language,
* depending on what YouTube decides to pick as the default.
*/
DEFAULT,

// Language codes found in locale_config.xml
Expand Down Expand Up @@ -86,15 +91,21 @@ public enum AudioStreamLanguage {
private final String iso639_1;

AudioStreamLanguage() {
iso639_1 = name().replace('_', '-');
String name = name();
final int regionSeparatorIndex = name.indexOf('_');
if (regionSeparatorIndex >= 0) {
iso639_1 = name.substring(0, regionSeparatorIndex).toLowerCase(Locale.US)
+ name.substring(regionSeparatorIndex);
} else {
iso639_1 = name().toLowerCase(Locale.US);
}
}

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 Locale.getDefault().toLanguageTag();
}

return iso639_1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public enum ClientType {
"32", // Android 12.1
"1.56.21",
true,
true),
false),
// Specific for kids videos.
IOS(5,
"IOS",
Expand All @@ -40,21 +40,8 @@ public enum ClientType {
? "17.40.5"
: "19.47.7",
false,
true),
/**
* Android VR with no language code.
* Used for age restricted videos and YouTube Music to disable stable volume.
*/
ANDROID_VR_NO_HL(
ANDROID_VR.id,
ANDROID_VR.clientName,
ANDROID_VR.deviceModel,
ANDROID_VR.osVersion,
ANDROID_VR.userAgent,
ANDROID_VR.androidSdkVersion,
ANDROID_VR.clientVersion,
ANDROID_VR.canLogin,
false);
true
);

private static boolean forceAVC() {
return BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,6 @@ public class SpoofVideoStreamsPatch {
private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0";
private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING);

/**
* Injection point. Used by YT Music to disable stable volume.
*/
public static void setClientTypeToAndroidVrNoHl() {
Logger.printDebug(() -> "Setting stream spoofing to: " + ClientType.ANDROID_VR_NO_HL);
BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.save(ClientType.ANDROID_VR_NO_HL);
}

/**
* Injection point.
* Blocks /get_watch requests by returning an unreachable URI.
Expand Down Expand Up @@ -173,10 +165,11 @@ public static byte[] removeVideoPlaybackPostBody(Uri uri, int method, byte[] pos
return postData;
}

public static final class ForceiOSAVCAvailability implements Setting.Availability {
public static final class SpoofiOSAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
return BaseSettings.SPOOF_VIDEO_STREAMS.get() && BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS;
return BaseSettings.SPOOF_VIDEO_STREAMS.get()
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ final class PlayerRoutes {
"?fields=streamingData" +
"&alt=proto"
).compile();

private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";

/**
* TCP connection and HTTP read timeout
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package app.revanced.extension.youtube.patches;

import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.settings.Settings;

@SuppressWarnings("unused")
public class ForceOriginalAudioPatch {

private static final String DEFAULT_AUDIO_TRACKS_IDENTIFIER = "original";

/**
* Injection point.
*/
public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackId, String audioTrackDisplayName) {
try {
if (!Settings.FORCE_ORIGINAL_AUDIO.get()) {
return isDefault;
}

if (audioTrackDisplayName.isEmpty()) {
// Older app targets can have empty audio tracks and these might be placeholders.
// The real audio tracks are called after these.
return isDefault;
}

Logger.printDebug(() -> "default: " + String.format("%-5s", isDefault) + " id: "
+ String.format("%-8s", audioTrackId) + " name:" + audioTrackDisplayName);

final boolean isOriginal = audioTrackDisplayName.contains(DEFAULT_AUDIO_TRACKS_IDENTIFIER);
if (isOriginal) {
Logger.printDebug(() -> "Using audio: " + audioTrackId);
}

return isOriginal;
} catch (Exception ex) {
Logger.printException(() -> "isDefaultAudioStream failure", ex);
}

return isDefault;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public class Settings extends BaseSettings {
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true);
// Audio
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE);

// Ads
public static final BooleanSetting HIDE_BUTTONED_ADS = new BooleanSetting("revanced_hide_buttoned_ads", TRUE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,35 +48,44 @@ public static Drawable getBackButtonDrawable() {

/**
* Sorts a preference list by menu entries, but preserves the first value as the first entry.
*
* @noinspection SameParameterValue
*/
private static void sortListPreferenceByValues(ListPreference listPreference) {
private static void sortListPreferenceByValues(ListPreference listPreference, int firstEntriesToPreserve) {
CharSequence[] entries = listPreference.getEntries();
CharSequence[] entryValues = listPreference.getEntryValues();
final int entrySize = entries.length;

if (entrySize != entryValues.length) {
// Xml array declaration has a missing/extra entry.
throw new IllegalStateException();
}

// Ensure the first entry remains the first after sorting.
CharSequence firstEntry = entries[0];
CharSequence firstEntryValue = entryValues[0];
List<Pair<String, String>> firstPairs = new ArrayList<>(firstEntriesToPreserve);
List<Pair<String, String>> pairsToSort = new ArrayList<>(entrySize);

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()));
for (int i = 0; i < entrySize; i++) {
Pair<String, String> pair = new Pair<>(entries[i].toString(), entryValues[i].toString());
if (i < firstEntriesToPreserve) {
firstPairs.add(pair);
} else {
pairsToSort.add(pair);
}
}

Collections.sort(entryPairs, (pair1, pair2) -> pair1.first.compareToIgnoreCase(pair2.first));
Collections.sort(pairsToSort, (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 = 0;
for (Pair<String, String> pair : firstPairs) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
i++;
}

int i = 1;
for (Pair<String, String> pair : entryPairs) {
for (Pair<String, String> pair : pairsToSort) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
i++;
Expand All @@ -102,7 +111,7 @@ protected void initialize() {

preference = findPreference(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE.key);
if (preference instanceof ListPreference languagePreference) {
sortListPreferenceByValues(languagePreference);
sortListPreferenceByValues(languagePreference, 1);
}
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
Expand Down
4 changes: 4 additions & 0 deletions patches/api/patches.api
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,10 @@ public final class app/revanced/patches/youtube/shared/FingerprintsKt {
public static final fun getRollingNumberTextViewAnimationUpdateFingerprint ()Lapp/revanced/patcher/Fingerprint;
}

public final class app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatchKt {
public static final fun getForceOriginalAudioPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}

public final class app/revanced/patches/youtube/video/information/VideoInformationPatchKt {
public static final fun getVideoInformationPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun userSelectedPlaybackSpeedHook (Ljava/lang/String;Ljava/lang/String;)V
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
package app.revanced.patches.music.misc.spoof

import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patches.music.misc.gms.musicActivityOnCreateFingerprint
import app.revanced.patches.shared.misc.spoof.EXTENSION_CLASS_DESCRIPTOR
import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch

val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
compatibleWith("com.google.android.apps.youtube.music")
}, {
musicActivityOnCreateFingerprint.method.addInstruction(
0,
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setClientTypeToAndroidVrNoHl()V"
)
})
Original file line number Diff line number Diff line change
Expand Up @@ -110,22 +110,22 @@ val enableDebuggingPatch = bytecodePatch(
"""
)
}
}

experimentalStringFeatureFlagFingerprint.match(
experimentalFeatureFlagParentFingerprint.originalClassDef
).method.apply {
val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT)
experimentalStringFeatureFlagFingerprint.match(
experimentalFeatureFlagParentFingerprint.originalClassDef
).method.apply {
val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT)

addInstructions(
insertIndex,
"""
move-result-object v0
invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String;
move-result-object v0
return-object v0
"""
)
addInstructions(
insertIndex,
"""
move-result-object v0
invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String;
move-result-object v0
return-object v0
"""
)
}
}

// There exists other experimental accessor methods for byte[]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package app.revanced.patches.youtube.video.audio

import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags

internal val streamingModelBuilderFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("L")
strings("vprng")
}

internal val menuItemAudioTrackFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameters("L")
returns("V")
strings("menu_item_audio_track")
}

internal val audioStreamingTypeSelector = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
returns("L")
strings("raw") // String is not unique
}
Loading

0 comments on commit f4aa440

Please sign in to comment.