This commit is contained in:
inotia00
2024-12-17 13:20:44 +09:00
parent 98f26a228a
commit a27a04d1f2
16 changed files with 465 additions and 132 deletions

View File

@ -7,6 +7,8 @@ import android.view.View;
import androidx.annotation.NonNull;
import java.util.Map;
import app.revanced.extension.music.settings.Settings;
import app.revanced.extension.music.utils.VideoUtils;
@ -40,6 +42,32 @@ public class ActionBarPatch {
);
}
private static final String senderView = "com.google.android.libraries.youtube.rendering.elements.sender_view";
public static boolean inAppDownloadButtonOnClick(Map mMap) {
if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) {
return false;
}
if (mMap == null || mMap.isEmpty()) {
return false;
}
if (!mMap.containsKey(senderView)) {
return false;
}
if (!(getLithoViewFromMap(mMap, senderView, View.class) instanceof View view)) {
return false;
}
VideoUtils.launchExternalDownloader();
return true;
}
/**
* Rest of the implementation added by patch.
*/
private static Object getLithoViewFromMap(Map mMap, Object mObject, Class<?> mClass) {
return null;
}
public static void inAppDownloadButtonOnClick(View view) {
if (!Settings.EXTERNAL_DOWNLOADER_ACTION_BUTTON.get()) {
return;

View File

@ -0,0 +1,98 @@
package app.revanced.extension.music.patches.components;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.components.ByteArrayFilterGroupList;
import app.revanced.extension.shared.patches.components.Filter;
import app.revanced.extension.shared.patches.components.StringFilterGroup;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public final class ActionButtonsFilter extends Filter {
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
private final StringFilterGroup actionBarRule;
private final StringFilterGroup bufferFilterPathRule;
private final StringFilterGroup likeDislikeContainer;
private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
private final ByteArrayFilterGroup downloadButton;
public ActionButtonsFilter() {
actionBarRule = new StringFilterGroup(
null,
VIDEO_ACTION_BAR_PATH_PREFIX
);
addIdentifierCallbacks(actionBarRule);
bufferFilterPathRule = new StringFilterGroup(
null,
"|ContainerType|button.eml|"
);
likeDislikeContainer = new StringFilterGroup(
Settings.HIDE_ACTION_BUTTON_LIKE_DISLIKE,
"segmented_like_dislike_button.eml"
);
addPathCallbacks(
bufferFilterPathRule,
likeDislikeContainer
);
bufferButtonsGroupList.addAll(
new ByteArrayFilterGroup(
Settings.HIDE_ACTION_BUTTON_COMMENT,
"yt_outline_message_bubble"
),
new ByteArrayFilterGroup(
Settings.HIDE_ACTION_BUTTON_ADD_TO_PLAYLIST,
"yt_outline_list_add"
),
new ByteArrayFilterGroup(
Settings.HIDE_ACTION_BUTTON_SHARE,
"yt_outline_share"
),
new ByteArrayFilterGroup(
Settings.HIDE_ACTION_BUTTON_RADIO,
"yt_outline_youtube_mix"
)
);
downloadButton = new ByteArrayFilterGroup(
Settings.HIDE_ACTION_BUTTON_DOWNLOAD,
"music_download_button"
);
}
private boolean isEveryFilterGroupEnabled() {
for (StringFilterGroup group : pathCallbacks)
if (!group.isEnabled()) return false;
for (ByteArrayFilterGroup group : bufferButtonsGroupList)
if (!group.isEnabled()) return false;
return downloadButton.isEnabled();
}
@Override
public boolean isFiltered(String path, @Nullable String identifier, String allValue, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (!path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX)) {
return false;
}
if (matchedGroup == actionBarRule && !isEveryFilterGroupEnabled()) {
return false;
}
if (contentType == FilterContentType.PATH) {
if (matchedGroup == bufferFilterPathRule) {
if (!bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) {
return false;
}
} else if (matchedGroup != likeDislikeContainer) {
if (!downloadButton.check(protobufBufferArray).isFiltered()) {
return false;
}
}
}
return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
}

View File

@ -8,11 +8,16 @@ import android.annotation.SuppressLint;
import android.graphics.Color;
import android.view.View;
import androidx.annotation.NonNull;
import java.util.Arrays;
import java.util.Objects;
import app.revanced.extension.music.settings.Settings;
import app.revanced.extension.music.shared.VideoType;
import app.revanced.extension.music.utils.VideoUtils;
import app.revanced.extension.shared.utils.Logger;
import app.revanced.extension.shared.utils.Utils;
@SuppressWarnings({"unused"})
public class PlayerPatch {
@ -150,9 +155,22 @@ public class PlayerPatch {
}
public static void shuffleTracks() {
shuffleTracks(false);
}
public static void shuffleTracksWithDelay() {
shuffleTracks(true);
}
private static void shuffleTracks(boolean needDelay) {
if (!Settings.ALWAYS_SHUFFLE.get())
return;
VideoUtils.shuffleTracks();
if (needDelay) {
Utils.runOnMainThreadDelayed(VideoUtils::shuffleTracks, 1000);
} else {
VideoUtils.shuffleTracks();
}
}
public static boolean rememberRepeatState(boolean original) {

View File

@ -2,8 +2,10 @@ package app.revanced.extension.music.patches.utils;
import static app.revanced.extension.shared.returnyoutubedislike.ReturnYouTubeDislike.Vote;
import android.text.SpannableString;
import android.text.Spanned;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.music.returnyoutubedislike.ReturnYouTubeDislike;
@ -18,6 +20,45 @@ import app.revanced.extension.shared.utils.Logger;
*/
@SuppressWarnings("unused")
public class ReturnYouTubeDislikePatch {
/**
* Injection point.
* <p>
* Called when a litho text component is initially created,
* and also when a Span is later reused again (such as scrolling off/on screen).
* <p>
* This method is sometimes called on the main thread, but it usually is called _off_ the main thread.
* This method can be called multiple times for the same UI element (including after dislikes was added).
*
* @param original Original char sequence was created or reused by Litho.
* @return The original char sequence (if nothing should change), or a replacement char sequence that contains dislikes.
*/
public static CharSequence onLithoTextLoaded(@NonNull Object conversionContext,
@NonNull CharSequence original) {
try {
if (!Settings.RYD_ENABLED.get()) {
return original;
}
String conversionContextString = conversionContext.toString();
if (!conversionContextString.contains("segmented_like_dislike_button.eml")) {
return original;
}
ReturnYouTubeDislike videoData = currentVideoData;
if (videoData == null) {
return original; // User enabled RYD while a video was on screen.
}
if (!(original instanceof Spanned)) {
original = new SpannableString(original);
}
return videoData.getDislikesSpan((Spanned) original, true);
} catch (Exception ex) {
Logger.printException(() -> "onLithoTextLoaded failure", ex);
}
return original;
}
/**
* RYD data for the current video on screen.
*/
@ -49,7 +90,7 @@ public class ReturnYouTubeDislikePatch {
if (videoData == null) {
return original; // User enabled RYD while a video was on screen.
}
return videoData.getDislikesSpan(original);
return videoData.getDislikesSpan(original, false);
} catch (Exception ex) {
Logger.printException(() -> "onSpannedCreated failure", ex);
}

View File

@ -74,8 +74,6 @@ public class ReturnYouTubeDislike {
*/
private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye'
private static final int SEPARATOR_COLOR = 872415231;
/**
* Cached lookup of all video ids.
*/
@ -99,19 +97,11 @@ public class ReturnYouTubeDislike {
@GuardedBy("ReturnYouTubeDislike.class")
private static NumberFormat dislikePercentageFormatter;
public static final Rect leftSeparatorBounds;
private static final Rect middleSeparatorBounds;
public static Rect leftSeparatorBounds;
private static Rect middleSeparatorBounds;
static {
DisplayMetrics dp = Objects.requireNonNull(Utils.getContext()).getResources().getDisplayMetrics();
leftSeparatorBounds = new Rect(0, 0,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1.2f, dp),
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 25, dp));
final int middleSeparatorSize =
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize);
ReturnYouTubeDislikeApi.toastOnConnectionError = Settings.RYD_TOAST_ON_CONNECTION_ERROR.get();
}
@ -150,9 +140,26 @@ public class ReturnYouTubeDislike {
@GuardedBy("this")
private SpannableString replacementLikeDislikeSpan;
/**
* Color of the left and middle separator, based on the color of the right separator.
* It's unknown where YT gets the color from, and the values here are approximated by hand.
* Ideally, this would be the actual color YT uses at runtime.
* <p>
* Older versions before the 'Me' library tab use a slightly different color.
* If spoofing was previously used and is now turned off,
* or an old version was recently upgraded then the old colors are sometimes still used.
*/
private static int getSeparatorColor(boolean isLithoText) {
return isLithoText
? 0x29AAAAAA
: 0x33FFFFFF;
}
@NonNull
private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable,
@NonNull RYDVoteData voteData) {
@NonNull RYDVoteData voteData,
boolean isLithoText) {
CharSequence oldLikes = oldSpannable;
// YouTube creators can hide the like count on a video,
@ -176,14 +183,27 @@ public class ReturnYouTubeDislike {
}
}
SpannableStringBuilder builder = new SpannableStringBuilder("\u2009\u2009");
SpannableStringBuilder builder = new SpannableStringBuilder("\u2009");
if (!isLithoText) {
builder.append("\u2009");
}
final boolean compactLayout = Settings.RYD_COMPACT_LAYOUT.get();
if (middleSeparatorBounds == null) {
final DisplayMetrics dp = Utils.getResources().getDisplayMetrics();
leftSeparatorBounds = new Rect(0, 0,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1.2f, dp),
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, isLithoText ? 23 : 25, dp));
final int middleSeparatorSize =
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize);
}
if (!compactLayout) {
String leftSeparatorString = "\u200E "; // u200E = left to right character
Spannable leftSeparatorSpan = new SpannableString(leftSeparatorString);
ShapeDrawable shapeDrawable = new ShapeDrawable(new RectShape());
shapeDrawable.getPaint().setColor(SEPARATOR_COLOR);
shapeDrawable.getPaint().setColor(getSeparatorColor(isLithoText));
shapeDrawable.setBounds(leftSeparatorBounds);
leftSeparatorSpan.setSpan(new VerticallyCenteredImageSpan(shapeDrawable), 1, 2,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE); // drawable cannot overwrite RTL or LTR character
@ -200,7 +220,7 @@ public class ReturnYouTubeDislike {
final int shapeInsertionIndex = middleSeparatorString.length() / 2;
Spannable middleSeparatorSpan = new SpannableString(middleSeparatorString);
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
shapeDrawable.getPaint().setColor(SEPARATOR_COLOR);
shapeDrawable.getPaint().setColor(getSeparatorColor(isLithoText));
shapeDrawable.setBounds(middleSeparatorBounds);
// Use original text width if using Rolling Number,
// to ensure the replacement styled span has the same width as the measured String,
@ -416,12 +436,12 @@ public class ReturnYouTubeDislike {
* @return the replacement span containing dislikes, or the original span if RYD is not available.
*/
@NonNull
public synchronized Spanned getDislikesSpan(@NonNull Spanned original) {
return waitForFetchAndUpdateReplacementSpan(original);
public synchronized Spanned getDislikesSpan(@NonNull Spanned original, boolean isLithoText) {
return waitForFetchAndUpdateReplacementSpan(original, isLithoText);
}
@NonNull
private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original) {
private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original, boolean isLithoText) {
try {
RYDVoteData votingData = getFetchData(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH);
if (votingData == null) {
@ -456,7 +476,7 @@ public class ReturnYouTubeDislike {
votingData.updateUsingVote(userVote);
}
originalDislikeSpan = original;
replacementLikeDislikeSpan = createDislikeSpan(original, votingData);
replacementLikeDislikeSpan = createDislikeSpan(original, votingData, isLithoText);
Logger.printDebug(() -> "Replaced: '" + originalDislikeSpan + "' with: '"
+ replacementLikeDislikeSpan + "'" + " using video: " + videoId);