diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index ac24f2301..2c089bb69 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -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); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/SegmentPlaybackController.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/SegmentPlaybackController.java index c53c12460..570496a07 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/SegmentPlaybackController.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/SegmentPlaybackController.java @@ -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 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; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategory.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategory.java index ae6900330..fadc61d64 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategory.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategory.java @@ -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, }; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButton.java index 57d7caec8..99dbf7e18 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/CreateSegmentButton.java @@ -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(); } } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/VotingButton.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/VotingButton.java index 32af03272..2403b5a35 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/VotingButton.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/VotingButton.java @@ -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(); } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/Fingerprints.kt index 80eca0557..dbbd0c000 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/Fingerprints.kt @@ -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() + reference?.definingClass == + "Lcom/google/android/libraries/youtube/ads/player/ui/AdProgressTextView;" + && reference.name =="setVisibility" + } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt index 06c271b97..80cbfe041 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/sponsorblock/SponsorBlockPatch.kt @@ -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(index).registerD + + addInstructionsAtControlFlowLabel( + index, + "invoke-static { v$register }, $EXTENSION_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setAdProgressTextVisibility(I)V" + ) + } + } } diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index d63f43d55..a94feaa51 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -1143,10 +1143,12 @@ Your user id is like a password and it should never be shared. An interval without actual content. Could be a pause, static frame, or repeating animation. Does not include transitions containing information Endcards / Credits Credits or when the YouTube endcards appear. Not for conclusions with information - Preview / Recap / Hook + Hook / Greeting + Narrated trailers for the upcoming video, greetings and goodbyes. This should not skip conclusions with information + Preview / Recap 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 - Filler Tangent / Jokes - 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 + Tangent / Jokes + 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 Music: Non-Music Section Only for use in music videos. Sections of music videos without music, that aren\'t already covered by another category Skip @@ -1159,10 +1161,11 @@ Your user id is like a password and it should never be shared. Skip intermission Skip intermission Skip outro + Skip hook Skip preview Skip preview Skip recap - Skip filler + Skip tangent Skip non-music Skip segment Skipped sponsor @@ -1173,10 +1176,11 @@ Your user id is like a password and it should never be shared. Skipped intermission Skipped intermission Skipped outro + Skipped hook Skipped preview Skipped preview Skipped recap - Skipped filler + Skipped tangent Skipped a non-music section Skipped unsubmitted segment Skipped multiple segments