fix(youtube/sponsorblock): fix some segments skipping slightly too late (#436)

This commit is contained in:
LisoUseInAIKyrios 2023-07-14 12:35:20 +04:00 committed by GitHub
parent 59850b2c04
commit f69492819e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 54 additions and 40 deletions

View File

@ -24,7 +24,7 @@ public final class VideoInformation {
@NonNull @NonNull
private static String videoId = ""; private static String videoId = "";
private static long videoLength = 0; private static long videoLength = 0;
private static volatile long videoTime = -1; // must be volatile. Value is set off main thread from high precision patch hook private static long videoTime = -1;
/** /**
* The current playback speed * The current playback speed
*/ */
@ -98,17 +98,17 @@ public final class VideoInformation {
/** /**
* Injection point. * Injection point.
* Called off the main thread approximately every 50ms to 100ms * Called on the main thread every 1000ms.
* *
* @param currentPlaybackTime The current playback time of the video in milliseconds. * @param currentPlaybackTime The current playback time of the video in milliseconds.
*/ */
public static void setVideoTimeHighPrecision(final long currentPlaybackTime) { public static void setVideoTime(final long currentPlaybackTime) {
videoTime = currentPlaybackTime; videoTime = currentPlaybackTime;
} }
/** /**
* Seek on the current video. * Seek on the current video.
* Does not function for playback of Shorts or Stories. * Does not function for playback of Shorts.
* *
* Caution: If called from a videoTimeHook() callback, * Caution: If called from a videoTimeHook() callback,
* this will cause a recursive call into the same videoTimeHook() callback. * this will cause a recursive call into the same videoTimeHook() callback.
@ -118,11 +118,6 @@ public final class VideoInformation {
*/ */
public static boolean seekTo(final long millisecond) { public static boolean seekTo(final long millisecond) {
ReVancedUtils.verifyOnMainThread(); ReVancedUtils.verifyOnMainThread();
if (seekMethod == null) {
LogHelper.printException(() -> "seekMethod was null");
return false;
}
try { try {
LogHelper.printDebug(() -> "Seeking to " + millisecond); LogHelper.printDebug(() -> "Seeking to " + millisecond);
return (Boolean) seekMethod.invoke(playerControllerRef.get(), millisecond); return (Boolean) seekMethod.invoke(playerControllerRef.get(), millisecond);
@ -137,7 +132,7 @@ public final class VideoInformation {
} }
/** /**
* Id of the current video playing. Includes Shorts and YouTube Stories. * Id of the current video playing. Includes Shorts.
* *
* @return The id of the video. Empty string if not set yet. * @return The id of the video. Empty string if not set yet.
*/ */
@ -154,7 +149,7 @@ public final class VideoInformation {
} }
/** /**
* Length of the current video playing. Includes Shorts and YouTube Stories. * Length of the current video playing. Includes Shorts.
* *
* @return The length of the video in milliseconds. * @return The length of the video in milliseconds.
* If the video is not yet loaded, or if the video is playing in the background with no video visible, * If the video is not yet loaded, or if the video is playing in the background with no video visible,
@ -165,14 +160,14 @@ public final class VideoInformation {
} }
/** /**
* Playback time of the current video playing. * Playback time of the current video playing. Includes Shorts.
* Value can lag up to approximately 100ms behind the actual current video playback time.
* *
* Note: Code inside a videoTimeHook patch callback * Value will lag behind the actual playback time by a variable amount based on the playback speed.
* should use the callback video time and avoid using this method
* (in situations of recursive hook callbacks, the value returned here may be outdated).
* *
* Includes Shorts and YouTube Stories. * If playback speed is 2.0x, this value may be up to 2000ms behind the actual playback time.
* If playback speed is 1.0x, this value may be up to 1000ms behind the actual playback time.
* If playback speed is 0.5x, this value may be up to 500ms behind the actual playback time.
* Etc.
* *
* @return The time of the video in milliseconds. -1 if not set yet. * @return The time of the video in milliseconds. -1 if not set yet.
*/ */
@ -192,7 +187,7 @@ public final class VideoInformation {
* @see VideoState * @see VideoState
*/ */
public static boolean isAtEndOfVideo() { public static boolean isAtEndOfVideo() {
return videoTime > 0 && videoLength > 0 && videoTime >= videoLength; return videoTime >= videoLength && videoLength > 0;
} }
} }

View File

@ -58,6 +58,7 @@ public class SegmentPlaybackController {
/** /**
* Because loading can take time, show the skip to highlight for a few seconds after the segments load. * Because loading can take time, show the skip to highlight for a few seconds after the segments load.
* This is the system time (in milliseconds) to no longer show the initial display skip to highlight. * This is the system time (in milliseconds) to no longer show the initial display skip to highlight.
* Value will be zero if no highlight segment exists, or if the system time to show the highlight has passed.
*/ */
private static long highlightSegmentInitialShowEndTime; private static long highlightSegmentInitialShowEndTime;
@ -198,7 +199,7 @@ public class SegmentPlaybackController {
return; return;
} }
if (PlayerType.getCurrent().isNoneOrHidden()) { if (PlayerType.getCurrent().isNoneOrHidden()) {
LogHelper.printDebug(() -> "ignoring short or story"); LogHelper.printDebug(() -> "ignoring Short");
return; return;
} }
if (!ReVancedUtils.isNetworkConnected()) { if (!ReVancedUtils.isNetworkConnected()) {
@ -238,14 +239,20 @@ public class SegmentPlaybackController {
setSegments(segments); setSegments(segments);
final long videoTime = VideoInformation.getVideoTime(); final long videoTime = VideoInformation.getVideoTime();
// if the current video time is before the highlight if (highlightSegment != null) {
if (highlightSegment != null && videoTime < highlightSegment.end) { // If the current video time is before the highlight.
if (highlightSegment.shouldAutoSkip()) { final long timeUntilHighlight = highlightSegment.start - videoTime;
skipSegment(highlightSegment, false); if (timeUntilHighlight > 0) {
return; if (highlightSegment.shouldAutoSkip()) {
skipSegment(highlightSegment, false);
return;
}
highlightSegmentInitialShowEndTime = System.currentTimeMillis() + Math.min(
(long) (timeUntilHighlight / VideoInformation.getPlaybackSpeed()),
DURATION_TO_SHOW_SKIP_BUTTON);
} }
highlightSegmentInitialShowEndTime = System.currentTimeMillis() + DURATION_TO_SHOW_SKIP_BUTTON;
} }
// check for any skips now, instead of waiting for the next update to setVideoTime() // check for any skips now, instead of waiting for the next update to setVideoTime()
setVideoTime(videoTime); setVideoTime(videoTime);
}); });
@ -262,7 +269,7 @@ public class SegmentPlaybackController {
public static void setVideoTime(long millis) { public static void setVideoTime(long millis) {
try { try {
if (!SettingsEnum.SB_ENABLED.getBoolean() if (!SettingsEnum.SB_ENABLED.getBoolean()
|| PlayerType.getCurrent().isNoneOrHidden() // shorts playback || PlayerType.getCurrent().isNoneOrHidden() // Shorts playback.
|| segments == null || segments.length == 0) { || segments == null || segments.length == 0) {
return; return;
} }
@ -270,11 +277,17 @@ public class SegmentPlaybackController {
updateHiddenSegments(millis); updateHiddenSegments(millis);
// to debug the timing logic, set this to a very large value (5000 or more)
// then try manually seeking just playback reaches a skip/hide of different segments
final long lookAheadMilliseconds = 1500; // must be larger than the average time between calls to this method
final float playbackSpeed = VideoInformation.getPlaybackSpeed(); final float playbackSpeed = VideoInformation.getPlaybackSpeed();
final long startTimerLookAheadThreshold = millis + (long)(playbackSpeed * lookAheadMilliseconds); // Amount of time to look ahead for the next segment,
// and the threshold to determine if a scheduled show/hide is at the correct video time when it's run.
//
// This value must be greater than largest time between calls to this method (1000ms),
// and must be adjusted for the video speed.
//
// To debug the stale skip logic, set this to a very large value (5000 or more)
// then try manually seeking just before playback reaches a segment skip.
final long speedAdjustedTimeThreshold = (long)(playbackSpeed * 1200);
final long startTimerLookAheadThreshold = millis + speedAdjustedTimeThreshold;
SponsorSegment foundSegmentCurrentlyPlaying = null; SponsorSegment foundSegmentCurrentlyPlaying = null;
SponsorSegment foundUpcomingSegment = null; SponsorSegment foundUpcomingSegment = null;
@ -344,9 +357,11 @@ public class SegmentPlaybackController {
} }
if (highlightSegment != null) { if (highlightSegment != null) {
if (millis < DURATION_TO_SHOW_SKIP_BUTTON || System.currentTimeMillis() < highlightSegmentInitialShowEndTime) { if (millis < DURATION_TO_SHOW_SKIP_BUTTON || (highlightSegmentInitialShowEndTime != 0
&& System.currentTimeMillis() < highlightSegmentInitialShowEndTime)) {
SponsorBlockViewController.showSkipHighlightButton(highlightSegment); SponsorBlockViewController.showSkipHighlightButton(highlightSegment);
} else { } else {
highlightSegmentInitialShowEndTime = 0;
SponsorBlockViewController.hideSkipHighlightButton(); SponsorBlockViewController.hideSkipHighlightButton();
} }
} }
@ -361,12 +376,9 @@ public class SegmentPlaybackController {
SponsorBlockViewController.hideSkipSegmentButton(); SponsorBlockViewController.hideSkipSegmentButton();
} }
// must be greater than the average time between updates to VideoInformation time
final long videoInformationTimeUpdateThresholdMilliseconds = 250;
// schedule a hide, only if the segment end is near // schedule a hide, only if the segment end is near
final SponsorSegment segmentToHide = final SponsorSegment segmentToHide =
(foundSegmentCurrentlyPlaying != null && foundSegmentCurrentlyPlaying.endIsNear(millis, lookAheadMilliseconds)) (foundSegmentCurrentlyPlaying != null && foundSegmentCurrentlyPlaying.endIsNear(millis, speedAdjustedTimeThreshold))
? foundSegmentCurrentlyPlaying ? foundSegmentCurrentlyPlaying
: null; : null;
@ -384,9 +396,13 @@ public class SegmentPlaybackController {
return; return;
} }
scheduledHideSegment = null; scheduledHideSegment = null;
if (VideoState.getCurrent() != VideoState.PLAYING) {
LogHelper.printDebug(() -> "Ignoring scheduled hide segment as video is paused: " + segmentToHide);
return;
}
final long videoTime = VideoInformation.getVideoTime(); final long videoTime = VideoInformation.getVideoTime();
if (!segmentToHide.endIsNear(videoTime, videoInformationTimeUpdateThresholdMilliseconds)) { if (!segmentToHide.endIsNear(videoTime, speedAdjustedTimeThreshold)) {
// current video time is not what's expected. User paused playback // current video time is not what's expected. User paused playback
LogHelper.printDebug(() -> "Ignoring outdated scheduled hide: " + segmentToHide LogHelper.printDebug(() -> "Ignoring outdated scheduled hide: " + segmentToHide
+ " videoInformation time: " + videoTime); + " videoInformation time: " + videoTime);
@ -419,10 +435,13 @@ public class SegmentPlaybackController {
return; return;
} }
scheduledUpcomingSegment = null; scheduledUpcomingSegment = null;
if (VideoState.getCurrent() != VideoState.PLAYING) {
LogHelper.printDebug(() -> "Ignoring scheduled hide segment as video is paused: " + segmentToSkip);
return;
}
final long videoTime = VideoInformation.getVideoTime(); final long videoTime = VideoInformation.getVideoTime();
if (!segmentToSkip.startIsNear(videoTime, if (!segmentToSkip.startIsNear(videoTime, speedAdjustedTimeThreshold)) {
videoInformationTimeUpdateThresholdMilliseconds)) {
// current video time is not what's expected. User paused playback // current video time is not what's expected. User paused playback
LogHelper.printDebug(() -> "Ignoring outdated scheduled segment: " + segmentToSkip LogHelper.printDebug(() -> "Ignoring outdated scheduled segment: " + segmentToSkip
+ " videoInformation time: " + videoTime); + " videoInformation time: " + videoTime);
@ -488,10 +507,10 @@ public class SegmentPlaybackController {
SponsorBlockViewController.hideSkipHighlightButton(); SponsorBlockViewController.hideSkipHighlightButton();
SponsorBlockViewController.hideSkipSegmentButton(); SponsorBlockViewController.hideSkipSegmentButton();
// If trying to seek to end of the video, YouTube can seek just short of the actual end. // If trying to seek to end of the video, YouTube can seek just before of the actual end.
// (especially if the video does not end on a whole second boundary). // (especially if the video does not end on a whole second boundary).
// This causes additional segment skip attempts, even though it cannot seek any closer to the desired time. // This causes additional segment skip attempts, even though it cannot seek any closer to the desired time.
// Check for and ignore repeated skip attempts of the same segment over a short time period. // Check for and ignore repeated skip attempts of the same segment over a small time period.
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
final long minimumMillisecondsBetweenSkippingSameSegment = 500; final long minimumMillisecondsBetweenSkippingSameSegment = 500;
if ((lastSegmentSkipped == segmentToSkip) && (now - lastSegmentSkippedTime < minimumMillisecondsBetweenSkippingSameSegment)) { if ((lastSegmentSkipped == segmentToSkip) && (now - lastSegmentSkippedTime < minimumMillisecondsBetweenSkippingSameSegment)) {