From 8659c489a02a4bdca13c4d56f6023e7e811ce14c Mon Sep 17 00:00:00 2001 From: Seanti <108224418+PizzaSpark@users.noreply.github.com> Date: Thu, 27 Feb 2025 12:49:02 +0800 Subject: [PATCH] feat(YouTube - SponsorBlock): After the skip button is automatically hidden, makes the visibility of the skip button match the player control's (#142) * ci: workflow to ping Discord users when patches are released (#72) * init: Workflow to notify discord users of releases * Rename workflow * chore (Background playback): Shorten description * Revert "chore (Background playback): Shorten description" This reverts commit 10661b870f0c9c670c5d522f9b2ca7cc82d32772. * Change message contents * feat(YouTube - SponsorBlock): Display skip segment button when player controls are shown Closes https://github.com/anddea/revanced-patches/issues/962 * feat: Apply code review suggestions --------- Co-authored-by: KobeW50 <84587632+KobeW50@users.noreply.github.com> Co-authored-by: inotia00 <108592928+inotia00@users.noreply.github.com> Co-authored-by: Aaron Veil <70171475+anddea@users.noreply.github.com> --- .../patches/utils/PlayerControlsPatch.java | 2 + .../SegmentPlaybackController.java | 86 ++++++++++++++++--- .../playercontrols/PlayerControlsPatch.kt | 8 +- .../utils/sponsorblock/SponsorBlockPatch.kt | 11 ++- 4 files changed, 91 insertions(+), 16 deletions(-) diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlayerControlsPatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlayerControlsPatch.java index 7a3442505..f1f34ccab 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlayerControlsPatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/utils/PlayerControlsPatch.java @@ -68,6 +68,7 @@ public class PlayerControlsPatch { // CreateSegmentButtonController.changeVisibility(showing, animation); // VotingButtonController.changeVisibility(showing, animation); + // SegmentPlaybackController.changeVisibility(showing, animation); } /** @@ -118,6 +119,7 @@ public class PlayerControlsPatch { // CreateSegmentButtonController.changeVisibilityNegatedImmediate(); // VotingButtonController.changeVisibilityNegatedImmediate(); + // SegmentPlaybackController.changeVisibilityNegatedImmediate(); } /** diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SegmentPlaybackController.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SegmentPlaybackController.java index 948f2d044..808133725 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SegmentPlaybackController.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/sponsorblock/SegmentPlaybackController.java @@ -15,7 +15,6 @@ 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.Locale; import java.util.Objects; @@ -48,7 +47,7 @@ public class SegmentPlaybackController { */ private static final long DURATION_TO_SHOW_SKIP_BUTTON = 3800; - /* + /** * Highlight segments have zero length as they are a point in time. * Draw them on screen using a fixed width bar. * Value is independent of device dpi. @@ -257,7 +256,7 @@ public class SegmentPlaybackController { } /** - * Length of the current video playing. Includes Shorts. + * Length of the current video playing. Includes Shorts. * * @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, @@ -511,14 +510,14 @@ public class SegmentPlaybackController { * Removes all previously hidden segments that are not longer contained in the given video time. */ private static void updateHiddenSegments(long currentVideoTime) { - Iterator i = hiddenSkipSegmentsForCurrentVideoTime.iterator(); - while (i.hasNext()) { - SponsorSegment hiddenSegment = i.next(); - if (!hiddenSegment.containsTime(currentVideoTime)) { - Logger.printDebug(() -> "Resetting hide skip button: " + hiddenSegment); - i.remove(); + // If you want to maintain compatibility with RVX Android 6, use Iterator. + hiddenSkipSegmentsForCurrentVideoTime.removeIf(segment -> { + if (!segment.containsTime(currentVideoTime)) { + Logger.printDebug(() -> "Resetting hide skip button: " + segment); + return true; } - } + return false; + }); } private static void setSegmentCurrentlyPlaying(@Nullable SponsorSegment segment) { @@ -545,6 +544,72 @@ public class SegmentPlaybackController { SponsorBlockViewController.showSkipSegmentButton(segment); } + public static void changeVisibility(boolean showing, boolean animation) { + onPlayerControlsVisibilityChanged(showing, false); + } + + public static void changeVisibilityNegatedImmediate() { + onPlayerControlsVisibilityChanged(false, true); + } + + /** + * Handles changes in player control visibility and manages the skip segment button accordingly. + * + *

This method is called whenever the visibility state of the player controls changes. + * If auto-hide is enabled and there is a currently playing sponsor segment, it will show + * the skip segment button when the controls are visible and schedule recursive checks + * to hide the button after a defined duration.

