fix(YouTube - SponsorBlock): Improve create segment manual seek accuracy (#671)

This commit is contained in:
LisoUseInAIKyrios 2024-08-02 09:39:33 -04:00 committed by GitHub
parent d57a64b659
commit 34c02aeb2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 88 additions and 52 deletions

View File

@ -7,7 +7,6 @@ import app.revanced.integrations.shared.Logger;
import app.revanced.integrations.shared.Utils; import app.revanced.integrations.shared.Utils;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.Objects; import java.util.Objects;
/** /**
@ -15,17 +14,21 @@ import java.util.Objects;
* @noinspection unused * @noinspection unused
*/ */
public final class VideoInformation { 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 float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f;
private static final String SEEK_METHOD_NAME = "seekTo";
/** /**
* Prefix present in all Short player parameters signature. * Prefix present in all Short player parameters signature.
*/ */
private static final String SHORTS_PLAYER_PARAMETERS = "8AEB"; private static final String SHORTS_PLAYER_PARAMETERS = "8AEB";
private static WeakReference<Object> playerControllerRef; private static WeakReference<PlaybackController> playerControllerRef = new WeakReference<>(null);
private static WeakReference<Object> mdxPlayerDirectorRef; private static WeakReference<PlaybackController> mdxPlayerDirectorRef = new WeakReference<>(null);
private static Method seekMethod;
private static Method mdxSeekMethod;
@NonNull @NonNull
private static String videoId = ""; private static String videoId = "";
@ -47,15 +50,12 @@ public final class VideoInformation {
* *
* @param playerController player controller object. * @param playerController player controller object.
*/ */
public static void initialize(@NonNull Object playerController) { public static void initialize(@NonNull PlaybackController playerController) {
try { try {
playerControllerRef = new WeakReference<>(Objects.requireNonNull(playerController)); playerControllerRef = new WeakReference<>(Objects.requireNonNull(playerController));
videoTime = -1; videoTime = -1;
videoLength = 0; videoLength = 0;
playbackSpeed = DEFAULT_YOUTUBE_PLAYBACK_SPEED; playbackSpeed = DEFAULT_YOUTUBE_PLAYBACK_SPEED;
seekMethod = playerController.getClass().getMethod(SEEK_METHOD_NAME, Long.TYPE);
seekMethod.setAccessible(true);
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "Failed to initialize", ex); Logger.printException(() -> "Failed to initialize", ex);
} }
@ -66,12 +66,9 @@ public final class VideoInformation {
* *
* @param mdxPlayerDirector MDX player director object (casting mode). * @param mdxPlayerDirector MDX player director object (casting mode).
*/ */
public static void initializeMdx(@NonNull Object mdxPlayerDirector) { public static void initializeMdx(@NonNull PlaybackController mdxPlayerDirector) {
try { try {
mdxPlayerDirectorRef = new WeakReference<>(Objects.requireNonNull(mdxPlayerDirector)); mdxPlayerDirectorRef = new WeakReference<>(Objects.requireNonNull(mdxPlayerDirector));
mdxSeekMethod = mdxPlayerDirector.getClass().getMethod(SEEK_METHOD_NAME, Long.TYPE);
mdxSeekMethod.setAccessible(true);
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "Failed to initialize MDX", ex); Logger.printException(() -> "Failed to initialize MDX", ex);
} }
@ -195,42 +192,80 @@ public final class VideoInformation {
return false; return false;
} }
Logger.printDebug(() -> "Seeking to " + adjustedSeekTime); Logger.printDebug(() -> "Seeking to: " + adjustedSeekTime);
try { // Try regular playback controller first, and it will not succeed if casting.
//noinspection DataFlowIssue PlaybackController controller = playerControllerRef.get();
if ((Boolean) seekMethod.invoke(playerControllerRef.get(), adjustedSeekTime)) { if (controller == null) {
return true; Logger.printDebug(() -> "Cannot seekTo because player controller is null");
} // Else the video is loading or changing videos, or video is casting to a different device. } else {
} catch (Exception ex) { if (controller.seekTo(adjustedSeekTime)) return true;
Logger.printInfo(() -> "seekTo method call failed", ex); 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). // 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 // The difference has to be a different second mark in order to avoid infinite skip loops
// as the Lounge API only supports seconds. // as the Lounge API only supports seconds.
if ((adjustedSeekTime / 1000) == (videoTime / 1000)) { if (adjustedSeekTime / 1000 == videoTime / 1000) {
Logger.printDebug(() -> "Skipping seekTo for MDX because seek time is too small (" Logger.printDebug(() -> "Skipping seekTo for MDX because seek time is too small "
+ (adjustedSeekTime - videoTime) + "ms)"); + "(" + (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);
return false; 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) { } catch (Exception ex) {
Logger.printException(() -> "Failed to seek", ex); Logger.printException(() -> "Failed to seek", ex);
return false; return false;
} }
} }
/** @noinspection UnusedReturnValue*/ /**
public static boolean seekToRelative(long millisecondsRelative) { * Seeks a relative amount. Should always be used over {@link #seekTo(long)}
return seekTo(videoTime + millisecondsRelative); * 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;
}
} }
/** /**

View File

@ -1,19 +1,20 @@
package app.revanced.integrations.youtube.patches.playback.quality; 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 androidx.annotation.Nullable;
import app.revanced.integrations.youtube.settings.Settings;
import app.revanced.integrations.shared.Logger;
import app.revanced.integrations.shared.Utils;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static app.revanced.integrations.shared.StringRef.str; import app.revanced.integrations.shared.Logger;
import static app.revanced.integrations.shared.Utils.NetworkType; 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") @SuppressWarnings("unused")
public class RememberVideoQualityPatch { public class RememberVideoQualityPatch {
@ -158,7 +159,7 @@ public class RememberVideoQualityPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static void newVideoStarted(Object ignoredPlayerController) { public static void newVideoStarted(VideoInformation.PlaybackController ignoredPlayerController) {
Logger.printDebug(() -> "newVideoStarted"); Logger.printDebug(() -> "newVideoStarted");
qualityNeedsUpdating = true; qualityNeedsUpdating = true;
videoQualities = null; videoQualities = null;

View File

@ -13,7 +13,7 @@ public final class RememberPlaybackSpeedPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static void newVideoStarted(Object ignoredPlayerController) { public static void newVideoStarted(VideoInformation.PlaybackController ignoredPlayerController) {
Logger.printDebug(() -> "newVideoStarted"); Logger.printDebug(() -> "newVideoStarted");
VideoInformation.overridePlaybackSpeed(Settings.PLAYBACK_SPEED_DEFAULT.get()); VideoInformation.overridePlaybackSpeed(Settings.PLAYBACK_SPEED_DEFAULT.get());
} }

View File

@ -11,11 +11,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import app.revanced.integrations.shared.Logger; import app.revanced.integrations.shared.Logger;
import app.revanced.integrations.shared.Utils; import app.revanced.integrations.shared.Utils;
@ -182,7 +178,7 @@ public class SegmentPlaybackController {
* Injection point. * Injection point.
* Initializes SponsorBlock when the video player starts playing a new video. * 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 { try {
Utils.verifyOnMainThread(); Utils.verifyOnMainThread();
SponsorBlockSettings.initialize(); SponsorBlockSettings.initialize();
@ -632,6 +628,7 @@ public class SegmentPlaybackController {
/** /**
* Injection point * Injection point
*/ */
@SuppressWarnings("unused")
public static void setSponsorBarRect(final Object self) { public static void setSponsorBarRect(final Object self) {
try { try {
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect"); Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
@ -663,6 +660,7 @@ public class SegmentPlaybackController {
/** /**
* Injection point * Injection point
*/ */
@SuppressWarnings("unused")
public static void setSponsorBarThickness(int thickness) { public static void setSponsorBarThickness(int thickness) {
if (sponsorBarThickness != thickness) { if (sponsorBarThickness != thickness) {
Logger.printDebug(() -> "setSponsorBarThickness: " + thickness); Logger.printDebug(() -> "setSponsorBarThickness: " + thickness);
@ -673,6 +671,7 @@ public class SegmentPlaybackController {
/** /**
* Injection point. * Injection point.
*/ */
@SuppressWarnings("unused")
public static String appendTimeWithoutSegments(String totalTime) { public static String appendTimeWithoutSegments(String totalTime) {
try { try {
if (Settings.SB_ENABLED.get() && Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get() 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 minutes = (timeWithoutSegmentsValue / 60000) % 60;
final long seconds = (timeWithoutSegmentsValue / 1000) % 60; final long seconds = (timeWithoutSegmentsValue / 1000) % 60;
if (hours > 0) { 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 { } 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. * Injection point.
*/ */
@SuppressWarnings("unused")
public static void drawSponsorTimeBars(final Canvas canvas, final float posY) { public static void drawSponsorTimeBars(final Canvas canvas, final float posY) {
try { try {
if (segments == null) return; if (segments == null) return;