feat(youtube/sponsorblock): skip to video highlight (#352)

This commit is contained in:
LisoUseInAIKyrios 2023-04-16 22:18:32 +04:00 committed by GitHub
parent 76a01d1b7c
commit 03f09cf7bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 439 additions and 296 deletions

View File

@ -28,7 +28,7 @@ public class PlayerTypeHookPatch {
LogHelper.printException(() -> "Unknown PlayerType encountered: " + type); LogHelper.printException(() -> "Unknown PlayerType encountered: " + type);
} else { } else {
PlayerType.setCurrent(newType); PlayerType.setCurrent(newType);
LogHelper.printDebug(() -> "YouTubePlayerOverlaysLayout player type was updated to " + newType); LogHelper.printDebug(() -> "PlayerType was updated to: " + newType);
} }
} }
} }

View File

@ -64,8 +64,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
try { try {
final boolean enabled = SettingsEnum.SB_ENABLED.getBoolean(); final boolean enabled = SettingsEnum.SB_ENABLED.getBoolean();
if (!enabled) { if (!enabled) {
SponsorBlockViewController.hideSkipButton(); SponsorBlockViewController.hideAll();
SponsorBlockViewController.hideNewSegmentLayout();
SegmentPlaybackController.setCurrentVideoId(null); SegmentPlaybackController.setCurrentVideoId(null);
} else if (!SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.getBoolean()) { } else if (!SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.getBoolean()) {
SponsorBlockViewController.hideNewSegmentLayout(); SponsorBlockViewController.hideNewSegmentLayout();
@ -141,12 +140,13 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
addNewSegment.setOnPreferenceChangeListener((preference1, o) -> { addNewSegment.setOnPreferenceChangeListener((preference1, o) -> {
Boolean newValue = (Boolean) o; Boolean newValue = (Boolean) o;
if (newValue && !SettingsEnum.SB_SEEN_GUIDELINES.getBoolean()) { if (newValue && !SettingsEnum.SB_SEEN_GUIDELINES.getBoolean()) {
SettingsEnum.SB_SEEN_GUIDELINES.saveValue(true);
new AlertDialog.Builder(preference1.getContext()) new AlertDialog.Builder(preference1.getContext())
.setTitle(str("sb_guidelines_popup_title")) .setTitle(str("sb_guidelines_popup_title"))
.setMessage(str("sb_guidelines_popup_content")) .setMessage(str("sb_guidelines_popup_content"))
.setNegativeButton(str("sb_guidelines_popup_already_read"), null) .setNegativeButton(str("sb_guidelines_popup_already_read"), null)
.setPositiveButton(str("sb_guidelines_popup_open"), (dialogInterface, i) -> openGuidelines()) .setPositiveButton(str("sb_guidelines_popup_open"), (dialogInterface, i) -> openGuidelines())
.setOnDismissListener(dialog -> SettingsEnum.SB_SEEN_GUIDELINES.saveValue(true))
.setCancelable(false)
.show(); .show();
} }
SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.saveValue(newValue); SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.saveValue(newValue);
@ -350,7 +350,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
segmentCategory.removeAll(); segmentCategory.removeAll();
Activity activity = getActivity(); Activity activity = getActivity();
for (SegmentCategory category : SegmentCategory.valuesWithoutUnsubmitted()) { for (SegmentCategory category : SegmentCategory.categoriesWithoutUnsubmitted()) {
segmentCategory.addPreference(new SegmentCategoryListPreference(activity, category)); segmentCategory.addPreference(new SegmentCategoryListPreference(activity, category));
} }
} catch (Exception ex) { } catch (Exception ex) {

View File

@ -16,7 +16,7 @@ enum class PlayerType {
WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED, WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED,
WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED, WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED,
WATCH_WHILE_SLIDING_FULLSCREEN_DISMISSED, WATCH_WHILE_SLIDING_FULLSCREEN_DISMISSED,
INLINE_MINIMAL, INLINE_MINIMAL, // home feed video playback
VIRTUAL_REALITY_FULLSCREEN, VIRTUAL_REALITY_FULLSCREEN,
WATCH_WHILE_PICTURE_IN_PICTURE; WATCH_WHILE_PICTURE_IN_PICTURE;

View File

@ -5,6 +5,7 @@ import static app.revanced.integrations.utils.StringRef.str;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Rect; import android.graphics.Rect;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.TypedValue;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -30,13 +31,39 @@ import app.revanced.integrations.utils.ReVancedUtils;
* 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 highlight segment manual skip.
* Because there is no scheduled hide of a skip to highlight,
* effectively this time value is rounded up to the next second.
*/
private static final long HIGHLIGHT_SEGMENT_DURATION_TO_SHOW_SKIP_PROMPT = 3800;
/*
* Highlight segments have zero length, as they are a point in time.
* Draw them on screen using a fixed width bar.
* Value is independent of device dpi.
*/
private static final int HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH = 7;
@Nullable @Nullable
private static String currentVideoId; private static String currentVideoId;
@Nullable @Nullable
private static SponsorSegment[] segmentsOfCurrentVideo; private static SponsorSegment[] segmentsOfCurrentVideo;
/** /**
* Current segment that user can manually skip * Highlight segment, if one exists.
*/
@Nullable
private static SponsorSegment highlightSegment;
/**
* Because loading can take time, show the skip to highlight for a few seconds after the segments load.
* This is the end time (in milliseconds) to no longer show the initial display skip to highlight.
*/
private static long highlightSegmentInitialShowEndTime;
/**
* Current (non-highlight) segment that user can manually skip
*/ */
@Nullable @Nullable
private static SponsorSegment segmentCurrentlyPlaying; private static SponsorSegment segmentCurrentlyPlaying;
@ -68,23 +95,28 @@ public class SegmentPlaybackController {
Arrays.sort(segments); Arrays.sort(segments);
segmentsOfCurrentVideo = segments; segmentsOfCurrentVideo = segments;
calculateTimeWithoutSegments(); calculateTimeWithoutSegments();
for (SponsorSegment segment : segments) {
if (segment.category == SegmentCategory.HIGHLIGHT) {
highlightSegment = segment;
return;
}
}
highlightSegment = null;
} }
public static boolean currentVideoHasSegments() { public static boolean currentVideoHasSegments() {
return segmentsOfCurrentVideo != null && segmentsOfCurrentVideo.length > 0; return segmentsOfCurrentVideo != null && segmentsOfCurrentVideo.length > 0;
} }
@Nullable
static String getCurrentVideoId() {
return currentVideoId;
}
/** /**
* Clears all downloaded data * Clears all downloaded data
*/ */
private static void clearData() { private static void clearData() {
currentVideoId = null; currentVideoId = null;
segmentsOfCurrentVideo = null; segmentsOfCurrentVideo = null;
highlightSegment = null;
highlightSegmentInitialShowEndTime = 0;
timeWithoutSegments = null; timeWithoutSegments = null;
segmentCurrentlyPlaying = null; segmentCurrentlyPlaying = null;
scheduledUpcomingSegment = null; // prevent any existing scheduled skip from running scheduledUpcomingSegment = null; // prevent any existing scheduled skip from running
@ -102,8 +134,7 @@ public class SegmentPlaybackController {
ReVancedUtils.verifyOnMainThread(); ReVancedUtils.verifyOnMainThread();
SponsorBlockSettings.initialize(); SponsorBlockSettings.initialize();
clearData(); clearData();
SponsorBlockViewController.hideSkipButton(); SponsorBlockViewController.hideAll();
SponsorBlockViewController.hideNewSegmentLayout();
SponsorBlockUtils.clearUnsubmittedSegmentTimes(); SponsorBlockUtils.clearUnsubmittedSegmentTimes();
LogHelper.printDebug(() -> "Initialized SponsorBlock"); LogHelper.printDebug(() -> "Initialized SponsorBlock");
} catch (Exception ex) { } catch (Exception ex) {
@ -164,7 +195,19 @@ public class SegmentPlaybackController {
return; return;
} }
setSegmentsOfCurrentVideo(segments); setSegmentsOfCurrentVideo(segments);
setVideoTime(VideoInformation.getVideoTime()); // check for any skips now, instead of waiting for the next update
final long videoTime = VideoInformation.getVideoTime();
// if the current video time is before the highlight
if (highlightSegment != null && videoTime < highlightSegment.end) {
if (highlightSegment.shouldAutoSkip()) {
skipSegment(highlightSegment, false);
return;
}
highlightSegmentInitialShowEndTime = System.currentTimeMillis()
+ HIGHLIGHT_SEGMENT_DURATION_TO_SHOW_SKIP_PROMPT;
}
// check for any skips now, instead of waiting for the next update to setVideoTime()
setVideoTime(videoTime);
}); });
} catch (Exception ex) { } catch (Exception ex) {
LogHelper.printException(() -> "executeDownloadSegments failure", ex); LogHelper.printException(() -> "executeDownloadSegments failure", ex);
@ -196,7 +239,8 @@ public class SegmentPlaybackController {
for (final SponsorSegment segment : segmentsOfCurrentVideo) { for (final SponsorSegment segment : segmentsOfCurrentVideo) {
if (segment.category.behaviour == CategoryBehaviour.SHOW_IN_SEEKBAR if (segment.category.behaviour == CategoryBehaviour.SHOW_IN_SEEKBAR
|| segment.category.behaviour == CategoryBehaviour.IGNORE) { || segment.category.behaviour == CategoryBehaviour.IGNORE
|| segment.category == SegmentCategory.HIGHLIGHT) {
continue; continue;
} }
if (segment.end <= millis) { if (segment.end <= millis) {
@ -217,7 +261,7 @@ public class SegmentPlaybackController {
// Also prevents showing the skip button if user seeks into the last half second of the segment. // Also prevents showing the skip button if user seeks into the last half second of the segment.
final long minMillisOfSegmentRemainingThreshold = 500; final long minMillisOfSegmentRemainingThreshold = 500;
if (segmentCurrentlyPlaying == segment if (segmentCurrentlyPlaying == segment
|| !segment.timeIsNearEnd(millis, minMillisOfSegmentRemainingThreshold)) { || !segment.endIsNear(millis, minMillisOfSegmentRemainingThreshold)) {
foundCurrentSegment = segment; foundCurrentSegment = segment;
} else { } else {
LogHelper.printDebug(() -> "Ignoring segment that ends very soon: " + segment); LogHelper.printDebug(() -> "Ignoring segment that ends very soon: " + segment);
@ -248,7 +292,7 @@ public class SegmentPlaybackController {
// This check is needed to prevent scheduled hide and show from clashing with each other. // This check is needed to prevent scheduled hide and show from clashing with each other.
final long minTimeBetweenStartEndOfSegments = 1000; final long minTimeBetweenStartEndOfSegments = 1000;
if (foundCurrentSegment == null if (foundCurrentSegment == null
|| !foundCurrentSegment.timeIsNearEnd(segment.start, minTimeBetweenStartEndOfSegments)) { || !foundCurrentSegment.endIsNear(segment.start, minTimeBetweenStartEndOfSegments)) {
foundUpcomingSegment = segment; foundUpcomingSegment = segment;
} else { } else {
LogHelper.printDebug(() -> "Not scheduling segment (start time is near end of current segment): " + segment); LogHelper.printDebug(() -> "Not scheduling segment (start time is near end of current segment): " + segment);
@ -256,16 +300,22 @@ public class SegmentPlaybackController {
} }
} }
if (highlightSegment != null && (millis < HIGHLIGHT_SEGMENT_DURATION_TO_SHOW_SKIP_PROMPT
|| System.currentTimeMillis() < highlightSegmentInitialShowEndTime)) {
SponsorBlockViewController.showSkipHighlightButton(highlightSegment);
} else {
SponsorBlockViewController.hideSkipHighlightButton();
}
if (segmentCurrentlyPlaying != foundCurrentSegment) { if (segmentCurrentlyPlaying != foundCurrentSegment) {
if (foundCurrentSegment == null) { if (foundCurrentSegment == null) {
LogHelper.printDebug(() -> "Hiding segment: " + segmentCurrentlyPlaying); LogHelper.printDebug(() -> "Hiding segment: " + segmentCurrentlyPlaying);
segmentCurrentlyPlaying = null; segmentCurrentlyPlaying = null;
SponsorBlockViewController.hideSkipButton(); SponsorBlockViewController.hideSkipSegmentButton();
} else { } else {
segmentCurrentlyPlaying = foundCurrentSegment; segmentCurrentlyPlaying = foundCurrentSegment;
LogHelper.printDebug(() -> "Showing segment: " + segmentCurrentlyPlaying); LogHelper.printDebug(() -> "Showing segment: " + segmentCurrentlyPlaying);
SponsorBlockViewController.showSkipButton(foundCurrentSegment); SponsorBlockViewController.showSkipSegmentButton(foundCurrentSegment);
} }
} }
@ -274,7 +324,7 @@ public class SegmentPlaybackController {
// schedule a hide, only if the segment end is near // schedule a hide, only if the segment end is near
final SponsorSegment segmentToHide = final SponsorSegment segmentToHide =
(foundCurrentSegment != null && foundCurrentSegment.timeIsNearEnd(millis, lookAheadMilliseconds)) (foundCurrentSegment != null && foundCurrentSegment.endIsNear(millis, lookAheadMilliseconds))
? foundCurrentSegment ? foundCurrentSegment
: null; : null;
@ -294,7 +344,7 @@ public class SegmentPlaybackController {
scheduledHideSegment = null; scheduledHideSegment = null;
final long videoTime = VideoInformation.getVideoTime(); final long videoTime = VideoInformation.getVideoTime();
if (!segmentToHide.timeIsNearEnd(videoTime, videoInformationTimeUpdateThresholdMilliseconds)) { if (!segmentToHide.endIsNear(videoTime, videoInformationTimeUpdateThresholdMilliseconds)) {
// current video time is not what's expected. User paused playback // current video time is not what's expected. User paused playback
LogHelper.printDebug(() -> "Ignoring outdated scheduled hide: " + segmentToHide LogHelper.printDebug(() -> "Ignoring outdated scheduled hide: " + segmentToHide
+ " videoInformation time: " + videoTime); + " videoInformation time: " + videoTime);
@ -306,7 +356,7 @@ public class SegmentPlaybackController {
// 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
segmentCurrentlyPlaying = null; segmentCurrentlyPlaying = null;
SponsorBlockViewController.hideSkipButton(); SponsorBlockViewController.hideSkipSegmentButton();
setVideoTime(segmentToHide.end); setVideoTime(segmentToHide.end);
}, delayUntilHide); }, delayUntilHide);
} }
@ -330,7 +380,7 @@ public class SegmentPlaybackController {
scheduledUpcomingSegment = null; scheduledUpcomingSegment = null;
final long videoTime = VideoInformation.getVideoTime(); final long videoTime = VideoInformation.getVideoTime();
if (!segmentToSkip.timeIsNearStart(videoTime, if (!segmentToSkip.startIsNear(videoTime,
videoInformationTimeUpdateThresholdMilliseconds)) { videoInformationTimeUpdateThresholdMilliseconds)) {
// current video time is not what's expected. User paused playback // current video time is not what's expected. User paused playback
LogHelper.printDebug(() -> "Ignoring outdated scheduled segment: " + segmentToSkip LogHelper.printDebug(() -> "Ignoring outdated scheduled segment: " + segmentToSkip
@ -343,7 +393,7 @@ public class SegmentPlaybackController {
} else { } else {
LogHelper.printDebug(() -> "Running scheduled show segment: " + segmentToSkip); LogHelper.printDebug(() -> "Running scheduled show segment: " + segmentToSkip);
segmentCurrentlyPlaying = segmentToSkip; segmentCurrentlyPlaying = segmentToSkip;
SponsorBlockViewController.showSkipButton(segmentToSkip); SponsorBlockViewController.showSkipSegmentButton(segmentToSkip);
} }
}, delayUntilSkip); }, delayUntilSkip);
} }
@ -357,7 +407,7 @@ public class SegmentPlaybackController {
private static SponsorSegment lastSegmentSkipped; private static SponsorSegment lastSegmentSkipped;
private static long lastSegmentSkippedTime; private static long lastSegmentSkippedTime;
private static void skipSegment(@NonNull SponsorSegment segment, boolean userManuallySkipped) { private static void skipSegment(@NonNull SponsorSegment segmentToSkip, boolean userManuallySkipped) {
try { try {
// If trying to seek to end of the video, YouTube can seek just short of the actual end. // If trying to seek to end of the video, YouTube can seek just short of the actual end.
// (especially if the video does not end on a whole second boundary). // (especially if the video does not end on a whole second boundary).
@ -365,23 +415,27 @@ public class SegmentPlaybackController {
// Check for and ignore repeated skip attempts of the same segment over a short time period. // Check for and ignore repeated skip attempts of the same segment over a short time period.
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
final long minimumMillisecondsBetweenSkippingSameSegment = 500; final long minimumMillisecondsBetweenSkippingSameSegment = 500;
if ((lastSegmentSkipped == segment) && (now - lastSegmentSkippedTime < minimumMillisecondsBetweenSkippingSameSegment)) { if ((lastSegmentSkipped == segmentToSkip) && (now - lastSegmentSkippedTime < minimumMillisecondsBetweenSkippingSameSegment)) {
LogHelper.printDebug(() -> "Ignoring skip segment request (already skipped as close as possible): " + segment); LogHelper.printDebug(() -> "Ignoring skip segment request (already skipped as close as possible): " + segmentToSkip);
return; return;
} }
LogHelper.printDebug(() -> "Skipping segment: " + segment); LogHelper.printDebug(() -> "Skipping segment: " + segmentToSkip);
lastSegmentSkipped = segment; lastSegmentSkipped = segmentToSkip;
lastSegmentSkippedTime = now; lastSegmentSkippedTime = now;
segmentCurrentlyPlaying = null; segmentCurrentlyPlaying = null;
scheduledHideSegment = null; // if a scheduled has not run yet scheduledHideSegment = null;
scheduledUpcomingSegment = null; scheduledUpcomingSegment = null;
SponsorBlockViewController.hideSkipButton(); if (segmentToSkip == highlightSegment) {
highlightSegmentInitialShowEndTime = 0;
}
SponsorBlockViewController.hideSkipHighlightButton();
SponsorBlockViewController.hideSkipSegmentButton();
final boolean seekSuccessful = VideoInformation.seekTo(segment.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
LogHelper.printDebug(() -> "Could not skip segment (seek unsuccessful): " + segment); LogHelper.printDebug(() -> "Could not skip segment (seek unsuccessful): " + segmentToSkip);
return; return;
} }
@ -389,10 +443,10 @@ public class SegmentPlaybackController {
// check for any smaller embedded segments, and count those as autoskipped // check for any smaller embedded segments, and count those as autoskipped
final boolean showSkipToast = SettingsEnum.SB_SHOW_TOAST_ON_SKIP.getBoolean(); final boolean showSkipToast = SettingsEnum.SB_SHOW_TOAST_ON_SKIP.getBoolean();
for (final SponsorSegment otherSegment : segmentsOfCurrentVideo) { for (final SponsorSegment otherSegment : segmentsOfCurrentVideo) {
if (segment.end <= otherSegment.start) { if (segmentToSkip.end < otherSegment.start) {
break; // no other segments can be contained break; // no other segments can be contained
} }
if (segment.containsSegment(otherSegment)) { // includes checking the segment against itself if (segmentToSkip.containsSegment(otherSegment)) { // includes checking the segment against itself
otherSegment.didAutoSkipped = true; // skipped this segment as well otherSegment.didAutoSkipped = true; // skipped this segment as well
if (showSkipToast) { if (showSkipToast) {
showSkippedSegmentToast(otherSegment); showSkippedSegmentToast(otherSegment);
@ -401,19 +455,19 @@ public class SegmentPlaybackController {
} }
} }
if (segment.category == SegmentCategory.UNSUBMITTED) { if (segmentToSkip.category == SegmentCategory.UNSUBMITTED) {
// skipped segment was a preview of unsubmitted segment // skipped segment was a preview of unsubmitted segment
// remove the segment from the UI view // remove the segment from the UI view
SponsorBlockUtils.setNewSponsorSegmentPreviewed(); SponsorBlockUtils.setNewSponsorSegmentPreviewed();
SponsorSegment[] newSegments = new SponsorSegment[segmentsOfCurrentVideo.length - 1]; SponsorSegment[] newSegments = new SponsorSegment[segmentsOfCurrentVideo.length - 1];
int i = 0; int i = 0;
for (SponsorSegment sponsorSegment : segmentsOfCurrentVideo) { for (SponsorSegment segment : segmentsOfCurrentVideo) {
if (sponsorSegment != segment) if (segment != segmentToSkip)
newSegments[i++] = sponsorSegment; newSegments[i++] = segment;
} }
setSegmentsOfCurrentVideo(newSegments); setSegmentsOfCurrentVideo(newSegments);
} else { } else {
SponsorBlockUtils.sendViewRequestAsync(segment); SponsorBlockUtils.sendViewRequestAsync(segmentToSkip);
} }
} catch (Exception ex) { } catch (Exception ex) {
LogHelper.printException(() -> "skipSegment failure", ex); LogHelper.printException(() -> "skipSegment failure", ex);
@ -433,7 +487,7 @@ public class SegmentPlaybackController {
} }
toastSegmentSkipped = segment; toastSegmentSkipped = segment;
final long delayToToastMilliseconds = 200; // also the maximum time between skips to be considered skipping multiple segments final long delayToToastMilliseconds = 250; // also the maximum time between skips to be considered skipping multiple segments
ReVancedUtils.runOnMainThreadDelayed(() -> { ReVancedUtils.runOnMainThreadDelayed(() -> {
try { try {
if (toastSegmentSkipped == null) { // video was changed just after skipping segment if (toastSegmentSkipped == null) { // video was changed just after skipping segment
@ -452,12 +506,20 @@ public class SegmentPlaybackController {
}, delayToToastMilliseconds); }, delayToToastMilliseconds);
} }
public static void onSkipSponsorClicked() { /**
if (segmentCurrentlyPlaying != null) { * @param segment can be either a highlight or a regular manual skip segment
skipSegment(segmentCurrentlyPlaying, true); */
} else { public static void onSkipSegmentClicked(@NonNull SponsorSegment segment) {
SponsorBlockViewController.hideSkipButton(); try {
if (segment != highlightSegment && segment != segmentCurrentlyPlaying) {
LogHelper.printException(() -> "error: segment not available to skip"); // should never happen LogHelper.printException(() -> "error: segment not available to skip"); // should never happen
SponsorBlockViewController.hideSkipSegmentButton();
SponsorBlockViewController.hideSkipHighlightButton();
return;
}
skipSegment(segment, true);
} catch (Exception ex) {
LogHelper.printException(() -> "onSkipSegmentClicked failure", ex);
} }
} }
@ -512,18 +574,18 @@ public class SegmentPlaybackController {
* Injection point * Injection point
*/ */
public static void setSponsorBarThickness(final int thickness) { public static void setSponsorBarThickness(final int thickness) {
try {
setSponsorBarThickness((float) thickness); setSponsorBarThickness((float) thickness);
} catch (Exception ex) {
LogHelper.printException(() -> "setSponsorBarThickness failure", ex);
}
} }
public static void setSponsorBarThickness(final float thickness) { public static void setSponsorBarThickness(final float thickness) {
try {
if (sponsorBarThickness != thickness) { if (sponsorBarThickness != thickness) {
LogHelper.printDebug(() -> String.format("setSponsorBarThickness: %.2f", thickness)); LogHelper.printDebug(() -> String.format("setSponsorBarThickness: %.2f", thickness));
sponsorBarThickness = thickness; sponsorBarThickness = thickness;
} }
} catch (Exception ex) {
LogHelper.printException(() -> "setSponsorBarThickness failure", ex);
}
} }
/** /**
@ -564,6 +626,16 @@ public class SegmentPlaybackController {
} }
} }
private static int highlightSegmentTimeBarScreenWidth = -1; // actual pixel width to use
private static int getHighlightSegmentTimeBarScreenWidth() {
if (highlightSegmentTimeBarScreenWidth == -1) {
highlightSegmentTimeBarScreenWidth = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH,
ReVancedUtils.getContext().getResources().getDisplayMetrics());
}
return highlightSegmentTimeBarScreenWidth;
}
/** /**
* Injection point * Injection point
*/ */
@ -582,8 +654,13 @@ public class SegmentPlaybackController {
final float tmp1 = (1f / currentVideoLength) * (absoluteRight - absoluteLeft); final float tmp1 = (1f / currentVideoLength) * (absoluteRight - absoluteLeft);
for (SponsorSegment segment : segmentsOfCurrentVideo) { for (SponsorSegment segment : segmentsOfCurrentVideo) {
float left = segment.start * tmp1 + absoluteLeft; final float left = segment.start * tmp1 + absoluteLeft;
float right = segment.end * tmp1 + absoluteLeft; final float right;
if (segment.category == SegmentCategory.HIGHLIGHT) {
right = left + getHighlightSegmentTimeBarScreenWidth();
} else {
right = segment.end * tmp1 + absoluteLeft;
}
canvas.drawRect(left, top, right, bottom, segment.category.paint); canvas.drawRect(left, top, right, bottom, segment.category.paint);
} }
} catch (Exception ex) { } catch (Exception ex) {

View File

@ -29,12 +29,14 @@ public class SponsorBlockSettings {
JSONObject barTypesObject = settingsJson.getJSONObject("barTypes"); JSONObject barTypesObject = settingsJson.getJSONObject("barTypes");
JSONArray categorySelectionsArray = settingsJson.getJSONArray("categorySelections"); JSONArray categorySelectionsArray = settingsJson.getJSONArray("categorySelections");
for (SegmentCategory category : SegmentCategory.valuesWithoutUnsubmitted()) { for (SegmentCategory category : SegmentCategory.categoriesWithoutUnsubmitted()) {
// clear existing behavior, as browser plugin exports no value for ignored categories // clear existing behavior, as browser plugin exports no behavior for ignored categories
category.behaviour = CategoryBehaviour.IGNORE; category.behaviour = CategoryBehaviour.IGNORE;
if (barTypesObject.has(category.key)) {
JSONObject categoryObject = barTypesObject.getJSONObject(category.key); JSONObject categoryObject = barTypesObject.getJSONObject(category.key);
category.setColor(categoryObject.getString("color")); category.setColor(categoryObject.getString("color"));
} }
}
for (int i = 0; i < categorySelectionsArray.length(); i++) { for (int i = 0; i < categorySelectionsArray.length(); i++) {
JSONObject categorySelectionObject = categorySelectionsArray.getJSONObject(i); JSONObject categorySelectionObject = categorySelectionsArray.getJSONObject(i);
@ -47,16 +49,19 @@ public class SponsorBlockSettings {
final int desktopKey = categorySelectionObject.getInt("option"); final int desktopKey = categorySelectionObject.getInt("option");
CategoryBehaviour behaviour = CategoryBehaviour.byDesktopKey(desktopKey); CategoryBehaviour behaviour = CategoryBehaviour.byDesktopKey(desktopKey);
if (behaviour != null) { if (behaviour == null) {
category.behaviour = behaviour; ReVancedUtils.showToastLong(categoryKey + " unknown behavior key: " + desktopKey);
} else if (category == SegmentCategory.HIGHLIGHT && behaviour == CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE) {
ReVancedUtils.showToastLong("Skip-once behavior not allowed for " + category.key);
category.behaviour = CategoryBehaviour.SKIP_AUTOMATICALLY; // use closest match
} else { } else {
LogHelper.printException(() -> "Unknown segment category behavior key: " + desktopKey); category.behaviour = behaviour;
} }
} }
SegmentCategory.updateEnabledCategories(); SegmentCategory.updateEnabledCategories();
SharedPreferences.Editor editor = SharedPrefCategory.SPONSOR_BLOCK.preferences.edit(); SharedPreferences.Editor editor = SharedPrefCategory.SPONSOR_BLOCK.preferences.edit();
for (SegmentCategory category : SegmentCategory.valuesWithoutUnsubmitted()) { for (SegmentCategory category : SegmentCategory.categoriesWithoutUnsubmitted()) {
category.save(editor); category.save(editor);
} }
editor.apply(); editor.apply();
@ -117,18 +122,20 @@ public class SponsorBlockSettings {
JSONObject barTypesObject = new JSONObject(); // categories' colors JSONObject barTypesObject = new JSONObject(); // categories' colors
JSONArray categorySelectionsArray = new JSONArray(); // categories' behavior JSONArray categorySelectionsArray = new JSONArray(); // categories' behavior
SegmentCategory[] categories = SegmentCategory.valuesWithoutUnsubmitted(); SegmentCategory[] categories = SegmentCategory.categoriesWithoutUnsubmitted();
for (SegmentCategory category : categories) { for (SegmentCategory category : categories) {
JSONObject categoryObject = new JSONObject(); JSONObject categoryObject = new JSONObject();
String categoryKey = category.key; String categoryKey = category.key;
categoryObject.put("color", category.colorString()); categoryObject.put("color", category.colorString());
barTypesObject.put(categoryKey, categoryObject); barTypesObject.put(categoryKey, categoryObject);
if (category.behaviour != CategoryBehaviour.IGNORE) {
JSONObject behaviorObject = new JSONObject(); JSONObject behaviorObject = new JSONObject();
behaviorObject.put("name", categoryKey); behaviorObject.put("name", categoryKey);
behaviorObject.put("option", category.behaviour.desktopKey); behaviorObject.put("option", category.behaviour.desktopKey);
categorySelectionsArray.put(behaviorObject); categorySelectionsArray.put(behaviorObject);
} }
}
json.put("userID", SettingsEnum.SB_UUID.getString()); json.put("userID", SettingsEnum.SB_UUID.getString());
json.put("isVip", SettingsEnum.SB_IS_VIP.getBoolean()); json.put("isVip", SettingsEnum.SB_IS_VIP.getBoolean());
json.put("serverAddress", SettingsEnum.SB_API_URL.getString()); json.put("serverAddress", SettingsEnum.SB_API_URL.getString());
@ -182,7 +189,7 @@ public class SponsorBlockSettings {
initialized = true; initialized = true;
String uuid = SettingsEnum.SB_UUID.getString(); String uuid = SettingsEnum.SB_UUID.getString();
if (uuid == null || uuid.isEmpty()) { if (uuid.isEmpty()) {
uuid = (UUID.randomUUID().toString() + uuid = (UUID.randomUUID().toString() +
UUID.randomUUID().toString() + UUID.randomUUID().toString() +
UUID.randomUUID().toString()) UUID.randomUUID().toString())

View File

@ -15,10 +15,8 @@ import java.lang.ref.WeakReference;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.TimeZone; import java.util.TimeZone;
@ -74,8 +72,8 @@ public class SponsorBlockUtils {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
try { try {
SegmentCategory category = SegmentCategory.valuesWithoutUnsubmitted()[which]; SegmentCategory category = SegmentCategory.categoriesWithoutHighlights()[which];
boolean enableButton; final boolean enableButton;
if (category.behaviour == CategoryBehaviour.IGNORE) { if (category.behaviour == CategoryBehaviour.IGNORE) {
ReVancedUtils.showToastLong(str("sb_new_segment_disabled_category")); ReVancedUtils.showToastLong(str("sb_new_segment_disabled_category"));
enableButton = false; enableButton = false;
@ -100,7 +98,7 @@ public class SponsorBlockUtils {
Context context = ((AlertDialog) dialog).getContext(); Context context = ((AlertDialog) dialog).getContext();
dialog.dismiss(); dialog.dismiss();
SegmentCategory[] categories = SegmentCategory.valuesWithoutUnsubmitted(); SegmentCategory[] categories = SegmentCategory.categoriesWithoutHighlights();
CharSequence[] titles = new CharSequence[categories.length]; CharSequence[] titles = new CharSequence[categories.length];
for (int i = 0, length = categories.length; i < length; i++) { for (int i = 0, length = categories.length; i < length; i++) {
titles[i] = categories[i].getTitleWithColorDot(); titles[i] = categories[i].getTitleWithColorDot();
@ -167,7 +165,9 @@ public class SponsorBlockUtils {
} }
SponsorSegment segment = currentSegments[which]; SponsorSegment segment = currentSegments[which];
SegmentVote[] voteOptions = SegmentVote.values(); SegmentVote[] voteOptions = (segment.category == SegmentCategory.HIGHLIGHT)
? SegmentVote.voteTypesWithoutCategoryChange // highlight segments cannot change category
: SegmentVote.values();
CharSequence[] items = new CharSequence[voteOptions.length]; CharSequence[] items = new CharSequence[voteOptions.length];
for (int i = 0; i < voteOptions.length; i++) { for (int i = 0; i < voteOptions.length; i++) {
@ -195,7 +195,7 @@ public class SponsorBlockUtils {
}) })
.show(); .show();
} catch (Exception ex) { } catch (Exception ex) {
LogHelper.printException(() -> "onPreviewClicked failure", ex); LogHelper.printException(() -> "segmentVoteClickListener failure", ex);
} }
}; };
@ -218,11 +218,12 @@ public class SponsorBlockUtils {
final String uuid = SettingsEnum.SB_UUID.getString(); final String uuid = SettingsEnum.SB_UUID.getString();
final long start = newSponsorSegmentStartMillis; final long start = newSponsorSegmentStartMillis;
final long end = newSponsorSegmentEndMillis; final long end = newSponsorSegmentEndMillis;
final String videoId = SegmentPlaybackController.getCurrentVideoId(); final String videoId = VideoInformation.getCurrentVideoId();
final long videoLength = VideoInformation.getCurrentVideoLength(); final long videoLength = VideoInformation.getCurrentVideoLength();
final SegmentCategory segmentCategory = newUserCreatedSegmentCategory; final SegmentCategory segmentCategory = newUserCreatedSegmentCategory;
if (start < 0 || end < 0 || start >= end || videoLength <= 0 || segmentCategory == null || videoId == null || uuid == null) { if (start < 0 || end < 0 || start >= end || videoLength <= 0 || videoId.isEmpty()
LogHelper.printException(() -> "Unable to submit times, invalid parameters"); || segmentCategory == null || uuid.isEmpty()) {
LogHelper.printException(() -> "invalid parameters");
return; return;
} }
clearUnsubmittedSegmentTimes(); clearUnsubmittedSegmentTimes();
@ -258,9 +259,13 @@ public class SponsorBlockUtils {
public static void onPublishClicked() { public static void onPublishClicked() {
try { try {
ReVancedUtils.verifyOnMainThread(); ReVancedUtils.verifyOnMainThread();
if (!newSponsorSegmentPreviewed) { if (newSponsorSegmentStartMillis < 0 || newSponsorSegmentEndMillis < 0) {
ReVancedUtils.showToastShort(str("sb_new_segment_mark_locations_first"));
} else if (newSponsorSegmentStartMillis >= newSponsorSegmentEndMillis) {
ReVancedUtils.showToastShort(str("sb_new_segment_start_is_before_end"));
} else if (!newSponsorSegmentPreviewed && newSponsorSegmentStartMillis != 0) {
ReVancedUtils.showToastLong(str("sb_new_segment_preview_segment_first")); ReVancedUtils.showToastLong(str("sb_new_segment_preview_segment_first"));
} else if (newSponsorSegmentStartMillis >= 0 && newSponsorSegmentStartMillis < newSponsorSegmentEndMillis) { } else {
long length = (newSponsorSegmentEndMillis - newSponsorSegmentStartMillis) / 1000; long length = (newSponsorSegmentEndMillis - newSponsorSegmentStartMillis) / 1000;
long start = (newSponsorSegmentStartMillis) / 1000; long start = (newSponsorSegmentStartMillis) / 1000;
long end = (newSponsorSegmentEndMillis) / 1000; long end = (newSponsorSegmentEndMillis) / 1000;
@ -273,8 +278,6 @@ public class SponsorBlockUtils {
.setNegativeButton(android.R.string.no, null) .setNegativeButton(android.R.string.no, null)
.setPositiveButton(android.R.string.yes, segmentReadyDialogButtonListener) .setPositiveButton(android.R.string.yes, segmentReadyDialogButtonListener)
.show(); .show();
} else {
ReVancedUtils.showToastShort(str("sb_new_segment_mark_locations_first"));
} }
} catch (Exception ex) { } catch (Exception ex) {
LogHelper.printException(() -> "onPublishClicked failure", ex); LogHelper.printException(() -> "onPublishClicked failure", ex);
@ -308,24 +311,27 @@ public class SponsorBlockUtils {
voteSegmentTimeFormatter.applyPattern(formatPattern); voteSegmentTimeFormatter.applyPattern(formatPattern);
final int numberOfSegments = currentSegments.length; final int numberOfSegments = currentSegments.length;
List<CharSequence> titles = new ArrayList<>(numberOfSegments); CharSequence[] titles = new CharSequence[numberOfSegments];
for (int i = 0; i < numberOfSegments; i++) { for (int i = 0; i < numberOfSegments; i++) {
SponsorSegment segment = currentSegments[i]; SponsorSegment segment = currentSegments[i];
if (segment.category == SegmentCategory.UNSUBMITTED) { if (segment.category == SegmentCategory.UNSUBMITTED) {
continue; continue;
} }
String start = voteSegmentTimeFormatter.format(new Date(segment.start));
String end = voteSegmentTimeFormatter.format(new Date(segment.end));
StringBuilder htmlBuilder = new StringBuilder(); StringBuilder htmlBuilder = new StringBuilder();
htmlBuilder.append(String.format("<b><font color=\"#%06X\">⬤</font> %s<br> %s to %s", htmlBuilder.append(String.format("<b><font color=\"#%06X\">⬤</font> %s<br>",
segment.category.color, segment.category.title, start, end)); segment.category.color, segment.category.title));
htmlBuilder.append(voteSegmentTimeFormatter.format(new Date(segment.start)));
if (segment.category != SegmentCategory.HIGHLIGHT) {
htmlBuilder.append(" to ").append(voteSegmentTimeFormatter.format(new Date(segment.end)));
}
htmlBuilder.append("</b>");
if (i + 1 != numberOfSegments) // prevents trailing new line after last segment if (i + 1 != numberOfSegments) // prevents trailing new line after last segment
htmlBuilder.append("<br>"); htmlBuilder.append("<br>");
titles.add(Html.fromHtml(htmlBuilder.toString())); titles[i] = Html.fromHtml(htmlBuilder.toString());
} }
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setItems(titles.toArray(new CharSequence[0]), segmentVoteClickListener) .setItems(titles, segmentVoteClickListener)
.show(); .show();
} catch (Exception ex) { } catch (Exception ex) {
LogHelper.printException(() -> "onVotingClicked failure", ex); LogHelper.printException(() -> "onVotingClicked failure", ex);
@ -335,7 +341,7 @@ public class SponsorBlockUtils {
private static void onNewCategorySelect(@NonNull SponsorSegment segment, @NonNull Context context) { private static void onNewCategorySelect(@NonNull SponsorSegment segment, @NonNull Context context) {
try { try {
ReVancedUtils.verifyOnMainThread(); ReVancedUtils.verifyOnMainThread();
final SegmentCategory[] values = SegmentCategory.valuesWithoutUnsubmitted(); final SegmentCategory[] values = SegmentCategory.categoriesWithoutHighlights();
CharSequence[] titles = new CharSequence[values.length]; CharSequence[] titles = new CharSequence[values.length];
for (int i = 0; i < values.length; i++) { for (int i = 0; i < values.length; i++) {
titles[i] = values[i].getTitleWithColorDot(); titles[i] = values[i].getTitleWithColorDot();
@ -353,7 +359,11 @@ public class SponsorBlockUtils {
public static void onPreviewClicked() { public static void onPreviewClicked() {
try { try {
ReVancedUtils.verifyOnMainThread(); ReVancedUtils.verifyOnMainThread();
if (newSponsorSegmentStartMillis >= 0 && newSponsorSegmentStartMillis < newSponsorSegmentEndMillis) { if (newSponsorSegmentStartMillis < 0 || newSponsorSegmentEndMillis < 0) {
ReVancedUtils.showToastShort(str("sb_new_segment_mark_locations_first"));
} else if (newSponsorSegmentStartMillis >= newSponsorSegmentEndMillis) {
ReVancedUtils.showToastShort(str("sb_new_segment_start_is_before_end"));
} else {
VideoInformation.seekTo(newSponsorSegmentStartMillis - 2500); VideoInformation.seekTo(newSponsorSegmentStartMillis - 2500);
final SponsorSegment[] original = SegmentPlaybackController.getSegmentsOfCurrentVideo(); final SponsorSegment[] original = SegmentPlaybackController.getSegmentsOfCurrentVideo();
final SponsorSegment[] segments = original == null ? new SponsorSegment[1] : Arrays.copyOf(original, original.length + 1); final SponsorSegment[] segments = original == null ? new SponsorSegment[1] : Arrays.copyOf(original, original.length + 1);
@ -362,8 +372,6 @@ public class SponsorBlockUtils {
newSponsorSegmentStartMillis, newSponsorSegmentEndMillis, false); newSponsorSegmentStartMillis, newSponsorSegmentEndMillis, false);
SegmentPlaybackController.setSegmentsOfCurrentVideo(segments); SegmentPlaybackController.setSegmentsOfCurrentVideo(segments);
} else {
ReVancedUtils.showToastShort(str("sb_new_segment_mark_locations_first"));
} }
} catch (Exception ex) { } catch (Exception ex) {
LogHelper.printException(() -> "onPreviewClicked failure", ex); LogHelper.printException(() -> "onPreviewClicked failure", ex);

View File

@ -11,32 +11,29 @@ import app.revanced.integrations.utils.ReVancedUtils;
import app.revanced.integrations.utils.StringRef; import app.revanced.integrations.utils.StringRef;
public enum CategoryBehaviour { public enum CategoryBehaviour {
SKIP_AUTOMATICALLY("skip", 2, sf("sb_skip_automatically"), true), SKIP_AUTOMATICALLY("skip", 2, true, sf("sb_skip_automatically")),
// desktop does not have skip-once behavior. Key is unique to ReVanced // desktop does not have skip-once behavior. Key is unique to ReVanced
SKIP_AUTOMATICALLY_ONCE("skip-once", 4, sf("sb_skip_automatically_once"), true), SKIP_AUTOMATICALLY_ONCE("skip-once", 3, true, sf("sb_skip_automatically_once")),
MANUAL_SKIP("manual-skip", 1, sf("sb_skip_showbutton"), false), MANUAL_SKIP("manual-skip", 1, false, sf("sb_skip_showbutton")),
SHOW_IN_SEEKBAR("seekbar-only", 0, sf("sb_skip_seekbaronly"), false), SHOW_IN_SEEKBAR("seekbar-only", 0, false, sf("sb_skip_seekbaronly")),
// Ignore is the default behavior if no desktop behavior key is present // ignored categories are not exported to json, and ignore is the default behavior when importing
IGNORE("ignore", 3, sf("sb_skip_ignore"), false); IGNORE("ignore", -1, false, sf("sb_skip_ignore"));
@NonNull @NonNull
public final String key; public final String key;
public final int desktopKey; public final int desktopKey;
@NonNull
public final StringRef name;
/** /**
* If the segment should skip automatically * If the segment should skip automatically
*/ */
public final boolean skip; public final boolean skipAutomatically;
@NonNull
public final StringRef description;
CategoryBehaviour(String key, CategoryBehaviour(String key, int desktopKey, boolean skipAutomatically, StringRef description) {
int desktopKey,
StringRef name,
boolean skip) {
this.key = Objects.requireNonNull(key); this.key = Objects.requireNonNull(key);
this.desktopKey = desktopKey; this.desktopKey = desktopKey;
this.name = Objects.requireNonNull(name); this.skipAutomatically = skipAutomatically;
this.skip = skip; this.description = Objects.requireNonNull(description);
} }
@Nullable @Nullable
@ -60,31 +57,60 @@ public enum CategoryBehaviour {
} }
private static String[] behaviorKeys; private static String[] behaviorKeys;
private static String[] behaviorNames; private static String[] behaviorDescriptions;
private static String[] behaviorKeysWithoutSkipOnce;
private static String[] behaviorDescriptionsWithoutSkipOnce;
private static void createNameAndKeyArrays() { private static void createNameAndKeyArrays() {
ReVancedUtils.verifyOnMainThread(); ReVancedUtils.verifyOnMainThread();
CategoryBehaviour[] behaviours = values(); CategoryBehaviour[] behaviours = values();
behaviorKeys = new String[behaviours.length]; final int behaviorLength = behaviours.length;
behaviorNames = new String[behaviours.length]; behaviorKeys = new String[behaviorLength];
for (int i = 0, length = behaviours.length; i < length; i++) { behaviorDescriptions = new String[behaviorLength];
CategoryBehaviour behaviour = behaviours[i]; behaviorKeysWithoutSkipOnce = new String[behaviorLength - 1];
behaviorKeys[i] = behaviour.key; behaviorDescriptionsWithoutSkipOnce = new String[behaviorLength - 1];
behaviorNames[i] = behaviour.name.toString();
int behaviorIndex = 0, behaviorHighlightIndex = 0;
while (behaviorIndex < behaviorLength) {
CategoryBehaviour behaviour = behaviours[behaviorIndex];
String key = behaviour.key;
String description = behaviour.description.toString();
behaviorKeys[behaviorIndex] = key;
behaviorDescriptions[behaviorIndex] = description;
behaviorIndex++;
if (behaviour != SKIP_AUTOMATICALLY_ONCE) {
behaviorKeysWithoutSkipOnce[behaviorHighlightIndex] = key;
behaviorDescriptionsWithoutSkipOnce[behaviorHighlightIndex] = description;
behaviorHighlightIndex++;
}
} }
} }
public static String[] getBehaviorNames() { static String[] getBehaviorKeys() {
if (behaviorNames == null) {
createNameAndKeyArrays();
}
return behaviorNames;
}
public static String[] getBehaviorKeys() {
if (behaviorKeys == null) { if (behaviorKeys == null) {
createNameAndKeyArrays(); createNameAndKeyArrays();
} }
return behaviorKeys; return behaviorKeys;
} }
static String[] getBehaviorKeysWithoutSkipOnce() {
if (behaviorKeysWithoutSkipOnce == null) {
createNameAndKeyArrays();
}
return behaviorKeysWithoutSkipOnce;
}
static String[] getBehaviorDescriptions() {
if (behaviorDescriptions == null) {
createNameAndKeyArrays();
}
return behaviorDescriptions;
}
static String[] getBehaviorDescriptionsWithoutSkipOnce() {
if (behaviorDescriptionsWithoutSkipOnce == null) {
createNameAndKeyArrays();
}
return behaviorDescriptionsWithoutSkipOnce;
}
} }

View File

@ -22,6 +22,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.settings.SharedPrefCategory; import app.revanced.integrations.settings.SharedPrefCategory;
import app.revanced.integrations.utils.LogHelper; import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.utils.StringRef; import app.revanced.integrations.utils.StringRef;
@ -33,6 +34,11 @@ public enum SegmentCategory {
MANUAL_SKIP, 0xFFFF00), MANUAL_SKIP, 0xFFFF00),
INTERACTION("interaction", sf("sb_segments_interaction"), sf("sb_segments_interaction_sum"), sf("sb_skip_button_interaction"), sf("sb_skipped_interaction"), INTERACTION("interaction", sf("sb_segments_interaction"), sf("sb_segments_interaction_sum"), sf("sb_skip_button_interaction"), sf("sb_skipped_interaction"),
MANUAL_SKIP, 0xCC00FF), MANUAL_SKIP, 0xCC00FF),
/**
* Unique category that is treated differently than the rest.
*/
HIGHLIGHT("poi_highlight", sf("sb_segments_highlight"), sf("sb_segments_highlight_sum"), sf("sb_skip_button_highlight"), sf("sb_skipped_highlight"),
MANUAL_SKIP, 0xFF1684),
INTRO("intro", sf("sb_segments_intro"), sf("sb_segments_intro_sum"), INTRO("intro", sf("sb_segments_intro"), sf("sb_segments_intro_sum"),
sf("sb_skip_button_intro_beginning"), sf("sb_skip_button_intro_middle"), sf("sb_skip_button_intro_end"), sf("sb_skip_button_intro_beginning"), sf("sb_skip_button_intro_middle"), sf("sb_skip_button_intro_end"),
sf("sb_skipped_intro_beginning"), sf("sb_skipped_intro_middle"), sf("sb_skipped_intro_end"), sf("sb_skipped_intro_beginning"), sf("sb_skipped_intro_middle"), sf("sb_skipped_intro_end"),
@ -50,7 +56,10 @@ public enum SegmentCategory {
UNSUBMITTED("unsubmitted", StringRef.empty, StringRef.empty, sf("sb_skip_button_unsubmitted"), sf("sb_skipped_unsubmitted"), UNSUBMITTED("unsubmitted", StringRef.empty, StringRef.empty, sf("sb_skip_button_unsubmitted"), sf("sb_skipped_unsubmitted"),
SKIP_AUTOMATICALLY, 0xFFFFFF); SKIP_AUTOMATICALLY, 0xFFFFFF);
private static final SegmentCategory[] mValuesWithoutUnsubmitted = new SegmentCategory[]{ private static final StringRef skipSponsorTextCompact = sf("sb_skip_button_compact");
private static final StringRef skipSponsorTextCompactHighlight = sf("sb_skip_button_compact_highlight");
private static final SegmentCategory[] categoriesWithoutHighlights = new SegmentCategory[]{
SPONSOR, SPONSOR,
SELF_PROMO, SELF_PROMO,
INTERACTION, INTERACTION,
@ -60,7 +69,19 @@ public enum SegmentCategory {
FILLER, FILLER,
MUSIC_OFFTOPIC, MUSIC_OFFTOPIC,
}; };
private static final Map<String, SegmentCategory> mValuesMap = new HashMap<>(2 * mValuesWithoutUnsubmitted.length);
private static final SegmentCategory[] categoriesWithoutUnsubmitted = new SegmentCategory[]{
SPONSOR,
SELF_PROMO,
INTERACTION,
HIGHLIGHT,
INTRO,
OUTRO,
PREVIEW,
FILLER,
MUSIC_OFFTOPIC,
};
private static final Map<String, SegmentCategory> mValuesMap = new HashMap<>(2 * categoriesWithoutUnsubmitted.length);
private static final String COLOR_PREFERENCE_KEY_SUFFIX = "_color"; private static final String COLOR_PREFERENCE_KEY_SUFFIX = "_color";
@ -70,13 +91,18 @@ public enum SegmentCategory {
public static String sponsorBlockAPIFetchCategories = "[]"; public static String sponsorBlockAPIFetchCategories = "[]";
static { static {
for (SegmentCategory value : mValuesWithoutUnsubmitted) for (SegmentCategory value : categoriesWithoutUnsubmitted)
mValuesMap.put(value.key, value); mValuesMap.put(value.key, value);
} }
@NonNull @NonNull
public static SegmentCategory[] valuesWithoutUnsubmitted() { public static SegmentCategory[] categoriesWithoutUnsubmitted() {
return mValuesWithoutUnsubmitted; return categoriesWithoutUnsubmitted;
}
@NonNull
public static SegmentCategory[] categoriesWithoutHighlights() {
return categoriesWithoutHighlights;
} }
@Nullable @Nullable
@ -87,7 +113,7 @@ public enum SegmentCategory {
public static void loadFromPreferences() { public static void loadFromPreferences() {
SharedPreferences preferences = SharedPrefCategory.SPONSOR_BLOCK.preferences; SharedPreferences preferences = SharedPrefCategory.SPONSOR_BLOCK.preferences;
LogHelper.printDebug(() -> "loadFromPreferences"); LogHelper.printDebug(() -> "loadFromPreferences");
for (SegmentCategory category : valuesWithoutUnsubmitted()) { for (SegmentCategory category : categoriesWithoutUnsubmitted()) {
category.load(preferences); category.load(preferences);
} }
updateEnabledCategories(); updateEnabledCategories();
@ -97,7 +123,7 @@ public enum SegmentCategory {
* Must be called if behavior of any category is changed * Must be called if behavior of any category is changed
*/ */
public static void updateEnabledCategories() { public static void updateEnabledCategories() {
SegmentCategory[] categories = valuesWithoutUnsubmitted(); SegmentCategory[] categories = categoriesWithoutUnsubmitted();
List<String> enabledCategories = new ArrayList<>(categories.length); List<String> enabledCategories = new ArrayList<>(categories.length);
for (SegmentCategory category : categories) { for (SegmentCategory category : categories) {
if (category.behaviour != CategoryBehaviour.IGNORE) { if (category.behaviour != CategoryBehaviour.IGNORE) {
@ -157,6 +183,7 @@ public enum SegmentCategory {
* If value is changed, then also call {@link #save(SharedPreferences.Editor)} * If value is changed, then also call {@link #save(SharedPreferences.Editor)}
*/ */
public int color; public int color;
/** /**
* If value is changed, then also call {@link #updateEnabledCategories()} * If value is changed, then also call {@link #updateEnabledCategories()}
*/ */
@ -272,17 +299,23 @@ public enum SegmentCategory {
* @return the skip button text * @return the skip button text
*/ */
@NonNull @NonNull
public String getSkipButtonText(long segmentStartTime, long videoLength) { StringRef getSkipButtonText(long segmentStartTime, long videoLength) {
if (SettingsEnum.SB_USE_COMPACT_SKIPBUTTON.getBoolean()) {
return (this == SegmentCategory.HIGHLIGHT)
? skipSponsorTextCompactHighlight
: skipSponsorTextCompact;
}
if (videoLength == 0) { if (videoLength == 0) {
return skipButtonTextBeginning.toString(); // video is still loading. Assume it's the beginning return skipButtonTextBeginning; // video is still loading. Assume it's the beginning
} }
final float position = segmentStartTime / (float) videoLength; final float position = segmentStartTime / (float) videoLength;
if (position < 0.25f) { if (position < 0.25f) {
return skipButtonTextBeginning.toString(); return skipButtonTextBeginning;
} else if (position < 0.75f) { } else if (position < 0.75f) {
return skipButtonTextMiddle.toString(); return skipButtonTextMiddle;
} }
return skipButtonTextEnd.toString(); return skipButtonTextEnd;
} }
/** /**
@ -291,16 +324,16 @@ public enum SegmentCategory {
* @return 'skipped segment' toast message * @return 'skipped segment' toast message
*/ */
@NonNull @NonNull
public String getSkippedToastText(long segmentStartTime, long videoLength) { StringRef getSkippedToastText(long segmentStartTime, long videoLength) {
if (videoLength == 0) { if (videoLength == 0) {
return skippedToastBeginning.toString(); // video is still loading. Assume it's the beginning return skippedToastBeginning; // video is still loading. Assume it's the beginning
} }
final float position = segmentStartTime / (float) videoLength; final float position = segmentStartTime / (float) videoLength;
if (position < 0.25f) { if (position < 0.25f) {
return skippedToastBeginning.toString(); return skippedToastBeginning;
} else if (position < 0.75f) { } else if (position < 0.75f) {
return skippedToastMiddle.toString(); return skippedToastMiddle;
} }
return skippedToastEnd.toString(); return skippedToastEnd;
} }
} }

View File

@ -29,11 +29,16 @@ public class SegmentCategoryListPreference extends ListPreference {
public SegmentCategoryListPreference(Context context, SegmentCategory category) { public SegmentCategoryListPreference(Context context, SegmentCategory category) {
super(context); super(context);
final boolean isHighlightCategory = category == SegmentCategory.HIGHLIGHT;
this.category = Objects.requireNonNull(category); this.category = Objects.requireNonNull(category);
setKey(category.key); setKey(category.key);
setDefaultValue(category.behaviour.key); setDefaultValue(category.behaviour.key);
setEntries(CategoryBehaviour.getBehaviorNames()); setEntries(isHighlightCategory
setEntryValues(CategoryBehaviour.getBehaviorKeys()); ? CategoryBehaviour.getBehaviorDescriptionsWithoutSkipOnce()
: CategoryBehaviour.getBehaviorDescriptions());
setEntryValues(isHighlightCategory
? CategoryBehaviour.getBehaviorKeysWithoutSkipOnce()
: CategoryBehaviour.getBehaviorKeys());
setSummary(category.description.toString()); setSummary(category.description.toString());
updateTitle(); updateTitle();
} }

View File

@ -14,6 +14,11 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
DOWNVOTE(sf("sb_vote_downvote"), 0, true), DOWNVOTE(sf("sb_vote_downvote"), 0, true),
CATEGORY_CHANGE(sf("sb_vote_category"), -1, true); // apiVoteType is not used for category change CATEGORY_CHANGE(sf("sb_vote_category"), -1, true); // apiVoteType is not used for category change
public static final SegmentVote[] voteTypesWithoutCategoryChange = {
UPVOTE,
DOWNVOTE,
};
@NonNull @NonNull
public final StringRef title; public final StringRef title;
public final int apiVoteType; public final int apiVoteType;
@ -51,36 +56,28 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
} }
public boolean shouldAutoSkip() { public boolean shouldAutoSkip() {
return category.behaviour.skip && !(didAutoSkipped && category.behaviour == CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE); return category.behaviour.skipAutomatically && !(didAutoSkipped && category.behaviour == CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE);
} }
/** /**
* @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 timeIsNearStart(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 timeIsNearEnd(long videoTime, long nearThreshold) { public boolean endIsNear(long videoTime, long nearThreshold) {
return Math.abs(end - videoTime) <= nearThreshold; return Math.abs(end - videoTime) <= nearThreshold;
} }
/** /**
* @param nearThreshold threshold to declare the time parameter is near this segment * @return if the time parameter is within this segment
* @return if the time parameter is within or close to this segment
*/ */
public boolean timeIsInsideOrNear(long videoTime, long nearThreshold) { public boolean containsTime(long videoTime) {
return (start - nearThreshold) <= videoTime && videoTime < (end + nearThreshold); return start <= videoTime && videoTime < end;
}
/**
* @return if the time parameter is outside this segment
*/
public boolean timeIsOutside(long videoTime) {
return start < videoTime || end <= videoTime;
} }
/** /**
@ -102,7 +99,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
*/ */
@NonNull @NonNull
public String getSkipButtonText() { public String getSkipButtonText() {
return category.getSkipButtonText(start, VideoInformation.getCurrentVideoLength()); return category.getSkipButtonText(start, VideoInformation.getCurrentVideoLength()).toString();
} }
/** /**
@ -110,7 +107,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
*/ */
@NonNull @NonNull
public String getSkippedToastText() { public String getSkippedToastText() {
return category.getSkippedToastText(start, VideoInformation.getCurrentVideoLength()); return category.getSkippedToastText(start, VideoInformation.getCurrentVideoLength()).toString();
} }
@Override @Override

View File

@ -65,19 +65,15 @@ public class SBRequester {
JSONArray segment = obj.getJSONArray("segment"); JSONArray segment = obj.getJSONArray("segment");
final long start = (long) (segment.getDouble(0) * 1000); final long start = (long) (segment.getDouble(0) * 1000);
final long end = (long) (segment.getDouble(1) * 1000); final long end = (long) (segment.getDouble(1) * 1000);
if ((end - start) < minSegmentDuration)
continue;
String categoryKey = obj.getString("category");
String uuid = obj.getString("UUID"); String uuid = obj.getString("UUID");
boolean locked = obj.getInt("locked") == 1; final boolean locked = obj.getInt("locked") == 1;
String categoryKey = obj.getString("category");
SegmentCategory segmentCategory = SegmentCategory.byCategoryKey(categoryKey); SegmentCategory category = SegmentCategory.byCategoryKey(categoryKey);
if (segmentCategory == null) { if (category == null) {
LogHelper.printException(() -> "Received unknown category: " + categoryKey); // should never happen LogHelper.printException(() -> "Received unknown category: " + categoryKey); // should never happen
} else if (segmentCategory.behaviour != CategoryBehaviour.IGNORE) { } else if ((end - start) >= minSegmentDuration || category == SegmentCategory.HIGHLIGHT) {
SponsorSegment sponsorSegment = new SponsorSegment(segmentCategory, uuid, start, end, locked); segments.add(new SponsorSegment(category, uuid, start, end, locked));
segments.add(sponsorSegment);
} }
} }
LogHelper.printDebug(() -> { LogHelper.printDebug(() -> {

View File

@ -4,10 +4,8 @@ import static app.revanced.integrations.utils.ReVancedUtils.getResourceColor;
import static app.revanced.integrations.utils.ReVancedUtils.getResourceDimension; import static app.revanced.integrations.utils.ReVancedUtils.getResourceDimension;
import static app.revanced.integrations.utils.ReVancedUtils.getResourceDimensionPixelSize; import static app.revanced.integrations.utils.ReVancedUtils.getResourceDimensionPixelSize;
import static app.revanced.integrations.utils.ReVancedUtils.getResourceIdentifier; import static app.revanced.integrations.utils.ReVancedUtils.getResourceIdentifier;
import static app.revanced.integrations.utils.StringRef.str;
import android.content.Context; import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.util.AttributeSet; import android.util.AttributeSet;
@ -16,9 +14,10 @@ import android.widget.FrameLayout;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import java.util.Objects; import java.util.Objects;
import app.revanced.integrations.settings.SettingsEnum;
import app.revanced.integrations.sponsorblock.SegmentPlaybackController; import app.revanced.integrations.sponsorblock.SegmentPlaybackController;
import app.revanced.integrations.sponsorblock.objects.SponsorSegment; import app.revanced.integrations.sponsorblock.objects.SponsorSegment;
import app.revanced.integrations.utils.LogHelper; import app.revanced.integrations.utils.LogHelper;
@ -27,9 +26,9 @@ public class SkipSponsorButton extends FrameLayout {
private static final boolean highContrast = true; private static final boolean highContrast = true;
private final LinearLayout skipSponsorBtnContainer; private final LinearLayout skipSponsorBtnContainer;
private final TextView skipSponsorTextView; private final TextView skipSponsorTextView;
private final CharSequence skipSponsorTextCompact;
private final Paint background; private final Paint background;
private final Paint border; private final Paint border;
private SponsorSegment segment;
final int defaultBottomMargin; final int defaultBottomMargin;
final int ctaBottomMargin; final int ctaBottomMargin;
@ -61,11 +60,9 @@ public class SkipSponsorButton extends FrameLayout {
skipSponsorTextView = Objects.requireNonNull((TextView) findViewById(getResourceIdentifier(context, "sb_skip_sponsor_button_text", "id"))); // id:skip_ad_button_text; skipSponsorTextView = Objects.requireNonNull((TextView) findViewById(getResourceIdentifier(context, "sb_skip_sponsor_button_text", "id"))); // id:skip_ad_button_text;
defaultBottomMargin = getResourceDimensionPixelSize("skip_button_default_bottom_margin"); // dimen:skip_button_default_bottom_margin defaultBottomMargin = getResourceDimensionPixelSize("skip_button_default_bottom_margin"); // dimen:skip_button_default_bottom_margin
ctaBottomMargin = getResourceDimensionPixelSize("skip_button_cta_bottom_margin"); // dimen:skip_button_cta_bottom_margin ctaBottomMargin = getResourceDimensionPixelSize("skip_button_cta_bottom_margin"); // dimen:skip_button_cta_bottom_margin
skipSponsorTextCompact = str("sb_skip_button_compact"); // string:skip_ads "Skip ads"
skipSponsorBtnContainer.setOnClickListener(v -> { skipSponsorBtnContainer.setOnClickListener(v -> {
LogHelper.printDebug(() -> "Skip button clicked"); SegmentPlaybackController.onSkipSegmentClicked(segment);
SegmentPlaybackController.onSkipSponsorClicked();
}); });
} }
@ -90,10 +87,9 @@ public class SkipSponsorButton extends FrameLayout {
/** /**
* @return true, if this button state was changed * @return true, if this button state was changed
*/ */
public boolean updateSkipButtonText(SponsorSegment segment) { public boolean updateSkipButtonText(@NonNull SponsorSegment segment) {
CharSequence newText = SettingsEnum.SB_USE_COMPACT_SKIPBUTTON.getBoolean() this.segment = segment;
? skipSponsorTextCompact CharSequence newText = segment.getSkipButtonText();
: segment.getSkipButtonText();
if (newText.equals(skipSponsorTextView.getText())) { if (newText.equals(skipSponsorTextView.getText())) {
return false; return false;
} }

View File

@ -23,9 +23,13 @@ import app.revanced.integrations.utils.ReVancedUtils;
public class SponsorBlockViewController { public class SponsorBlockViewController {
private static WeakReference<RelativeLayout> inlineSponsorOverlayRef = new WeakReference<>(null); private static WeakReference<RelativeLayout> inlineSponsorOverlayRef = new WeakReference<>(null);
private static WeakReference<ViewGroup> youtubeOverlaysLayoutRef = new WeakReference<>(null); private static WeakReference<ViewGroup> youtubeOverlaysLayoutRef = new WeakReference<>(null);
private static WeakReference<SkipSponsorButton> skipHighlightButtonRef = new WeakReference<>(null);
private static WeakReference<SkipSponsorButton> skipSponsorButtonRef = new WeakReference<>(null); private static WeakReference<SkipSponsorButton> skipSponsorButtonRef = new WeakReference<>(null);
private static WeakReference<NewSegmentLayout> newSegmentLayoutRef = new WeakReference<>(null); private static WeakReference<NewSegmentLayout> newSegmentLayoutRef = new WeakReference<>(null);
private static boolean canShowViewElements = true; private static boolean canShowViewElements;
private static boolean newSegmentLayoutVisible;
@Nullable
private static SponsorSegment skipHighlight;
@Nullable @Nullable
private static SponsorSegment skipSegment; private static SponsorSegment skipSegment;
@ -51,161 +55,155 @@ public class SponsorBlockViewController {
try { try {
LogHelper.printDebug(() -> "initializing"); LogHelper.printDebug(() -> "initializing");
RelativeLayout layout = new RelativeLayout(ReVancedUtils.getContext()); Context context = ReVancedUtils.getContext();
RelativeLayout layout = new RelativeLayout(context);
layout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,RelativeLayout.LayoutParams.MATCH_PARENT)); layout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,RelativeLayout.LayoutParams.MATCH_PARENT));
LayoutInflater.from(ReVancedUtils.getContext()).inflate(getResourceIdentifier("inline_sponsor_overlay", "layout"), layout); LayoutInflater.from(context).inflate(getResourceIdentifier("inline_sponsor_overlay", "layout"), layout);
inlineSponsorOverlayRef = new WeakReference<>(layout); inlineSponsorOverlayRef = new WeakReference<>(layout);
ViewGroup viewGroup = (ViewGroup) obj; ViewGroup viewGroup = (ViewGroup) obj;
viewGroup.addView(layout, viewGroup.getChildCount() - 2); viewGroup.addView(layout);
viewGroup.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
@Override
public void onChildViewAdded(View parent, View child) {
// ensure SB buttons and controls are always on top, otherwise the endscreen cards can cover the skip button
RelativeLayout layout = inlineSponsorOverlayRef.get();
if (layout != null) {
layout.bringToFront();
}
}
@Override
public void onChildViewRemoved(View parent, View child) {
}
});
youtubeOverlaysLayoutRef = new WeakReference<>(viewGroup); youtubeOverlaysLayoutRef = new WeakReference<>(viewGroup);
skipHighlightButtonRef = new WeakReference<>(
Objects.requireNonNull(layout.findViewById(getResourceIdentifier("sb_skip_highlight_button", "id"))));
skipSponsorButtonRef = new WeakReference<>( skipSponsorButtonRef = new WeakReference<>(
Objects.requireNonNull(layout.findViewById(getResourceIdentifier("sb_skip_sponsor_button", "id")))); Objects.requireNonNull(layout.findViewById(getResourceIdentifier("sb_skip_sponsor_button", "id"))));
newSegmentLayoutRef = new WeakReference<>( newSegmentLayoutRef = new WeakReference<>(
Objects.requireNonNull(layout.findViewById(getResourceIdentifier("sb_new_segment_view", "id")))); Objects.requireNonNull(layout.findViewById(getResourceIdentifier("sb_new_segment_view", "id"))));
newSegmentLayoutVisible = false;
skipHighlight = null;
skipSegment = null;
} catch (Exception ex) { } catch (Exception ex) {
LogHelper.printException(() -> "initialize failure", ex); LogHelper.printException(() -> "initialize failure", ex);
} }
} }
public static void showSkipButton(@NonNull SponsorSegment info) { public static void hideAll() {
skipSegment = Objects.requireNonNull(info); hideSkipHighlightButton();
updateSkipButton(); hideSkipSegmentButton();
hideNewSegmentLayout();
} }
public static void hideSkipButton() { public static void showSkipHighlightButton(@NonNull SponsorSegment segment) {
skipSegment = null; skipHighlight = Objects.requireNonNull(segment);
updateSkipButton();
}
private static void updateSkipButton() {
SkipSponsorButton skipSponsorButton = skipSponsorButtonRef.get();
if (skipSponsorButton == null) {
return;
}
if (skipSegment == null) {
setSkipSponsorButtonVisibility(false);
} else {
final boolean layoutNeedsUpdating = skipSponsorButton.updateSkipButtonText(skipSegment);
if (layoutNeedsUpdating) {
bringLayoutToFront();
}
setSkipSponsorButtonVisibility(true);
}
}
public static void showNewSegmentLayout() {
setNewSegmentLayoutVisibility(true);
}
public static void hideNewSegmentLayout() {
NewSegmentLayout newSegmentLayout = newSegmentLayoutRef.get(); NewSegmentLayout newSegmentLayout = newSegmentLayoutRef.get();
if (newSegmentLayout == null) { // don't show highlight button if create new segment is visible
final boolean buttonVisibility = newSegmentLayout != null && newSegmentLayout.getVisibility() != View.VISIBLE;
updateSkipButton(skipHighlightButtonRef.get(), segment, buttonVisibility);
}
public static void showSkipSegmentButton(@NonNull SponsorSegment segment) {
skipSegment = Objects.requireNonNull(segment);
updateSkipButton(skipSponsorButtonRef.get(), segment, true);
}
public static void hideSkipHighlightButton() {
skipHighlight = null;
updateSkipButton(skipHighlightButtonRef.get(), null, false);
}
public static void hideSkipSegmentButton() {
skipSegment = null;
updateSkipButton(skipSponsorButtonRef.get(), null, false);
}
private static void updateSkipButton(@Nullable SkipSponsorButton button,
@Nullable SponsorSegment segment, boolean visible) {
if (button == null) {
return; return;
} }
setNewSegmentLayoutVisibility(false); if (segment != null) {
button.updateSkipButtonText(segment);
}
setViewVisibility(button, visible);
} }
public static void toggleNewSegmentLayoutVisibility() { public static void toggleNewSegmentLayoutVisibility() {
NewSegmentLayout newSegmentLayout = newSegmentLayoutRef.get(); NewSegmentLayout newSegmentLayout = newSegmentLayoutRef.get();
if (newSegmentLayout == null) { if (newSegmentLayout == null) { // should never happen
LogHelper.printException(() -> "toggleNewSegmentLayoutVisibility failure"); LogHelper.printException(() -> "toggleNewSegmentLayoutVisibility failure");
return; return;
} }
setNewSegmentLayoutVisibility(newSegmentLayout.getVisibility() == View.VISIBLE ? false : true); newSegmentLayoutVisible = (newSegmentLayout.getVisibility() != View.VISIBLE);
if (skipHighlight != null) {
setViewVisibility(skipHighlightButtonRef.get(), !newSegmentLayoutVisible);
}
setViewVisibility(newSegmentLayout, newSegmentLayoutVisible);
} }
private static void playerTypeChanged(PlayerType playerType) { public static void hideNewSegmentLayout() {
newSegmentLayoutVisible = false;
NewSegmentLayout newSegmentLayout = newSegmentLayoutRef.get();
if (newSegmentLayout == null) {
return;
}
setViewVisibility(newSegmentLayout, false);
}
private static void setViewVisibility(@Nullable View view, boolean visible) {
if (view == null) {
return;
}
visible &= canShowViewElements;
final int desiredVisibility = visible ? View.VISIBLE : View.GONE;
if (view.getVisibility() != desiredVisibility) {
view.setVisibility(desiredVisibility);
}
}
private static void playerTypeChanged(@NonNull PlayerType playerType) {
try { try {
final boolean isWatchFullScreen = playerType == PlayerType.WATCH_WHILE_FULLSCREEN; final boolean isWatchFullScreen = playerType == PlayerType.WATCH_WHILE_FULLSCREEN;
canShowViewElements = (isWatchFullScreen || playerType == PlayerType.WATCH_WHILE_MAXIMIZED); canShowViewElements = (isWatchFullScreen || playerType == PlayerType.WATCH_WHILE_MAXIMIZED);
setSkipButtonMargins(isWatchFullScreen);
setNewSegmentLayoutMargins(isWatchFullScreen);
updateSkipButton();
} catch (Exception ex) {
LogHelper.printException(() -> "Player type changed error", ex);
}
}
private static void setSkipButtonMargins(boolean fullScreen) {
SkipSponsorButton skipSponsorButton = skipSponsorButtonRef.get();
if (skipSponsorButton == null) {
LogHelper.printException(() -> "setSkipButtonMargins failure");
return;
}
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) skipSponsorButton.getLayoutParams();
if (params == null) {
LogHelper.printException(() -> "setSkipButtonMargins failure");
return;
}
params.bottomMargin = fullScreen ? skipSponsorButton.ctaBottomMargin : skipSponsorButton.defaultBottomMargin;
skipSponsorButton.setLayoutParams(params);
}
private static void setSkipSponsorButtonVisibility(boolean visible) {
SkipSponsorButton skipSponsorButton = skipSponsorButtonRef.get();
if (skipSponsorButton == null) {
LogHelper.printException(() -> "setSkipSponsorButtonVisibility failure");
return;
}
visible &= canShowViewElements;
final int desiredVisibility = visible ? View.VISIBLE : View.GONE;
if (skipSponsorButton.getVisibility() != desiredVisibility) {
skipSponsorButton.setVisibility(desiredVisibility);
if (visible) {
bringLayoutToFront();
}
}
}
private static void setNewSegmentLayoutMargins(boolean fullScreen) {
NewSegmentLayout newSegmentLayout = newSegmentLayoutRef.get(); NewSegmentLayout newSegmentLayout = newSegmentLayoutRef.get();
if (newSegmentLayout == null) { setNewSegmentLayoutMargins(newSegmentLayout, isWatchFullScreen);
LogHelper.printException(() -> "Unable to setNewSegmentLayoutMargins (button is null)"); setViewVisibility(newSegmentLayoutRef.get(), newSegmentLayoutVisible);
return;
SkipSponsorButton skipHighlightButton = skipHighlightButtonRef.get();
setSkipButtonMargins(skipHighlightButton, isWatchFullScreen);
setViewVisibility(skipHighlightButton, skipHighlight != null);
SkipSponsorButton skipSponsorButton = skipSponsorButtonRef.get();
setSkipButtonMargins(skipSponsorButton, isWatchFullScreen);
setViewVisibility(skipSponsorButton, skipSegment != null);
} catch (Exception ex) {
LogHelper.printException(() -> "Player type changed failure", ex);
}
} }
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) newSegmentLayout.getLayoutParams(); private static void setNewSegmentLayoutMargins(@Nullable NewSegmentLayout layout, boolean fullScreen) {
if (layout != null) {
setLayoutMargins(layout, fullScreen, layout.defaultBottomMargin, layout.ctaBottomMargin);
}
}
private static void setSkipButtonMargins(@Nullable SkipSponsorButton button, boolean fullScreen) {
if (button != null) {
setLayoutMargins(button, fullScreen, button.defaultBottomMargin, button.ctaBottomMargin);
}
}
private static void setLayoutMargins(@NonNull View view, boolean fullScreen,
int defaultBottomMargin, int ctaBottomMargin) {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.getLayoutParams();
if (params == null) { if (params == null) {
LogHelper.printException(() -> "Unable to setNewSegmentLayoutMargins (params are null)"); LogHelper.printException(() -> "Unable to setNewSegmentLayoutMargins (params are null)");
return; return;
} }
params.bottomMargin = fullScreen ? newSegmentLayout.ctaBottomMargin : newSegmentLayout.defaultBottomMargin; params.bottomMargin = fullScreen ? ctaBottomMargin : defaultBottomMargin;
newSegmentLayout.setLayoutParams(params); view.setLayoutParams(params);
}
private static void setNewSegmentLayoutVisibility(boolean visible) {
NewSegmentLayout newSegmentLayout = newSegmentLayoutRef.get();
if (newSegmentLayout == null) {
LogHelper.printException(() -> "setNewSegmentLayoutVisibility failure");
return;
}
visible &= canShowViewElements;
final int desiredVisibility = visible ? View.VISIBLE : View.GONE;
if (newSegmentLayout.getVisibility() != desiredVisibility) {
newSegmentLayout.setVisibility(desiredVisibility);
if (visible) {
bringLayoutToFront();
}
}
}
private static void bringLayoutToFront() {
RelativeLayout layout = inlineSponsorOverlayRef.get();
if (layout != null) {
// needed to keep skip button overtop end screen cards
layout.bringToFront();
layout.requestLayout();
layout.invalidate();
}
} }
/** /**