fix(YouTube): Show video chapter titles without clipping when overlay buttons are enabled (#3674)

This commit is contained in:
LisoUseInAIKyrios 2024-09-23 18:50:16 -04:00 committed by GitHub
parent f27cbece71
commit 4b88c316ed
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 537 additions and 273 deletions

View File

@ -2000,13 +2000,19 @@ public final class app/revanced/patches/youtube/misc/playercontrols/BottomContro
public final class app/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch : app/revanced/patcher/patch/BytecodePatch { public final class app/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch; public static final field INSTANCE Lapp/revanced/patches/youtube/misc/playercontrols/PlayerControlsBytecodePatch;
public static field showPlayerControlsFingerprintResult Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
public final fun getShowPlayerControlsFingerprintResult ()Lapp/revanced/patcher/fingerprint/MethodFingerprintResult; public final fun initializeBottomControl (Ljava/lang/String;)V
public final fun initializeControl (Ljava/lang/String;)V public final fun initializeControl (Ljava/lang/String;)V
public final fun injectVisibilityCheckCall (Ljava/lang/String;)V public final fun injectVisibilityCheckCall (Ljava/lang/String;)V
public final fun setShowPlayerControlsFingerprintResult (Lapp/revanced/patcher/fingerprint/MethodFingerprintResult;)V }
public final class app/revanced/patches/youtube/misc/playercontrols/PlayerControlsResourcePatch : app/revanced/patcher/patch/ResourcePatch, java/io/Closeable {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/playercontrols/PlayerControlsResourcePatch;
public final fun addBottomControls (Ljava/lang/String;)V
public fun close ()V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
} }
public final class app/revanced/patches/youtube/misc/playeroverlay/PlayerOverlaysHookPatch : app/revanced/patcher/patch/BytecodePatch { public final class app/revanced/patches/youtube/misc/playeroverlay/PlayerOverlaysHookPatch : app/revanced/patcher/patch/BytecodePatch {
@ -2174,6 +2180,8 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I public static synthetic fun indexOfFirstInstructionOrThrow$default (Lcom/android/tools/smali/dexlib2/iface/Method;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)I
public static final fun indexOfFirstWideLiteralInstructionValue (Lcom/android/tools/smali/dexlib2/iface/Method;J)I public static final fun indexOfFirstWideLiteralInstructionValue (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
public static final fun indexOfFirstWideLiteralInstructionValueOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I public static final fun indexOfFirstWideLiteralInstructionValueOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
public static final fun indexOfFirstWideLiteralInstructionValueReversed (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
public static final fun indexOfFirstWideLiteralInstructionValueReversedOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;J)I
public static final fun indexOfIdResource (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I public static final fun indexOfIdResource (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
public static final fun indexOfIdResourceOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I public static final fun indexOfIdResourceOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I
public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V
@ -2201,6 +2209,7 @@ public final class app/revanced/util/ResourceUtilsKt {
public static final fun copyXmlNode (Ljava/lang/String;Lapp/revanced/patcher/util/DomFileEditor;Lapp/revanced/patcher/util/DomFileEditor;)Ljava/lang/AutoCloseable; public static final fun copyXmlNode (Ljava/lang/String;Lapp/revanced/patcher/util/DomFileEditor;Lapp/revanced/patcher/util/DomFileEditor;)Ljava/lang/AutoCloseable;
public static final fun doRecursively (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V public static final fun doRecursively (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V
public static final fun forEachChildElement (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V public static final fun forEachChildElement (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V
public static final fun insertFirst (Lorg/w3c/dom/Node;Lorg/w3c/dom/Node;)V
public static final fun iterateXmlNodeChildren (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V public static final fun iterateXmlNodeChildren (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
} }

View File

@ -9,6 +9,7 @@ import app.revanced.patches.shared.misc.settings.preference.IntentPreference
import app.revanced.util.ResourceGroup import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources import app.revanced.util.copyResources
import app.revanced.util.getNode import app.revanced.util.getNode
import app.revanced.util.insertFirst
import org.w3c.dom.Node import org.w3c.dom.Node
import java.io.Closeable import java.io.Closeable
@ -47,11 +48,7 @@ abstract class BaseSettingsResourcePatch(
// It may be necessary to ask for the desired resourceValue in the future. // It may be necessary to ask for the desired resourceValue in the future.
AddResourcesPatch("values", resource) AddResourcesPatch("values", resource)
}.let { preferenceNode -> }.let { preferenceNode ->
if (prepend && firstChild != null) { insertFirst(preferenceNode)
insertBefore(preferenceNode, firstChild)
} else {
appendChild(preferenceNode)
}
} }
} }

View File

@ -51,8 +51,8 @@ object CopyVideoUrlBytecodePatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
BUTTONS_DESCRIPTORS.forEach { descriptor -> BUTTONS_DESCRIPTORS.forEach { descriptor ->
PlayerControlsBytecodePatch.initializeControl("$descriptor->initializeButton(Landroid/view/View;)V") PlayerControlsBytecodePatch.initializeBottomControl(descriptor)
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$descriptor->changeVisibility(Z)V") PlayerControlsBytecodePatch.injectVisibilityCheckCall(descriptor)
} }
} }
} }

View File

@ -5,7 +5,7 @@ import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.all.misc.resources.AddResourcesPatch import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.util.ResourceGroup import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources import app.revanced.util.copyResources
@ -13,7 +13,7 @@ import app.revanced.util.copyResources
@Patch( @Patch(
dependencies = [ dependencies = [
SettingsPatch::class, SettingsPatch::class,
BottomControlsResourcePatch::class, PlayerControlsResourcePatch::class,
AddResourcesPatch::class AddResourcesPatch::class
] ]
) )
@ -34,6 +34,6 @@ internal object CopyVideoUrlResourcePatch : ResourcePatch() {
) )
) )
BottomControlsResourcePatch.addControls("copyvideourl") PlayerControlsResourcePatch.addBottomControls("copyvideourl")
} }
} }

View File

