diff --git a/CHANGELOG.md b/CHANGELOG.md index ec9eb408..0502537e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +# [0.105.0-dev.2](https://github.com/revanced/revanced-integrations/compare/v0.105.0-dev.1...v0.105.0-dev.2) (2023-04-27) + + +### Bug Fixes + +* **youtube/minimized-playback:** disable minimized playback for shorts ([#371](https://github.com/revanced/revanced-integrations/issues/371)) ([df4b03f](https://github.com/revanced/revanced-integrations/commit/df4b03fed5a0622b18bf4a8dca1940d26a590d8f)) + +# [0.105.0-dev.1](https://github.com/revanced/revanced-integrations/compare/v0.104.1-dev.2...v0.105.0-dev.1) (2023-04-26) + + +### Features + +* **youtube/sponsorblock:** automatically hide skip button ([#365](https://github.com/revanced/revanced-integrations/issues/365)) ([75dad2f](https://github.com/revanced/revanced-integrations/commit/75dad2f3071c19aa097ebdc7bd83d1ce9afb78ea)) + +## [0.104.1-dev.2](https://github.com/revanced/revanced-integrations/compare/v0.104.1-dev.1...v0.104.1-dev.2) (2023-04-25) + + +### Bug Fixes + +* **youtube/spoof-signature-verification:** additional fixes for subtitle window positions ([#369](https://github.com/revanced/revanced-integrations/issues/369)) ([6f2ae31](https://github.com/revanced/revanced-integrations/commit/6f2ae313cf492166d64e5e33e759f2b234191b64)) + +## [0.104.1-dev.1](https://github.com/revanced/revanced-integrations/compare/v0.104.0...v0.104.1-dev.1) (2023-04-25) + + +### Bug Fixes + +* **youtube/return-youtube-dislike:** fix dislikes using wrong font if dark mode is enabled during video playback ([#368](https://github.com/revanced/revanced-integrations/issues/368)) ([3b37a3b](https://github.com/revanced/revanced-integrations/commit/3b37a3b41f7bfbc4a6d6d12e2deb2acd9bb2ccc8)) + # [0.104.0](https://github.com/revanced/revanced-integrations/compare/v0.103.0...v0.104.0) (2023-04-24) diff --git a/app/src/main/java/app/revanced/integrations/patches/CopyVideoUrlPatch.java b/app/src/main/java/app/revanced/integrations/patches/CopyVideoUrlPatch.java index a38ac8fd..023c2bd6 100644 --- a/app/src/main/java/app/revanced/integrations/patches/CopyVideoUrlPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/CopyVideoUrlPatch.java @@ -8,7 +8,7 @@ import app.revanced.integrations.utils.ReVancedUtils; public class CopyVideoUrlPatch { public static void copyUrl(Boolean withTimestamp) { try { - String url = String.format("https://youtu.be/%s", VideoInformation.getCurrentVideoId()); + String url = String.format("https://youtu.be/%s", VideoInformation.getVideoId()); if (withTimestamp) { long seconds = VideoInformation.getVideoTime() / 1000; url += String.format("?t=%s", seconds); diff --git a/app/src/main/java/app/revanced/integrations/patches/MinimizedPlaybackPatch.java b/app/src/main/java/app/revanced/integrations/patches/MinimizedPlaybackPatch.java index b4ec200f..e7f8af36 100644 --- a/app/src/main/java/app/revanced/integrations/patches/MinimizedPlaybackPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/MinimizedPlaybackPatch.java @@ -1,16 +1,17 @@ package app.revanced.integrations.patches; -import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.shared.PlayerType; public class MinimizedPlaybackPatch { - public static boolean isNotPlayingShorts(boolean isPipEnabled) { - return !PlayerType.getCurrent().isNoneOrHidden() && isPipEnabled; + public static boolean isPlaybackNotShort() { + return !PlayerType.getCurrent().isNoneOrHidden(); } - public static boolean isMinimizedPlaybackEnabled() { - return SettingsEnum.ENABLE_MINIMIZED_PLAYBACK.getBoolean(); + public static boolean overrideMinimizedPlaybackAvailable() { + // This could be done entirely in the patch, + // but having a unique method to search for makes manually inspecting the patched apk much easier. + return true; } } diff --git a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java index bbadf90d..690e3e48 100644 --- a/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/ReturnYouTubeDislikePatch.java @@ -56,7 +56,7 @@ public class ReturnYouTubeDislikePatch { return replacement; } } catch (Exception ex) { - LogHelper.printException(() -> "onComponentCreated AtomicReference failure", ex); + LogHelper.printException(() -> "onLithoTextLoaded failure", ex); } return original; } diff --git a/app/src/main/java/app/revanced/integrations/patches/SpoofSignatureVerificationPatch.java b/app/src/main/java/app/revanced/integrations/patches/SpoofSignatureVerificationPatch.java index 3b46d2be..16270f39 100644 --- a/app/src/main/java/app/revanced/integrations/patches/SpoofSignatureVerificationPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/SpoofSignatureVerificationPatch.java @@ -1,12 +1,15 @@ package app.revanced.integrations.patches; +import static app.revanced.integrations.utils.ReVancedUtils.containsAny; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.shared.PlayerType; import app.revanced.integrations.utils.LogHelper; import app.revanced.integrations.utils.ReVancedUtils; -import static app.revanced.integrations.utils.ReVancedUtils.containsAny; - public class SpoofSignatureVerificationPatch { /** * Protobuf parameters used for autoplay in scrim. @@ -28,6 +31,14 @@ public class SpoofSignatureVerificationPatch { "SAFg" // Autoplay in scrim }; + @Nullable + private static String currentVideoId; + + /** + * If any of the subtitles settings encountered from the current video have been non default values. + */ + private static boolean nonDefaultSubtitlesEncountered; + /** * Injection point. * @@ -49,7 +60,7 @@ public class SpoofSignatureVerificationPatch { if (isPlayingFeed) { // Videos in feed won't autoplay with sound. return PROTOBUF_PARAMETER_SCRIM + PROTOBUF_PARAMETER_SHORTS; - } else{ + } else { // Spoof the parameter to prevent playback issues. return PROTOBUF_PARAMETER_SHORTS; } @@ -108,7 +119,7 @@ public class SpoofSignatureVerificationPatch { final boolean signatureSpoofing = SettingsEnum.SIGNATURE_SPOOFING.getBoolean(); if (SettingsEnum.DEBUG.getBoolean()) { if (ap != lastAp || ah != lastAh || av != lastAv || vs != lastVs || sd != lastSd) { - LogHelper.printDebug(() -> "video: " + VideoInformation.getCurrentVideoId() + " spoof: " + signatureSpoofing + LogHelper.printDebug(() -> "video: " + VideoInformation.getVideoId() + " spoof: " + signatureSpoofing + " ap:" + ap + " ah:" + ah + " av:" + av + " vs:" + vs + " sd:" + sd); lastAp = ap; lastAh = ah; @@ -121,21 +132,40 @@ public class SpoofSignatureVerificationPatch { // Videos with custom captions that specify screen positions appear to always have correct screen positions (even with spoofing). // But for auto generated and most other captions, the spoof incorrectly gives various default Shorts caption settings. // Check for these known default shorts captions parameters, and replace with the known correct values. - if (signatureSpoofing && !PlayerType.getCurrent().isNoneOrHidden()) { // video is not a Short or Story + // + // If a regular video uses a custom subtitle setting that match a default short setting, + // then this will incorrectly replace the setting. + // But, if the video uses multiple subtitles in different screen locations, then detect the non-default values + // and do not replace any window settings for the video (regardless if they match a shorts default). + if (signatureSpoofing && !nonDefaultSubtitlesEncountered && !PlayerType.getCurrent().isNoneOrHidden()) { for (SubtitleWindowReplacementSettings setting : SubtitleWindowReplacementSettings.values()) { if (setting.match(ap, ah, av, vs, sd)) { return setting.replacementSetting(); } } - // Parameters are either subtitles with custom positions, or a set of unidentified (and incorrect) default parameters. - // The subtitles could be forced to the bottom no matter what, but that would override custom screen positions. - // For now, just return the original parameters. + // Settings appear to be custom subtitles. + nonDefaultSubtitlesEncountered = true; + LogHelper.printDebug(() -> "Non default subtitles found. Using existing settings without replacement."); } - // No matches, pass back the original values return new int[]{ap, ah, av}; } + /** + * Injection point. + */ + public static void setCurrentVideoId(@NonNull String videoId) { + try { + if (videoId.equals(currentVideoId)) { + return; + } + currentVideoId = videoId; + nonDefaultSubtitlesEncountered = false; + } catch (Exception ex) { + LogHelper.printException(() -> "setCurrentVideoId failure", ex); + } + } + /** * Known incorrect default Shorts subtitle parameters, and the corresponding correct (non-Shorts) values. @@ -152,8 +182,8 @@ public class SpoofSignatureVerificationPatch { final int ap, ah, av; final boolean vs, sd; - // replacement values - final int replacementAp, replacementAh, replacementAv; + // replacement int values + final int[] replacement; SubtitleWindowReplacementSettings(int ap, int ah, int av, boolean vs, boolean sd, int replacementAp, int replacementAh, int replacementAv) { @@ -162,9 +192,7 @@ public class SpoofSignatureVerificationPatch { this.av = av; this.vs = vs; this.sd = sd; - this.replacementAp = replacementAp; - this.replacementAh = replacementAh; - this.replacementAv = replacementAv; + this.replacement = new int[]{replacementAp, replacementAh, replacementAv}; } boolean match(int ap, int ah, int av, boolean vs, boolean sd) { @@ -172,7 +200,7 @@ public class SpoofSignatureVerificationPatch { } int[] replacementSetting() { - return new int[]{replacementAp, replacementAh, replacementAv}; + return replacement; } } } diff --git a/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java b/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java index ccdde90d..13149541 100644 --- a/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java +++ b/app/src/main/java/app/revanced/integrations/patches/VideoInformation.java @@ -42,7 +42,7 @@ public final class VideoInformation { try { seekMethod = thisRef.getClass().getMethod(SEEK_METHOD_NAME, Long.TYPE); seekMethod.setAccessible(true); - } catch (NoSuchMethodException ex) { + } catch (Exception ex) { LogHelper.printException(() -> "Failed to initialize", ex); } } @@ -141,26 +141,25 @@ public final class VideoInformation { * @return The id of the video. Empty string if not set yet. */ @NonNull - public static String getCurrentVideoId() { + public static String getVideoId() { return videoId; } /** * @return The current playback speed. */ - public static float getCurrentPlaybackSpeed() { + public static float getPlaybackSpeed() { return playbackSpeed; } /** - * Length of the current video playing. - * Includes Shorts playback. + * Length of the current video playing. Includes Shorts and YouTube Stories. * * @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, * then this returns zero. */ - public static long getCurrentVideoLength() { + public static long getVideoLength() { return videoLength; } @@ -172,7 +171,7 @@ public final class VideoInformation { * 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 playback. + * Includes Shorts and YouTube Stories. * * @return The time of the video in milliseconds. -1 if not set yet. */ diff --git a/app/src/main/java/app/revanced/integrations/patches/playback/speed/RememberPlaybackSpeedPatch.java b/app/src/main/java/app/revanced/integrations/patches/playback/speed/RememberPlaybackSpeedPatch.java index b3475944..4ccd457f 100644 --- a/app/src/main/java/app/revanced/integrations/patches/playback/speed/RememberPlaybackSpeedPatch.java +++ b/app/src/main/java/app/revanced/integrations/patches/playback/speed/RememberPlaybackSpeedPatch.java @@ -49,7 +49,7 @@ public final class RememberPlaybackSpeedPatch { * Overrides the video speed. Called after video loads, and immediately after user selects a different playback speed */ public static float getPlaybackSpeedOverride() { - return VideoInformation.getCurrentPlaybackSpeed(); + return VideoInformation.getPlaybackSpeed(); } /** diff --git a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java index b256964b..bc950f70 100644 --- a/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/returnyoutubedislike/ReturnYouTubeDislike.java @@ -15,6 +15,7 @@ import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; +import android.text.style.ForegroundColorSpan; import android.text.style.ImageSpan; import android.util.DisplayMetrics; import android.util.TypedValue; @@ -257,12 +258,11 @@ public class ReturnYouTubeDislike { try { synchronized (videoIdLockObject) { if (replacementLikeDislikeSpan != null) { - String oldSpannableString = oldSpannable.toString(); - if (replacementLikeDislikeSpan.toString().equals(oldSpannableString)) { + if (spansHaveEqualTextAndColor(replacementLikeDislikeSpan, oldSpannable)) { LogHelper.printDebug(() -> "Ignoring previously created dislikes span"); return null; } - if (originalDislikeSpan.toString().equals(oldSpannableString)) { + if (spansHaveEqualTextAndColor(Objects.requireNonNull(originalDislikeSpan), oldSpannable)) { LogHelper.printDebug(() -> "Replacing span with previously created dislike span"); return replacementLikeDislikeSpan; } @@ -470,6 +470,27 @@ public class ReturnYouTubeDislike { return false; } + private static boolean spansHaveEqualTextAndColor(@NonNull Spanned one, @NonNull Spanned two) { + // Cannot use equals on the span, because many of the inner styling spans do not implement equals. + // Instead, compare the underlying text and the text color to handle when dark mode is changed. + // Cannot compare the status of device dark mode, as Litho components are updated just before dark mode status changes. + if (!one.toString().equals(two.toString())) { + return false; + } + ForegroundColorSpan[] oneColors = one.getSpans(0, one.length(), ForegroundColorSpan.class); + ForegroundColorSpan[] twoColors = two.getSpans(0, two.length(), ForegroundColorSpan.class); + final int oneLength = oneColors.length; + if (oneLength != twoColors.length) { + return false; + } + for (int i = 0; i < oneLength; i++) { + if (oneColors[i].getForegroundColor() != twoColors[i].getForegroundColor()) { + return false; + } + } + return true; + } + private static SpannableString newSpannableWithDislikes(@NonNull Spanned sourceStyling, @NonNull RYDVoteData voteData) { return newSpanUsingStylingOfAnotherSpan(sourceStyling, SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.getBoolean() diff --git a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java index bd4f5a3d..7a72ea7b 100644 --- a/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java +++ b/app/src/main/java/app/revanced/integrations/settings/SettingsEnum.java @@ -111,7 +111,6 @@ public enum SettingsEnum { CAPTIONS_ENABLED("revanced_autocaptions_enabled", BOOLEAN, FALSE), DISABLE_ZOOM_HAPTICS("revanced_disable_zoom_haptics", BOOLEAN, TRUE), ENABLE_EXTERNAL_BROWSER("revanced_enable_external_browser", BOOLEAN, TRUE, true), - ENABLE_MINIMIZED_PLAYBACK("revanced_enable_minimized_playback", BOOLEAN, TRUE), PREFERRED_AUTO_REPEAT("revanced_pref_auto_repeat", BOOLEAN, FALSE), TAP_SEEKING_ENABLED("revanced_enable_tap_seeking", BOOLEAN, TRUE), USE_HDR_AUTO_BRIGHTNESS("revanced_pref_hdr_autobrightness", BOOLEAN, TRUE), @@ -147,7 +146,8 @@ public enum SettingsEnum { SB_ENABLED("sb-enabled", BOOLEAN, TRUE, SPONSOR_BLOCK), SB_VOTING_ENABLED("sb-voting-enabled", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)), SB_CREATE_NEW_SEGMENT_ENABLED("sb-new-segment-enabled", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)), - SB_USE_COMPACT_SKIPBUTTON("sb-use-compact-skip-button", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)), + SB_USE_COMPACT_SKIP_BUTTON("sb-use-compact-skip-button", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)), + SB_AUTO_HIDE_SKIP_BUTTON("sb-auto-hide-skip-segment-button", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)), SB_SHOW_TOAST_ON_SKIP("show-toast", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)), SB_TRACK_SKIP_COUNT("count-skips", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)), SB_UUID("uuid", STRING, "", SPONSOR_BLOCK), diff --git a/app/src/main/java/app/revanced/integrations/settingsmenu/SponsorBlockSettingsFragment.java b/app/src/main/java/app/revanced/integrations/settingsmenu/SponsorBlockSettingsFragment.java index 61bd92c0..9a9e0141 100644 --- a/app/src/main/java/app/revanced/integrations/settingsmenu/SponsorBlockSettingsFragment.java +++ b/app/src/main/java/app/revanced/integrations/settingsmenu/SponsorBlockSettingsFragment.java @@ -47,6 +47,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { private SwitchPreference addNewSegment; private SwitchPreference votingEnabled; private SwitchPreference compactSkipButton; + private SwitchPreference autoHideSkipSegmentButton; private SwitchPreference showSkipToast; private SwitchPreference trackSkips; private SwitchPreference showTimeWithoutSegments; @@ -79,9 +80,12 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { votingEnabled.setChecked(SettingsEnum.SB_VOTING_ENABLED.getBoolean()); votingEnabled.setEnabled(enabled); - compactSkipButton.setChecked(SettingsEnum.SB_USE_COMPACT_SKIPBUTTON.getBoolean()); + compactSkipButton.setChecked(SettingsEnum.SB_USE_COMPACT_SKIP_BUTTON.getBoolean()); compactSkipButton.setEnabled(enabled); + autoHideSkipSegmentButton.setChecked(SettingsEnum.SB_AUTO_HIDE_SKIP_BUTTON.getBoolean()); + autoHideSkipSegmentButton.setEnabled(enabled); + showSkipToast.setChecked(SettingsEnum.SB_SHOW_TOAST_ON_SKIP.getBoolean()); showSkipToast.setEnabled(enabled); @@ -91,10 +95,10 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { showTimeWithoutSegments.setChecked(SettingsEnum.SB_SHOW_TIME_WITHOUT_SEGMENTS.getBoolean()); showTimeWithoutSegments.setEnabled(enabled); - newSegmentStep.setText(String.valueOf(SettingsEnum.SB_ADJUST_NEW_SEGMENT_STEP.getInt())); + newSegmentStep.setText(SettingsEnum.SB_ADJUST_NEW_SEGMENT_STEP.getObjectValue().toString()); newSegmentStep.setEnabled(enabled); - minSegmentDuration.setText(String.valueOf(SettingsEnum.SB_MIN_DURATION.getFloat())); + minSegmentDuration.setText(SettingsEnum.SB_MIN_DURATION.getObjectValue().toString()); minSegmentDuration.setEnabled(enabled); privateUserId.setText(SettingsEnum.SB_UUID.getString()); @@ -132,57 +136,17 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { return true; }); - addNewSegment = new SwitchPreference(context); - addNewSegment.setTitle(str("sb_enable_create_segment")); - addNewSegment.setSummaryOn(str("sb_enable_create_segment_sum_on")); - addNewSegment.setSummaryOff(str("sb_enable_create_segment_sum_off")); - preferenceScreen.addPreference(addNewSegment); - addNewSegment.setOnPreferenceChangeListener((preference1, o) -> { - Boolean newValue = (Boolean) o; - if (newValue && !SettingsEnum.SB_SEEN_GUIDELINES.getBoolean()) { - new AlertDialog.Builder(preference1.getContext()) - .setTitle(str("sb_guidelines_popup_title")) - .setMessage(str("sb_guidelines_popup_content")) - .setNegativeButton(str("sb_guidelines_popup_already_read"), null) - .setPositiveButton(str("sb_guidelines_popup_open"), (dialogInterface, i) -> openGuidelines()) - .setOnDismissListener(dialog -> SettingsEnum.SB_SEEN_GUIDELINES.saveValue(true)) - .setCancelable(false) - .show(); - } - SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.saveValue(newValue); - updateUI(); - return true; - }); - - votingEnabled = new SwitchPreference(context); - votingEnabled.setTitle(str("sb_enable_voting")); - votingEnabled.setSummaryOn(str("sb_enable_voting_sum_on")); - votingEnabled.setSummaryOff(str("sb_enable_voting_sum_off")); - preferenceScreen.addPreference(votingEnabled); - votingEnabled.setOnPreferenceChangeListener((preference1, newValue) -> { - SettingsEnum.SB_VOTING_ENABLED.saveValue(newValue); - updateUI(); - return true; - }); - - compactSkipButton = new SwitchPreference(context); - compactSkipButton.setTitle(str("sb_enable_compact_skip_button")); - compactSkipButton.setSummaryOn(str("sb_enable_compact_skip_button_sum_on")); - compactSkipButton.setSummaryOff(str("sb_enable_compact_skip_button_sum_off")); - preferenceScreen.addPreference(compactSkipButton); - compactSkipButton.setOnPreferenceChangeListener((preference1, newValue) -> { - SettingsEnum.SB_USE_COMPACT_SKIPBUTTON.saveValue(newValue); - updateUI(); - return true; - }); - - addGeneralCategory(context, preferenceScreen); + addAppearanceCategory(context, preferenceScreen); segmentCategory = new PreferenceCategory(context); segmentCategory.setTitle(str("sb_diff_segments")); preferenceScreen.addPreference(segmentCategory); updateSegmentCategories(); + addCreateSegmentCategory(context, preferenceScreen); + + addGeneralCategory(context, preferenceScreen); + statsCategory = new PreferenceCategory(context); statsCategory.setTitle(str("sb_stats")); preferenceScreen.addPreference(statsCategory); @@ -196,20 +160,43 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { } } - private void addGeneralCategory(final Context context, PreferenceScreen screen) { - final PreferenceCategory category = new PreferenceCategory(context); + private void addAppearanceCategory(Context context, PreferenceScreen screen) { + PreferenceCategory category = new PreferenceCategory(context); screen.addPreference(category); - category.setTitle(str("sb_general")); + category.setTitle(str("sb_appearance_category")); - Preference guidelinePreferences = new Preference(context); - guidelinePreferences.setTitle(str("sb_guidelines_preference_title")); - guidelinePreferences.setSummary(str("sb_guidelines_preference_sum")); - guidelinePreferences.setOnPreferenceClickListener(preference1 -> { - openGuidelines(); + votingEnabled = new SwitchPreference(context); + votingEnabled.setTitle(str("sb_enable_voting")); + votingEnabled.setSummaryOn(str("sb_enable_voting_sum_on")); + votingEnabled.setSummaryOff(str("sb_enable_voting_sum_off")); + category.addPreference(votingEnabled); + votingEnabled.setOnPreferenceChangeListener((preference1, newValue) -> { + SettingsEnum.SB_VOTING_ENABLED.saveValue(newValue); + updateUI(); return true; }); - category.addPreference(guidelinePreferences); + compactSkipButton = new SwitchPreference(context); + compactSkipButton.setTitle(str("sb_enable_compact_skip_button")); + compactSkipButton.setSummaryOn(str("sb_enable_compact_skip_button_sum_on")); + compactSkipButton.setSummaryOff(str("sb_enable_compact_skip_button_sum_off")); + category.addPreference(compactSkipButton); + compactSkipButton.setOnPreferenceChangeListener((preference1, newValue) -> { + SettingsEnum.SB_USE_COMPACT_SKIP_BUTTON.saveValue(newValue); + updateUI(); + return true; + }); + + autoHideSkipSegmentButton = new SwitchPreference(context); + autoHideSkipSegmentButton.setTitle(str("sb_enable_auto_hide_skip_segment_button")); + autoHideSkipSegmentButton.setSummaryOn(str("sb_enable_auto_hide_skip_segment_button_sum_on")); + autoHideSkipSegmentButton.setSummaryOff(str("sb_enable_auto_hide_skip_segment_button_sum_off")); + category.addPreference(autoHideSkipSegmentButton); + autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> { + SettingsEnum.SB_AUTO_HIDE_SKIP_BUTTON.saveValue(newValue); + updateUI(); + return true; + }); showSkipToast = new SwitchPreference(context); showSkipToast.setTitle(str("sb_general_skiptoast")); @@ -226,19 +213,6 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { }); category.addPreference(showSkipToast); - - trackSkips = new SwitchPreference(context); - trackSkips.setTitle(str("sb_general_skipcount")); - trackSkips.setSummaryOn(str("sb_general_skipcount_sum_on")); - trackSkips.setSummaryOff(str("sb_general_skipcount_sum_off")); - trackSkips.setOnPreferenceChangeListener((preference1, newValue) -> { - SettingsEnum.SB_TRACK_SKIP_COUNT.saveValue(newValue); - updateUI(); - return true; - }); - category.addPreference(trackSkips); - - showTimeWithoutSegments = new SwitchPreference(context); showTimeWithoutSegments.setTitle(str("sb_general_time_without")); showTimeWithoutSegments.setSummaryOn(str("sb_general_time_without_sum_on")); @@ -249,7 +223,34 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { return true; }); category.addPreference(showTimeWithoutSegments); + } + private void addCreateSegmentCategory(Context context, PreferenceScreen screen) { + PreferenceCategory category = new PreferenceCategory(context); + screen.addPreference(category); + category.setTitle(str("sb_create_segment_category")); + + addNewSegment = new SwitchPreference(context); + addNewSegment.setTitle(str("sb_enable_create_segment")); + addNewSegment.setSummaryOn(str("sb_enable_create_segment_sum_on")); + addNewSegment.setSummaryOff(str("sb_enable_create_segment_sum_off")); + category.addPreference(addNewSegment); + addNewSegment.setOnPreferenceChangeListener((preference1, o) -> { + Boolean newValue = (Boolean) o; + if (newValue && !SettingsEnum.SB_SEEN_GUIDELINES.getBoolean()) { + new AlertDialog.Builder(preference1.getContext()) + .setTitle(str("sb_guidelines_popup_title")) + .setMessage(str("sb_guidelines_popup_content")) + .setNegativeButton(str("sb_guidelines_popup_already_read"), null) + .setPositiveButton(str("sb_guidelines_popup_open"), (dialogInterface, i) -> openGuidelines()) + .setOnDismissListener(dialog -> SettingsEnum.SB_SEEN_GUIDELINES.saveValue(true)) + .setCancelable(false) + .show(); + } + SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.saveValue(newValue); + updateUI(); + return true; + }); newSegmentStep = new EditTextPreference(context); newSegmentStep.setTitle(str("sb_general_adjusting")); @@ -266,6 +267,31 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { }); category.addPreference(newSegmentStep); + Preference guidelinePreferences = new Preference(context); + guidelinePreferences.setTitle(str("sb_guidelines_preference_title")); + guidelinePreferences.setSummary(str("sb_guidelines_preference_sum")); + guidelinePreferences.setOnPreferenceClickListener(preference1 -> { + openGuidelines(); + return true; + }); + category.addPreference(guidelinePreferences); + } + + private void addGeneralCategory(final Context context, PreferenceScreen screen) { + PreferenceCategory category = new PreferenceCategory(context); + screen.addPreference(category); + category.setTitle(str("sb_general")); + + trackSkips = new SwitchPreference(context); + trackSkips.setTitle(str("sb_general_skipcount")); + trackSkips.setSummaryOn(str("sb_general_skipcount_sum_on")); + trackSkips.setSummaryOff(str("sb_general_skipcount_sum_off")); + trackSkips.setOnPreferenceChangeListener((preference1, newValue) -> { + SettingsEnum.SB_TRACK_SKIP_COUNT.saveValue(newValue); + updateUI(); + return true; + }); + category.addPreference(trackSkips); minSegmentDuration = new EditTextPreference(context); minSegmentDuration.setTitle(str("sb_general_min_duration")); @@ -277,7 +303,6 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { }); category.addPreference(minSegmentDuration); - privateUserId = new EditTextPreference(context); privateUserId.setTitle(str("sb_general_uuid")); privateUserId.setSummary(str("sb_general_uuid_sum")); @@ -293,7 +318,6 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { }); category.addPreference(privateUserId); - apiUrl = new Preference(context); apiUrl.setTitle(str("sb_general_api_url")); apiUrl.setSummary(Html.fromHtml(str("sb_general_api_url_sum"))); @@ -327,7 +351,6 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { }); category.addPreference(apiUrl); - importExport = new EditTextPreference(context); importExport.setTitle(str("sb_settings_ie")); importExport.setSummary(str("sb_settings_ie_sum")); @@ -365,7 +388,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { { Preference preference = new Preference(context); - screen.addPreference(preference); + category.addPreference(preference); preference.setTitle(str("sb_about_api")); preference.setSummary(str("sb_about_api_sum")); preference.setOnPreferenceClickListener(preference1 -> { @@ -378,7 +401,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment { { Preference preference = new Preference(context); - screen.addPreference(preference); + category.addPreference(preference); preference.setSummary(str("sb_about_made_by")); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { preference.setSingleLineTitle(false); diff --git a/app/src/main/java/app/revanced/integrations/sponsorblock/SegmentPlaybackController.java b/app/src/main/java/app/revanced/integrations/sponsorblock/SegmentPlaybackController.java index 60efccf1..918e657e 100644 --- a/app/src/main/java/app/revanced/integrations/sponsorblock/SegmentPlaybackController.java +++ b/app/src/main/java/app/revanced/integrations/sponsorblock/SegmentPlaybackController.java @@ -11,7 +11,10 @@ import androidx.annotation.NonNull; 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.Objects; import app.revanced.integrations.patches.VideoInformation; @@ -32,11 +35,12 @@ import app.revanced.integrations.utils.ReVancedUtils; */ public class SegmentPlaybackController { /** - * Length of time to show a highlight segment manual skip. - * Because there is no scheduled hide of a skip to highlight, - * effectively this time value is rounded up to the next second. + * Length of time to show a skip button for a highlight segment, + * or a regular segment if {@link SettingsEnum#SB_AUTO_HIDE_SKIP_BUTTON} is enabled. + * + * Because Effectively, this value is rounded up to the next second. */ - private static final long HIGHLIGHT_SEGMENT_DURATION_TO_SHOW_SKIP_PROMPT = 3800; + private static final long DURATION_TO_SHOW_SKIP_BUTTON = 3800; /* * Highlight segments have zero length, as they are a point in time. @@ -48,7 +52,7 @@ public class SegmentPlaybackController { @Nullable private static String currentVideoId; @Nullable - private static SponsorSegment[] segmentsOfCurrentVideo; + private static SponsorSegment[] segments; /** * Highlight segment, if one exists. @@ -63,12 +67,12 @@ public class SegmentPlaybackController { private static long highlightSegmentInitialShowEndTime; /** - * Current (non-highlight) segment that user can manually skip. + * Currently playing (non-highlight) segment that user can manually skip. */ @Nullable private static SponsorSegment segmentCurrentlyPlaying; /** - * Currently playing manual skip segment, that is scheduled to hide. + * Currently playing manual skip segment that is scheduled to hide. * This will always be NULL or equal to {@link #segmentCurrentlyPlaying}. */ @Nullable @@ -79,6 +83,22 @@ public class SegmentPlaybackController { @Nullable private static SponsorSegment scheduledUpcomingSegment; + /** + * Used to prevent re-showing a previously hidden skip button when exiting an embedded segment. + * Only used when {@link SettingsEnum#SB_AUTO_HIDE_SKIP_BUTTON} is enabled. + * + * A collection of segments that have automatically hidden the skip button for, and all segments in this list + * contain the current video time. Segment are removed when playback exits the segment. + */ + private static final List hiddenSkipSegmentsForCurrentVideoTime = new ArrayList<>(); + + /** + * System time (in milliseconds) of when to hide the skip button of {@link #segmentCurrentlyPlaying}. + * Value is zero if playback is not inside a segment ({@link #segmentCurrentlyPlaying} is null), + * or if {@link SettingsEnum#SB_AUTO_HIDE_SKIP_BUTTON} is not enabled. + */ + private static long skipSegmentButtonEndTime; + @Nullable private static String timeWithoutSegments; @@ -87,16 +107,16 @@ public class SegmentPlaybackController { private static float sponsorBarThickness = 2f; @Nullable - public static SponsorSegment[] getSegmentsOfCurrentVideo() { - return segmentsOfCurrentVideo; + static SponsorSegment[] getSegments() { + return segments; } - static void setSegmentsOfCurrentVideo(@NonNull SponsorSegment[] segments) { - Arrays.sort(segments); - segmentsOfCurrentVideo = segments; + private static void setSegments(@NonNull SponsorSegment[] videoSegments) { + Arrays.sort(videoSegments); + segments = videoSegments; calculateTimeWithoutSegments(); - for (SponsorSegment segment : segments) { + for (SponsorSegment segment : videoSegments) { if (segment.category == SegmentCategory.HIGHLIGHT) { highlightSegment = segment; return; @@ -105,8 +125,34 @@ public class SegmentPlaybackController { highlightSegment = null; } - public static boolean currentVideoHasSegments() { - return segmentsOfCurrentVideo != null && segmentsOfCurrentVideo.length > 0; + static void addUnsubmittedSegment(@NonNull SponsorSegment segment) { + Objects.requireNonNull(segment); + if (segments == null) { + segments = new SponsorSegment[1]; + } else { + segments = Arrays.copyOf(segments, segments.length + 1); + } + segments[segments.length - 1] = segment; + setSegments(segments); + } + + static void removeUnsubmittedSegments() { + if (segments == null || segments.length == 0) { + return; + } + List replacement = new ArrayList<>(); + for (SponsorSegment segment : segments) { + if (segment.category != SegmentCategory.UNSUBMITTED) { + replacement.add(segment); + } + } + if (replacement.size() != segments.length) { + setSegments(replacement.toArray(new SponsorSegment[0])); + } + } + + public static boolean videoHasSegments() { + return segments != null && segments.length > 0; } /** @@ -114,15 +160,17 @@ public class SegmentPlaybackController { */ private static void clearData() { currentVideoId = null; - segmentsOfCurrentVideo = null; + segments = null; highlightSegment = null; highlightSegmentInitialShowEndTime = 0; timeWithoutSegments = null; segmentCurrentlyPlaying = null; - scheduledUpcomingSegment = null; // prevent any existing scheduled skip from running + scheduledUpcomingSegment = null; scheduledHideSegment = null; - toastSegmentSkipped = null; // prevent any scheduled skip toasts from showing + skipSegmentButtonEndTime = 0; + toastSegmentSkipped = null; toastNumberOfSegmentsSkipped = 0; + hiddenSkipSegmentsForCurrentVideoTime.clear(); } /** @@ -166,11 +214,9 @@ public class SegmentPlaybackController { currentVideoId = videoId; LogHelper.printDebug(() -> "setCurrentVideoId: " + videoId); - //noinspection UnnecessaryLocalVariable - String videoIdToDownload = videoId; // make a copy, to use off main thread ReVancedUtils.runOnBackgroundThread(() -> { try { - executeDownloadSegments(videoIdToDownload); + executeDownloadSegments(videoId); } catch (Exception e) { LogHelper.printException(() -> "Failed to download segments", e); } @@ -189,12 +235,12 @@ public class SegmentPlaybackController { SponsorSegment[] segments = SBRequester.getSegments(videoId); ReVancedUtils.runOnMainThread(()-> { - if (!videoId.equals(currentVideoId)) { + if (!videoId.equals(SegmentPlaybackController.currentVideoId)) { // user changed videos before get segments network call could complete LogHelper.printDebug(() -> "Ignoring segments for prior video: " + videoId); return; } - setSegmentsOfCurrentVideo(segments); + setSegments(segments); final long videoTime = VideoInformation.getVideoTime(); // if the current video time is before the highlight @@ -203,8 +249,7 @@ public class SegmentPlaybackController { skipSegment(highlightSegment, false); return; } - highlightSegmentInitialShowEndTime = System.currentTimeMillis() - + HIGHLIGHT_SEGMENT_DURATION_TO_SHOW_SKIP_PROMPT; + highlightSegmentInitialShowEndTime = System.currentTimeMillis() + DURATION_TO_SHOW_SKIP_BUTTON; } // check for any skips now, instead of waiting for the next update to setVideoTime() setVideoTime(videoTime); @@ -223,21 +268,23 @@ public class SegmentPlaybackController { try { if (!SettingsEnum.SB_ENABLED.getBoolean() || PlayerType.getCurrent().isNoneOrHidden() // shorts playback - || segmentsOfCurrentVideo == null || segmentsOfCurrentVideo.length == 0) { + || segments == null || segments.length == 0) { return; } LogHelper.printDebug(() -> "setVideoTime: " + 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.getCurrentPlaybackSpeed(); + final float playbackSpeed = VideoInformation.getPlaybackSpeed(); final long startTimerLookAheadThreshold = millis + (long)(playbackSpeed * lookAheadMilliseconds); - SponsorSegment foundCurrentSegment = null; + SponsorSegment foundSegmentCurrentlyPlaying = null; SponsorSegment foundUpcomingSegment = null; - for (final SponsorSegment segment : segmentsOfCurrentVideo) { + for (final SponsorSegment segment : segments) { if (segment.category.behaviour == CategoryBehaviour.SHOW_IN_SEEKBAR || segment.category.behaviour == CategoryBehaviour.IGNORE || segment.category == SegmentCategory.HIGHLIGHT) { @@ -255,14 +302,14 @@ public class SegmentPlaybackController { } // first found segment, or it's an embedded segment and fully inside the outer segment - if (foundCurrentSegment == null || foundCurrentSegment.containsSegment(segment)) { + if (foundSegmentCurrentlyPlaying == null || foundSegmentCurrentlyPlaying.containsSegment(segment)) { // If the found segment is not currently displayed, then do not show if the segment is nearly over. // This check prevents the skip button text from rapidly changing when multiple segments end at nearly the same time. - // Also prevents showing the skip button if user seeks into the last half second of the segment. - final long minMillisOfSegmentRemainingThreshold = 500; + // Also prevents showing the skip button if user seeks into the last 800ms of the segment. + final long minMillisOfSegmentRemainingThreshold = 800; if (segmentCurrentlyPlaying == segment || !segment.endIsNear(millis, minMillisOfSegmentRemainingThreshold)) { - foundCurrentSegment = segment; + foundSegmentCurrentlyPlaying = segment; } else { LogHelper.printDebug(() -> "Ignoring segment that ends very soon: " + segment); } @@ -284,15 +331,16 @@ public class SegmentPlaybackController { // upcoming manual skip // do not schedule upcoming segment, if it is not fully contained inside the current segment - if ((foundCurrentSegment == null || foundCurrentSegment.containsSegment(segment)) + if ((foundSegmentCurrentlyPlaying == null || foundSegmentCurrentlyPlaying.containsSegment(segment)) // use the most inner upcoming segment && (foundUpcomingSegment == null || foundUpcomingSegment.containsSegment(segment))) { // Only schedule, if the segment start time is not near the end time of the current segment. // This check is needed to prevent scheduled hide and show from clashing with each other. + // Instead the upcoming segment will be handled when the current segment scheduled hide calls back into this method. final long minTimeBetweenStartEndOfSegments = 1000; - if (foundCurrentSegment == null - || !foundCurrentSegment.endIsNear(segment.start, minTimeBetweenStartEndOfSegments)) { + if (foundSegmentCurrentlyPlaying == null + || !foundSegmentCurrentlyPlaying.endIsNear(segment.start, minTimeBetweenStartEndOfSegments)) { foundUpcomingSegment = segment; } else { LogHelper.printDebug(() -> "Not scheduling segment (start time is near end of current segment): " + segment); @@ -300,23 +348,22 @@ public class SegmentPlaybackController { } } - if (highlightSegment != null && (millis < HIGHLIGHT_SEGMENT_DURATION_TO_SHOW_SKIP_PROMPT - || System.currentTimeMillis() < highlightSegmentInitialShowEndTime)) { - SponsorBlockViewController.showSkipHighlightButton(highlightSegment); - } else { - SponsorBlockViewController.hideSkipHighlightButton(); + if (highlightSegment != null) { + if (millis < DURATION_TO_SHOW_SKIP_BUTTON || System.currentTimeMillis() < highlightSegmentInitialShowEndTime) { + SponsorBlockViewController.showSkipHighlightButton(highlightSegment); + } else { + SponsorBlockViewController.hideSkipHighlightButton(); + } } - if (segmentCurrentlyPlaying != foundCurrentSegment) { - if (foundCurrentSegment == null) { - LogHelper.printDebug(() -> "Hiding segment: " + segmentCurrentlyPlaying); - segmentCurrentlyPlaying = null; - SponsorBlockViewController.hideSkipSegmentButton(); - } else { - segmentCurrentlyPlaying = foundCurrentSegment; - LogHelper.printDebug(() -> "Showing segment: " + segmentCurrentlyPlaying); - SponsorBlockViewController.showSkipSegmentButton(foundCurrentSegment); - } + if (segmentCurrentlyPlaying != foundSegmentCurrentlyPlaying) { + setSegmentCurrentlyPlaying(foundSegmentCurrentlyPlaying); + } else if (foundSegmentCurrentlyPlaying != null + && skipSegmentButtonEndTime != 0 && skipSegmentButtonEndTime <= System.currentTimeMillis()) { + LogHelper.printDebug(() -> "Auto hiding skip button for segment: " + segmentCurrentlyPlaying); + skipSegmentButtonEndTime = 0; + hiddenSkipSegmentsForCurrentVideoTime.add(foundSegmentCurrentlyPlaying); + SponsorBlockViewController.hideSkipSegmentButton(); } // must be greater than the average time between updates to VideoInformation time @@ -324,8 +371,8 @@ public class SegmentPlaybackController { // schedule a hide, only if the segment end is near final SponsorSegment segmentToHide = - (foundCurrentSegment != null && foundCurrentSegment.endIsNear(millis, lookAheadMilliseconds)) - ? foundCurrentSegment + (foundSegmentCurrentlyPlaying != null && foundSegmentCurrentlyPlaying.endIsNear(millis, lookAheadMilliseconds)) + ? foundSegmentCurrentlyPlaying : null; if (scheduledHideSegment != segmentToHide) { @@ -355,8 +402,7 @@ public class SegmentPlaybackController { // Instead call back into setVideoTime to check everything again. // Should not use VideoInformation time as it is less accurate, // but this scheduled handler was scheduled precisely so we can just use the segment end time - segmentCurrentlyPlaying = null; - SponsorBlockViewController.hideSkipSegmentButton(); + setSegmentCurrentlyPlaying(null); setVideoTime(segmentToHide.end); }, delayUntilHide); } @@ -392,8 +438,7 @@ public class SegmentPlaybackController { skipSegment(segmentToSkip, false); } else { LogHelper.printDebug(() -> "Running scheduled show segment: " + segmentToSkip); - segmentCurrentlyPlaying = segmentToSkip; - SponsorBlockViewController.showSkipSegmentButton(segmentToSkip); + setSegmentCurrentlyPlaying(segmentToSkip); } }, delayUntilSkip); } @@ -403,12 +448,51 @@ 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)) { + LogHelper.printDebug(() -> "Resetting hide skip button: " + hiddenSegment); + i.remove(); + } + } + } + + private static void setSegmentCurrentlyPlaying(@Nullable SponsorSegment segment) { + if (segment == null) { + if (segmentCurrentlyPlaying != null) LogHelper.printDebug(() -> "Hiding segment: " + segmentCurrentlyPlaying); + segmentCurrentlyPlaying = null; + skipSegmentButtonEndTime = 0; + SponsorBlockViewController.hideSkipSegmentButton(); + return; + } + segmentCurrentlyPlaying = segment; + skipSegmentButtonEndTime = 0; + if (SettingsEnum.SB_AUTO_HIDE_SKIP_BUTTON.getBoolean()) { + if (hiddenSkipSegmentsForCurrentVideoTime.contains(segment)) { + // Playback exited a nested segment and the outer segment skip button was previously hidden. + LogHelper.printDebug(() -> "Ignoring previously auto-hidden segment: " + segment); + SponsorBlockViewController.hideSkipSegmentButton(); + return; + } + skipSegmentButtonEndTime = System.currentTimeMillis() + DURATION_TO_SHOW_SKIP_BUTTON; + } + LogHelper.printDebug(() -> "Showing segment: " + segment); + SponsorBlockViewController.showSkipSegmentButton(segment); + } private static SponsorSegment lastSegmentSkipped; private static long lastSegmentSkippedTime; private static void skipSegment(@NonNull SponsorSegment segmentToSkip, boolean userManuallySkipped) { try { + SponsorBlockViewController.hideSkipHighlightButton(); + SponsorBlockViewController.hideSkipSegmentButton(); + // If trying to seek to end of the video, YouTube can seek just short of the actual end. // (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. @@ -423,15 +507,14 @@ public class SegmentPlaybackController { LogHelper.printDebug(() -> "Skipping segment: " + segmentToSkip); lastSegmentSkipped = segmentToSkip; lastSegmentSkippedTime = now; - segmentCurrentlyPlaying = null; + setSegmentCurrentlyPlaying(null); scheduledHideSegment = null; scheduledUpcomingSegment = null; if (segmentToSkip == highlightSegment) { highlightSegmentInitialShowEndTime = 0; } - SponsorBlockViewController.hideSkipHighlightButton(); - SponsorBlockViewController.hideSkipSegmentButton(); + // If the seek is successful, then the seek causes a recursive call back into this class. final boolean seekSuccessful = VideoInformation.seekTo(segmentToSkip.end); if (!seekSuccessful) { // can happen when switching videos and is normal @@ -442,12 +525,13 @@ public class SegmentPlaybackController { if (!userManuallySkipped) { // check for any smaller embedded segments, and count those as autoskipped final boolean showSkipToast = SettingsEnum.SB_SHOW_TOAST_ON_SKIP.getBoolean(); - for (final SponsorSegment otherSegment : segmentsOfCurrentVideo) { + for (final SponsorSegment otherSegment : segments) { if (segmentToSkip.end < otherSegment.start) { break; // no other segments can be contained } - if (segmentToSkip.containsSegment(otherSegment)) { // includes checking the segment against itself - otherSegment.didAutoSkipped = true; // skipped this segment as well + if (otherSegment == segmentToSkip || + (otherSegment.category != SegmentCategory.HIGHLIGHT && segmentToSkip.containsSegment(otherSegment))) { + otherSegment.didAutoSkipped = true; if (showSkipToast) { showSkippedSegmentToast(otherSegment); } @@ -456,16 +540,8 @@ public class SegmentPlaybackController { } if (segmentToSkip.category == SegmentCategory.UNSUBMITTED) { - // skipped segment was a preview of unsubmitted segment - // remove the segment from the UI view + removeUnsubmittedSegments(); SponsorBlockUtils.setNewSponsorSegmentPreviewed(); - SponsorSegment[] newSegments = new SponsorSegment[segmentsOfCurrentVideo.length - 1]; - int i = 0; - for (SponsorSegment segment : segmentsOfCurrentVideo) { - if (segment != segmentToSkip) - newSegments[i++] = segment; - } - setSegmentsOfCurrentVideo(newSegments); } else { SponsorBlockUtils.sendViewRequestAsync(segmentToSkip); } @@ -578,13 +654,9 @@ public class SegmentPlaybackController { } public static void setSponsorBarThickness(final float thickness) { - try { - if (sponsorBarThickness != thickness) { - LogHelper.printDebug(() -> String.format("setSponsorBarThickness: %.2f", thickness)); - sponsorBarThickness = thickness; - } - } catch (Exception ex) { - LogHelper.printException(() -> "setSponsorBarThickness failure", ex); + if (sponsorBarThickness != thickness) { + LogHelper.printDebug(() -> String.format("setSponsorBarThickness: %.2f", thickness)); + sponsorBarThickness = thickness; } } @@ -606,17 +678,39 @@ public class SegmentPlaybackController { } private static void calculateTimeWithoutSegments() { - final long currentVideoLength = VideoInformation.getCurrentVideoLength(); + final long currentVideoLength = VideoInformation.getVideoLength(); if (!SettingsEnum.SB_SHOW_TIME_WITHOUT_SEGMENTS.getBoolean() || currentVideoLength <= 0 - || segmentsOfCurrentVideo == null || segmentsOfCurrentVideo.length == 0) { + || segments == null || segments.length == 0) { timeWithoutSegments = null; return; } - long timeWithoutSegmentsValue = currentVideoLength + 500; // YouTube:tm: - for (SponsorSegment segment : segmentsOfCurrentVideo) { - timeWithoutSegmentsValue -= segment.length(); + boolean foundNonhighlightSegments = false; + long timeWithoutSegmentsValue = currentVideoLength; + + for (int i = 0, length = segments.length; i < length; i++) { + SponsorSegment segment = segments[i]; + if (segment.category == SegmentCategory.HIGHLIGHT) { + continue; + } + foundNonhighlightSegments = true; + long start = segment.start; + final long end = segment.end; + // To prevent nested segments from incorrectly counting additional time, + // check if the segment overlaps any earlier segments. + for (int j = 0; j < i; j++) { + start = Math.max(start, segments[j].end); + } + if (start < end) { + timeWithoutSegmentsValue -= (end - start); + } } + + if (!foundNonhighlightSegments) { + timeWithoutSegments = null; + return; + } + final long hours = timeWithoutSegmentsValue / 3600000; final long minutes = (timeWithoutSegmentsValue / 60000) % 60; final long seconds = (timeWithoutSegmentsValue / 1000) % 60; @@ -643,9 +737,9 @@ public class SegmentPlaybackController { public static void drawSponsorTimeBars(final Canvas canvas, final float posY) { try { if (sponsorBarThickness < 0.1) return; - if (segmentsOfCurrentVideo == null) return; - final long currentVideoLength = VideoInformation.getCurrentVideoLength(); - if (currentVideoLength <= 0) return; + if (segments == null) return; + final long videoLength = VideoInformation.getVideoLength(); + if (videoLength <= 0) return; final float thicknessDiv2 = sponsorBarThickness / 2; final float top = posY - thicknessDiv2; @@ -653,8 +747,8 @@ public class SegmentPlaybackController { final float absoluteLeft = sponsorBarLeft; final float absoluteRight = sponsorBarRight; - final float tmp1 = (1f / currentVideoLength) * (absoluteRight - absoluteLeft); - for (SponsorSegment segment : segmentsOfCurrentVideo) { + final float tmp1 = (1f / videoLength) * (absoluteRight - absoluteLeft); + for (SponsorSegment segment : segments) { final float left = segment.start * tmp1 + absoluteLeft; final float right; if (segment.category == SegmentCategory.HIGHLIGHT) { diff --git a/app/src/main/java/app/revanced/integrations/sponsorblock/SponsorBlockUtils.java b/app/src/main/java/app/revanced/integrations/sponsorblock/SponsorBlockUtils.java index cea3360f..6f411586 100644 --- a/app/src/main/java/app/revanced/integrations/sponsorblock/SponsorBlockUtils.java +++ b/app/src/main/java/app/revanced/integrations/sponsorblock/SponsorBlockUtils.java @@ -15,7 +15,6 @@ import java.lang.ref.WeakReference; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Duration; -import java.util.Arrays; import java.util.Date; import java.util.Objects; import java.util.TimeZone; @@ -157,13 +156,13 @@ public class SponsorBlockUtils { private static final DialogInterface.OnClickListener segmentVoteClickListener = (dialog, which) -> { try { final Context context = ((AlertDialog) dialog).getContext(); - SponsorSegment[] currentSegments = SegmentPlaybackController.getSegmentsOfCurrentVideo(); - if (currentSegments == null || currentSegments.length == 0) { + SponsorSegment[] segments = SegmentPlaybackController.getSegments(); + if (segments == null || segments.length == 0) { // should never be reached LogHelper.printException(() -> "Segment is no longer available on the client"); return; } - SponsorSegment segment = currentSegments[which]; + SponsorSegment segment = segments[which]; SegmentVote[] voteOptions = (segment.category == SegmentCategory.HIGHLIGHT) ? SegmentVote.voteTypesWithoutCategoryChange // highlight segments cannot change category @@ -218,8 +217,8 @@ public class SponsorBlockUtils { final String uuid = SettingsEnum.SB_UUID.getString(); final long start = newSponsorSegmentStartMillis; final long end = newSponsorSegmentEndMillis; - final String videoId = VideoInformation.getCurrentVideoId(); - final long videoLength = VideoInformation.getCurrentVideoLength(); + final String videoId = VideoInformation.getVideoId(); + final long videoLength = VideoInformation.getVideoLength(); final SegmentCategory segmentCategory = newUserCreatedSegmentCategory; if (start < 0 || end < 0 || start >= end || videoLength <= 0 || videoId.isEmpty() || segmentCategory == null || uuid.isEmpty()) { @@ -287,33 +286,33 @@ public class SponsorBlockUtils { public static void onVotingClicked(@NonNull Context context) { try { ReVancedUtils.verifyOnMainThread(); - SponsorSegment[] currentSegments = SegmentPlaybackController.getSegmentsOfCurrentVideo(); - if (currentSegments == null || currentSegments.length == 0) { - // button is hidden if no segments exist. + SponsorSegment[] segments = SegmentPlaybackController.getSegments(); + if (segments == null || segments.length == 0) { + // Button is hidden if no segments exist. // But if prior video had segments, and current video does not, - // then the button persists until the overlay fades out (this is intentional, as abruptly hiding the button is jarring) + // then the button persists until the overlay fades out (this is intentional, as abruptly hiding the button is jarring). ReVancedUtils.showToastShort(str("sb_vote_no_segments")); return; } // use same time formatting as shown in the video player - final long currentVideoLength = VideoInformation.getCurrentVideoLength(); + final long videoLength = VideoInformation.getVideoLength(); final String formatPattern; - if (currentVideoLength < (10 * 60 * 1000)) { + if (videoLength < (10 * 60 * 1000)) { formatPattern = "m:ss.SSS"; // less than 10 minutes - } else if (currentVideoLength < (60 * 60 * 1000)) { + } else if (videoLength < (60 * 60 * 1000)) { formatPattern = "mm:ss.SSS"; // less than 1 hour - } else if (currentVideoLength < (10 * 60 * 60 * 1000)) { + } else if (videoLength < (10 * 60 * 60 * 1000)) { formatPattern = "H:mm:ss.SSS"; // less than 10 hours } else { formatPattern = "HH:mm:ss.SSS"; // why is this on YouTube } voteSegmentTimeFormatter.applyPattern(formatPattern); - final int numberOfSegments = currentSegments.length; + final int numberOfSegments = segments.length; CharSequence[] titles = new CharSequence[numberOfSegments]; for (int i = 0; i < numberOfSegments; i++) { - SponsorSegment segment = currentSegments[i]; + SponsorSegment segment = segments[i]; if (segment.category == SegmentCategory.UNSUBMITTED) { continue; } @@ -364,14 +363,11 @@ public class SponsorBlockUtils { } else if (newSponsorSegmentStartMillis >= newSponsorSegmentEndMillis) { ReVancedUtils.showToastShort(str("sb_new_segment_start_is_before_end")); } else { + SegmentPlaybackController.removeUnsubmittedSegments(); // If user hits preview more than once before playing. + SegmentPlaybackController.addUnsubmittedSegment( + new SponsorSegment(SegmentCategory.UNSUBMITTED, null, + newSponsorSegmentStartMillis, newSponsorSegmentEndMillis, false)); VideoInformation.seekTo(newSponsorSegmentStartMillis - 2500); - final SponsorSegment[] original = SegmentPlaybackController.getSegmentsOfCurrentVideo(); - final SponsorSegment[] segments = original == null ? new SponsorSegment[1] : Arrays.copyOf(original, original.length + 1); - - segments[segments.length - 1] = new SponsorSegment(SegmentCategory.UNSUBMITTED, null, - newSponsorSegmentStartMillis, newSponsorSegmentEndMillis, false); - - SegmentPlaybackController.setSegmentsOfCurrentVideo(segments); } } catch (Exception ex) { LogHelper.printException(() -> "onPreviewClicked failure", ex); diff --git a/app/src/main/java/app/revanced/integrations/sponsorblock/objects/SegmentCategory.java b/app/src/main/java/app/revanced/integrations/sponsorblock/objects/SegmentCategory.java index 2a6d3059..6ed56fce 100644 --- a/app/src/main/java/app/revanced/integrations/sponsorblock/objects/SegmentCategory.java +++ b/app/src/main/java/app/revanced/integrations/sponsorblock/objects/SegmentCategory.java @@ -300,7 +300,7 @@ public enum SegmentCategory { */ @NonNull StringRef getSkipButtonText(long segmentStartTime, long videoLength) { - if (SettingsEnum.SB_USE_COMPACT_SKIPBUTTON.getBoolean()) { + if (SettingsEnum.SB_USE_COMPACT_SKIP_BUTTON.getBoolean()) { return (this == SegmentCategory.HIGHLIGHT) ? skipSponsorTextCompactHighlight : skipSponsorTextCompact; diff --git a/app/src/main/java/app/revanced/integrations/sponsorblock/objects/SponsorSegment.java b/app/src/main/java/app/revanced/integrations/sponsorblock/objects/SponsorSegment.java index dff6e230..60c60598 100644 --- a/app/src/main/java/app/revanced/integrations/sponsorblock/objects/SponsorSegment.java +++ b/app/src/main/java/app/revanced/integrations/sponsorblock/objects/SponsorSegment.java @@ -1,13 +1,14 @@ package app.revanced.integrations.sponsorblock.objects; -import static app.revanced.integrations.utils.StringRef.sf; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import app.revanced.integrations.patches.VideoInformation; import app.revanced.integrations.utils.StringRef; +import java.util.Objects; + +import static app.revanced.integrations.utils.StringRef.sf; + public class SponsorSegment implements Comparable { public enum SegmentVote { UPVOTE(sf("sb_vote_upvote"), 1,false), @@ -99,7 +100,7 @@ public class SponsorSegment implements Comparable { */ @NonNull public String getSkipButtonText() { - return category.getSkipButtonText(start, VideoInformation.getCurrentVideoLength()).toString(); + return category.getSkipButtonText(start, VideoInformation.getVideoLength()).toString(); } /** @@ -107,12 +108,30 @@ public class SponsorSegment implements Comparable { */ @NonNull public String getSkippedToastText() { - return category.getSkippedToastText(start, VideoInformation.getCurrentVideoLength()).toString(); + return category.getSkippedToastText(start, VideoInformation.getVideoLength()).toString(); } @Override public int compareTo(SponsorSegment o) { - return (int) (this.start - o.start); + // If both segments start at the same time, then sort with the longer segment first. + // This keeps the seekbar drawing correct since it draws the segments using the sorted order. + return start == o.start ? Long.compare(o.length(), length()) : Long.compare(start, o.start); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SponsorSegment)) return false; + SponsorSegment other = (SponsorSegment) o; + return Objects.equals(UUID, other.UUID) + && category == other.category + && start == other.start + && end == other.end; + } + + @Override + public int hashCode() { + return Objects.hashCode(UUID); } @NonNull diff --git a/app/src/main/java/app/revanced/integrations/sponsorblock/requests/SBRequester.java b/app/src/main/java/app/revanced/integrations/sponsorblock/requests/SBRequester.java index cd17eb3e..a2985f4d 100644 --- a/app/src/main/java/app/revanced/integrations/sponsorblock/requests/SBRequester.java +++ b/app/src/main/java/app/revanced/integrations/sponsorblock/requests/SBRequester.java @@ -97,6 +97,33 @@ public class SBRequester { } catch (Exception ex) { LogHelper.printException(() -> "Failed to get segments", ex, str("sb_sponsorblock_connection_failure_generic")); } + + // Crude debug tests to verify random features + // Could benefit from: + // 1) collection of YouTube videos with test segment times (verify client skip timing matches the video, verify seekbar draws correctly) + // 2) unit tests (verify everything else) + if (false) { + segments.clear(); + // Test auto-hide skip button: + // Button should appear only once + segments.add(new SponsorSegment(SegmentCategory.INTRO, "debug", 5000, 120000, false)); + // Button should appear only once + segments.add(new SponsorSegment(SegmentCategory.SELF_PROMO, "debug", 10000, 60000, false)); + // Button should appear only once + segments.add(new SponsorSegment(SegmentCategory.INTERACTION, "debug", 15000, 20000, false)); + // Button should appear _twice_ (at 21s and 27s) + segments.add(new SponsorSegment(SegmentCategory.SPONSOR, "debug", 21000, 30000, false)); + // Button should appear only once + segments.add(new SponsorSegment(SegmentCategory.OUTRO, "debug", 24000, 27000, false)); + + + // Test seekbar visibility: + // All three segments should be viewable on the seekbar + segments.add(new SponsorSegment(SegmentCategory.MUSIC_OFFTOPIC, "debug", 200000, 300000, false)); + segments.add(new SponsorSegment(SegmentCategory.SPONSOR, "debug", 200000, 250000, false)); + segments.add(new SponsorSegment(SegmentCategory.SELF_PROMO, "debug", 200000, 330000, false)); + } + return segments.toArray(new SponsorSegment[0]); } diff --git a/app/src/main/java/app/revanced/integrations/sponsorblock/ui/NewSegmentLayout.java b/app/src/main/java/app/revanced/integrations/sponsorblock/ui/NewSegmentLayout.java index 88ed535b..e1e09182 100644 --- a/app/src/main/java/app/revanced/integrations/sponsorblock/ui/NewSegmentLayout.java +++ b/app/src/main/java/app/revanced/integrations/sponsorblock/ui/NewSegmentLayout.java @@ -1,8 +1,5 @@ package app.revanced.integrations.sponsorblock.ui; -import static app.revanced.integrations.utils.ReVancedUtils.getResourceDimensionPixelSize; -import static app.revanced.integrations.utils.ReVancedUtils.getResourceIdentifier; - import android.content.Context; import android.content.res.ColorStateList; import android.graphics.drawable.RippleDrawable; @@ -11,114 +8,119 @@ import android.util.TypedValue; import android.view.LayoutInflater; import android.widget.FrameLayout; import android.widget.ImageButton; - import app.revanced.integrations.patches.VideoInformation; import app.revanced.integrations.settings.SettingsEnum; import app.revanced.integrations.sponsorblock.SponsorBlockUtils; import app.revanced.integrations.utils.LogHelper; -public class NewSegmentLayout extends FrameLayout { +import static app.revanced.integrations.utils.ReVancedUtils.getResourceDimensionPixelSize; +import static app.revanced.integrations.utils.ReVancedUtils.getResourceIdentifier; + +public final class NewSegmentLayout extends FrameLayout { + private static final ColorStateList rippleColorStateList = new ColorStateList( + new int[][]{new int[]{android.R.attr.state_enabled}}, + new int[]{0x33ffffff} // sets the ripple color to white + ); private final int rippleEffectId; + final int defaultBottomMargin; final int ctaBottomMargin; - public NewSegmentLayout(Context context) { + public NewSegmentLayout(final Context context) { this(context, null); } - public NewSegmentLayout(Context context, AttributeSet attributeSet) { + public NewSegmentLayout(final Context context, final AttributeSet attributeSet) { this(context, attributeSet, 0); } - public NewSegmentLayout(Context context, AttributeSet attributeSet, int defStyleAttr) { + public NewSegmentLayout(final Context context, final AttributeSet attributeSet, final int defStyleAttr) { this(context, attributeSet, defStyleAttr, 0); } - public NewSegmentLayout(Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes) { + public NewSegmentLayout(final Context context, final AttributeSet attributeSet, + final int defStyleAttr, final int defStyleRes) { super(context, attributeSet, defStyleAttr, defStyleRes); - LayoutInflater.from(context).inflate(getResourceIdentifier(context, "new_segment", "layout"), this, true); + LayoutInflater.from(context).inflate( + getResourceIdentifier(context, "new_segment", "layout"), this, true + ); TypedValue rippleEffect = new TypedValue(); context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, rippleEffect, true); rippleEffectId = rippleEffect.resourceId; - // LinearLayout newSegmentContainer = findViewById(getResourceIdentifier(context, "sb_new_segment_container", "id")); + initializeButton( + context, + "sb_new_segment_rewind", + () -> VideoInformation.seekToRelative(-SettingsEnum.SB_ADJUST_NEW_SEGMENT_STEP.getInt()), + "Rewind button clicked" + ); - ImageButton rewindButton = findViewById(getResourceIdentifier(context, "sb_new_segment_rewind", "id")); - if (rewindButton == null) { - LogHelper.printException(() -> "Could not find rewindButton"); - } else { - setClickEffect(rewindButton); - rewindButton.setOnClickListener(v -> { - LogHelper.printDebug(() -> "Rewind button clicked"); - VideoInformation.seekToRelative(-SettingsEnum.SB_ADJUST_NEW_SEGMENT_STEP.getInt()); - }); - } - ImageButton forwardButton = findViewById(getResourceIdentifier(context, "sb_new_segment_forward", "id")); - if (forwardButton == null) { - LogHelper.printException(() -> "Could not find forwardButton"); - } else { - setClickEffect(forwardButton); - forwardButton.setOnClickListener(v -> { - LogHelper.printDebug(() -> "Forward button clicked"); - VideoInformation.seekToRelative(SettingsEnum.SB_ADJUST_NEW_SEGMENT_STEP.getInt()); - }); - } - ImageButton adjustButton = findViewById(getResourceIdentifier(context, "sb_new_segment_adjust", "id")); - if (adjustButton == null) { - LogHelper.printException(() -> "Could not find adjustButton"); - } else { - setClickEffect(adjustButton); - adjustButton.setOnClickListener(v -> { - LogHelper.printDebug(() -> "Adjust button clicked"); - SponsorBlockUtils.onMarkLocationClicked(); - }); - } - ImageButton compareButton = findViewById(getResourceIdentifier(context, "sb_new_segment_compare", "id")); - if (compareButton == null) { - LogHelper.printException(() -> "Could not find compareButton"); - } else { - setClickEffect(compareButton); - compareButton.setOnClickListener(v -> { - LogHelper.printDebug(() -> "Compare button clicked"); - SponsorBlockUtils.onPreviewClicked(); - }); - } - ImageButton editButton = findViewById(getResourceIdentifier(context, "sb_new_segment_edit", "id")); - if (editButton == null) { - LogHelper.printException(() -> "Could not find editButton"); - } else { - setClickEffect(editButton); - editButton.setOnClickListener(v -> { - LogHelper.printDebug(() -> "Edit button clicked"); - SponsorBlockUtils.onEditByHandClicked(); - }); - } - ImageButton publishButton = findViewById(getResourceIdentifier(context, "sb_new_segment_publish", "id")); - if (publishButton == null) { - LogHelper.printException(() -> "Could not find publishButton"); - } else { - setClickEffect(publishButton); - publishButton.setOnClickListener(v -> { - LogHelper.printDebug(() -> "Publish button clicked"); - SponsorBlockUtils.onPublishClicked(); - }); - } + initializeButton( + context, + "sb_new_segment_forward", + () -> VideoInformation.seekToRelative(SettingsEnum.SB_ADJUST_NEW_SEGMENT_STEP.getInt()), + "Forward button clicked" + ); + + initializeButton( + context, + "sb_new_segment_adjust", + SponsorBlockUtils::onMarkLocationClicked, + "Adjust button clicked" + ); + + initializeButton( + context, + "sb_new_segment_compare", + SponsorBlockUtils::onPreviewClicked, + "Compare button clicked" + ); + + initializeButton( + context, + "sb_new_segment_edit", + SponsorBlockUtils::onEditByHandClicked, + "Edit button clicked" + ); + + initializeButton( + context, + "sb_new_segment_publish", + SponsorBlockUtils::onPublishClicked, + "Publish button clicked" + ); defaultBottomMargin = getResourceDimensionPixelSize("brand_interaction_default_bottom_margin"); ctaBottomMargin = getResourceDimensionPixelSize("brand_interaction_cta_bottom_margin"); } - private void setClickEffect(ImageButton btn) { - btn.setBackgroundResource(rippleEffectId); + /** + * Initializes a segment button with the given resource identifier name with the given handler and a ripple effect. + * + * @param context The context. + * @param resourceIdentifierName The resource identifier name for the button. + * @param handler The handler for the button's click event. + * @param debugMessage The debug message to print when the button is clicked. + */ + private void initializeButton(final Context context, final String resourceIdentifierName, + final ButtonOnClickHandlerFunction handler, final String debugMessage) { + final ImageButton button = findViewById(getResourceIdentifier(context, resourceIdentifierName, "id")); - RippleDrawable rippleDrawable = (RippleDrawable) btn.getBackground(); + // Add ripple effect + button.setBackgroundResource(rippleEffectId); + RippleDrawable rippleDrawable = (RippleDrawable) button.getBackground(); + rippleDrawable.setColor(rippleColorStateList); - int[][] states = new int[][]{new int[]{android.R.attr.state_enabled}}; - int[] colors = new int[]{0x33ffffff}; // sets the ripple color to white + button.setOnClickListener((v) -> { + handler.apply(); + LogHelper.printDebug(() -> debugMessage); + }); + } - ColorStateList colorStateList = new ColorStateList(states, colors); - rippleDrawable.setColor(colorStateList); + @FunctionalInterface + public interface ButtonOnClickHandlerFunction { + void apply(); } } diff --git a/app/src/main/java/app/revanced/integrations/sponsorblock/ui/SkipSponsorButton.java b/app/src/main/java/app/revanced/integrations/sponsorblock/ui/SkipSponsorButton.java index 65666da8..47613392 100644 --- a/app/src/main/java/app/revanced/integrations/sponsorblock/ui/SkipSponsorButton.java +++ b/app/src/main/java/app/revanced/integrations/sponsorblock/ui/SkipSponsorButton.java @@ -10,6 +10,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.LayoutInflater; +import android.view.View; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; @@ -20,7 +21,6 @@ import java.util.Objects; import app.revanced.integrations.sponsorblock.SegmentPlaybackController; import app.revanced.integrations.sponsorblock.objects.SponsorSegment; -import app.revanced.integrations.utils.LogHelper; public class SkipSponsorButton extends FrameLayout { private static final boolean highContrast = true; @@ -62,6 +62,8 @@ public class SkipSponsorButton extends FrameLayout { ctaBottomMargin = getResourceDimensionPixelSize("skip_button_cta_bottom_margin"); // dimen:skip_button_cta_bottom_margin skipSponsorBtnContainer.setOnClickListener(v -> { + // The view controller handles hiding this button, but hide it here as well just in case something goofs. + setVisibility(View.GONE); SegmentPlaybackController.onSkipSegmentClicked(segment); }); } diff --git a/app/src/main/java/app/revanced/integrations/sponsorblock/ui/SponsorBlockViewController.java b/app/src/main/java/app/revanced/integrations/sponsorblock/ui/SponsorBlockViewController.java index 5925f80c..fcbaf2da 100644 --- a/app/src/main/java/app/revanced/integrations/sponsorblock/ui/SponsorBlockViewController.java +++ b/app/src/main/java/app/revanced/integrations/sponsorblock/ui/SponsorBlockViewController.java @@ -55,6 +55,9 @@ public class SponsorBlockViewController { try { LogHelper.printDebug(() -> "initializing"); + // hide any old components, just in case they somehow are still hanging around + hideAll(); + Context context = ReVancedUtils.getContext(); RelativeLayout layout = new RelativeLayout(context); layout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,RelativeLayout.LayoutParams.MATCH_PARENT)); @@ -103,7 +106,7 @@ public class SponsorBlockViewController { skipHighlight = Objects.requireNonNull(segment); NewSegmentLayout newSegmentLayout = newSegmentLayoutRef.get(); // don't show highlight button if create new segment is visible - final boolean buttonVisibility = newSegmentLayout != null && newSegmentLayout.getVisibility() != View.VISIBLE; + final boolean buttonVisibility = newSegmentLayout == null || newSegmentLayout.getVisibility() != View.VISIBLE; updateSkipButton(skipHighlightButtonRef.get(), segment, buttonVisibility); } public static void showSkipSegmentButton(@NonNull SponsorSegment segment) { @@ -146,11 +149,7 @@ public class SponsorBlockViewController { public static void hideNewSegmentLayout() { newSegmentLayoutVisible = false; - NewSegmentLayout newSegmentLayout = newSegmentLayoutRef.get(); - if (newSegmentLayout == null) { - return; - } - setViewVisibility(newSegmentLayout, false); + setViewVisibility(newSegmentLayoutRef.get(), false); } private static void setViewVisibility(@Nullable View view, boolean visible) { diff --git a/app/src/main/java/app/revanced/integrations/sponsorblock/ui/VotingButtonController.java b/app/src/main/java/app/revanced/integrations/sponsorblock/ui/VotingButtonController.java index 4a5a162d..0a5c499a 100644 --- a/app/src/main/java/app/revanced/integrations/sponsorblock/ui/VotingButtonController.java +++ b/app/src/main/java/app/revanced/integrations/sponsorblock/ui/VotingButtonController.java @@ -106,7 +106,7 @@ public class VotingButtonController { private static boolean shouldBeShown() { return SettingsEnum.SB_ENABLED.getBoolean() && SettingsEnum.SB_VOTING_ENABLED.getBoolean() - && SegmentPlaybackController.currentVideoHasSegments() && !VideoInformation.isAtEndOfVideo(); + && SegmentPlaybackController.videoHasSegments() && !VideoInformation.isAtEndOfVideo(); } public static void hide() { diff --git a/app/src/main/java/app/revanced/integrations/videoplayer/DownloadButton.java b/app/src/main/java/app/revanced/integrations/videoplayer/DownloadButton.java index 5928e021..4b915dc0 100644 --- a/app/src/main/java/app/revanced/integrations/videoplayer/DownloadButton.java +++ b/app/src/main/java/app/revanced/integrations/videoplayer/DownloadButton.java @@ -52,7 +52,7 @@ public class DownloadButton extends BottomControlButton { // Launch PowerTube intent try { - String content = String.format("https://youtu.be/%s", VideoInformation.getCurrentVideoId()); + String content = String.format("https://youtu.be/%s", VideoInformation.getVideoId()); Intent intent = new Intent("android.intent.action.SEND"); intent.setType("text/plain"); diff --git a/gradle.properties b/gradle.properties index 3f5701c4..53819efd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.jvmargs = -Xmx2048m android.useAndroidX = true -version = 0.104.0 +version = 0.105.0-dev.2