From a57c255ac5fece2e373b285c2054b442bafa2606 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 22 Sep 2022 07:17:10 +0200 Subject: [PATCH] feat(custom-playback-speed): max, min, granularity option --- .../VideoSpeedPatchFingerprint.kt | 21 ++++ .../patch/CustomPlaybackSpeedPatch.kt | 95 +++++++++++++++++-- 2 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/fingerprints/VideoSpeedPatchFingerprint.kt diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/fingerprints/VideoSpeedPatchFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/fingerprints/VideoSpeedPatchFingerprint.kt new file mode 100644 index 000000000..b6e212ab8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/fingerprints/VideoSpeedPatchFingerprint.kt @@ -0,0 +1,21 @@ +package app.revanced.patches.youtube.misc.customplaybackspeed.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.youtube.misc.customplaybackspeed.annotations.CustomPlaybackSpeedCompatibility +import org.jf.dexlib2.Opcode + +@Name("video-speed-patch-fingerprint") +@MatchingMethod( + "Lapp/revanced/integrations/patches/VideoSpeedPatch;", "" +) +@CustomPlaybackSpeedCompatibility +@Version("0.0.1") +object VideoSpeedPatchFingerprint : MethodFingerprint( + opcodes = listOf(Opcode.FILL_ARRAY_DATA), + customFingerprint = { methodDef -> + methodDef.definingClass.endsWith("VideoSpeedPatch;") && methodDef.name == "" + } +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/patch/CustomPlaybackSpeedPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/patch/CustomPlaybackSpeedPatch.kt index 09ef68f6b..75ec992aa 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/patch/CustomPlaybackSpeedPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/patch/CustomPlaybackSpeedPatch.kt @@ -6,21 +6,23 @@ import app.revanced.patcher.annotation.Version import app.revanced.patcher.data.impl.BytecodeData import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.replaceInstruction -import app.revanced.patcher.patch.PatchResult -import app.revanced.patcher.patch.PatchResultError -import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.* import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.Patch import app.revanced.patcher.patch.impl.BytecodePatch import app.revanced.patches.youtube.misc.customplaybackspeed.annotations.CustomPlaybackSpeedCompatibility import app.revanced.patches.youtube.misc.customplaybackspeed.fingerprints.SpeedArrayGeneratorFingerprint import app.revanced.patches.youtube.misc.customplaybackspeed.fingerprints.SpeedLimiterFingerprint +import app.revanced.patches.youtube.misc.customplaybackspeed.fingerprints.VideoSpeedPatchFingerprint import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import org.jf.dexlib2.builder.instruction.BuilderArrayPayload import org.jf.dexlib2.iface.instruction.NarrowLiteralInstruction import org.jf.dexlib2.iface.instruction.OneRegisterInstruction import org.jf.dexlib2.iface.instruction.ReferenceInstruction import org.jf.dexlib2.iface.reference.FieldReference import org.jf.dexlib2.iface.reference.MethodReference +import java.util.stream.DoubleStream +import kotlin.math.roundToInt @Patch @Name("custom-playback-speed") @@ -30,13 +32,17 @@ import org.jf.dexlib2.iface.reference.MethodReference @Version("0.0.1") class CustomPlaybackSpeedPatch : BytecodePatch( listOf( - SpeedArrayGeneratorFingerprint, SpeedLimiterFingerprint + SpeedArrayGeneratorFingerprint, SpeedLimiterFingerprint, VideoSpeedPatchFingerprint ) ) { override fun execute(data: BytecodeData): PatchResult { //TODO: include setting to skip remembering the new speed + val speedLimitMin = minVideoSpeed!!.toFloat() + val speedLimitMax = maxVideoSpeed!!.toFloat().coerceAtLeast(speedLimitMin) + val speedsGranularity = videoSpeedsGranularity!!.toFloat() + val arrayGenMethod = SpeedArrayGeneratorFingerprint.result?.mutableMethod!! val arrayGenMethodImpl = arrayGenMethod.implementation!! @@ -85,9 +91,6 @@ class CustomPlaybackSpeedPatch : BytecodePatch( val limiterMethod = SpeedLimiterFingerprint.result?.mutableMethod!!; val limiterMethodImpl = limiterMethod.implementation!! - val speedLimitMin = 0.25f - val speedLimitMax = 100f - val (limiterMinConstIndex, limiterMinConst) = limiterMethodImpl.instructions.withIndex() .first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == 0.25f.toRawBits() } val (limiterMaxConstIndex, limiterMaxConst) = limiterMethodImpl.instructions.withIndex() @@ -107,6 +110,84 @@ class CustomPlaybackSpeedPatch : BytecodePatch( "const/high16 v$limiterMaxConstDestination, ${hexFloat(speedLimitMax)}" ) + val constructorResult = VideoSpeedPatchFingerprint.result!! + val constructor = constructorResult.mutableMethod + val implementation = constructor.implementation!! + + val stepsGranularity = 8F + val step = speedLimitMax + .minus(speedLimitMin) // calculate the range of the speeds + .div(speedsGranularity) + .times(stepsGranularity) + .roundToInt() + .div(stepsGranularity)// round to nearest multiple of stepsGranularity + .coerceAtLeast(1 / stepsGranularity) // ensure steps are at least 1/8th of the step granularity + + val videoSpeedsArray = DoubleStream + .iterate(speedLimitMin.toDouble()) { it + step } // create a stream of speeds + .takeWhile { it <= speedLimitMax } // limit the stream to the max speed + .mapToObj { it.toFloat().toRawBits() } + .toList() as List + + // adjust the new array of speeds size + constructor.replaceInstruction( + 0, + "const/16 v0, ${videoSpeedsArray.size}" + ) + + // create the payload with the new speeds + val arrayPayloadIndex = implementation.instructions.size - 1 + implementation.replaceInstruction( + arrayPayloadIndex, + BuilderArrayPayload( + 4, + videoSpeedsArray + ) + ) + return PatchResultSuccess() } + + companion object : OptionsContainer() { + private fun String?.validate(max: Int? = null) = this?.toFloatOrNull() != null && + toFloat().let { float -> + float > 0 && max?.let { max -> float <= max } ?: true + } + + val videoSpeedsGranularity by option( + PatchOption.StringOption( + "granularity", + "16", + "Video speed granularity", + "The granularity of the video speeds. The higher the value, the more speeds will be available.", + true + ) { + it.validate() + } + ) + + val minVideoSpeed by option( + PatchOption.StringOption( + "min", + "0.25", + "Minimum video speed", + "The minimum video speed.", + true + ) { + it.validate() + } + ) + + val maxVideoSpeed by option( + PatchOption.StringOption( + "max", + "5.0", + "Maximum video speed", + "The maximum video speed. Must be greater than the minimum video speed and smaller than 5.", + true + ) { + it.validate(5) + } + ) + } }