@ -58,8 +58,8 @@ object DownloadsPatch : BytecodePatch(
private const val BUTTON_DESCRIPTOR = "Lapp/revanced/integrations/youtube/videoplayer/ExternalDownloadButton;" private const val BUTTON_DESCRIPTOR = "Lapp/revanced/integrations/youtube/videoplayer/ExternalDownloadButton;"
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
PlayerControlsBytecodePatch.initializeControl("$BUTTON_DESCRIPTOR->initializeButton(Landroid/view/View;)V") PlayerControlsBytecodePatch.initializeBottomControl(BUTTON_DESCRIPTOR)
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$BUTTON_DESCRIPTOR->changeVisibility(Z)V") PlayerControlsBytecodePatch.injectVisibilityCheckCall(BUTTON_DESCRIPTOR)
// Main activity is used to launch downloader intent. // Main activity is used to launch downloader intent.
MainActivityFingerprint.resultOrThrow().mutableMethod.apply { MainActivityFingerprint.resultOrThrow().mutableMethod.apply {

View File

@ -9,14 +9,14 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.settings.preference.TextPreference import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.util.ResourceGroup import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources import app.revanced.util.copyResources
@Patch( @Patch(
dependencies = [ dependencies = [
BottomControlsResourcePatch::class, PlayerControlsResourcePatch::class,
SettingsPatch::class, SettingsPatch::class,
AddResourcesPatch::class, AddResourcesPatch::class,
], ],
@ -42,6 +42,6 @@ internal object DownloadsResourcePatch : ResourcePatch() {
ResourceGroup("drawable", "revanced_yt_download_button.xml"), ResourceGroup("drawable", "revanced_yt_download_button.xml"),
) )
BottomControlsResourcePatch.addControls("downloads") PlayerControlsResourcePatch.addBottomControls("downloads")
} }
} }

View File

@ -10,7 +10,6 @@ import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.AppendTimeFingerprint import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.AppendTimeFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.ControlsOverlayFingerprint import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.ControlsOverlayFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.RectangleFieldInvalidatorFingerprint import app.revanced.patches.youtube.layout.sponsorblock.fingerprints.RectangleFieldInvalidatorFingerprint
@ -26,7 +25,10 @@ import app.revanced.patches.youtube.video.information.VideoInformationPatch
import app.revanced.patches.youtube.video.videoid.VideoIdPatch import app.revanced.patches.youtube.video.videoid.VideoIdPatch
import app.revanced.util.exception import app.revanced.util.exception
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.* import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
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.formats.Instruction35c import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
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
@ -169,59 +171,14 @@ object SponsorBlockBytecodePatch : BytecodePatch(
break break
} }
/* // Change visibility of the buttons.
* Voting & Shield button PlayerControlsBytecodePatch.initializeTopControl(INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
*/ PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
val controlsMethodResult = PlayerControlsBytecodePatch.showPlayerControlsFingerprintResult
val controlsLayoutStubResourceId = PlayerControlsBytecodePatch.initializeTopControl(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
ResourceMappingPatch["id", "controls_layout_stub"] PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
val zoomOverlayResourceId =
ResourceMappingPatch["id", "video_zoom_overlay_stub"]
methods@ for (method in controlsMethodResult.mutableClass.methods) { // Append the new time to the player layout.
val instructions = method.implementation?.instructions!!
instructions@ for ((index, instruction) in instructions.withIndex()) {
// search for method which inflates the controls layout view
if (instruction.opcode != Opcode.CONST) continue@instructions
when ((instruction as NarrowLiteralInstruction).wideLiteral) {
controlsLayoutStubResourceId -> {
// replace the view with the YouTubeControlsOverlay
val moveResultInstructionIndex = index + 5
val inflatedViewRegister =
(instructions[moveResultInstructionIndex] as OneRegisterInstruction).registerA
// initialize with the player overlay object
method.addInstructions(
moveResultInstructionIndex + 1, // insert right after moving the view to the register and use that register
"""
invoke-static {v$inflatedViewRegister}, $INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->initialize(Landroid/view/View;)V
invoke-static {v$inflatedViewRegister}, $INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->initialize(Landroid/view/View;)V
""",
)
}
zoomOverlayResourceId -> {
val invertVisibilityMethod =
context.toMethodWalker(method).nextMethod(index - 6, true).getMethod() as MutableMethod
// change visibility of the buttons
invertVisibilityMethod.addInstructions(
0,
"""
invoke-static {p1}, $INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibilityNegatedImmediate(Z)V
invoke-static {p1}, $INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibilityNegatedImmediate(Z)V
""".trimIndent(),
)
}
}
}
}
// change visibility of the buttons
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibility(Z)V")
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR->changeVisibility(Z)V")
// append the new time to the player layout
val appendTimeFingerprintResult = AppendTimeFingerprint.result!! val appendTimeFingerprintResult = AppendTimeFingerprint.result!!
val appendTimePatternScanStartIndex = appendTimeFingerprintResult.scanResult.patternScanResult!!.startIndex val appendTimePatternScanStartIndex = appendTimeFingerprintResult.scanResult.patternScanResult!!.startIndex
val targetRegister = val targetRegister =

View File

@ -1,18 +1,16 @@
package app.revanced.patches.youtube.layout.sponsorblock package app.revanced.patches.youtube.layout.sponsorblock
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.all.misc.resources.AddResourcesPatch import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.IntentPreference import app.revanced.patches.shared.misc.settings.preference.IntentPreference
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.patches.youtube.misc.settings.SettingsResourcePatch import app.revanced.patches.youtube.misc.settings.SettingsResourcePatch
import app.revanced.util.ResourceGroup import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources import app.revanced.util.copyResources
import app.revanced.util.copyXmlNode
import app.revanced.util.inputStreamFromBundledResource
@Patch( @Patch(
dependencies = [ dependencies = [
@ -60,49 +58,6 @@ internal object SponsorBlockResourcePatch : ResourcePatch() {
context.copyResources("sponsorblock", resourceGroup) context.copyResources("sponsorblock", resourceGroup)
} }
// copy nodes from host resources to their real xml files PlayerControlsResourcePatch.addTopControls("sponsorblock")
val hostingResourceStream =
inputStreamFromBundledResource(
"sponsorblock",
"host/layout/youtube_controls_layout.xml",
)!!
var modifiedControlsLayout = false
val editor = context.xmlEditor["res/layout/youtube_controls_layout.xml"]
"RelativeLayout".copyXmlNode(
context.xmlEditor[hostingResourceStream],
editor,
).also {
val document = editor.file
val children = document.getElementsByTagName("RelativeLayout").item(0).childNodes
// Replace the startOf with the voting button view so that the button does not overlap
for (i in 1 until children.length) {
val view = children.item(i)
// Replace the attribute for a specific node only
if (!(
view.hasAttributes() &&
view.attributes.getNamedItem(
"android:id",
).nodeValue.endsWith("live_chat_overlay_button")
)
) {
continue
}
// voting button id from the voting button view from the youtube_controls_layout.xml host file
val votingButtonId = "@+id/revanced_sb_voting_button"
view.attributes.getNamedItem("android:layout_toStartOf").nodeValue = votingButtonId
modifiedControlsLayout = true
break
}
}.close()
if (!modifiedControlsLayout) throw PatchException("Could not modify controls layout")
} }
} }

