Merge 003a84305d
into 7d102e7a69
This commit is contained in:
commit
ccd6e64cc1
|
@ -5,6 +5,7 @@ import android.os.Build;
|
|||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class RemoveScreencaptureRestrictionPatch {
|
||||
// Member of AudioAttributes.Builder
|
||||
@RequiresApi(api = Build.VERSION_CODES.Q)
|
||||
|
|
|
@ -3,6 +3,7 @@ package app.revanced.integrations.all.screenshot.removerestriction;
|
|||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class RemoveScreenshotRestrictionPatch {
|
||||
|
||||
public static void addFlags(Window window, int flags) {
|
||||
|
|
|
@ -5,8 +5,12 @@ import com.reddit.domain.model.ILink;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class FilterPromotedLinksPatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* Filters list from promoted links.
|
||||
**/
|
||||
public static List<?> filterChildren(final Iterable<?> links) {
|
||||
|
|
|
@ -138,7 +138,11 @@ public class GmsCoreSupport {
|
|||
}
|
||||
}
|
||||
|
||||
// Modified by a patch. Do not touch.
|
||||
/**
|
||||
* Modified by a patch. Do not touch.
|
||||
*
|
||||
* @noinspection SameReturnValue
|
||||
*/
|
||||
private static String getGmsCoreVendorGroupId() {
|
||||
return "app.revanced";
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ public class Utils {
|
|||
* Injection point.
|
||||
*
|
||||
* @return The manifest 'Version' entry of the patches.jar used during patching.
|
||||
* @noinspection SameReturnValue
|
||||
*/
|
||||
public static String getPatchesReleaseVersion() {
|
||||
return ""; // Value is replaced during patching.
|
||||
|
|
|
@ -6,7 +6,12 @@ import app.revanced.integrations.shared.Logger;
|
|||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class FixSLinksPatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String resolveSLink(String link) {
|
||||
if (link.matches(".*reddit\\.com/r/[^/]+/s/[^/]+")) {
|
||||
Logger.printInfo(() -> "Resolving " + link);
|
||||
|
|
|
@ -3,6 +3,7 @@ package app.revanced.integrations.tiktok.feedfilter;
|
|||
import app.revanced.integrations.tiktok.settings.Settings;
|
||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||
|
||||
/** @noinspection unused*/
|
||||
public class AdsFilter implements IFilter {
|
||||
@Override
|
||||
public boolean getEnabled() {
|
||||
|
|
|
@ -6,6 +6,7 @@ import com.ss.android.ugc.aweme.feed.model.FeedItemList;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class FeedItemsFilter {
|
||||
private static final List<IFilter> FILTERS = List.of(
|
||||
new AdsFilter(),
|
||||
|
@ -16,6 +17,9 @@ public final class FeedItemsFilter {
|
|||
new LikeCountFilter()
|
||||
);
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void filter(FeedItemList feedItemList) {
|
||||
Iterator<Aweme> feedItemListIterator = feedItemList.items.iterator();
|
||||
while (feedItemListIterator.hasNext()) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package app.revanced.integrations.tiktok.feedfilter;
|
|||
import app.revanced.integrations.tiktok.settings.Settings;
|
||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ImageVideoFilter implements IFilter {
|
||||
@Override
|
||||
public boolean getEnabled() {
|
||||
|
|
|
@ -3,6 +3,7 @@ package app.revanced.integrations.tiktok.feedfilter;
|
|||
import app.revanced.integrations.tiktok.settings.Settings;
|
||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class LiveFilter implements IFilter {
|
||||
@Override
|
||||
public boolean getEnabled() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package app.revanced.integrations.tiktok.settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SettingsStatus {
|
||||
public static boolean feedFilterEnabled = false;
|
||||
public static boolean downloadEnabled = false;
|
||||
|
@ -17,6 +18,7 @@ public class SettingsStatus {
|
|||
simSpoofEnabled = true;
|
||||
}
|
||||
|
||||
/** @noinspection EmptyMethod*/
|
||||
public static void load() {
|
||||
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import org.jetbrains.annotations.NotNull;
|
|||
/**
|
||||
* Preference fragment for ReVanced settings
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
|
||||
@Override
|
||||
|
@ -33,6 +33,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unused")
|
||||
protected void initialize() {
|
||||
final var context = getContext();
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import android.content.Context;
|
|||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceScreen;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public abstract class ConditionalPreferenceCategory extends PreferenceCategory {
|
||||
public ConditionalPreferenceCategory(Context context, PreferenceScreen screen) {
|
||||
super(context);
|
||||
|
|
|
@ -7,7 +7,7 @@ import app.revanced.integrations.tiktok.settings.SettingsStatus;
|
|||
import app.revanced.integrations.tiktok.settings.preference.DownloadPathPreference;
|
||||
import app.revanced.integrations.tiktok.settings.preference.TogglePreference;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class DownloadsPreferenceCategory extends ConditionalPreferenceCategory {
|
||||
public DownloadsPreferenceCategory(Context context, PreferenceScreen screen) {
|
||||
super(context, screen);
|
||||
|
|
|
@ -7,7 +7,7 @@ import app.revanced.integrations.tiktok.settings.Settings;
|
|||
import app.revanced.integrations.tiktok.settings.SettingsStatus;
|
||||
import app.revanced.integrations.tiktok.settings.preference.TogglePreference;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class FeedFilterPreferenceCategory extends ConditionalPreferenceCategory {
|
||||
public FeedFilterPreferenceCategory(Context context, PreferenceScreen screen) {
|
||||
super(context, screen);
|
||||
|
|
|
@ -6,7 +6,7 @@ import android.preference.PreferenceScreen;
|
|||
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||
import app.revanced.integrations.tiktok.settings.preference.TogglePreference;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressWarnings({"deprecation", "unused"})
|
||||
public class IntegrationsPreferenceCategory extends ConditionalPreferenceCategory {
|
||||
public IntegrationsPreferenceCategory(Context context, PreferenceScreen screen) {
|
||||
super(context, screen);
|
||||
|
|
|
@ -2,7 +2,9 @@ package app.revanced.integrations.tiktok.speed;
|
|||
|
||||
import app.revanced.integrations.tiktok.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class PlaybackSpeedPatch {
|
||||
|
||||
public static void rememberPlaybackSpeed(float newSpeed) {
|
||||
Settings.REMEMBERED_SPEED.save(newSpeed);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.util.AttributeSet;
|
|||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
/** @noinspection unused*/
|
||||
public class CustomPreferenceCategory extends PreferenceCategory {
|
||||
public CustomPreferenceCategory(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
|
|
@ -10,6 +10,7 @@ import app.revanced.integrations.twitch.settings.Settings;
|
|||
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unused")
|
||||
protected void initialize() {
|
||||
super.initialize();
|
||||
|
||||
|
|
|
@ -4,7 +4,9 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class OpenLinksWithAppChooserPatch {
|
||||
|
||||
public static void openWithChooser(final Context context, final Intent intent) {
|
||||
Log.d("ReVanced", "Opening intent with chooser: " + intent);
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ public class ThemeHelper {
|
|||
|
||||
/**
|
||||
* Injection point.
|
||||
* @noinspection SameReturnValue
|
||||
*/
|
||||
private static String darkThemeResourceName() {
|
||||
// Value is changed by Theme patch, if included.
|
||||
|
@ -57,6 +58,7 @@ public class ThemeHelper {
|
|||
|
||||
/**
|
||||
* Injection point.
|
||||
* @noinspection SameReturnValue
|
||||
*/
|
||||
private static String lightThemeResourceName() {
|
||||
// Value is changed by Theme patch, if included.
|
||||
|
|
|
@ -380,7 +380,7 @@ public abstract class TrieSearch<T> {
|
|||
throw new IllegalArgumentException("endIndex: " + endIndex
|
||||
+ " is greater than texToSearchLength: " + textToSearchLength);
|
||||
}
|
||||
if (patterns.size() == 0) {
|
||||
if (patterns.isEmpty()) {
|
||||
return false; // No patterns were added.
|
||||
}
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
|
@ -393,7 +393,7 @@ public abstract class TrieSearch<T> {
|
|||
* @return Estimated memory size (in kilobytes) of this instance.
|
||||
*/
|
||||
public int getEstimatedMemorySize() {
|
||||
if (patterns.size() == 0) {
|
||||
if (patterns.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
// Assume the device has less than 32GB of ram (and can use pointer compression),
|
||||
|
|
|
@ -18,7 +18,7 @@ public class BypassURLRedirectsPatch {
|
|||
public static Uri parseRedirectUri(String uri) {
|
||||
final var parsed = Uri.parse(uri);
|
||||
|
||||
if (Settings.BYPASS_URL_REDIRECTS.get() && parsed.getPath().equals(YOUTUBE_REDIRECT_PATH)) {
|
||||
if (Settings.BYPASS_URL_REDIRECTS.get() && YOUTUBE_REDIRECT_PATH.equals(parsed.getPath())) {
|
||||
var query = Uri.parse(Uri.decode(parsed.getQueryParameter("q")));
|
||||
|
||||
Logger.printDebug(() -> "Bypassing YouTube redirect URI: " + query);
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
package app.revanced.integrations.youtube.patches;
|
||||
|
||||
import android.view.WindowManager;
|
||||
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.youtube.swipecontrols.SwipeControlsHostActivity;
|
||||
|
||||
/**
|
||||
* Patch class for 'hdr-auto-brightness' patch.
|
||||
*
|
||||
* Edit: This patch no longer does anything, as YT already uses BRIGHTNESS_OVERRIDE_NONE
|
||||
* as the default brightness level. The hooked code was also removed from YT 19.09+ as well.
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("unused")
|
||||
public class HDRAutoBrightnessPatch {
|
||||
/**
|
||||
* get brightness override for HDR brightness
|
||||
*
|
||||
* @param original brightness youtube would normally set
|
||||
* @return brightness to set on HRD video
|
||||
*/
|
||||
public static float getHDRBrightness(float original) {
|
||||
// do nothing if disabled
|
||||
if (!Settings.HDR_AUTO_BRIGHTNESS.get()) {
|
||||
return original;
|
||||
}
|
||||
|
||||
// override with brightness set by swipe-controls
|
||||
// only when swipe-controls is active and has overridden the brightness
|
||||
final SwipeControlsHostActivity swipeControlsHost = SwipeControlsHostActivity.getCurrentHost().get();
|
||||
if (swipeControlsHost != null
|
||||
&& swipeControlsHost.getScreen() != null
|
||||
&& swipeControlsHost.getConfig().getEnableBrightnessControl()
|
||||
&& !swipeControlsHost.getScreen().isDefaultBrightness()) {
|
||||
return swipeControlsHost.getScreen().getRawScreenBrightness();
|
||||
}
|
||||
|
||||
// otherwise, set the brightness to auto
|
||||
return WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE;
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package app.revanced.integrations.youtube.patches;
|
||||
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
/**
|
||||
* Patch is obsolete and will be deleted in a future release
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Deprecated()
|
||||
public class HideEmailAddressPatch {
|
||||
//Used by app.revanced.patches.youtube.layout.personalinformation.patch.HideEmailAddressPatch
|
||||
public static int hideEmailAddress(int originalValue) {
|
||||
if (Settings.HIDE_EMAIL_ADDRESS.get())
|
||||
return 8;
|
||||
return originalValue;
|
||||
}
|
||||
}
|
|
@ -91,99 +91,6 @@ public class ReturnYouTubeDislikePatch {
|
|||
// while a regular video is on screen.
|
||||
}
|
||||
|
||||
//
|
||||
// 17.x non litho regular video player.
|
||||
//
|
||||
|
||||
/**
|
||||
* Resource identifier of old UI dislike button.
|
||||
*/
|
||||
private static final int OLD_UI_DISLIKE_BUTTON_RESOURCE_ID
|
||||
= Utils.getResourceIdentifier("dislike_button", "id");
|
||||
|
||||
/**
|
||||
* Dislikes text label used by old UI.
|
||||
*/
|
||||
@NonNull
|
||||
private static WeakReference<TextView> oldUITextViewRef = new WeakReference<>(null);
|
||||
|
||||
/**
|
||||
* Original old UI 'Dislikes' text before patch modifications.
|
||||
* Required to reset the dislikes when changing videos and RYD is not available.
|
||||
* Set only once during the first load.
|
||||
*/
|
||||
private static Spanned oldUIOriginalSpan;
|
||||
|
||||
/**
|
||||
* Replacement span that contains dislike value. Used by {@link #oldUiTextWatcher}.
|
||||
*/
|
||||
@Nullable
|
||||
private static Spanned oldUIReplacementSpan;
|
||||
|
||||
/**
|
||||
* Old UI dislikes can be set multiple times by YouTube.
|
||||
* To prevent reverting changes made here, this listener overrides any future changes YouTube makes.
|
||||
*/
|
||||
private static final TextWatcher oldUiTextWatcher = new TextWatcher() {
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (oldUIReplacementSpan == null || oldUIReplacementSpan.toString().equals(s.toString())) {
|
||||
return;
|
||||
}
|
||||
s.replace(0, s.length(), oldUIReplacementSpan); // Causes a recursive call back into this listener
|
||||
}
|
||||
};
|
||||
|
||||
private static void updateOldUIDislikesTextView() {
|
||||
ReturnYouTubeDislike videoData = currentVideoData;
|
||||
if (videoData == null) {
|
||||
return;
|
||||
}
|
||||
TextView oldUITextView = oldUITextViewRef.get();
|
||||
if (oldUITextView == null) {
|
||||
return;
|
||||
}
|
||||
oldUIReplacementSpan = videoData.getDislikesSpanForRegularVideo(oldUIOriginalSpan, false, false);
|
||||
if (!oldUIReplacementSpan.equals(oldUITextView.getText())) {
|
||||
oldUITextView.setText(oldUIReplacementSpan);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point. Called on main thread.
|
||||
*
|
||||
* Used when spoofing to 16.x and 17.x versions.
|
||||
*/
|
||||
public static void setOldUILayoutDislikes(int buttonViewResourceId, @Nullable TextView textView) {
|
||||
try {
|
||||
if (!Settings.RYD_ENABLED.get()
|
||||
|| buttonViewResourceId != OLD_UI_DISLIKE_BUTTON_RESOURCE_ID
|
||||
|| textView == null) {
|
||||
return;
|
||||
}
|
||||
Logger.printDebug(() -> "setOldUILayoutDislikes");
|
||||
|
||||
if (oldUIOriginalSpan == null) {
|
||||
// Use value of the first instance, as it appears TextViews can be recycled
|
||||
// and might contain dislikes previously added by the patch.
|
||||
oldUIOriginalSpan = (Spanned) textView.getText();
|
||||
}
|
||||
oldUITextViewRef = new WeakReference<>(textView);
|
||||
// No way to check if a listener is already attached, so remove and add again.
|
||||
textView.removeTextChangedListener(oldUiTextWatcher);
|
||||
textView.addTextChangedListener(oldUiTextWatcher);
|
||||
|
||||
updateOldUIDislikesTextView();
|
||||
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "setOldUILayoutDislikes failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Litho player for both regular videos and Shorts.
|
||||
//
|
||||
|
@ -690,11 +597,8 @@ public class ReturnYouTubeDislikePatch {
|
|||
if (v.value == vote) {
|
||||
videoData.sendVote(v);
|
||||
|
||||
if (isNoneHiddenOrMinimized) {
|
||||
if (lastLithoShortsVideoData != null) {
|
||||
lithoShortsShouldUseCurrentData = true;
|
||||
}
|
||||
updateOldUIDislikesTextView();
|
||||
if (isNoneHiddenOrMinimized && lastLithoShortsVideoData != null) {
|
||||
lithoShortsShouldUseCurrentData = true;
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
|
@ -84,27 +84,10 @@ public final class AnnouncementsPatch {
|
|||
message = jsonString;
|
||||
}
|
||||
|
||||
// TODO: Remove this migration code after a few months.
|
||||
if (!Settings.DEPRECATED_ANNOUNCEMENT_LAST_HASH.isSetToDefault()){
|
||||
final byte[] hashBytes = MessageDigest
|
||||
.getInstance("SHA-256")
|
||||
.digest(jsonString.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
final var hash = java.util.Base64.getEncoder().encodeToString(hashBytes);
|
||||
|
||||
// Migrate to saving the id instead of the hash.
|
||||
if (hash.equals(Settings.DEPRECATED_ANNOUNCEMENT_LAST_HASH.get())) {
|
||||
Settings.ANNOUNCEMENT_LAST_ID.save(id);
|
||||
}
|
||||
|
||||
Settings.DEPRECATED_ANNOUNCEMENT_LAST_HASH.resetToDefault();
|
||||
}
|
||||
|
||||
// Do not show the announcement, if the last announcement id is the same as the current one.
|
||||
if (Settings.ANNOUNCEMENT_LAST_ID.get() == id) return;
|
||||
|
||||
|
||||
|
||||
int finalId = id;
|
||||
final var finalTitle = title;
|
||||
final var finalMessage = Html.fromHtml(message, FROM_HTML_MODE_COMPACT);
|
||||
|
|
|
@ -7,6 +7,7 @@ import app.revanced.integrations.youtube.patches.playback.speed.CustomPlaybackSp
|
|||
/**
|
||||
* Abuse LithoFilter for {@link CustomPlaybackSpeedPatch}.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class PlaybackSpeedMenuFilterPatch extends Filter {
|
||||
// Must be volatile or synchronized, as litho filtering runs off main thread and this field is then access from the main thread.
|
||||
public static volatile boolean isPlaybackSpeedMenuVisible;
|
||||
|
|
|
@ -26,6 +26,7 @@ import app.revanced.integrations.youtube.TrieSearch;
|
|||
*
|
||||
* Once a way to asynchronously update litho text is found, this strategy will no longer be needed.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,241 +0,0 @@
|
|||
package app.revanced.integrations.youtube.patches.spoof;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import androidx.annotation.Nullable;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.youtube.patches.VideoInformation;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static app.revanced.integrations.shared.Utils.containsAny;
|
||||
import static app.revanced.integrations.youtube.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer;
|
||||
|
||||
/** @noinspection unused*/
|
||||
@Deprecated
|
||||
public class SpoofSignaturePatch {
|
||||
/**
|
||||
* Parameter (also used by
|
||||
* <a href="https://github.com/yt-dlp/yt-dlp/blob/81ca451480051d7ce1a31c017e005358345a9149/yt_dlp/extractor/youtube.py#L3602">yt-dlp</a>)
|
||||
* to fix playback issues.
|
||||
*/
|
||||
private static final String INCOGNITO_PARAMETERS = "CgIQBg==";
|
||||
|
||||
/**
|
||||
* Parameters used when playing clips.
|
||||
*/
|
||||
private static final String CLIPS_PARAMETERS = "kAIB";
|
||||
|
||||
/**
|
||||
* Parameters causing playback issues.
|
||||
*/
|
||||
private static final String[] AUTOPLAY_PARAMETERS = {
|
||||
"YAHI", // Autoplay in feed.
|
||||
"SAFg" // Autoplay in scrim.
|
||||
};
|
||||
|
||||
/**
|
||||
* Parameter used for autoplay in scrim.
|
||||
* Prepend this parameter to mute video playback (for autoplay in feed).
|
||||
*/
|
||||
private static final String SCRIM_PARAMETER = "SAFgAXgB";
|
||||
|
||||
/**
|
||||
* Last video id loaded. Used to prevent reloading the same spec multiple times.
|
||||
*/
|
||||
@Nullable
|
||||
private static volatile String lastPlayerResponseVideoId;
|
||||
|
||||
@Nullable
|
||||
private static volatile Future<StoryboardRenderer> rendererFuture;
|
||||
|
||||
private static volatile boolean useOriginalStoryboardRenderer;
|
||||
|
||||
private static volatile boolean isPlayingShorts;
|
||||
|
||||
@Nullable
|
||||
private static StoryboardRenderer getRenderer(boolean waitForCompletion) {
|
||||
Future<StoryboardRenderer> future = rendererFuture;
|
||||
if (future != null) {
|
||||
try {
|
||||
if (waitForCompletion || future.isDone()) {
|
||||
return future.get(20000, TimeUnit.MILLISECONDS); // Any arbitrarily large timeout.
|
||||
} // else, return null.
|
||||
} catch (TimeoutException ex) {
|
||||
Logger.printDebug(() -> "Could not get renderer (get timed out)");
|
||||
} catch (ExecutionException | InterruptedException ex) {
|
||||
// Should never happen.
|
||||
Logger.printException(() -> "Could not get renderer", ex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* Called off the main thread, and called multiple times for each video.
|
||||
*
|
||||
* @param parameters Original protobuf parameter value.
|
||||
*/
|
||||
public static String spoofParameter(String parameters, boolean isShortAndOpeningOrPlaying) {
|
||||
try {
|
||||
Logger.printDebug(() -> "Original protobuf parameter value: " + parameters);
|
||||
|
||||
if (parameters == null || !Settings.SPOOF_SIGNATURE.get()) {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
// Clip's player parameters contain a lot of information (e.g. video start and end time or whether it loops)
|
||||
// For this reason, the player parameters of a clip are usually very long (150~300 characters).
|
||||
// Clips are 60 seconds or less in length, so no spoofing.
|
||||
//noinspection AssignmentUsedAsCondition
|
||||
if (useOriginalStoryboardRenderer = parameters.length() > 150 || parameters.startsWith(CLIPS_PARAMETERS)) {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
// Shorts do not need to be spoofed.
|
||||
//noinspection AssignmentUsedAsCondition
|
||||
if (useOriginalStoryboardRenderer = VideoInformation.playerParametersAreShort(parameters)) {
|
||||
isPlayingShorts = true;
|
||||
return parameters;
|
||||
}
|
||||
isPlayingShorts = false;
|
||||
|
||||
boolean isPlayingFeed = PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL
|
||||
&& containsAny(parameters, AUTOPLAY_PARAMETERS);
|
||||
if (isPlayingFeed) {
|
||||
//noinspection AssignmentUsedAsCondition
|
||||
if (useOriginalStoryboardRenderer = !Settings.SPOOF_SIGNATURE_IN_FEED.get()) {
|
||||
// Don't spoof the feed video playback. This will cause video playback issues,
|
||||
// but only if user continues watching for more than 1 minute.
|
||||
return parameters;
|
||||
}
|
||||
// Spoof the feed video. Video will show up in watch history and video subtitles are missing.
|
||||
fetchStoryboardRenderer();
|
||||
return SCRIM_PARAMETER + INCOGNITO_PARAMETERS;
|
||||
}
|
||||
|
||||
fetchStoryboardRenderer();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "spoofParameter failure", ex);
|
||||
}
|
||||
return INCOGNITO_PARAMETERS;
|
||||
}
|
||||
|
||||
private static void fetchStoryboardRenderer() {
|
||||
if (!Settings.SPOOF_STORYBOARD_RENDERER.get()) {
|
||||
lastPlayerResponseVideoId = null;
|
||||
rendererFuture = null;
|
||||
return;
|
||||
}
|
||||
String videoId = VideoInformation.getPlayerResponseVideoId();
|
||||
if (!videoId.equals(lastPlayerResponseVideoId)) {
|
||||
rendererFuture = Utils.submitOnBackgroundThread(() -> getStoryboardRenderer(videoId));
|
||||
lastPlayerResponseVideoId = videoId;
|
||||
}
|
||||
// Block until the renderer fetch completes.
|
||||
// This is desired because if this returns without finishing the fetch
|
||||
// then video will start playback but the storyboard is not ready yet.
|
||||
getRenderer(true);
|
||||
}
|
||||
|
||||
private static String getStoryboardRendererSpec(String originalStoryboardRendererSpec,
|
||||
boolean returnNullIfLiveStream) {
|
||||
if (Settings.SPOOF_SIGNATURE.get() && !useOriginalStoryboardRenderer) {
|
||||
StoryboardRenderer renderer = getRenderer(false);
|
||||
if (renderer != null) {
|
||||
if (returnNullIfLiveStream && renderer.isLiveStream()) {
|
||||
return null;
|
||||
}
|
||||
String spec = renderer.getSpec();
|
||||
if (spec != null) {
|
||||
return spec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return originalStoryboardRendererSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Called from background threads and from the main thread.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getStoryboardRendererSpec(String originalStoryboardRendererSpec) {
|
||||
return getStoryboardRendererSpec(originalStoryboardRendererSpec, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Uses additional check to handle live streams.
|
||||
* Called from background threads and from the main thread.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getStoryboardDecoderRendererSpec(String originalStoryboardRendererSpec) {
|
||||
return getStoryboardRendererSpec(originalStoryboardRendererSpec, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static int getRecommendedLevel(int originalLevel) {
|
||||
if (Settings.SPOOF_SIGNATURE.get() && !useOriginalStoryboardRenderer) {
|
||||
StoryboardRenderer renderer = getRenderer(false);
|
||||
if (renderer != null) {
|
||||
Integer recommendedLevel = renderer.getRecommendedLevel();
|
||||
if (recommendedLevel != null) return recommendedLevel;
|
||||
}
|
||||
}
|
||||
|
||||
return originalLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point. Forces seekbar to be shown for paid videos or
|
||||
* if {@link Settings#SPOOF_STORYBOARD_RENDERER} is not enabled.
|
||||
*/
|
||||
public static boolean getSeekbarThumbnailOverrideValue() {
|
||||
if (!Settings.SPOOF_SIGNATURE.get()) {
|
||||
return false;
|
||||
}
|
||||
StoryboardRenderer renderer = getRenderer(false);
|
||||
if (renderer == null) {
|
||||
// Spoof storyboard renderer is turned off,
|
||||
// video is paid, or the storyboard fetch timed out.
|
||||
// Show empty thumbnails so the seek time and chapters still show up.
|
||||
return true;
|
||||
}
|
||||
return renderer.getSpec() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* @param view seekbar thumbnail view. Includes both shorts and regular videos.
|
||||
*/
|
||||
public static void seekbarImageViewCreated(ImageView view) {
|
||||
try {
|
||||
if (!Settings.SPOOF_SIGNATURE.get()
|
||||
|| Settings.SPOOF_STORYBOARD_RENDERER.get()) {
|
||||
return;
|
||||
}
|
||||
if (isPlayingShorts) return;
|
||||
|
||||
view.setVisibility(View.GONE);
|
||||
// Also hide the border around the thumbnail (otherwise a 1 pixel wide bordered frame is visible).
|
||||
ViewGroup parentLayout = (ViewGroup) view.getParent();
|
||||
parentLayout.setPadding(0, 0, 0, 0);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "seekbarImageViewCreated failure", ex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package app.revanced.integrations.youtube.patches.spoof;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Deprecated
|
||||
public final class StoryboardRenderer {
|
||||
@Nullable
|
||||
private final String spec;
|
||||
private final boolean isLiveStream;
|
||||
@Nullable
|
||||
private final Integer recommendedLevel;
|
||||
|
||||
public StoryboardRenderer(@Nullable String spec, boolean isLiveStream, @Nullable Integer recommendedLevel) {
|
||||
this.spec = spec;
|
||||
this.isLiveStream = isLiveStream;
|
||||
this.recommendedLevel = recommendedLevel;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSpec() {
|
||||
return spec;
|
||||
}
|
||||
|
||||
public boolean isLiveStream() {
|
||||
return isLiveStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Recommended image quality level, or NULL if no recommendation exists.
|
||||
*/
|
||||
@Nullable
|
||||
public Integer getRecommendedLevel() {
|
||||
return recommendedLevel;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StoryboardRenderer{" +
|
||||
"isLiveStream=" + isLiveStream +
|
||||
", spec='" + spec + '\'' +
|
||||
", recommendedLevel=" + recommendedLevel +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
package app.revanced.integrations.youtube.patches.spoof.requests;
|
||||
|
||||
import app.revanced.integrations.youtube.requests.Requester;
|
||||
import app.revanced.integrations.youtube.requests.Route;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
@Deprecated
|
||||
final class PlayerRoutes {
|
||||
private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1/";
|
||||
static final Route.CompiledRoute GET_STORYBOARD_SPEC_RENDERER = new Route(
|
||||
Route.Method.POST,
|
||||
"player" +
|
||||
"?fields=storyboards.playerStoryboardSpecRenderer," +
|
||||
"storyboards.playerLiveStoryboardSpecRenderer," +
|
||||
"playabilityStatus.status"
|
||||
).compile();
|
||||
|
||||
static final String ANDROID_INNER_TUBE_BODY;
|
||||
static final String TV_EMBED_INNER_TUBE_BODY;
|
||||
|
||||
/**
|
||||
* TCP connection and HTTP read timeout
|
||||
*/
|
||||
private static final int CONNECTION_TIMEOUT_MILLISECONDS = 4 * 1000; // 4 Seconds.
|
||||
|
||||
static {
|
||||
JSONObject innerTubeBody = new JSONObject();
|
||||
|
||||
try {
|
||||
JSONObject context = new JSONObject();
|
||||
|
||||
JSONObject client = new JSONObject();
|
||||
client.put("clientName", "ANDROID");
|
||||
client.put("clientVersion", Utils.getAppVersionName());
|
||||
client.put("androidSdkVersion", 34);
|
||||
|
||||
context.put("client", client);
|
||||
|
||||
innerTubeBody.put("context", context);
|
||||
innerTubeBody.put("videoId", "%s");
|
||||
} catch (JSONException e) {
|
||||
Logger.printException(() -> "Failed to create innerTubeBody", e);
|
||||
}
|
||||
|
||||
ANDROID_INNER_TUBE_BODY = innerTubeBody.toString();
|
||||
|
||||
JSONObject tvEmbedInnerTubeBody = new JSONObject();
|
||||
|
||||
try {
|
||||
JSONObject context = new JSONObject();
|
||||
|
||||
JSONObject client = new JSONObject();
|
||||
client.put("clientName", "TVHTML5_SIMPLY_EMBEDDED_PLAYER");
|
||||
client.put("clientVersion", "2.0");
|
||||
client.put("platform", "TV");
|
||||
client.put("clientScreen", "EMBED");
|
||||
|
||||
JSONObject thirdParty = new JSONObject();
|
||||
thirdParty.put("embedUrl", "https://www.youtube.com/watch?v=%s");
|
||||
|
||||
context.put("thirdParty", thirdParty);
|
||||
context.put("client", client);
|
||||
|
||||
tvEmbedInnerTubeBody.put("context", context);
|
||||
tvEmbedInnerTubeBody.put("videoId", "%s");
|
||||
} catch (JSONException e) {
|
||||
Logger.printException(() -> "Failed to create tvEmbedInnerTubeBody", e);
|
||||
}
|
||||
|
||||
TV_EMBED_INNER_TUBE_BODY = tvEmbedInnerTubeBody.toString();
|
||||
}
|
||||
|
||||
private PlayerRoutes() {
|
||||
}
|
||||
|
||||
/** @noinspection SameParameterValue*/
|
||||
static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route) throws IOException {
|
||||
var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route);
|
||||
|
||||
connection.setRequestProperty(
|
||||
"User-Agent", "com.google.android.youtube/" +
|
||||
Utils.getAppVersionName() +
|
||||
" (Linux; U; Android 12; GB) gzip"
|
||||
);
|
||||
connection.setRequestProperty("X-Goog-Api-Format-Version", "2");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
|
||||
connection.setUseCaches(false);
|
||||
connection.setDoOutput(true);
|
||||
|
||||
connection.setConnectTimeout(CONNECTION_TIMEOUT_MILLISECONDS);
|
||||
connection.setReadTimeout(CONNECTION_TIMEOUT_MILLISECONDS);
|
||||
return connection;
|
||||
}
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
package app.revanced.integrations.youtube.patches.spoof.requests;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||
import app.revanced.integrations.youtube.patches.spoof.StoryboardRenderer;
|
||||
import app.revanced.integrations.youtube.requests.Requester;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
import static app.revanced.integrations.youtube.patches.spoof.requests.PlayerRoutes.*;
|
||||
|
||||
@Deprecated
|
||||
public class StoryboardRendererRequester {
|
||||
|
||||
/**
|
||||
* For videos that have no storyboard.
|
||||
* Usually for low resolution videos as old as YouTube itself.
|
||||
* Does not include paid videos where the renderer fetch fails.
|
||||
*/
|
||||
private static final StoryboardRenderer emptyStoryboard
|
||||
= new StoryboardRenderer(null, false, null);
|
||||
|
||||
private StoryboardRendererRequester() {
|
||||
}
|
||||
|
||||
private static void randomlyWaitIfLocallyDebugging() {
|
||||
final boolean randomlyWait = false; // Enable to simulate slow connection responses.
|
||||
if (randomlyWait) {
|
||||
final long maximumTimeToRandomlyWait = 10000;
|
||||
Utils.doNothingForDuration(maximumTimeToRandomlyWait);
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleConnectionError(@NonNull String toastMessage, @Nullable Exception ex,
|
||||
boolean showToastOnIOException) {
|
||||
if (showToastOnIOException) Utils.showToastShort(toastMessage);
|
||||
Logger.printInfo(() -> toastMessage, ex);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static JSONObject fetchPlayerResponse(@NonNull String requestBody, boolean showToastOnIOException) {
|
||||
final long startTime = System.currentTimeMillis();
|
||||
try {
|
||||
Utils.verifyOffMainThread();
|
||||
Objects.requireNonNull(requestBody);
|
||||
|
||||
final byte[] innerTubeBody = requestBody.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_STORYBOARD_SPEC_RENDERER);
|
||||
connection.getOutputStream().write(innerTubeBody, 0, innerTubeBody.length);
|
||||
|
||||
final int responseCode = connection.getResponseCode();
|
||||
randomlyWaitIfLocallyDebugging();
|
||||
if (responseCode == 200) return Requester.parseJSONObject(connection);
|
||||
|
||||
// Always show a toast for this, as a non 200 response means something is broken.
|
||||
// Not a normal code path and should not be reached, so no translations are needed.
|
||||
handleConnectionError("Spoof storyboard not available: " + responseCode,
|
||||
null, showToastOnIOException || BaseSettings.DEBUG_TOAST_ON_ERROR.get());
|
||||
connection.disconnect();
|
||||
} catch (SocketTimeoutException ex) {
|
||||
handleConnectionError(str("revanced_spoof_storyboard_timeout"), ex, showToastOnIOException);
|
||||
} catch (IOException ex) {
|
||||
handleConnectionError(str("revanced_spoof_storyboard_io_exception", ex.getMessage()),
|
||||
ex, showToastOnIOException);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Spoof storyboard fetch failed", ex); // Should never happen.
|
||||
} finally {
|
||||
Logger.printDebug(() -> "Request took: " + (System.currentTimeMillis() - startTime) + "ms");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isPlayabilityStatusOk(@NonNull JSONObject playerResponse) {
|
||||
try {
|
||||
return playerResponse.getJSONObject("playabilityStatus").getString("status").equals("OK");
|
||||
} catch (JSONException e) {
|
||||
Logger.printDebug(() -> "Failed to get playabilityStatus for response: " + playerResponse);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the storyboardRenderer from the innerTubeBody.
|
||||
* @param innerTubeBody The innerTubeBody to use to fetch the storyboardRenderer.
|
||||
* @return StoryboardRenderer or null if playabilityStatus is not OK.
|
||||
*/
|
||||
@Nullable
|
||||
private static StoryboardRenderer getStoryboardRendererUsingBody(@NonNull String innerTubeBody,
|
||||
boolean showToastOnIOException) {
|
||||
final JSONObject playerResponse = fetchPlayerResponse(innerTubeBody, showToastOnIOException);
|
||||
if (playerResponse != null && isPlayabilityStatusOk(playerResponse))
|
||||
return getStoryboardRendererUsingResponse(playerResponse);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static StoryboardRenderer getStoryboardRendererUsingResponse(@NonNull JSONObject playerResponse) {
|
||||
try {
|
||||
Logger.printDebug(() -> "Parsing response: " + playerResponse);
|
||||
if (!playerResponse.has("storyboards")) {
|
||||
Logger.printDebug(() -> "Using empty storyboard");
|
||||
return emptyStoryboard;
|
||||
}
|
||||
final JSONObject storyboards = playerResponse.getJSONObject("storyboards");
|
||||
final boolean isLiveStream = storyboards.has("playerLiveStoryboardSpecRenderer");
|
||||
final String storyboardsRendererTag = isLiveStream
|
||||
? "playerLiveStoryboardSpecRenderer"
|
||||
: "playerStoryboardSpecRenderer";
|
||||
|
||||
final var rendererElement = storyboards.getJSONObject(storyboardsRendererTag);
|
||||
StoryboardRenderer renderer = new StoryboardRenderer(
|
||||
rendererElement.getString("spec"),
|
||||
isLiveStream,
|
||||
rendererElement.has("recommendedLevel")
|
||||
? rendererElement.getInt("recommendedLevel")
|
||||
: null
|
||||
);
|
||||
|
||||
Logger.printDebug(() -> "Fetched: " + renderer);
|
||||
|
||||
return renderer;
|
||||
} catch (JSONException e) {
|
||||
Logger.printException(() -> "Failed to get storyboardRenderer", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static StoryboardRenderer getStoryboardRenderer(@NonNull String videoId) {
|
||||
Objects.requireNonNull(videoId);
|
||||
|
||||
var renderer = getStoryboardRendererUsingBody(
|
||||
String.format(ANDROID_INNER_TUBE_BODY, videoId), false);
|
||||
if (renderer == null) {
|
||||
Logger.printDebug(() -> videoId + " not available using Android client");
|
||||
renderer = getStoryboardRendererUsingBody(
|
||||
String.format(TV_EMBED_INNER_TUBE_BODY, videoId, videoId), true);
|
||||
if (renderer == null) {
|
||||
Logger.printDebug(() -> videoId + " not available using TV embedded client");
|
||||
}
|
||||
}
|
||||
|
||||
return renderer;
|
||||
}
|
||||
}
|
|
@ -69,7 +69,7 @@ public class ReturnYouTubeDislike {
|
|||
* Maximum amount of time to block the UI from updates while waiting for network call to complete.
|
||||
*
|
||||
* Must be less than 5 seconds, as per:
|
||||
* https://developer.android.com/topic/performance/vitals/anr
|
||||
* <a href="https://developer.android.com/topic/performance/vitals/anr">...</a>
|
||||
*/
|
||||
private static final long MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH = 4000;
|
||||
|
||||
|
|
|
@ -147,6 +147,7 @@ public class ReturnYouTubeDislikeApi {
|
|||
*/
|
||||
private static void randomlyWaitIfLocallyDebugging() {
|
||||
final boolean DEBUG_RANDOMLY_DELAY_NETWORK_CALLS = false; // set true to debug UI
|
||||
//noinspection ConstantValue
|
||||
if (DEBUG_RANDOMLY_DELAY_NETWORK_CALLS) {
|
||||
final long amountOfTimeToWaste = (long) (Math.random()
|
||||
* (API_GET_VOTES_TCP_TIMEOUT_MILLISECONDS + API_GET_VOTES_HTTP_TIMEOUT_MILLISECONDS));
|
||||
|
@ -187,6 +188,7 @@ public class ReturnYouTubeDislikeApi {
|
|||
*/
|
||||
private static boolean checkIfRateLimitWasHit(int httpResponseCode) {
|
||||
final boolean DEBUG_RATE_LIMIT = false; // set to true, to verify rate limit works
|
||||
//noinspection ConstantValue
|
||||
if (DEBUG_RATE_LIMIT) {
|
||||
final double RANDOM_RATE_LIMIT_PERCENTAGE = 0.2; // 20% chance of a triggering a rate limit
|
||||
if (Math.random() < RANDOM_RATE_LIMIT_PERCENTAGE) {
|
||||
|
@ -569,7 +571,11 @@ public class ReturnYouTubeDislikeApi {
|
|||
throw new IllegalStateException("Failed to solve puzzle challenge: " + challenge + " difficulty: " + difficulty);
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/157202
|
||||
/**
|
||||
* <a href="https://stackoverflow.com/a/157202">...</a>
|
||||
*
|
||||
* @noinspection SameParameterValue
|
||||
*/
|
||||
private static String randomString(int len) {
|
||||
String AB = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
SecureRandom rnd = new SecureRandom();
|
||||
|
|
|
@ -30,8 +30,6 @@ public class Settings extends BaseSettings {
|
|||
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", 1.0f);
|
||||
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
||||
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true);
|
||||
@Deprecated // Patch is obsolete and no longer works with 19.09+
|
||||
public static final BooleanSetting HDR_AUTO_BRIGHTNESS = new BooleanSetting("revanced_hdr_auto_brightness", TRUE);
|
||||
|
||||
// Ads
|
||||
public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", TRUE);
|
||||
|
@ -83,7 +81,6 @@ public class Settings extends BaseSettings {
|
|||
public static final BooleanSetting HIDE_COMMUNITY_POSTS = new BooleanSetting("revanced_hide_community_posts", FALSE);
|
||||
public static final BooleanSetting HIDE_COMPACT_BANNER = new BooleanSetting("revanced_hide_compact_banner", TRUE);
|
||||
public static final BooleanSetting HIDE_CROWDFUNDING_BOX = new BooleanSetting("revanced_hide_crowdfunding_box", FALSE, true);
|
||||
@Deprecated public static final BooleanSetting HIDE_EMAIL_ADDRESS = new BooleanSetting("revanced_hide_email_address", FALSE);
|
||||
public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE);
|
||||
public static final BooleanSetting HIDE_ENDSCREEN_CARDS = new BooleanSetting("revanced_hide_endscreen_cards", TRUE);
|
||||
public static final BooleanSetting HIDE_EXPANDABLE_CHIP = new BooleanSetting("revanced_hide_expandable_chip", TRUE);
|
||||
|
@ -99,7 +96,6 @@ public class Settings extends BaseSettings {
|
|||
public static final BooleanSetting HIDE_IMAGE_SHELF = new BooleanSetting("revanced_hide_image_shelf", TRUE);
|
||||
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", TRUE);
|
||||
public static final BooleanSetting HIDE_JOIN_MEMBERSHIP_BUTTON = new BooleanSetting("revanced_hide_join_membership_button", TRUE);
|
||||
@Deprecated public static final BooleanSetting HIDE_LOAD_MORE_BUTTON = new BooleanSetting("revanced_hide_load_more_button", TRUE);
|
||||
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
|
||||
public static final BooleanSetting HIDE_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", TRUE);
|
||||
|
@ -234,8 +230,6 @@ public class Settings extends BaseSettings {
|
|||
"revanced_spoof_device_dimensions_user_dialog_message");
|
||||
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
|
||||
public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
|
||||
@Deprecated
|
||||
public static final StringSetting DEPRECATED_ANNOUNCEMENT_LAST_HASH = new StringSetting("revanced_announcement_last_hash", "");
|
||||
public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1);
|
||||
public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE);
|
||||
|
||||
|
@ -246,14 +240,6 @@ public class Settings extends BaseSettings {
|
|||
*/
|
||||
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, parent(BaseSettings.DEBUG));
|
||||
|
||||
// Old deprecated signature spoofing
|
||||
@Deprecated public static final BooleanSetting SPOOF_SIGNATURE = new BooleanSetting("revanced_spoof_signature_verification_enabled", TRUE, true, false,
|
||||
"revanced_spoof_signature_verification_enabled_user_dialog_message", null);
|
||||
@Deprecated public static final BooleanSetting SPOOF_SIGNATURE_IN_FEED = new BooleanSetting("revanced_spoof_signature_in_feed_enabled", FALSE, false, false, null,
|
||||
parent(SPOOF_SIGNATURE));
|
||||
@Deprecated public static final BooleanSetting SPOOF_STORYBOARD_RENDERER = new BooleanSetting("revanced_spoof_storyboard", TRUE, true, false, null,
|
||||
parent(SPOOF_SIGNATURE));
|
||||
|
||||
// Swipe controls
|
||||
public static final BooleanSetting SWIPE_BRIGHTNESS = new BooleanSetting("revanced_swipe_brightness", TRUE);
|
||||
public static final BooleanSetting SWIPE_VOLUME = new BooleanSetting("revanced_swipe_volume", TRUE);
|
||||
|
@ -395,8 +381,6 @@ public class Settings extends BaseSettings {
|
|||
// Remove any previously saved announcement consumer (a random generated string).
|
||||
Setting.preferences.removeKey("revanced_announcement_consumer");
|
||||
|
||||
migrateOldSettingToNew(HIDE_LOAD_MORE_BUTTON, HIDE_SHOW_MORE_BUTTON);
|
||||
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,11 @@ package app.revanced.integrations.youtube.settings.preference;
|
|||
import android.os.Build;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceGroup;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.settings.preference.AbstractPreferenceFragment;
|
||||
import app.revanced.integrations.youtube.patches.DownloadsPatch;
|
||||
import app.revanced.integrations.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
|
@ -21,6 +19,7 @@ import app.revanced.integrations.youtube.settings.Settings;
|
|||
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
protected void initialize() {
|
||||
super.initialize();
|
||||
|
|
|
@ -21,9 +21,6 @@ enum class PlayerType {
|
|||
|
||||
/**
|
||||
* A regular video is minimized.
|
||||
*
|
||||
* When spoofing to 16.x YouTube and watching a short with a regular video in the background,
|
||||
* the type can be this (and not [HIDDEN]).
|
||||
*/
|
||||
WATCH_WHILE_MINIMIZED,
|
||||
WATCH_WHILE_MAXIMIZED,
|
||||
|
@ -49,7 +46,7 @@ enum class PlayerType {
|
|||
|
||||
companion object {
|
||||
|
||||
private val nameToPlayerType = values().associateBy { it.name }
|
||||
private val nameToPlayerType = entries.associateBy { it.name }
|
||||
|
||||
@JvmStatic
|
||||
fun setFromString(enumName: String) {
|
||||
|
@ -87,9 +84,8 @@ enum class PlayerType {
|
|||
* Check if the current player type is [NONE] or [HIDDEN].
|
||||
* Useful to check if a short is currently playing.
|
||||
*
|
||||
* Does not include the first moment after a short is opened when a regular video is minimized on screen,
|
||||
* or while watching a short with a regular video present on a spoofed 16.x version of YouTube.
|
||||
* To include those situations instead use [isNoneHiddenOrMinimized].
|
||||
* Does not include the first moment after a short is opened when a regular video is minimized on screen.
|
||||
* To include that, instead use [isNoneHiddenOrMinimized].
|
||||
*
|
||||
* @see VideoInformation
|
||||
*/
|
||||
|
@ -103,9 +99,7 @@ enum class PlayerType {
|
|||
*
|
||||
* Useful to check if a Short is being played or opened.
|
||||
*
|
||||
* Usually covers all use cases with no false positives, except if called from some hooks
|
||||
* when spoofing to an old version this will return false even
|
||||
* though a Short is being opened or is on screen (see [isNoneHiddenOrMinimized]).
|
||||
* Usually covers all use cases with no false positives.
|
||||
*
|
||||
* @return If nothing, a Short, or a regular video is sliding off screen to a dismissed or hidden state.
|
||||
* @see VideoInformation
|
||||
|
|
|
@ -22,7 +22,7 @@ enum class VideoState {
|
|||
|
||||
companion object {
|
||||
|
||||
private val nameToVideoState = values().associateBy { it.name }
|
||||
private val nameToVideoState = entries.associateBy { it.name }
|
||||
|
||||
@JvmStatic
|
||||
fun setFromString(enumName: String) {
|
||||
|
|
|
@ -182,6 +182,7 @@ public class SegmentPlaybackController {
|
|||
* Injection point.
|
||||
* Initializes SponsorBlock when the video player starts playing a new video.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void initialize(Object ignoredPlayerController) {
|
||||
try {
|
||||
Utils.verifyOnMainThread();
|
||||
|
|
|
@ -113,6 +113,7 @@ public class SBRequester {
|
|||
// Could benefit from:
|
||||
// 1) collection of YouTube videos with test segment times (verify client skip timing matches the video, verify seekbar draws correctly)
|
||||
// 2) unit tests (verify everything else)
|
||||
//noinspection ConstantValue
|
||||
if (false) {
|
||||
segments.clear();
|
||||
// Test auto-hide skip button:
|
||||
|
|
|
@ -14,6 +14,7 @@ import app.revanced.integrations.shared.Logger;
|
|||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.youtube.videoplayer.BottomControlButton;
|
||||
|
||||
/** @noinspection unused*/
|
||||
public class CreateSegmentButtonController {
|
||||
private static WeakReference<ImageView> buttonReference = new WeakReference<>(null);
|
||||
private static boolean isShowing;
|
||||
|
|
|
@ -92,6 +92,7 @@ public class SkipSponsorButton extends FrameLayout {
|
|||
public boolean updateSkipButtonText(@NonNull SponsorSegment segment) {
|
||||
this.segment = segment;
|
||||
CharSequence newText = segment.getSkipButtonText();
|
||||
//noinspection StringEqualsCharSequence
|
||||
if (newText.equals(skipSponsorTextView.getText())) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -29,9 +29,7 @@ public class VotingButtonController {
|
|||
ImageView imageView = Objects.requireNonNull(youtubeControlsLayout.findViewById(
|
||||
getResourceIdentifier("revanced_sb_voting_button", "id")));
|
||||
imageView.setVisibility(View.GONE);
|
||||
imageView.setOnClickListener(v -> {
|
||||
SponsorBlockUtils.onVotingClicked(v.getContext());
|
||||
});
|
||||
imageView.setOnClickListener(v -> SponsorBlockUtils.onVotingClicked(v.getContext()));
|
||||
|
||||
buttonReference = new WeakReference<>(imageView);
|
||||
} catch (Exception ex) {
|
||||
|
|
|
@ -9,6 +9,7 @@ import app.revanced.integrations.youtube.patches.CopyVideoUrlPatch;
|
|||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
|
||||
/** @noinspection unused*/
|
||||
public class CopyVideoUrlButton extends BottomControlButton {
|
||||
@Nullable
|
||||
private static CopyVideoUrlButton instance;
|
||||
|
|
|
@ -9,6 +9,7 @@ import app.revanced.integrations.youtube.patches.CopyVideoUrlPatch;
|
|||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
|
||||
/** @noinspection unused*/
|
||||
public class CopyVideoUrlTimestampButton extends BottomControlButton {
|
||||
@Nullable
|
||||
private static CopyVideoUrlTimestampButton instance;
|
||||
|
|
Loading…
Reference in New Issue