Skip to content

Commit a27a04d

Browse files
committed
feat(YouTube Music): Support version 7.25.52 inotia00/ReVanced_Extended#2554
1 parent 98f26a2 commit a27a04d

File tree

16 files changed

+466
-133
lines changed

16 files changed

+466
-133
lines changed

extensions/shared/src/main/java/app/revanced/extension/music/patches/actionbar/ActionBarPatch.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import androidx.annotation.NonNull;
99

10+
import java.util.Map;
11+
1012
import app.revanced.extension.music.settings.Settings;
1113
import app.revanced.extension.music.utils.VideoUtils;
1214

@@ -40,6 +42,32 @@ public static void hideLikeDislikeButton(View view) {
4042
);
4143
}
4244

45+
private static final String senderView = "com.google.android.libraries.youtube.rendering.elements.sender_view";
46+
47+
public static boolean inAppDownloadButtonOnClick(Map mMap) {
48+
if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) {
49+
return false;
50+
}
51+
if (mMap == null || mMap.isEmpty()) {
52+
return false;
53+
}
54+
if (!mMap.containsKey(senderView)) {
55+
return false;
56+
}
57+
if (!(getLithoViewFromMap(mMap, senderView, View.class) instanceof View view)) {
58+
return false;
59+
}
60+
VideoUtils.launchExternalDownloader();
61+
return true;
62+
}
63+
64+
/**
65+
* Rest of the implementation added by patch.
66+
*/
67+
private static Object getLithoViewFromMap(Map mMap, Object mObject, Class<?> mClass) {
68+
return null;
69+
}
70+
4371
public static void inAppDownloadButtonOnClick(View view) {
4472
if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) {
4573
return;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package app.revanced.extension.music.patches.components;
2+
3+
import androidx.annotation.Nullable;
4+
5+
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
6+
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroupList;
7+
import app.revanced.extension.shared.patches.components.Filter;
8+
import app.revanced.extension.shared.patches.components.StringFilterGroup;
9+
import app.revanced.extension.music.settings.Settings;
10+
11+
@SuppressWarnings("unused")
12+
public final class ActionButtonsFilter extends Filter {
13+
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
14+
15+
private final StringFilterGroup actionBarRule;
16+
private final StringFilterGroup bufferFilterPathRule;
17+
private final StringFilterGroup likeDislikeContainer;
18+
private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
19+
private final ByteArrayFilterGroup downloadButton;
20+
21+
public ActionButtonsFilter() {
22+
actionBarRule = new StringFilterGroup(
23+
null,
24+
VIDEO_ACTION_BAR_PATH_PREFIX
25+
);
26+
addIdentifierCallbacks(actionBarRule);
27+
28+
bufferFilterPathRule = new StringFilterGroup(
29+
null,
30+
"|ContainerType|button.eml|"
31+
);
32+
likeDislikeContainer = new StringFilterGroup(
33+
Settings.HIDE_ACTION_BUTTON_LIKE_DISLIKE,
34+
"segmented_like_dislike_button.eml"
35+
);
36+
addPathCallbacks(
37+
bufferFilterPathRule,
38+
likeDislikeContainer
39+
);
40+
41+
bufferButtonsGroupList.addAll(
42+
new ByteArrayFilterGroup(
43+
Settings.HIDE_ACTION_BUTTON_COMMENT,
44+
"yt_outline_message_bubble"
45+
),
46+
new ByteArrayFilterGroup(
47+
Settings.HIDE_ACTION_BUTTON_ADD_TO_PLAYLIST,
48+
"yt_outline_list_add"
49+
),
50+
new ByteArrayFilterGroup(
51+
Settings.HIDE_ACTION_BUTTON_SHARE,
52+
"yt_outline_share"
53+
),
54+
new ByteArrayFilterGroup(
55+
Settings.HIDE_ACTION_BUTTON_RADIO,
56+
"yt_outline_youtube_mix"
57+
)
58+
);
59+
downloadButton = new ByteArrayFilterGroup(
60+
Settings.HIDE_ACTION_BUTTON_DOWNLOAD,
61+
"music_download_button"
62+
);
63+
}
64+
65+
private boolean isEveryFilterGroupEnabled() {
66+
for (StringFilterGroup group : pathCallbacks)
67+
if (!group.isEnabled()) return false;
68+
69+
for (ByteArrayFilterGroup group : bufferButtonsGroupList)
70+
if (!group.isEnabled()) return false;
71+
72+
return downloadButton.isEnabled();
73+
}
74+
75+
@Override
76+
public boolean isFiltered(String path, @Nullable String identifier, String allValue, byte[] protobufBufferArray,
77+
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
78+
if (!path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX)) {
79+
return false;
80+
}
81+
if (matchedGroup == actionBarRule && !isEveryFilterGroupEnabled()) {
82+
return false;
83+
}
84+
if (contentType == FilterContentType.PATH) {
85+
if (matchedGroup == bufferFilterPathRule) {
86+
if (!bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) {
87+
return false;
88+
}
89+
} else if (matchedGroup != likeDislikeContainer) {
90+
if (!downloadButton.check(protobufBufferArray).isFiltered()) {
91+
return false;
92+
}
93+
}
94+
}
95+
96+
return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex);
97+
}
98+
}