View File

@ -3,70 +3,18 @@ package app.revanced.patches.youtube.misc.playercontrols
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.DomFileEditor
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import java.io.Closeable import java.io.Closeable
@Patch(dependencies = [ResourceMappingPatch::class]) @Patch(
dependencies = [PlayerControlsBytecodePatch::class],
)
@Deprecated("Patch renamed to PlayerControlsResourcePatch", replaceWith = ReplaceWith("PlayerControlsBytecodePatch"))
object BottomControlsResourcePatch : ResourcePatch(), Closeable { object BottomControlsResourcePatch : ResourcePatch(), Closeable {
internal var bottomUiContainerResourceId: Long = -1 override fun execute(context: ResourceContext) {}
private const val TARGET_RESOURCE_NAME = "youtube_controls_bottom_ui_container.xml"
private const val TARGET_RESOURCE = "res/layout/$TARGET_RESOURCE_NAME"
// The element to the left of the element being added.
private var lastLeftOf = "fullscreen_button"
private lateinit var resourceContext: ResourceContext
private lateinit var targetDocumentEditor: DomFileEditor
override fun execute(context: ResourceContext) {
resourceContext = context
targetDocumentEditor = context.xmlEditor[TARGET_RESOURCE]
bottomUiContainerResourceId = ResourceMappingPatch["id", "bottom_ui_container_stub"]
}
/**
* Add new controls to the bottom of the YouTube player.
*
* @param resourceDirectoryName The name of the directory containing the hosting resource.
*/
fun addControls(resourceDirectoryName: String) { fun addControls(resourceDirectoryName: String) {
val sourceDocumentEditor = resourceContext.xmlEditor[ PlayerControlsResourcePatch.addBottomControls(resourceDirectoryName)
this::class.java.classLoader.getResourceAsStream(
"$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME",
)!!,
]
val sourceDocument = sourceDocumentEditor.file
val targetDocument = targetDocumentEditor.file
val targetElementTag = "android.support.constraint.ConstraintLayout"
val sourceElements = sourceDocument.getElementsByTagName(targetElementTag).item(0).childNodes
val targetElement = targetDocument.getElementsByTagName(targetElementTag).item(0)
for (index in 1 until sourceElements.length) {
val element = sourceElements.item(index).cloneNode(true)
// If the element has no attributes there's no point to adding it to the destination.
if (!element.hasAttributes()) continue
// Set the elements lastLeftOf attribute to the lastLeftOf value.
val namespace = "@+id"
element.attributes.getNamedItem("yt:layout_constraintRight_toLeftOf").nodeValue =
"$namespace/$lastLeftOf"
// Set lastLeftOf attribute to the current element.
val nameSpaceLength = 5
lastLeftOf = element.attributes.getNamedItem("android:id").nodeValue.substring(nameSpaceLength)
// Add the element.
targetDocument.adoptNode(element)
targetElement.appendChild(element)
}
sourceDocumentEditor.close()
} }
override fun close() = targetDocumentEditor.close() override fun close() {}
} }

View File