+ * + * @param visible if true, player controls are visible (The user touched the player when the player controls were invisible) + * @param immediate if true, player controls are invisible (The user touched the player when the player controls were visible) + */ + private static void onPlayerControlsVisibilityChanged(boolean visible, boolean immediate) { + if (!Settings.SB_ENABLED.get() + || !Settings.SB_AUTO_HIDE_SKIP_BUTTON.get() + || segmentCurrentlyPlaying == null + || !hiddenSkipSegmentsForCurrentVideoTime.contains(segmentCurrentlyPlaying)) { + return; + } + + // When the player button appears after the skip button is hidden + if (visible) { + SponsorBlockViewController.showSkipSegmentButton(segmentCurrentlyPlaying); + skipSegmentButtonEndTime = System.currentTimeMillis() + 2000; // Player buttons are hidden after 2000ms + checkPlayerControlsVisibilityRecursive(segmentCurrentlyPlaying); + } else if (immediate) { + // Hide the skip segment button and reset the end time + skipSegmentButtonEndTime = 0; + SponsorBlockViewController.hideSkipSegmentButton(); + } + } + + /** + * Recursively checks whether the skip segment button should remain visible or be hidden. + * + *

This method continues checking at a fixed interval (500 milliseconds) if the button + * should be hidden. The recursion stops if the current segment changes or the duration + * to show the button has expired.

+ * + * @param segment the sponsor segment associated with the current check + */ + private static void checkPlayerControlsVisibilityRecursive(SponsorSegment segment) { + if (skipSegmentButtonEndTime == 0 + // Stop recursion if the current segment has changed + || segment != segmentCurrentlyPlaying) { + return; + } + + // Continue recursion if the button's visibility duration has not expired + if (skipSegmentButtonEndTime > System.currentTimeMillis()) { + Utils.runOnMainThreadDelayed(() -> checkPlayerControlsVisibilityRecursive(segment), 1000); + } else { + // Hide the skip segment button and reset the end time + skipSegmentButtonEndTime = 0; + hiddenSkipSegmentsForCurrentVideoTime.add(segment); + SponsorBlockViewController.hideSkipSegmentButton(); + } + } + private static void skipSegment(@NonNull SponsorSegment segmentToSkip, boolean userManuallySkipped) { try { SponsorBlockViewController.hideSkipHighlightButton(); @@ -793,5 +858,4 @@ public class SegmentPlaybackController { Logger.printException(() -> "drawSponsorTimeBars failure", ex); } } - } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch.kt index 5dbf43ec9..a4763e286 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playercontrols/PlayerControlsPatch.kt @@ -201,25 +201,25 @@ private fun MutableMethod.initializeHook(classDescriptor: String) = "invoke-static {p0}, $classDescriptor->initialize(Landroid/view/View;)V" ) -private fun changeVisibilityHook(classDescriptor: String) = +internal fun changeVisibilityHook(classDescriptor: String) = changeVisibilityMethod.addInstruction( 0, "invoke-static {p0, p1}, $classDescriptor->changeVisibility(ZZ)V" ) -private fun changeVisibilityNegatedImmediateHook(classDescriptor: String) = +internal fun changeVisibilityNegatedImmediateHook(classDescriptor: String) = changeVisibilityNegatedImmediatelyMethod.addInstruction( 0, "invoke-static {}, $classDescriptor->changeVisibilityNegatedImmediate()V" ) -fun hookBottomControlButton(classDescriptor: String) { +internal fun hookBottomControlButton(classDescriptor: String) { initializeBottomControlButtonMethod.initializeHook(classDescriptor) changeVisibilityHook(classDescriptor) changeVisibilityNegatedImmediateHook(classDescriptor) } -fun hookTopControlButton(classDescriptor: String) { +internal fun hookTopControlButton(classDescriptor: String) { initializeTopControlButtonMethod.initializeHook(classDescriptor) changeVisibilityHook(classDescriptor) changeVisibilityNegatedImmediateHook(classDescriptor) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/sponsorblock/SponsorBlockPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/sponsorblock/SponsorBlockPatch.kt index 2c1692bb3..42420d418 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/sponsorblock/SponsorBlockPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/sponsorblock/SponsorBlockPatch.kt @@ -12,6 +12,8 @@ import app.revanced.patches.youtube.utils.extension.Constants.EXTENSION_PATH import app.revanced.patches.youtube.utils.extension.Constants.PATCH_STATUS_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.patch.PatchList.SPONSORBLOCK import app.revanced.patches.youtube.utils.playercontrols.addTopControl +import app.revanced.patches.youtube.utils.playercontrols.changeVisibilityHook +import app.revanced.patches.youtube.utils.playercontrols.changeVisibilityNegatedImmediateHook import app.revanced.patches.youtube.utils.playercontrols.hookTopControlButton import app.revanced.patches.youtube.utils.playercontrols.playerControlsPatch import app.revanced.patches.youtube.utils.resourceid.insetOverlayViewLayout @@ -111,10 +113,17 @@ val sponsorBlockBytecodePatch = bytecodePatch( } // Voting & Shield button - setOf("CreateSegmentButtonController;", "VotingButtonController;").forEach { className -> + setOf( + "CreateSegmentButtonController;", + "VotingButtonController;" + ).forEach { className -> hookTopControlButton("$EXTENSION_SPONSOR_BLOCK_UI_PATH/$className") } + // Skip button + changeVisibilityHook(EXTENSION_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR) + changeVisibilityNegatedImmediateHook(EXTENSION_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR) + // Append timestamp totalTimeFingerprint.methodOrThrow().apply { val targetIndex = indexOfFirstInstructionOrThrow {