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 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<Object> playerControllerRef;
private static WeakReference<Object> mdxPlayerDirectorRef;
private static Method seekMethod;
private static Method mdxSeekMethod;
private static WeakReference<PlaybackController> playerControllerRef = new WeakReference<>(null);
private static WeakReference<PlaybackController> 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;
}
}
/**

View File

@ -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;

View File

@ -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());
}

View File

@ -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;