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 {
defaultConfig {
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.SpoofiOSAvailability;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
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,
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
// 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));
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();
}
}
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));
}

View File

@@ -12,7 +12,7 @@ import app.revanced.extension.shared.settings.BaseSettings;
public enum ClientType {
// https://dumps.tadiphone.dev/dumps/oculus/eureka
ANDROID_VR_NO_AUTH(
ANDROID_VR_1_61_48(
28,
"ANDROID_VR",
"com.google.android.apps.youtube.vr.oculus",
@@ -27,7 +27,7 @@ public enum ClientType {
"1.61.48",
false,
false,
"Android VR No auth"
"Android VR 1.61"
),
// Chromecast with Google TV 4K.
// https://dumps.tadiphone.dev/dumps/google/kirkwood
@@ -96,6 +96,26 @@ public enum ClientType {
forceAVC()
? "iOS TV Force AVC"
: "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() {

View File

@@ -252,8 +252,9 @@ public class SpoofVideoStreamsPatch {
public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
ClientType clientType = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.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
// the audio language is not selectable in the UI.
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()
: Locale.getDefault();

View File

@@ -35,21 +35,22 @@ import app.revanced.extension.shared.spoof.ClientType;
*/
public class StreamingDataRequest {
private static final ClientType[] CLIENT_ORDER_TO_USE;
private static volatile ClientType[] clientOrderToUse = ClientType.values();
static {
ClientType[] allClientTypes = ClientType.values();
ClientType preferredClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
public static void setClientOrderToUse(ClientType[] availableClients, ClientType preferredClient) {
Objects.requireNonNull(availableClients);
CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length];
CLIENT_ORDER_TO_USE[0] = preferredClient;
clientOrderToUse = new ClientType[availableClients.length];
clientOrderToUse[0] = preferredClient;
int i = 1;
for (ClientType c : allClientTypes) {
for (ClientType c : availableClients) {
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";
@@ -193,9 +194,9 @@ public class StreamingDataRequest {
// Retry with different client if empty response body is received.
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.
final boolean showErrorToast = (++i == CLIENT_ORDER_TO_USE.length) || debugEnabled;
final boolean showErrorToast = (++i == clientOrderToUse.length) || debugEnabled;
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
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");
// 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");
}

View File

@@ -1,13 +1,20 @@
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.gms.musicActivityOnCreateFingerprint
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_15_or_greater
import app.revanced.patches.music.playservice.versionCheckPatch
import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/music/patches/spoof/SpoofVideoStreamsPatch;"
val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
fixMediaFetchHotConfigChanges = { true },
fixMediaFetchHotConfigAlternativeChanges = { is_8_11_or_greater && !is_8_15_or_greater },
fixParsePlaybackResponseFeatureFlag = { is_7_33_or_greater },
block = {
compatibleWith(
"com.google.android.apps.youtube.music"(
@@ -17,7 +24,10 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
dependsOn(sharedExtensionPatch, versionCheckPatch, userAgentClientSpoofPatch)
},
fixMediaFetchHotConfigChanges = { true },
fixMediaFetchHotConfigAlternativeChanges = { is_8_11_or_greater && !is_8_15_or_greater },
fixParsePlaybackResponseFeatureFlag = { is_7_33_or_greater }
executeBlock = {
musicActivityOnCreateFingerprint.method.addInstruction(
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
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
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.NonInteractivePreference
@@ -13,56 +14,70 @@ 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.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
compatibleWith(
"com.google.android.youtube"(
"19.34.42",
"19.43.41",
"19.47.53",
"20.07.39",
"20.12.46",
"20.13.41",
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/spoof/SpoofVideoStreamsPatch;"
val spoofVideoStreamsPatch = spoofVideoStreamsPatch(
block = {
compatibleWith(
"com.google.android.youtube"(
"19.34.42",
"19.43.41",
"19.47.53",
"20.07.39",
"20.12.46",
"20.13.41",
)
)
)
dependsOn(
userAgentClientSpoofPatch,
settingsPatch,
versionCheckPatch
)
}, {
is_19_34_or_greater
}, {
// 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_03_or_greater
}, {
addResources("youtube", "misc.fix.playback.spoofVideoStreamsPatch")
dependsOn(
userAgentClientSpoofPatch,
settingsPatch,
versionCheckPatch
)
},
fixMediaFetchHotConfigChanges = {
is_19_34_or_greater
},
fixMediaFetchHotConfigAlternativeChanges = {
// In 20.14 the flag was merged with 20.03 start playback flag.
is_20_10_or_greater && !is_20_14_or_greater
},
fixParsePlaybackResponseFeatureFlag = {
is_20_03_or_greater
},
executeBlock = {
addResources("youtube", "misc.fix.playback.spoofVideoStreamsPatch")
PreferenceScreen.MISC.addPreferences(
PreferenceScreenPreference(
key = "revanced_spoof_video_streams_screen",
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
preferences = setOf(
SwitchPreference("revanced_spoof_video_streams"),
ListPreference("revanced_spoof_video_streams_client_type"),
NonInteractivePreference(
// Requires a key and title but the actual text is chosen at runtime.
key = "revanced_spoof_video_streams_about_android",
tag = "app.revanced.extension.youtube.settings.preference.SpoofStreamingDataSideEffectsPreference"
PreferenceScreen.MISC.addPreferences(
PreferenceScreenPreference(
key = "revanced_spoof_video_streams_screen",
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
preferences = setOf(
SwitchPreference("revanced_spoof_video_streams"),
ListPreference("revanced_spoof_video_streams_client_type"),
NonInteractivePreference(
// Requires a key and title but the actual text is chosen at runtime.
key = "revanced_spoof_video_streams_about_android",
tag = "app.revanced.extension.youtube.settings.preference.SpoofStreamingDataSideEffectsPreference"
),
ListPreference(
key = "revanced_spoof_video_streams_language",
// Language strings are declared in Setting patch.
entriesKey = "revanced_language_entries",
entryValuesKey = "revanced_language_entry_values",
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference"
),
SwitchPreference("revanced_spoof_video_streams_ios_force_avc"),
SwitchPreference("revanced_spoof_streaming_data_stats_for_nerds"),
),
ListPreference(
key = "revanced_spoof_video_streams_language",
// Language strings are declared in Setting patch.
entriesKey = "revanced_language_entries",
entryValuesKey = "revanced_language_entry_values",
tag = "app.revanced.extension.shared.settings.preference.SortedListPreference"
),
SwitchPreference("revanced_spoof_video_streams_ios_force_avc"),
SwitchPreference("revanced_spoof_streaming_data_stats_for_nerds"),
),
),
)
})
)
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 name="revanced_spoof_video_streams_client_type_entry_values">
<item>ANDROID_UNPLUGGED</item>
<item>ANDROID_VR_NO_AUTH</item>
<item>ANDROID_VR_1_61_48</item>
<item>IOS_UNPLUGGED</item>
</string-array>
</patch>