feat(music): add enable playback speed patch

This commit is contained in:
inotia00 2023-09-05 11:46:34 +09:00
parent a25d4be63e
commit 63628d6f75
15 changed files with 295 additions and 0 deletions

View File

@ -0,0 +1,15 @@
package app.revanced.patches.music.flyoutpanel.playbackspeed.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.music.utils.resourceid.patch.SharedResourceIdPatch.Companion.MusicMenuLikeButtons
import app.revanced.util.bytecode.isWideLiteralExists
import com.android.tools.smali.dexlib2.AccessFlags
object FlyoutPanelLikeButtonFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.SYNTHETIC,
parameters = listOf("L", "Ljava/lang/Object;"),
customFingerprint = { methodDef, _ -> methodDef.isWideLiteralExists(MusicMenuLikeButtons) }
)

View File

@ -0,0 +1,72 @@
package app.revanced.patches.music.flyoutpanel.playbackspeed.patch
import app.revanced.extensions.exception
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patches.music.flyoutpanel.playbackspeed.fingerprints.FlyoutPanelLikeButtonFingerprint
import app.revanced.patches.music.utils.annotations.MusicCompatibility
import app.revanced.patches.music.utils.flyoutbuttonhook.patch.FlyoutButtonHookPatch
import app.revanced.patches.music.utils.overridespeed.patch.OverrideSpeedHookPatch
import app.revanced.patches.music.utils.resourceid.patch.SharedResourceIdPatch
import app.revanced.patches.music.utils.settings.resource.patch.SettingsPatch
import app.revanced.util.bytecode.getWideLiteralIndex
import app.revanced.util.enum.CategoryType
import app.revanced.util.integrations.Constants.MUSIC_FLYOUT
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Patch
@Name("Enable playback speed")
@Description("Add playback speed button to the flyout panel.")
@DependsOn(
[
FlyoutButtonHookPatch::class,
OverrideSpeedHookPatch::class,
SettingsPatch::class,
SharedResourceIdPatch::class
]
)
@MusicCompatibility
class PlaybackSpeedPatch : BytecodePatch(
listOf(FlyoutPanelLikeButtonFingerprint)
) {
override fun execute(context: BytecodeContext) {
FlyoutPanelLikeButtonFingerprint.result?.let {
it.mutableMethod.apply {
val targetIndex = getWideLiteralIndex(SharedResourceIdPatch.MusicMenuLikeButtons)
var insertIndex = -1
for (index in targetIndex until targetIndex + 5) {
if (getInstruction(index).opcode != Opcode.MOVE_RESULT_OBJECT) continue
val register = getInstruction<OneRegisterInstruction>(index).registerA
insertIndex = index
addInstruction(
index + 1,
"invoke-static {v$register}, $MUSIC_FLYOUT->setFlyoutButtonContainer(Landroid/view/View;)V"
)
break
}
if (insertIndex == -1)
throw PatchException("Couldn't find target Index")
}
} ?: throw FlyoutPanelLikeButtonFingerprint.exception
SettingsPatch.addMusicPreference(
CategoryType.FLYOUT,
"revanced_enable_flyout_panel_playback_speed",
"false"
)
}
}

View File

@ -0,0 +1,39 @@
package app.revanced.patches.music.utils.flyoutbuttonhook.patch
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.util.resources.ResourceUtils
import app.revanced.util.resources.ResourceUtils.copyResources
class FlyoutButtonHookPatch : ResourcePatch {
override fun execute(context: ResourceContext) {
/**
* create directory for flyout button container
*/
context["res/layout-v21"].mkdirs()
arrayOf(
ResourceUtils.ResourceGroup(
"layout-v21",
"music_menu_like_buttons.xml"
)
).forEach { resourceGroup ->
context.copyResources("music/flyout", resourceGroup)
}
fun copyResources(resourceGroups: List<ResourceUtils.ResourceGroup>) {
resourceGroups.forEach { context.copyResources("music/flyout", it) }
}
fun createGroup(directory: String) = ResourceUtils.ResourceGroup(
directory, "yt_outline_play_arrow_half_circle_black_24.png"
)
arrayOf("xxxhdpi", "xxhdpi", "xhdpi", "hdpi", "mdpi")
.map { "drawable-$it" }
.map(::createGroup)
.let(::copyResources)
}
}