@ -1,65 +1,144 @@
package app.revanced.patches.youtube.misc.playercontrols package app.revanced.patches.youtube.misc.playercontrols
import app.revanced.util.exception
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.fingerprint.MethodFingerprintResult import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.youtube.shared.fingerprints.LayoutConstructorFingerprint import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.BottomControlsInflateFingerprint import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerControlsVisibilityFingerprint import app.revanced.patches.youtube.misc.playercontrols.fingerprints.ControlsOverlayVisibility
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.OverlayViewInflateFingerprint
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerBottomControlsInflateFingerprint
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerControlsIntegrationHookFingerprint
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerTopControlsInflateFingerprint
import app.revanced.util.alsoResolve
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstWideLiteralInstructionValueReversedOrThrow
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.Opcode
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.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
@Patch( @Patch(
description = "Manages the code for the player controls of the YouTube player.", description = "Manages the code for the player controls of the YouTube player.",
dependencies = [BottomControlsResourcePatch::class], dependencies = [PlayerControlsResourcePatch::class],
) )
object PlayerControlsBytecodePatch : BytecodePatch( object PlayerControlsBytecodePatch : BytecodePatch(
setOf(LayoutConstructorFingerprint, BottomControlsInflateFingerprint) setOf(
PlayerTopControlsInflateFingerprint,
PlayerBottomControlsInflateFingerprint,
OverlayViewInflateFingerprint,
PlayerControlsIntegrationHookFingerprint
)
) { ) {
lateinit var showPlayerControlsFingerprintResult: MethodFingerprintResult private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/youtube/patches/PlayerControlsPatch;"
private var moveToRegisterInstructionIndex: Int = 0 private lateinit var inflateTopControlMethod: MutableMethod
private var viewRegister: Int = 0 private var inflateTopControlInsertIndex: Int = -1
private lateinit var inflateFingerprintResult: MethodFingerprintResult private var inflateTopControlRegister: Int = -1
private lateinit var inflateBottomControlMethod: MutableMethod
private var inflateBottomControlInsertIndex: Int = -1
private var inflateBottomControlRegister: Int = -1
private lateinit var visibilityMethod: MutableMethod
private var visibilityInsertIndex: Int = 0
private lateinit var visibilityImmediateMethod: MutableMethod
private var visibilityImmediateInsertIndex: Int = 0
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
LayoutConstructorFingerprint.result?.let { fun MutableMethod.indexOfFirstViewInflateOrThrow() =
if (!PlayerControlsVisibilityFingerprint.resolve(context, it.classDef)) indexOfFirstInstructionOrThrow {
throw LayoutConstructorFingerprint.exception val reference = getReference<MethodReference>()
} ?: throw LayoutConstructorFingerprint.exception reference?.definingClass == "Landroid/view/ViewStub;" &&
reference.name == "inflate"
}
showPlayerControlsFingerprintResult = PlayerControlsVisibilityFingerprint.result!! PlayerBottomControlsInflateFingerprint.resultOrThrow().mutableMethod.apply{
inflateBottomControlMethod = this
inflateFingerprintResult = BottomControlsInflateFingerprint.result!!.also { val inflateReturnObjectIndex = indexOfFirstViewInflateOrThrow() + 1
moveToRegisterInstructionIndex = it.scanResult.patternScanResult!!.endIndex inflateBottomControlRegister = getInstruction<OneRegisterInstruction>(inflateReturnObjectIndex).registerA
viewRegister = inflateBottomControlInsertIndex = inflateReturnObjectIndex + 1
(it.mutableMethod.implementation!!.instructions[moveToRegisterInstructionIndex] as OneRegisterInstruction).registerA
} }
PlayerTopControlsInflateFingerprint.resultOrThrow().mutableMethod.apply {
inflateTopControlMethod = this
val inflateReturnObjectIndex = indexOfFirstViewInflateOrThrow() + 1
inflateTopControlRegister = getInstruction<OneRegisterInstruction>(inflateReturnObjectIndex).registerA
inflateTopControlInsertIndex = inflateReturnObjectIndex + 1
}
ControlsOverlayVisibility.alsoResolve(
context, PlayerTopControlsInflateFingerprint
).mutableMethod.apply {
visibilityMethod = this
}
// Hook the fullscreen close button. Used to fix visibility
// when seeking and other situations.
OverlayViewInflateFingerprint.resultOrThrow().mutableMethod.apply {
val resourceIndex = indexOfFirstWideLiteralInstructionValueReversedOrThrow(
PlayerControlsResourcePatch.fullscreenButton
)
val index = indexOfFirstInstructionOrThrow(resourceIndex) {
opcode == Opcode.CHECK_CAST && getReference<TypeReference>()?.type ==
"Landroid/widget/ImageView;"
}
val register = getInstruction<OneRegisterInstruction>(index).registerA
addInstruction(index + 1, "invoke-static { v$register }, " +
"$INTEGRATIONS_CLASS_DESCRIPTOR->setFullscreenCloseButton(Landroid/widget/ImageView;)V")
}
visibilityImmediateMethod = PlayerControlsIntegrationHookFingerprint.resultOrThrow().mutableMethod
} }
/** /**
* Injects the code to change the visibility of controls. * Injects the code to initialize the controls.
* @param descriptor The descriptor of the method which should be called. * @param descriptor The descriptor of the method which should be called.
*/ */
fun injectVisibilityCheckCall(descriptor: String) { internal fun initializeTopControl(descriptor: String) {
showPlayerControlsFingerprintResult.mutableMethod.addInstruction( inflateTopControlMethod.addInstruction(
0, inflateTopControlInsertIndex++,
""" "invoke-static { v$inflateTopControlRegister }, $descriptor->initialize(Landroid/view/View;)V"
invoke-static {p1}, $descriptor
"""
) )
} }
/** /**
* Injects the code to initialize the controls. * Injects the code to initialize the controls.
* @param descriptor The descriptor of the method which should be calleed. * @param descriptor The descriptor of the method which should be called.
*/ */
fun initializeControl(descriptor: String) { fun initializeBottomControl(descriptor: String) {
inflateFingerprintResult.mutableMethod.addInstruction( inflateBottomControlMethod.addInstruction(
moveToRegisterInstructionIndex + 1, inflateBottomControlInsertIndex++,
"invoke-static {v$viewRegister}, $descriptor" "invoke-static { v$inflateBottomControlRegister }, $descriptor->initializeButton(Landroid/view/View;)V"
) )
} }
/**
* Injects the code to change the visibility of controls.
* @param descriptor The descriptor of the method which should be called.
*/
fun injectVisibilityCheckCall(descriptor: String) {
visibilityMethod.addInstruction(
visibilityInsertIndex++,
"invoke-static { p1 , p2 }, $descriptor->changeVisibility(ZZ)V"
)
visibilityImmediateMethod.addInstruction(
visibilityImmediateInsertIndex++,
"invoke-static { p0 }, $descriptor->changeVisibilityImmediate(Z)V"
)
}
@Deprecated("Obsolete", replaceWith = ReplaceWith("initializeBottomControl"))
fun initializeControl(descriptor: String)= initializeBottomControl(descriptor)
} }

View File

