mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-04-30 14:44:33 +02:00
fix(YouTube): Show video chapter titles without clipping when overlay buttons are enabled (#3674)
This commit is contained in:
parent
f27cbece71
commit
4b88c316ed
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 {
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 =
|
||||||
|
@ -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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {}
|
||||||
}
|
}
|
@ -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)
|
||||||
}
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -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 }
|
|
||||||
)
|
|
@ -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")
|
@ -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)
|
||||||
|
}
|
||||||
|
)
|
@ -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 }
|
||||||
|
)
|
@ -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;"
|
||||||
|
}
|
||||||
|
)
|
@ -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 }
|
||||||
|
)
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user