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>
This commit is contained in:
Seanti 2025-02-27 12:49:02 +08:00 committed by GitHub
parent 6ee7649581
commit 8659c489a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 91 additions and 16 deletions

View File

@ -68,6 +68,7 @@ public class PlayerControlsPatch {
// CreateSegmentButtonController.changeVisibility(showing, animation); // CreateSegmentButtonController.changeVisibility(showing, animation);
// VotingButtonController.changeVisibility(showing, animation); // VotingButtonController.changeVisibility(showing, animation);
// SegmentPlaybackController.changeVisibility(showing, animation);
} }
/** /**
@ -118,6 +119,7 @@ public class PlayerControlsPatch {
// CreateSegmentButtonController.changeVisibilityNegatedImmediate(); // CreateSegmentButtonController.changeVisibilityNegatedImmediate();
// VotingButtonController.changeVisibilityNegatedImmediate(); // VotingButtonController.changeVisibilityNegatedImmediate();
// SegmentPlaybackController.changeVisibilityNegatedImmediate();
} }
/** /**

View File

@ -15,7 +15,6 @@ import androidx.annotation.Nullable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
@ -48,7 +47,7 @@ public class SegmentPlaybackController {
*/ */
private static final long DURATION_TO_SHOW_SKIP_BUTTON = 3800; private static final long DURATION_TO_SHOW_SKIP_BUTTON = 3800;
/* /**
* Highlight segments have zero length as they are a point in time. * Highlight segments have zero length as they are a point in time.
* Draw them on screen using a fixed width bar. * Draw them on screen using a fixed width bar.
* Value is independent of device dpi. * 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. * @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,
@ -511,14 +510,14 @@ public class SegmentPlaybackController {
* Removes all previously hidden segments that are not longer contained in the given video time. * Removes all previously hidden segments that are not longer contained in the given video time.
*/ */
private static void updateHiddenSegments(long currentVideoTime) { private static void updateHiddenSegments(long currentVideoTime) {
Iterator<SponsorSegment> i = hiddenSkipSegmentsForCurrentVideoTime.iterator(); // If you want to maintain compatibility with RVX Android 6, use Iterator.
while (i.hasNext()) { hiddenSkipSegmentsForCurrentVideoTime.removeIf(segment -> {
SponsorSegment hiddenSegment = i.next(); if (!segment.containsTime(currentVideoTime)) {
if (!hiddenSegment.containsTime(currentVideoTime)) { Logger.printDebug(() -> "Resetting hide skip button: " + segment);
Logger.printDebug(() -> "Resetting hide skip button: " + hiddenSegment); return true;
i.remove();
} }
} return false;
});
} }
private static void setSegmentCurrentlyPlaying(@Nullable SponsorSegment segment) { private static void setSegmentCurrentlyPlaying(@Nullable SponsorSegment segment) {
@ -545,6 +544,72 @@ public class SegmentPlaybackController {
SponsorBlockViewController.showSkipSegmentButton(segment); 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.
*
* <p>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.</p>
*
* @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.
*
* <p>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.</p>
*
* @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) { private static void skipSegment(@NonNull SponsorSegment segmentToSkip, boolean userManuallySkipped) {
try { try {
SponsorBlockViewController.hideSkipHighlightButton(); SponsorBlockViewController.hideSkipHighlightButton();
@ -793,5 +858,4 @@ public class SegmentPlaybackController {
Logger.printException(() -> "drawSponsorTimeBars failure", ex); Logger.printException(() -> "drawSponsorTimeBars failure", ex);
} }
} }
} }

View File

@ -201,25 +201,25 @@ private fun MutableMethod.initializeHook(classDescriptor: String) =
"invoke-static {p0}, $classDescriptor->initialize(Landroid/view/View;)V" "invoke-static {p0}, $classDescriptor->initialize(Landroid/view/View;)V"
) )
private fun changeVisibilityHook(classDescriptor: String) = internal fun changeVisibilityHook(classDescriptor: String) =
changeVisibilityMethod.addInstruction( changeVisibilityMethod.addInstruction(
0, 0,
"invoke-static {p0, p1}, $classDescriptor->changeVisibility(ZZ)V" "invoke-static {p0, p1}, $classDescriptor->changeVisibility(ZZ)V"
) )
private fun changeVisibilityNegatedImmediateHook(classDescriptor: String) = internal fun changeVisibilityNegatedImmediateHook(classDescriptor: String) =
changeVisibilityNegatedImmediatelyMethod.addInstruction( changeVisibilityNegatedImmediatelyMethod.addInstruction(
0, 0,
"invoke-static {}, $classDescriptor->changeVisibilityNegatedImmediate()V" "invoke-static {}, $classDescriptor->changeVisibilityNegatedImmediate()V"
) )
fun hookBottomControlButton(classDescriptor: String) { internal fun hookBottomControlButton(classDescriptor: String) {
initializeBottomControlButtonMethod.initializeHook(classDescriptor) initializeBottomControlButtonMethod.initializeHook(classDescriptor)
changeVisibilityHook(classDescriptor) changeVisibilityHook(classDescriptor)
changeVisibilityNegatedImmediateHook(classDescriptor) changeVisibilityNegatedImmediateHook(classDescriptor)
} }
fun hookTopControlButton(classDescriptor: String) { internal fun hookTopControlButton(classDescriptor: String) {
initializeTopControlButtonMethod.initializeHook(classDescriptor) initializeTopControlButtonMethod.initializeHook(classDescriptor)
changeVisibilityHook(classDescriptor) changeVisibilityHook(classDescriptor)
changeVisibilityNegatedImmediateHook(classDescriptor) changeVisibilityNegatedImmediateHook(classDescriptor)

View File

@ -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.extension.Constants.PATCH_STATUS_CLASS_DESCRIPTOR
import app.revanced.patches.youtube.utils.patch.PatchList.SPONSORBLOCK import app.revanced.patches.youtube.utils.patch.PatchList.SPONSORBLOCK
import app.revanced.patches.youtube.utils.playercontrols.addTopControl 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.hookTopControlButton
import app.revanced.patches.youtube.utils.playercontrols.playerControlsPatch import app.revanced.patches.youtube.utils.playercontrols.playerControlsPatch
import app.revanced.patches.youtube.utils.resourceid.insetOverlayViewLayout import app.revanced.patches.youtube.utils.resourceid.insetOverlayViewLayout
@ -111,10 +113,17 @@ val sponsorBlockBytecodePatch = bytecodePatch(
} }
// Voting & Shield button // Voting & Shield button
setOf("CreateSegmentButtonController;", "VotingButtonController;").forEach { className -> setOf(
"CreateSegmentButtonController;",
"VotingButtonController;"
).forEach { className ->
hookTopControlButton("$EXTENSION_SPONSOR_BLOCK_UI_PATH/$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 // Append timestamp
totalTimeFingerprint.methodOrThrow().apply { totalTimeFingerprint.methodOrThrow().apply {
val targetIndex = indexOfFirstInstructionOrThrow { val targetIndex = indexOfFirstInstructionOrThrow {