fix(YouTube Music/SponsorBlock): SponsorBlock segments at the end of a song cause the player to get stuck https://github.com/inotia00/ReVanced_Extended/issues/2360

This commit is contained in:
inotia00 2024-09-05 19:46:55 +09:00
parent 250641a600
commit 547fd291ff
3 changed files with 146 additions and 41 deletions

View File

@ -8,6 +8,7 @@ import app.revanced.patcher.fingerprint.MethodFingerprintResult
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod 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.patcher.util.smali.toInstructions import app.revanced.patcher.util.smali.toInstructions
@ -22,20 +23,26 @@ import app.revanced.patches.music.video.information.fingerprints.VideoLengthFing
import app.revanced.patches.music.video.information.fingerprints.VideoQualityListFingerprint import app.revanced.patches.music.video.information.fingerprints.VideoQualityListFingerprint
import app.revanced.patches.music.video.information.fingerprints.VideoQualityTextFingerprint import app.revanced.patches.music.video.information.fingerprints.VideoQualityTextFingerprint
import app.revanced.patches.music.video.videoid.VideoIdPatch import app.revanced.patches.music.video.videoid.VideoIdPatch
import app.revanced.patches.shared.fingerprints.MdxPlayerDirectorSetVideoStageFingerprint
import app.revanced.util.addFieldAndInstructions import app.revanced.util.addFieldAndInstructions
import app.revanced.util.getReference
import app.revanced.util.getTargetIndexWithFieldReferenceTypeReversedOrThrow import app.revanced.util.getTargetIndexWithFieldReferenceTypeReversedOrThrow
import app.revanced.util.getTargetIndexWithMethodReferenceNameReversedOrThrow import app.revanced.util.getTargetIndexWithMethodReferenceNameReversedOrThrow
import app.revanced.util.getWalkerMethod import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.resultOrThrow import app.revanced.util.resultOrThrow
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.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.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.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
import com.android.tools.smali.dexlib2.util.MethodUtil
@Patch( @Patch(
dependencies = [ dependencies = [
@ -46,6 +53,7 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
object VideoInformationPatch : BytecodePatch( object VideoInformationPatch : BytecodePatch(
setOf( setOf(
MdxPlayerDirectorSetVideoStageFingerprint,
PlayerControllerSetTimeReferenceFingerprint, PlayerControllerSetTimeReferenceFingerprint,
PlaybackSpeedParentFingerprint, PlaybackSpeedParentFingerprint,
SeekBarConstructorFingerprint, SeekBarConstructorFingerprint,
@ -57,6 +65,23 @@ object VideoInformationPatch : BytecodePatch(
private const val INTEGRATIONS_CLASS_DESCRIPTOR = private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"$SHARED_PATH/VideoInformation;" "$SHARED_PATH/VideoInformation;"
/**
* Used in [VideoEndFingerprint] and [MdxPlayerDirectorSetVideoStageFingerprint].
* Since both classes are inherited from the same class,
* [VideoEndFingerprint] and [MdxPlayerDirectorSetVideoStageFingerprint] always have the same [seekSourceEnumType] and [seekSourceMethodName].
*/
private var seekSourceEnumType = ""
private var seekSourceMethodName = ""
private lateinit var videoInformationMutableClass: MutableClass
private lateinit var context: BytecodeContext
private lateinit var playerConstructorMethod: MutableMethod
private var playerConstructorInsertIndex = -1
private lateinit var mdxConstructorMethod: MutableMethod
private var mdxConstructorInsertIndex = -1
private lateinit var videoTimeConstructorMethod: MutableMethod private lateinit var videoTimeConstructorMethod: MutableMethod
private var videoTimeConstructorInsertIndex = 2 private var videoTimeConstructorInsertIndex = 2
@ -64,54 +89,107 @@ object VideoInformationPatch : BytecodePatch(
lateinit var rectangleFieldName: String lateinit var rectangleFieldName: String
internal lateinit var playbackSpeedResult: MethodFingerprintResult internal lateinit var playbackSpeedResult: MethodFingerprintResult
private fun addSeekInterfaceMethods(
result: MethodFingerprintResult,
seekMethodName: String,
methodName: String,
fieldName: String
) {
result.mutableMethod.apply {
result.mutableClass.methods.add(
ImmutableMethod(
definingClass,
"seekTo",
listOf(ImmutableMethodParameter("J", annotations, "time")),
"Z",
AccessFlags.PUBLIC or AccessFlags.FINAL,
annotations,
null,
ImmutableMethodImplementation(
4, """
# first enum (field a) is SEEK_SOURCE_UNKNOWN
sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType
invoke-virtual {p0, p1, p2, v0}, ${definingClass}->$seekMethodName(J$seekSourceEnumType)Z
move-result p1
return p1
""".toInstructions(),
null,
null
)
).toMutable()
)
val smaliInstructions =
"""
if-eqz v0, :ignore
invoke-virtual {v0, p0, p1}, $definingClass->seekTo(J)Z
move-result v0
return v0
:ignore
const/4 v0, 0x0
return v0
"""
videoInformationMutableClass.addFieldAndInstructions(
context,
methodName,
fieldName,
definingClass,
smaliInstructions,
true
)
}
}
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
val videoInformationMutableClass = this.context = context
videoInformationMutableClass =
context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR)!!.mutableClass context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR)!!.mutableClass
VideoEndFingerprint.resultOrThrow().let { VideoEndFingerprint.resultOrThrow().let {
it.mutableMethod.apply { it.mutableMethod.apply {
val seekSourceEnumType = parameterTypes[1].toString() playerConstructorMethod =
it.mutableClass.methods.first { method -> MethodUtil.isConstructor(method) }
it.mutableClass.methods.add( playerConstructorInsertIndex =
ImmutableMethod( playerConstructorMethod.indexOfFirstInstructionOrThrow {
definingClass, opcode == Opcode.INVOKE_DIRECT && getReference<MethodReference>()?.name == "<init>"
"seekTo", } + 1
listOf(ImmutableMethodParameter("J", annotations, "time")),
"Z",
AccessFlags.PUBLIC or AccessFlags.FINAL,
annotations,
null,
ImmutableMethodImplementation(
4, """
sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType
invoke-virtual {p0, p1, p2, v0}, ${definingClass}->${name}(J$seekSourceEnumType)Z
move-result p1
return p1
""".toInstructions(),
null,
null
)
).toMutable()
)
val smaliInstructions = // hook the player controller for use through integrations
""" onCreateHook(INTEGRATIONS_CLASS_DESCRIPTOR, "initialize")
if-eqz v0, :ignore
invoke-virtual {v0, p0, p1}, $definingClass->seekTo(J)Z
move-result v0
return v0
:ignore
const/4 v0, 0x0
return v0
"""
videoInformationMutableClass.addFieldAndInstructions( seekSourceEnumType = parameterTypes[1].toString()
context, seekSourceMethodName = name
// Create integrations interface methods.
addSeekInterfaceMethods(
it,
seekSourceMethodName,
"overrideVideoTime", "overrideVideoTime",
"videoInformationClass", "videoInformationClass"
definingClass, )
smaliInstructions, }
true }
MdxPlayerDirectorSetVideoStageFingerprint.resultOrThrow().let {
it.mutableMethod.apply {
mdxConstructorMethod =
it.mutableClass.methods.first { method -> MethodUtil.isConstructor(method) }
mdxConstructorInsertIndex = mdxConstructorMethod.indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_DIRECT && getReference<MethodReference>()?.name == "<init>"
} + 1
// hook the MDX director for use through integrations
onCreateHookMdx(INTEGRATIONS_CLASS_DESCRIPTOR, "initializeMdx")
// Create integrations interface methods.
addSeekInterfaceMethods(
it,
seekSourceMethodName,
"overrideMDXVideoTime",
"videoInformationMDXClass"
) )
} }
} }
@ -246,6 +324,33 @@ object VideoInformationPatch : BytecodePatch(
private fun MutableMethod.insertTimeHook(insertIndex: Int, descriptor: String) = private fun MutableMethod.insertTimeHook(insertIndex: Int, descriptor: String) =
insert(insertIndex, "p1, p2", descriptor) insert(insertIndex, "p1, p2", descriptor)
/**
* Hook the player controller. Called when a video is opened or the current video is changed.
*
* Note: This hook is called very early and is called before the video id, video time, video length,
* and many other data fields are set.
*
* @param targetMethodClass The descriptor for the class to invoke when the player controller is created.
* @param targetMethodName The name of the static method to invoke when the player controller is created.
*/
internal fun onCreateHook(targetMethodClass: String, targetMethodName: String) =
playerConstructorMethod.addInstruction(
playerConstructorInsertIndex++,
"invoke-static { }, $targetMethodClass->$targetMethodName()V"
)
/**
* Hook the MDX player director. Called when playing videos while casting to a big screen device.
*
* @param targetMethodClass The descriptor for the class to invoke when the player controller is created.
* @param targetMethodName The name of the static method to invoke when the player controller is created.
*/
internal fun onCreateHookMdx(targetMethodClass: String, targetMethodName: String) =
mdxConstructorMethod.addInstruction(
mdxConstructorInsertIndex++,
"invoke-static { }, $targetMethodClass->$targetMethodName()V"
)
/** /**
* Hook the video time. * Hook the video time.
* The hook is usually called once per second. * The hook is usually called once per second.

View File

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.video.information.fingerprints package app.revanced.patches.shared.fingerprints
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint

View File

@ -14,13 +14,13 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod 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.patcher.util.smali.toInstructions import app.revanced.patcher.util.smali.toInstructions
import app.revanced.patches.shared.fingerprints.MdxPlayerDirectorSetVideoStageFingerprint
import app.revanced.patches.youtube.utils.fingerprints.VideoEndFingerprint import app.revanced.patches.youtube.utils.fingerprints.VideoEndFingerprint
import app.revanced.patches.youtube.utils.integrations.Constants.SHARED_PATH import app.revanced.patches.youtube.utils.integrations.Constants.SHARED_PATH
import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch
import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch import app.revanced.patches.youtube.utils.resourceid.SharedResourceIdPatch
import app.revanced.patches.youtube.video.information.fingerprints.ChannelIdFingerprint import app.revanced.patches.youtube.video.information.fingerprints.ChannelIdFingerprint
import app.revanced.patches.youtube.video.information.fingerprints.ChannelNameFingerprint import app.revanced.patches.youtube.video.information.fingerprints.ChannelNameFingerprint
import app.revanced.patches.youtube.video.information.fingerprints.MdxPlayerDirectorSetVideoStageFingerprint
import app.revanced.patches.youtube.video.information.fingerprints.OnPlaybackSpeedItemClickFingerprint import app.revanced.patches.youtube.video.information.fingerprints.OnPlaybackSpeedItemClickFingerprint
import app.revanced.patches.youtube.video.information.fingerprints.PlaybackInitializationFingerprint import app.revanced.patches.youtube.video.information.fingerprints.PlaybackInitializationFingerprint
import app.revanced.patches.youtube.video.information.fingerprints.PlaybackSpeedClassFingerprint import app.revanced.patches.youtube.video.information.fingerprints.PlaybackSpeedClassFingerprint