feat(YouTube - SponsorBlock): Add 'Hook' segment category (#5783)

This commit is contained in:
LisoUseInAIKyrios
2025-09-12 05:56:50 +04:00
committed by GitHub
parent 5ace6f587c
commit 9d4aa5cd16
8 changed files with 105 additions and 15 deletions

View File

@@ -436,6 +436,9 @@ public class Settings extends BaseSettings {
public static final StringSetting SB_CATEGORY_HIGHLIGHT = new StringSetting("sb_highlight", MANUAL_SKIP.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_HIGHLIGHT_COLOR = new StringSetting("sb_highlight_color", "#FF1684");
public static final FloatSetting SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_HOOK = new StringSetting("sb_hook", IGNORE.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_HOOK_COLOR = new StringSetting("sb_hook_color", "#395699");
public static final FloatSetting SB_CATEGORY_HOOK_OPACITY = new FloatSetting("sb_hook_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", MANUAL_SKIP.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#00FFFF");
public static final FloatSetting SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f);

View File

@@ -15,6 +15,7 @@ import android.graphics.drawable.shapes.RoundRectShape;
import android.text.TextUtils;
import android.util.Range;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
@@ -167,6 +168,11 @@ public class SegmentPlaybackController {
*/
private static WeakReference<Dialog> toastDialogRef = new WeakReference<>(null);
/**
* Visibility of the ad progress UI component.
*/
private static volatile int adProgressTextVisibility = -1;
static {
// Dismiss toast if app changes to PiP while undo skip is shown.
PlayerType.getOnChange().addObserver((PlayerType type) -> {
@@ -336,6 +342,7 @@ public class SegmentPlaybackController {
*/
static void executeDownloadSegments(String videoId) {
Objects.requireNonNull(videoId);
Utils.verifyOffMainThread();
SponsorSegment[] segments = SBRequester.getSegments(videoId);
@@ -367,6 +374,35 @@ public class SegmentPlaybackController {
});
}
/**
* Injection point.
*/
@SuppressWarnings("unused")
public static void setAdProgressTextVisibility(int visibility) {
if (adProgressTextVisibility != visibility) {
adProgressTextVisibility = visibility;
Logger.printDebug(() -> {
String visibilityMessage = switch (visibility) {
case View.VISIBLE -> "VISIBLE";
case View.GONE -> "GONE";
case View.INVISIBLE -> "INVISIBLE";
default -> "UNKNOWN";
};
return "AdProgressText visibility changed to: " + visibilityMessage;
});
}
}
/**
* When a video ad is playing in a regular video player, segments or the Skip button should be hidden.
* @return Whether the Ad Progress TextView is visible in the regular video player.
*/
public static boolean isAdProgressTextVisible() {
return adProgressTextVisibility == View.VISIBLE;
}
/**
* Injection point.
* Updates SponsorBlock every 1000ms.
@@ -376,7 +412,8 @@ public class SegmentPlaybackController {
try {
if (!Settings.SB_ENABLED.get()
|| PlayerType.getCurrent().isNoneOrHidden() // Shorts playback.
|| segments == null || segments.length == 0) {
|| segments == null || segments.length == 0
|| isAdProgressTextVisible()) {
return;
}
Logger.printDebug(() -> "setVideoTime: " + millis);
@@ -671,7 +708,14 @@ public class SegmentPlaybackController {
// Check for any smaller embedded segments, and count those as auto-skipped.
final boolean showSkipToast = Settings.SB_TOAST_ON_SKIP.get();
for (SponsorSegment otherSegment : Objects.requireNonNull(segments)) {
if (segmentToSkip.end < otherSegment.start) {
if (otherSegment.end <= segmentToSkip.start) {
// Other segment does not overlap, and is before this skipped segment.
// This situation can only happen if a video is opened and adjusted to
// a later time in the video where earlier auto skip segments
// have not been encountered yet.
continue;
}
if (segmentToSkip.end <= otherSegment.start) {
break; // No other segments can be contained.
}
@@ -922,7 +966,8 @@ public class SegmentPlaybackController {
public static String appendTimeWithoutSegments(String totalTime) {
try {
if (Settings.SB_ENABLED.get() && Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get()
&& !TextUtils.isEmpty(totalTime) && !TextUtils.isEmpty(timeWithoutSegments)) {
&& !TextUtils.isEmpty(totalTime) && !TextUtils.isEmpty(timeWithoutSegments)
&& !isAdProgressTextVisible()) {
// Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages
return "\u202D" + totalTime + timeWithoutSegments; // u202D = left to right override
}
@@ -983,7 +1028,7 @@ public class SegmentPlaybackController {
@SuppressWarnings("unused")
public static void drawSponsorTimeBars(final Canvas canvas, final float posY) {
try {
if (segments == null) return;
if (segments == null || isAdProgressTextVisible()) return;
final long videoLength = VideoInformation.getVideoLength();
if (videoLength <= 0) return;

View File

@@ -52,6 +52,8 @@ public enum SegmentCategory {
sf("revanced_sb_skip_button_preview_beginning"), sf("revanced_sb_skip_button_preview_middle"), sf("revanced_sb_skip_button_preview_end"),
sf("revanced_sb_skipped_preview_beginning"), sf("revanced_sb_skipped_preview_middle"), sf("revanced_sb_skipped_preview_end"),
SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR, SB_CATEGORY_PREVIEW_OPACITY),
HOOK("hook", sf("revanced_sb_segments_hook"), sf("revanced_sb_segments_hook_sum"), sf("revanced_sb_skip_button_hook"), sf("revanced_sb_skipped_hook"),
SB_CATEGORY_HOOK, SB_CATEGORY_HOOK_COLOR, SB_CATEGORY_HOOK_OPACITY),
FILLER("filler", sf("revanced_sb_segments_filler"), sf("revanced_sb_segments_filler_sum"), sf("revanced_sb_skip_button_filler"), sf("revanced_sb_skipped_filler"),
SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR, SB_CATEGORY_FILLER_OPACITY),
MUSIC_OFFTOPIC("music_offtopic", sf("revanced_sb_segments_nomusic"), sf("revanced_sb_segments_nomusic_sum"), sf("revanced_sb_skip_button_nomusic"), sf("revanced_sb_skipped_nomusic"),
@@ -69,6 +71,7 @@ public enum SegmentCategory {
INTRO,
OUTRO,
PREVIEW,
HOOK,
FILLER,
MUSIC_OFFTOPIC,
};
@@ -81,6 +84,7 @@ public enum SegmentCategory {
INTRO,
OUTRO,
PREVIEW,
HOOK,
FILLER,
MUSIC_OFFTOPIC,
};

View File

@@ -6,6 +6,7 @@ import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.videoplayer.PlayerControlButton;
@SuppressWarnings("unused")
@@ -26,7 +27,7 @@ public class CreateSegmentButton {
controlsView,
"revanced_sb_create_segment_button",
null,
CreateSegmentButton::shouldBeShown,
CreateSegmentButton::isButtonEnabled,
v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility(),
null
);
@@ -56,7 +57,8 @@ public class CreateSegmentButton {
if (instance != null) instance.setVisibility(visible, animated);
}
private static boolean shouldBeShown() {
return Settings.SB_ENABLED.get() && Settings.SB_CREATE_NEW_SEGMENT.get();
private static boolean isButtonEnabled() {
return Settings.SB_ENABLED.get() && Settings.SB_CREATE_NEW_SEGMENT.get()
&& !SegmentPlaybackController.isAdProgressTextVisible();
}
}

View File

@@ -28,7 +28,7 @@ public class VotingButton {
controlsView,
"revanced_sb_voting_button",
null,
VotingButton::shouldBeShown,
VotingButton::isButtonEnabled,
v -> SponsorBlockUtils.onVotingClicked(v.getContext()),
null
);
@@ -58,8 +58,9 @@ public class VotingButton {
if (instance != null) instance.setVisibility(visible, animated);
}
private static boolean shouldBeShown() {
private static boolean isButtonEnabled() {
return Settings.SB_ENABLED.get() && Settings.SB_VOTING_BUTTON.get()
&& SegmentPlaybackController.videoHasSegments();
&& SegmentPlaybackController.videoHasSegments()
&& !SegmentPlaybackController.isAdProgressTextVisible();
}
}

View File

@@ -1,8 +1,11 @@
package app.revanced.patches.youtube.layout.sponsorblock
import app.revanced.patcher.fingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionReversed
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@@ -56,3 +59,20 @@ internal val rectangleFieldInvalidatorFingerprint = fingerprint {
reference?.parameterTypes?.size == 1 && reference.name == "invalidate" // the reference is the invalidate(..) method
}
}
internal val adProgressTextViewVisibilityFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
parameters("Z")
custom { method, _ ->
indexOfAdProgressTextViewVisibilityInstruction(method) >= 0
}
}
internal fun indexOfAdProgressTextViewVisibilityInstruction(method: Method) =
method.indexOfFirstInstructionReversed {
val reference = getReference<MethodReference>()
reference?.definingClass ==
"Lcom/google/android/libraries/youtube/ads/player/ui/AdProgressTextView;"
&& reference.name =="setVisibility"
}

View File

@@ -252,5 +252,16 @@ val sponsorBlockPatch = bytecodePatch(
}
} ?: throw PatchException("Could not find the method which contains the replaceMeWith* strings")
}
adProgressTextViewVisibilityFingerprint.method.apply {
val index = indexOfAdProgressTextViewVisibilityInstruction(this)
val register = getInstruction<FiveRegisterInstruction>(index).registerD
addInstructionsAtControlFlowLabel(
index,
"invoke-static { v$register }, $EXTENSION_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setAdProgressTextVisibility(I)V"
)
}
}
}

View File

@@ -1143,10 +1143,12 @@ Your user id is like a password and it should never be shared.
<string name="revanced_sb_segments_intro_sum">An interval without actual content. Could be a pause, static frame, or repeating animation. Does not include transitions containing information</string>
<string name="revanced_sb_segments_outro">Endcards / Credits</string>
<string name="revanced_sb_segments_outro_sum">Credits or when the YouTube endcards appear. Not for conclusions with information</string>
<string name="revanced_sb_segments_preview">Preview / Recap / Hook</string>
<string name="revanced_sb_segments_hook">Hook / Greeting</string>
<string name="revanced_sb_segments_hook_sum">Narrated trailers for the upcoming video, greetings and goodbyes. This should not skip conclusions with information</string>
<string name="revanced_sb_segments_preview">Preview / Recap</string>
<string name="revanced_sb_segments_preview_sum">Collection of clips that show what is coming up or what happened in the video or in other videos of a series, where all information is repeated elsewhere</string>
<string name="revanced_sb_segments_filler">Filler Tangent / Jokes</string>
<string name="revanced_sb_segments_filler_sum">Tangential scenes added only for filler or humor that are not required to understand the main content of the video. Does not include segments providing context or background details</string>
<string name="revanced_sb_segments_filler">Tangent / Jokes</string>
<string name="revanced_sb_segments_filler_sum">Tangential scenes or jokes that are not required to understand the main content of the video. This should not include segments providing context or background details</string>
<string name="revanced_sb_segments_nomusic">Music: Non-Music Section</string>
<string name="revanced_sb_segments_nomusic_sum">Only for use in music videos. Sections of music videos without music, that aren\'t already covered by another category</string>
<string name="revanced_sb_skip_button_compact">Skip</string>
@@ -1159,10 +1161,11 @@ Your user id is like a password and it should never be shared.
<string name="revanced_sb_skip_button_intro_middle">Skip intermission</string>
<string name="revanced_sb_skip_button_intro_end">Skip intermission</string>
<string name="revanced_sb_skip_button_outro">Skip outro</string>
<string name="revanced_sb_skip_button_hook">Skip hook</string>
<string name="revanced_sb_skip_button_preview_beginning">Skip preview</string>
<string name="revanced_sb_skip_button_preview_middle">Skip preview</string>
<string name="revanced_sb_skip_button_preview_end">Skip recap</string>
<string name="revanced_sb_skip_button_filler">Skip filler</string>
<string name="revanced_sb_skip_button_filler">Skip tangent</string>
<string name="revanced_sb_skip_button_nomusic">Skip non-music</string>
<string name="revanced_sb_skip_button_unsubmitted">Skip segment</string>
<string name="revanced_sb_skipped_sponsor">Skipped sponsor</string>
@@ -1173,10 +1176,11 @@ Your user id is like a password and it should never be shared.
<string name="revanced_sb_skipped_intro_middle">Skipped intermission</string>
<string name="revanced_sb_skipped_intro_end">Skipped intermission</string>
<string name="revanced_sb_skipped_outro">Skipped outro</string>
<string name="revanced_sb_skipped_hook">Skipped hook</string>
<string name="revanced_sb_skipped_preview_beginning">Skipped preview</string>
<string name="revanced_sb_skipped_preview_middle">Skipped preview</string>
<string name="revanced_sb_skipped_preview_end">Skipped recap</string>
<string name="revanced_sb_skipped_filler">Skipped filler</string>
<string name="revanced_sb_skipped_filler">Skipped tangent</string>
<string name="revanced_sb_skipped_nomusic">Skipped a non-music section</string>
<string name="revanced_sb_skipped_unsubmitted">Skipped unsubmitted segment</string>
<string name="revanced_sb_skipped_multiple_segments">Skipped multiple segments</string>