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 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_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_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 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 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);
|
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());
|
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.
|
* 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",
|
"Disable dislike redirection",
|
||||||
"Adds an option to disable redirection to the next track when clicking the Dislike button."
|
"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(
|
||||||
"Enable OPUS codec",
|
"Enable OPUS codec",
|
||||||
"Adds an options to enable the OPUS audio codec if the player response includes."
|
"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
|
private set
|
||||||
var is_6_42_or_greater = false
|
var is_6_42_or_greater = false
|
||||||
private set
|
private set
|
||||||
|
var is_7_03_or_greater = false
|
||||||
|
private set
|
||||||
var is_7_06_or_greater = false
|
var is_7_06_or_greater = false
|
||||||
private set
|
private set
|
||||||
var is_7_13_or_greater = false
|
var is_7_13_or_greater = false
|
||||||
@ -43,6 +45,7 @@ val versionCheckPatch = resourcePatch(
|
|||||||
is_6_27_or_greater = 234412000 <= playStoreServicesVersion
|
is_6_27_or_greater = 234412000 <= playStoreServicesVersion
|
||||||
is_6_36_or_greater = 240399000 <= playStoreServicesVersion
|
is_6_36_or_greater = 240399000 <= playStoreServicesVersion
|
||||||
is_6_42_or_greater = 240999000 <= playStoreServicesVersion
|
is_6_42_or_greater = 240999000 <= playStoreServicesVersion
|
||||||
|
is_7_03_or_greater = 242199000 <= playStoreServicesVersion
|
||||||
is_7_06_or_greater = 242499000 <= playStoreServicesVersion
|
is_7_06_or_greater = 242499000 <= playStoreServicesVersion
|
||||||
is_7_13_or_greater = 243199000 <= playStoreServicesVersion
|
is_7_13_or_greater = 243199000 <= playStoreServicesVersion
|
||||||
is_7_17_or_greater = 243530000 <= 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_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_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_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_title">Enable debug logging</string>
|
||||||
<string name="revanced_enable_debug_logging_summary">Prints the debug log.</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>
|
<string name="revanced_enable_debug_buffer_logging_title">Enable debug buffer logging</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user