Skip to content

Commit

Permalink
feat(YouTube Music): Support version 7.25.52 inotia00/ReVanced_Exte…
Browse files Browse the repository at this point in the history
  • Loading branch information
inotia00 committed Dec 17, 2024
1 parent 98f26a2 commit a27a04d
Show file tree
Hide file tree
Showing 16 changed files with 466 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import androidx.annotation.NonNull;

import java.util.Map;

import app.revanced.extension.music.settings.Settings;
import app.revanced.extension.music.utils.VideoUtils;

Expand Down Expand Up @@ -40,6 +42,32 @@ public static void hideLikeDislikeButton(View view) {
);
}

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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@
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 {
Expand Down Expand Up @@ -150,9 +155,22 @@ public static void setShuffleState(Enum<?> shuffleState) {
}

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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

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;
Expand All @@ -18,6 +20,45 @@
*/
@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.
*/
Expand Down Expand Up @@ -49,7 +90,7 @@ public static Spanned onSpannedCreated(Spanned original) {
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -99,19 +97,11 @@ public class ReturnYouTubeDislike {
@GuardedBy("ReturnYouTubeDislike.class")
private static NumberFormat dislikePercentageFormatter;

public static final Rect leftSeparatorBounds;
private static final Rect middleSeparatorBounds;

static {
DisplayMetrics dp = Objects.requireNonNull(Utils.getContext()).getResources().getDisplayMetrics();
public static Rect leftSeparatorBounds;
private static Rect middleSeparatorBounds;

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);

static {
ReturnYouTubeDislikeApi.toastOnConnectionError = Settings.RYD_TOAST_ON_CONNECTION_ERROR.get();
}

Expand Down Expand Up @@ -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,
Expand All @@ -176,14 +183,27 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable,
}
}

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
Expand All @@ -200,7 +220,7 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable,
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,
Expand Down Expand Up @@ -416,12 +436,12 @@ public String getVideoId() {
* @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) {
Expand Down Expand Up @@ -456,7 +476,7 @@ private Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned original)
votingData.updateUsingVote(userVote);
}
originalDislikeSpan = original;
replacementLikeDislikeSpan = createDislikeSpan(original, votingData);
replacementLikeDislikeSpan = createDislikeSpan(original, votingData, isLithoText);
Logger.printDebug(() -> "Replaced: '" + originalDislikeSpan + "' with: '"
+ replacementLikeDislikeSpan + "'" + " using video: " + videoId);

Expand Down
Loading

0 comments on commit a27a04d

Please sign in to comment.