mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-19 17:13:59 +00:00
feat(YouTube - SponsorBlock): Add "Undo automatic skip toast" (#5277)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
@@ -311,6 +311,10 @@ public class Utils {
|
||||
return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen"));
|
||||
}
|
||||
|
||||
public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException {
|
||||
return getContext().getResources().getStringArray(getResourceIdentifier(resourceIdentifierName, "array"));
|
||||
}
|
||||
|
||||
public interface MatchFilter<T> {
|
||||
boolean matches(T object);
|
||||
}
|
||||
@@ -579,7 +583,7 @@ public class Utils {
|
||||
Context currentContext = context;
|
||||
|
||||
if (currentContext == null) {
|
||||
Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast, null);
|
||||
Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast);
|
||||
} else {
|
||||
Logger.printDebug(() -> "Showing toast: " + messageToToast);
|
||||
Toast.makeText(currentContext, messageToToast, toastDuration).show();
|
||||
@@ -809,7 +813,7 @@ public class Utils {
|
||||
|
||||
// Create content container (message/EditText) inside a ScrollView only if message or editText is provided.
|
||||
ScrollView contentScrollView = null;
|
||||
LinearLayout contentContainer = null;
|
||||
LinearLayout contentContainer;
|
||||
if (message != null || editText != null) {
|
||||
contentScrollView = new ScrollView(context);
|
||||
contentScrollView.setVerticalScrollBarEnabled(false); // Disable the vertical scrollbar.
|
||||
@@ -833,7 +837,7 @@ public class Utils {
|
||||
contentScrollView.addView(contentContainer);
|
||||
|
||||
// Message (if not replaced by EditText).
|
||||
if (editText == null && message != null) {
|
||||
if (editText == null) {
|
||||
TextView messageView = new TextView(context);
|
||||
messageView.setText(message); // Supports Spanned (HTML).
|
||||
messageView.setTextSize(16);
|
||||
|
||||
@@ -71,15 +71,20 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
|
||||
json.put(importExportKey, value.name().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private T getEnumFromString(String enumName) {
|
||||
/**
|
||||
* @param enumName Enum name. Casing does not matter.
|
||||
* @return Enum of this type with the same declared name.
|
||||
* @throws IllegalArgumentException if the name is not a valid enum of this type.
|
||||
*/
|
||||
protected T getEnumFromString(String enumName) {
|
||||
//noinspection ConstantConditions
|
||||
for (Enum<?> value : defaultValue.getClass().getEnumConstants()) {
|
||||
if (value.name().equalsIgnoreCase(enumName)) {
|
||||
// noinspection unchecked
|
||||
//noinspection unchecked
|
||||
return (T) value;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown enum value: " + enumName);
|
||||
}
|
||||
|
||||
@@ -103,7 +108,9 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
|
||||
* Availability based on if this setting is currently set to any of the provided types.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final Setting.Availability availability(@NonNull T... types) {
|
||||
public final Setting.Availability availability(T... types) {
|
||||
Objects.requireNonNull(types);
|
||||
|
||||
return () -> {
|
||||
T currentEnumType = get();
|
||||
for (T enumType : types) {
|
||||
|
||||
@@ -28,16 +28,14 @@ public abstract class Setting<T> {
|
||||
/**
|
||||
* Availability based on a single parent setting being enabled.
|
||||
*/
|
||||
@NonNull
|
||||
public static Availability parent(@NonNull BooleanSetting parent) {
|
||||
public static Availability parent(BooleanSetting parent) {
|
||||
return parent::get;
|
||||
}
|
||||
|
||||
/**
|
||||
* Availability based on all parents being enabled.
|
||||
*/
|
||||
@NonNull
|
||||
public static Availability parentsAll(@NonNull BooleanSetting... parents) {
|
||||
public static Availability parentsAll(BooleanSetting... parents) {
|
||||
return () -> {
|
||||
for (BooleanSetting parent : parents) {
|
||||
if (!parent.get()) return false;
|
||||
@@ -49,8 +47,7 @@ public abstract class Setting<T> {
|
||||
/**
|
||||
* Availability based on any parent being enabled.
|
||||
*/
|
||||
@NonNull
|
||||
public static Availability parentsAny(@NonNull BooleanSetting... parents) {
|
||||
public static Availability parentsAny(BooleanSetting... parents) {
|
||||
return () -> {
|
||||
for (BooleanSetting parent : parents) {
|
||||
if (parent.get()) return true;
|
||||
@@ -79,7 +76,7 @@ public abstract class Setting<T> {
|
||||
/**
|
||||
* Adds a callback for {@link #importFromJSON(Context, String)} and {@link #exportToJson(Context)}.
|
||||
*/
|
||||
public static void addImportExportCallback(@NonNull ImportExportCallback callback) {
|
||||
public static void addImportExportCallback(ImportExportCallback callback) {
|
||||
importExportCallbacks.add(Objects.requireNonNull(callback));
|
||||
}
|
||||
|
||||
@@ -100,14 +97,13 @@ public abstract class Setting<T> {
|
||||
public static final SharedPrefCategory preferences = new SharedPrefCategory("revanced_prefs");
|
||||
|
||||
@Nullable
|
||||
public static Setting<?> getSettingFromPath(@NonNull String str) {
|
||||
public static Setting<?> getSettingFromPath(String str) {
|
||||
return PATH_TO_SETTINGS.get(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return All settings that have been created.
|
||||
*/
|
||||
@NonNull
|
||||
public static List<Setting<?>> allLoadedSettings() {
|
||||
return Collections.unmodifiableList(SETTINGS);
|
||||
}
|
||||
@@ -115,7 +111,6 @@ public abstract class Setting<T> {
|
||||
/**
|
||||
* @return All settings that have been created, sorted by keys.
|
||||
*/
|
||||
@NonNull
|
||||
private static List<Setting<?>> allLoadedSettingsSorted() {
|
||||
Collections.sort(SETTINGS, (Setting<?> o1, Setting<?> o2) -> o1.key.compareTo(o2.key));
|
||||
return allLoadedSettings();
|
||||
@@ -124,13 +119,11 @@ public abstract class Setting<T> {
|
||||
/**
|
||||
* The key used to store the value in the shared preferences.
|
||||
*/
|
||||
@NonNull
|
||||
public final String key;
|
||||
|
||||
/**
|
||||
* The default value of the setting.
|
||||
*/
|
||||
@NonNull
|
||||
public final T defaultValue;
|
||||
|
||||
/**
|
||||
@@ -161,7 +154,6 @@ public abstract class Setting<T> {
|
||||
/**
|
||||
* The value of the setting.
|
||||
*/
|
||||
@NonNull
|
||||
protected volatile T value;
|
||||
|
||||
public Setting(String key, T defaultValue) {
|
||||
@@ -199,8 +191,8 @@ public abstract class Setting<T> {
|
||||
* @param userDialogMessage Confirmation message to display, if the user tries to change the setting from the default value.
|
||||
* @param availability Condition that must be true, for this setting to be available to configure.
|
||||
*/
|
||||
public Setting(@NonNull String key,
|
||||
@NonNull T defaultValue,
|
||||
public Setting(String key,
|
||||
T defaultValue,
|
||||
boolean rebootApp,
|
||||
boolean includeWithImportExport,
|
||||
@Nullable String userDialogMessage,
|
||||
@@ -227,7 +219,7 @@ public abstract class Setting<T> {
|
||||
/**
|
||||
* Migrate a setting value if the path is renamed but otherwise the old and new settings are identical.
|
||||
*/
|
||||
public static <T> void migrateOldSettingToNew(@NonNull Setting<T> oldSetting, @NonNull Setting<T> newSetting) {
|
||||
public static <T> void migrateOldSettingToNew(Setting<T> oldSetting, Setting<T> newSetting) {
|
||||
if (oldSetting == newSetting) throw new IllegalArgumentException();
|
||||
|
||||
if (!oldSetting.isSetToDefault()) {
|
||||
@@ -243,7 +235,7 @@ public abstract class Setting<T> {
|
||||
* This method will be deleted in the future.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static void migrateFromOldPreferences(@NonNull SharedPrefCategory oldPrefs, @NonNull Setting setting, String settingKey) {
|
||||
public static void migrateFromOldPreferences(SharedPrefCategory oldPrefs, Setting setting, String settingKey) {
|
||||
if (!oldPrefs.preferences.contains(settingKey)) {
|
||||
return; // Nothing to do.
|
||||
}
|
||||
@@ -285,7 +277,7 @@ public abstract class Setting<T> {
|
||||
* This intentionally is a static method to deter
|
||||
* accidental usage when {@link #save(Object)} was intended.
|
||||
*/
|
||||
public static void privateSetValueFromString(@NonNull Setting<?> setting, @NonNull String newValue) {
|
||||
public static void privateSetValueFromString(Setting<?> setting, String newValue) {
|
||||
setting.setValueFromString(newValue);
|
||||
|
||||
// Clear the preference value since default is used, to allow changing
|
||||
@@ -299,7 +291,7 @@ public abstract class Setting<T> {
|
||||
/**
|
||||
* Sets the value of {@link #value}, but do not save to {@link #preferences}.
|
||||
*/
|
||||
protected abstract void setValueFromString(@NonNull String newValue);
|
||||
protected abstract void setValueFromString(String newValue);
|
||||
|
||||
/**
|
||||
* Load and set the value of {@link #value}.
|
||||
@@ -309,7 +301,7 @@ public abstract class Setting<T> {
|
||||
/**
|
||||
* Persistently saves the value.
|
||||
*/
|
||||
public final void save(@NonNull T newValue) {
|
||||
public final void save(T newValue) {
|
||||
if (value.equals(newValue)) {
|
||||
return;
|
||||
}
|
||||
@@ -406,7 +398,6 @@ public abstract class Setting<T> {
|
||||
json.put(importExportKey, value);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String exportToJson(@Nullable Context alertDialogContext) {
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
@@ -445,7 +436,7 @@ public abstract class Setting<T> {
|
||||
/**
|
||||
* @return if any settings that require a reboot were changed.
|
||||
*/
|
||||
public static boolean importFromJSON(@NonNull Context alertDialogContext, @NonNull String settingsJsonString) {
|
||||
public static boolean importFromJSON(Context alertDialogContext, String settingsJsonString) {
|
||||
try {
|
||||
if (!settingsJsonString.matches("[\\s\\S]*\\{")) {
|
||||
settingsJsonString = '{' + settingsJsonString + '}'; // Restore outer JSON braces
|
||||
|
||||
@@ -5,6 +5,7 @@ import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.shared.settings.Setting.Availability;
|
||||
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
|
||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
import static app.revanced.extension.shared.settings.Setting.parentsAll;
|
||||
import static app.revanced.extension.shared.settings.Setting.parentsAny;
|
||||
import static app.revanced.extension.youtube.patches.ChangeFormFactorPatch.FormFactor;
|
||||
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.ChangeStartPageTypeAvailability;
|
||||
@@ -22,6 +23,7 @@ import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPa
|
||||
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
|
||||
import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
|
||||
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
|
||||
import static app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController.SponsorBlockDuration;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
||||
@@ -381,7 +383,11 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting SB_SQUARE_LAYOUT = new BooleanSetting("sb_square_layout", FALSE, parent(SB_ENABLED));
|
||||
public static final BooleanSetting SB_COMPACT_SKIP_BUTTON = new BooleanSetting("sb_compact_skip_button", FALSE, parent(SB_ENABLED));
|
||||
public static final BooleanSetting SB_AUTO_HIDE_SKIP_BUTTON = new BooleanSetting("sb_auto_hide_skip_button", TRUE, parent(SB_ENABLED));
|
||||
public static final EnumSetting<SponsorBlockDuration> SB_AUTO_HIDE_SKIP_BUTTON_DURATION = new EnumSetting<>("sb_auto_hide_skip_button_duration",
|
||||
SponsorBlockDuration.FOUR_SECONDS, parent(SB_ENABLED));
|
||||
public static final BooleanSetting SB_TOAST_ON_SKIP = new BooleanSetting("sb_toast_on_skip", TRUE, parent(SB_ENABLED));
|
||||
public static final EnumSetting<SponsorBlockDuration> SB_TOAST_ON_SKIP_DURATION = new EnumSetting<>("sb_toast_on_skip_duration",
|
||||
SponsorBlockDuration.FOUR_SECONDS, parentsAll(SB_ENABLED, SB_TOAST_ON_SKIP));
|
||||
public static final BooleanSetting SB_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("sb_toast_on_connection_error", TRUE, parent(SB_ENABLED));
|
||||
public static final BooleanSetting SB_TRACK_SKIP_COUNT = new BooleanSetting("sb_track_skip_count", TRUE, parent(SB_ENABLED));
|
||||
public static final FloatSetting SB_SEGMENT_MIN_DURATION = new FloatSetting("sb_min_segment_duration", 0F, parent(SB_ENABLED));
|
||||
|
||||
@@ -1,16 +1,37 @@
|
||||
package app.revanced.extension.youtube.sponsorblock;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.graphics.drawable.shapes.RoundRectShape;
|
||||
import android.text.TextUtils;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.util.Range;
|
||||
import android.view.Gravity;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
@@ -30,20 +51,37 @@ import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockViewController
|
||||
* Class is not thread safe. All methods must be called on the main thread unless otherwise specified.
|
||||
*/
|
||||
public class SegmentPlaybackController {
|
||||
|
||||
/**
|
||||
* Length of time to show a skip button for a highlight segment,
|
||||
* or a regular segment if {@link Settings#SB_AUTO_HIDE_SKIP_BUTTON} is enabled.
|
||||
*
|
||||
* Effectively this value is rounded up to the next second.
|
||||
* Enum for configurable durations (1 to 10 seconds) for skip button and toast display.
|
||||
*/
|
||||
private static final long DURATION_TO_SHOW_SKIP_BUTTON = 3800;
|
||||
public enum SponsorBlockDuration {
|
||||
ONE_SECOND(1),
|
||||
TWO_SECONDS(2),
|
||||
THREE_SECONDS(3),
|
||||
FOUR_SECONDS(4),
|
||||
FIVE_SECONDS(5),
|
||||
SIX_SECONDS(6),
|
||||
SEVEN_SECONDS(7),
|
||||
EIGHT_SECONDS(8),
|
||||
NINE_SECONDS(9),
|
||||
TEN_SECONDS(10);
|
||||
|
||||
/**
|
||||
* Duration, minus 200ms to adjust for exclusive end time checking in scheduled show/hides.
|
||||
*/
|
||||
private final long adjustedDuration;
|
||||
|
||||
SponsorBlockDuration(int seconds) {
|
||||
adjustedDuration = seconds * 1000L - 200;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Highlight segments have zero length as they are a point in time.
|
||||
* Draw them on screen using a fixed width bar.
|
||||
* Value is independent of device dpi.
|
||||
*/
|
||||
private static final int HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH = 7;
|
||||
private static final int HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH = dipToPixels(7);
|
||||
|
||||
@Nullable
|
||||
private static String currentVideoId;
|
||||
@@ -59,7 +97,7 @@ public class SegmentPlaybackController {
|
||||
/**
|
||||
* Because loading can take time, show the skip to highlight for a few seconds after the segments load.
|
||||
* This is the system time (in milliseconds) to no longer show the initial display skip to highlight.
|
||||
* Value will be zero if no highlight segment exists, or if the system time to show the highlight has passed.
|
||||
* Value is zero if no highlight segment exists, or if the system time to show the highlight has passed.
|
||||
*/
|
||||
private static long highlightSegmentInitialShowEndTime;
|
||||
|
||||
@@ -70,7 +108,7 @@ public class SegmentPlaybackController {
|
||||
private static SponsorSegment segmentCurrentlyPlaying;
|
||||
/**
|
||||
* Currently playing manual skip segment that is scheduled to hide.
|
||||
* This will always be NULL or equal to {@link #segmentCurrentlyPlaying}.
|
||||
* This is always NULL or equal to {@link #segmentCurrentlyPlaying}.
|
||||
*/
|
||||
@Nullable
|
||||
private static SponsorSegment scheduledHideSegment;
|
||||
@@ -89,31 +127,71 @@ public class SegmentPlaybackController {
|
||||
*/
|
||||
private static final List<SponsorSegment> hiddenSkipSegmentsForCurrentVideoTime = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Current segments that have been auto skipped.
|
||||
* If field is non null then the range will always contain the current video time.
|
||||
* Range is used to prevent auto-skipping after undo.
|
||||
* Android Range object has inclusive end time, unlike {@link SponsorSegment}.
|
||||
*/
|
||||
@Nullable
|
||||
private static Range<Long> undoAutoSkipRange;
|
||||
/**
|
||||
* Range to undo if the toast is tapped.
|
||||
* Is always null or identical to the last non null value of {@link #undoAutoSkipRange}.
|
||||
*/
|
||||
@Nullable
|
||||
private static Range<Long> undoAutoSkipRangeToast;
|
||||
|
||||
/**
|
||||
* 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 Settings#SB_AUTO_HIDE_SKIP_BUTTON} is not enabled.
|
||||
*/
|
||||
private static long skipSegmentButtonEndTime;
|
||||
|
||||
@Nullable
|
||||
private static String timeWithoutSegments;
|
||||
|
||||
private static int sponsorBarAbsoluteLeft;
|
||||
private static int sponsorAbsoluteBarRight;
|
||||
private static int sponsorBarThickness;
|
||||
|
||||
@Nullable
|
||||
private static SponsorSegment lastSegmentSkipped;
|
||||
private static long lastSegmentSkippedTime;
|
||||
|
||||
@Nullable
|
||||
private static SponsorSegment toastSegmentSkipped;
|
||||
private static int toastNumberOfSegmentsSkipped;
|
||||
|
||||
/**
|
||||
* The last toast dialog showing on screen.
|
||||
*/
|
||||
private static WeakReference<Dialog> toastDialogRef = new WeakReference<>(null);
|
||||
|
||||
/**
|
||||
* @return The adjusted duration to show the skip button, in milliseconds.
|
||||
*/
|
||||
private static long getSkipButtonDuration() {
|
||||
return Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION.get().adjustedDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The adjusted duration to show the skipped toast, in milliseconds.
|
||||
*/
|
||||
private static long getToastDuration() {
|
||||
return Settings.SB_TOAST_ON_SKIP_DURATION.get().adjustedDuration;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static SponsorSegment[] getSegments() {
|
||||
return segments;
|
||||
}
|
||||
|
||||
private static void setSegments(@NonNull SponsorSegment[] videoSegments) {
|
||||
private static void setSegments(SponsorSegment[] videoSegments) {
|
||||
Arrays.sort(videoSegments);
|
||||
segments = videoSegments;
|
||||
calculateTimeWithoutSegments();
|
||||
|
||||
if (SegmentCategory.HIGHLIGHT.behaviour == CategoryBehaviour.SKIP_AUTOMATICALLY
|
||||
if (SegmentCategory.HIGHLIGHT.behaviour == SKIP_AUTOMATICALLY
|
||||
|| SegmentCategory.HIGHLIGHT.behaviour == CategoryBehaviour.MANUAL_SKIP) {
|
||||
for (SponsorSegment segment : videoSegments) {
|
||||
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
||||
@@ -125,7 +203,7 @@ public class SegmentPlaybackController {
|
||||
highlightSegment = null;
|
||||
}
|
||||
|
||||
static void addUnsubmittedSegment(@NonNull SponsorSegment segment) {
|
||||
static void addUnsubmittedSegment(SponsorSegment segment) {
|
||||
Objects.requireNonNull(segment);
|
||||
if (segments == null) {
|
||||
segments = new SponsorSegment[1];
|
||||
@@ -140,6 +218,7 @@ public class SegmentPlaybackController {
|
||||
if (segments == null || segments.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<SponsorSegment> replacement = new ArrayList<>();
|
||||
for (SponsorSegment segment : segments) {
|
||||
if (segment.category != SegmentCategory.UNSUBMITTED) {
|
||||
@@ -156,7 +235,7 @@ public class SegmentPlaybackController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all downloaded data.
|
||||
* Clear all data.
|
||||
*/
|
||||
private static void clearData() {
|
||||
currentVideoId = null;
|
||||
@@ -170,6 +249,8 @@ public class SegmentPlaybackController {
|
||||
skipSegmentButtonEndTime = 0;
|
||||
toastSegmentSkipped = null;
|
||||
toastNumberOfSegmentsSkipped = 0;
|
||||
undoAutoSkipRange = null;
|
||||
undoAutoSkipRangeToast = null;
|
||||
hiddenSkipSegmentsForCurrentVideoTime.clear();
|
||||
}
|
||||
|
||||
@@ -186,7 +267,7 @@ public class SegmentPlaybackController {
|
||||
SponsorBlockUtils.clearUnsubmittedSegmentTimes();
|
||||
Logger.printDebug(() -> "Initialized SponsorBlock");
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed to initialize SponsorBlock", ex);
|
||||
Logger.printException(() -> "initialize failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +284,7 @@ public class SegmentPlaybackController {
|
||||
return;
|
||||
}
|
||||
if (PlayerType.getCurrent().isNoneOrHidden()) {
|
||||
Logger.printDebug(() -> "ignoring Short");
|
||||
Logger.printDebug(() -> "Ignoring Short");
|
||||
return;
|
||||
}
|
||||
if (!Utils.isNetworkConnected()) {
|
||||
@@ -212,7 +293,7 @@ public class SegmentPlaybackController {
|
||||
}
|
||||
|
||||
currentVideoId = videoId;
|
||||
Logger.printDebug(() -> "setCurrentVideoId: " + videoId);
|
||||
Logger.printDebug(() -> "New video ID: " + videoId);
|
||||
|
||||
Utils.runOnBackgroundThread(() -> {
|
||||
try {
|
||||
@@ -227,42 +308,39 @@ public class SegmentPlaybackController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called off main thread
|
||||
* Must be called off main thread.
|
||||
*/
|
||||
static void executeDownloadSegments(@NonNull String videoId) {
|
||||
static void executeDownloadSegments(String videoId) {
|
||||
Objects.requireNonNull(videoId);
|
||||
try {
|
||||
SponsorSegment[] segments = SBRequester.getSegments(videoId);
|
||||
|
||||
Utils.runOnMainThread(()-> {
|
||||
if (!videoId.equals(currentVideoId)) {
|
||||
// user changed videos before get segments network call could complete
|
||||
Logger.printDebug(() -> "Ignoring segments for prior video: " + videoId);
|
||||
return;
|
||||
}
|
||||
setSegments(segments);
|
||||
SponsorSegment[] segments = SBRequester.getSegments(videoId);
|
||||
|
||||
final long videoTime = VideoInformation.getVideoTime();
|
||||
if (highlightSegment != null) {
|
||||
// If the current video time is before the highlight.
|
||||
final long timeUntilHighlight = highlightSegment.start - videoTime;
|
||||
if (timeUntilHighlight > 0) {
|
||||
if (highlightSegment.shouldAutoSkip()) {
|
||||
skipSegment(highlightSegment, false);
|
||||
return;
|
||||
}
|
||||
highlightSegmentInitialShowEndTime = System.currentTimeMillis() + Math.min(
|
||||
(long) (timeUntilHighlight / VideoInformation.getPlaybackSpeed()),
|
||||
DURATION_TO_SHOW_SKIP_BUTTON);
|
||||
Utils.runOnMainThread(() -> {
|
||||
if (!videoId.equals(currentVideoId)) {
|
||||
// user changed videos before get segments network call could complete
|
||||
Logger.printDebug(() -> "Ignoring segments for prior video: " + videoId);
|
||||
return;
|
||||
}
|
||||
setSegments(segments);
|
||||
|
||||
final long videoTime = VideoInformation.getVideoTime();
|
||||
if (highlightSegment != null) {
|
||||
// If the current video time is before the highlight.
|
||||
final long timeUntilHighlight = highlightSegment.start - videoTime;
|
||||
if (timeUntilHighlight > 0) {
|
||||
if (highlightSegment.shouldAutoSkip()) {
|
||||
skipSegment(highlightSegment, false);
|
||||
return;
|
||||
}
|
||||
highlightSegmentInitialShowEndTime = System.currentTimeMillis() + Math.min(
|
||||
(long) (timeUntilHighlight / VideoInformation.getPlaybackSpeed()),
|
||||
getSkipButtonDuration());
|
||||
}
|
||||
}
|
||||
|
||||
// check for any skips now, instead of waiting for the next update to setVideoTime()
|
||||
setVideoTime(videoTime);
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "executeDownloadSegments failure", ex);
|
||||
}
|
||||
// check for any skips now, instead of waiting for the next update to setVideoTime()
|
||||
setVideoTime(videoTime);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,8 +351,8 @@ public class SegmentPlaybackController {
|
||||
public static void setVideoTime(long millis) {
|
||||
try {
|
||||
if (!Settings.SB_ENABLED.get()
|
||||
|| PlayerType.getCurrent().isNoneOrHidden() // Shorts playback.
|
||||
|| segments == null || segments.length == 0) {
|
||||
|| PlayerType.getCurrent().isNoneOrHidden() // Shorts playback.
|
||||
|| segments == null || segments.length == 0) {
|
||||
return;
|
||||
}
|
||||
Logger.printDebug(() -> "setVideoTime: " + millis);
|
||||
@@ -290,7 +368,7 @@ public class SegmentPlaybackController {
|
||||
//
|
||||
// To debug the stale skip logic, set this to a very large value (5000 or more)
|
||||
// then try manually seeking just before playback reaches a segment skip.
|
||||
final long speedAdjustedTimeThreshold = (long)(playbackSpeed * 1200);
|
||||
final long speedAdjustedTimeThreshold = (long) (playbackSpeed * 1200);
|
||||
final long startTimerLookAheadThreshold = millis + speedAdjustedTimeThreshold;
|
||||
|
||||
SponsorSegment foundSegmentCurrentlyPlaying = null;
|
||||
@@ -298,22 +376,24 @@ public class SegmentPlaybackController {
|
||||
|
||||
for (final SponsorSegment segment : segments) {
|
||||
if (segment.category.behaviour == CategoryBehaviour.SHOW_IN_SEEKBAR
|
||||
|| segment.category.behaviour == CategoryBehaviour.IGNORE
|
||||
|| segment.category == SegmentCategory.HIGHLIGHT) {
|
||||
|| segment.category.behaviour == CategoryBehaviour.IGNORE
|
||||
|| segment.category == SegmentCategory.HIGHLIGHT) {
|
||||
continue;
|
||||
}
|
||||
if (segment.end <= millis) {
|
||||
continue; // past this segment
|
||||
continue; // Past this segment.
|
||||
}
|
||||
|
||||
final boolean segmentShouldAutoSkip = shouldAutoSkipAndUndoSkipNotActive(segment, millis);
|
||||
|
||||
if (segment.start <= millis) {
|
||||
// we are in the segment!
|
||||
if (segment.shouldAutoSkip()) {
|
||||
// We are in the segment!
|
||||
if (segmentShouldAutoSkip) {
|
||||
skipSegment(segment, false);
|
||||
return; // must return, as skipping causes a recursive call back into this method
|
||||
return; // Must return, as skipping causes a recursive call back into this method.
|
||||
}
|
||||
|
||||
// first found segment, or it's an embedded segment and fully inside the outer segment
|
||||
// First found segment, or it's an embedded segment and fully inside the outer 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.
|
||||
@@ -327,25 +407,27 @@ public class SegmentPlaybackController {
|
||||
}
|
||||
}
|
||||
// Keep iterating and looking. There may be an upcoming autoskip,
|
||||
// or there may be another smaller segment nested inside this segment
|
||||
// or there may be another smaller segment nested inside this segment.
|
||||
continue;
|
||||
}
|
||||
|
||||
// segment is upcoming
|
||||
// Segment is upcoming.
|
||||
if (startTimerLookAheadThreshold < segment.start) {
|
||||
break; // segment is not close enough to schedule, and no segments after this are of interest
|
||||
// Segment is not close enough to schedule, and no segments after this are of interest.
|
||||
break;
|
||||
}
|
||||
if (segment.shouldAutoSkip()) { // upcoming autoskip
|
||||
|
||||
if (segmentShouldAutoSkip) {
|
||||
foundUpcomingSegment = segment;
|
||||
break; // must stop here
|
||||
break; // Must stop here.
|
||||
}
|
||||
|
||||
// upcoming manual skip
|
||||
// Upcoming manual skip.
|
||||
|
||||
// do not schedule upcoming segment, if it is not fully contained inside the current segment
|
||||
// Do not schedule upcoming segment, if it is not fully contained inside the current segment.
|
||||
if ((foundSegmentCurrentlyPlaying == null || foundSegmentCurrentlyPlaying.containsSegment(segment))
|
||||
// use the most inner upcoming segment
|
||||
&& (foundUpcomingSegment == null || foundUpcomingSegment.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.
|
||||
@@ -361,8 +443,8 @@ public class SegmentPlaybackController {
|
||||
}
|
||||
|
||||
if (highlightSegment != null) {
|
||||
if (millis < DURATION_TO_SHOW_SKIP_BUTTON || (highlightSegmentInitialShowEndTime != 0
|
||||
&& System.currentTimeMillis() < highlightSegmentInitialShowEndTime)) {
|
||||
if (millis < getSkipButtonDuration() || (highlightSegmentInitialShowEndTime != 0
|
||||
&& System.currentTimeMillis() < highlightSegmentInitialShowEndTime)) {
|
||||
SponsorBlockViewController.showSkipHighlightButton(highlightSegment);
|
||||
} else {
|
||||
highlightSegmentInitialShowEndTime = 0;
|
||||
@@ -373,16 +455,17 @@ public class SegmentPlaybackController {
|
||||
if (segmentCurrentlyPlaying != foundSegmentCurrentlyPlaying) {
|
||||
setSegmentCurrentlyPlaying(foundSegmentCurrentlyPlaying);
|
||||
} else if (foundSegmentCurrentlyPlaying != null
|
||||
&& skipSegmentButtonEndTime != 0 && skipSegmentButtonEndTime <= System.currentTimeMillis()) {
|
||||
&& skipSegmentButtonEndTime != 0
|
||||
&& skipSegmentButtonEndTime <= System.currentTimeMillis()) {
|
||||
Logger.printDebug(() -> "Auto hiding skip button for segment: " + segmentCurrentlyPlaying);
|
||||
skipSegmentButtonEndTime = 0;
|
||||
hiddenSkipSegmentsForCurrentVideoTime.add(foundSegmentCurrentlyPlaying);
|
||||
SponsorBlockViewController.hideSkipSegmentButton();
|
||||
}
|
||||
|
||||
// schedule a hide, only if the segment end is near
|
||||
final SponsorSegment segmentToHide =
|
||||
(foundSegmentCurrentlyPlaying != null && foundSegmentCurrentlyPlaying.endIsNear(millis, speedAdjustedTimeThreshold))
|
||||
// Schedule a hide, but only if the segment end is near.
|
||||
final SponsorSegment segmentToHide = (foundSegmentCurrentlyPlaying != null &&
|
||||
foundSegmentCurrentlyPlaying.endIsNear(millis, speedAdjustedTimeThreshold))
|
||||
? foundSegmentCurrentlyPlaying
|
||||
: null;
|
||||
|
||||
@@ -407,7 +490,7 @@ public class SegmentPlaybackController {
|
||||
|
||||
final long videoTime = VideoInformation.getVideoTime();
|
||||
if (!segmentToHide.endIsNear(videoTime, speedAdjustedTimeThreshold)) {
|
||||
// current video time is not what's expected. User paused playback
|
||||
// Current video time is not what's expected. User paused playback.
|
||||
Logger.printDebug(() -> "Ignoring outdated scheduled hide: " + segmentToHide
|
||||
+ " videoInformation time: " + videoTime);
|
||||
return;
|
||||
@@ -416,7 +499,7 @@ public class SegmentPlaybackController {
|
||||
// Need more than just hide the skip button, as this may have been an embedded segment
|
||||
// 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
|
||||
// but this scheduled handler was scheduled precisely so we can just use the segment end time.
|
||||
setSegmentCurrentlyPlaying(null);
|
||||
setVideoTime(segmentToHide.end);
|
||||
}, delayUntilHide);
|
||||
@@ -446,12 +529,12 @@ public class SegmentPlaybackController {
|
||||
|
||||
final long videoTime = VideoInformation.getVideoTime();
|
||||
if (!segmentToSkip.startIsNear(videoTime, speedAdjustedTimeThreshold)) {
|
||||
// current video time is not what's expected. User paused playback
|
||||
// Current video time is not what's expected. User paused playback.
|
||||
Logger.printDebug(() -> "Ignoring outdated scheduled segment: " + segmentToSkip
|
||||
+ " videoInformation time: " + videoTime);
|
||||
return;
|
||||
}
|
||||
if (segmentToSkip.shouldAutoSkip()) {
|
||||
if (shouldAutoSkipAndUndoSkipNotActive(segmentToSkip, videoTime)) {
|
||||
Logger.printDebug(() -> "Running scheduled skip segment: " + segmentToSkip);
|
||||
skipSegment(segmentToSkip, false);
|
||||
} else {
|
||||
@@ -461,6 +544,12 @@ public class SegmentPlaybackController {
|
||||
}, delayUntilSkip);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear undo range if video time is outside the segment. Must check last.
|
||||
if (undoAutoSkipRange != null && !undoAutoSkipRange.contains(millis)) {
|
||||
Logger.printDebug(() -> "Clearing undo range as current time is now outside range: " + undoAutoSkipRange);
|
||||
undoAutoSkipRange = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.printException(() -> "setVideoTime failure", e);
|
||||
}
|
||||
@@ -470,14 +559,13 @@ 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<SponsorSegment> i = hiddenSkipSegmentsForCurrentVideoTime.iterator();
|
||||
while (i.hasNext()) {
|
||||
SponsorSegment hiddenSegment = i.next();
|
||||
hiddenSkipSegmentsForCurrentVideoTime.removeIf((hiddenSegment) -> {
|
||||
if (!hiddenSegment.containsTime(currentVideoTime)) {
|
||||
Logger.printDebug(() -> "Resetting hide skip button: " + hiddenSegment);
|
||||
i.remove();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private static void setSegmentCurrentlyPlaying(@Nullable SponsorSegment segment) {
|
||||
@@ -488,8 +576,10 @@ public class SegmentPlaybackController {
|
||||
SponsorBlockViewController.hideSkipSegmentButton();
|
||||
return;
|
||||
}
|
||||
|
||||
segmentCurrentlyPlaying = segment;
|
||||
skipSegmentButtonEndTime = 0;
|
||||
|
||||
if (Settings.SB_AUTO_HIDE_SKIP_BUTTON.get()) {
|
||||
if (hiddenSkipSegmentsForCurrentVideoTime.contains(segment)) {
|
||||
// Playback exited a nested segment and the outer segment skip button was previously hidden.
|
||||
@@ -497,16 +587,13 @@ public class SegmentPlaybackController {
|
||||
SponsorBlockViewController.hideSkipSegmentButton();
|
||||
return;
|
||||
}
|
||||
skipSegmentButtonEndTime = System.currentTimeMillis() + DURATION_TO_SHOW_SKIP_BUTTON;
|
||||
skipSegmentButtonEndTime = System.currentTimeMillis() + getSkipButtonDuration();
|
||||
}
|
||||
Logger.printDebug(() -> "Showing segment: " + segment);
|
||||
SponsorBlockViewController.showSkipSegmentButton(segment);
|
||||
}
|
||||
|
||||
private static SponsorSegment lastSegmentSkipped;
|
||||
private static long lastSegmentSkippedTime;
|
||||
|
||||
private static void skipSegment(@NonNull SponsorSegment segmentToSkip, boolean userManuallySkipped) {
|
||||
private static void skipSegment(SponsorSegment segmentToSkip, boolean userManuallySkipped) {
|
||||
try {
|
||||
SponsorBlockViewController.hideSkipHighlightButton();
|
||||
SponsorBlockViewController.hideSkipSegmentButton();
|
||||
@@ -525,7 +612,7 @@ public class SegmentPlaybackController {
|
||||
}
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "Skipping segment: " + segmentToSkip);
|
||||
Logger.printDebug(() -> "Skipping segment: " + segmentToSkip + " videoState: " + VideoState.getCurrent());
|
||||
lastSegmentSkipped = segmentToSkip;
|
||||
lastSegmentSkippedTime = now;
|
||||
setSegmentCurrentlyPlaying(null);
|
||||
@@ -535,29 +622,39 @@ public class SegmentPlaybackController {
|
||||
highlightSegmentInitialShowEndTime = 0;
|
||||
}
|
||||
|
||||
// Set or update undo skip range.
|
||||
Range<Long> range = segmentToSkip.getUndoRange();
|
||||
if (undoAutoSkipRange == null) {
|
||||
Logger.printDebug(() -> "Setting new undo range to: " + range);
|
||||
undoAutoSkipRange = range;
|
||||
} else {
|
||||
Range<Long> extendedRange = undoAutoSkipRange.extend(range);
|
||||
Logger.printDebug(() -> "Extending undo range from: " + undoAutoSkipRange +
|
||||
" to: " + extendedRange);
|
||||
undoAutoSkipRange = extendedRange;
|
||||
}
|
||||
undoAutoSkipRangeToast = undoAutoSkipRange;
|
||||
|
||||
// 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
|
||||
// Can happen when switching videos and is normal.
|
||||
Logger.printDebug(() -> "Could not skip segment (seek unsuccessful): " + segmentToSkip);
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean videoIsPaused = VideoState.getCurrent() == VideoState.PAUSED;
|
||||
if (!userManuallySkipped) {
|
||||
// check for any smaller embedded segments, and count those as autoskipped
|
||||
// Check for any smaller embedded segments, and count those as auto-skipped.
|
||||
final boolean showSkipToast = Settings.SB_TOAST_ON_SKIP.get();
|
||||
for (final SponsorSegment otherSegment : Objects.requireNonNull(segments)) {
|
||||
for (SponsorSegment otherSegment : Objects.requireNonNull(segments)) {
|
||||
if (segmentToSkip.end < otherSegment.start) {
|
||||
break; // no other segments can be contained
|
||||
break; // No other segments can be contained.
|
||||
}
|
||||
|
||||
if (otherSegment == segmentToSkip ||
|
||||
(otherSegment.category != SegmentCategory.HIGHLIGHT && segmentToSkip.containsSegment(otherSegment))) {
|
||||
otherSegment.didAutoSkipped = true;
|
||||
// Do not show a toast if the user is scrubbing thru a paused video.
|
||||
// Cannot do this video state check in setTime or earlier in this method, as the video state may not be up to date.
|
||||
// So instead, only hide toasts because all other skip logic done while paused causes no harm.
|
||||
if (showSkipToast && !videoIsPaused) {
|
||||
if (showSkipToast) {
|
||||
showSkippedSegmentToast(otherSegment);
|
||||
}
|
||||
}
|
||||
@@ -567,7 +664,7 @@ public class SegmentPlaybackController {
|
||||
if (segmentToSkip.category == SegmentCategory.UNSUBMITTED) {
|
||||
removeUnsubmittedSegments();
|
||||
SponsorBlockUtils.setNewSponsorSegmentPreviewed();
|
||||
} else if (!videoIsPaused) {
|
||||
} else if (VideoState.getCurrent() != VideoState.PAUSED) {
|
||||
SponsorBlockUtils.sendViewRequestAsync(segmentToSkip);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
@@ -575,29 +672,44 @@ public class SegmentPlaybackController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the segment should be auto-skipped _and_ if undo autoskip is not active.
|
||||
*/
|
||||
private static boolean shouldAutoSkipAndUndoSkipNotActive(SponsorSegment segment, long currentVideoTime) {
|
||||
return segment.shouldAutoSkip() && (undoAutoSkipRange == null
|
||||
|| !undoAutoSkipRange.contains(currentVideoTime));
|
||||
}
|
||||
|
||||
private static int toastNumberOfSegmentsSkipped;
|
||||
@Nullable
|
||||
private static SponsorSegment toastSegmentSkipped;
|
||||
|
||||
private static void showSkippedSegmentToast(@NonNull SponsorSegment segment) {
|
||||
private static void showSkippedSegmentToast(SponsorSegment segment) {
|
||||
Utils.verifyOnMainThread();
|
||||
toastNumberOfSegmentsSkipped++;
|
||||
if (toastNumberOfSegmentsSkipped > 1) {
|
||||
return; // toast already scheduled
|
||||
}
|
||||
toastSegmentSkipped = segment;
|
||||
if (toastNumberOfSegmentsSkipped++ > 0) {
|
||||
return; // Toast is already scheduled.
|
||||
}
|
||||
|
||||
final long delayToToastMilliseconds = 250; // also the maximum time between skips to be considered skipping multiple segments
|
||||
// Maximum time between skips to be considered skipping multiple segments.
|
||||
final long delayToToastMilliseconds = 250;
|
||||
Utils.runOnMainThreadDelayed(() -> {
|
||||
try {
|
||||
if (toastSegmentSkipped == null) { // video was changed just after skipping segment
|
||||
// Do not show a toast if the user is scrubbing thru a paused video.
|
||||
// Cannot do this video state check in setTime or before calling this this method,
|
||||
// as the video state may not be up to date. So instead, only ignore the toast
|
||||
// just before it's about to show since the video state is up to date.
|
||||
if (VideoState.getCurrent() == VideoState.PAUSED) {
|
||||
Logger.printDebug(() -> "Ignoring scheduled toast as video state is paused");
|
||||
return;
|
||||
}
|
||||
|
||||
if (toastSegmentSkipped == null || undoAutoSkipRangeToast == null) {
|
||||
// Video was changed immediately after skipping segment.
|
||||
Logger.printDebug(() -> "Ignoring old scheduled show toast");
|
||||
return;
|
||||
}
|
||||
Utils.showToastShort(toastNumberOfSegmentsSkipped == 1
|
||||
String message = toastNumberOfSegmentsSkipped == 1
|
||||
? toastSegmentSkipped.getSkippedToastText()
|
||||
: str("revanced_sb_skipped_multiple_segments"));
|
||||
: str("revanced_sb_skipped_multiple_segments");
|
||||
|
||||
showToastShortWithTapAction(message, undoAutoSkipRangeToast);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "showSkippedSegmentToast failure", ex);
|
||||
} finally {
|
||||
@@ -607,13 +719,128 @@ public class SegmentPlaybackController {
|
||||
}, delayToToastMilliseconds);
|
||||
}
|
||||
|
||||
private static void showToastShortWithTapAction(String messageToToast, Range<Long> rangeToUndo) {
|
||||
Objects.requireNonNull(messageToToast);
|
||||
Utils.verifyOnMainThread();
|
||||
|
||||
Context currentContext = SponsorBlockViewController.getOverLaysViewGroupContext();
|
||||
if (currentContext == null) {
|
||||
Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "Showing toast: " + messageToToast);
|
||||
|
||||
Dialog dialog = new Dialog(currentContext);
|
||||
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
// Do not dismiss dialog if tapped outside the dialog bounds.
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
|
||||
LinearLayout mainLayout = new LinearLayout(currentContext);
|
||||
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
final int dip8 = dipToPixels(8);
|
||||
final int dip16 = dipToPixels(16);
|
||||
mainLayout.setPadding(dip16, dip8, dip16, dip8);
|
||||
mainLayout.setGravity(Gravity.CENTER);
|
||||
mainLayout.setMinimumHeight(dipToPixels(48));
|
||||
|
||||
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
|
||||
Utils.createCornerRadii(20), null, null));
|
||||
background.getPaint().setColor(Utils.getDialogBackgroundColor());
|
||||
mainLayout.setBackground(background);
|
||||
|
||||
TextView textView = new TextView(currentContext);
|
||||
textView.setText(messageToToast);
|
||||
textView.setTextSize(14);
|
||||
textView.setTextColor(Utils.getAppForegroundColor());
|
||||
textView.setGravity(Gravity.CENTER);
|
||||
LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
);
|
||||
textParams.gravity = Gravity.CENTER;
|
||||
textView.setLayoutParams(textParams);
|
||||
mainLayout.addView(textView);
|
||||
mainLayout.setAlpha(0.8f); // Opacity for the entire dialog.
|
||||
|
||||
final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast");
|
||||
Animation fadeIn = Utils.getResourceAnimation("fade_in");
|
||||
Animation fadeOut = Utils.getResourceAnimation("fade_out");
|
||||
fadeIn.setDuration(fadeDurationFast);
|
||||
fadeOut.setDuration(fadeDurationFast);
|
||||
fadeOut.setAnimationListener(new Animation.AnimationListener() {
|
||||
public void onAnimationStart(Animation animation) { }
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
if (dialog.isShowing()) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
public void onAnimationRepeat(Animation animation) { }
|
||||
});
|
||||
|
||||
mainLayout.setOnClickListener(v -> {
|
||||
try {
|
||||
Logger.printDebug(() -> "Undoing autoskip using range: " + rangeToUndo);
|
||||
// Restore undo autoskip range since it's already cleared by now.
|
||||
undoAutoSkipRange = rangeToUndo;
|
||||
VideoInformation.seekTo(rangeToUndo.getLower());
|
||||
|
||||
mainLayout.startAnimation(fadeOut);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "showToastShortWithTapAction setOnClickListener failure", ex);
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
mainLayout.setClickable(true);
|
||||
dialog.setContentView(mainLayout);
|
||||
|
||||
Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
// Remove window animations and use custom fade animation.
|
||||
window.setWindowAnimations(0);
|
||||
|
||||
WindowManager.LayoutParams params = window.getAttributes();
|
||||
params.gravity = Gravity.BOTTOM;
|
||||
params.y = dipToPixels(72);
|
||||
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
|
||||
int portraitWidth = (int) (displayMetrics.widthPixels * 0.6);
|
||||
|
||||
if (Resources.getSystem().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
portraitWidth = (int) Math.min(portraitWidth, displayMetrics.heightPixels * 0.6);
|
||||
}
|
||||
params.width = portraitWidth;
|
||||
params.dimAmount = 0.0f;
|
||||
window.setAttributes(params);
|
||||
window.setBackgroundDrawable(null);
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
|
||||
}
|
||||
|
||||
Dialog priorDialog = toastDialogRef.get();
|
||||
if (priorDialog != null && priorDialog.isShowing()) {
|
||||
Logger.printDebug(() -> "Removing previous skip toast that is still on screen: " + priorDialog);
|
||||
priorDialog.dismiss();
|
||||
}
|
||||
toastDialogRef = new WeakReference<>(dialog);
|
||||
|
||||
mainLayout.startAnimation(fadeIn);
|
||||
dialog.show();
|
||||
|
||||
// Fade out and dismiss the dialog if the user does not undo the skip.
|
||||
Utils.runOnMainThreadDelayed(() -> {
|
||||
if (dialog.isShowing()) {
|
||||
mainLayout.startAnimation(fadeOut);
|
||||
}
|
||||
}, getToastDuration());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param segment can be either a highlight or a regular manual skip segment.
|
||||
*/
|
||||
public static void onSkipSegmentClicked(@NonNull SponsorSegment segment) {
|
||||
public static void onSkipSegmentClicked(SponsorSegment segment) {
|
||||
try {
|
||||
if (segment != highlightSegment && segment != segmentCurrentlyPlaying) {
|
||||
Logger.printException(() -> "error: segment not available to skip"); // should never happen
|
||||
Logger.printException(() -> "error: segment not available to skip"); // Should never happen.
|
||||
SponsorBlockViewController.hideSkipSegmentButton();
|
||||
SponsorBlockViewController.hideSkipHighlightButton();
|
||||
return;
|
||||
@@ -628,7 +855,7 @@ public class SegmentPlaybackController {
|
||||
* Injection point
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void setSponsorBarRect(final Object self) {
|
||||
public static void setSponsorBarRect(Object self) {
|
||||
try {
|
||||
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
|
||||
field.setAccessible(true);
|
||||
@@ -651,7 +878,7 @@ public class SegmentPlaybackController {
|
||||
private static void setSponsorBarAbsoluteRight(Rect rect) {
|
||||
final int right = rect.right;
|
||||
if (sponsorAbsoluteBarRight != right) {
|
||||
Logger.printDebug(() -> "setSponsorBarAbsoluteRight: " + right);
|
||||
Logger.printDebug(() -> "setSponsorBarAbsoluteRight: " + right);
|
||||
sponsorAbsoluteBarRight = right;
|
||||
}
|
||||
}
|
||||
@@ -726,12 +953,6 @@ public class SegmentPlaybackController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actual screen pixel width to use for the highlight segment time bar.
|
||||
*/
|
||||
private static final int highlightSegmentTimeBarScreenWidth
|
||||
= Utils.dipToPixels(HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH);
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@@ -752,9 +973,9 @@ public class SegmentPlaybackController {
|
||||
final float left = leftPadding + segment.start * videoMillisecondsToPixels;
|
||||
final float right;
|
||||
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
||||
right = left + highlightSegmentTimeBarScreenWidth;
|
||||
right = left + HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH;
|
||||
} else {
|
||||
right = leftPadding + segment.end * videoMillisecondsToPixels;
|
||||
right = leftPadding + segment.end * videoMillisecondsToPixels;
|
||||
}
|
||||
canvas.drawRect(left, top, right, bottom, segment.category.paint);
|
||||
}
|
||||
@@ -762,5 +983,4 @@ public class SegmentPlaybackController {
|
||||
Logger.printException(() -> "drawSponsorTimeBars failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -223,13 +223,18 @@ public class SponsorBlockUtils {
|
||||
Logger.printException(() -> "invalid parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
clearUnsubmittedSegmentTimes();
|
||||
Utils.runOnBackgroundThread(() -> {
|
||||
SBRequester.submitSegments(videoId, segmentCategory.keyValue, start, end, videoLength);
|
||||
SegmentPlaybackController.executeDownloadSegments(videoId);
|
||||
try {
|
||||
SBRequester.submitSegments(videoId, segmentCategory.keyValue, start, end, videoLength);
|
||||
SegmentPlaybackController.executeDownloadSegments(videoId);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "submitNewSegment failure", ex);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Logger.printException(() -> "Unable to submit segment", e);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "submitNewSegment failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,7 +371,7 @@ public class SponsorBlockUtils {
|
||||
}
|
||||
|
||||
|
||||
static void sendViewRequestAsync(@NonNull SponsorSegment segment) {
|
||||
static void sendViewRequestAsync(SponsorSegment segment) {
|
||||
if (segment.recordedAsSkipped || segment.category == SegmentCategory.UNSUBMITTED) {
|
||||
return;
|
||||
}
|
||||
@@ -409,7 +414,7 @@ public class SponsorBlockUtils {
|
||||
return statsNumberFormatter.format(viewCount);
|
||||
}
|
||||
|
||||
private static long parseSegmentTime(@NonNull String time) {
|
||||
private static long parseSegmentTime(String time) {
|
||||
Matcher matcher = manualEditTimePattern.matcher(time);
|
||||
if (!matcher.matches()) {
|
||||
return -1;
|
||||
@@ -419,9 +424,12 @@ public class SponsorBlockUtils {
|
||||
String secondsStr = matcher.group(4);
|
||||
String millisecondsStr = matcher.group(6); // Milliseconds is optional.
|
||||
|
||||
|
||||
try {
|
||||
final int hours = (hoursStr != null) ? Integer.parseInt(hoursStr) : 0;
|
||||
//noinspection ConstantConditions
|
||||
final int minutes = Integer.parseInt(minutesStr);
|
||||
//noinspection ConstantConditions
|
||||
final int seconds = Integer.parseInt(secondsStr);
|
||||
final int milliseconds;
|
||||
if (millisecondsStr != null) {
|
||||
@@ -468,32 +476,29 @@ public class SponsorBlockUtils {
|
||||
}
|
||||
|
||||
public static String getTimeSavedString(long totalSecondsSaved) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
Duration duration = Duration.ofSeconds(totalSecondsSaved);
|
||||
final long hours = duration.toHours();
|
||||
final long minutes = duration.toMinutes() % 60;
|
||||
Duration duration = Duration.ofSeconds(totalSecondsSaved);
|
||||
final long hours = duration.toHours();
|
||||
final long minutes = duration.toMinutes() % 60;
|
||||
|
||||
// Format all numbers so non-western numbers use a consistent appearance.
|
||||
String minutesFormatted = statsNumberFormatter.format(minutes);
|
||||
if (hours > 0) {
|
||||
String hoursFormatted = statsNumberFormatter.format(hours);
|
||||
return str("revanced_sb_stats_saved_hour_format", hoursFormatted, minutesFormatted);
|
||||
}
|
||||
|
||||
final long seconds = duration.getSeconds() % 60;
|
||||
String secondsFormatted = statsNumberFormatter.format(seconds);
|
||||
if (minutes > 0) {
|
||||
return str("revanced_sb_stats_saved_minute_format", minutesFormatted, secondsFormatted);
|
||||
}
|
||||
|
||||
return str("revanced_sb_stats_saved_second_format", secondsFormatted);
|
||||
// Format all numbers so non-western numbers use a consistent appearance.
|
||||
String minutesFormatted = statsNumberFormatter.format(minutes);
|
||||
if (hours > 0) {
|
||||
String hoursFormatted = statsNumberFormatter.format(hours);
|
||||
return str("revanced_sb_stats_saved_hour_format", hoursFormatted, minutesFormatted);
|
||||
}
|
||||
return "error"; // will never be reached. YouTube requires Android O or greater
|
||||
|
||||
final long seconds = duration.getSeconds() % 60;
|
||||
String secondsFormatted = statsNumberFormatter.format(seconds);
|
||||
if (minutes > 0) {
|
||||
return str("revanced_sb_stats_saved_minute_format", minutesFormatted, secondsFormatted);
|
||||
}
|
||||
|
||||
return str("revanced_sb_stats_saved_second_format", secondsFormatted);
|
||||
}
|
||||
|
||||
private static class EditByHandSaveDialogListener implements DialogInterface.OnClickListener {
|
||||
boolean settingStart;
|
||||
WeakReference<EditText> editTextRef = new WeakReference<>(null);
|
||||
private boolean settingStart;
|
||||
private WeakReference<EditText> editTextRef = new WeakReference<>(null);
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
@@ -512,10 +517,11 @@ public class SponsorBlockUtils {
|
||||
}
|
||||
}
|
||||
|
||||
if (settingStart)
|
||||
if (settingStart) {
|
||||
newSponsorSegmentStartMillis = Math.max(time, 0);
|
||||
else
|
||||
} else {
|
||||
newSponsorSegmentEndMillis = time;
|
||||
}
|
||||
|
||||
if (which == DialogInterface.BUTTON_NEUTRAL)
|
||||
editByHandDialogListener.onClick(dialog, settingStart ?
|
||||
|
||||
@@ -9,7 +9,10 @@ import java.util.Objects;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.sf;
|
||||
|
||||
import android.util.Range;
|
||||
|
||||
public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||
|
||||
public enum SegmentVote {
|
||||
UPVOTE(sf("revanced_sb_vote_upvote"), 1,false),
|
||||
DOWNVOTE(sf("revanced_sb_vote_downvote"), 0, true),
|
||||
@@ -38,7 +41,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||
@NonNull
|
||||
public final SegmentCategory category;
|
||||
/**
|
||||
* NULL if segment is unsubmitted
|
||||
* NULL if segment is unsubmitted.
|
||||
*/
|
||||
@Nullable
|
||||
public final String UUID;
|
||||
@@ -64,33 +67,54 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nearThreshold threshold to declare the time parameter is near this segment. Must be a positive number
|
||||
* @param nearThreshold threshold to declare the time parameter is near this segment. Must be a positive number.
|
||||
*/
|
||||
public boolean startIsNear(long videoTime, long nearThreshold) {
|
||||
return Math.abs(start - videoTime) <= nearThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nearThreshold threshold to declare the time parameter is near this segment. Must be a positive number
|
||||
* @param nearThreshold threshold to declare the time parameter is near this segment. Must be a positive number.
|
||||
*/
|
||||
public boolean endIsNear(long videoTime, long nearThreshold) {
|
||||
return Math.abs(end - videoTime) <= nearThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the time parameter is within this segment
|
||||
* @return if the time parameter is within this segment.
|
||||
*/
|
||||
public boolean containsTime(long videoTime) {
|
||||
return start <= videoTime && videoTime < end;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the segment is completely contained inside this segment
|
||||
* @return if the segment is completely contained inside this segment.
|
||||
*/
|
||||
public boolean containsSegment(SponsorSegment other) {
|
||||
return start <= other.start && other.end <= end;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the range has any overlap with this segment.
|
||||
*/
|
||||
public boolean intersectsRange(Range<Long> range) {
|
||||
return range.getLower() < end && range.getUpper() >= start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The start/end time in range form.
|
||||
* Range times are adjusted since it uses inclusive and Segments use exclusive.
|
||||
*
|
||||
* {@link SegmentCategory#HIGHLIGHT} is unique and
|
||||
* returns a range from the start of the video until the highlight.
|
||||
*/
|
||||
public Range<Long> getUndoRange() {
|
||||
final long undoStart = category == SegmentCategory.HIGHLIGHT
|
||||
? 0
|
||||
: start;
|
||||
return Range.create(undoStart, end - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length of this segment, in milliseconds. Always a positive number.
|
||||
*/
|
||||
@@ -99,7 +123,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 'skip segment' UI overlay button text
|
||||
* @return 'skip segment' UI overlay button text.
|
||||
*/
|
||||
@NonNull
|
||||
public String getSkipButtonText() {
|
||||
@@ -107,7 +131,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 'skipped segment' toast message
|
||||
* @return 'skipped segment' toast message.
|
||||
*/
|
||||
@NonNull
|
||||
public String getSkippedToastText() {
|
||||
|
||||
@@ -53,7 +53,7 @@ public class SBRequester {
|
||||
private SBRequester() {
|
||||
}
|
||||
|
||||
private static void handleConnectionError(@NonNull String toastMessage, @Nullable Exception ex) {
|
||||
private static void handleConnectionError(String toastMessage, @Nullable Exception ex) {
|
||||
if (Settings.SB_TOAST_ON_CONNECTION_ERROR.get()) {
|
||||
Utils.showToastShort(toastMessage);
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public class SBRequester {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static SponsorSegment[] getSegments(@NonNull String videoId) {
|
||||
public static SponsorSegment[] getSegments(String videoId) {
|
||||
Utils.verifyOffMainThread();
|
||||
List<SponsorSegment> segments = new ArrayList<>();
|
||||
try {
|
||||
@@ -113,10 +113,10 @@ public class SBRequester {
|
||||
Logger.printException(() -> "getSegments failure", ex);
|
||||
}
|
||||
|
||||
// Crude debug tests to verify random features
|
||||
// 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)
|
||||
// 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).
|
||||
//noinspection ConstantValue
|
||||
if (false) {
|
||||
segments.clear();
|
||||
@@ -140,10 +140,30 @@ public class SBRequester {
|
||||
segments.add(new SponsorSegment(SegmentCategory.SELF_PROMO, "debug", 200000, 330000, false));
|
||||
}
|
||||
|
||||
// Test undo skip functionality.
|
||||
// To test enable 'Autoskip always' for intro and self promo.
|
||||
//noinspection ConstantValue
|
||||
if (false) {
|
||||
// Should autoskip to 12 seconds.
|
||||
// Undoing skip should seek to 2 seconds.
|
||||
// Skip button should show at 2 seconds, and again at 8 seconds.
|
||||
// Self promo at 8 second time should not autoskip.
|
||||
segments.clear();
|
||||
segments.add(new SponsorSegment(SegmentCategory.INTRO, "debug", 2000, 12000, false));
|
||||
segments.add(new SponsorSegment(SegmentCategory.SELF_PROMO, "debug", 8000, 15000, false));
|
||||
|
||||
// Test multiple autoskip dialogs rapidly showing.
|
||||
// Only one toast should be shown at anytime.
|
||||
segments.add(new SponsorSegment(SegmentCategory.INTRO, "debug", 16000, 17000, false));
|
||||
segments.add(new SponsorSegment(SegmentCategory.INTRO, "debug", 18000, 19000, false));
|
||||
segments.add(new SponsorSegment(SegmentCategory.INTRO, "debug", 20000, 21000, false));
|
||||
segments.add(new SponsorSegment(SegmentCategory.INTRO, "debug", 22000, 23000, false));
|
||||
}
|
||||
|
||||
return segments.toArray(new SponsorSegment[0]);
|
||||
}
|
||||
|
||||
public static void submitSegments(@NonNull String videoId, @NonNull String category,
|
||||
public static void submitSegments(String videoId, String category,
|
||||
long startTime, long endTime, long videoLength) {
|
||||
Utils.verifyOffMainThread();
|
||||
|
||||
@@ -189,7 +209,7 @@ public class SBRequester {
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendSegmentSkippedViewedRequest(@NonNull SponsorSegment segment) {
|
||||
public static void sendSegmentSkippedViewedRequest(SponsorSegment segment) {
|
||||
Utils.verifyOffMainThread();
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.VIEWED_SEGMENT, segment.UUID);
|
||||
@@ -208,13 +228,13 @@ public class SBRequester {
|
||||
}
|
||||
}
|
||||
|
||||
public static void voteForSegmentOnBackgroundThread(@NonNull SponsorSegment segment, @NonNull SegmentVote voteOption) {
|
||||
public static void voteForSegmentOnBackgroundThread(SponsorSegment segment, SegmentVote voteOption) {
|
||||
voteOrRequestCategoryChange(segment, voteOption, null);
|
||||
}
|
||||
public static void voteToChangeCategoryOnBackgroundThread(@NonNull SponsorSegment segment, @NonNull SegmentCategory categoryToVoteFor) {
|
||||
public static void voteToChangeCategoryOnBackgroundThread(SponsorSegment segment, SegmentCategory categoryToVoteFor) {
|
||||
voteOrRequestCategoryChange(segment, SegmentVote.CATEGORY_CHANGE, categoryToVoteFor);
|
||||
}
|
||||
private static void voteOrRequestCategoryChange(@NonNull SponsorSegment segment, @NonNull SegmentVote voteOption, SegmentCategory categoryToVoteFor) {
|
||||
private static void voteOrRequestCategoryChange(SponsorSegment segment, SegmentVote voteOption, SegmentCategory categoryToVoteFor) {
|
||||
Utils.runOnBackgroundThread(() -> {
|
||||
try {
|
||||
String segmentUuid = segment.UUID;
|
||||
@@ -280,7 +300,7 @@ public class SBRequester {
|
||||
* @return NULL if the call was successful. If unsuccessful, an error message is returned.
|
||||
*/
|
||||
@Nullable
|
||||
public static String setUsername(@NonNull String username) {
|
||||
public static String setUsername(String username) {
|
||||
Utils.verifyOffMainThread();
|
||||
try {
|
||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.CHANGE_USERNAME, SponsorBlockSettings.getSBPrivateUserID(), username);
|
||||
@@ -320,14 +340,14 @@ public class SBRequester {
|
||||
|
||||
// helpers
|
||||
|
||||
private static HttpURLConnection getConnectionFromRoute(@NonNull Route route, String... params) throws IOException {
|
||||
private static HttpURLConnection getConnectionFromRoute(Route route, String... params) throws IOException {
|
||||
HttpURLConnection connection = Requester.getConnectionFromRoute(Settings.SB_API_URL.get(), route, params);
|
||||
connection.setConnectTimeout(TIMEOUT_TCP_DEFAULT_MILLISECONDS);
|
||||
connection.setReadTimeout(TIMEOUT_HTTP_DEFAULT_MILLISECONDS);
|
||||
return connection;
|
||||
}
|
||||
|
||||
private static JSONObject getJSONObject(@NonNull Route route, String... params) throws IOException, JSONException {
|
||||
private static JSONObject getJSONObject(Route route, String... params) throws IOException, JSONException {
|
||||
return Requester.parseJSONObject(getConnectionFromRoute(route, params));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.revanced.extension.youtube.sponsorblock.ui;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController.SponsorBlockDuration;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
@@ -28,6 +29,7 @@ import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||
import app.revanced.extension.shared.settings.preference.ResettableEditTextPreference;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
|
||||
@@ -62,6 +64,8 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
private SwitchPreference trackSkips;
|
||||
private SwitchPreference showTimeWithoutSegments;
|
||||
private SwitchPreference toastOnConnectionError;
|
||||
private CustomDialogListPreference autoHideSkipSegmentButtonDuration;
|
||||
private CustomDialogListPreference showSkipToastDuration;
|
||||
|
||||
private ResettableEditTextPreference newSegmentStep;
|
||||
private ResettableEditTextPreference minSegmentDuration;
|
||||
@@ -69,8 +73,8 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
private EditTextPreference importExport;
|
||||
private Preference apiUrl;
|
||||
|
||||
private final List<SegmentCategoryListPreference> segmentCategories = new ArrayList<>();
|
||||
private PreferenceCategory segmentCategory;
|
||||
private final List<SegmentCategoryListPreference> segmentCategories = new ArrayList<>();
|
||||
|
||||
public SponsorBlockPreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
@@ -114,17 +118,23 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
votingEnabled.setChecked(Settings.SB_VOTING_BUTTON.get());
|
||||
votingEnabled.setEnabled(enabled);
|
||||
|
||||
autoHideSkipSegmentButton.setEnabled(enabled);
|
||||
autoHideSkipSegmentButton.setChecked(Settings.SB_AUTO_HIDE_SKIP_BUTTON.get());
|
||||
autoHideSkipSegmentButton.setEnabled(enabled);
|
||||
|
||||
autoHideSkipSegmentButtonDuration.setValue(Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION.get().toString());
|
||||
autoHideSkipSegmentButtonDuration.setEnabled(Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION.isAvailable());
|
||||
|
||||
compactSkipButton.setChecked(Settings.SB_COMPACT_SKIP_BUTTON.get());
|
||||
compactSkipButton.setEnabled(enabled);
|
||||
|
||||
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
|
||||
showSkipToast.setEnabled(enabled);
|
||||
|
||||
squareLayout.setChecked(Settings.SB_SQUARE_LAYOUT.get());
|
||||
squareLayout.setEnabled(enabled);
|
||||
|
||||
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
|
||||
showSkipToast.setEnabled(enabled);
|
||||
showSkipToastDuration.setValue(Settings.SB_TOAST_ON_SKIP_DURATION.get().toString());
|
||||
showSkipToastDuration.setEnabled(Settings.SB_TOAST_ON_SKIP_DURATION.isAvailable());
|
||||
|
||||
toastOnConnectionError.setChecked(Settings.SB_TOAST_ON_CONNECTION_ERROR.get());
|
||||
toastOnConnectionError.setEnabled(enabled);
|
||||
@@ -166,7 +176,7 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
try {
|
||||
super.onAttachedToActivity();
|
||||
|
||||
if (preferencesInitialized) {
|
||||
if (preferencesInitialized) {
|
||||
if (settingsImported) {
|
||||
settingsImported = false;
|
||||
updateUI();
|
||||
@@ -205,17 +215,6 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
});
|
||||
appearanceCategory.addPreference(votingEnabled);
|
||||
|
||||
autoHideSkipSegmentButton = new SwitchPreference(context);
|
||||
autoHideSkipSegmentButton.setTitle(str("revanced_sb_enable_auto_hide_skip_segment_button"));
|
||||
autoHideSkipSegmentButton.setSummaryOn(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_on"));
|
||||
autoHideSkipSegmentButton.setSummaryOff(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_off"));
|
||||
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
|
||||
updateUI();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(autoHideSkipSegmentButton);
|
||||
|
||||
compactSkipButton = new SwitchPreference(context);
|
||||
compactSkipButton.setTitle(str("revanced_sb_enable_compact_skip_button"));
|
||||
compactSkipButton.setSummaryOn(str("revanced_sb_enable_compact_skip_button_sum_on"));
|
||||
@@ -227,25 +226,38 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
});
|
||||
appearanceCategory.addPreference(compactSkipButton);
|
||||
|
||||
squareLayout = new SwitchPreference(context);
|
||||
squareLayout.setTitle(str("revanced_sb_square_layout"));
|
||||
squareLayout.setSummaryOn(str("revanced_sb_square_layout_sum_on"));
|
||||
squareLayout.setSummaryOff(str("revanced_sb_square_layout_sum_off"));
|
||||
squareLayout.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_SQUARE_LAYOUT.save((Boolean) newValue);
|
||||
autoHideSkipSegmentButton = new SwitchPreference(context);
|
||||
autoHideSkipSegmentButton.setTitle(str("revanced_sb_enable_auto_hide_skip_segment_button"));
|
||||
autoHideSkipSegmentButton.setSummaryOn(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_on"));
|
||||
autoHideSkipSegmentButton.setSummaryOff(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_off"));
|
||||
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
|
||||
updateUI();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(squareLayout);
|
||||
appearanceCategory.addPreference(autoHideSkipSegmentButton);
|
||||
|
||||
String[] durationEntries = Utils.getResourceStringArray("revanced_sb_duration_entries");
|
||||
String[] durationEntryValues = Utils.getResourceStringArray("revanced_sb_duration_entry_values");
|
||||
|
||||
autoHideSkipSegmentButtonDuration = new CustomDialogListPreference(context);
|
||||
autoHideSkipSegmentButtonDuration.setTitle(str("revanced_sb_auto_hide_skip_button_duration"));
|
||||
autoHideSkipSegmentButtonDuration.setSummary(str("revanced_sb_auto_hide_skip_button_duration_sum"));
|
||||
autoHideSkipSegmentButtonDuration.setEntries(durationEntries);
|
||||
autoHideSkipSegmentButtonDuration.setEntryValues(durationEntryValues);
|
||||
autoHideSkipSegmentButtonDuration.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION.save(
|
||||
SponsorBlockDuration.valueOf((String) newValue)
|
||||
);
|
||||
updateUI();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(autoHideSkipSegmentButtonDuration);
|
||||
|
||||
showSkipToast = new SwitchPreference(context);
|
||||
showSkipToast.setTitle(str("revanced_sb_general_skiptoast"));
|
||||
showSkipToast.setSummaryOn(str("revanced_sb_general_skiptoast_sum_on"));
|
||||
showSkipToast.setSummaryOff(str("revanced_sb_general_skiptoast_sum_off"));
|
||||
showSkipToast.setOnPreferenceClickListener(preference1 -> {
|
||||
Utils.showToastShort(str("revanced_sb_skipped_sponsor"));
|
||||
return false;
|
||||
});
|
||||
showSkipToast.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_TOAST_ON_SKIP.save((Boolean) newValue);
|
||||
updateUI();
|
||||
@@ -253,6 +265,20 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
});
|
||||
appearanceCategory.addPreference(showSkipToast);
|
||||
|
||||
showSkipToastDuration = new CustomDialogListPreference(context);
|
||||
showSkipToastDuration.setTitle(str("revanced_sb_toast_on_skip_duration"));
|
||||
showSkipToastDuration.setSummary(str("revanced_sb_toast_on_skip_duration_sum"));
|
||||
showSkipToastDuration.setEntries(durationEntries);
|
||||
showSkipToastDuration.setEntryValues(durationEntryValues);
|
||||
showSkipToastDuration.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_TOAST_ON_SKIP_DURATION.save(
|
||||
SponsorBlockDuration.valueOf((String) newValue)
|
||||
);
|
||||
updateUI();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(showSkipToastDuration);
|
||||
|
||||
showTimeWithoutSegments = new SwitchPreference(context);
|
||||
showTimeWithoutSegments.setTitle(str("revanced_sb_general_time_without"));
|
||||
showTimeWithoutSegments.setSummaryOn(str("revanced_sb_general_time_without_sum_on"));
|
||||
@@ -264,6 +290,17 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||
});
|
||||
appearanceCategory.addPreference(showTimeWithoutSegments);
|
||||
|
||||
squareLayout = new SwitchPreference(context);
|
||||
squareLayout.setTitle(str("revanced_sb_square_layout"));
|
||||
squareLayout.setSummaryOn(str("revanced_sb_square_layout_sum_on"));
|
||||
squareLayout.setSummaryOff(str("revanced_sb_square_layout_sum_off"));
|
||||
squareLayout.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
Settings.SB_SQUARE_LAYOUT.save((Boolean) newValue);
|
||||
updateUI();
|
||||
return true;
|
||||
});
|
||||
appearanceCategory.addPreference(squareLayout);
|
||||
|
||||
segmentCategory = new PreferenceCategory(context);
|
||||
segmentCategory.setTitle(str("revanced_sb_diff_segments"));
|
||||
addPreference(segmentCategory);
|
||||
|
||||
@@ -203,7 +203,7 @@ public class SponsorBlockViewController {
|
||||
setSkipButtonMargins(skipSponsorButton, isWatchFullScreen);
|
||||
setViewVisibility(skipSponsorButton, skipSegment != null);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Player type changed failure", ex);
|
||||
Logger.printException(() -> "playerTypeChanged failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
<item>@string/revanced_language_ZH</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_language_entry_values">
|
||||
<!-- Extension enum names. -->
|
||||
<item>DEFAULT</item>
|
||||
<item>AM</item>
|
||||
<item>AR</item>
|
||||
@@ -129,7 +130,6 @@
|
||||
<item>iOS TV</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_spoof_video_streams_client_type_entry_values">
|
||||
<!-- Extension enum names. -->
|
||||
<item>ANDROID_UNPLUGGED</item>
|
||||
<item>ANDROID_VR_NO_AUTH</item>
|
||||
<item>IOS_UNPLUGGED</item>
|
||||
@@ -146,7 +146,6 @@
|
||||
<item>@string/revanced_swipe_overlay_style_entry_7</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_swipe_overlay_style_entry_values">
|
||||
<!-- Extension enum names. -->
|
||||
<item>HORIZONTAL</item>
|
||||
<item>HORIZONTAL_MINIMAL_TOP</item>
|
||||
<item>HORIZONTAL_MINIMAL_CENTER</item>
|
||||
@@ -180,7 +179,6 @@
|
||||
<item>@string/revanced_change_form_factor_entry_4</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_change_form_factor_entry_values">
|
||||
<!-- Extension enum names. -->
|
||||
<item>DEFAULT</item>
|
||||
<item>SMALL</item>
|
||||
<item>LARGE</item>
|
||||
@@ -210,7 +208,6 @@
|
||||
<item>@string/revanced_exit_fullscreen_entry_4</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_exit_fullscreen_entry_values">
|
||||
<!-- Enum names from the extension. -->
|
||||
<item>DISABLED</item>
|
||||
<item>PORTRAIT</item>
|
||||
<item>LANDSCAPE</item>
|
||||
@@ -229,7 +226,6 @@
|
||||
<item>@string/revanced_miniplayer_type_entry_7</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_miniplayer_type_entry_values">
|
||||
<!-- Extension enum names. -->
|
||||
<item>DISABLED</item>
|
||||
<item>DEFAULT</item>
|
||||
<item>MINIMAL</item>
|
||||
@@ -330,13 +326,38 @@
|
||||
<item>YOUR_CLIPS</item>
|
||||
</string-array>
|
||||
</patch>
|
||||
<patch id="layout.sponsorblock.sponsorBlockResourcePatch">
|
||||
<string-array name="revanced_sb_duration_entries">
|
||||
<item>@string/revanced_sb_duration_1s</item>
|
||||
<item>@string/revanced_sb_duration_2s</item>
|
||||
<item>@string/revanced_sb_duration_3s</item>
|
||||
<item>@string/revanced_sb_duration_4s</item>
|
||||
<item>@string/revanced_sb_duration_5s</item>
|
||||
<item>@string/revanced_sb_duration_6s</item>
|
||||
<item>@string/revanced_sb_duration_7s</item>
|
||||
<item>@string/revanced_sb_duration_8s</item>
|
||||
<item>@string/revanced_sb_duration_9s</item>
|
||||
<item>@string/revanced_sb_duration_10s</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_sb_duration_entry_values">
|
||||
<item>ONE_SECOND</item>
|
||||
<item>TWO_SECONDS</item>
|
||||
<item>THREE_SECONDS</item>
|
||||
<item>FOUR_SECONDS</item>
|
||||
<item>FIVE_SECONDS</item>
|
||||
<item>SIX_SECONDS</item>
|
||||
<item>SEVEN_SECONDS</item>
|
||||
<item>EIGHT_SECONDS</item>
|
||||
<item>NINE_SECONDS</item>
|
||||
<item>TEN_SECONDS</item>
|
||||
</string-array>
|
||||
</patch>
|
||||
<patch id="layout.shortsplayer.shortsPlayerTypePatch">
|
||||
<string-array name="revanced_shorts_player_type_legacy_entries">
|
||||
<item>@string/revanced_shorts_player_type_shorts</item>
|
||||
<item>@string/revanced_shorts_player_type_regular_player</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_shorts_player_type_legacy_entry_values">
|
||||
<!-- Extension enum names. -->
|
||||
<item>SHORTS_PLAYER</item>
|
||||
<item>REGULAR_PLAYER</item>
|
||||
</string-array>
|
||||
@@ -346,7 +367,6 @@
|
||||
<item>@string/revanced_shorts_player_type_regular_player_fullscreen</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_shorts_player_type_entry_values">
|
||||
<!-- Enum names from extension -->
|
||||
<item>SHORTS_PLAYER</item>
|
||||
<item>REGULAR_PLAYER</item>
|
||||
<item>REGULAR_PLAYER_FULLSCREEN</item>
|
||||
@@ -360,7 +380,6 @@
|
||||
<item>@string/revanced_alt_thumbnail_options_entry_4</item>
|
||||
</string-array>
|
||||
<string-array name="revanced_alt_thumbnail_options_entry_values">
|
||||
<!-- Extension enum names. -->
|
||||
<item>ORIGINAL</item>
|
||||
<item>DEARROW</item>
|
||||
<item>DEARROW_STILL_IMAGES</item>
|
||||
|
||||
@@ -1027,11 +1027,25 @@ This feature works best with a video quality of 720p or lower and when using a v
|
||||
<string name="revanced_sb_enable_auto_hide_skip_segment_button">Automatically hide Skip button</string>
|
||||
<string name="revanced_sb_enable_auto_hide_skip_segment_button_sum_on">Skip button hides after a few seconds</string>
|
||||
<string name="revanced_sb_enable_auto_hide_skip_segment_button_sum_off">Skip button is shown for the entire segment</string>
|
||||
<string name="revanced_sb_general_skiptoast">Show a toast when skipping</string>
|
||||
<string name="revanced_sb_general_skiptoast_sum_on">Toast is shown when a segment is automatically skipped. Tap here to see an example</string>
|
||||
<string name="revanced_sb_general_skiptoast_sum_off">Toast is not shown. Tap here to see an example</string>
|
||||
<string name="revanced_sb_auto_hide_skip_button_duration">Skip button duration</string>
|
||||
<string name="revanced_sb_auto_hide_skip_button_duration_sum">How long the auto hide skip and skip to highlight buttons are shown</string>
|
||||
<string name="revanced_sb_general_skiptoast">Show undo skip toast</string>
|
||||
<string name="revanced_sb_general_skiptoast_sum_on">Toast is shown when a segment is automatically skipped. Tap the toast notification to undo the skip</string>
|
||||
<string name="revanced_sb_general_skiptoast_sum_off">Toast is not shown</string>
|
||||
<string name="revanced_sb_toast_on_skip_duration">Skip toast duration</string>
|
||||
<string name="revanced_sb_toast_on_skip_duration_sum">How long the skip toast notification is shown</string>
|
||||
<string name="revanced_sb_duration_1s">1 second</string>
|
||||
<string name="revanced_sb_duration_2s">2 seconds</string>
|
||||
<string name="revanced_sb_duration_3s">3 seconds</string>
|
||||
<string name="revanced_sb_duration_4s">4 seconds</string>
|
||||
<string name="revanced_sb_duration_5s">5 seconds</string>
|
||||
<string name="revanced_sb_duration_6s">6 seconds</string>
|
||||
<string name="revanced_sb_duration_7s">7 seconds</string>
|
||||
<string name="revanced_sb_duration_8s">8 seconds</string>
|
||||
<string name="revanced_sb_duration_9s">9 seconds</string>
|
||||
<string name="revanced_sb_duration_10s">10 seconds</string>
|
||||
<string name="revanced_sb_general_time_without">Show video length without segments</string>
|
||||
<string name="revanced_sb_general_time_without_sum_on">Video length minus all segments, shown in parentheses next to the full video length</string>
|
||||
<string name="revanced_sb_general_time_without_sum_on">Video length minus all segments is shown on the seekbar</string>
|
||||
<string name="revanced_sb_general_time_without_sum_off">Full video length shown</string>
|
||||
<string name="revanced_sb_create_segment_category">Creating new segments</string>
|
||||
<string name="revanced_sb_enable_create_segment">Show Create new segment button</string>
|
||||
|
||||
Reference in New Issue
Block a user