@@ -2,10 +2,13 @@ package io.github.chsbuffer.revancedxposed.youtube.video
22
33import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory
44import app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch
5+ import com.google.android.libraries.youtube.innertube.model.media.VideoQuality
56import de.robv.android.xposed.XC_MethodHook
7+ import de.robv.android.xposed.XposedBridge
68import io.github.chsbuffer.revancedxposed.AccessFlags
79import io.github.chsbuffer.revancedxposed.Opcode
810import io.github.chsbuffer.revancedxposed.ScopedHook
11+ import io.github.chsbuffer.revancedxposed.findFirstFieldByExactType
912import io.github.chsbuffer.revancedxposed.fingerprint
1013import io.github.chsbuffer.revancedxposed.getIntField
1114import io.github.chsbuffer.revancedxposed.shared.misc.settings.preference.ListPreference
@@ -14,8 +17,15 @@ import io.github.chsbuffer.revancedxposed.shared.misc.settings.preference.Prefer
1417import io.github.chsbuffer.revancedxposed.shared.misc.settings.preference.SwitchPreference
1518import io.github.chsbuffer.revancedxposed.youtube.YoutubeHook
1619import io.github.chsbuffer.revancedxposed.youtube.misc.PreferenceScreen
20+ import org.luckypray.dexkit.wrap.DexClass
1721import java.lang.reflect.Modifier
1822
23+ private lateinit var getQualityName: (VideoQuality ) -> String
24+ private lateinit var getResolution: (VideoQuality ) -> Int
25+
26+ fun VideoQuality.getResolution () = getResolution(this )
27+ fun VideoQuality.getQualityName () = getQualityName(this )
28+
1929fun YoutubeHook.RememberVideoQuality () {
2030 val settingsMenuVideoQualityGroup = setOf (
2131 ListPreference (
@@ -55,17 +65,32 @@ fun YoutubeHook.RememberVideoQuality() {
5565 )
5666 )
5767
58- /*
59- * The following code works by hooking the method which is called when the user selects a video quality
60- * to remember the last selected video quality.
61- *
62- * It also hooks the method which is called when the video quality to set is determined.
63- * Conveniently, at this point the video quality is overridden to the remembered playback speed.
64- */
6568 playerInitHooks.add { controller ->
6669 RememberVideoQualityPatch .newVideoStarted(controller)
6770 }
6871
72+ val YOUTUBE_VIDEO_QUALITY_CLASS_TYPE =
73+ " Lcom/google/android/libraries/youtube/innertube/model/media/VideoQuality;"
74+
75+ val videoQualityClass = DexClass (YOUTUBE_VIDEO_QUALITY_CLASS_TYPE ).toClass()
76+ val qualityNameField = videoQualityClass.findFirstFieldByExactType(String ::class .java)
77+ val resolutionField = videoQualityClass.findFirstFieldByExactType(Int ::class .java)
78+
79+ getQualityName = { quality -> qualityNameField.get(quality) as String }
80+ getResolution = { quality -> resolutionField.get(quality) as Int }
81+
82+ // Fix bad data used by YouTube.
83+ XposedBridge .hookAllConstructors(
84+ videoQualityClass, object : XC_MethodHook () {
85+ override fun afterHookedMethod (param : MethodHookParam ) {
86+ val quality = param.thisObject as VideoQuality
87+ val newResolution = RememberVideoQualityPatch .fixVideoQualityResolution(
88+ quality.getQualityName(), quality.getResolution()
89+ )
90+ resolutionField.set(quality, newResolution)
91+ }
92+ })
93+
6994 val videoQualitySetterFingerprint = getDexMethod(" videoQualitySetterFingerprint" ) {
7095 fingerprint {
7196 accessFlags(AccessFlags .PUBLIC , AccessFlags .FINAL )
@@ -82,7 +107,7 @@ fun YoutubeHook.RememberVideoQuality() {
82107 }
83108 }
84109
85- getDexMethod(" setQualityByIndexMethodClassFieldReferenceFingerprint " ) {
110+ getDexMethod(" setVideoQualityFingerprint " ) {
86111 fingerprint {
87112 returns(" V" )
88113 parameters(" L" )
@@ -96,40 +121,42 @@ fun YoutubeHook.RememberVideoQuality() {
96121 }
97122 }.also { method ->
98123 val usingFields = method.usingFields
99- getDexField(" getOnItemClickListenerClassReference " ) {
124+ getDexField(" onItemClickListenerClassReference " ) {
100125 usingFields[0 ].field
101126 }
102- getDexField(" getSetQualityByIndexMethodClassFieldReference " ) {
127+ getDexField(" setQualityFieldReference " ) {
103128 usingFields[1 ].field
104129 }
105- getDexMethod(" setQualityByIndexMethod" ) {
106- usingFields[1 ].field.type.findMethod { matcher { paramTypes(" int" ) } }.single()
130+ getDexMethod(" setQualityMenuIndexMethod" ) {
131+ usingFields[1 ].field.type.findMethod {
132+ matcher { addParamType { descriptor = YOUTUBE_VIDEO_QUALITY_CLASS_TYPE } }
133+ }.single()
107134 }
108135 }
109136 }
110137
111138 // Inject a call to set the remembered quality once a video loads.
112139 videoQualitySetterFingerprint.hookMethod(object : XC_MethodHook () {
113- val getOnItemClickListener = getDexField(" getOnItemClickListenerClassReference" ).toField()
114- val getSetQualityByIndexMethod =
115- getDexField(" getSetQualityByIndexMethodClassFieldReference" ).toField()
116- val setQualityByIndexMethod = getDexMethod(" setQualityByIndexMethod" ).name
140+ val onItemClickListenerClass = getDexField(" onItemClickListenerClassReference" ).toField()
141+ val setQualityField = getDexField(" setQualityFieldReference" ).toField()
142+ val setQualityMenuIndexMethod = getDexMethod(" setQualityMenuIndexMethod" ).toMethod()
117143
118144 @Suppress(" UNCHECKED_CAST" )
119145 override fun beforeHookedMethod (param : MethodHookParam ) {
120- val qualities = param.args[0 ] as Array <out Any >
146+ val qualities = param.args[0 ] as Array <out VideoQuality >
121147 val originalQualityIndex = param.args[1 ] as Int
122- val qInterface = param.thisObject.let { getOnItemClickListener .get(it) }
123- .let { getSetQualityByIndexMethod .get(it) }
124- val qIndexMethod = setQualityByIndexMethod
148+ val menu = param.thisObject.let { onItemClickListenerClass .get(it) }
149+ .let { setQualityField .get(it) }
150+
125151 param.args[1 ] = RememberVideoQualityPatch .setVideoQuality(
126- qualities, originalQualityIndex, qInterface, qIndexMethod
152+ qualities,
153+ { quality -> setQualityMenuIndexMethod(menu, quality) },
154+ originalQualityIndex
127155 )
128156 }
129157 })
130158
131-
132- // Inject a call to remember the selected quality.
159+ // Inject a call to remember the selected quality for Shorts.
133160 getDexMethod(" videoQualityItemOnClickParentFingerprint" ) {
134161 fingerprint {
135162 returns(" V" )
@@ -145,8 +172,8 @@ fun YoutubeHook.RememberVideoQuality() {
145172 }
146173 })
147174
148- // Remember video quality if not using old layout menu .
149- getDexMethod(" newVideoQualityChangedFingerprint " ) {
175+ // Inject a call to remember the user selected quality for regular videos .
176+ getDexMethod(" videoQualityChangedFingerprint " ) {
150177 fingerprint {
151178 accessFlags(AccessFlags .PUBLIC , AccessFlags .FINAL )
152179 methodMatcher {
@@ -172,7 +199,7 @@ fun YoutubeHook.RememberVideoQuality() {
172199 }.hookMethod(ScopedHook (getDexMethod(" VideoQualityReceiver" ).toMember()) {
173200 before {
174201 val selectedQualityIndex = param.args[0 ].getIntField(" a" )
175- RememberVideoQualityPatch .userChangedQualityInNewFlyout (selectedQualityIndex)
202+ RememberVideoQualityPatch .userChangedQuality (selectedQualityIndex)
176203 }
177204 })
178205}
0 commit comments