@ -0,0 +1,146 @@
package app.revanced.patches.youtube.misc.playercontrols
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.DomFileEditor
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import app.revanced.util.copyXmlNode
import app.revanced.util.findElementByAttributeValue
import app.revanced.util.findElementByAttributeValueOrThrow
import app.revanced.util.inputStreamFromBundledResource
import org.w3c.dom.Node
import java.io.Closeable
@Patch(dependencies = [ResourceMappingPatch::class])
object PlayerControlsResourcePatch : ResourcePatch(), Closeable {
private const val TARGET_RESOURCE_NAME = "youtube_controls_bottom_ui_container.xml"
private const val TARGET_RESOURCE = "res/layout/$TARGET_RESOURCE_NAME"
internal var bottomUiContainerResourceId: Long = -1L
internal var controlsLayoutStub: Long = -1L
internal var heatseekerViewstub = -1L
internal var fullscreenButton = -1L
private lateinit var resourceContext: ResourceContext
/**
* The element to the left of the element being added.
*/
private var bottomLastLeftOf = "@id/fullscreen_button"
private lateinit var bottomInsertBeforeNode: Node
private lateinit var bottomTargetDocumentEditor: DomFileEditor
private lateinit var bottomTargetElement : Node
override fun execute(context: ResourceContext) {
bottomUiContainerResourceId = ResourceMappingPatch["id", "bottom_ui_container_stub"]
controlsLayoutStub = ResourceMappingPatch["id", "controls_layout_stub"]
heatseekerViewstub = ResourceMappingPatch["id", "heatseeker_viewstub"]
fullscreenButton = ResourceMappingPatch["id", "fullscreen_button"]
resourceContext = context
bottomTargetDocumentEditor = context.xmlEditor[TARGET_RESOURCE]
val document = bottomTargetDocumentEditor.file
bottomTargetElement = document.getElementsByTagName(
"android.support.constraint.ConstraintLayout"
).item(0)
bottomInsertBeforeNode = document.childNodes.findElementByAttributeValue(
"android:inflatedId",
bottomLastLeftOf
) ?: document.childNodes.findElementByAttributeValueOrThrow(
"android:id", // Older targets use non inflated id.
bottomLastLeftOf
)
}
// Internal until this is modified to work with any patch (and not just SponsorBlock).
internal fun addTopControls(resourceDirectoryName: String) {
val hostingResourceStream = inputStreamFromBundledResource(
resourceDirectoryName,
"host/layout/youtube_controls_layout.xml",
)!!
val editor = resourceContext.xmlEditor["res/layout/youtube_controls_layout.xml"]
"RelativeLayout".copyXmlNode(
resourceContext.xmlEditor[hostingResourceStream],
editor,
).use {
val document = editor.file
val children = document.getElementsByTagName("RelativeLayout").item(0).childNodes
// Replace the startOf with the voting button view so that the button does not overlap
for (index in 1 until children.length) {
val view = children.item(index)
// FIXME: This uses hard coded values that only works with SponsorBlock.
// If other top buttons are added by other patches, this code must be changed.
if (view.hasAttributes() && view.attributes.getNamedItem("android:id")
.nodeValue.endsWith("live_chat_overlay_button")
) {
// voting button id from the voting button view from the youtube_controls_layout.xml host file
val votingButtonId = "@+id/revanced_sb_voting_button"
view.attributes.getNamedItem("android:layout_toStartOf").nodeValue =
votingButtonId
return
}
}
}
throw PatchException("Could not find expected xml to modify")
}
/**
* Add new controls to the bottom of the YouTube player.
*
* @param resourceDirectoryName The name of the directory containing the hosting resource.
*/
fun addBottomControls(resourceDirectoryName: String) {
val sourceDocumentEditor = resourceContext.xmlEditor[
this::class.java.classLoader.getResourceAsStream(
"$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME",
)!!,
]
val sourceElements = sourceDocumentEditor.file.getElementsByTagName(
"android.support.constraint.ConstraintLayout"
).item(0).childNodes
// Copy the patch layout xml into the target layout file.
for (index in 1 until sourceElements.length) {
val element = sourceElements.item(index).cloneNode(true)
// If the element has no attributes there's no point to adding it to the destination.
if (!element.hasAttributes()) continue
element.attributes.getNamedItem("yt:layout_constraintRight_toLeftOf").nodeValue = bottomLastLeftOf
bottomLastLeftOf = element.attributes.getNamedItem("android:id").nodeValue
bottomTargetDocumentEditor.file.adoptNode(element)
// Elements do not need to be added in the layout order since a layout constraint is used,
// but in order is easier to make sense of while debugging.
bottomTargetElement.insertBefore(element, bottomInsertBeforeNode)
bottomInsertBeforeNode = element
}
sourceDocumentEditor.close()
}
override fun close() {
arrayOf(
"@id/bottom_end_container",
"@id/multiview_button",
).forEach {
bottomTargetDocumentEditor.file.childNodes.findElementByAttributeValue(
"android:id",
it
)?.setAttribute("yt:layout_constraintRight_toLeftOf", bottomLastLeftOf)
}
bottomTargetDocumentEditor.close()
}
}

View File

@ -1,19 +0,0 @@
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal object BottomControlsInflateFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.SYNTHETIC,
returnType = "L",
parameters = listOf(),
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT
),
literalSupplier = { BottomControlsResourcePatch.bottomUiContainerResourceId }
)

View File

@ -4,7 +4,10 @@ import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
internal object PlayerControlsVisibilityFingerprint : MethodFingerprint( /**
* Resolves to the class found in [PlayerTopControlsInflateFingerprint].
*/
internal object ControlsOverlayVisibility : MethodFingerprint(
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
returnType = "V", returnType = "V",
parameters = listOf("Z", "Z") parameters = listOf("Z", "Z")

View File

@ -0,0 +1,17 @@
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.util.containsWideLiteralInstructionValue
import com.android.tools.smali.dexlib2.AccessFlags
internal object OverlayViewInflateFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "V",
parameters = listOf("Landroid/view/View;"),
customFingerprint = { methodDef, _ ->
methodDef.containsWideLiteralInstructionValue(PlayerControlsResourcePatch.fullscreenButton) &&
methodDef.containsWideLiteralInstructionValue(PlayerControlsResourcePatch.heatseekerViewstub)
}
)

View File

@ -0,0 +1,10 @@
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint
internal object PlayerBottomControlsInflateFingerprint : LiteralValueFingerprint(
returnType = "Ljava/lang/Object;",
parameters = listOf(),
literalSupplier = { PlayerControlsResourcePatch.bottomUiContainerResourceId }
)

View File

@ -0,0 +1,15 @@
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object PlayerControlsIntegrationHookFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
returnType = "V",
parameters = listOf("Z"),
customFingerprint = { methodDef, classDef ->
methodDef.name == "fullscreenButtonVisibilityChanged" &&
classDef.type == "Lapp/revanced/integrations/youtube/patches/PlayerControlsPatch;"
}
)

