feat(YouTube - SponsorBlock): Add "Undo automatic skip toast" (#5277)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
parent
21688201af
commit
6ee94f8532
12 changed files with 591 additions and 243 deletions
|
|
@ -311,6 +311,10 @@ public class Utils {
|
||||||
return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen"));
|
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> {
|
public interface MatchFilter<T> {
|
||||||
boolean matches(T object);
|
boolean matches(T object);
|
||||||
}
|
}
|
||||||
|
|
@ -579,7 +583,7 @@ public class Utils {
|
||||||
Context currentContext = context;
|
Context currentContext = context;
|
||||||
|
|
||||||
if (currentContext == null) {
|
if (currentContext == null) {
|
||||||
Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast, null);
|
Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast);
|
||||||
} else {
|
} else {
|
||||||
Logger.printDebug(() -> "Showing toast: " + messageToToast);
|
Logger.printDebug(() -> "Showing toast: " + messageToToast);
|
||||||
Toast.makeText(currentContext, messageToToast, toastDuration).show();
|
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.
|
// Create content container (message/EditText) inside a ScrollView only if message or editText is provided.
|
||||||
ScrollView contentScrollView = null;
|
ScrollView contentScrollView = null;
|
||||||
LinearLayout contentContainer = null;
|
LinearLayout contentContainer;
|
||||||
if (message != null || editText != null) {
|
if (message != null || editText != null) {
|
||||||
contentScrollView = new ScrollView(context);
|
contentScrollView = new ScrollView(context);
|
||||||
contentScrollView.setVerticalScrollBarEnabled(false); // Disable the vertical scrollbar.
|
contentScrollView.setVerticalScrollBarEnabled(false); // Disable the vertical scrollbar.
|
||||||
|
|
@ -833,7 +837,7 @@ public class Utils {
|
||||||
contentScrollView.addView(contentContainer);
|
contentScrollView.addView(contentContainer);
|
||||||
|
|
||||||
// Message (if not replaced by EditText).
|
// Message (if not replaced by EditText).
|
||||||
if (editText == null && message != null) {
|
if (editText == null) {
|
||||||
TextView messageView = new TextView(context);
|
TextView messageView = new TextView(context);
|
||||||
messageView.setText(message); // Supports Spanned (HTML).
|
messageView.setText(message); // Supports Spanned (HTML).
|
||||||
messageView.setTextSize(16);
|
messageView.setTextSize(16);
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,12 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
|
||||||
json.put(importExportKey, value.name().toLowerCase(Locale.ENGLISH));
|
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
|
//noinspection ConstantConditions
|
||||||
for (Enum<?> value : defaultValue.getClass().getEnumConstants()) {
|
for (Enum<?> value : defaultValue.getClass().getEnumConstants()) {
|
||||||
if (value.name().equalsIgnoreCase(enumName)) {
|
if (value.name().equalsIgnoreCase(enumName)) {
|
||||||
|
|
@ -80,6 +84,7 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
|
||||||
return (T) value;
|
return (T) value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException("Unknown enum value: " + enumName);
|
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.
|
* Availability based on if this setting is currently set to any of the provided types.
|
||||||
*/
|
*/
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
public final Setting.Availability availability(@NonNull T... types) {
|
public final Setting.Availability availability(T... types) {
|
||||||
|
Objects.requireNonNull(types);
|
||||||
|
|
||||||
return () -> {
|
return () -> {
|
||||||
T currentEnumType = get();
|
T currentEnumType = get();
|
||||||
for (T enumType : types) {
|
for (T enumType : types) {
|
||||||
|
|
|
||||||
|
|
@ -28,16 +28,14 @@ public abstract class Setting<T> {
|
||||||
/**
|
/**
|
||||||
* Availability based on a single parent setting being enabled.
|
* Availability based on a single parent setting being enabled.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
public static Availability parent(BooleanSetting parent) {
|
||||||
public static Availability parent(@NonNull BooleanSetting parent) {
|
|
||||||
return parent::get;
|
return parent::get;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Availability based on all parents being enabled.
|
* Availability based on all parents being enabled.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
public static Availability parentsAll(BooleanSetting... parents) {
|
||||||
public static Availability parentsAll(@NonNull BooleanSetting... parents) {
|
|
||||||
return () -> {
|
return () -> {
|
||||||
for (BooleanSetting parent : parents) {
|
for (BooleanSetting parent : parents) {
|
||||||
if (!parent.get()) return false;
|
if (!parent.get()) return false;
|
||||||
|
|
@ -49,8 +47,7 @@ public abstract class Setting<T> {
|
||||||
/**
|
/**
|
||||||
* Availability based on any parent being enabled.
|
* Availability based on any parent being enabled.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
public static Availability parentsAny(BooleanSetting... parents) {
|
||||||
public static Availability parentsAny(@NonNull BooleanSetting... parents) {
|
|
||||||
return () -> {
|
return () -> {
|
||||||
for (BooleanSetting parent : parents) {
|
for (BooleanSetting parent : parents) {
|
||||||
if (parent.get()) return true;
|
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)}.
|
* 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));
|
importExportCallbacks.add(Objects.requireNonNull(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,14 +97,13 @@ public abstract class Setting<T> {
|
||||||
public static final SharedPrefCategory preferences = new SharedPrefCategory("revanced_prefs");
|
public static final SharedPrefCategory preferences = new SharedPrefCategory("revanced_prefs");
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Setting<?> getSettingFromPath(@NonNull String str) {
|
public static Setting<?> getSettingFromPath(String str) {
|
||||||
return PATH_TO_SETTINGS.get(str);
|
return PATH_TO_SETTINGS.get(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return All settings that have been created.
|
* @return All settings that have been created.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
public static List<Setting<?>> allLoadedSettings() {
|
public static List<Setting<?>> allLoadedSettings() {
|
||||||
return Collections.unmodifiableList(SETTINGS);
|
return Collections.unmodifiableList(SETTINGS);
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +111,6 @@ public abstract class Setting<T> {
|
||||||
/**
|
/**
|
||||||
* @return All settings that have been created, sorted by keys.
|
* @return All settings that have been created, sorted by keys.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
private static List<Setting<?>> allLoadedSettingsSorted() {
|
private static List<Setting<?>> allLoadedSettingsSorted() {
|
||||||
Collections.sort(SETTINGS, (Setting<?> o1, Setting<?> o2) -> o1.key.compareTo(o2.key));
|
Collections.sort(SETTINGS, (Setting<?> o1, Setting<?> o2) -> o1.key.compareTo(o2.key));
|
||||||
return allLoadedSettings();
|
return allLoadedSettings();
|
||||||
|
|
@ -124,13 +119,11 @@ public abstract class Setting<T> {
|
||||||
/**
|
/**
|
||||||
* The key used to store the value in the shared preferences.
|
* The key used to store the value in the shared preferences.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
public final String key;
|
public final String key;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default value of the setting.
|
* The default value of the setting.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
public final T defaultValue;
|
public final T defaultValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -161,7 +154,6 @@ public abstract class Setting<T> {
|
||||||
/**
|
/**
|
||||||
* The value of the setting.
|
* The value of the setting.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
protected volatile T value;
|
protected volatile T value;
|
||||||
|
|
||||||
public Setting(String key, T defaultValue) {
|
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 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.
|
* @param availability Condition that must be true, for this setting to be available to configure.
|
||||||
*/
|
*/
|
||||||
public Setting(@NonNull String key,
|
public Setting(String key,
|
||||||
@NonNull T defaultValue,
|
T defaultValue,
|
||||||
boolean rebootApp,
|
boolean rebootApp,
|
||||||
boolean includeWithImportExport,
|
boolean includeWithImportExport,
|
||||||
@Nullable String userDialogMessage,
|
@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.
|
* 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 == newSetting) throw new IllegalArgumentException();
|
||||||
|
|
||||||
if (!oldSetting.isSetToDefault()) {
|
if (!oldSetting.isSetToDefault()) {
|
||||||
|
|
@ -243,7 +235,7 @@ public abstract class Setting<T> {
|
||||||
* This method will be deleted in the future.
|
* This method will be deleted in the future.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@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)) {
|
if (!oldPrefs.preferences.contains(settingKey)) {
|
||||||
return; // Nothing to do.
|
return; // Nothing to do.
|
||||||
}
|
}
|
||||||
|
|
@ -285,7 +277,7 @@ public abstract class Setting<T> {
|
||||||
* This intentionally is a static method to deter
|
* This intentionally is a static method to deter
|
||||||
* accidental usage when {@link #save(Object)} was intended.
|
* 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);
|
setting.setValueFromString(newValue);
|
||||||
|
|
||||||
// Clear the preference value since default is used, to allow changing
|
// 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}.
|
* 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}.
|
* Load and set the value of {@link #value}.
|
||||||
|
|
@ -309,7 +301,7 @@ public abstract class Setting<T> {
|
||||||
/**
|
/**
|
||||||
* Persistently saves the value.
|
* Persistently saves the value.
|
||||||
*/
|
*/
|
||||||
public final void save(@NonNull T newValue) {
|
public final void save(T newValue) {
|
||||||
if (value.equals(newValue)) {
|
if (value.equals(newValue)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -406,7 +398,6 @@ public abstract class Setting<T> {
|
||||||
json.put(importExportKey, value);
|
json.put(importExportKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static String exportToJson(@Nullable Context alertDialogContext) {
|
public static String exportToJson(@Nullable Context alertDialogContext) {
|
||||||
try {
|
try {
|
||||||
JSONObject json = new JSONObject();
|
JSONObject json = new JSONObject();
|
||||||
|
|
@ -445,7 +436,7 @@ public abstract class Setting<T> {
|
||||||
/**
|
/**
|
||||||
* @return if any settings that require a reboot were changed.
|
* @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 {
|
try {
|
||||||
if (!settingsJsonString.matches("[\\s\\S]*\\{")) {
|
if (!settingsJsonString.matches("[\\s\\S]*\\{")) {
|
||||||
settingsJsonString = '{' + settingsJsonString + '}'; // Restore outer JSON braces
|
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.Availability;
|
||||||
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
|
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.parent;
|
||||||
|
import static app.revanced.extension.shared.settings.Setting.parentsAll;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parentsAny;
|
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.ChangeFormFactorPatch.FormFactor;
|
||||||
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.ChangeStartPageTypeAvailability;
|
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.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
|
||||||
import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
|
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.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.IGNORE;
|
||||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
|
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
|
||||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
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_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_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 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 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_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 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));
|
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;
|
package app.revanced.extension.youtube.sponsorblock;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
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.Canvas;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
|
import android.graphics.drawable.shapes.RoundRectShape;
|
||||||
import android.text.TextUtils;
|
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 androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.lang.reflect.Field;
|
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.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
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.
|
* Class is not thread safe. All methods must be called on the main thread unless otherwise specified.
|
||||||
*/
|
*/
|
||||||
public class SegmentPlaybackController {
|
public class SegmentPlaybackController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Length of time to show a skip button for a highlight segment,
|
* Enum for configurable durations (1 to 10 seconds) for skip button and toast display.
|
||||||
* or a regular segment if {@link Settings#SB_AUTO_HIDE_SKIP_BUTTON} is enabled.
|
|
||||||
*
|
|
||||||
* Effectively this value is rounded up to the next second.
|
|
||||||
*/
|
*/
|
||||||
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.
|
* Highlight segments have zero length as they are a point in time.
|
||||||
* Draw them on screen using a fixed width bar.
|
* Draw them on screen using a fixed width bar.
|
||||||
* Value is independent of device dpi.
|
|
||||||
*/
|
*/
|
||||||
private static final int HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH = 7;
|
private static final int HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH = dipToPixels(7);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String currentVideoId;
|
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.
|
* 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.
|
* 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;
|
private static long highlightSegmentInitialShowEndTime;
|
||||||
|
|
||||||
|
|
@ -70,7 +108,7 @@ public class SegmentPlaybackController {
|
||||||
private static SponsorSegment segmentCurrentlyPlaying;
|
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}.
|
* This is always NULL or equal to {@link #segmentCurrentlyPlaying}.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static SponsorSegment scheduledHideSegment;
|
private static SponsorSegment scheduledHideSegment;
|
||||||
|
|
@ -89,31 +127,71 @@ public class SegmentPlaybackController {
|
||||||
*/
|
*/
|
||||||
private static final List<SponsorSegment> hiddenSkipSegmentsForCurrentVideoTime = new ArrayList<>();
|
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}.
|
* 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),
|
* 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.
|
* or if {@link Settings#SB_AUTO_HIDE_SKIP_BUTTON} is not enabled.
|
||||||
*/
|
*/
|
||||||
private static long skipSegmentButtonEndTime;
|
private static long skipSegmentButtonEndTime;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String timeWithoutSegments;
|
private static String timeWithoutSegments;
|
||||||
|
|
||||||
private static int sponsorBarAbsoluteLeft;
|
private static int sponsorBarAbsoluteLeft;
|
||||||
private static int sponsorAbsoluteBarRight;
|
private static int sponsorAbsoluteBarRight;
|
||||||
private static int sponsorBarThickness;
|
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
|
@Nullable
|
||||||
static SponsorSegment[] getSegments() {
|
static SponsorSegment[] getSegments() {
|
||||||
return segments;
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setSegments(@NonNull SponsorSegment[] videoSegments) {
|
private static void setSegments(SponsorSegment[] videoSegments) {
|
||||||
Arrays.sort(videoSegments);
|
Arrays.sort(videoSegments);
|
||||||
segments = videoSegments;
|
segments = videoSegments;
|
||||||
calculateTimeWithoutSegments();
|
calculateTimeWithoutSegments();
|
||||||
|
|
||||||
if (SegmentCategory.HIGHLIGHT.behaviour == CategoryBehaviour.SKIP_AUTOMATICALLY
|
if (SegmentCategory.HIGHLIGHT.behaviour == SKIP_AUTOMATICALLY
|
||||||
|| SegmentCategory.HIGHLIGHT.behaviour == CategoryBehaviour.MANUAL_SKIP) {
|
|| SegmentCategory.HIGHLIGHT.behaviour == CategoryBehaviour.MANUAL_SKIP) {
|
||||||
for (SponsorSegment segment : videoSegments) {
|
for (SponsorSegment segment : videoSegments) {
|
||||||
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
||||||
|
|
@ -125,7 +203,7 @@ public class SegmentPlaybackController {
|
||||||
highlightSegment = null;
|
highlightSegment = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void addUnsubmittedSegment(@NonNull SponsorSegment segment) {
|
static void addUnsubmittedSegment(SponsorSegment segment) {
|
||||||
Objects.requireNonNull(segment);
|
Objects.requireNonNull(segment);
|
||||||
if (segments == null) {
|
if (segments == null) {
|
||||||
segments = new SponsorSegment[1];
|
segments = new SponsorSegment[1];
|
||||||
|
|
@ -140,6 +218,7 @@ public class SegmentPlaybackController {
|
||||||
if (segments == null || segments.length == 0) {
|
if (segments == null || segments.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SponsorSegment> replacement = new ArrayList<>();
|
List<SponsorSegment> replacement = new ArrayList<>();
|
||||||
for (SponsorSegment segment : segments) {
|
for (SponsorSegment segment : segments) {
|
||||||
if (segment.category != SegmentCategory.UNSUBMITTED) {
|
if (segment.category != SegmentCategory.UNSUBMITTED) {
|
||||||
|
|
@ -156,7 +235,7 @@ public class SegmentPlaybackController {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all downloaded data.
|
* Clear all data.
|
||||||
*/
|
*/
|
||||||
private static void clearData() {
|
private static void clearData() {
|
||||||
currentVideoId = null;
|
currentVideoId = null;
|
||||||
|
|
@ -170,6 +249,8 @@ public class SegmentPlaybackController {
|
||||||
skipSegmentButtonEndTime = 0;
|
skipSegmentButtonEndTime = 0;
|
||||||
toastSegmentSkipped = null;
|
toastSegmentSkipped = null;
|
||||||
toastNumberOfSegmentsSkipped = 0;
|
toastNumberOfSegmentsSkipped = 0;
|
||||||
|
undoAutoSkipRange = null;
|
||||||
|
undoAutoSkipRangeToast = null;
|
||||||
hiddenSkipSegmentsForCurrentVideoTime.clear();
|
hiddenSkipSegmentsForCurrentVideoTime.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,7 +267,7 @@ public class SegmentPlaybackController {
|
||||||
SponsorBlockUtils.clearUnsubmittedSegmentTimes();
|
SponsorBlockUtils.clearUnsubmittedSegmentTimes();
|
||||||
Logger.printDebug(() -> "Initialized SponsorBlock");
|
Logger.printDebug(() -> "Initialized SponsorBlock");
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "Failed to initialize SponsorBlock", ex);
|
Logger.printException(() -> "initialize failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,7 +284,7 @@ public class SegmentPlaybackController {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (PlayerType.getCurrent().isNoneOrHidden()) {
|
if (PlayerType.getCurrent().isNoneOrHidden()) {
|
||||||
Logger.printDebug(() -> "ignoring Short");
|
Logger.printDebug(() -> "Ignoring Short");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Utils.isNetworkConnected()) {
|
if (!Utils.isNetworkConnected()) {
|
||||||
|
|
@ -212,7 +293,7 @@ public class SegmentPlaybackController {
|
||||||
}
|
}
|
||||||
|
|
||||||
currentVideoId = videoId;
|
currentVideoId = videoId;
|
||||||
Logger.printDebug(() -> "setCurrentVideoId: " + videoId);
|
Logger.printDebug(() -> "New video ID: " + videoId);
|
||||||
|
|
||||||
Utils.runOnBackgroundThread(() -> {
|
Utils.runOnBackgroundThread(() -> {
|
||||||
try {
|
try {
|
||||||
|
|
@ -227,11 +308,11 @@ 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);
|
Objects.requireNonNull(videoId);
|
||||||
try {
|
|
||||||
SponsorSegment[] segments = SBRequester.getSegments(videoId);
|
SponsorSegment[] segments = SBRequester.getSegments(videoId);
|
||||||
|
|
||||||
Utils.runOnMainThread(() -> {
|
Utils.runOnMainThread(() -> {
|
||||||
|
|
@ -253,16 +334,13 @@ public class SegmentPlaybackController {
|
||||||
}
|
}
|
||||||
highlightSegmentInitialShowEndTime = System.currentTimeMillis() + Math.min(
|
highlightSegmentInitialShowEndTime = System.currentTimeMillis() + Math.min(
|
||||||
(long) (timeUntilHighlight / VideoInformation.getPlaybackSpeed()),
|
(long) (timeUntilHighlight / VideoInformation.getPlaybackSpeed()),
|
||||||
DURATION_TO_SHOW_SKIP_BUTTON);
|
getSkipButtonDuration());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for any skips now, instead of waiting for the next update to setVideoTime()
|
// check for any skips now, instead of waiting for the next update to setVideoTime()
|
||||||
setVideoTime(videoTime);
|
setVideoTime(videoTime);
|
||||||
});
|
});
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "executeDownloadSegments failure", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -303,17 +381,19 @@ public class SegmentPlaybackController {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (segment.end <= millis) {
|
if (segment.end <= millis) {
|
||||||
continue; // past this segment
|
continue; // Past this segment.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean segmentShouldAutoSkip = shouldAutoSkipAndUndoSkipNotActive(segment, millis);
|
||||||
|
|
||||||
if (segment.start <= millis) {
|
if (segment.start <= millis) {
|
||||||
// we are in the segment!
|
// We are in the segment!
|
||||||
if (segment.shouldAutoSkip()) {
|
if (segmentShouldAutoSkip) {
|
||||||
skipSegment(segment, false);
|
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 (foundSegmentCurrentlyPlaying == null || foundSegmentCurrentlyPlaying.containsSegment(segment)) {
|
||||||
// If the found segment is not currently displayed, then do not show if the segment is nearly over.
|
// 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.
|
// This check prevents the skip button text from rapidly changing when multiple segments end at nearly the same time.
|
||||||
|
|
@ -327,24 +407,26 @@ public class SegmentPlaybackController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Keep iterating and looking. There may be an upcoming autoskip,
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// segment is upcoming
|
// Segment is upcoming.
|
||||||
if (startTimerLookAheadThreshold < segment.start) {
|
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;
|
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))
|
if ((foundSegmentCurrentlyPlaying == null || foundSegmentCurrentlyPlaying.containsSegment(segment))
|
||||||
// use the most inner upcoming segment
|
// Use the most inner upcoming segment.
|
||||||
&& (foundUpcomingSegment == null || foundUpcomingSegment.containsSegment(segment))) {
|
&& (foundUpcomingSegment == null || foundUpcomingSegment.containsSegment(segment))) {
|
||||||
|
|
||||||
// Only schedule, if the segment start time is not near the end time of the current segment.
|
// Only schedule, if the segment start time is not near the end time of the current segment.
|
||||||
|
|
@ -361,7 +443,7 @@ public class SegmentPlaybackController {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (highlightSegment != null) {
|
if (highlightSegment != null) {
|
||||||
if (millis < DURATION_TO_SHOW_SKIP_BUTTON || (highlightSegmentInitialShowEndTime != 0
|
if (millis < getSkipButtonDuration() || (highlightSegmentInitialShowEndTime != 0
|
||||||
&& System.currentTimeMillis() < highlightSegmentInitialShowEndTime)) {
|
&& System.currentTimeMillis() < highlightSegmentInitialShowEndTime)) {
|
||||||
SponsorBlockViewController.showSkipHighlightButton(highlightSegment);
|
SponsorBlockViewController.showSkipHighlightButton(highlightSegment);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -373,16 +455,17 @@ public class SegmentPlaybackController {
|
||||||
if (segmentCurrentlyPlaying != foundSegmentCurrentlyPlaying) {
|
if (segmentCurrentlyPlaying != foundSegmentCurrentlyPlaying) {
|
||||||
setSegmentCurrentlyPlaying(foundSegmentCurrentlyPlaying);
|
setSegmentCurrentlyPlaying(foundSegmentCurrentlyPlaying);
|
||||||
} else if (foundSegmentCurrentlyPlaying != null
|
} else if (foundSegmentCurrentlyPlaying != null
|
||||||
&& skipSegmentButtonEndTime != 0 && skipSegmentButtonEndTime <= System.currentTimeMillis()) {
|
&& skipSegmentButtonEndTime != 0
|
||||||
|
&& skipSegmentButtonEndTime <= System.currentTimeMillis()) {
|
||||||
Logger.printDebug(() -> "Auto hiding skip button for segment: " + segmentCurrentlyPlaying);
|
Logger.printDebug(() -> "Auto hiding skip button for segment: " + segmentCurrentlyPlaying);
|
||||||
skipSegmentButtonEndTime = 0;
|
skipSegmentButtonEndTime = 0;
|
||||||
hiddenSkipSegmentsForCurrentVideoTime.add(foundSegmentCurrentlyPlaying);
|
hiddenSkipSegmentsForCurrentVideoTime.add(foundSegmentCurrentlyPlaying);
|
||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
// schedule a hide, only if the segment end is near
|
// Schedule a hide, but only if the segment end is near.
|
||||||
final SponsorSegment segmentToHide =
|
final SponsorSegment segmentToHide = (foundSegmentCurrentlyPlaying != null &&
|
||||||
(foundSegmentCurrentlyPlaying != null && foundSegmentCurrentlyPlaying.endIsNear(millis, speedAdjustedTimeThreshold))
|
foundSegmentCurrentlyPlaying.endIsNear(millis, speedAdjustedTimeThreshold))
|
||||||
? foundSegmentCurrentlyPlaying
|
? foundSegmentCurrentlyPlaying
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
|
@ -407,7 +490,7 @@ public class SegmentPlaybackController {
|
||||||
|
|
||||||
final long videoTime = VideoInformation.getVideoTime();
|
final long videoTime = VideoInformation.getVideoTime();
|
||||||
if (!segmentToHide.endIsNear(videoTime, speedAdjustedTimeThreshold)) {
|
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
|
Logger.printDebug(() -> "Ignoring outdated scheduled hide: " + segmentToHide
|
||||||
+ " videoInformation time: " + videoTime);
|
+ " videoInformation time: " + videoTime);
|
||||||
return;
|
return;
|
||||||
|
|
@ -416,7 +499,7 @@ public class SegmentPlaybackController {
|
||||||
// Need more than just hide the skip button, as this may have been an embedded segment
|
// 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.
|
// Instead call back into setVideoTime to check everything again.
|
||||||
// Should not use VideoInformation time as it is less accurate,
|
// 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);
|
setSegmentCurrentlyPlaying(null);
|
||||||
setVideoTime(segmentToHide.end);
|
setVideoTime(segmentToHide.end);
|
||||||
}, delayUntilHide);
|
}, delayUntilHide);
|
||||||
|
|
@ -446,12 +529,12 @@ public class SegmentPlaybackController {
|
||||||
|
|
||||||
final long videoTime = VideoInformation.getVideoTime();
|
final long videoTime = VideoInformation.getVideoTime();
|
||||||
if (!segmentToSkip.startIsNear(videoTime, speedAdjustedTimeThreshold)) {
|
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
|
Logger.printDebug(() -> "Ignoring outdated scheduled segment: " + segmentToSkip
|
||||||
+ " videoInformation time: " + videoTime);
|
+ " videoInformation time: " + videoTime);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (segmentToSkip.shouldAutoSkip()) {
|
if (shouldAutoSkipAndUndoSkipNotActive(segmentToSkip, videoTime)) {
|
||||||
Logger.printDebug(() -> "Running scheduled skip segment: " + segmentToSkip);
|
Logger.printDebug(() -> "Running scheduled skip segment: " + segmentToSkip);
|
||||||
skipSegment(segmentToSkip, false);
|
skipSegment(segmentToSkip, false);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -461,6 +544,12 @@ public class SegmentPlaybackController {
|
||||||
}, delayUntilSkip);
|
}, 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) {
|
} catch (Exception e) {
|
||||||
Logger.printException(() -> "setVideoTime failure", 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.
|
* Removes all previously hidden segments that are not longer contained in the given video time.
|
||||||
*/
|
*/
|
||||||
private static void updateHiddenSegments(long currentVideoTime) {
|
private static void updateHiddenSegments(long currentVideoTime) {
|
||||||
Iterator<SponsorSegment> i = hiddenSkipSegmentsForCurrentVideoTime.iterator();
|
hiddenSkipSegmentsForCurrentVideoTime.removeIf((hiddenSegment) -> {
|
||||||
while (i.hasNext()) {
|
|
||||||
SponsorSegment hiddenSegment = i.next();
|
|
||||||
if (!hiddenSegment.containsTime(currentVideoTime)) {
|
if (!hiddenSegment.containsTime(currentVideoTime)) {
|
||||||
Logger.printDebug(() -> "Resetting hide skip button: " + hiddenSegment);
|
Logger.printDebug(() -> "Resetting hide skip button: " + hiddenSegment);
|
||||||
i.remove();
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setSegmentCurrentlyPlaying(@Nullable SponsorSegment segment) {
|
private static void setSegmentCurrentlyPlaying(@Nullable SponsorSegment segment) {
|
||||||
|
|
@ -488,8 +576,10 @@ public class SegmentPlaybackController {
|
||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
segmentCurrentlyPlaying = segment;
|
segmentCurrentlyPlaying = segment;
|
||||||
skipSegmentButtonEndTime = 0;
|
skipSegmentButtonEndTime = 0;
|
||||||
|
|
||||||
if (Settings.SB_AUTO_HIDE_SKIP_BUTTON.get()) {
|
if (Settings.SB_AUTO_HIDE_SKIP_BUTTON.get()) {
|
||||||
if (hiddenSkipSegmentsForCurrentVideoTime.contains(segment)) {
|
if (hiddenSkipSegmentsForCurrentVideoTime.contains(segment)) {
|
||||||
// Playback exited a nested segment and the outer segment skip button was previously hidden.
|
// Playback exited a nested segment and the outer segment skip button was previously hidden.
|
||||||
|
|
@ -497,16 +587,13 @@ public class SegmentPlaybackController {
|
||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
skipSegmentButtonEndTime = System.currentTimeMillis() + DURATION_TO_SHOW_SKIP_BUTTON;
|
skipSegmentButtonEndTime = System.currentTimeMillis() + getSkipButtonDuration();
|
||||||
}
|
}
|
||||||
Logger.printDebug(() -> "Showing segment: " + segment);
|
Logger.printDebug(() -> "Showing segment: " + segment);
|
||||||
SponsorBlockViewController.showSkipSegmentButton(segment);
|
SponsorBlockViewController.showSkipSegmentButton(segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SponsorSegment lastSegmentSkipped;
|
private static void skipSegment(SponsorSegment segmentToSkip, boolean userManuallySkipped) {
|
||||||
private static long lastSegmentSkippedTime;
|
|
||||||
|
|
||||||
private static void skipSegment(@NonNull SponsorSegment segmentToSkip, boolean userManuallySkipped) {
|
|
||||||
try {
|
try {
|
||||||
SponsorBlockViewController.hideSkipHighlightButton();
|
SponsorBlockViewController.hideSkipHighlightButton();
|
||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
|
|
@ -525,7 +612,7 @@ public class SegmentPlaybackController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.printDebug(() -> "Skipping segment: " + segmentToSkip);
|
Logger.printDebug(() -> "Skipping segment: " + segmentToSkip + " videoState: " + VideoState.getCurrent());
|
||||||
lastSegmentSkipped = segmentToSkip;
|
lastSegmentSkipped = segmentToSkip;
|
||||||
lastSegmentSkippedTime = now;
|
lastSegmentSkippedTime = now;
|
||||||
setSegmentCurrentlyPlaying(null);
|
setSegmentCurrentlyPlaying(null);
|
||||||
|
|
@ -535,29 +622,39 @@ public class SegmentPlaybackController {
|
||||||
highlightSegmentInitialShowEndTime = 0;
|
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.
|
// If the seek is successful, then the seek causes a recursive call back into this class.
|
||||||
final boolean seekSuccessful = VideoInformation.seekTo(segmentToSkip.end);
|
final boolean seekSuccessful = VideoInformation.seekTo(segmentToSkip.end);
|
||||||
if (!seekSuccessful) {
|
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);
|
Logger.printDebug(() -> "Could not skip segment (seek unsuccessful): " + segmentToSkip);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean videoIsPaused = VideoState.getCurrent() == VideoState.PAUSED;
|
|
||||||
if (!userManuallySkipped) {
|
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();
|
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) {
|
if (segmentToSkip.end < otherSegment.start) {
|
||||||
break; // no other segments can be contained
|
break; // No other segments can be contained.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (otherSegment == segmentToSkip ||
|
if (otherSegment == segmentToSkip ||
|
||||||
(otherSegment.category != SegmentCategory.HIGHLIGHT && segmentToSkip.containsSegment(otherSegment))) {
|
(otherSegment.category != SegmentCategory.HIGHLIGHT && segmentToSkip.containsSegment(otherSegment))) {
|
||||||
otherSegment.didAutoSkipped = true;
|
otherSegment.didAutoSkipped = true;
|
||||||
// Do not show a toast if the user is scrubbing thru a paused video.
|
if (showSkipToast) {
|
||||||
// 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) {
|
|
||||||
showSkippedSegmentToast(otherSegment);
|
showSkippedSegmentToast(otherSegment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -567,7 +664,7 @@ public class SegmentPlaybackController {
|
||||||
if (segmentToSkip.category == SegmentCategory.UNSUBMITTED) {
|
if (segmentToSkip.category == SegmentCategory.UNSUBMITTED) {
|
||||||
removeUnsubmittedSegments();
|
removeUnsubmittedSegments();
|
||||||
SponsorBlockUtils.setNewSponsorSegmentPreviewed();
|
SponsorBlockUtils.setNewSponsorSegmentPreviewed();
|
||||||
} else if (!videoIsPaused) {
|
} else if (VideoState.getCurrent() != VideoState.PAUSED) {
|
||||||
SponsorBlockUtils.sendViewRequestAsync(segmentToSkip);
|
SponsorBlockUtils.sendViewRequestAsync(segmentToSkip);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
|
@ -575,29 +672,44 @@ public class SegmentPlaybackController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
private static int toastNumberOfSegmentsSkipped;
|
* Checks if the segment should be auto-skipped _and_ if undo autoskip is not active.
|
||||||
@Nullable
|
*/
|
||||||
private static SponsorSegment toastSegmentSkipped;
|
private static boolean shouldAutoSkipAndUndoSkipNotActive(SponsorSegment segment, long currentVideoTime) {
|
||||||
|
return segment.shouldAutoSkip() && (undoAutoSkipRange == null
|
||||||
private static void showSkippedSegmentToast(@NonNull SponsorSegment segment) {
|
|| !undoAutoSkipRange.contains(currentVideoTime));
|
||||||
Utils.verifyOnMainThread();
|
|
||||||
toastNumberOfSegmentsSkipped++;
|
|
||||||
if (toastNumberOfSegmentsSkipped > 1) {
|
|
||||||
return; // toast already scheduled
|
|
||||||
}
|
}
|
||||||
toastSegmentSkipped = segment;
|
|
||||||
|
|
||||||
final long delayToToastMilliseconds = 250; // also the maximum time between skips to be considered skipping multiple segments
|
private static void showSkippedSegmentToast(SponsorSegment segment) {
|
||||||
|
Utils.verifyOnMainThread();
|
||||||
|
toastSegmentSkipped = segment;
|
||||||
|
if (toastNumberOfSegmentsSkipped++ > 0) {
|
||||||
|
return; // Toast is already scheduled.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maximum time between skips to be considered skipping multiple segments.
|
||||||
|
final long delayToToastMilliseconds = 250;
|
||||||
Utils.runOnMainThreadDelayed(() -> {
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
try {
|
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");
|
Logger.printDebug(() -> "Ignoring old scheduled show toast");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Utils.showToastShort(toastNumberOfSegmentsSkipped == 1
|
String message = toastNumberOfSegmentsSkipped == 1
|
||||||
? toastSegmentSkipped.getSkippedToastText()
|
? toastSegmentSkipped.getSkippedToastText()
|
||||||
: str("revanced_sb_skipped_multiple_segments"));
|
: str("revanced_sb_skipped_multiple_segments");
|
||||||
|
|
||||||
|
showToastShortWithTapAction(message, undoAutoSkipRangeToast);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "showSkippedSegmentToast failure", ex);
|
Logger.printException(() -> "showSkippedSegmentToast failure", ex);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -607,13 +719,128 @@ public class SegmentPlaybackController {
|
||||||
}, delayToToastMilliseconds);
|
}, 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.
|
* @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 {
|
try {
|
||||||
if (segment != highlightSegment && segment != segmentCurrentlyPlaying) {
|
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.hideSkipSegmentButton();
|
||||||
SponsorBlockViewController.hideSkipHighlightButton();
|
SponsorBlockViewController.hideSkipHighlightButton();
|
||||||
return;
|
return;
|
||||||
|
|
@ -628,7 +855,7 @@ public class SegmentPlaybackController {
|
||||||
* Injection point
|
* Injection point
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static void setSponsorBarRect(final Object self) {
|
public static void setSponsorBarRect(Object self) {
|
||||||
try {
|
try {
|
||||||
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
|
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
|
|
@ -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.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
|
|
@ -752,7 +973,7 @@ public class SegmentPlaybackController {
|
||||||
final float left = leftPadding + segment.start * videoMillisecondsToPixels;
|
final float left = leftPadding + segment.start * videoMillisecondsToPixels;
|
||||||
final float right;
|
final float right;
|
||||||
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
||||||
right = left + highlightSegmentTimeBarScreenWidth;
|
right = left + HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH;
|
||||||
} else {
|
} else {
|
||||||
right = leftPadding + segment.end * videoMillisecondsToPixels;
|
right = leftPadding + segment.end * videoMillisecondsToPixels;
|
||||||
}
|
}
|
||||||
|
|
@ -762,5 +983,4 @@ public class SegmentPlaybackController {
|
||||||
Logger.printException(() -> "drawSponsorTimeBars failure", ex);
|
Logger.printException(() -> "drawSponsorTimeBars failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -223,13 +223,18 @@ public class SponsorBlockUtils {
|
||||||
Logger.printException(() -> "invalid parameters");
|
Logger.printException(() -> "invalid parameters");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearUnsubmittedSegmentTimes();
|
clearUnsubmittedSegmentTimes();
|
||||||
Utils.runOnBackgroundThread(() -> {
|
Utils.runOnBackgroundThread(() -> {
|
||||||
|
try {
|
||||||
SBRequester.submitSegments(videoId, segmentCategory.keyValue, start, end, videoLength);
|
SBRequester.submitSegments(videoId, segmentCategory.keyValue, start, end, videoLength);
|
||||||
SegmentPlaybackController.executeDownloadSegments(videoId);
|
SegmentPlaybackController.executeDownloadSegments(videoId);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "submitNewSegment failure", ex);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "Unable to submit segment", e);
|
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) {
|
if (segment.recordedAsSkipped || segment.category == SegmentCategory.UNSUBMITTED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -409,7 +414,7 @@ public class SponsorBlockUtils {
|
||||||
return statsNumberFormatter.format(viewCount);
|
return statsNumberFormatter.format(viewCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long parseSegmentTime(@NonNull String time) {
|
private static long parseSegmentTime(String time) {
|
||||||
Matcher matcher = manualEditTimePattern.matcher(time);
|
Matcher matcher = manualEditTimePattern.matcher(time);
|
||||||
if (!matcher.matches()) {
|
if (!matcher.matches()) {
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -419,9 +424,12 @@ public class SponsorBlockUtils {
|
||||||
String secondsStr = matcher.group(4);
|
String secondsStr = matcher.group(4);
|
||||||
String millisecondsStr = matcher.group(6); // Milliseconds is optional.
|
String millisecondsStr = matcher.group(6); // Milliseconds is optional.
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final int hours = (hoursStr != null) ? Integer.parseInt(hoursStr) : 0;
|
final int hours = (hoursStr != null) ? Integer.parseInt(hoursStr) : 0;
|
||||||
|
//noinspection ConstantConditions
|
||||||
final int minutes = Integer.parseInt(minutesStr);
|
final int minutes = Integer.parseInt(minutesStr);
|
||||||
|
//noinspection ConstantConditions
|
||||||
final int seconds = Integer.parseInt(secondsStr);
|
final int seconds = Integer.parseInt(secondsStr);
|
||||||
final int milliseconds;
|
final int milliseconds;
|
||||||
if (millisecondsStr != null) {
|
if (millisecondsStr != null) {
|
||||||
|
|
@ -468,7 +476,6 @@ public class SponsorBlockUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getTimeSavedString(long totalSecondsSaved) {
|
public static String getTimeSavedString(long totalSecondsSaved) {
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
|
||||||
Duration duration = Duration.ofSeconds(totalSecondsSaved);
|
Duration duration = Duration.ofSeconds(totalSecondsSaved);
|
||||||
final long hours = duration.toHours();
|
final long hours = duration.toHours();
|
||||||
final long minutes = duration.toMinutes() % 60;
|
final long minutes = duration.toMinutes() % 60;
|
||||||
|
|
@ -488,12 +495,10 @@ public class SponsorBlockUtils {
|
||||||
|
|
||||||
return str("revanced_sb_stats_saved_second_format", secondsFormatted);
|
return str("revanced_sb_stats_saved_second_format", secondsFormatted);
|
||||||
}
|
}
|
||||||
return "error"; // will never be reached. YouTube requires Android O or greater
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class EditByHandSaveDialogListener implements DialogInterface.OnClickListener {
|
private static class EditByHandSaveDialogListener implements DialogInterface.OnClickListener {
|
||||||
boolean settingStart;
|
private boolean settingStart;
|
||||||
WeakReference<EditText> editTextRef = new WeakReference<>(null);
|
private WeakReference<EditText> editTextRef = new WeakReference<>(null);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
|
@ -512,10 +517,11 @@ public class SponsorBlockUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingStart)
|
if (settingStart) {
|
||||||
newSponsorSegmentStartMillis = Math.max(time, 0);
|
newSponsorSegmentStartMillis = Math.max(time, 0);
|
||||||
else
|
} else {
|
||||||
newSponsorSegmentEndMillis = time;
|
newSponsorSegmentEndMillis = time;
|
||||||
|
}
|
||||||
|
|
||||||
if (which == DialogInterface.BUTTON_NEUTRAL)
|
if (which == DialogInterface.BUTTON_NEUTRAL)
|
||||||
editByHandDialogListener.onClick(dialog, settingStart ?
|
editByHandDialogListener.onClick(dialog, settingStart ?
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ import java.util.Objects;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.sf;
|
import static app.revanced.extension.shared.StringRef.sf;
|
||||||
|
|
||||||
|
import android.util.Range;
|
||||||
|
|
||||||
public class SponsorSegment implements Comparable<SponsorSegment> {
|
public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||||
|
|
||||||
public enum SegmentVote {
|
public enum SegmentVote {
|
||||||
UPVOTE(sf("revanced_sb_vote_upvote"), 1,false),
|
UPVOTE(sf("revanced_sb_vote_upvote"), 1,false),
|
||||||
DOWNVOTE(sf("revanced_sb_vote_downvote"), 0, true),
|
DOWNVOTE(sf("revanced_sb_vote_downvote"), 0, true),
|
||||||
|
|
@ -38,7 +41,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||||
@NonNull
|
@NonNull
|
||||||
public final SegmentCategory category;
|
public final SegmentCategory category;
|
||||||
/**
|
/**
|
||||||
* NULL if segment is unsubmitted
|
* NULL if segment is unsubmitted.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public final String UUID;
|
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) {
|
public boolean startIsNear(long videoTime, long nearThreshold) {
|
||||||
return Math.abs(start - videoTime) <= 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) {
|
public boolean endIsNear(long videoTime, long nearThreshold) {
|
||||||
return Math.abs(end - videoTime) <= 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) {
|
public boolean containsTime(long videoTime) {
|
||||||
return start <= videoTime && videoTime < end;
|
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) {
|
public boolean containsSegment(SponsorSegment other) {
|
||||||
return start <= other.start && other.end <= end;
|
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.
|
* @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
|
@NonNull
|
||||||
public String getSkipButtonText() {
|
public String getSkipButtonText() {
|
||||||
|
|
@ -107,7 +131,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 'skipped segment' toast message
|
* @return 'skipped segment' toast message.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public String getSkippedToastText() {
|
public String getSkippedToastText() {
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ public class SBRequester {
|
||||||
private 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()) {
|
if (Settings.SB_TOAST_ON_CONNECTION_ERROR.get()) {
|
||||||
Utils.showToastShort(toastMessage);
|
Utils.showToastShort(toastMessage);
|
||||||
}
|
}
|
||||||
|
|
@ -63,7 +63,7 @@ public class SBRequester {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static SponsorSegment[] getSegments(@NonNull String videoId) {
|
public static SponsorSegment[] getSegments(String videoId) {
|
||||||
Utils.verifyOffMainThread();
|
Utils.verifyOffMainThread();
|
||||||
List<SponsorSegment> segments = new ArrayList<>();
|
List<SponsorSegment> segments = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
|
|
@ -113,10 +113,10 @@ public class SBRequester {
|
||||||
Logger.printException(() -> "getSegments failure", ex);
|
Logger.printException(() -> "getSegments failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crude debug tests to verify random features
|
// Crude debug tests to verify random features.
|
||||||
// Could benefit from:
|
// Could benefit from:
|
||||||
// 1) collection of YouTube videos with test segment times (verify client skip timing matches the video, verify seekbar draws correctly)
|
// 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)
|
// 2) Unit tests (verify everything else).
|
||||||
//noinspection ConstantValue
|
//noinspection ConstantValue
|
||||||
if (false) {
|
if (false) {
|
||||||
segments.clear();
|
segments.clear();
|
||||||
|
|
@ -140,10 +140,30 @@ public class SBRequester {
|
||||||
segments.add(new SponsorSegment(SegmentCategory.SELF_PROMO, "debug", 200000, 330000, false));
|
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]);
|
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) {
|
long startTime, long endTime, long videoLength) {
|
||||||
Utils.verifyOffMainThread();
|
Utils.verifyOffMainThread();
|
||||||
|
|
||||||
|
|
@ -189,7 +209,7 @@ public class SBRequester {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void sendSegmentSkippedViewedRequest(@NonNull SponsorSegment segment) {
|
public static void sendSegmentSkippedViewedRequest(SponsorSegment segment) {
|
||||||
Utils.verifyOffMainThread();
|
Utils.verifyOffMainThread();
|
||||||
try {
|
try {
|
||||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.VIEWED_SEGMENT, segment.UUID);
|
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);
|
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);
|
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(() -> {
|
Utils.runOnBackgroundThread(() -> {
|
||||||
try {
|
try {
|
||||||
String segmentUuid = segment.UUID;
|
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.
|
* @return NULL if the call was successful. If unsuccessful, an error message is returned.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static String setUsername(@NonNull String username) {
|
public static String setUsername(String username) {
|
||||||
Utils.verifyOffMainThread();
|
Utils.verifyOffMainThread();
|
||||||
try {
|
try {
|
||||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.CHANGE_USERNAME, SponsorBlockSettings.getSBPrivateUserID(), username);
|
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.CHANGE_USERNAME, SponsorBlockSettings.getSBPrivateUserID(), username);
|
||||||
|
|
@ -320,14 +340,14 @@ public class SBRequester {
|
||||||
|
|
||||||
// helpers
|
// 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);
|
HttpURLConnection connection = Requester.getConnectionFromRoute(Settings.SB_API_URL.get(), route, params);
|
||||||
connection.setConnectTimeout(TIMEOUT_TCP_DEFAULT_MILLISECONDS);
|
connection.setConnectTimeout(TIMEOUT_TCP_DEFAULT_MILLISECONDS);
|
||||||
connection.setReadTimeout(TIMEOUT_HTTP_DEFAULT_MILLISECONDS);
|
connection.setReadTimeout(TIMEOUT_HTTP_DEFAULT_MILLISECONDS);
|
||||||
return connection;
|
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));
|
return Requester.parseJSONObject(getConnectionFromRoute(route, params));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package app.revanced.extension.youtube.sponsorblock.ui;
|
package app.revanced.extension.youtube.sponsorblock.ui;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
import static app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController.SponsorBlockDuration;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
|
|
@ -28,6 +29,7 @@ import java.util.List;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
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.shared.settings.preference.ResettableEditTextPreference;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
|
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
|
||||||
|
|
@ -62,6 +64,8 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||||
private SwitchPreference trackSkips;
|
private SwitchPreference trackSkips;
|
||||||
private SwitchPreference showTimeWithoutSegments;
|
private SwitchPreference showTimeWithoutSegments;
|
||||||
private SwitchPreference toastOnConnectionError;
|
private SwitchPreference toastOnConnectionError;
|
||||||
|
private CustomDialogListPreference autoHideSkipSegmentButtonDuration;
|
||||||
|
private CustomDialogListPreference showSkipToastDuration;
|
||||||
|
|
||||||
private ResettableEditTextPreference newSegmentStep;
|
private ResettableEditTextPreference newSegmentStep;
|
||||||
private ResettableEditTextPreference minSegmentDuration;
|
private ResettableEditTextPreference minSegmentDuration;
|
||||||
|
|
@ -69,8 +73,8 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||||
private EditTextPreference importExport;
|
private EditTextPreference importExport;
|
||||||
private Preference apiUrl;
|
private Preference apiUrl;
|
||||||
|
|
||||||
private final List<SegmentCategoryListPreference> segmentCategories = new ArrayList<>();
|
|
||||||
private PreferenceCategory segmentCategory;
|
private PreferenceCategory segmentCategory;
|
||||||
|
private final List<SegmentCategoryListPreference> segmentCategories = new ArrayList<>();
|
||||||
|
|
||||||
public SponsorBlockPreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public SponsorBlockPreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
|
@ -114,17 +118,23 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||||
votingEnabled.setChecked(Settings.SB_VOTING_BUTTON.get());
|
votingEnabled.setChecked(Settings.SB_VOTING_BUTTON.get());
|
||||||
votingEnabled.setEnabled(enabled);
|
votingEnabled.setEnabled(enabled);
|
||||||
|
|
||||||
autoHideSkipSegmentButton.setEnabled(enabled);
|
|
||||||
autoHideSkipSegmentButton.setChecked(Settings.SB_AUTO_HIDE_SKIP_BUTTON.get());
|
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.setChecked(Settings.SB_COMPACT_SKIP_BUTTON.get());
|
||||||
compactSkipButton.setEnabled(enabled);
|
compactSkipButton.setEnabled(enabled);
|
||||||
|
|
||||||
|
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
|
||||||
|
showSkipToast.setEnabled(enabled);
|
||||||
|
|
||||||
squareLayout.setChecked(Settings.SB_SQUARE_LAYOUT.get());
|
squareLayout.setChecked(Settings.SB_SQUARE_LAYOUT.get());
|
||||||
squareLayout.setEnabled(enabled);
|
squareLayout.setEnabled(enabled);
|
||||||
|
|
||||||
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
|
showSkipToastDuration.setValue(Settings.SB_TOAST_ON_SKIP_DURATION.get().toString());
|
||||||
showSkipToast.setEnabled(enabled);
|
showSkipToastDuration.setEnabled(Settings.SB_TOAST_ON_SKIP_DURATION.isAvailable());
|
||||||
|
|
||||||
toastOnConnectionError.setChecked(Settings.SB_TOAST_ON_CONNECTION_ERROR.get());
|
toastOnConnectionError.setChecked(Settings.SB_TOAST_ON_CONNECTION_ERROR.get());
|
||||||
toastOnConnectionError.setEnabled(enabled);
|
toastOnConnectionError.setEnabled(enabled);
|
||||||
|
|
@ -205,17 +215,6 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||||
});
|
});
|
||||||
appearanceCategory.addPreference(votingEnabled);
|
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 = new SwitchPreference(context);
|
||||||
compactSkipButton.setTitle(str("revanced_sb_enable_compact_skip_button"));
|
compactSkipButton.setTitle(str("revanced_sb_enable_compact_skip_button"));
|
||||||
compactSkipButton.setSummaryOn(str("revanced_sb_enable_compact_skip_button_sum_on"));
|
compactSkipButton.setSummaryOn(str("revanced_sb_enable_compact_skip_button_sum_on"));
|
||||||
|
|
@ -227,25 +226,38 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||||
});
|
});
|
||||||
appearanceCategory.addPreference(compactSkipButton);
|
appearanceCategory.addPreference(compactSkipButton);
|
||||||
|
|
||||||
squareLayout = new SwitchPreference(context);
|
autoHideSkipSegmentButton = new SwitchPreference(context);
|
||||||
squareLayout.setTitle(str("revanced_sb_square_layout"));
|
autoHideSkipSegmentButton.setTitle(str("revanced_sb_enable_auto_hide_skip_segment_button"));
|
||||||
squareLayout.setSummaryOn(str("revanced_sb_square_layout_sum_on"));
|
autoHideSkipSegmentButton.setSummaryOn(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_on"));
|
||||||
squareLayout.setSummaryOff(str("revanced_sb_square_layout_sum_off"));
|
autoHideSkipSegmentButton.setSummaryOff(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_off"));
|
||||||
squareLayout.setOnPreferenceChangeListener((preference1, newValue) -> {
|
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
Settings.SB_SQUARE_LAYOUT.save((Boolean) newValue);
|
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
|
||||||
updateUI();
|
updateUI();
|
||||||
return true;
|
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 = new SwitchPreference(context);
|
||||||
showSkipToast.setTitle(str("revanced_sb_general_skiptoast"));
|
showSkipToast.setTitle(str("revanced_sb_general_skiptoast"));
|
||||||
showSkipToast.setSummaryOn(str("revanced_sb_general_skiptoast_sum_on"));
|
showSkipToast.setSummaryOn(str("revanced_sb_general_skiptoast_sum_on"));
|
||||||
showSkipToast.setSummaryOff(str("revanced_sb_general_skiptoast_sum_off"));
|
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) -> {
|
showSkipToast.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
Settings.SB_TOAST_ON_SKIP.save((Boolean) newValue);
|
Settings.SB_TOAST_ON_SKIP.save((Boolean) newValue);
|
||||||
updateUI();
|
updateUI();
|
||||||
|
|
@ -253,6 +265,20 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||||
});
|
});
|
||||||
appearanceCategory.addPreference(showSkipToast);
|
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 = new SwitchPreference(context);
|
||||||
showTimeWithoutSegments.setTitle(str("revanced_sb_general_time_without"));
|
showTimeWithoutSegments.setTitle(str("revanced_sb_general_time_without"));
|
||||||
showTimeWithoutSegments.setSummaryOn(str("revanced_sb_general_time_without_sum_on"));
|
showTimeWithoutSegments.setSummaryOn(str("revanced_sb_general_time_without_sum_on"));
|
||||||
|
|
@ -264,6 +290,17 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
||||||
});
|
});
|
||||||
appearanceCategory.addPreference(showTimeWithoutSegments);
|
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 = new PreferenceCategory(context);
|
||||||
segmentCategory.setTitle(str("revanced_sb_diff_segments"));
|
segmentCategory.setTitle(str("revanced_sb_diff_segments"));
|
||||||
addPreference(segmentCategory);
|
addPreference(segmentCategory);
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ public class SponsorBlockViewController {
|
||||||
setSkipButtonMargins(skipSponsorButton, isWatchFullScreen);
|
setSkipButtonMargins(skipSponsorButton, isWatchFullScreen);
|
||||||
setViewVisibility(skipSponsorButton, skipSegment != null);
|
setViewVisibility(skipSponsorButton, skipSegment != null);
|
||||||
} catch (Exception ex) {
|
} 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>
|
<item>@string/revanced_language_ZH</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="revanced_language_entry_values">
|
<string-array name="revanced_language_entry_values">
|
||||||
|
<!-- Extension enum names. -->
|
||||||
<item>DEFAULT</item>
|
<item>DEFAULT</item>
|
||||||
<item>AM</item>
|
<item>AM</item>
|
||||||
<item>AR</item>
|
<item>AR</item>
|
||||||
|
|
@ -129,7 +130,6 @@
|
||||||
<item>iOS TV</item>
|
<item>iOS TV</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="revanced_spoof_video_streams_client_type_entry_values">
|
<string-array name="revanced_spoof_video_streams_client_type_entry_values">
|
||||||
<!-- Extension enum names. -->
|
|
||||||
<item>ANDROID_UNPLUGGED</item>
|
<item>ANDROID_UNPLUGGED</item>
|
||||||
<item>ANDROID_VR_NO_AUTH</item>
|
<item>ANDROID_VR_NO_AUTH</item>
|
||||||
<item>IOS_UNPLUGGED</item>
|
<item>IOS_UNPLUGGED</item>
|
||||||
|
|
@ -146,7 +146,6 @@
|
||||||
<item>@string/revanced_swipe_overlay_style_entry_7</item>
|
<item>@string/revanced_swipe_overlay_style_entry_7</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="revanced_swipe_overlay_style_entry_values">
|
<string-array name="revanced_swipe_overlay_style_entry_values">
|
||||||
<!-- Extension enum names. -->
|
|
||||||
<item>HORIZONTAL</item>
|
<item>HORIZONTAL</item>
|
||||||
<item>HORIZONTAL_MINIMAL_TOP</item>
|
<item>HORIZONTAL_MINIMAL_TOP</item>
|
||||||
<item>HORIZONTAL_MINIMAL_CENTER</item>
|
<item>HORIZONTAL_MINIMAL_CENTER</item>
|
||||||
|
|
@ -180,7 +179,6 @@
|
||||||
<item>@string/revanced_change_form_factor_entry_4</item>
|
<item>@string/revanced_change_form_factor_entry_4</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="revanced_change_form_factor_entry_values">
|
<string-array name="revanced_change_form_factor_entry_values">
|
||||||
<!-- Extension enum names. -->
|
|
||||||
<item>DEFAULT</item>
|
<item>DEFAULT</item>
|
||||||
<item>SMALL</item>
|
<item>SMALL</item>
|
||||||
<item>LARGE</item>
|
<item>LARGE</item>
|
||||||
|
|
@ -210,7 +208,6 @@
|
||||||
<item>@string/revanced_exit_fullscreen_entry_4</item>
|
<item>@string/revanced_exit_fullscreen_entry_4</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="revanced_exit_fullscreen_entry_values">
|
<string-array name="revanced_exit_fullscreen_entry_values">
|
||||||
<!-- Enum names from the extension. -->
|
|
||||||
<item>DISABLED</item>
|
<item>DISABLED</item>
|
||||||
<item>PORTRAIT</item>
|
<item>PORTRAIT</item>
|
||||||
<item>LANDSCAPE</item>
|
<item>LANDSCAPE</item>
|
||||||
|
|
@ -229,7 +226,6 @@
|
||||||
<item>@string/revanced_miniplayer_type_entry_7</item>
|
<item>@string/revanced_miniplayer_type_entry_7</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="revanced_miniplayer_type_entry_values">
|
<string-array name="revanced_miniplayer_type_entry_values">
|
||||||
<!-- Extension enum names. -->
|
|
||||||
<item>DISABLED</item>
|
<item>DISABLED</item>
|
||||||
<item>DEFAULT</item>
|
<item>DEFAULT</item>
|
||||||
<item>MINIMAL</item>
|
<item>MINIMAL</item>
|
||||||
|
|
@ -330,13 +326,38 @@
|
||||||
<item>YOUR_CLIPS</item>
|
<item>YOUR_CLIPS</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</patch>
|
</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">
|
<patch id="layout.shortsplayer.shortsPlayerTypePatch">
|
||||||
<string-array name="revanced_shorts_player_type_legacy_entries">
|
<string-array name="revanced_shorts_player_type_legacy_entries">
|
||||||
<item>@string/revanced_shorts_player_type_shorts</item>
|
<item>@string/revanced_shorts_player_type_shorts</item>
|
||||||
<item>@string/revanced_shorts_player_type_regular_player</item>
|
<item>@string/revanced_shorts_player_type_regular_player</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="revanced_shorts_player_type_legacy_entry_values">
|
<string-array name="revanced_shorts_player_type_legacy_entry_values">
|
||||||
<!-- Extension enum names. -->
|
|
||||||
<item>SHORTS_PLAYER</item>
|
<item>SHORTS_PLAYER</item>
|
||||||
<item>REGULAR_PLAYER</item>
|
<item>REGULAR_PLAYER</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
@ -346,7 +367,6 @@
|
||||||
<item>@string/revanced_shorts_player_type_regular_player_fullscreen</item>
|
<item>@string/revanced_shorts_player_type_regular_player_fullscreen</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="revanced_shorts_player_type_entry_values">
|
<string-array name="revanced_shorts_player_type_entry_values">
|
||||||
<!-- Enum names from extension -->
|
|
||||||
<item>SHORTS_PLAYER</item>
|
<item>SHORTS_PLAYER</item>
|
||||||
<item>REGULAR_PLAYER</item>
|
<item>REGULAR_PLAYER</item>
|
||||||
<item>REGULAR_PLAYER_FULLSCREEN</item>
|
<item>REGULAR_PLAYER_FULLSCREEN</item>
|
||||||
|
|
@ -360,7 +380,6 @@
|
||||||
<item>@string/revanced_alt_thumbnail_options_entry_4</item>
|
<item>@string/revanced_alt_thumbnail_options_entry_4</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="revanced_alt_thumbnail_options_entry_values">
|
<string-array name="revanced_alt_thumbnail_options_entry_values">
|
||||||
<!-- Extension enum names. -->
|
|
||||||
<item>ORIGINAL</item>
|
<item>ORIGINAL</item>
|
||||||
<item>DEARROW</item>
|
<item>DEARROW</item>
|
||||||
<item>DEARROW_STILL_IMAGES</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">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_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_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_auto_hide_skip_button_duration">Skip button duration</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_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_sum_off">Toast is not shown. Tap here to see an example</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">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_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_create_segment_category">Creating new segments</string>
|
||||||
<string name="revanced_sb_enable_create_segment">Show Create new segment button</string>
|
<string name="revanced_sb_enable_create_segment">Show Create new segment button</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue