Skip to content

Commit 5c7c8b5

Browse files
fix(Spoof video streams): Resolve occasional playback stuttering
Code adapted from: inotia00/revanced-patches@2cf9db6 inotia00/revanced-patches@50d9c60
1 parent 729997e commit 5c7c8b5

File tree

3 files changed

+132
-15
lines changed

3 files changed

+132
-15
lines changed

extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/SpoofVideoStreamsPatch.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,35 @@ public static Uri blockGetWatchRequest(Uri playerRequestUri) {
9797
return playerRequestUri;
9898
}
9999

100+
/**
101+
* Injection point.
102+
*
103+
* Blocks /get_watch requests by returning an unreachable URI.
104+
* /att/get requests are used to obtain a PoToken challenge.
105+
* See: <a href="https://github.com/FreeTubeApp/FreeTube/blob/4b7208430bc1032019a35a35eb7c8a84987ddbd7/src/botGuardScript.js#L15">botGuardScript.js#L15</a>
106+
* <p>
107+
* Since the Spoof streaming data patch was implemented because a valid PoToken cannot be obtained,
108+
* Blocking /att/get requests are not a problem.
109+
*/
110+
public static String blockGetAttRequest(String originalUrlString) {
111+
if (SPOOF_STREAMING_DATA) {
112+
try {
113+
var originalUri = Uri.parse(originalUrlString);
114+
String path = originalUri.getPath();
115+
116+
if (path != null && path.contains("att/get")) {
117+
Logger.printDebug(() -> "Blocking 'att/get' by returning internet connection check uri");
118+
119+
return INTERNET_CONNECTION_CHECK_URI_STRING;
120+
}
121+
} catch (Exception ex) {
122+
Logger.printException(() -> "blockGetAttRequest failure", ex);
123+
}
124+
}
125+
126+
return originalUrlString;
127+
}
128+
100129
/**
101130
* Injection point.
102131
* <p>
@@ -130,7 +159,7 @@ public static boolean isSpoofingEnabled() {
130159

131160
/**
132161
* Injection point.
133-
* Only invoked when playing a livestream on an iOS client.
162+
* Only invoked when playing a livestream on an Apple client.
134163
*/
135164
public static boolean fixHLSCurrentTime(boolean original) {
136165
if (!SPOOF_STREAMING_DATA) {
@@ -139,6 +168,14 @@ public static boolean fixHLSCurrentTime(boolean original) {
139168
return false;
140169
}
141170

171+
/*
172+
* Injection point.
173+
* Fix audio stuttering in YouTube Music.
174+
*/
175+
public static boolean disableSABR() {
176+
return SPOOF_STREAMING_DATA;
177+
}
178+
142179
/**
143180
* Injection point.
144181
* Turns off a feature flag that interferes with spoofing.

patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package app.revanced.patches.shared.misc.spoof
22

33
import app.revanced.patcher.fingerprint
4-
import app.revanced.util.getReference
5-
import app.revanced.util.indexOfFirstInstruction
64
import app.revanced.util.literal
75
import com.android.tools.smali.dexlib2.AccessFlags
86
import com.android.tools.smali.dexlib2.Opcode
9-
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
107

118
internal val buildInitPlaybackRequestFingerprint = fingerprint {
129
returns("Lorg/chromium/net/UrlRequest\$Builder;")
@@ -40,10 +37,7 @@ internal val buildRequestFingerprint = fingerprint {
4037
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
4138
returns("Lorg/chromium/net/UrlRequest") // UrlRequest; or UrlRequest$Builder;
4239
custom { methodDef, _ ->
43-
if (methodDef.indexOfFirstInstruction {
44-
val reference = getReference<MethodReference>()
45-
reference?.name == "newUrlRequestBuilder"
46-
} < 0) {
40+
if (indexOfNewUrlRequestBuilderInstruction(methodDef) < 0) {
4741
return@custom false
4842
}
4943

@@ -142,6 +136,17 @@ internal val hlsCurrentTimeFingerprint = fingerprint {
142136
}
143137
}
144138

139+
internal const val DISABLED_BY_SABR_STREAMING_URI_STRING = "DISABLED_BY_SABR_STREAMING_URI"
140+
141+
internal val mediaFetchEnumConstructorFingerprint = fingerprint {
142+
returns("V")
143+
strings(
144+
"ENABLED",
145+
"DISABLED_FOR_PLAYBACK",
146+
DISABLED_BY_SABR_STREAMING_URI_STRING
147+
)
148+
}
149+
145150
internal val nerdsStatsVideoFormatBuilderFingerprint = fingerprint {
146151
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
147152
returns("Ljava/lang/String;")

patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,28 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
55
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
66
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
77
import app.revanced.patcher.extensions.InstructionExtensions.instructions
8+
import app.revanced.patcher.fingerprint
89
import app.revanced.patcher.patch.BytecodePatchBuilder
910
import app.revanced.patcher.patch.BytecodePatchContext
1011
import app.revanced.patcher.patch.bytecodePatch
12+
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
1113
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
1214
import app.revanced.patches.all.misc.resources.addResources
1315
import app.revanced.patches.all.misc.resources.addResourcesPatch
1416
import app.revanced.util.findFreeRegister
1517
import app.revanced.util.findInstructionIndicesReversedOrThrow
1618
import app.revanced.util.getReference
19+
import app.revanced.util.indexOfFirstInstruction
1720
import app.revanced.util.indexOfFirstInstructionOrThrow
1821
import app.revanced.util.insertLiteralOverride
1922
import app.revanced.util.returnEarly
2023
import com.android.tools.smali.dexlib2.AccessFlags
2124
import com.android.tools.smali.dexlib2.Opcode
2225
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
26+
import com.android.tools.smali.dexlib2.iface.Method
2327
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
2428
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
29+
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
2530
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
2631
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
2732
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@@ -31,6 +36,9 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
3136
internal const val EXTENSION_CLASS_DESCRIPTOR =
3237
"Lapp/revanced/extension/shared/spoof/SpoofVideoStreamsPatch;"
3338

39+
private lateinit var buildRequestMethod: MutableMethod
40+
private var buildRequestMethodUrlRegister = -1
41+
3442
fun spoofVideoStreamsPatch(
3543
block: BytecodePatchBuilder.() -> Unit = {},
3644
fixMediaFetchHotConfigChanges: BytecodePatchBuilder.() -> Boolean = { false },
@@ -91,18 +99,17 @@ fun spoofVideoStreamsPatch(
9199
// region Get replacement streams at player requests.
92100

93101
buildRequestFingerprint.method.apply {
94-
val newRequestBuilderIndex = indexOfFirstInstructionOrThrow {
95-
opcode == Opcode.INVOKE_VIRTUAL &&
96-
getReference<MethodReference>()?.name == "newUrlRequestBuilder"
97-
}
98-
val urlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
99-
val freeRegister = findFreeRegister(newRequestBuilderIndex, urlRegister)
102+
buildRequestMethod = this
103+
104+
val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
105+
buildRequestMethodUrlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
106+
val freeRegister = findFreeRegister(newRequestBuilderIndex, buildRequestMethodUrlRegister)
100107

101108
addInstructions(
102109
newRequestBuilderIndex,
103110
"""
104111
move-object v$freeRegister, p1
105-
invoke-static { v$urlRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
112+
invoke-static { v$buildRequestMethodUrlRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
106113
"""
107114
)
108115
}
@@ -187,6 +194,21 @@ fun spoofVideoStreamsPatch(
187194

188195
// endregion
189196

197+
// region block getAtt request
198+
199+
buildRequestMethod.apply {
200+
val insertIndex = indexOfNewUrlRequestBuilderInstruction(this)
201+
202+
addInstructions(
203+
insertIndex, """
204+
invoke-static { v$buildRequestMethodUrlRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetAttRequest(Ljava/lang/String;)Ljava/lang/String;
205+
move-result-object v$buildRequestMethodUrlRegister
206+
"""
207+
)
208+
}
209+
210+
// endregion
211+
190212
// region Remove /videoplayback request body to fix playback.
191213
// It is assumed, YouTube makes a request with a body tuned for Android.
192214
// Requesting streams intended for other platforms with a body tuned for Android could be the cause of 400 errors.
@@ -243,6 +265,50 @@ fun spoofVideoStreamsPatch(
243265

244266
// endregion
245267

268+
// region Disable SABR playback.
269+
// If SABR is disabled, it seems 'MediaFetchHotConfig' may no longer need an override (not confirmed).
270+
271+
val (mediaFetchEnumClass, sabrFieldReference) = with(mediaFetchEnumConstructorFingerprint.method) {
272+
val stringIndex = mediaFetchEnumConstructorFingerprint.stringMatches!!.first {
273+
it.string == DISABLED_BY_SABR_STREAMING_URI_STRING
274+
}.index
275+
276+
val mediaFetchEnumClass = definingClass
277+
val sabrFieldIndex = indexOfFirstInstructionOrThrow(stringIndex) {
278+
opcode == Opcode.SPUT_OBJECT &&
279+
getReference<FieldReference>()?.type == mediaFetchEnumClass
280+
}
281+
282+
Pair(
283+
mediaFetchEnumClass,
284+
getInstruction<ReferenceInstruction>(sabrFieldIndex).reference
285+
)
286+
}
287+
288+
fingerprint {
289+
returns(mediaFetchEnumClass)
290+
opcodes(
291+
Opcode.SGET_OBJECT,
292+
Opcode.RETURN_OBJECT,
293+
)
294+
custom { method, _ ->
295+
!method.parameterTypes.isEmpty()
296+
}
297+
}.method.addInstructionsWithLabels(
298+
0,
299+
"""
300+
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->disableSABR()Z
301+
move-result v0
302+
if-eqz v0, :ignore
303+
sget-object v0, $sabrFieldReference
304+
return-object v0
305+
:ignore
306+
nop
307+
"""
308+
)
309+
310+
// endregion
311+
246312
// region turn off stream config replacement feature flag.
247313

248314
if (fixMediaFetchHotConfigChanges()) {
@@ -271,3 +337,12 @@ fun spoofVideoStreamsPatch(
271337
executeBlock()
272338
}
273339
}
340+
341+
internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) = method.indexOfFirstInstruction {
342+
opcode == Opcode.INVOKE_VIRTUAL && getReference<MethodReference>().toString() ==
343+
"Lorg/chromium/net/CronetEngine;" +
344+
"->newUrlRequestBuilder(" +
345+
"Ljava/lang/String;Lorg/chromium/net/UrlRequest${'$'}Callback;" +
346+
"Ljava/util/concurrent/Executor;" +
347+
")Lorg/chromium/net/UrlRequest${'$'}Builder;"
348+
}

0 commit comments

Comments
 (0)