View File

@ -0,0 +1,13 @@
package app.revanced.patches.youtube.misc.playercontrols.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal object PlayerTopControlsInflateFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "V",
parameters = listOf(),
literalSupplier = { PlayerControlsResourcePatch.controlsLayoutStub }
)

View File

@ -32,7 +32,7 @@ object PlaybackSpeedButtonPatch : BytecodePatch(emptySet()) {
SwitchPreference("revanced_playback_speed_dialog_button"), SwitchPreference("revanced_playback_speed_dialog_button"),
) )
PlayerControlsBytecodePatch.initializeControl("$SPEED_BUTTON_CLASS_DESCRIPTOR->initializeButton(Landroid/view/View;)V") PlayerControlsBytecodePatch.initializeBottomControl(SPEED_BUTTON_CLASS_DESCRIPTOR)
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$SPEED_BUTTON_CLASS_DESCRIPTOR->changeVisibility(Z)V") PlayerControlsBytecodePatch.injectVisibilityCheckCall(SPEED_BUTTON_CLASS_DESCRIPTOR)
} }
} }

View File

@ -3,12 +3,12 @@ package app.revanced.patches.youtube.video.speed.button
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.youtube.misc.playercontrols.BottomControlsResourcePatch import app.revanced.patches.youtube.misc.playercontrols.PlayerControlsResourcePatch
import app.revanced.util.ResourceGroup import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources import app.revanced.util.copyResources
@Patch( @Patch(
dependencies = [BottomControlsResourcePatch::class], dependencies = [PlayerControlsResourcePatch::class],
) )
internal object PlaybackSpeedButtonResourcePatch : ResourcePatch() { internal object PlaybackSpeedButtonResourcePatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
@ -20,6 +20,6 @@ internal object PlaybackSpeedButtonResourcePatch : ResourcePatch() {
), ),
) )
BottomControlsResourcePatch.addControls("speedbutton") PlayerControlsResourcePatch.addBottomControls("speedbutton")
} }
} }

View File

