Skip to content

Commit 3678155

Browse files
committed
YouTube: Add copy video URL buttons
1 parent 790a932 commit 3678155

File tree

7 files changed

+246
-2
lines changed

7 files changed

+246
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- Swipe controls
2323
- Remember video quality changes
2424
- Show advanced video quality menu
25+
- Copy video url video player button
2526

2627
### Spotify
2728
- Unlock Spotify Premium

app/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ abstract class CopyResourcesTask @Inject constructor() : DefaultTask() {
188188
"qualitybutton/drawable",
189189
"settings/drawable",
190190
"sponsorblock/drawable",
191-
"swipecontrols/drawable"
191+
"swipecontrols/drawable",
192+
"copyvideourl/drawable"
192193
)
193194

194195
for (drawable in drawables) {

app/src/main/java/io/github/chsbuffer/revancedxposed/youtube/YoutubeHook.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import io.github.chsbuffer.revancedxposed.addModuleAssets
1010
import io.github.chsbuffer.revancedxposed.injectHostClassLoaderToSelf
1111
import io.github.chsbuffer.revancedxposed.youtube.ad.HideAds
1212
import io.github.chsbuffer.revancedxposed.youtube.ad.VideoAds
13+
import io.github.chsbuffer.revancedxposed.youtube.interaction.CopyVideoUrl
1314
import io.github.chsbuffer.revancedxposed.youtube.interaction.SwipeControls
1415
import io.github.chsbuffer.revancedxposed.youtube.layout.NavigationButtons
1516
import io.github.chsbuffer.revancedxposed.youtube.layout.SponsorBlock
@@ -31,6 +32,7 @@ class YoutubeHook(
3132
::RemoveTrackingQueryParameter,
3233
::HideAds,
3334
::SponsorBlock,
35+
::CopyVideoUrl,
3436
::HideShortsComponents,
3537
::NavigationButtons,
3638
::SwipeControls,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.github.chsbuffer.revancedxposed.youtube.interaction
2+
3+
import app.revanced.extension.youtube.videoplayer.CopyVideoUrlButton
4+
import app.revanced.extension.youtube.videoplayer.CopyVideoUrlTimestampButton
5+
import io.github.chsbuffer.revancedxposed.R
6+
import io.github.chsbuffer.revancedxposed.shared.misc.settings.preference.SwitchPreference
7+
import io.github.chsbuffer.revancedxposed.youtube.YoutubeHook
8+
import io.github.chsbuffer.revancedxposed.youtube.misc.BottomControl
9+
import io.github.chsbuffer.revancedxposed.youtube.misc.PlayerControls
10+
import io.github.chsbuffer.revancedxposed.youtube.misc.PreferenceScreen
11+
import io.github.chsbuffer.revancedxposed.youtube.misc.addBottomControl
12+
import io.github.chsbuffer.revancedxposed.youtube.misc.initializeBottomControl
13+
import io.github.chsbuffer.revancedxposed.youtube.video.VideoInformationHook
14+
15+
fun YoutubeHook.CopyVideoUrl() {
16+
dependsOn(
17+
::PlayerControls,
18+
::VideoInformationHook,
19+
)
20+
21+
PreferenceScreen.PLAYER.addPreferences(
22+
SwitchPreference("revanced_copy_video_url"),
23+
SwitchPreference("revanced_copy_video_url_timestamp"),
24+
)
25+
26+
addBottomControl(R.layout.revanced_copy_video_url_button)
27+
initializeBottomControl(
28+
BottomControl(
29+
R.id.revanced_copy_video_url_timestamp_button,
30+
CopyVideoUrlTimestampButton::initializeButton,
31+
CopyVideoUrlTimestampButton::setVisibility,
32+
CopyVideoUrlTimestampButton::setVisibilityImmediate,
33+
CopyVideoUrlTimestampButton::setVisibilityNegatedImmediate
34+
)
35+
)
36+
initializeBottomControl(
37+
BottomControl(
38+
R.id.revanced_copy_video_url_button,
39+
CopyVideoUrlButton::initializeButton,
40+
CopyVideoUrlButton::setVisibility,
41+
CopyVideoUrlButton::setVisibilityImmediate,
42+
CopyVideoUrlButton::setVisibilityNegatedImmediate
43+
)
44+
)
45+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package io.github.chsbuffer.revancedxposed.youtube.misc
2+
3+
import android.view.View
4+
import android.view.ViewGroup
5+
import android.view.ViewStub
6+
import android.widget.ImageView
7+
import app.revanced.extension.shared.Utils
8+
import app.revanced.extension.youtube.patches.PlayerControlsPatch
9+
import io.github.chsbuffer.revancedxposed.AccessFlags
10+
import io.github.chsbuffer.revancedxposed.fingerprint
11+
import io.github.chsbuffer.revancedxposed.scopedHook
12+
import io.github.chsbuffer.revancedxposed.youtube.YoutubeHook
13+
import org.luckypray.dexkit.wrap.DexMethod
14+
15+
class BottomControl(
16+
val id: Int,
17+
@JvmField val initializeButton: (controlsView: ViewGroup) -> Unit,
18+
@JvmField val setVisibility: (Boolean, Boolean) -> Unit,
19+
@JvmField val setVisibilityImmediate: (Boolean) -> Unit,
20+
// Patch works without this hook, but it is needed to use the correct fade out animation
21+
// duration when tapping the overlay to dismiss.
22+
@JvmField val setVisibilityNegatedImmediate: () -> Unit
23+
)
24+
25+
@JvmField
26+
var visibilityImmediateCallbacksExistModified = false
27+
28+
@JvmField
29+
val bottomControls = mutableListOf<BottomControl>()
30+
31+
private val bottomControlLayouts = mutableListOf<Int>()
32+
fun addBottomControl(layout: Int) {
33+
bottomControlLayouts.add(layout)
34+
}
35+
36+
fun initializeBottomControl(bottomControl: BottomControl) {
37+
bottomControls.add(bottomControl)
38+
39+
if (!visibilityImmediateCallbacksExistModified) {
40+
visibilityImmediateCallbacksExistModified = true
41+
}
42+
}
43+
44+
fun YoutubeHook.PlayerControls() {
45+
DexMethod("Landroid/view/ViewStub;->inflate()Landroid/view/View;").hookMethod {
46+
after {
47+
val viewStub = it.thisObject as ViewStub
48+
val viewStubName = Utils.getContext().resources.getResourceName(viewStub.id)
49+
// Logger.printDebug { "ViewStub->inflate()" + viewStubName }
50+
51+
if (viewStubName.endsWith("bottom_ui_container_stub")) {
52+
// Logger.printDebug { "inject into $viewStubName" }
53+
val viewGroup = it.result as ViewGroup
54+
55+
bottomControlLayouts.forEach { layout ->
56+
viewStub.layoutInflater.inflate(layout, viewGroup, true)
57+
}
58+
59+
bottomControls.forEach { bottomControl -> bottomControl.initializeButton(viewGroup) }
60+
}
61+
}
62+
63+
initInjectVisibilityCheckCall()
64+
65+
val youtube_controls_bottom_ui_container =
66+
Utils.getResourceIdentifier("youtube_controls_bottom_ui_container", "id")
67+
68+
DexMethod("Landroid/support/constraint/ConstraintLayout;->onLayout(ZIIII)V").hookMethod {
69+
after {
70+
val controlsView = it.thisObject as ViewGroup
71+
if (controlsView.id != youtube_controls_bottom_ui_container) return@after
72+
73+
var rightButton =
74+
Utils.getChildViewByResourceName<View>(controlsView, "fullscreen_button")
75+
76+
for (bottomControl in bottomControls) {
77+
val leftButton = controlsView.findViewById<View>(bottomControl.id)
78+
// put this button to the left
79+
leftButton.x = rightButton.x - leftButton.width
80+
leftButton.y = rightButton.y
81+
rightButton = leftButton
82+
}
83+
}
84+
}
85+
}
86+
}
87+
88+
private fun YoutubeHook.initInjectVisibilityCheckCall() {
89+
getDexMethod("controlsOverlayVisibilityFingerprint") {
90+
val controls_layout_stub_id = Utils.getResourceIdentifier("controls_layout_stub", "id")
91+
val playerTopControlsInflateFingerprint = fingerprint {
92+
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
93+
returns("V")
94+
parameters()
95+
literal { controls_layout_stub_id }
96+
}
97+
98+
fingerprint {
99+
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
100+
returns("V")
101+
parameters("Z", "Z")
102+
classMatcher {
103+
descriptor = playerTopControlsInflateFingerprint.declaredClass!!.descriptor
104+
}
105+
}
106+
}.hookMethod {
107+
before { param ->
108+
bottomControls.forEach {
109+
it.setVisibility(param.args[0] as Boolean, param.args[1] as Boolean)
110+
}
111+
// Logger.printDebug { "setVisibility(visible: ${param.args[0]}, animated: ${param.args[1]})" }
112+
}
113+
}
114+
115+
// Hook the fullscreen close button. Used to fix visibility
116+
// when seeking and other situations.
117+
val fullscreen_button_id = Utils.getResourceIdentifier("fullscreen_button", "id")
118+
getDexMethod("overlayViewInflateFingerprint") {
119+
val heatseeker_viewstub_id = Utils.getResourceIdentifier("heatseeker_viewstub", "id")
120+
fingerprint {
121+
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
122+
returns("V")
123+
parameters("Landroid/view/View;")
124+
methodMatcher {
125+
addUsingNumber(fullscreen_button_id)
126+
addUsingNumber(heatseeker_viewstub_id)
127+
}
128+
}
129+
}.hookMethod(scopedHook(DexMethod("Landroid/view/View;->findViewById(I)Landroid/view/View;").toMember()) {
130+
after {
131+
if (it.args[0] == fullscreen_button_id) {
132+
PlayerControlsPatch.setFullscreenCloseButton(it.result as ImageView)
133+
}
134+
}
135+
})
136+
137+
//
138+
getDexMethod("motionEventFingerprint") {
139+
val youtubeControlsOverlayFingerprint = fingerprint {
140+
accessFlags(AccessFlags.PRIVATE, AccessFlags.FINAL)
141+
returns("V")
142+
parameters()
143+
methodMatcher {
144+
addInvoke { name = "setFocusableInTouchMode" }
145+
addUsingNumber(Utils.getResourceIdentifier("inset_overlay_view_layout", "id"))
146+
addUsingNumber(Utils.getResourceIdentifier("scrim_overlay", "id"))
147+
}
148+
}
149+
fingerprint {
150+
returns("V")
151+
parameters("Landroid/view/MotionEvent;")
152+
methodMatcher {
153+
addInvoke { name = "setTranslationY" }
154+
}
155+
classMatcher {
156+
descriptor = youtubeControlsOverlayFingerprint.declaredClass!!.descriptor
157+
}
158+
}
159+
}.hookMethod(scopedHook(DexMethod("Landroid/view/View;->setTranslationY(F)V").toMethod()) {
160+
after {
161+
// FIXME Animation lags behind
162+
bottomControls.forEach { it.setVisibilityNegatedImmediate() }
163+
// Logger.printDebug { "setVisibilityNegatedImmediate()" }
164+
}
165+
})
166+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<merge xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools"
4+
tools:ignore="MissingClass">
5+
6+
<com.google.android.libraries.youtube.common.ui.TouchImageView
7+
android:id="@+id/revanced_copy_video_url_button"
8+
android:layout_width="48.0dip"
9+
android:layout_height="60.0dip"
10+
android:background="@null"
11+
android:clickable="true"
12+
android:focusable="true"
13+
android:paddingTop="6.0dp"
14+
android:paddingBottom="0dp"
15+
android:scaleType="center"
16+
android:src="@drawable/revanced_yt_copy" />
17+
18+
<com.google.android.libraries.youtube.common.ui.TouchImageView
19+
android:id="@+id/revanced_copy_video_url_timestamp_button"
20+
android:layout_width="48.0dip"
21+
android:layout_height="60.0dip"
22+
android:background="@null"
23+
android:clickable="true"
24+
android:focusable="true"
25+
android:paddingTop="6.0dp"
26+
android:paddingBottom="0dp"
27+
android:scaleType="center"
28+
android:src="@drawable/revanced_yt_copy_timestamp" />
29+
</merge>

0 commit comments

Comments
 (0)