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 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 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 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 {
@ -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 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 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 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
@ -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 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 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
}

View file

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

View file

@ -51,8 +51,8 @@ object CopyVideoUrlBytecodePatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) {
BUTTONS_DESCRIPTORS.forEach { descriptor ->
PlayerControlsBytecodePatch.initializeControl("$descriptor->initializeButton(Landroid/view/View;)V")
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$descriptor->changeVisibility(Z)V")
PlayerControlsBytecodePatch.initializeBottomControl(descriptor)
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.patches.all.misc.resources.AddResourcesPatch
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.util.ResourceGroup
import app.revanced.util.copyResources
@ -13,7 +13,7 @@ import app.revanced.util.copyResources
@Patch(
dependencies = [
SettingsPatch::class,
BottomControlsResourcePatch::class,
PlayerControlsResourcePatch::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;"
override fun execute(context: BytecodeContext) {
PlayerControlsBytecodePatch.initializeControl("$BUTTON_DESCRIPTOR->initializeButton(Landroid/view/View;)V")
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$BUTTON_DESCRIPTOR->changeVisibility(Z)V")
PlayerControlsBytecodePatch.initializeBottomControl(BUTTON_DESCRIPTOR)
PlayerControlsBytecodePatch.injectVisibilityCheckCall(BUTTON_DESCRIPTOR)
// Main activity is used to launch downloader intent.
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.SwitchPreference
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.util.ResourceGroup
import app.revanced.util.copyResources
@Patch(
dependencies = [
BottomControlsResourcePatch::class,
PlayerControlsResourcePatch::class,
SettingsPatch::class,
AddResourcesPatch::class,
],
@ -42,6 +42,6 @@ internal object DownloadsResourcePatch : ResourcePatch() {
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.Patch
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.ControlsOverlayFingerprint
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.util.exception
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.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@ -169,59 +171,14 @@ object SponsorBlockBytecodePatch : BytecodePatch(
break
}
/*
* Voting & Shield button
*/
val controlsMethodResult = PlayerControlsBytecodePatch.showPlayerControlsFingerprintResult
// Change visibility of the buttons.
PlayerControlsBytecodePatch.initializeTopControl(INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_CREATE_SEGMENT_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
val controlsLayoutStubResourceId =
ResourceMappingPatch["id", "controls_layout_stub"]
val zoomOverlayResourceId =
ResourceMappingPatch["id", "video_zoom_overlay_stub"]
PlayerControlsBytecodePatch.initializeTopControl(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
PlayerControlsBytecodePatch.injectVisibilityCheckCall(INTEGRATIONS_VOTING_BUTTON_CONTROLLER_CLASS_DESCRIPTOR)
methods@ for (method in controlsMethodResult.mutableClass.methods) {
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
// Append the new time to the player layout.
val appendTimeFingerprintResult = AppendTimeFingerprint.result!!
val appendTimePatternScanStartIndex = appendTimeFingerprintResult.scanResult.patternScanResult!!.startIndex
val targetRegister =

View file

@ -1,18 +1,16 @@
package app.revanced.patches.youtube.layout.sponsorblock
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.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
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.SettingsResourcePatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
import app.revanced.util.copyXmlNode
import app.revanced.util.inputStreamFromBundledResource
@Patch(
dependencies = [
@ -60,49 +58,6 @@ internal object SponsorBlockResourcePatch : ResourcePatch() {
context.copyResources("sponsorblock", resourceGroup)
}
// copy nodes from host resources to their real xml files
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")
PlayerControlsResourcePatch.addTopControls("sponsorblock")
}
}

View file

@ -3,70 +3,18 @@ package app.revanced.patches.youtube.misc.playercontrols
import app.revanced.patcher.data.ResourceContext
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 java.io.Closeable
@Patch(dependencies = [ResourceMappingPatch::class])
@Patch(
dependencies = [PlayerControlsBytecodePatch::class],
)
@Deprecated("Patch renamed to PlayerControlsResourcePatch", replaceWith = ReplaceWith("PlayerControlsBytecodePatch"))
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) {
val sourceDocumentEditor = resourceContext.xmlEditor[
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()
PlayerControlsResourcePatch.addBottomControls(resourceDirectoryName)
}
override fun close() = targetDocumentEditor.close()
}
override fun close() {}
}

View file

@ -1,65 +1,144 @@
package app.revanced.patches.youtube.misc.playercontrols
import app.revanced.util.exception
import app.revanced.patcher.data.BytecodeContext
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.annotation.Patch
import app.revanced.patches.youtube.shared.fingerprints.LayoutConstructorFingerprint
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.BottomControlsInflateFingerprint
import app.revanced.patches.youtube.misc.playercontrols.fingerprints.PlayerControlsVisibilityFingerprint
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
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.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
@Patch(
description = "Manages the code for the player controls of the YouTube player.",
dependencies = [BottomControlsResourcePatch::class],
dependencies = [PlayerControlsResourcePatch::class],
)
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 var viewRegister: Int = 0
private lateinit var inflateFingerprintResult: MethodFingerprintResult
private lateinit var inflateTopControlMethod: MutableMethod
private var inflateTopControlInsertIndex: Int = -1
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) {
LayoutConstructorFingerprint.result?.let {
if (!PlayerControlsVisibilityFingerprint.resolve(context, it.classDef))
throw LayoutConstructorFingerprint.exception
} ?: throw LayoutConstructorFingerprint.exception
fun MutableMethod.indexOfFirstViewInflateOrThrow() =
indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/view/ViewStub;" &&
reference.name == "inflate"
}
showPlayerControlsFingerprintResult = PlayerControlsVisibilityFingerprint.result!!
PlayerBottomControlsInflateFingerprint.resultOrThrow().mutableMethod.apply{
inflateBottomControlMethod = this
inflateFingerprintResult = BottomControlsInflateFingerprint.result!!.also {
moveToRegisterInstructionIndex = it.scanResult.patternScanResult!!.endIndex
viewRegister =
(it.mutableMethod.implementation!!.instructions[moveToRegisterInstructionIndex] as OneRegisterInstruction).registerA
val inflateReturnObjectIndex = indexOfFirstViewInflateOrThrow() + 1
inflateBottomControlRegister = getInstruction<OneRegisterInstruction>(inflateReturnObjectIndex).registerA
inflateBottomControlInsertIndex = inflateReturnObjectIndex + 1
}
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.
*/
fun injectVisibilityCheckCall(descriptor: String) {
showPlayerControlsFingerprintResult.mutableMethod.addInstruction(
0,
"""
invoke-static {p1}, $descriptor
"""
internal fun initializeTopControl(descriptor: String) {
inflateTopControlMethod.addInstruction(
inflateTopControlInsertIndex++,
"invoke-static { v$inflateTopControlRegister }, $descriptor->initialize(Landroid/view/View;)V"
)
}
/**
* 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) {
inflateFingerprintResult.mutableMethod.addInstruction(
moveToRegisterInstructionIndex + 1,
"invoke-static {v$viewRegister}, $descriptor"
fun initializeBottomControl(descriptor: String) {
inflateBottomControlMethod.addInstruction(
inflateBottomControlInsertIndex++,
"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 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,
returnType = "V",
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"),
)
PlayerControlsBytecodePatch.initializeControl("$SPEED_BUTTON_CLASS_DESCRIPTOR->initializeButton(Landroid/view/View;)V")
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$SPEED_BUTTON_CLASS_DESCRIPTOR->changeVisibility(Z)V")
PlayerControlsBytecodePatch.initializeBottomControl(SPEED_BUTTON_CLASS_DESCRIPTOR)
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.patch.ResourcePatch
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.copyResources
@Patch(
dependencies = [BottomControlsResourcePatch::class],
dependencies = [PlayerControlsResourcePatch::class],
)
internal object PlaybackSpeedButtonResourcePatch : ResourcePatch() {
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.reference.Reference
import com.android.tools.smali.dexlib2.util.MethodUtil
import org.stringtemplate.v4.compiler.Bytecode.instructions
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.
* @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.
* @see [indexOfIdResourceOrThrow]
* @see [indexOfIdResourceOrThrow], [indexOfFirstWideLiteralInstructionValueReversed]
*/
fun Method.indexOfIdResource(resourceName: String): Int {
val resourceId = ResourceMappingPatch["id", resourceName]
@ -86,6 +87,7 @@ fun Method.indexOfIdResource(resourceName: String): Int {
* Requires [ResourceMappingPatch] as a dependency.
*
* @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 {
val index = indexOfIdResource(resourceName)
@ -120,6 +122,30 @@ fun Method.indexOfFirstWideLiteralInstructionValueOrThrow(literal: Long): Int {
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.
*

View file

@ -1,8 +1,11 @@
package app.revanced.util
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.util.DomFileEditor
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.NodeList
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)
}
fun Node.insertFirst(node: Node) {
if (hasChildNodes()) {
insertBefore(node, firstChild)
} else {
appendChild(node)
}
}
/**
* Copy resources from the current class loader to the resource directory.
*
@ -49,7 +60,7 @@ fun ResourceContext.copyResources(
sourceResourceDirectory: String,
vararg resources: ResourceGroup,
) {
val targetResourceDirectory = this.get("res")
val targetResourceDirectory = this["res", false]
for (resourceGroup in resources) {
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 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
The icon has been resized
Changes made: Icon has been resized.
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/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

View file

@ -1,5 +1,34 @@
<?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">
<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"/>
<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.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">
<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>

View file

@ -1,6 +1,6 @@
<!--
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

View file

@ -1,4 +1,21 @@
<?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">
<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"/>
<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">
<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>

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
The icon has been resized
Changes made: Icon has been resized.
Copyright 2022 Google

View file

@ -1,4 +1,21 @@
<?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">
<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"/>
<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">
<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>

View file

@ -1,6 +1,6 @@
<!--
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

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
The icon has been mirrored and resized
Changes made: The icon has been mirrored and resized
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
The icon has been resized
Changes made: Icon has been resized.
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
The icon has been resized
Changes made: Icon has been resized.
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
The icon has been resized
Changes made: Icon has been resized.
Copyright 2022 Google

View file

@ -1,6 +1,6 @@
<!--
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>

View file

@ -1,6 +1,6 @@
<!--
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

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
The icon has been resized
Changes made: Icon has been resized.
Copyright 2022 Google

View file

@ -1,18 +1,5 @@
<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
android:id="@+id/revanced_sb_voting_button"
style="@style/YouTubePlayerButton"
@ -25,4 +12,17 @@
android:layout_toStartOf="@+id/revanced_sb_create_segment_button"
android:padding="@dimen/controls_overlay_action_button_padding"
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>

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
The icon has been resized
Changes made: Icon has been resized.
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
The icon has been resized
Changes made: Icon has been resized.
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
The icon has been resized
Changes made: Icon has been resized.
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
The icon has been resized
Changes made: Icon has been resized.
Copyright 2022 Google