@ -15,6 +15,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
import com.android.tools.smali.dexlib2.iface.reference.Reference import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.util.MethodUtil import com.android.tools.smali.dexlib2.util.MethodUtil
import org.stringtemplate.v4.compiler.Bytecode.instructions
fun MethodFingerprint.resultOrThrow() = result ?: throw exception fun MethodFingerprint.resultOrThrow() = result ?: throw exception
@ -73,7 +74,7 @@ fun MutableMethod.injectHideViewCall(
* @param resourceName the name of the resource to find the id for. * @param resourceName the name of the resource to find the id for.
* @return the index of the first instruction with the id of the given resource name, or -1 if not found. * @return the index of the first instruction with the id of the given resource name, or -1 if not found.
* @throws PatchException if the resource cannot be found. * @throws PatchException if the resource cannot be found.
* @see [indexOfIdResourceOrThrow] * @see [indexOfIdResourceOrThrow], [indexOfFirstWideLiteralInstructionValueReversed]
*/ */
fun Method.indexOfIdResource(resourceName: String): Int { fun Method.indexOfIdResource(resourceName: String): Int {
val resourceId = ResourceMappingPatch["id", resourceName] val resourceId = ResourceMappingPatch["id", resourceName]
@ -86,6 +87,7 @@ fun Method.indexOfIdResource(resourceName: String): Int {
* Requires [ResourceMappingPatch] as a dependency. * Requires [ResourceMappingPatch] as a dependency.
* *
* @throws [PatchException] if the resource is not found, or the method does not contain the resource id literal value. * @throws [PatchException] if the resource is not found, or the method does not contain the resource id literal value.
* @see [indexOfIdResource], [indexOfFirstWideLiteralInstructionValueReversedOrThrow]
*/ */
fun Method.indexOfIdResourceOrThrow(resourceName: String): Int { fun Method.indexOfIdResourceOrThrow(resourceName: String): Int {
val index = indexOfIdResource(resourceName) val index = indexOfIdResource(resourceName)
@ -120,6 +122,30 @@ fun Method.indexOfFirstWideLiteralInstructionValueOrThrow(literal: Long): Int {
return index return index
} }
/**
* Find the index of the last wide literal instruction with the given value.
*
* @return the last literal instruction with the value, or -1 if not found.
* @see indexOfFirstWideLiteralInstructionValueOrThrow
*/
fun Method.indexOfFirstWideLiteralInstructionValueReversed(literal: Long) = implementation?.let {
it.instructions.indexOfLast { instruction ->
(instruction as? WideLiteralInstruction)?.wideLiteral == literal
}
} ?: -1
/**
* Find the index of the last wide literal instruction with the given value,
* or throw an exception if not found.
*
* @return the last literal instruction with the value, or throws [PatchException] if not found.
*/
fun Method.indexOfFirstWideLiteralInstructionValueReversedOrThrow(literal: Long): Int {
val index = indexOfFirstWideLiteralInstructionValueReversed(literal)
if (index < 0) throw PatchException("Could not find literal value: $literal")
return index
}
/** /**
* Check if the method contains a literal with the given value. * Check if the method contains a literal with the given value.
* *

View File

@ -1,8 +1,11 @@
package app.revanced.util package app.revanced.util
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.util.DomFileEditor import app.revanced.patcher.util.DomFileEditor
import app.revanced.util.resource.BaseResource import app.revanced.util.resource.BaseResource
import org.w3c.dom.Attr
import org.w3c.dom.Element
import org.w3c.dom.Node import org.w3c.dom.Node
import org.w3c.dom.NodeList import org.w3c.dom.NodeList
import java.io.InputStream import java.io.InputStream
@ -39,6 +42,14 @@ fun Node.doRecursively(action: (Node) -> Unit) {
for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action) for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action)
} }
fun Node.insertFirst(node: Node) {
if (hasChildNodes()) {
insertBefore(node, firstChild)
} else {
appendChild(node)
}
}
/** /**
* Copy resources from the current class loader to the resource directory. * Copy resources from the current class loader to the resource directory.
* *
@ -49,7 +60,7 @@ fun ResourceContext.copyResources(
sourceResourceDirectory: String, sourceResourceDirectory: String,
vararg resources: ResourceGroup, vararg resources: ResourceGroup,
) { ) {
val targetResourceDirectory = this.get("res") val targetResourceDirectory = this["res", false]
for (resourceGroup in resources) { for (resourceGroup in resources) {
resourceGroup.resources.forEach { resource -> resourceGroup.resources.forEach { resource ->
@ -164,3 +175,37 @@ internal fun Node.addResource(
} }
internal fun org.w3c.dom.Document.getNode(tagName: String) = this.getElementsByTagName(tagName).item(0) internal fun org.w3c.dom.Document.getNode(tagName: String) = this.getElementsByTagName(tagName).item(0)
internal fun NodeList.findElementByAttributeValue(attributeName: String, value: String): Element? {
for (i in 0 until length) {
val node = item(i)
if (node.nodeType == Node.ELEMENT_NODE) {
val element = node as Element
if (element.getAttribute(attributeName) == value) {
return element
}
// Recursively search.
val found = element.childNodes.findElementByAttributeValue(attributeName, value)
if (found != null) {
return found
}
}
}
return null
}
internal fun NodeList.findElementByAttributeValueOrThrow(attributeName: String, value: String): Element {
return findElementByAttributeValue(attributeName, value) ?: throw PatchException("Could not find: $attributeName $value")
}
internal fun Element.copyAttributesFrom(oldContainer: Element) {
// Copy attributes from the old element to the new element
val attributes = oldContainer.attributes
for (i in 0 until attributes.length) {
val attr = attributes.item(i) as Attr
setAttribute(attr.name, attr.value)
}
}

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/content_copy/materialsymbolsoutlined/content_copy_wght200gradN25_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/content_copy/materialsymbolsoutlined/content_copy_wght200gradN25_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,7 +1,7 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/content_copy/materialsymbolsoutlined/content_copy_wght200gradN25_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/content_copy/materialsymbolsoutlined/content_copy_wght200gradN25_24px.xml
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/schedule/materialsymbolsoutlined/schedule_wght300_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/schedule/materialsymbolsoutlined/schedule_wght300_24px.xml
This icon is the result of a combination of "content copy" and "schedule" icons. Changes made: This icon is the result of a combination of "content copy" and "schedule" icons.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,5 +1,34 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:yt="http://schemas.android.com/apk/res-auto" android:id="@+id/youtube_controls_bottom_ui_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layoutDirection="ltr"> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.libraries.youtube.common.ui.TouchImageView android:id="@+id/revanced_copy_video_url_timestamp_button" android:paddingLeft="12dp" android:paddingTop="22dp" android:paddingRight="12dp" android:paddingBottom="16dp" android:longClickable="false" android:layout_width="60dp" android:layout_height="60dp" android:src="@drawable/revanced_yt_copy_timestamp" android:scaleType="center" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" style="@style/YouTubePlayerButton"/> xmlns:yt="http://schemas.android.com/apk/res-auto"
<com.google.android.libraries.youtube.common.ui.TouchImageView android:id="@+id/revanced_copy_video_url_button" android:paddingLeft="12dp" android:paddingTop="22dp" android:paddingRight="12dp" android:paddingBottom="16dp" android:longClickable="false" android:layout_width="60dp" android:layout_height="60dp" android:src="@drawable/revanced_yt_copy" android:scaleType="center" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" style="@style/YouTubePlayerButton"/> android:id="@+id/youtube_controls_bottom_ui_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layoutDirection="ltr">
<com.google.android.libraries.youtube.common.ui.TouchImageView
android:id="@+id/revanced_copy_video_url_timestamp_button"
style="@style/YouTubePlayerButton"
android:layout_width="48.0dip"
android:layout_height="60.0dip"
android:paddingTop="6.0dp"
android:paddingBottom="0dp"
android:longClickable="false"
android:scaleType="center"
android:src="@drawable/revanced_yt_copy_timestamp"
yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container"
yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" />
<com.google.android.libraries.youtube.common.ui.TouchImageView
android:id="@+id/revanced_copy_video_url_button"
style="@style/YouTubePlayerButton"
android:layout_width="48.0dip"
android:layout_height="60.0dip"
android:paddingTop="6.0dp"
android:paddingBottom="0dp"
android:longClickable="false"
android:scaleType="center"
android:src="@drawable/revanced_yt_copy"
yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container"
yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" />
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/download/materialsymbolsoutlined/download_wght200gradN25_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/download/materialsymbolsoutlined/download_wght200gradN25_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,4 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:yt="http://schemas.android.com/apk/res-auto" android:id="@+id/youtube_controls_bottom_ui_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layoutDirection="ltr"> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.libraries.youtube.common.ui.TouchImageView android:id="@+id/revanced_external_download_button" android:paddingLeft="12dp" android:paddingTop="22dp" android:paddingRight="12dp" android:paddingBottom="16dp" android:longClickable="false" android:layout_width="60dp" android:layout_height="60dp" android:src="@drawable/revanced_yt_download_button" android:scaleType="center" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" style="@style/YouTubePlayerButton"/> xmlns:yt="http://schemas.android.com/apk/res-auto"
android:id="@+id/youtube_controls_bottom_ui_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layoutDirection="ltr">
<com.google.android.libraries.youtube.common.ui.TouchImageView
android:id="@+id/revanced_external_download_button"
style="@style/YouTubePlayerButton"
android:layout_width="48.0dip"
android:layout_height="60.0dip"
android:paddingTop="6.5dp"
android:paddingBottom="0dp"
android:longClickable="false"
android:scaleType="center"
android:src="@drawable/revanced_yt_download_button"
yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container"
yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" />
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/slow_motion_video/materialsymbolsoutlined/slow_motion_video_wght200gradN25_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/slow_motion_video/materialsymbolsoutlined/slow_motion_video_wght200gradN25_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,4 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:yt="http://schemas.android.com/apk/res-auto" android:id="@+id/youtube_controls_bottom_ui_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layoutDirection="ltr"> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.libraries.youtube.common.ui.TouchImageView android:id="@+id/revanced_playback_speed_dialog_button" android:paddingLeft="12dp" android:paddingTop="22dp" android:paddingRight="12dp" android:paddingBottom="16dp" android:longClickable="false" android:layout_width="60dp" android:layout_height="60dp" android:src="@drawable/revanced_playback_speed_dialog_button" android:scaleType="center" yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container" yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" style="@style/YouTubePlayerButton"/> xmlns:yt="http://schemas.android.com/apk/res-auto"
android:id="@+id/youtube_controls_bottom_ui_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layoutDirection="ltr">
<com.google.android.libraries.youtube.common.ui.TouchImageView
android:id="@+id/revanced_playback_speed_dialog_button"
style="@style/YouTubePlayerButton"
android:layout_width="48.0dip"
android:layout_height="60.0dip"
android:paddingTop="6.0dp"
android:paddingBottom="0dp"
android:longClickable="false"
android:scaleType="center"
android:src="@drawable/revanced_playback_speed_dialog_button"
yt:layout_constraintBottom_toTopOf="@+id/quick_actions_container"
yt:layout_constraintRight_toLeftOf="@+id/fullscreen_button" />
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/adjust/materialsymbolsoutlined/adjust_wght200gradN25_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/adjust/materialsymbolsoutlined/adjust_wght200gradN25_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/fast_forward/materialsymbolsoutlined/fast_forward_wght200gradN25_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/fast_forward/materialsymbolsoutlined/fast_forward_wght200gradN25_24px.xml
The icon has been mirrored and resized Changes made: The icon has been mirrored and resized
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/compare/materialsymbolsoutlined/compare_wght200gradN25_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/compare/materialsymbolsoutlined/compare_wght200gradN25_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/edit/materialsymbolsoutlined/edit_wght200gradN25_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/edit/materialsymbolsoutlined/edit_wght200gradN25_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/fast_forward/materialsymbolsoutlined/fast_forward_wght200gradN25_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/fast_forward/materialsymbolsoutlined/fast_forward_wght200gradN25_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/ajayyy/SponsorBlock/blob/e1d656f43f8b3cfb40e1c521e4103d61db756872/public/icons/PlayerStartIconSponsorBlocker.svg https://github.com/ajayyy/SponsorBlock/blob/e1d656f43f8b3cfb40e1c521e4103d61db756872/public/icons/PlayerStartIconSponsorBlocker.svg
The SponsorBlock logo was inverted Changes made: The SponsorBlock logo was inverted.
Copyright 2021 Ajay Ramachandran <dev@ajay.app> Copyright 2021 Ajay Ramachandran <dev@ajay.app>

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/publish/materialsymbolsoutlined/publish_wght200gradN25_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/publish/materialsymbolsoutlined/publish_wght200gradN25_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/thumbs_up_down/materialsymbolsoutlined/thumbs_up_down_wght300gradN25_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/thumbs_up_down/materialsymbolsoutlined/thumbs_up_down_wght300gradN25_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,18 +1,5 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android">
<com.google.android.libraries.youtube.common.ui.TouchImageView
android:id="@+id/revanced_sb_create_segment_button"
style="@style/YouTubePlayerButton"
android:layout_width="@dimen/controls_overlay_action_button_size"
android:layout_height="@dimen/controls_overlay_action_button_size"
android:layout_alignWithParentIfMissing="true"
android:layout_alignParentTop="true"
android:layout_marginTop="2dp"
android:layout_marginEnd="4dp"
android:layout_toStartOf="@+id/player_additional_view_container"
android:padding="@dimen/controls_overlay_action_button_padding"
android:src="@drawable/revanced_sb_logo" />
<com.google.android.libraries.youtube.common.ui.TouchImageView <com.google.android.libraries.youtube.common.ui.TouchImageView
android:id="@+id/revanced_sb_voting_button" android:id="@+id/revanced_sb_voting_button"
style="@style/YouTubePlayerButton" style="@style/YouTubePlayerButton"
@ -25,4 +12,17 @@
android:layout_toStartOf="@+id/revanced_sb_create_segment_button" android:layout_toStartOf="@+id/revanced_sb_create_segment_button"
android:padding="@dimen/controls_overlay_action_button_padding" android:padding="@dimen/controls_overlay_action_button_padding"
android:src="@drawable/revanced_sb_voting" /> android:src="@drawable/revanced_sb_voting" />
<com.google.android.libraries.youtube.common.ui.TouchImageView
android:id="@+id/revanced_sb_create_segment_button"
style="@style/YouTubePlayerButton"
android:layout_width="@dimen/controls_overlay_action_button_size"
android:layout_height="@dimen/controls_overlay_action_button_size"
android:layout_alignWithParentIfMissing="true"
android:layout_alignParentTop="true"
android:layout_marginTop="2dp"
android:layout_marginEnd="4dp"
android:layout_toStartOf="@+id/player_additional_view_container"
android:padding="@dimen/controls_overlay_action_button_padding"
android:src="@drawable/revanced_sb_logo" />
</RelativeLayout> </RelativeLayout>

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/brightness_auto/materialsymbolsoutlined/brightness_auto_wght300_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/brightness_auto/materialsymbolsoutlined/brightness_auto_wght300_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/brightness_6/materialsymbolsoutlined/brightness_6_wght300_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/brightness_6/materialsymbolsoutlined/brightness_6_wght300_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/volume_off/materialsymbolsoutlined/volume_off_wght300_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/volume_off/materialsymbolsoutlined/volume_off_wght300_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google

View File

@ -1,6 +1,6 @@
<!-- <!--
https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/volume_up/materialsymbolsoutlined/volume_up_wght300_24px.xml https://github.com/google/material-design-icons/blob/9beae745bb758f3ad56654fb377ea5cf62be4915/symbols/android/volume_up/materialsymbolsoutlined/volume_up_wght300_24px.xml
The icon has been resized Changes made: Icon has been resized.
Copyright 2022 Google Copyright 2022 Google