Skip to content

Commit 0d97d88

Browse files
committed
YouTube: Add PlaybackSpeedPatch (custom playback speed, remember playback speed, and playback speed dialog button)
1 parent f9eef19 commit 0d97d88

File tree

16 files changed

+519
-37
lines changed

16 files changed

+519
-37
lines changed

app/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,8 @@ abstract class CopyResourcesTask @Inject constructor() : DefaultTask() {
199199
"sponsorblock/drawable",
200200
"swipecontrols/drawable",
201201
"copyvideourl/drawable",
202-
"downloads/drawable"
202+
"downloads/drawable",
203+
"speedbutton/drawable",
203204
)
204205

205206
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
@@ -23,6 +23,7 @@ import io.github.chsbuffer.revancedxposed.youtube.misc.backgroundplayback.Backgr
2323
import io.github.chsbuffer.revancedxposed.youtube.misc.privacy.RemoveTrackingQueryParameter
2424
import io.github.chsbuffer.revancedxposed.youtube.misc.settings.SettingsHook
2525
import io.github.chsbuffer.revancedxposed.youtube.video.quality.VideoQuality
26+
import io.github.chsbuffer.revancedxposed.youtube.video.speed.PlaybackSpeed
2627

2728
class YoutubeHook(
2829
app: Application,
@@ -45,6 +46,7 @@ class YoutubeHook(
4546
::DisableResumingShortsOnStartup,
4647
::HideLayoutComponents,
4748
::HideButtons,
49+
::PlaybackSpeed,
4850
// make sure settingsHook at end to build preferences
4951
::SettingsHook
5052
)

app/src/main/java/io/github/chsbuffer/revancedxposed/youtube/interaction/copyvideourl/CopyVideoUrlPatch.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import io.github.chsbuffer.revancedxposed.youtube.misc.playercontrols.PlayerCont
1010
import io.github.chsbuffer.revancedxposed.youtube.misc.playercontrols.addBottomControl
1111
import io.github.chsbuffer.revancedxposed.youtube.misc.playercontrols.initializeBottomControl
1212
import io.github.chsbuffer.revancedxposed.youtube.misc.settings.PreferenceScreen
13-
import io.github.chsbuffer.revancedxposed.youtube.video.information.VideoInformationHook
13+
import io.github.chsbuffer.revancedxposed.youtube.video.information.VideoInformation
1414

1515
fun YoutubeHook.CopyVideoUrl() {
1616
dependsOn(
1717
::PlayerControls,
18-
::VideoInformationHook,
18+
::VideoInformation,
1919
)
2020

2121
PreferenceScreen.PLAYER.addPreferences(

app/src/main/java/io/github/chsbuffer/revancedxposed/youtube/interaction/downloads/DownloadsPatch.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import io.github.chsbuffer.revancedxposed.youtube.misc.playercontrols.addBottomC
1515
import io.github.chsbuffer.revancedxposed.youtube.misc.playercontrols.initializeBottomControl
1616
import io.github.chsbuffer.revancedxposed.youtube.misc.settings.PreferenceScreen
1717
import io.github.chsbuffer.revancedxposed.youtube.shared.mainActivityOnCreateFingerprint
18-
import io.github.chsbuffer.revancedxposed.youtube.video.information.VideoInformationHook
18+
import io.github.chsbuffer.revancedxposed.youtube.video.information.VideoInformation
1919

2020
fun YoutubeHook.Downloads() {
2121

2222
dependsOn(
2323
::PlayerControls,
24-
::VideoInformationHook,
24+
::VideoInformation,
2525
)
2626

2727
PreferenceScreen.PLAYER.addPreferences(

app/src/main/java/io/github/chsbuffer/revancedxposed/youtube/layout/sponsorblock/SponsorBlockPatch.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.chsbuffer.revancedxposed.youtube.layout.sponsorblock
22

3-
import android.content.Context
43
import android.graphics.Canvas
54
import android.graphics.Rect
65
import android.view.ViewGroup
@@ -26,16 +25,16 @@ import io.github.chsbuffer.revancedxposed.youtube.misc.playercontrols.addTopCont
2625
import io.github.chsbuffer.revancedxposed.youtube.misc.playercontrols.initializeTopControl
2726
import io.github.chsbuffer.revancedxposed.youtube.misc.playertype.PlayerTypeHook
2827
import io.github.chsbuffer.revancedxposed.youtube.misc.settings.PreferenceScreen
29-
import io.github.chsbuffer.revancedxposed.youtube.video.information.VideoInformationHook
30-
import io.github.chsbuffer.revancedxposed.youtube.video.information.playerInitHooks
28+
import io.github.chsbuffer.revancedxposed.youtube.video.information.VideoInformation
29+
import io.github.chsbuffer.revancedxposed.youtube.video.information.onCreateHook
3130
import io.github.chsbuffer.revancedxposed.youtube.video.information.videoTimeHooks
3231
import io.github.chsbuffer.revancedxposed.youtube.video.videoid.VideoId
3332
import io.github.chsbuffer.revancedxposed.youtube.video.videoid.videoIdHooks
3433
import org.luckypray.dexkit.wrap.DexMethod
3534

3635
fun YoutubeHook.SponsorBlock() {
3736
dependsOn(
38-
::VideoInformationHook,
37+
::VideoInformation,
3938
::VideoId,
4039
::PlayerTypeHook,
4140
::PlayerControls,
@@ -128,7 +127,7 @@ fun YoutubeHook.SponsorBlock() {
128127
// TODO Append the new time to the player layout.
129128

130129
// Initialize the player controller.
131-
playerInitHooks.add { SegmentPlaybackController.initialize(it) }
130+
onCreateHook.add { SegmentPlaybackController.initialize(it) }
132131

133132
// Initialize the SponsorBlock view.
134133
val controls_overlay_layout =

app/src/main/java/io/github/chsbuffer/revancedxposed/youtube/video/information/Fingerprints.kt

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import io.github.chsbuffer.revancedxposed.findClassDirect
77
import io.github.chsbuffer.revancedxposed.findFieldDirect
88
import io.github.chsbuffer.revancedxposed.findMethodDirect
99
import io.github.chsbuffer.revancedxposed.fingerprint
10+
import io.github.chsbuffer.revancedxposed.parameters
11+
import io.github.chsbuffer.revancedxposed.youtube.shared.videoQualityChangedFingerprint
1012
import org.luckypray.dexkit.query.enums.OpCodeMatchType
13+
import org.luckypray.dexkit.query.enums.UsingType
1114
import org.luckypray.dexkit.result.FieldUsingType
1215

1316
@get:SkipTest
@@ -16,18 +19,42 @@ val createVideoPlayerSeekbarFingerprint = fingerprint {
1619
strings("timed_markers_width")
1720
}
1821

19-
//val onPlaybackSpeedItemClickFingerprint = fingerprint {
20-
// accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
21-
// returns("V")
22-
// parameters("L", "L", "I", "J")
23-
// custom { method, _ ->
24-
// method.name == "onItemClick" &&
25-
// method.implementation?.instructions?.find {
26-
// it.opcode == Opcode.IGET_OBJECT &&
27-
// it.getReference<FieldReference>()!!.type == "Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"
28-
// } != null
29-
// }
30-
//}
22+
val onPlaybackSpeedItemClickFingerprint = fingerprint {
23+
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
24+
returns("V")
25+
parameters("L", "L", "I", "J")
26+
methodMatcher {
27+
name = "onItemClick"
28+
addUsingField {
29+
this.type {
30+
descriptor =
31+
"Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"
32+
}
33+
this.usingType = UsingType.Read
34+
}
35+
}
36+
}
37+
38+
val setPlaybackSpeedMethodReference = findMethodDirect {
39+
onPlaybackSpeedItemClickFingerprint().invokes.findMethod { matcher { paramTypes("float") } }
40+
.single()
41+
}
42+
43+
val setPlaybackSpeedClass = findClassDirect {
44+
setPlaybackSpeedMethodReference().declaredClass!!
45+
}
46+
47+
val setPlaybackSpeedClassFieldReference = findFieldDirect {
48+
val setPlaybackSpeedClassName = setPlaybackSpeedClass().name
49+
onPlaybackSpeedItemClickFingerprint().usingFields.distinct()
50+
.single { it.field.typeName == setPlaybackSpeedClassName }.field
51+
}
52+
53+
val setPlaybackSpeedContainerClassFieldReference = findFieldDirect {
54+
val setPlaybackSpeedContainerClassName = setPlaybackSpeedClassFieldReference().declaredClassName
55+
onPlaybackSpeedItemClickFingerprint().usingFields.distinct()
56+
.single { it.field.typeName == setPlaybackSpeedContainerClassName }.field
57+
}
3158

3259
val playerControllerSetTimeReferenceFingerprint = fingerprint {
3360
opcodes(Opcode.INVOKE_DIRECT_RANGE, Opcode.IGET_OBJECT)
@@ -142,8 +169,8 @@ val seekRelativeFingerprint = fingerprint {
142169
/**
143170
* Resolves with the class found in [videoQualityChangedFingerprint].
144171
*/
145-
@get:SkipTest
146172
val playbackSpeedMenuSpeedChangedFingerprint = fingerprint {
173+
classMatcher { className(videoQualityChangedFingerprint(dexkit).className) }
147174
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
148175
returns("L")
149176
parameters("L")
@@ -155,18 +182,16 @@ val playbackSpeedMenuSpeedChangedFingerprint = fingerprint {
155182
)
156183
}
157184

158-
@get:SkipTest
159185
val playbackSpeedClassFingerprint = fingerprint {
160186
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
161187
returns("L")
162188
parameters("L")
163189
opcodes(
164190
Opcode.RETURN_OBJECT
165191
)
166-
strings("PLAYBACK_RATE_MENU_BOTTOM_SHEET_FRAGMENT")
192+
methodMatcher { addEqString("PLAYBACK_RATE_MENU_BOTTOM_SHEET_FRAGMENT") }
167193
}
168194

169-
170195
const val YOUTUBE_VIDEO_QUALITY_CLASS_TYPE =
171196
"Lcom/google/android/libraries/youtube/innertube/model/media/VideoQuality;"
172197

app/src/main/java/io/github/chsbuffer/revancedxposed/youtube/video/information/VideoInformationPatch.kt

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
package io.github.chsbuffer.revancedxposed.youtube.video.information
22

3+
import app.revanced.extension.shared.Logger
34
import app.revanced.extension.youtube.patches.VideoInformation
45
import com.google.android.libraries.youtube.innertube.model.media.VideoQuality
56
import de.robv.android.xposed.XC_MethodHook
67
import de.robv.android.xposed.XposedBridge
78
import io.github.chsbuffer.revancedxposed.findFirstFieldByExactType
89
import io.github.chsbuffer.revancedxposed.getStaticObjectField
10+
import io.github.chsbuffer.revancedxposed.scopedHook
911
import io.github.chsbuffer.revancedxposed.youtube.YoutubeHook
1012
import io.github.chsbuffer.revancedxposed.youtube.video.playerresponse.PlayerResponseMethodHook
1113
import io.github.chsbuffer.revancedxposed.youtube.video.playerresponse.playerResponseBeforeVideoIdHooks
1214
import io.github.chsbuffer.revancedxposed.youtube.video.playerresponse.playerResponseVideoIdHooks
1315
import io.github.chsbuffer.revancedxposed.youtube.video.videoid.VideoId
1416
import io.github.chsbuffer.revancedxposed.youtube.video.videoid.videoIdHooks
1517
import org.luckypray.dexkit.wrap.DexClass
18+
import java.lang.reflect.Field
1619
import java.lang.reflect.Method
1720

1821
/**
@@ -24,9 +27,34 @@ import java.lang.reflect.Method
2427
* @param targetMethodClass The descriptor for the class to invoke when the player controller is created.
2528
* @param targetMethodName The name of the static method to invoke when the player controller is created.
2629
*/
27-
val playerInitHooks = mutableListOf<(VideoInformation.PlaybackController) -> Unit>()
30+
val onCreateHook = mutableListOf<(VideoInformation.PlaybackController) -> Unit>()
2831
val videoTimeHooks = mutableListOf<(Long) -> Unit>()
2932

33+
/*
34+
* Hook when the video speed is changed for any reason _except when the user manually selects a new speed_.
35+
* */
36+
val videoSpeedChangedHook = mutableListOf<(Float) -> Unit>()
37+
/**
38+
* Hook the video speed selected by the user.
39+
*/
40+
val userSelectedPlaybackSpeedHook = mutableListOf<(Float) -> Unit>()
41+
42+
lateinit var setPlaybackSpeedMethod: Method
43+
lateinit var setPlaybackSpeedClassField: Field
44+
lateinit var setPlaybackSpeedContainerClassField: Field
45+
46+
private var playbackSpeedClass: Any? = null
47+
48+
fun doOverridePlaybackSpeed(speedOverride: Float) {
49+
val setPlaybackSpeedObj = playbackSpeedClass.let { setPlaybackSpeedContainerClassField.get(it) }
50+
if (speedOverride <= 0.0f || setPlaybackSpeedObj == null)
51+
return
52+
53+
setPlaybackSpeedObj
54+
.let { setPlaybackSpeedClassField.get(it) }
55+
.let { setPlaybackSpeedMethod(it, speedOverride) }
56+
}
57+
3058
class PlaybackController(
3159
private val obj: Any,
3260
private val seekTo: Method,
@@ -48,7 +76,7 @@ private lateinit var getResolution: (VideoQuality) -> Int
4876
fun VideoQuality.getResolution() = getResolution(this)
4977
fun VideoQuality.getQualityName() = getQualityName(this)
5078

51-
fun YoutubeHook.VideoInformationHook() {
79+
fun YoutubeHook.VideoInformation() {
5280
dependsOn(
5381
::VideoId,
5482
::PlayerResponseMethodHook,
@@ -66,12 +94,11 @@ fun YoutubeHook.VideoInformationHook() {
6694
val playerController = PlaybackController(
6795
param.thisObject, seekFingerprint, seekRelativeFingerprint, seekSourceNone
6896
)
69-
playerInitHooks.forEach { it(playerController) }
97+
onCreateHook.forEach { it(playerController) }
7098
}
7199
}
72100
}
73101

74-
playerInitHooks.add { VideoInformation.initialize(it) }
75102
//endregion
76103

77104
//region mdxPlayerDirector
@@ -97,8 +124,9 @@ fun YoutubeHook.VideoInformationHook() {
97124
val videoLengthHolderField = ::videoLengthHolderField.field
98125

99126
after { param ->
100-
val videoLengthHolder = videoLengthHolderField.get(param.thisObject)
101-
val videoLength = videoLengthField.getLong(videoLengthHolder)
127+
val videoLength = param.thisObject
128+
.let { videoLengthHolderField.get(it) }
129+
.let { videoLengthField.getLong(it) }
102130
VideoInformation.setVideoLength(videoLength)
103131
}
104132
}
@@ -127,13 +155,49 @@ fun YoutubeHook.VideoInformationHook() {
127155
}
128156
}
129157

158+
/*
159+
* Hook the methods which set the time
160+
*/
130161
videoTimeHooks.add { videoTime ->
131162
VideoInformation.setVideoTime(videoTime)
132163
}
133164

134-
// TODO Hook the user playback speed selection.
165+
/*
166+
* Hook the user playback speed selection.
167+
*/
168+
setPlaybackSpeedMethod = ::setPlaybackSpeedMethodReference.method
169+
setPlaybackSpeedClassField = ::setPlaybackSpeedClassFieldReference.field
170+
setPlaybackSpeedContainerClassField = ::setPlaybackSpeedContainerClassFieldReference.field
171+
172+
::setPlaybackSpeedMethodReference.hookMethod {
173+
before { param ->
174+
// Hook when the video speed is changed for any reason _except when the user manually selects a new speed_.
175+
videoSpeedChangedHook.forEach { it(param.args[0] as Float) }
176+
}
177+
}
178+
179+
::onPlaybackSpeedItemClickFingerprint.hookMethod(scopedHook(::setPlaybackSpeedMethodReference.member) {
180+
before { param ->
181+
// Hook the video speed selected by the user.
182+
Logger.printDebug { "onPlaybackSpeedItemClickFingerprint: ${param.args[0]}" }
183+
userSelectedPlaybackSpeedHook.forEach { it.invoke(param.args[0] as Float) }
184+
videoSpeedChangedHook.forEach { it.invoke(param.args[0] as Float) }
185+
}
186+
})
187+
188+
::playbackSpeedClassFingerprint.hookMethod {
189+
// Set playback speed class.
190+
after { playbackSpeedClass = it.result }
191+
}
135192

136-
// TODO Handle new playback speed menu.
193+
// Handle new playback speed menu.
194+
::playbackSpeedMenuSpeedChangedFingerprint.hookMethod(scopedHook(::setPlaybackSpeedMethodReference.member) {
195+
before { param ->
196+
Logger.printDebug { "Playback speed menu speed changed: ${param.args[0]}" }
197+
userSelectedPlaybackSpeedHook.forEach { it.invoke(param.args[0] as Float) }
198+
videoSpeedChangedHook.forEach { it.invoke(param.args[0] as Float) }
199+
}
200+
})
137201

138202
// videoQuality
139203
val YOUTUBE_VIDEO_QUALITY_CLASS_TYPE =
@@ -177,4 +241,8 @@ fun YoutubeHook.VideoInformationHook() {
177241
)
178242
}
179243
}
244+
245+
onCreateHook.add { VideoInformation.initialize(it) }
246+
videoSpeedChangedHook.add { VideoInformation.videoSpeedChanged(it) }
247+
userSelectedPlaybackSpeedHook.add { VideoInformation.userSelectedPlaybackSpeed(it) }
180248
}

app/src/main/java/io/github/chsbuffer/revancedxposed/youtube/video/quality/RememberVideoQualityPatch.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import io.github.chsbuffer.revancedxposed.shared.misc.settings.preference.Switch
88
import io.github.chsbuffer.revancedxposed.youtube.YoutubeHook
99
import io.github.chsbuffer.revancedxposed.youtube.shared.VideoQualityReceiver
1010
import io.github.chsbuffer.revancedxposed.youtube.shared.videoQualityChangedFingerprint
11-
import io.github.chsbuffer.revancedxposed.youtube.video.information.playerInitHooks
11+
import io.github.chsbuffer.revancedxposed.youtube.video.information.onCreateHook
1212

1313
fun YoutubeHook.RememberVideoQuality() {
1414
settingsMenuVideoQualityGroup.addAll(
@@ -40,7 +40,7 @@ fun YoutubeHook.RememberVideoQuality() {
4040
)
4141
)
4242

43-
playerInitHooks.add { controller ->
43+
onCreateHook.add { controller ->
4444
RememberVideoQualityPatch.newVideoStarted(controller)
4545
}
4646

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package io.github.chsbuffer.revancedxposed.youtube.video.speed
2+
3+
import io.github.chsbuffer.revancedxposed.youtube.video.speed.button.PlaybackSpeedButton
4+
import io.github.chsbuffer.revancedxposed.youtube.video.speed.custom.CustomPlaybackSpeed
5+
import io.github.chsbuffer.revancedxposed.youtube.video.speed.remember.RememberPlaybackSpeed
6+
import io.github.chsbuffer.revancedxposed.shared.misc.settings.preference.BasePreference
7+
import io.github.chsbuffer.revancedxposed.shared.misc.settings.preference.PreferenceCategory
8+
import io.github.chsbuffer.revancedxposed.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
9+
import io.github.chsbuffer.revancedxposed.youtube.YoutubeHook
10+
import io.github.chsbuffer.revancedxposed.youtube.misc.settings.PreferenceScreen
11+
12+
/**
13+
* Speed menu settings. Used to organize all speed related settings together.
14+
*/
15+
internal val settingsMenuVideoSpeedGroup = mutableSetOf<BasePreference>()
16+
17+
@Suppress("unused")
18+
fun YoutubeHook.PlaybackSpeed() {
19+
dependsOn(
20+
::CustomPlaybackSpeed,
21+
::RememberPlaybackSpeed,
22+
::PlaybackSpeedButton,
23+
)
24+
25+
PreferenceScreen.VIDEO.addPreferences(
26+
PreferenceCategory(
27+
key = "revanced_zz_video_key", // Dummy key to force the speed settings last.
28+
titleKey = null,
29+
sorting = Sorting.UNSORTED,
30+
tag = app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory::class.java,
31+
preferences = settingsMenuVideoSpeedGroup
32+
)
33+
)
34+
}
35+

0 commit comments

Comments
 (0)