diff --git a/integrations/java/app/revanced/integrations/youtube/patches/VideoInformation.java b/integrations/java/app/revanced/integrations/youtube/patches/VideoInformation.java index 21aafa667..0d7a29aa6 100644 --- a/integrations/java/app/revanced/integrations/youtube/patches/VideoInformation.java +++ b/integrations/java/app/revanced/integrations/youtube/patches/VideoInformation.java @@ -7,7 +7,6 @@ import app.revanced.integrations.shared.Logger; import app.revanced.integrations.shared.Utils; import java.lang.ref.WeakReference; -import java.lang.reflect.Method; import java.util.Objects; /** @@ -15,17 +14,21 @@ import java.util.Objects; * @noinspection unused */ public final class VideoInformation { + + public interface PlaybackController { + // Methods are added to YT classes during patching. + boolean seekTo(long videoTime); + boolean seekToRelative(long videoTimeOffset); + } + private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f; - private static final String SEEK_METHOD_NAME = "seekTo"; /** * Prefix present in all Short player parameters signature. */ private static final String SHORTS_PLAYER_PARAMETERS = "8AEB"; - private static WeakReference playerControllerRef; - private static WeakReference mdxPlayerDirectorRef; - private static Method seekMethod; - private static Method mdxSeekMethod; + private static WeakReference playerControllerRef = new WeakReference<>(null); + private static WeakReference mdxPlayerDirectorRef = new WeakReference<>(null); @NonNull private static String videoId = ""; @@ -47,15 +50,12 @@ public final class VideoInformation { * * @param playerController player controller object. */ - public static void initialize(@NonNull Object playerController) { + public static void initialize(@NonNull PlaybackController playerController) { try { playerControllerRef = new WeakReference<>(Objects.requireNonNull(playerController)); videoTime = -1; videoLength = 0; playbackSpeed = DEFAULT_YOUTUBE_PLAYBACK_SPEED; - - seekMethod = playerController.getClass().getMethod(SEEK_METHOD_NAME, Long.TYPE); - seekMethod.setAccessible(true); } catch (Exception ex) { Logger.printException(() -> "Failed to initialize", ex); } @@ -66,12 +66,9 @@ public final class VideoInformation { * * @param mdxPlayerDirector MDX player director object (casting mode). */ - public static void initializeMdx(@NonNull Object mdxPlayerDirector) { + public static void initializeMdx(@NonNull PlaybackController mdxPlayerDirector) { try { mdxPlayerDirectorRef = new WeakReference<>(Objects.requireNonNull(mdxPlayerDirector)); - - mdxSeekMethod = mdxPlayerDirector.getClass().getMethod(SEEK_METHOD_NAME, Long.TYPE); - mdxSeekMethod.setAccessible(true); } catch (Exception ex) { Logger.printException(() -> "Failed to initialize MDX", ex); } @@ -195,42 +192,80 @@ public final class VideoInformation { return false; } - Logger.printDebug(() -> "Seeking to " + adjustedSeekTime); + Logger.printDebug(() -> "Seeking to: " + adjustedSeekTime); - try { - //noinspection DataFlowIssue - if ((Boolean) seekMethod.invoke(playerControllerRef.get(), adjustedSeekTime)) { - return true; - } // Else the video is loading or changing videos, or video is casting to a different device. - } catch (Exception ex) { - Logger.printInfo(() -> "seekTo method call failed", ex); + // Try regular playback controller first, and it will not succeed if casting. + PlaybackController controller = playerControllerRef.get(); + if (controller == null) { + Logger.printDebug(() -> "Cannot seekTo because player controller is null"); + } else { + if (controller.seekTo(adjustedSeekTime)) return true; + Logger.printDebug(() -> "seekTo did not succeeded. Trying MXD."); + // Else the video is loading or changing videos, or video is casting to a different device. } // Try calling the seekTo method of the MDX player director (called when casting). // The difference has to be a different second mark in order to avoid infinite skip loops // as the Lounge API only supports seconds. - if ((adjustedSeekTime / 1000) == (videoTime / 1000)) { - Logger.printDebug(() -> "Skipping seekTo for MDX because seek time is too small (" - + (adjustedSeekTime - videoTime) + "ms)"); - return false; - } - try { - //noinspection DataFlowIssue - return (Boolean) mdxSeekMethod.invoke(mdxPlayerDirectorRef.get(), adjustedSeekTime); - } catch (Exception ex) { - Logger.printInfo(() -> "seekTo (MDX) method call failed", ex); + if (adjustedSeekTime / 1000 == videoTime / 1000) { + Logger.printDebug(() -> "Skipping seekTo for MDX because seek time is too small " + + "(" + (adjustedSeekTime - videoTime) + "ms)"); return false; } + controller = mdxPlayerDirectorRef.get(); + if (controller == null) { + Logger.printDebug(() -> "Cannot seekTo MXD because player controller is null"); + return false; + } + + return controller.seekTo(adjustedSeekTime); } catch (Exception ex) { Logger.printException(() -> "Failed to seek", ex); return false; } } - /** @noinspection UnusedReturnValue*/ - public static boolean seekToRelative(long millisecondsRelative) { - return seekTo(videoTime + millisecondsRelative); + /** + * Seeks a relative amount. Should always be used over {@link #seekTo(long)} + * when the desired seek time is an offset of the current time. + * + * @noinspection UnusedReturnValue + */ + public static boolean seekToRelative(long seekTime) { + Utils.verifyOnMainThread(); + try { + Logger.printDebug(() -> "Seeking relative to: " + seekTime); + + // Try regular playback controller first, and it will not succeed if casting. + PlaybackController controller = playerControllerRef.get(); + if (controller == null) { + Logger.printDebug(() -> "Cannot seek relative as player controller is null"); + } else { + if (controller.seekToRelative(seekTime)) return true; + Logger.printDebug(() -> "seekToRelative did not succeeded. Trying MXD."); + } + + // Adjust the fine adjustment function so it's at least 1 second before/after. + // Otherwise the fine adjustment will do nothing when casting. + final long adjustedSeekTime; + if (seekTime < 0) { + adjustedSeekTime = Math.min(seekTime, -1000); + } else { + adjustedSeekTime = Math.max(seekTime, 1000); + } + + controller = mdxPlayerDirectorRef.get(); + if (controller == null) { + Logger.printDebug(() -> "Cannot seek relative as MXD player controller is null"); + return false; + } + + return controller.seekToRelative(adjustedSeekTime); + } catch (Exception ex) { + Logger.printException(() -> "Failed to seek relative", ex); + return false; + } } /** diff --git a/integrations/java/app/revanced/integrations/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/integrations/java/app/revanced/integrations/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 566792857..56013f4d3 100644 --- a/integrations/java/app/revanced/integrations/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/integrations/java/app/revanced/integrations/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -1,19 +1,20 @@ package app.revanced.integrations.youtube.patches.playback.quality; -import androidx.annotation.Nullable; +import static app.revanced.integrations.shared.StringRef.str; +import static app.revanced.integrations.shared.Utils.NetworkType; -import app.revanced.integrations.shared.settings.IntegerSetting; -import app.revanced.integrations.youtube.settings.Settings; -import app.revanced.integrations.shared.Logger; -import app.revanced.integrations.shared.Utils; +import androidx.annotation.Nullable; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; -import static app.revanced.integrations.shared.StringRef.str; -import static app.revanced.integrations.shared.Utils.NetworkType; +import app.revanced.integrations.shared.Logger; +import app.revanced.integrations.shared.Utils; +import app.revanced.integrations.shared.settings.IntegerSetting; +import app.revanced.integrations.youtube.patches.VideoInformation; +import app.revanced.integrations.youtube.settings.Settings; @SuppressWarnings("unused") public class RememberVideoQualityPatch { @@ -158,7 +159,7 @@ public class RememberVideoQualityPatch { /** * Injection point. */ - public static void newVideoStarted(Object ignoredPlayerController) { + public static void newVideoStarted(VideoInformation.PlaybackController ignoredPlayerController) { Logger.printDebug(() -> "newVideoStarted"); qualityNeedsUpdating = true; videoQualities = null; diff --git a/integrations/java/app/revanced/integrations/youtube/patches/playback/speed/RememberPlaybackSpeedPatch.java b/integrations/java/app/revanced/integrations/youtube/patches/playback/speed/RememberPlaybackSpeedPatch.java index 1782ade6b..2bb3f9cb1 100644 --- a/integrations/java/app/revanced/integrations/youtube/patches/playback/speed/RememberPlaybackSpeedPatch.java +++ b/integrations/java/app/revanced/integrations/youtube/patches/playback/speed/RememberPlaybackSpeedPatch.java @@ -13,7 +13,7 @@ public final class RememberPlaybackSpeedPatch { /** * Injection point. */ - public static void newVideoStarted(Object ignoredPlayerController) { + public static void newVideoStarted(VideoInformation.PlaybackController ignoredPlayerController) { Logger.printDebug(() -> "newVideoStarted"); VideoInformation.overridePlaybackSpeed(Settings.PLAYBACK_SPEED_DEFAULT.get()); } diff --git a/integrations/java/app/revanced/integrations/youtube/sponsorblock/SegmentPlaybackController.java b/integrations/java/app/revanced/integrations/youtube/sponsorblock/SegmentPlaybackController.java index 9a35adced..ad02eec8b 100644 --- a/integrations/java/app/revanced/integrations/youtube/sponsorblock/SegmentPlaybackController.java +++ b/integrations/java/app/revanced/integrations/youtube/sponsorblock/SegmentPlaybackController.java @@ -11,11 +11,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; +import java.util.*; import app.revanced.integrations.shared.Logger; import app.revanced.integrations.shared.Utils; @@ -182,7 +178,7 @@ public class SegmentPlaybackController { * Injection point. * Initializes SponsorBlock when the video player starts playing a new video. */ - public static void initialize(Object ignoredPlayerController) { + public static void initialize(VideoInformation.PlaybackController ignoredPlayerController) { try { Utils.verifyOnMainThread(); SponsorBlockSettings.initialize(); @@ -632,6 +628,7 @@ public class SegmentPlaybackController { /** * Injection point */ + @SuppressWarnings("unused") public static void setSponsorBarRect(final Object self) { try { Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect"); @@ -663,6 +660,7 @@ public class SegmentPlaybackController { /** * Injection point */ + @SuppressWarnings("unused") public static void setSponsorBarThickness(int thickness) { if (sponsorBarThickness != thickness) { Logger.printDebug(() -> "setSponsorBarThickness: " + thickness); @@ -673,6 +671,7 @@ public class SegmentPlaybackController { /** * Injection point. */ + @SuppressWarnings("unused") public static String appendTimeWithoutSegments(String totalTime) { try { if (Settings.SB_ENABLED.get() && Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get() @@ -725,9 +724,9 @@ public class SegmentPlaybackController { final long minutes = (timeWithoutSegmentsValue / 60000) % 60; final long seconds = (timeWithoutSegmentsValue / 1000) % 60; if (hours > 0) { - timeWithoutSegments = String.format("\u2009(%d:%02d:%02d)", hours, minutes, seconds); + timeWithoutSegments = String.format(Locale.ENGLISH, "\u2009(%d:%02d:%02d)", hours, minutes, seconds); } else { - timeWithoutSegments = String.format("\u2009(%d:%02d)", minutes, seconds); + timeWithoutSegments = String.format(Locale.ENGLISH, "\u2009(%d:%02d)", minutes, seconds); } } @@ -744,6 +743,7 @@ public class SegmentPlaybackController { /** * Injection point. */ + @SuppressWarnings("unused") public static void drawSponsorTimeBars(final Canvas canvas, final float posY) { try { if (segments == null) return;