From 88435902ff5daa876fccb176fa3f7c827928fc58 Mon Sep 17 00:00:00 2001
From: inotia00 <108592928+inotia00@users.noreply.github.com>
Date: Sat, 4 Jan 2025 19:18:10 +0900
Subject: [PATCH] feat(YouTube Music - Disable music video in album): Add
redirection option
---
.../patches/misc/AlbumMusicVideoPatch.java | 134 +++++++++++++++---
.../extension/music/settings/Settings.java | 3 +
.../ReVancedPreferenceFragment.java | 7 +-
.../music/misc/album/AlbumMusicVideoPatch.kt | 48 +++++++
.../patches/music/misc/album/Fingerprints.kt | 20 +++
.../music/settings/host/values/arrays.xml | 16 +++
.../music/settings/host/values/strings.xml | 12 +-
7 files changed, 217 insertions(+), 23 deletions(-)
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/AlbumMusicVideoPatch.java b/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/AlbumMusicVideoPatch.java
index d7ccccbad..08fca8a31 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/AlbumMusicVideoPatch.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/patches/misc/AlbumMusicVideoPatch.java
@@ -1,20 +1,58 @@
package app.revanced.extension.music.patches.misc;
+import android.view.View;
+
+import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
+import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import app.revanced.extension.music.patches.misc.requests.PipedRequester;
import app.revanced.extension.music.settings.Settings;
+import app.revanced.extension.music.shared.VideoInformation;
import app.revanced.extension.music.utils.VideoUtils;
import app.revanced.extension.shared.utils.Logger;
@SuppressWarnings("unused")
public class AlbumMusicVideoPatch {
- private static final String YOUTUBE_MUSIC_ALBUM_PREFIX = "OLAK";
+
+ public enum RedirectType {
+ REDIRECT_DISMISS(true),
+ REDIRECT(false),
+ ON_CLICK_DISMISS(true),
+ ON_CLICK(false),
+ ON_LONG_CLICK_DISMISS(true),
+ ON_LONG_CLICK(false);
+
+ public final boolean dismissQueue;
+
+ RedirectType(boolean dismissQueue) {
+ this.dismissQueue = dismissQueue;
+ }
+ }
+
+ private static final RedirectType REDIRECT_TYPE =
+ Settings.DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE.get();
+
private static final boolean DISABLE_MUSIC_VIDEO_IN_ALBUM =
Settings.DISABLE_MUSIC_VIDEO_IN_ALBUM.get();
+ private static final boolean DISMISS_QUEUE =
+ DISABLE_MUSIC_VIDEO_IN_ALBUM && REDIRECT_TYPE.dismissQueue;
+
+ private static final boolean REDIRECT =
+ REDIRECT_TYPE == RedirectType.REDIRECT || REDIRECT_TYPE == RedirectType.REDIRECT_DISMISS;
+
+ private static final boolean ON_CLICK =
+ REDIRECT_TYPE == RedirectType.ON_CLICK || REDIRECT_TYPE == RedirectType.ON_CLICK_DISMISS;
+
+ private static final boolean ON_LONG_CLICK =
+ REDIRECT_TYPE == RedirectType.ON_LONG_CLICK || REDIRECT_TYPE == RedirectType.ON_LONG_CLICK_DISMISS;
+
+ private static final String YOUTUBE_MUSIC_ALBUM_PREFIX = "OLAK";
+
private static final AtomicBoolean isVideoLaunched = new AtomicBoolean(false);
@NonNull
@@ -23,6 +61,16 @@ public class AlbumMusicVideoPatch {
@NonNull
private static volatile String currentVideoId = "";
+ @GuardedBy("itself")
+ private static final Map lastVideoIds = new LinkedHashMap<>() {
+ private static final int NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK = 5;
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK;
+ }
+ };
+
/**
* Injection point.
*/
@@ -41,7 +89,7 @@ public class AlbumMusicVideoPatch {
}
playerResponseVideoId = videoId;
- // Fetch from piped instances.
+ // Fetch Piped instance.
PipedRequester.fetchRequestIfNeeded(videoId, playlistId, playlistIndex);
}
@@ -56,13 +104,10 @@ public class AlbumMusicVideoPatch {
return;
}
currentVideoId = videoId;
-
- // If the user is using a not fast enough internet connection, there will be a slight delay.
- // Otherwise, the video may open repeatedly.
- VideoUtils.runOnMainThreadDelayed(() -> openOfficialMusicIfNeeded(videoId), 750);
+ checkVideo(videoId);
}
- private static void openOfficialMusicIfNeeded(@NonNull String videoId) {
+ private static void checkVideo(@NonNull String videoId) {
try {
PipedRequester request = PipedRequester.getRequestForVideoId(videoId);
if (request == null) {
@@ -72,13 +117,46 @@ public class AlbumMusicVideoPatch {
if (songId == null) {
return;
}
+ synchronized (lastVideoIds) {
+ if (lastVideoIds.put(videoId, songId) == null) {
+ Logger.printDebug(() -> "Official song found, videoId: " + videoId + ", songId: " + songId);
+ if (REDIRECT) {
+ openMusic(songId);
+ }
+ }
+ }
+ } catch (Exception ex) {
+ Logger.printException(() -> "check failure", ex);
+ }
+ }
- // It is handled by YouTube Music's internal code.
- // There is a slight delay before the dismiss request is reflected.
- VideoUtils.dismissQueue();
+ /**
+ * Injection point.
+ */
+ public static boolean openMusic() {
+ if (DISABLE_MUSIC_VIDEO_IN_ALBUM && ON_CLICK) {
+ try {
+ String videoId = VideoInformation.getVideoId();
+ synchronized (lastVideoIds) {
+ String songId = lastVideoIds.get(videoId);
+ if (songId != null) {
+ openMusic(songId);
+ return true;
+ }
+ }
+ } catch (Exception ex) {
+ Logger.printException(() -> "openMusic failure", ex);
+ }
+ }
+ return false;
+ }
+
+ private static void openMusic(@NonNull String songId) {
+ try {
+ if (DISMISS_QUEUE) {
+ VideoUtils.dismissQueue();
+ }
- // Every time a new video is opened, a snack bar appears indicating that the account has been switched.
- // To prevent this, hide the snack bar while a new video is opening.
isVideoLaunched.compareAndSet(false, true);
// The newly opened video is not a music video.
@@ -87,12 +165,33 @@ public class AlbumMusicVideoPatch {
playerResponseVideoId = songId;
currentVideoId = songId;
VideoUtils.openInYouTubeMusic(songId);
- }, 750);
+ }, 500);
- // If a new video is opened, the snack bar will be shown.
VideoUtils.runOnMainThreadDelayed(() -> isVideoLaunched.compareAndSet(true, false), 1500);
} catch (Exception ex) {
- Logger.printException(() -> "openOfficialMusicIfNeeded failure", ex);
+ Logger.printException(() -> "openMusic failure", ex);
+ }
+ }
+
+ /**
+ * Injection point.
+ */
+ public static void setAudioVideoSwitchToggleOnLongClickListener(View view) {
+ if (DISABLE_MUSIC_VIDEO_IN_ALBUM && ON_LONG_CLICK) {
+ view.setOnLongClickListener(v -> {
+ try {
+ String videoId = VideoInformation.getVideoId();
+ synchronized (lastVideoIds) {
+ String songId = lastVideoIds.get(videoId);
+ if (songId != null) {
+ openMusic(songId);
+ }
+ }
+ } catch (Exception ex) {
+ Logger.printException(() -> "onLongClickListener failure", ex);
+ }
+ return true;
+ });
}
}
@@ -100,10 +199,7 @@ public class AlbumMusicVideoPatch {
* Injection point.
*/
public static boolean hideSnackBar() {
- if (!DISABLE_MUSIC_VIDEO_IN_ALBUM) {
- return false;
- }
- return isVideoLaunched.get();
+ return DISABLE_MUSIC_VIDEO_IN_ALBUM && isVideoLaunched.get();
}
}
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/Settings.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/Settings.java
index 4ff5dc57e..f2638e371 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/music/settings/Settings.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/Settings.java
@@ -6,6 +6,7 @@ import static app.revanced.extension.music.sponsorblock.objects.CategoryBehaviou
import androidx.annotation.NonNull;
+import app.revanced.extension.music.patches.misc.AlbumMusicVideoPatch.RedirectType;
import app.revanced.extension.music.patches.misc.client.AppClient.ClientType;
import app.revanced.extension.music.patches.utils.PatchStatus;
import app.revanced.extension.music.sponsorblock.SponsorBlockSettings;
@@ -179,6 +180,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting DISABLE_CAIRO_SPLASH_ANIMATION = new BooleanSetting("revanced_disable_cairo_splash_animation", FALSE, true);
public static final BooleanSetting DISABLE_DRC_AUDIO = new BooleanSetting("revanced_disable_drc_audio", FALSE, true);
public static final BooleanSetting DISABLE_MUSIC_VIDEO_IN_ALBUM = new BooleanSetting("revanced_disable_music_video_in_album", FALSE, true);
+ public static final EnumSetting DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE = new EnumSetting<>("revanced_disable_music_video_in_album_redirect_type", RedirectType.REDIRECT_DISMISS, true);
public static final BooleanSetting ENABLE_OPUS_CODEC = new BooleanSetting("revanced_enable_opus_codec", FALSE, true);
public static final BooleanSetting SETTINGS_IMPORT_EXPORT = new BooleanSetting("revanced_extended_settings_import_export", FALSE, false);
public static final BooleanSetting SPOOF_CLIENT = new BooleanSetting("revanced_spoof_client", FALSE, true);
@@ -248,6 +250,7 @@ public class Settings extends BaseSettings {
CHANGE_START_PAGE.key,
CUSTOM_FILTER_STRINGS.key,
CUSTOM_PLAYBACK_SPEEDS.key,
+ DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE.key,
EXTERNAL_DOWNLOADER_PACKAGE_NAME.key,
HIDE_ACCOUNT_MENU_FILTER_STRINGS.key,
SB_API_URL.key,
diff --git a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java
index 313588ef9..1245e8dce 100644
--- a/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java
+++ b/extensions/shared/src/main/java/app/revanced/extension/music/settings/preference/ReVancedPreferenceFragment.java
@@ -4,6 +4,7 @@ import static app.revanced.extension.music.settings.Settings.BYPASS_IMAGE_REGION
import static app.revanced.extension.music.settings.Settings.CHANGE_START_PAGE;
import static app.revanced.extension.music.settings.Settings.CUSTOM_FILTER_STRINGS;
import static app.revanced.extension.music.settings.Settings.CUSTOM_PLAYBACK_SPEEDS;
+import static app.revanced.extension.music.settings.Settings.DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE;
import static app.revanced.extension.music.settings.Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME;
import static app.revanced.extension.music.settings.Settings.HIDE_ACCOUNT_MENU_FILTER_STRINGS;
import static app.revanced.extension.music.settings.Settings.OPEN_DEFAULT_APP_SETTINGS;
@@ -160,9 +161,11 @@ public class ReVancedPreferenceFragment extends PreferenceFragment {
Logger.printDebug(() -> "Failed to find the right value: " + dataString);
}
} else if (settings instanceof EnumSetting> enumSetting) {
- if (settings.equals(RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT)
+ if (settings.equals(DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE)
+ || settings.equals(RETURN_YOUTUBE_USERNAME_DISPLAY_FORMAT)
|| settings.equals(SPOOF_CLIENT_TYPE)
- || settings.equals(SPOOF_STREAMING_DATA_TYPE)) {
+ || settings.equals(SPOOF_STREAMING_DATA_TYPE)
+ ) {
ResettableListPreference.showDialog(mActivity, enumSetting, 0);
}
}
diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/album/AlbumMusicVideoPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/album/AlbumMusicVideoPatch.kt
index 51bf2d4c1..1479cc65f 100644
--- a/patches/src/main/kotlin/app/revanced/patches/music/misc/album/AlbumMusicVideoPatch.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/album/AlbumMusicVideoPatch.kt
@@ -2,6 +2,7 @@ package app.revanced.patches.music.misc.album
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
+import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.dismiss.dismissQueueHookPatch
@@ -9,13 +10,21 @@ import app.revanced.patches.music.utils.extension.Constants.MISC_PATH
import app.revanced.patches.music.utils.patch.PatchList.DISABLE_MUSIC_VIDEO_IN_ALBUM
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
+import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.music.video.information.videoIdHook
import app.revanced.patches.music.video.information.videoInformationPatch
import app.revanced.patches.music.video.playerresponse.hookPlayerResponse
import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
+import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.methodOrThrow
+import app.revanced.util.getReference
+import app.revanced.util.indexOfFirstInstructionReversedOrThrow
+import com.android.tools.smali.dexlib2.Opcode
+import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
+import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
+import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private const val EXTENSION_CLASS_DESCRIPTOR =
"$MISC_PATH/AlbumMusicVideoPatch;"
@@ -64,11 +73,50 @@ val albumMusicVideoPatch = bytecodePatch(
// endregion
+ // region patch for setOnClick / setOnLongClick listener
+
+ audioVideoSwitchToggleConstructorFingerprint.methodOrThrow().apply {
+ val onClickListenerIndex = indexOfAudioVideoSwitchSetOnClickListenerInstruction(this)
+ val viewRegister = getInstruction(onClickListenerIndex).registerC
+
+ addInstruction(
+ onClickListenerIndex + 1,
+ "invoke-static { v$viewRegister }, " +
+ "$EXTENSION_CLASS_DESCRIPTOR->setAudioVideoSwitchToggleOnLongClickListener(Landroid/view/View;)V"
+ )
+
+ val onClickListenerSyntheticIndex = indexOfFirstInstructionReversedOrThrow(onClickListenerIndex) {
+ opcode == Opcode.INVOKE_DIRECT &&
+ getReference()?.name == ""
+ }
+ val onClickListenerSyntheticClass = (getInstruction(onClickListenerSyntheticIndex).reference as MethodReference).definingClass
+
+ findMethodOrThrow(onClickListenerSyntheticClass) {
+ name == "onClick"
+ }.addInstructionsWithLabels(
+ 0, """
+ invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->openMusic()Z
+ move-result v0
+ if-eqz v0, :ignore
+ return-void
+ :ignore
+ nop
+ """
+ )
+ }
+
+ // endregion
+
addSwitchPreference(
CategoryType.MISC,
"revanced_disable_music_video_in_album",
"false"
)
+ addPreferenceWithIntent(
+ CategoryType.MISC,
+ "revanced_disable_music_video_in_album_redirect_type",
+ "revanced_disable_music_video_in_album"
+ )
updatePatchStatus(DISABLE_MUSIC_VIDEO_IN_ALBUM)
diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/album/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/album/Fingerprints.kt
index e91201b8c..52f0bf163 100644
--- a/patches/src/main/kotlin/app/revanced/patches/music/misc/album/Fingerprints.kt
+++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/album/Fingerprints.kt
@@ -1,9 +1,29 @@
package app.revanced.patches.music.misc.album
import app.revanced.util.fingerprint.legacyFingerprint
+import app.revanced.util.getReference
+import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
+import com.android.tools.smali.dexlib2.Opcode
+import com.android.tools.smali.dexlib2.iface.Method
+import com.android.tools.smali.dexlib2.iface.reference.MethodReference
+internal val audioVideoSwitchToggleConstructorFingerprint = legacyFingerprint(
+ name = "audioVideoSwitchToggleConstructorFingerprint",
+ accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
+ returnType = "V",
+ opcodes = listOf(Opcode.INVOKE_DIRECT),
+ customFingerprint = { method, _ ->
+ indexOfAudioVideoSwitchSetOnClickListenerInstruction(method) >= 0
+ }
+)
+
+internal fun indexOfAudioVideoSwitchSetOnClickListenerInstruction(method: Method) =
+ method.indexOfFirstInstruction {
+ opcode == Opcode.INVOKE_VIRTUAL &&
+ getReference()?.toString() == "Lcom/google/android/apps/youtube/music/player/AudioVideoSwitcherToggleView;->setOnClickListener(Landroid/view/View${'$'}OnClickListener;)V"
+ }
internal val snackBarParentFingerprint = legacyFingerprint(
name = "snackBarParentFingerprint",
diff --git a/patches/src/main/resources/music/settings/host/values/arrays.xml b/patches/src/main/resources/music/settings/host/values/arrays.xml
index e61708942..97897dac9 100644
--- a/patches/src/main/resources/music/settings/host/values/arrays.xml
+++ b/patches/src/main/resources/music/settings/host/values/arrays.xml
@@ -14,6 +14,22 @@
- FEmusic_library_landing
- FEmusic_library_corpus_artists
+
+ - @string/revanced_disable_music_video_in_album_redirect_type_entry_redirect_dismiss
+ - @string/revanced_disable_music_video_in_album_redirect_type_entry_redirect
+ - @string/revanced_disable_music_video_in_album_redirect_type_entry_on_click_dismiss
+ - @string/revanced_disable_music_video_in_album_redirect_type_entry_on_click
+ - @string/revanced_disable_music_video_in_album_redirect_type_entry_on_long_click_dismiss
+ - @string/revanced_disable_music_video_in_album_redirect_type_entry_on_long_click
+
+
+ - REDIRECT_DISMISS
+ - REDIRECT
+ - ON_CLICK_DISMISS
+ - ON_CLICK
+ - ON_LONG_CLICK_DISMISS
+ - ON_LONG_CLICK
+
- @string/revanced_extended_settings_export_as_file
- @string/revanced_extended_settings_import_as_file
diff --git a/patches/src/main/resources/music/settings/host/values/strings.xml b/patches/src/main/resources/music/settings/host/values/strings.xml
index ef3c97f92..e93faecae 100644
--- a/patches/src/main/resources/music/settings/host/values/strings.xml
+++ b/patches/src/main/resources/music/settings/host/values/strings.xml
@@ -423,9 +423,17 @@ Click to see how to issue a API key."
Disable music video in album
"When a non-premium user plays a song included in an album, the music video is sometimes played instead of the official song.
-If such a music video is detected playing, it is redirected to the official song.
+Find the official song if a music video is detected playing from an album.
-A piped instance is used, but the API may not be available in some regions."
+• Powered by Piped Instance API."
+ Redirection type
+ Specifies how to redirect to official song.
+ Redirect (Dismiss queue)
+ Redirect
+ Tap Audio / Video toggle (Dismiss queue)
+ Tap Audio / Video toggle
+ Tap and hold Audio / Video toggle (Dismiss queue)
+ Tap and hold Audio / Video toggle
Enable debug logging
Prints the debug log.
Enable debug buffer logging