View File

@ -0,0 +1,17 @@
package app.revanced.patches.music.utils.overridespeed.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
object PlaybackSpeedFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.CONST_HIGH16,
Opcode.INVOKE_VIRTUAL
)
)

View File

@ -0,0 +1,12 @@
package app.revanced.patches.music.utils.overridespeed.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
object PlaybackSpeedParentFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
parameters = emptyList(),
strings = listOf("BT metadata: %s, %s, %s")
)

View File

@ -0,0 +1,15 @@
package app.revanced.patches.music.utils.overridespeed.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
object PlaybackSpeedPatchFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("F"),
customFingerprint = { methodDef, _ ->
methodDef.definingClass == "Lapp/revanced/music/patches/misc/PlaybackSpeedPatch;"
&& methodDef.name == "overrideSpeed"
}
)

View File

@ -0,0 +1,114 @@
package app.revanced.patches.music.utils.overridespeed.patch
import app.revanced.extensions.exception
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.music.utils.overridespeed.fingerprints.PlaybackSpeedFingerprint
import app.revanced.patches.music.utils.overridespeed.fingerprints.PlaybackSpeedParentFingerprint
import app.revanced.patches.music.utils.overridespeed.fingerprints.PlaybackSpeedPatchFingerprint
import app.revanced.util.integrations.Constants.MUSIC_INTEGRATIONS_PATH
import app.revanced.util.integrations.Constants.MUSIC_MISC_PATH
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c
import com.android.tools.smali.dexlib2.dexbacked.reference.DexBackedMethodReference
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.immutable.ImmutableField
class OverrideSpeedHookPatch : BytecodePatch(
listOf(
PlaybackSpeedPatchFingerprint,
PlaybackSpeedParentFingerprint
)
) {
override fun execute(context: BytecodeContext) {
PlaybackSpeedParentFingerprint.result?.let { parentResult ->
val parentClassDef = parentResult.classDef
PlaybackSpeedFingerprint.also {
it.resolve(
context,
parentClassDef
)
}.result?.let {
it.mutableMethod.apply {
val startIndex = it.scanResult.patternScanResult!!.startIndex
val endIndex = it.scanResult.patternScanResult!!.endIndex
val speedClassRegister =
getInstruction<OneRegisterInstruction>(startIndex).registerA
val speedRegister =
getInstruction<OneRegisterInstruction>(startIndex + 1).registerA
SPEED_REFERENCE = getInstruction<BuilderInstruction35c>(endIndex).reference
SPEED_CLASS = (SPEED_REFERENCE as DexBackedMethodReference).definingClass
with(
context
.toMethodWalker(this)
.nextMethod(endIndex, true)
.getMethod() as MutableMethod
) {
addInstruction(
this.implementation!!.instructions.size - 1,
"sput p1, $INTEGRATIONS_VIDEO_HELPER_CLASS_DESCRIPTOR->currentSpeed:F"
)
}
addInstructions(
startIndex + 2, """
sput-object v$speedClassRegister, $INTEGRATIONS_PLAYBACK_SPEED_CLASS_DESCRIPTOR->speedClass:$SPEED_CLASS
invoke-static {}, $INTEGRATIONS_PLAYBACK_SPEED_CLASS_DESCRIPTOR->getPlaybackSpeed()F
move-result v$speedRegister
"""
)
}
} ?: throw PlaybackSpeedFingerprint.exception
} ?: throw PlaybackSpeedParentFingerprint.exception
PlaybackSpeedPatchFingerprint.result?.let {
it.mutableMethod.apply {
it.mutableClass.staticFields.add(
ImmutableField(
definingClass,
"speedClass",
SPEED_CLASS,
AccessFlags.PUBLIC or AccessFlags.STATIC,
null,
annotations,
null
).toMutable()
)
addInstructions(
4, """
sget-object v0, $INTEGRATIONS_PLAYBACK_SPEED_CLASS_DESCRIPTOR->speedClass:$SPEED_CLASS
invoke-virtual {v0, p0}, $SPEED_REFERENCE
"""
)
}
} ?: throw PlaybackSpeedPatchFingerprint.exception
}
internal companion object {
const val INTEGRATIONS_PLAYBACK_SPEED_CLASS_DESCRIPTOR =
"$MUSIC_MISC_PATH/PlaybackSpeedPatch;"
const val INTEGRATIONS_VIDEO_HELPER_CLASS_DESCRIPTOR =
"$MUSIC_INTEGRATIONS_PATH/utils/VideoHelpers;"
private lateinit var SPEED_CLASS: String
private lateinit var SPEED_REFERENCE: Reference
}
}

