mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-12 04:13:57 +01:00
fix(Spoof video streams): Resolve occasional playback stuttering
Code adapted from:2cf9db66ac50d9c60374
This commit is contained in:
@@ -97,6 +97,35 @@ public class SpoofVideoStreamsPatch {
|
|||||||
return playerRequestUri;
|
return playerRequestUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* Blocks /get_watch requests by returning an unreachable URI.
|
||||||
|
* /att/get requests are used to obtain a PoToken challenge.
|
||||||
|
* See: <a href="https://github.com/FreeTubeApp/FreeTube/blob/4b7208430bc1032019a35a35eb7c8a84987ddbd7/src/botGuardScript.js#L15">botGuardScript.js#L15</a>
|
||||||
|
* <p>
|
||||||
|
* Since the Spoof streaming data patch was implemented because a valid PoToken cannot be obtained,
|
||||||
|
* Blocking /att/get requests are not a problem.
|
||||||
|
*/
|
||||||
|
public static String blockGetAttRequest(String originalUrlString) {
|
||||||
|
if (SPOOF_STREAMING_DATA) {
|
||||||
|
try {
|
||||||
|
var originalUri = Uri.parse(originalUrlString);
|
||||||
|
String path = originalUri.getPath();
|
||||||
|
|
||||||
|
if (path != null && path.contains("att/get")) {
|
||||||
|
Logger.printDebug(() -> "Blocking 'att/get' by returning internet connection check uri");
|
||||||
|
|
||||||
|
return INTERNET_CONNECTION_CHECK_URI_STRING;
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "blockGetAttRequest failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalUrlString;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -130,7 +159,7 @@ public class SpoofVideoStreamsPatch {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
* Only invoked when playing a livestream on an iOS client.
|
* Only invoked when playing a livestream on an Apple client.
|
||||||
*/
|
*/
|
||||||
public static boolean fixHLSCurrentTime(boolean original) {
|
public static boolean fixHLSCurrentTime(boolean original) {
|
||||||
if (!SPOOF_STREAMING_DATA) {
|
if (!SPOOF_STREAMING_DATA) {
|
||||||
@@ -139,6 +168,14 @@ public class SpoofVideoStreamsPatch {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Injection point.
|
||||||
|
* Fix audio stuttering in YouTube Music.
|
||||||
|
*/
|
||||||
|
public static boolean disableSABR() {
|
||||||
|
return SPOOF_STREAMING_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
* Turns off a feature flag that interferes with spoofing.
|
* Turns off a feature flag that interferes with spoofing.
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
package app.revanced.patches.shared.misc.spoof
|
package app.revanced.patches.shared.misc.spoof
|
||||||
|
|
||||||
import app.revanced.patcher.fingerprint
|
import app.revanced.patcher.fingerprint
|
||||||
import app.revanced.util.getReference
|
|
||||||
import app.revanced.util.indexOfFirstInstruction
|
|
||||||
import app.revanced.util.literal
|
import app.revanced.util.literal
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
|
||||||
|
|
||||||
internal val buildInitPlaybackRequestFingerprint = fingerprint {
|
internal val buildInitPlaybackRequestFingerprint = fingerprint {
|
||||||
returns("Lorg/chromium/net/UrlRequest\$Builder;")
|
returns("Lorg/chromium/net/UrlRequest\$Builder;")
|
||||||
@@ -40,10 +37,7 @@ internal val buildRequestFingerprint = fingerprint {
|
|||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||||
returns("Lorg/chromium/net/UrlRequest") // UrlRequest; or UrlRequest$Builder;
|
returns("Lorg/chromium/net/UrlRequest") // UrlRequest; or UrlRequest$Builder;
|
||||||
custom { methodDef, _ ->
|
custom { methodDef, _ ->
|
||||||
if (methodDef.indexOfFirstInstruction {
|
if (indexOfNewUrlRequestBuilderInstruction(methodDef) < 0) {
|
||||||
val reference = getReference<MethodReference>()
|
|
||||||
reference?.name == "newUrlRequestBuilder"
|
|
||||||
} < 0) {
|
|
||||||
return@custom false
|
return@custom false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +136,17 @@ internal val hlsCurrentTimeFingerprint = fingerprint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal const val DISABLED_BY_SABR_STREAMING_URI_STRING = "DISABLED_BY_SABR_STREAMING_URI"
|
||||||
|
|
||||||
|
internal val mediaFetchEnumConstructorFingerprint = fingerprint {
|
||||||
|
returns("V")
|
||||||
|
strings(
|
||||||
|
"ENABLED",
|
||||||
|
"DISABLED_FOR_PLAYBACK",
|
||||||
|
DISABLED_BY_SABR_STREAMING_URI_STRING
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
internal val nerdsStatsVideoFormatBuilderFingerprint = fingerprint {
|
internal val nerdsStatsVideoFormatBuilderFingerprint = fingerprint {
|
||||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||||
returns("Ljava/lang/String;")
|
returns("Ljava/lang/String;")
|
||||||
|
|||||||
@@ -5,23 +5,28 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
|||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||||
|
import app.revanced.patcher.fingerprint
|
||||||
import app.revanced.patcher.patch.BytecodePatchBuilder
|
import app.revanced.patcher.patch.BytecodePatchBuilder
|
||||||
import app.revanced.patcher.patch.BytecodePatchContext
|
import app.revanced.patcher.patch.BytecodePatchContext
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||||
import app.revanced.patches.all.misc.resources.addResources
|
import app.revanced.patches.all.misc.resources.addResources
|
||||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||||
import app.revanced.util.findFreeRegister
|
import app.revanced.util.findFreeRegister
|
||||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||||
import app.revanced.util.getReference
|
import app.revanced.util.getReference
|
||||||
|
import app.revanced.util.indexOfFirstInstruction
|
||||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||||
import app.revanced.util.insertLiteralOverride
|
import app.revanced.util.insertLiteralOverride
|
||||||
import app.revanced.util.returnEarly
|
import app.revanced.util.returnEarly
|
||||||
import com.android.tools.smali.dexlib2.AccessFlags
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
import com.android.tools.smali.dexlib2.Opcode
|
import com.android.tools.smali.dexlib2.Opcode
|
||||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||||
|
import com.android.tools.smali.dexlib2.iface.Method
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
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.TwoRegisterInstruction
|
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||||
@@ -31,6 +36,9 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
|||||||
internal const val EXTENSION_CLASS_DESCRIPTOR =
|
internal const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
"Lapp/revanced/extension/shared/spoof/SpoofVideoStreamsPatch;"
|
"Lapp/revanced/extension/shared/spoof/SpoofVideoStreamsPatch;"
|
||||||
|
|
||||||
|
private lateinit var buildRequestMethod: MutableMethod
|
||||||
|
private var buildRequestMethodUrlRegister = -1
|
||||||
|
|
||||||
fun spoofVideoStreamsPatch(
|
fun spoofVideoStreamsPatch(
|
||||||
block: BytecodePatchBuilder.() -> Unit = {},
|
block: BytecodePatchBuilder.() -> Unit = {},
|
||||||
fixMediaFetchHotConfigChanges: BytecodePatchBuilder.() -> Boolean = { false },
|
fixMediaFetchHotConfigChanges: BytecodePatchBuilder.() -> Boolean = { false },
|
||||||
@@ -91,18 +99,17 @@ fun spoofVideoStreamsPatch(
|
|||||||
// region Get replacement streams at player requests.
|
// region Get replacement streams at player requests.
|
||||||
|
|
||||||
buildRequestFingerprint.method.apply {
|
buildRequestFingerprint.method.apply {
|
||||||
val newRequestBuilderIndex = indexOfFirstInstructionOrThrow {
|
buildRequestMethod = this
|
||||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
|
||||||
getReference<MethodReference>()?.name == "newUrlRequestBuilder"
|
val newRequestBuilderIndex = indexOfNewUrlRequestBuilderInstruction(this)
|
||||||
}
|
buildRequestMethodUrlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
||||||
val urlRegister = getInstruction<FiveRegisterInstruction>(newRequestBuilderIndex).registerD
|
val freeRegister = findFreeRegister(newRequestBuilderIndex, buildRequestMethodUrlRegister)
|
||||||
val freeRegister = findFreeRegister(newRequestBuilderIndex, urlRegister)
|
|
||||||
|
|
||||||
addInstructions(
|
addInstructions(
|
||||||
newRequestBuilderIndex,
|
newRequestBuilderIndex,
|
||||||
"""
|
"""
|
||||||
move-object v$freeRegister, p1
|
move-object v$freeRegister, p1
|
||||||
invoke-static { v$urlRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
|
invoke-static { v$buildRequestMethodUrlRegister, v$freeRegister }, $EXTENSION_CLASS_DESCRIPTOR->fetchStreams(Ljava/lang/String;Ljava/util/Map;)V
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -187,6 +194,21 @@ fun spoofVideoStreamsPatch(
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
// region block getAtt request
|
||||||
|
|
||||||
|
buildRequestMethod.apply {
|
||||||
|
val insertIndex = indexOfNewUrlRequestBuilderInstruction(this)
|
||||||
|
|
||||||
|
addInstructions(
|
||||||
|
insertIndex, """
|
||||||
|
invoke-static { v$buildRequestMethodUrlRegister }, $EXTENSION_CLASS_DESCRIPTOR->blockGetAttRequest(Ljava/lang/String;)Ljava/lang/String;
|
||||||
|
move-result-object v$buildRequestMethodUrlRegister
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
// region Remove /videoplayback request body to fix playback.
|
// region Remove /videoplayback request body to fix playback.
|
||||||
// It is assumed, YouTube makes a request with a body tuned for Android.
|
// It is assumed, YouTube makes a request with a body tuned for Android.
|
||||||
// Requesting streams intended for other platforms with a body tuned for Android could be the cause of 400 errors.
|
// 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(
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
// region Disable SABR playback.
|
||||||
|
// If SABR is disabled, it seems 'MediaFetchHotConfig' may no longer need an override (not confirmed).
|
||||||
|
|
||||||
|
val (mediaFetchEnumClass, sabrFieldReference) = with(mediaFetchEnumConstructorFingerprint.method) {
|
||||||
|
val stringIndex = mediaFetchEnumConstructorFingerprint.stringMatches!!.first {
|
||||||
|
it.string == DISABLED_BY_SABR_STREAMING_URI_STRING
|
||||||
|
}.index
|
||||||
|
|
||||||
|
val mediaFetchEnumClass = definingClass
|
||||||
|
val sabrFieldIndex = indexOfFirstInstructionOrThrow(stringIndex) {
|
||||||
|
opcode == Opcode.SPUT_OBJECT &&
|
||||||
|
getReference<FieldReference>()?.type == mediaFetchEnumClass
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair(
|
||||||
|
mediaFetchEnumClass,
|
||||||
|
getInstruction<ReferenceInstruction>(sabrFieldIndex).reference
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fingerprint {
|
||||||
|
returns(mediaFetchEnumClass)
|
||||||
|
opcodes(
|
||||||
|
Opcode.SGET_OBJECT,
|
||||||
|
Opcode.RETURN_OBJECT,
|
||||||
|
)
|
||||||
|
custom { method, _ ->
|
||||||
|
!method.parameterTypes.isEmpty()
|
||||||
|
}
|
||||||
|
}.method.addInstructionsWithLabels(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-static { }, $EXTENSION_CLASS_DESCRIPTOR->disableSABR()Z
|
||||||
|
move-result v0
|
||||||
|
if-eqz v0, :ignore
|
||||||
|
sget-object v0, $sabrFieldReference
|
||||||
|
return-object v0
|
||||||
|
:ignore
|
||||||
|
nop
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
// region turn off stream config replacement feature flag.
|
// region turn off stream config replacement feature flag.
|
||||||
|
|
||||||
if (fixMediaFetchHotConfigChanges()) {
|
if (fixMediaFetchHotConfigChanges()) {
|
||||||
@@ -271,3 +337,12 @@ fun spoofVideoStreamsPatch(
|
|||||||
executeBlock()
|
executeBlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun indexOfNewUrlRequestBuilderInstruction(method: Method) = method.indexOfFirstInstruction {
|
||||||
|
opcode == Opcode.INVOKE_VIRTUAL && getReference<MethodReference>().toString() ==
|
||||||
|
"Lorg/chromium/net/CronetEngine;" +
|
||||||
|
"->newUrlRequestBuilder(" +
|
||||||
|
"Ljava/lang/String;Lorg/chromium/net/UrlRequest${'$'}Callback;" +
|
||||||
|
"Ljava/util/concurrent/Executor;" +
|
||||||
|
")Lorg/chromium/net/UrlRequest${'$'}Builder;"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user