mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-05-29 05:10:20 +02:00
feat(YouTube Music): Add Disable music video in album
patch https://github.com/inotia00/ReVanced_Extended/issues/2568
This commit is contained in:
parent
1da2664513
commit
8ab67bc6ef
@ -0,0 +1,109 @@
|
||||
package app.revanced.extension.music.patches.misc;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
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.utils.VideoUtils;
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class AlbumMusicVideoPatch {
|
||||
private static final String YOUTUBE_MUSIC_ALBUM_PREFIX = "OLAK";
|
||||
private static final boolean DISABLE_MUSIC_VIDEO_IN_ALBUM =
|
||||
Settings.DISABLE_MUSIC_VIDEO_IN_ALBUM.get();
|
||||
|
||||
private static final AtomicBoolean isVideoLaunched = new AtomicBoolean(false);
|
||||
|
||||
@NonNull
|
||||
private static volatile String playerResponseVideoId = "";
|
||||
|
||||
@NonNull
|
||||
private static volatile String currentVideoId = "";
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void newPlayerResponse(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) {
|
||||
if (!DISABLE_MUSIC_VIDEO_IN_ALBUM) {
|
||||
return;
|
||||
}
|
||||
if (!playlistId.startsWith(YOUTUBE_MUSIC_ALBUM_PREFIX)) {
|
||||
return;
|
||||
}
|
||||
if (playlistIndex < 0) {
|
||||
return;
|
||||
}
|
||||
if (playerResponseVideoId.equals(videoId)) {
|
||||
return;
|
||||
}
|
||||
playerResponseVideoId = videoId;
|
||||
|
||||
// Fetch from piped instances.
|
||||
PipedRequester.fetchRequestIfNeeded(videoId, playlistId, playlistIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void newVideoLoaded(@NonNull String videoId) {
|
||||
if (!DISABLE_MUSIC_VIDEO_IN_ALBUM) {
|
||||
return;
|
||||
}
|
||||
if (currentVideoId.equals(videoId)) {
|
||||
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);
|
||||
}
|
||||
|
||||
private static void openOfficialMusicIfNeeded(@NonNull String videoId) {
|
||||
try {
|
||||
PipedRequester request = PipedRequester.getRequestForVideoId(videoId);
|
||||
if (request == null) {
|
||||
return;
|
||||
}
|
||||
String songId = request.getStream();
|
||||
if (songId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// It is handled by YouTube Music's internal code.
|
||||
// There is a slight delay before the dismiss request is reflected.
|
||||
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.
|
||||
// To prevent fetch requests from being sent, set the video id to the newly opened video
|
||||
VideoUtils.runOnMainThreadDelayed(() -> {
|
||||
playerResponseVideoId = songId;
|
||||
currentVideoId = songId;
|
||||
VideoUtils.openInYouTubeMusic(songId);
|
||||
}, 750);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean hideSnackBar() {
|
||||
if (!DISABLE_MUSIC_VIDEO_IN_ALBUM) {
|
||||
return false;
|
||||
}
|
||||
return isVideoLaunched.get();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
package app.revanced.extension.music.patches.misc.requests;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import app.revanced.extension.shared.requests.Requester;
|
||||
import app.revanced.extension.shared.utils.Logger;
|
||||
import app.revanced.extension.shared.utils.Utils;
|
||||
|
||||
public class PipedRequester {
|
||||
/**
|
||||
* How long to keep fetches until they are expired.
|
||||
*/
|
||||
private static final long CACHE_RETENTION_TIME_MILLISECONDS = 60 * 1000; // 1 Minute
|
||||
|
||||
private static final long MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000; // 20 seconds
|
||||
|
||||
@GuardedBy("itself")
|
||||
private static final Map<String, PipedRequester> cache = new HashMap<>();
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
public static void fetchRequestIfNeeded(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) {
|
||||
synchronized (cache) {
|
||||
final long now = System.currentTimeMillis();
|
||||
|
||||
cache.values().removeIf(request -> {
|
||||
final boolean expired = request.isExpired(now);
|
||||
if (expired) Logger.printDebug(() -> "Removing expired stream: " + request.videoId);
|
||||
return expired;
|
||||
});
|
||||
|
||||
if (!cache.containsKey(videoId)) {
|
||||
PipedRequester pipedRequester = new PipedRequester(videoId, playlistId, playlistIndex);
|
||||
cache.put(videoId, pipedRequester);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PipedRequester getRequestForVideoId(@Nullable String videoId) {
|
||||
synchronized (cache) {
|
||||
return cache.get(videoId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TCP timeout
|
||||
*/
|
||||
private static final int TIMEOUT_TCP_DEFAULT_MILLISECONDS = 2 * 1000; // 2 seconds
|
||||
|
||||
/**
|
||||
* HTTP response timeout
|
||||
*/
|
||||
private static final int TIMEOUT_HTTP_DEFAULT_MILLISECONDS = 4 * 1000; // 4 seconds
|
||||
|
||||
@Nullable
|
||||
private static JSONObject send(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
Logger.printDebug(() -> "Fetching piped instances (videoId: '" + videoId +
|
||||
"', playlistId: '" + playlistId + "', playlistIndex: '" + playlistIndex + "'");
|
||||
|
||||
try {
|
||||
HttpURLConnection connection = PipedRoutes.getPlaylistConnectionFromRoute(playlistId);
|
||||
connection.setConnectTimeout(TIMEOUT_TCP_DEFAULT_MILLISECONDS);
|
||||
connection.setReadTimeout(TIMEOUT_HTTP_DEFAULT_MILLISECONDS);
|
||||
|
||||
final int responseCode = connection.getResponseCode();
|
||||
if (responseCode == 200) return Requester.parseJSONObject(connection);
|
||||
|
||||
handleConnectionError("API not available: " + responseCode);
|
||||
} catch (SocketTimeoutException ex) {
|
||||
handleConnectionError("Connection timeout", ex);
|
||||
} catch (IOException ex) {
|
||||
handleConnectionError("Network error", ex);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "send failed", ex);
|
||||
} finally {
|
||||
Logger.printDebug(() -> "playlist: " + playlistId + " took: " + (System.currentTimeMillis() - startTime) + "ms");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String fetch(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) {
|
||||
final JSONObject playlistJson = send(videoId, playlistId, playlistIndex);
|
||||
if (playlistJson != null) {
|
||||
try {
|
||||
final String songId = playlistJson.getJSONArray("relatedStreams")
|
||||
.getJSONObject(playlistIndex)
|
||||
.getString("url")
|
||||
.replaceAll("/.+=", "");
|
||||
if (songId.isEmpty()) {
|
||||
handleConnectionError("Url is empty!");
|
||||
} else if (!songId.equals(videoId)) {
|
||||
return songId;
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Logger.printDebug(() -> "Fetch failed while processing response data for response: " + playlistJson);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void handleConnectionError(@NonNull String errorMessage) {
|
||||
handleConnectionError(errorMessage, null);
|
||||
}
|
||||
|
||||
private static void handleConnectionError(@NonNull String errorMessage, @Nullable Exception ex) {
|
||||
if (ex != null) {
|
||||
Logger.printInfo(() -> errorMessage, ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Time this instance and the fetch future was created.
|
||||
*/
|
||||
private final long timeFetched;
|
||||
private final String videoId;
|
||||
private final Future<String> future;
|
||||
|
||||
private PipedRequester(@NonNull String videoId, @NonNull String playlistId, final int playlistIndex) {
|
||||
this.timeFetched = System.currentTimeMillis();
|
||||
this.videoId = videoId;
|
||||
this.future = Utils.submitOnBackgroundThread(() -> fetch(videoId, playlistId, playlistIndex));
|
||||
}
|
||||
|
||||
public boolean isExpired(long now) {
|
||||
final long timeSinceCreation = now - timeFetched;
|
||||
if (timeSinceCreation > CACHE_RETENTION_TIME_MILLISECONDS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only expired if the fetch failed (API null response).
|
||||
return (fetchCompleted() && getStream() == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the fetch call has completed.
|
||||
*/
|
||||
public boolean fetchCompleted() {
|
||||
return future.isDone();
|
||||
}
|
||||
|
||||
public String getStream() {
|
||||
try {
|
||||
return future.get(MAX_MILLISECONDS_TO_WAIT_FOR_FETCH, TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException ex) {
|
||||
Logger.printInfo(() -> "getStream timed out", ex);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.printException(() -> "getStream interrupted", ex);
|
||||
Thread.currentThread().interrupt(); // Restore interrupt status flag.
|
||||
} catch (ExecutionException ex) {
|
||||
Logger.printException(() -> "getStream failure", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package app.revanced.extension.music.patches.misc.requests;
|
||||
|
||||
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import app.revanced.extension.shared.requests.Requester;
|
||||
import app.revanced.extension.shared.requests.Route;
|
||||
|
||||
class PipedRoutes {
|
||||
private static final String PIPED_URL = "https://pipedapi.kavin.rocks/";
|
||||
private static final Route GET_PLAYLIST = new Route(GET, "playlists/{playlist_id}");
|
||||
|
||||
private PipedRoutes() {
|
||||
}
|
||||
|
||||
static HttpURLConnection getPlaylistConnectionFromRoute(String... params) throws IOException {
|
||||
return Requester.getConnectionFromRoute(PIPED_URL, GET_PLAYLIST, params);
|
||||
}
|
||||
|
||||
}
|
@ -178,6 +178,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting CHANGE_SHARE_SHEET = new BooleanSetting("revanced_change_share_sheet", FALSE, true);
|
||||
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 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);
|
||||
|
@ -71,6 +71,13 @@ public class VideoUtils extends IntentUtils {
|
||||
launchView(url, context.getPackageName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Rest of the implementation added by patch.
|
||||
*/
|
||||
public static void dismissQueue() {
|
||||
Log.d("Extended: VideoUtils", "Queue dismissed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Rest of the implementation added by patch.
|
||||
*/
|
||||
|
@ -0,0 +1,85 @@
|
||||
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.patch.bytecodePatch
|
||||
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||
import app.revanced.patches.music.utils.dismiss.dismissQueueHookPatch
|
||||
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.playservice.is_7_03_or_greater
|
||||
import app.revanced.patches.music.utils.playservice.versionCheckPatch
|
||||
import app.revanced.patches.music.utils.settings.CategoryType
|
||||
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
|
||||
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.util.fingerprint.methodOrThrow
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"$MISC_PATH/AlbumMusicVideoPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val albumMusicVideoPatch = bytecodePatch(
|
||||
DISABLE_MUSIC_VIDEO_IN_ALBUM.title,
|
||||
DISABLE_MUSIC_VIDEO_IN_ALBUM.summary,
|
||||
false,
|
||||
) {
|
||||
compatibleWith(COMPATIBLE_PACKAGE)
|
||||
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
dismissQueueHookPatch,
|
||||
videoInformationPatch,
|
||||
versionCheckPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
|
||||
// region hook player response
|
||||
|
||||
val fingerprint = if (is_7_03_or_greater) {
|
||||
playerParameterBuilderFingerprint
|
||||
} else {
|
||||
playerParameterBuilderLegacyFingerprint
|
||||
}
|
||||
|
||||
fingerprint.methodOrThrow().addInstruction(
|
||||
0,
|
||||
"invoke-static {p1, p4, p5}, $EXTENSION_CLASS_DESCRIPTOR->newPlayerResponse(Ljava/lang/String;Ljava/lang/String;I)V"
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
// region hook video id
|
||||
|
||||
videoIdHook("$EXTENSION_CLASS_DESCRIPTOR->newVideoLoaded(Ljava/lang/String;)V")
|
||||
|
||||
// endregion
|
||||
|
||||
// region patch for hide snack bar
|
||||
|
||||
snackBarParentFingerprint.methodOrThrow().addInstructionsWithLabels(
|
||||
0, """
|
||||
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->hideSnackBar()Z
|
||||
move-result v0
|
||||
if-eqz v0, :hide
|
||||
return-void
|
||||
:hide
|
||||
nop
|
||||
"""
|
||||
)
|
||||
|
||||
// endregion
|
||||
|
||||
addSwitchPreference(
|
||||
CategoryType.MISC,
|
||||
"revanced_disable_music_video_in_album",
|
||||
"false"
|
||||
)
|
||||
|
||||
updatePatchStatus(DISABLE_MUSIC_VIDEO_IN_ALBUM)
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package app.revanced.patches.music.misc.album
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.or
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
/**
|
||||
* For targets 7.03 and later.
|
||||
*/
|
||||
internal val playerParameterBuilderFingerprint = legacyFingerprint(
|
||||
name = "playerParameterBuilderFingerprint",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "L",
|
||||
parameters = listOf(
|
||||
"Ljava/lang/String;", // VideoId.
|
||||
"[B",
|
||||
"Ljava/lang/String;", // Player parameters proto buffer.
|
||||
"Ljava/lang/String;", // PlaylistId.
|
||||
"I", // PlaylistIndex.
|
||||
"I",
|
||||
"L",
|
||||
"Ljava/util/Set;",
|
||||
"Ljava/lang/String;",
|
||||
"Ljava/lang/String;",
|
||||
"L",
|
||||
"Z",
|
||||
"Z",
|
||||
"Z", // Appears to indicate if the video id is being opened or is currently playing.
|
||||
),
|
||||
strings = listOf("psps")
|
||||
)
|
||||
|
||||
/**
|
||||
* For targets 7.02 and earlier.
|
||||
*/
|
||||
internal val playerParameterBuilderLegacyFingerprint = legacyFingerprint(
|
||||
name = "playerParameterBuilderLegacyFingerprint",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "L",
|
||||
parameters = listOf(
|
||||
"Ljava/lang/String;", // VideoId.
|
||||
"[B",
|
||||
"Ljava/lang/String;", // Player parameters proto buffer.
|
||||
"Ljava/lang/String;", // PlaylistId.
|
||||
"I", // PlaylistIndex.
|
||||
"I",
|
||||
"Ljava/util/Set;",
|
||||
"Ljava/lang/String;",
|
||||
"Ljava/lang/String;",
|
||||
"L",
|
||||
"Z",
|
||||
"Z", // Appears to indicate if the video id is being opened or is currently playing.
|
||||
),
|
||||
opcodes = listOf(
|
||||
Opcode.INVOKE_INTERFACE,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CHECK_CAST,
|
||||
Opcode.INVOKE_INTERFACE
|
||||
)
|
||||
)
|
||||
|
||||
internal val snackBarParentFingerprint = legacyFingerprint(
|
||||
name = "snackBarParentFingerprint",
|
||||
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||
returnType = "V",
|
||||
parameters = listOf("L"),
|
||||
strings = listOf("No suitable parent found from the given view. Please provide a valid view.")
|
||||
)
|
@ -0,0 +1,42 @@
|
||||
package app.revanced.patches.music.utils.dismiss
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.music.utils.extension.Constants.EXTENSION_PATH
|
||||
import app.revanced.util.addStaticFieldToExtension
|
||||
import app.revanced.util.fingerprint.methodOrThrow
|
||||
import app.revanced.util.getWalkerMethod
|
||||
|
||||
private const val EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR =
|
||||
"$EXTENSION_PATH/utils/VideoUtils;"
|
||||
|
||||
@Suppress("unused")
|
||||
val dismissQueueHookPatch = bytecodePatch(
|
||||
description = "dismissQueueHookPatch"
|
||||
) {
|
||||
|
||||
execute {
|
||||
|
||||
dismissQueueFingerprint.methodOrThrow().apply {
|
||||
val dismissQueueIndex = indexOfDismissQueueInstruction(this)
|
||||
|
||||
getWalkerMethod(dismissQueueIndex).apply {
|
||||
val smaliInstructions =
|
||||
"""
|
||||
if-eqz v0, :ignore
|
||||
invoke-virtual {v0}, $definingClass->$name()V
|
||||
:ignore
|
||||
return-void
|
||||
"""
|
||||
|
||||
addStaticFieldToExtension(
|
||||
EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR,
|
||||
"dismissQueue",
|
||||
"dismissQueueClass",
|
||||
definingClass,
|
||||
smaliInstructions
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package app.revanced.patches.music.utils.dismiss
|
||||
|
||||
import app.revanced.util.fingerprint.legacyFingerprint
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
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 dismissQueueFingerprint = legacyFingerprint(
|
||||
name = "dismissQueueFingerprint",
|
||||
returnType = "V",
|
||||
parameters = listOf("L"),
|
||||
customFingerprint = { method, _ ->
|
||||
method.name == "handleDismissWatchEvent" &&
|
||||
indexOfDismissQueueInstruction(method) >= 0
|
||||
}
|
||||
)
|
||||
|
||||
internal fun indexOfDismissQueueInstruction(method: Method) =
|
||||
method.indexOfFirstInstruction {
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
getReference<MethodReference>()?.definingClass?.endsWith("/MppWatchWhileLayout;") == true
|
||||
}
|
@ -57,6 +57,10 @@ internal enum class PatchList(
|
||||
"Disable dislike redirection",
|
||||
"Adds an option to disable redirection to the next track when clicking the Dislike button."
|
||||
),
|
||||
DISABLE_MUSIC_VIDEO_IN_ALBUM(
|
||||
"Disable music video in album",
|
||||
"Adds option to redirect music videos from albums."
|
||||
),
|
||||
ENABLE_OPUS_CODEC(
|
||||
"Enable OPUS codec",
|
||||
"Adds an options to enable the OPUS audio codec if the player response includes."
|
||||
|
@ -11,6 +11,8 @@ var is_6_36_or_greater = false
|
||||
private set
|
||||
var is_6_42_or_greater = false
|
||||
private set
|
||||
var is_7_03_or_greater = false
|
||||
private set
|
||||
var is_7_06_or_greater = false
|
||||
private set
|
||||
var is_7_13_or_greater = false
|
||||
@ -43,6 +45,7 @@ val versionCheckPatch = resourcePatch(
|
||||
is_6_27_or_greater = 234412000 <= playStoreServicesVersion
|
||||
is_6_36_or_greater = 240399000 <= playStoreServicesVersion
|
||||
is_6_42_or_greater = 240999000 <= playStoreServicesVersion
|
||||
is_7_03_or_greater = 242199000 <= playStoreServicesVersion
|
||||
is_7_06_or_greater = 242499000 <= playStoreServicesVersion
|
||||
is_7_13_or_greater = 243199000 <= playStoreServicesVersion
|
||||
is_7_17_or_greater = 243530000 <= playStoreServicesVersion
|
||||
|
@ -420,6 +420,12 @@ Click to see how to issue a API key."</string>
|
||||
<string name="revanced_disable_cairo_splash_animation_summary">Disables Cairo splash animation when the app starts up.</string>
|
||||
<string name="revanced_disable_drc_audio_title">Disable DRC audio</string>
|
||||
<string name="revanced_disable_drc_audio_summary">Disables DRC (Dynamic Range Compression) applied to audio.</string>
|
||||
<string name="revanced_disable_music_video_in_album_title">Disable music video in album</string>
|
||||
<string name="revanced_disable_music_video_in_album_summary">"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.
|
||||
|
||||
A piped instance is used, but the API may not be available in some regions."</string>
|
||||
<string name="revanced_enable_debug_logging_title">Enable debug logging</string>
|
||||
<string name="revanced_enable_debug_logging_summary">Prints the debug log.</string>
|
||||
<string name="revanced_enable_debug_buffer_logging_title">Enable debug buffer logging</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user