mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-05-29 13:20:19 +02:00
feat(YouTube Music): Add Spoof player parameter
patch https://github.com/inotia00/ReVanced_Extended/issues/2832
This commit is contained in:
parent
806976b6d8
commit
d4af0f1ee0
@ -0,0 +1,174 @@
|
|||||||
|
package app.revanced.extension.music.patches.misc;
|
||||||
|
|
||||||
|
import static app.revanced.extension.music.shared.VideoInformation.parameterIsAgeRestricted;
|
||||||
|
import static app.revanced.extension.music.shared.VideoInformation.parameterIsSample;
|
||||||
|
|
||||||
|
import androidx.annotation.GuardedBy;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import app.revanced.extension.music.settings.Settings;
|
||||||
|
import app.revanced.extension.music.shared.VideoInformation;
|
||||||
|
import app.revanced.extension.shared.utils.Logger;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class SpoofPlayerParameterPatch {
|
||||||
|
/**
|
||||||
|
* Used in YouTube Music.
|
||||||
|
*/
|
||||||
|
private static final boolean SPOOF_PLAYER_PARAMETER = Settings.SPOOF_PLAYER_PARAMETER.get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameter to fix playback issues.
|
||||||
|
* Used in YouTube Music Samples.
|
||||||
|
*/
|
||||||
|
private static final String PLAYER_PARAMETER_SAMPLES =
|
||||||
|
"8AEB2AUBogYVAUY4C8W9wrM-FdhjSW4MnCgH44uhkAcI";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameter to fix playback issues.
|
||||||
|
* Used in YouTube Shorts.
|
||||||
|
*/
|
||||||
|
private static final String PLAYER_PARAMETER_SHORTS =
|
||||||
|
"8AEByAMkuAQ0ogYVAePzwRN3uesV1sPI2x4-GkDYlvqUkAcC";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On app first start, the first video played usually contains a single non-default window setting value
|
||||||
|
* and all other subtitle settings for the video are (incorrect) default Samples window settings.
|
||||||
|
* For this situation, the Samples settings must be replaced.
|
||||||
|
* <p>
|
||||||
|
* But some videos use multiple text positions on screen (such as youtu.be/3hW1rMNC89o),
|
||||||
|
* and by chance many of the subtitles uses window positions that match a default Samples position.
|
||||||
|
* To handle these videos, selectively allowing the Samples specific window settings to 'pass thru' unchanged,
|
||||||
|
* but only if the video contains multiple non-default subtitle window positions.
|
||||||
|
* <p>
|
||||||
|
* Do not enable 'pass thru mode' until this many non default subtitle settings are observed for a single video.
|
||||||
|
*/
|
||||||
|
private static final int NUMBER_OF_NON_DEFAULT_SUBTITLES_BEFORE_ENABLING_PASSTHRU = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of non default subtitle settings encountered for the current video.
|
||||||
|
*/
|
||||||
|
private static int numberOfNonDefaultSettingsObserved;
|
||||||
|
|
||||||
|
@GuardedBy("itself")
|
||||||
|
private static final Map<String, Boolean> lastVideoIds = new LinkedHashMap<>() {
|
||||||
|
private static final int NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK = 5;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean removeEldestEntry(Entry eldest) {
|
||||||
|
return size() > NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static String spoofParameter(@NonNull String videoId, @Nullable String parameter) {
|
||||||
|
if (SPOOF_PLAYER_PARAMETER) {
|
||||||
|
synchronized (lastVideoIds) {
|
||||||
|
Boolean isSamples = parameterIsSample(parameter);
|
||||||
|
if (lastVideoIds.put(videoId, isSamples) == null) {
|
||||||
|
Logger.printDebug(() -> "New video loaded (videoId: " + videoId + ", isSamples: " + isSamples + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parameterIsAgeRestricted(parameter)
|
||||||
|
? PLAYER_PARAMETER_SHORTS
|
||||||
|
: PLAYER_PARAMETER_SAMPLES;
|
||||||
|
}
|
||||||
|
return parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point. Overrides values passed into SubtitleWindowSettings constructor.
|
||||||
|
*
|
||||||
|
* @param ap anchor position. A bitmask with 6 bit fields, that appears to indicate the layout position on screen
|
||||||
|
* @param ah anchor horizontal. A percentage [0, 100], that appears to be a horizontal text anchor point
|
||||||
|
* @param av anchor vertical. A percentage [0, 100], that appears to be a vertical text anchor point
|
||||||
|
* @param vs appears to indicate if subtitles exist, and the value is always true.
|
||||||
|
* @param sd function is not entirely clear
|
||||||
|
*/
|
||||||
|
public static int[] fixSubtitleWindowPosition(int ap, int ah, int av, boolean vs, boolean sd) {
|
||||||
|
// Videos with custom captions that specify screen positions appear to always have correct screen positions (even with spoofing).
|
||||||
|
// But for auto generated and most other captions, the spoof incorrectly gives various default Samples caption settings.
|
||||||
|
// Check for these known default Samples captions parameters, and replace with the known correct values.
|
||||||
|
//
|
||||||
|
// If a regular video uses a custom subtitle setting that match a default Samples setting,
|
||||||
|
// then this will incorrectly replace the setting.
|
||||||
|
// But, if the video uses multiple subtitles in different screen locations, then detect the non-default values
|
||||||
|
// and do not replace any window settings for the video (regardless if they match a Samples default).
|
||||||
|
if (SPOOF_PLAYER_PARAMETER &&
|
||||||
|
numberOfNonDefaultSettingsObserved < NUMBER_OF_NON_DEFAULT_SUBTITLES_BEFORE_ENABLING_PASSTHRU) {
|
||||||
|
synchronized (lastVideoIds) {
|
||||||
|
String videoId = VideoInformation.getVideoId();
|
||||||
|
Boolean isSample = lastVideoIds.get(videoId);
|
||||||
|
if (BooleanUtils.isFalse(isSample)) {
|
||||||
|
for (SubtitleWindowReplacementSettings setting : SubtitleWindowReplacementSettings.values()) {
|
||||||
|
if (setting.match(ap, ah, av, vs, sd)) {
|
||||||
|
return setting.replacementSetting();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
numberOfNonDefaultSettingsObserved++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new int[]{ap, ah, av};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* <p>
|
||||||
|
* Return false to force disable age restricted playback feature flag.
|
||||||
|
*/
|
||||||
|
public static boolean forceDisableAgeRestrictedPlaybackFeatureFlag(boolean original) {
|
||||||
|
if (SPOOF_PLAYER_PARAMETER) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Known incorrect default Samples subtitle parameters, and the corresponding correct (non-Samples) values.
|
||||||
|
*/
|
||||||
|
private enum SubtitleWindowReplacementSettings {
|
||||||
|
DEFAULT_SAMPLES_PARAMETERS_1(10, 50, 0, true, false,
|
||||||
|
34, 50, 95),
|
||||||
|
DEFAULT_SAMPLES_PARAMETERS_2(9, 20, 0, true, false,
|
||||||
|
34, 50, 90),
|
||||||
|
DEFAULT_SAMPLES_PARAMETERS_3(9, 20, 0, true, true,
|
||||||
|
33, 20, 100);
|
||||||
|
|
||||||
|
// original values
|
||||||
|
final int ap, ah, av;
|
||||||
|
final boolean vs, sd;
|
||||||
|
|
||||||
|
// replacement int values
|
||||||
|
final int[] replacement;
|
||||||
|
|
||||||
|
SubtitleWindowReplacementSettings(int ap, int ah, int av, boolean vs, boolean sd,
|
||||||
|
int replacementAp, int replacementAh, int replacementAv) {
|
||||||
|
this.ap = ap;
|
||||||
|
this.ah = ah;
|
||||||
|
this.av = av;
|
||||||
|
this.vs = vs;
|
||||||
|
this.sd = sd;
|
||||||
|
this.replacement = new int[]{replacementAp, replacementAh, replacementAv};
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean match(int ap, int ah, int av, boolean vs, boolean sd) {
|
||||||
|
return this.ap == ap && this.ah == ah && this.av == av && this.vs == vs && this.sd == sd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] replacementSetting() {
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -190,6 +190,7 @@ public class Settings extends BaseSettings {
|
|||||||
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 DISABLE_MUSIC_VIDEO_IN_ALBUM = new BooleanSetting("revanced_disable_music_video_in_album", FALSE, true);
|
||||||
public static final EnumSetting<RedirectType> DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE = new EnumSetting<>("revanced_disable_music_video_in_album_redirect_type", RedirectType.REDIRECT, true);
|
public static final EnumSetting<RedirectType> DISABLE_MUSIC_VIDEO_IN_ALBUM_REDIRECT_TYPE = new EnumSetting<>("revanced_disable_music_video_in_album_redirect_type", RedirectType.REDIRECT, true);
|
||||||
|
public static final BooleanSetting SPOOF_PLAYER_PARAMETER = new BooleanSetting("revanced_spoof_player_parameter", TRUE, 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);
|
||||||
|
|
||||||
// PreferenceScreen: Return YouTube Dislike
|
// PreferenceScreen: Return YouTube Dislike
|
||||||
|
@ -22,12 +22,25 @@ public final class VideoInformation {
|
|||||||
private static final float DEFAULT_YOUTUBE_MUSIC_PLAYBACK_SPEED = 1.0f;
|
private static final float DEFAULT_YOUTUBE_MUSIC_PLAYBACK_SPEED = 1.0f;
|
||||||
private static final int DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY = -2;
|
private static final int DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY = -2;
|
||||||
private static final String DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY_STRING = getString("quality_auto");
|
private static final String DEFAULT_YOUTUBE_MUSIC_VIDEO_QUALITY_STRING = getString("quality_auto");
|
||||||
|
/**
|
||||||
|
* Prefix present in all Age-restricted music player parameters signature.
|
||||||
|
*/
|
||||||
|
private static final String AGE_RESTRICTED_PLAYER_PARAMETER = "ygYQ";
|
||||||
|
/**
|
||||||
|
* Prefix present in all Sample player parameters signature.
|
||||||
|
*/
|
||||||
|
private static final String SAMPLES_PLAYER_PARAMETERS = "8AEB";
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private static String videoId = "";
|
private static String videoId = "";
|
||||||
|
|
||||||
private static long videoLength = 0;
|
private static long videoLength = 0;
|
||||||
private static long videoTime = -1;
|
private static long videoTime = -1;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static volatile String playerResponseVideoId = "";
|
||||||
|
private static volatile boolean playerResponseVideoIdIsSample;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current playback speed
|
* The current playback speed
|
||||||
*/
|
*/
|
||||||
@ -85,6 +98,65 @@ public final class VideoInformation {
|
|||||||
videoId = newlyLoadedVideoId;
|
videoId = newlyLoadedVideoId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Differs from {@link #videoId} as this is the video id for the
|
||||||
|
* last player response received, which may not be the last video opened.
|
||||||
|
* <p>
|
||||||
|
* If Shorts are loading the background, this commonly will be
|
||||||
|
* different from the Short that is currently on screen.
|
||||||
|
* <p>
|
||||||
|
* For most use cases, you should instead use {@link #getVideoId()}.
|
||||||
|
*
|
||||||
|
* @return The id of the last video loaded, or an empty string if no videos have been loaded yet.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static String getPlayerResponseVideoId() {
|
||||||
|
return playerResponseVideoId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the last player response video id was a Sample.
|
||||||
|
*/
|
||||||
|
public static boolean lastPlayerResponseIsSample() {
|
||||||
|
return playerResponseVideoIdIsSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point. Called off the main thread.
|
||||||
|
*
|
||||||
|
* @param videoId The id of the last video loaded.
|
||||||
|
*/
|
||||||
|
public static void setPlayerResponseVideoId(@NonNull String videoId) {
|
||||||
|
if (!playerResponseVideoId.equals(videoId)) {
|
||||||
|
playerResponseVideoId = videoId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the player parameter is for a Age-restricted video.
|
||||||
|
*/
|
||||||
|
public static boolean parameterIsAgeRestricted(@Nullable String parameter) {
|
||||||
|
return parameter != null && parameter.startsWith(AGE_RESTRICTED_PLAYER_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the player parameter is for a Sample.
|
||||||
|
*/
|
||||||
|
public static boolean parameterIsSample(@Nullable String parameter) {
|
||||||
|
return parameter != null && parameter.startsWith(SAMPLES_PLAYER_PARAMETERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static String newPlayerResponseParameter(@NonNull String videoId, @Nullable String playerParameter) {
|
||||||
|
playerResponseVideoIdIsSample = parameterIsSample(playerParameter);
|
||||||
|
Logger.printDebug(() -> "videoId: " + videoId + ", playerParameter: " + playerParameter);
|
||||||
|
|
||||||
|
return playerParameter; // Return the original value since we are observing and not modifying.
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seek on the current video.
|
* Seek on the current video.
|
||||||
* Does not function for playback of Shorts.
|
* Does not function for playback of Shorts.
|
||||||
|
@ -14,7 +14,8 @@ import app.revanced.patches.music.utils.settings.addSwitchPreference
|
|||||||
import app.revanced.patches.music.utils.settings.settingsPatch
|
import app.revanced.patches.music.utils.settings.settingsPatch
|
||||||
import app.revanced.patches.music.video.information.videoIdHook
|
import app.revanced.patches.music.video.information.videoIdHook
|
||||||
import app.revanced.patches.music.video.information.videoInformationPatch
|
import app.revanced.patches.music.video.information.videoInformationPatch
|
||||||
import app.revanced.patches.music.video.playerresponse.hookPlayerResponse
|
import app.revanced.patches.music.video.playerresponse.Hook
|
||||||
|
import app.revanced.patches.music.video.playerresponse.addPlayerResponseMethodHook
|
||||||
import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
|
import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
|
||||||
import app.revanced.util.findMethodOrThrow
|
import app.revanced.util.findMethodOrThrow
|
||||||
import app.revanced.util.fingerprint.methodOrThrow
|
import app.revanced.util.fingerprint.methodOrThrow
|
||||||
@ -46,7 +47,11 @@ val albumMusicVideoPatch = bytecodePatch(
|
|||||||
|
|
||||||
// region hook player response
|
// region hook player response
|
||||||
|
|
||||||
hookPlayerResponse("$EXTENSION_CLASS_DESCRIPTOR->newPlayerResponse(Ljava/lang/String;Ljava/lang/String;I)V")
|
addPlayerResponseMethodHook(
|
||||||
|
Hook.VideoIdAndPlaylistId(
|
||||||
|
"$EXTENSION_CLASS_DESCRIPTOR->newPlayerResponse(Ljava/lang/String;Ljava/lang/String;I)V"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package app.revanced.patches.music.utils.fix.parameter
|
||||||
|
|
||||||
|
import app.revanced.util.fingerprint.legacyFingerprint
|
||||||
|
import app.revanced.util.or
|
||||||
|
import com.android.tools.smali.dexlib2.AccessFlags
|
||||||
|
|
||||||
|
internal val subtitleWindowFingerprint = legacyFingerprint(
|
||||||
|
name = "subtitleWindowFingerprint",
|
||||||
|
returnType = "V",
|
||||||
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
|
||||||
|
parameters = listOf("I", "I", "I", "Z", "Z"),
|
||||||
|
strings = listOf("invalid anchorHorizontalPos: %s"),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this flag is activated, a playback issue occurs in age-restricted videos.
|
||||||
|
*/
|
||||||
|
internal const val AGE_RESTRICTED_PLAYBACK_FEATURE_FLAG = 45651506L
|
||||||
|
|
||||||
|
internal val ageRestrictedPlaybackFeatureFlagFingerprint = legacyFingerprint(
|
||||||
|
name = "ageRestrictedPlaybackFeatureFlagFingerprint",
|
||||||
|
returnType = "Z",
|
||||||
|
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
|
||||||
|
parameters = emptyList(),
|
||||||
|
literals = listOf(AGE_RESTRICTED_PLAYBACK_FEATURE_FLAG),
|
||||||
|
)
|
@ -0,0 +1,82 @@
|
|||||||
|
package app.revanced.patches.music.utils.fix.parameter
|
||||||
|
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
|
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
|
||||||
|
import app.revanced.patches.music.utils.extension.Constants.MISC_PATH
|
||||||
|
import app.revanced.patches.music.utils.patch.PatchList.SPOOF_PLAYER_PARAMETER
|
||||||
|
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.videoInformationPatch
|
||||||
|
import app.revanced.patches.music.video.playerresponse.Hook
|
||||||
|
import app.revanced.patches.music.video.playerresponse.addPlayerResponseMethodHook
|
||||||
|
import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
|
||||||
|
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
|
||||||
|
import app.revanced.util.fingerprint.methodOrThrow
|
||||||
|
import app.revanced.util.fingerprint.resolvable
|
||||||
|
|
||||||
|
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||||
|
"$MISC_PATH/SpoofPlayerParameterPatch;"
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
val spoofPlayerParameterPatch = bytecodePatch(
|
||||||
|
SPOOF_PLAYER_PARAMETER.title,
|
||||||
|
SPOOF_PLAYER_PARAMETER.summary
|
||||||
|
) {
|
||||||
|
compatibleWith(COMPATIBLE_PACKAGE)
|
||||||
|
|
||||||
|
dependsOn(
|
||||||
|
settingsPatch,
|
||||||
|
videoInformationPatch,
|
||||||
|
playerResponseMethodHookPatch,
|
||||||
|
)
|
||||||
|
|
||||||
|
execute {
|
||||||
|
|
||||||
|
addPlayerResponseMethodHook(
|
||||||
|
Hook.PlayerParameter(
|
||||||
|
"$EXTENSION_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// region fix for subtitles position
|
||||||
|
|
||||||
|
subtitleWindowFingerprint.methodOrThrow().addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-static {p1, p2, p3, p4, p5}, $EXTENSION_CLASS_DESCRIPTOR->fixSubtitleWindowPosition(IIIZZ)[I
|
||||||
|
move-result-object v0
|
||||||
|
const/4 v1, 0x0
|
||||||
|
aget p1, v0, v1 # ap, anchor position
|
||||||
|
const/4 v1, 0x1
|
||||||
|
aget p2, v0, v1 # ah, horizontal anchor
|
||||||
|
const/4 v1, 0x2
|
||||||
|
aget p3, v0, v1 # av, vertical anchor
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region fix for feature flags
|
||||||
|
|
||||||
|
if (ageRestrictedPlaybackFeatureFlagFingerprint.resolvable()) {
|
||||||
|
ageRestrictedPlaybackFeatureFlagFingerprint.injectLiteralInstructionBooleanCall(
|
||||||
|
AGE_RESTRICTED_PLAYBACK_FEATURE_FLAG,
|
||||||
|
"$EXTENSION_CLASS_DESCRIPTOR->forceDisableAgeRestrictedPlaybackFeatureFlag(Z)Z"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
addSwitchPreference(
|
||||||
|
CategoryType.MISC,
|
||||||
|
"revanced_spoof_player_parameter",
|
||||||
|
"true"
|
||||||
|
)
|
||||||
|
|
||||||
|
updatePatchStatus(SPOOF_PLAYER_PARAMETER)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -157,6 +157,10 @@ internal enum class PatchList(
|
|||||||
"Spoof client",
|
"Spoof client",
|
||||||
"Adds options to spoof the client to allow playback."
|
"Adds options to spoof the client to allow playback."
|
||||||
),
|
),
|
||||||
|
SPOOF_PLAYER_PARAMETER(
|
||||||
|
"Spoof player parameter",
|
||||||
|
"Adds options to spoof player parameter to allow playback."
|
||||||
|
),
|
||||||
TRANSLATIONS_FOR_YOUTUBE_MUSIC(
|
TRANSLATIONS_FOR_YOUTUBE_MUSIC(
|
||||||
"Translations for YouTube Music",
|
"Translations for YouTube Music",
|
||||||
"Add translations or remove string resources."
|
"Add translations or remove string resources."
|
||||||
|
@ -13,6 +13,9 @@ import app.revanced.patches.music.utils.extension.Constants.SHARED_PATH
|
|||||||
import app.revanced.patches.music.utils.playbackSpeedFingerprint
|
import app.revanced.patches.music.utils.playbackSpeedFingerprint
|
||||||
import app.revanced.patches.music.utils.playbackSpeedParentFingerprint
|
import app.revanced.patches.music.utils.playbackSpeedParentFingerprint
|
||||||
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
|
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
|
||||||
|
import app.revanced.patches.music.video.playerresponse.Hook
|
||||||
|
import app.revanced.patches.music.video.playerresponse.addPlayerResponseMethodHook
|
||||||
|
import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
|
||||||
import app.revanced.patches.shared.mdxPlayerDirectorSetVideoStageFingerprint
|
import app.revanced.patches.shared.mdxPlayerDirectorSetVideoStageFingerprint
|
||||||
import app.revanced.patches.shared.videoLengthFingerprint
|
import app.revanced.patches.shared.videoLengthFingerprint
|
||||||
import app.revanced.util.addStaticFieldToExtension
|
import app.revanced.util.addStaticFieldToExtension
|
||||||
@ -71,7 +74,10 @@ private var videoTimeConstructorInsertIndex = 2
|
|||||||
val videoInformationPatch = bytecodePatch(
|
val videoInformationPatch = bytecodePatch(
|
||||||
description = "videoInformationPatch",
|
description = "videoInformationPatch",
|
||||||
) {
|
) {
|
||||||
dependsOn(sharedResourceIdPatch)
|
dependsOn(
|
||||||
|
playerResponseMethodHookPatch,
|
||||||
|
sharedResourceIdPatch
|
||||||
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
fun addSeekInterfaceMethods(
|
fun addSeekInterfaceMethods(
|
||||||
@ -241,7 +247,18 @@ val videoInformationPatch = bytecodePatch(
|
|||||||
* Set current video id
|
* Set current video id
|
||||||
*/
|
*/
|
||||||
videoIdHook("$EXTENSION_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V")
|
videoIdHook("$EXTENSION_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V")
|
||||||
|
addPlayerResponseMethodHook(
|
||||||
|
Hook.VideoId(
|
||||||
|
"$EXTENSION_CLASS_DESCRIPTOR->setPlayerResponseVideoId(Ljava/lang/String;)V"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
// Call before any other video id hooks,
|
||||||
|
// so they can use VideoInformation and check if the video id is for a Short.
|
||||||
|
addPlayerResponseMethodHook(
|
||||||
|
Hook.PlayerParameterBeforeVideoId(
|
||||||
|
"$EXTENSION_CLASS_DESCRIPTOR->newPlayerResponseParameter(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
|
||||||
|
)
|
||||||
|
)
|
||||||
/**
|
/**
|
||||||
* Hook current playback speed
|
* Hook current playback speed
|
||||||
*/
|
*/
|
||||||
|
@ -11,8 +11,7 @@ private val PLAYER_PARAMETER_STARTS_WITH_PARAMETER_LIST = listOf(
|
|||||||
"[B",
|
"[B",
|
||||||
"Ljava/lang/String;", // Player parameters proto buffer.
|
"Ljava/lang/String;", // Player parameters proto buffer.
|
||||||
"Ljava/lang/String;", // PlaylistId.
|
"Ljava/lang/String;", // PlaylistId.
|
||||||
"I", // PlaylistIndex.
|
"I" // PlaylistIndex.
|
||||||
"I"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,7 +29,7 @@ internal val playerParameterBuilderFingerprint = legacyFingerprint(
|
|||||||
return@custom false
|
return@custom false
|
||||||
}
|
}
|
||||||
|
|
||||||
val startsWithMethodParameterList = parameterTypes.slice(0..5)
|
val startsWithMethodParameterList = parameterTypes.slice(0..4)
|
||||||
|
|
||||||
parametersEqual(
|
parametersEqual(
|
||||||
PLAYER_PARAMETER_STARTS_WITH_PARAMETER_LIST,
|
PLAYER_PARAMETER_STARTS_WITH_PARAMETER_LIST,
|
||||||
|
@ -1,22 +1,35 @@
|
|||||||
package app.revanced.patches.music.video.playerresponse
|
package app.revanced.patches.music.video.playerresponse
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||||
|
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||||
|
import app.revanced.patches.music.utils.extension.sharedExtensionPatch
|
||||||
import app.revanced.patches.music.utils.playservice.is_7_03_or_greater
|
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.playservice.versionCheckPatch
|
||||||
import app.revanced.util.fingerprint.methodOrThrow
|
import app.revanced.util.fingerprint.methodOrThrow
|
||||||
|
|
||||||
|
private val hooks = mutableSetOf<Hook>()
|
||||||
|
|
||||||
|
fun addPlayerResponseMethodHook(hook: Hook) {
|
||||||
|
hooks += hook
|
||||||
|
}
|
||||||
|
|
||||||
private const val REGISTER_VIDEO_ID = "p1"
|
private const val REGISTER_VIDEO_ID = "p1"
|
||||||
|
private const val REGISTER_PLAYER_PARAMETER = "p3"
|
||||||
private const val REGISTER_PLAYLIST_ID = "p4"
|
private const val REGISTER_PLAYLIST_ID = "p4"
|
||||||
private const val REGISTER_PLAYLIST_INDEX = "p5"
|
private const val REGISTER_PLAYLIST_INDEX = "p5"
|
||||||
|
|
||||||
private lateinit var playerResponseMethod: MutableMethod
|
private lateinit var playerResponseMethod: MutableMethod
|
||||||
|
private var numberOfInstructionsAdded = 0
|
||||||
|
|
||||||
val playerResponseMethodHookPatch = bytecodePatch(
|
val playerResponseMethodHookPatch = bytecodePatch(
|
||||||
description = "playerResponseMethodHookPatch"
|
description = "playerResponseMethodHookPatch"
|
||||||
) {
|
) {
|
||||||
dependsOn(versionCheckPatch)
|
dependsOn(
|
||||||
|
sharedExtensionPatch,
|
||||||
|
versionCheckPatch,
|
||||||
|
)
|
||||||
|
|
||||||
execute {
|
execute {
|
||||||
playerResponseMethod = if (is_7_03_or_greater) {
|
playerResponseMethod = if (is_7_03_or_greater) {
|
||||||
@ -25,16 +38,70 @@ val playerResponseMethodHookPatch = bytecodePatch(
|
|||||||
playerParameterBuilderLegacyFingerprint
|
playerParameterBuilderLegacyFingerprint
|
||||||
}.methodOrThrow()
|
}.methodOrThrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalize {
|
||||||
|
fun hookVideoId(hook: Hook) {
|
||||||
|
playerResponseMethod.addInstruction(
|
||||||
|
0,
|
||||||
|
"invoke-static {$REGISTER_VIDEO_ID}, $hook",
|
||||||
|
)
|
||||||
|
numberOfInstructionsAdded++
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hookVideoIdAndPlaylistId(hook: Hook) {
|
||||||
|
playerResponseMethod.addInstruction(
|
||||||
|
0,
|
||||||
|
"invoke-static {$REGISTER_VIDEO_ID, $REGISTER_PLAYLIST_ID, $REGISTER_PLAYLIST_INDEX}, $hook",
|
||||||
|
)
|
||||||
|
numberOfInstructionsAdded++
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hookPlayerParameter(hook: Hook) {
|
||||||
|
playerResponseMethod.addInstructions(
|
||||||
|
0,
|
||||||
|
"""
|
||||||
|
invoke-static {$REGISTER_VIDEO_ID, v0}, $hook
|
||||||
|
move-result-object v0
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
numberOfInstructionsAdded += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse the order in order to preserve insertion order of the hooks.
|
||||||
|
val beforeVideoIdHooks =
|
||||||
|
hooks.filterIsInstance<Hook.PlayerParameterBeforeVideoId>().asReversed()
|
||||||
|
val videoIdHooks = hooks.filterIsInstance<Hook.VideoId>().asReversed()
|
||||||
|
val videoIdAndPlaylistIdHooks = hooks.filterIsInstance<Hook.VideoIdAndPlaylistId>().asReversed()
|
||||||
|
val afterVideoIdHooks = hooks.filterIsInstance<Hook.PlayerParameter>().asReversed()
|
||||||
|
|
||||||
|
// Add the hooks in this specific order as they insert instructions at the beginning of the method.
|
||||||
|
afterVideoIdHooks.forEach(::hookPlayerParameter)
|
||||||
|
videoIdAndPlaylistIdHooks.forEach(::hookVideoIdAndPlaylistId)
|
||||||
|
videoIdHooks.forEach(::hookVideoId)
|
||||||
|
beforeVideoIdHooks.forEach(::hookPlayerParameter)
|
||||||
|
|
||||||
|
playerResponseMethod.apply {
|
||||||
|
addInstruction(
|
||||||
|
0,
|
||||||
|
"move-object/from16 v0, $REGISTER_PLAYER_PARAMETER"
|
||||||
|
)
|
||||||
|
numberOfInstructionsAdded++
|
||||||
|
|
||||||
|
// Move the modified register back.
|
||||||
|
addInstruction(
|
||||||
|
numberOfInstructionsAdded,
|
||||||
|
"move-object/from16 $REGISTER_PLAYER_PARAMETER, v0"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hookPlayerResponse(
|
sealed class Hook(private val methodDescriptor: String) {
|
||||||
descriptor: String,
|
class VideoId(methodDescriptor: String) : Hook(methodDescriptor)
|
||||||
onlyVideoId: Boolean = false
|
class VideoIdAndPlaylistId(methodDescriptor: String) : Hook(methodDescriptor)
|
||||||
) {
|
|
||||||
val smaliInstruction = if (onlyVideoId)
|
|
||||||
"invoke-static {$REGISTER_VIDEO_ID}, $descriptor"
|
|
||||||
else
|
|
||||||
"invoke-static {$REGISTER_VIDEO_ID, $REGISTER_PLAYLIST_ID, $REGISTER_PLAYLIST_INDEX}, $descriptor"
|
|
||||||
|
|
||||||
playerResponseMethod.addInstruction(0, smaliInstruction)
|
class PlayerParameter(methodDescriptor: String) : Hook(methodDescriptor)
|
||||||
|
class PlayerParameterBeforeVideoId(methodDescriptor: String) : Hook(methodDescriptor)
|
||||||
|
|
||||||
|
override fun toString() = methodDescriptor
|
||||||
}
|
}
|
||||||
|
@ -501,6 +501,11 @@ Info:
|
|||||||
<string name="revanced_spoof_client_type_entry_android_music_5_29">Android Music 5.29.53</string>
|
<string name="revanced_spoof_client_type_entry_android_music_5_29">Android Music 5.29.53</string>
|
||||||
<string name="revanced_spoof_client_type_entry_ios_music_6_21">iOS Music 6.21</string>
|
<string name="revanced_spoof_client_type_entry_ios_music_6_21">iOS Music 6.21</string>
|
||||||
<string name="revanced_spoof_client_type_entry_ios_music_7_04">iOS Music 7.04</string>
|
<string name="revanced_spoof_client_type_entry_ios_music_7_04">iOS Music 7.04</string>
|
||||||
|
<string name="revanced_spoof_player_parameter_title">Spoof player parameter</string>
|
||||||
|
<string name="revanced_spoof_player_parameter_summary">"Spoof the player parameter to prevent playback issues.
|
||||||
|
|
||||||
|
Side effect:
|
||||||
|
• Sometimes the subtitles are located at the top of the player instead of the bottom."</string>
|
||||||
<string name="revanced_watch_history_type_title">Watch history type</string>
|
<string name="revanced_watch_history_type_title">Watch history type</string>
|
||||||
<string name="revanced_watch_history_type_summary">"• Original: Follows the watch history settings of Google account, but watch history may not work due to DNS or VPN.
|
<string name="revanced_watch_history_type_summary">"• Original: Follows the watch history settings of Google account, but watch history may not work due to DNS or VPN.
|
||||||
• Replace domain: Follows the watch history settings of Google account.
|
• Replace domain: Follows the watch history settings of Google account.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user