fix(YouTube Music - Spoof streaming data): Fix audio playback stuttering (#5839)

This commit is contained in:
LisoUseInAIKyrios
2025-09-14 22:19:13 +04:00
committed by GitHub
parent eee72208dd
commit 2a85a3b290
12 changed files with 174 additions and 82 deletions

View File

@@ -1,3 +1,9 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:youtube:stub"))
compileOnly(libs.annotation)
}
android { android {
defaultConfig { defaultConfig {
minSdk = 26 minSdk = 26

View File

@@ -0,0 +1,23 @@
package app.revanced.extension.music.patches.spoof;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
import app.revanced.extension.shared.spoof.ClientType;
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
@SuppressWarnings("unused")
public class SpoofVideoStreamsPatch {
/**
* Injection point.
*/
public static void setClientOrderToUse() {
ClientType[] availableClients = {
ANDROID_VR_1_43_32,
ANDROID_VR_1_61_48,
};
StreamingDataRequest.setClientOrderToUse(availableClients, ANDROID_VR_1_43_32);
}
}

View File

@@ -6,8 +6,6 @@ import static app.revanced.extension.shared.settings.Setting.parent;
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability; import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability;
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.SpoofiOSAvailability; import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.SpoofiOSAvailability;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.spoof.ClientType; import app.revanced.extension.shared.spoof.ClientType;
/** /**
@@ -36,18 +34,5 @@ public class BaseSettings {
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true, 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 SpoofiOSAvailability()); "revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
// Client type must be last spoof setting due to cyclic references. // Client type must be last spoof setting due to cyclic references.
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR_NO_AUTH, true, parent(SPOOF_VIDEO_STREAMS)); public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR_1_61_48, true, parent(SPOOF_VIDEO_STREAMS));
static {
// Data migration fix for YT Music users updating from very old patches that always
// stored default values in preference object, which requires manually updating
// the setting if the default changes. Package name may not contain "youtube.music"
// if the user has used change package name patch, but this will detect users
// with default installations.
if (!SPOOF_VIDEO_STREAMS_CLIENT_TYPE.isSetToDefault()
&& Utils.getContext().getPackageName().contains("youtube.music")) {
Logger.printInfo(() -> "Resetting spoof client from: " + SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get());
SPOOF_VIDEO_STREAMS_CLIENT_TYPE.resetToDefault();
}
}
} }

View File

@@ -12,7 +12,7 @@ import app.revanced.extension.shared.settings.BaseSettings;
public enum ClientType { public enum ClientType {
// https://dumps.tadiphone.dev/dumps/oculus/eureka // https://dumps.tadiphone.dev/dumps/oculus/eureka
ANDROID_VR_NO_AUTH( ANDROID_VR_1_61_48(
28, 28,
"ANDROID_VR", "ANDROID_VR",
"com.google.android.apps.youtube.vr.oculus", "com.google.android.apps.youtube.vr.oculus",
@@ -27,7 +27,7 @@ public enum ClientType {
"1.61.48", "1.61.48",
false, false,
false, false,
"Android VR No auth" "Android VR 1.61"
), ),
// Chromecast with Google TV 4K. // Chromecast with Google TV 4K.
// https://dumps.tadiphone.dev/dumps/google/kirkwood // https://dumps.tadiphone.dev/dumps/google/kirkwood
@@ -96,6 +96,26 @@ public enum ClientType {
forceAVC() forceAVC()
? "iOS TV Force AVC" ? "iOS TV Force AVC"
: "iOS TV" : "iOS TV"
),
/**
* Uses non adaptive bitrate, which fixes audio stuttering with YT Music.
* Uses VP9 and not AV1.
*/
ANDROID_VR_1_43_32(
ANDROID_VR_1_61_48.id,
ANDROID_VR_1_61_48.clientName,
ANDROID_VR_1_61_48.packageName,
ANDROID_VR_1_61_48.deviceMake,
ANDROID_VR_1_61_48.deviceModel,
ANDROID_VR_1_61_48.osName,
ANDROID_VR_1_61_48.osVersion,
ANDROID_VR_1_61_48.androidSdkVersion,
ANDROID_VR_1_61_48.buildId,
"107.0.5284.2",
"1.43.32",
ANDROID_VR_1_61_48.requiresAuth,
ANDROID_VR_1_61_48.useAuth,
"Android VR 1.43"
); );
private static boolean forceAVC() { private static boolean forceAVC() {

View File

@@ -252,8 +252,9 @@ public class SpoofVideoStreamsPatch {
public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability { public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability {
@Override @Override
public boolean isAvailable() { public boolean isAvailable() {
ClientType clientType = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
return BaseSettings.SPOOF_VIDEO_STREAMS.get() return BaseSettings.SPOOF_VIDEO_STREAMS.get()
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_VR_NO_AUTH; && (clientType == ClientType.ANDROID_VR_1_61_48 || clientType == ClientType.ANDROID_VR_1_43_32);
} }
} }

View File

@@ -42,7 +42,8 @@ final class PlayerRoutes {
// but if this is a fall over client it will set the language even though // but if this is a fall over client it will set the language even though
// the audio language is not selectable in the UI. // the audio language is not selectable in the UI.
ClientType userSelectedClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get(); ClientType userSelectedClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
Locale streamLocale = userSelectedClient == ClientType.ANDROID_VR_NO_AUTH Locale streamLocale = (userSelectedClient == ClientType.ANDROID_VR_1_61_48
|| userSelectedClient == ClientType.ANDROID_VR_1_43_32)
? BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get().getLocale() ? BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get().getLocale()
: Locale.getDefault(); : Locale.getDefault();

View File

@@ -35,21 +35,22 @@ import app.revanced.extension.shared.spoof.ClientType;
*/ */
public class StreamingDataRequest { public class StreamingDataRequest {
private static final ClientType[] CLIENT_ORDER_TO_USE; private static volatile ClientType[] clientOrderToUse = ClientType.values();
static { public static void setClientOrderToUse(ClientType[] availableClients, ClientType preferredClient) {
ClientType[] allClientTypes = ClientType.values(); Objects.requireNonNull(availableClients);
ClientType preferredClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length]; clientOrderToUse = new ClientType[availableClients.length];
CLIENT_ORDER_TO_USE[0] = preferredClient; clientOrderToUse[0] = preferredClient;
int i = 1; int i = 1;
for (ClientType c : allClientTypes) { for (ClientType c : availableClients) {
if (c != preferredClient) { if (c != preferredClient) {
CLIENT_ORDER_TO_USE[i++] = c; clientOrderToUse[i++] = c;
} }
} }
Logger.printDebug(() -> "Available spoof clients: " + Arrays.toString(clientOrderToUse));
} }
private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String AUTHORIZATION_HEADER = "Authorization";
@@ -193,9 +194,9 @@ public class StreamingDataRequest {
// Retry with different client if empty response body is received. // Retry with different client if empty response body is received.
int i = 0; int i = 0;
for (ClientType clientType : CLIENT_ORDER_TO_USE) { for (ClientType clientType : clientOrderToUse) {
// Show an error if the last client type fails, or if debug is enabled then show for all attempts. // Show an error if the last client type fails, or if debug is enabled then show for all attempts.
final boolean showErrorToast = (++i == CLIENT_ORDER_TO_USE.length) || debugEnabled; final boolean showErrorToast = (++i == clientOrderToUse.length) || debugEnabled;
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast); HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
if (connection != null) { if (connection != null) {

View File

@@ -0,0 +1,29 @@
package app.revanced.extension.youtube.patches.spoof;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_CREATOR;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_UNPLUGGED;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
import static app.revanced.extension.shared.spoof.ClientType.IOS_UNPLUGGED;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.spoof.ClientType;
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
@SuppressWarnings("unused")
public class SpoofVideoStreamsPatch {
/**
* Injection point.
*/
public static void setClientOrderToUse() {
ClientType[] availableClients = {
ANDROID_VR_1_61_48,
ANDROID_UNPLUGGED,
ANDROID_CREATOR,
IOS_UNPLUGGED
};
StreamingDataRequest.setClientOrderToUse(availableClients,
BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get());
}
}

View File

@@ -86,7 +86,8 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
String summary = str(key + "_summary"); String summary = str(key + "_summary");
// Android VR supports AV1 but all other clients do not. // Android VR supports AV1 but all other clients do not.
if (clientType != ClientType.ANDROID_VR_NO_AUTH) { if (clientType != ClientType.ANDROID_VR_1_61_48
&& clientType != ClientType.ANDROID_VR_1_43_32) {
summary += '\n' + str("revanced_spoof_video_streams_about_no_av1"); summary += '\n' + str("revanced_spoof_video_streams_about_no_av1");
} }

View File

@@ -1,13 +1,20 @@
package app.revanced.patches.music.misc.spoof package app.revanced.patches.music.misc.spoof
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patches.music.misc.extension.sharedExtensionPatch import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.gms.musicActivityOnCreateFingerprint
import app.revanced.patches.music.playservice.is_7_33_or_greater import app.revanced.patches.music.playservice.is_7_33_or_greater
import app.revanced.patches.music.playservice.is_8_11_or_greater import app.revanced.patches.music.playservice.is_8_11_or_greater
import app.revanced.patches.music.playservice.is_8_15_or_greater import app.revanced.patches.music.playservice.is_8_15_or_greater
import app.revanced.patches.music.playservice.versionCheckPatch import app.revanced.patches.music.playservice.versionCheckPatch
import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/spoof/SpoofVideoStreamsPatch;"
val spoofVideoStreamsPatch = spoofVideoStreamsPatch( val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
fixMediaFetchHotConfigChanges = { true },
fixMediaFetchHotConfigAlternativeChanges = { is_8_11_or_greater && !is_8_15_or_greater },
fixParsePlaybackResponseFeatureFlag = { is_7_33_or_greater },
block = { block = {
compatibleWith( compatibleWith(
"com.google.android.apps.youtube.music"( "com.google.android.apps.youtube.music"(
@@ -17,7 +24,10 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
dependsOn(sharedExtensionPatch, versionCheckPatch, userAgentClientSpoofPatch) dependsOn(sharedExtensionPatch, versionCheckPatch, userAgentClientSpoofPatch)
}, },
fixMediaFetchHotConfigChanges = { true }, executeBlock = {
fixMediaFetchHotConfigAlternativeChanges = { is_8_11_or_greater && !is_8_15_or_greater }, musicActivityOnCreateFingerprint.method.addInstruction(
fixParsePlaybackResponseFeatureFlag = { is_7_33_or_greater } 1, // Must use 1 index so context is set by extension patch.
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setClientOrderToUse()V"
)
}
) )

View File

@@ -1,5 +1,6 @@
package app.revanced.patches.youtube.misc.spoof package app.revanced.patches.youtube.misc.spoof
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.shared.misc.settings.preference.ListPreference import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
@@ -13,8 +14,12 @@ import app.revanced.patches.youtube.misc.playservice.is_20_14_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
val spoofVideoStreamsPatch = spoofVideoStreamsPatch({ private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/spoof/SpoofVideoStreamsPatch;"
val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
block = {
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.34.42", "19.34.42",
@@ -31,14 +36,18 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
settingsPatch, settingsPatch,
versionCheckPatch versionCheckPatch
) )
}, { },
fixMediaFetchHotConfigChanges = {
is_19_34_or_greater is_19_34_or_greater
}, { },
fixMediaFetchHotConfigAlternativeChanges = {
// In 20.14 the flag was merged with 20.03 start playback flag. // In 20.14 the flag was merged with 20.03 start playback flag.
is_20_10_or_greater && !is_20_14_or_greater is_20_10_or_greater && !is_20_14_or_greater
}, { },
fixParsePlaybackResponseFeatureFlag = {
is_20_03_or_greater is_20_03_or_greater
}, { },
executeBlock = {
addResources("youtube", "misc.fix.playback.spoofVideoStreamsPatch") addResources("youtube", "misc.fix.playback.spoofVideoStreamsPatch")
PreferenceScreen.MISC.addPreferences( PreferenceScreen.MISC.addPreferences(
@@ -65,4 +74,10 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
), ),
), ),
) )
})
mainActivityOnCreateFingerprint.method.addInstruction(
1, // Must use 1 index so context is set by extension patch.,
"invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->setClientOrderToUse()V"
)
}
)

View File

@@ -131,7 +131,7 @@
</string-array> </string-array>
<string-array name="revanced_spoof_video_streams_client_type_entry_values"> <string-array name="revanced_spoof_video_streams_client_type_entry_values">
<item>ANDROID_UNPLUGGED</item> <item>ANDROID_UNPLUGGED</item>
<item>ANDROID_VR_NO_AUTH</item> <item>ANDROID_VR_1_61_48</item>
<item>IOS_UNPLUGGED</item> <item>IOS_UNPLUGGED</item>
</string-array> </string-array>
</patch> </patch>