extensions/shared/src/main/java/app/revanced/extension/music/patches/player/PlayerPatch.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,16 @@
88
import android.graphics.Color;
99
import android.view.View;
1010

11+
import androidx.annotation.NonNull;
12+
1113
import java.util.Arrays;
14+
import java.util.Objects;
1215

1316
import app.revanced.extension.music.settings.Settings;
1417
import app.revanced.extension.music.shared.VideoType;
1518
import app.revanced.extension.music.utils.VideoUtils;
19+
import app.revanced.extension.shared.utils.Logger;
20+
import app.revanced.extension.shared.utils.Utils;
1621

1722
@SuppressWarnings({"unused"})
1823
public class PlayerPatch {
@@ -150,9 +155,22 @@ public static void setShuffleState(Enum<?> shuffleState) {
150155
}
151156

152157
public static void shuffleTracks() {
158+
shuffleTracks(false);
159+
}
160+
161+
public static void shuffleTracksWithDelay() {
162+
shuffleTracks(true);
163+
}
164+
165+
private static void shuffleTracks(boolean needDelay) {
153166
if (!Settings.ALWAYS_SHUFFLE.get())
154167
return;
155-
VideoUtils.shuffleTracks();
168+
169+
if (needDelay) {
170+
Utils.runOnMainThreadDelayed(VideoUtils::shuffleTracks, 1000);
171+
} else {
172+
VideoUtils.shuffleTracks();
173+
}
156174
}
157175

158176
public static boolean rememberRepeatState(boolean original) {

extensions/shared/src/main/java/app/revanced/extension/music/patches/utils/ReturnYouTubeDislikePatch.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import static app.revanced.extension.shared.returnyoutubedislike.ReturnYouTubeDislike.Vote;
44

5+
import android.text.SpannableString;
56
import android.text.Spanned;
67

8+
import androidx.annotation.NonNull;
79
import androidx.annotation.Nullable;
810

911
import app.revanced.extension.music.returnyoutubedislike.ReturnYouTubeDislike;
@@ -18,6 +20,45 @@
1820
*/
1921
@SuppressWarnings("unused")
2022
public class ReturnYouTubeDislikePatch {
23+
24+
/**
25+
* Injection point.
26+
* <p>
27+
* Called when a litho text component is initially created,
28+
* and also when a Span is later reused again (such as scrolling off/on screen).
29+
* <p>
30+
* This method is sometimes called on the main thread, but it usually is called _off_ the main thread.
31+
* This method can be called multiple times for the same UI element (including after dislikes was added).
32+
*
33+
* @param original Original char sequence was created or reused by Litho.
34+
* @return The original char sequence (if nothing should change), or a replacement char sequence that contains dislikes.
35+
*/
36+
public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext,
37+
@NonNull CharSequence original) {
38+
try {
39+
if (!Settings.RYD_ENABLED.get()) {
40+
return original;
41+
}
42+
43+
String conversionContextString = conversionContext.toString();
44+
45+
if (!conversionContextString.contains("segmented_like_dislike_button.eml")) {
46+
return original;
47+
}
48+
ReturnYouTubeDislike videoData = currentVideoData;
49+
if (videoData == null) {
50+
return original; // User enabled RYD while a video was on screen.
51+
}
52+
if (!(original instanceof Spanned)) {
53+
original = new SpannableString(original);
54+
}
55+
return videoData.getDislikesSpan((Spanned) original, true);
56+
} catch (Exception ex) {
57+
Logger.printException(() -> "onLithoTextLoaded failure", ex);
58+
}
59+
return original;
60+
}
61+
2162
/**
2263
* RYD data for the current video on screen.
2364
*/
@@ -49,7 +90,7 @@ public static Spanned onSpannedCreated(Spanned original) {
4990
if (videoData == null) {
5091
return original; // User enabled RYD while a video was on screen.
5192
}
52-
return videoData.getDislikesSpan(original);
93+
return videoData.getDislikesSpan(original, false);
5394
} catch (Exception ex) {
5495
Logger.printException(() -> "onSpannedCreated failure", ex);
5596
}

extensions/shared/src/main/java/app/revanced/extension/music/returnyoutubedislike/ReturnYouTubeDislike.java

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ public class ReturnYouTubeDislike {
7474
*/
7575
private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye'
7676

77-
private static final int SEPARATOR_COLOR = 872415231;
78-
7977
/**
8078
* Cached lookup of all video ids.
8179
*/
@@ -99,19 +97,11 @@ public class ReturnYouTubeDislike {
9997
@GuardedBy("ReturnYouTubeDislike.class")
10098
private static NumberFormat dislikePercentageFormatter;
10199

102-
public static final Rect leftSeparatorBounds;
103-
private static final Rect middleSeparatorBounds;
104-
105-
static {
106-
DisplayMetrics dp = Objects.requireNonNull(Utils.getContext()).getResources().getDisplayMetrics();
100+
public static Rect leftSeparatorBounds;
101+
private static Rect middleSeparatorBounds;
107102

108-
leftSeparatorBounds = new Rect(0, 0,
109-
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1.2f, dp),
110-
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 25, dp));
111-
final int middleSeparatorSize =
112-
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
113-
middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize);
114103

104+
static {
115105
ReturnYouTubeDislikeApi.toastOnConnectionError = Settings.RYD_TOAST_ON_CONNECTION_ERROR.get();
116106
}
117107

@@ -150,9 +140,26 @@ public class ReturnYouTubeDislike {
150140
@GuardedBy("this")
151141
private SpannableString replacementLikeDislikeSpan;
152142

143+
144+
/**
145+
* Color of the left and middle separator, based on the color of the right separator.
146+
* It's unknown where YT gets the color from, and the values here are approximated by hand.
147+
* Ideally, this would be the actual color YT uses at runtime.
148+
* <p>
149+
* Older versions before the 'Me' library tab use a slightly different color.
150+
* If spoofing was previously used and is now turned off,
151+
* or an old version was recently upgraded then the old colors are sometimes still used.
152+
*/
153+
private static int getSeparatorColor(boolean isLithoText) {
154+
return isLithoText
155+
? 0x29AAAAAA
156+
: 0x33FFFFFF;
157+
}
158+
153159
@NonNull
154160
private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable,
155-
@NonNull RYDVoteData voteData) {
161+
@NonNull RYDVoteData voteData,
162+
boolean isLithoText) {
156163
CharSequence oldLikes = oldSpannable;
157164

158165
// YouTube creators can hide the like count on a video,
@@ -176,14 +183,27 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable,
176183
}
177184
}
178185

179-
SpannableStringBuilder builder = new SpannableStringBuilder("\u2009\u2009");
186+
SpannableStringBuilder builder = new SpannableStringBuilder("\u2009");
187+
if (!isLithoText) {
188+
builder.append("\u2009");
189+
}
180190
final boolean compactLayout = Settings.RYD_COMPACT_LAYOUT.get();
181191

192+
if (middleSeparatorBounds == null) {
193+
final DisplayMetrics dp = Utils.getResources().getDisplayMetrics();
194+
leftSeparatorBounds = new Rect(0, 0,
195+
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1.2f, dp),
196+
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, isLithoText ? 23 : 25, dp));
197+
final int middleSeparatorSize =
198+
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
199+
middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize);
200+
}
201+
182202
if (!compactLayout) {
183203
String leftSeparatorString = "\u200E "; // u200E = left to right character
184204
Spannable leftSeparatorSpan = new SpannableString(leftSeparatorString);
185205
ShapeDrawable shapeDrawable = new ShapeDrawable(new RectShape());
186-
shapeDrawable.getPaint().setColor(SEPARATOR_COLOR);
206+
shapeDrawable.getPaint().setColor(getSeparatorColor(isLithoText));
187207
shapeDrawable.setBounds(leftSeparatorBounds);
188208
leftSeparatorSpan.setSpan(new VerticallyCenteredImageSpan(shapeDrawable), 1, 2,
189209
Spannable.SPAN_INCLUSIVE_EXCLUSIVE); // drawable cannot overwrite RTL or LTR character
@@ -200,7 +220,7 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable,
200220
final int shapeInsertionIndex = middleSeparatorString.length() / 2;
201221
Spannable middleSeparatorSpan = new SpannableString(middleSeparatorString);
202222
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
203-
shapeDrawable.getPaint().setColor(SEPARATOR_COLOR);
223+
shapeDrawable.getPaint().setColor(getSeparatorColor(isLithoText));
204224
shapeDrawable.setBounds(middleSeparatorBounds);
205225
// Use original text width if using Rolling Number,
206226
// to ensure the replacement styled span has the same width as the measured String,
@@ -416,12 +436,12 @@ public String getVideoId() {
416436
* @return the replacement span containing dislikes, or the original span if RYD is not available.
417437
*/
418438
@NonNull
419-
public synchronized Spanned getDislikesSpan(@NonNull Spanned original) {
420-
return waitForFetchAndUpdateReplacementSpan(original);
439+
public synchronized Spanned getDislikesSpan(@NonNull Spanned original, boolean isLithoText) {
440+
return waitForFetchAndUpdateReplacementSpan(original, isLithoText);
421441
}
422442

423443
@NonNull
424-
private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original) {
444+
private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, boolean isLithoText) {
425445
try {
426446
RYDVoteData votingData = getFetchData(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH);
427447
if (votingData == null) {
@@ -456,7 +476,7 @@ private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original)
456476
votingData.updateUsingVote(userVote);
457477
}
458478
originalDislikeSpan = original;
459-
replacementLikeDislikeSpan = createDislikeSpan(original, votingData);
479+
replacementLikeDislikeSpan = createDislikeSpan(original, votingData, isLithoText);
460480
Logger.printDebug(() -> "Replaced: '" + originalDislikeSpan + "' with: '"
461481
+ replacementLikeDislikeSpan + "'" + " using video: " + videoId);
462482

0 commit comments

Comments
 (0)