View File

@ -23,6 +23,7 @@ class SharedResourceIdPatch : ResourcePatch {
var DialogSolid: Long = -1
var DisabledIconAlpha: Long = -1
var IsTablet: Long = -1
var MusicMenuLikeButtons: Long = -1
var MusicNotifierShelf: Long = -1
var PrivacyTosFooter: Long = -1
var QualityTitle: Long = -1
@ -43,6 +44,7 @@ class SharedResourceIdPatch : ResourcePatch {
DialogSolid = find(STYLE, "Theme.YouTubeMusic.Dialog.Solid")
DisabledIconAlpha = find(DIMEN, "disabled_icon_alpha")
IsTablet = find(BOOL, "is_tablet")
MusicMenuLikeButtons = find(LAYOUT, "music_menu_like_buttons")
MusicNotifierShelf = find(LAYOUT, "music_notifier_shelf")
PrivacyTosFooter = find(ID, "privacy_tos_footer")
QualityTitle = find(STRING, "quality_title")

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android">
<com.google.android.libraries.youtube.common.ui.TouchImageView android:id="@+id/playback_speed_button" android:padding="@dimen/remix_overlay_player_control_button_padding" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/yt_outline_play_arrow_half_circle_black_24" android:tint="@color/ytm_icon_color_active" android:contentDescription="@string/action_add_to_offline_songs" style="@style/MusicPlayerButton" />
<com.google.android.libraries.youtube.common.ui.TouchImageView android:id="@id/dislike_button" android:padding="@dimen/remix_overlay_player_control_button_padding" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ytdislike_drawable" android:tint="@color/ytm_icon_color_active" android:contentDescription="@string/accessibility_dislike_video" style="@style/MusicPlayerButton" />
<com.google.android.libraries.youtube.common.ui.TouchImageView android:id="@id/like_button" android:padding="@dimen/remix_overlay_player_control_button_padding" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ytlike_drawable" android:tint="@color/ytm_icon_color_active" android:contentDescription="@string/accessibility_like_video" style="@style/MusicPlayerButton" />
</LinearLayout>

View File

@ -22,6 +22,8 @@
<string name="revanced_enable_compact_dialog_title">Enable compact dialog</string>
<string name="revanced_enable_debug_logging_summary">Prints the debug log</string>
<string name="revanced_enable_debug_logging_title">Enable debug logging</string>
<string name="revanced_enable_flyout_panel_playback_speed_summary">Add an playback speed button to the flyout panel.</string>
<string name="revanced_enable_flyout_panel_playback_speed_title">Enable playback speed</string>
<string name="revanced_enable_force_minimized_player_summary">Keep player permanently minimized even if another track is played.</string>
<string name="revanced_enable_force_minimized_player_title">Enable force minimized player</string>
<string name="revanced_enable_force_shuffle_summary">Enable force shuffle even if another track is played.</string>