diff --git a/README.md b/README.md index 4609731..919c815 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # SponsorBlock YouTube Vanced Implementation In order to use this in YouTube/Vanced you must first apply the smali mods applied to vanced (the patching process used for this is currently automated using our closed source tools with no plans to open source it for the time being) (if you mod vanced directly it is not required) * First make your edits in android studio -* Change the string "replaceMeWithsetMillisecondMethod" on PlayerController.java to the method name of YouTube package +* Change the string "replaceMeWithsetMillisecondMethod", "replaceMeWithsetVolumeMethod", and "replaceMeWithgetVolumeMethod" on PlayerController.java to the method name of YouTube package * Compile debug apk * Decompile this apk using apktool https://github.com/iBotPeaches/Apktool * Take this decompiled folder and look for a folder labeled pl in one of your dex class folders diff --git a/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SkipSponsorButton.java b/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SkipSponsorButton.java index 925e28f..d324ff8 100644 --- a/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SkipSponsorButton.java +++ b/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SkipSponsorButton.java @@ -23,6 +23,7 @@ public class SkipSponsorButton extends FrameLayout { String TAG = "SkipSponsorButton"; public CharSequence skipSponsorTextViewText; public CharSequence skipSponsorText; + public CharSequence muteSegmentText; public ImageView skipSponsorButtonIcon; public TextView skipSponsorTextView; public int currentTextColor; @@ -88,7 +89,8 @@ private final void initialize(Context context) { Resources resources = context.getResources(); this.defaultBottomMargin = resources.getDimensionPixelSize(getIdentifier(context, "skip_button_default_bottom_margin", "dimen")); // dimen:skip_button_default_bottom_margin this.ctaBottomMargin = resources.getDimensionPixelSize(getIdentifier(context, "skip_button_cta_bottom_margin", "dimen")); // dimen:skip_button_cta_bottom_margin - this.skipSponsorText = resources.getText(getIdentifier(context, "skip_sponsor", "string")); // string:skip_ads "Skip ads" + this.skipSponsorText = resources.getText(getIdentifier(context, "skip_sponsor", "string")); // string:skip_ads "Skip segment" + this.muteSegmentText = resources.getText(getIdentifier(context, "mute_segment", "string")); // string:mute_segment "Mute segment" this.skipSponsorBtnContainer.setOnClickListener(new View.OnClickListener() { @Override @@ -119,6 +121,14 @@ protected final void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); } + public void setMuted(boolean muted) { + if (muted) { + this.skipSponsorTextView.setText(this.muteSegmentText); + } else { + this.skipSponsorTextView.setText(this.skipSponsorText); + } + } + public static int getColor(Context context, int arg3) { return Build.VERSION.SDK_INT < 23 ? context.getResources().getColor(arg3) : context.getColor(arg3); diff --git a/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SponsorBlockView.java b/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SponsorBlockView.java index f2cedf9..13bde58 100644 --- a/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SponsorBlockView.java +++ b/app/src/main/java/fi/vanced/libraries/youtube/sponsors/player/ui/SponsorBlockView.java @@ -38,8 +38,8 @@ public static void initialize(Object viewGroup) { } } - public static void showSkipButton() { - skipSponsorButtonVisibility(true); + public static void showSkipButton(boolean mute) { + skipSponsorButtonVisibility(true, mute); } public static void hideSkipButton() { skipSponsorButtonVisibility(false); @@ -105,6 +105,10 @@ private static void setSkipBtnMargins(boolean fullScreen) { } private static void skipSponsorButtonVisibility(boolean visible) { + skipSponsorButtonVisibility(visible, false); + } + + private static void skipSponsorButtonVisibility(boolean visible, boolean mute) { SkipSponsorButton skipSponsorButton = _skipSponsorButton.get(); if (skipSponsorButton == null) { Log.e(TAG, "Unable to skipSponsorButtonVisibility"); @@ -113,6 +117,7 @@ private static void skipSponsorButtonVisibility(boolean visible) { visible &= shouldShowOnPlayerType; + skipSponsorButton.setMuted(mute); skipSponsorButton.setVisibility(visible ? View.VISIBLE : View.GONE); bringLayoutToFront(); } diff --git a/app/src/main/java/pl/jakubweg/PlayerController.java b/app/src/main/java/pl/jakubweg/PlayerController.java index e884f57..9524462 100644 --- a/app/src/main/java/pl/jakubweg/PlayerController.java +++ b/app/src/main/java/pl/jakubweg/PlayerController.java @@ -40,10 +40,15 @@ public class PlayerController { public static SponsorSegment[] sponsorSegmentsOfCurrentVideo; private static WeakReference currentPlayerController = new WeakReference<>(null); private static Method setMillisecondMethod; + private static Method setVolumeMethod; + private static Method getVolumeMethod; private static long allowNextSkipRequestTime = 0L; private static String currentVideoId; private static long currentVideoLength = 1L; private static long lastKnownVideoTime = -1L; + private static long lastKnownVolume = -1L; + private static boolean currentlyMuted = false; + private static long muteEndTime = -1L; private static final Runnable findAndSkipSegmentRunnable = () -> { // Log.d(TAG, "findAndSkipSegmentRunnable"); findAndSkipSegment(false); @@ -111,6 +116,10 @@ public static void onCreate(final Object o) { try { setMillisecondMethod = o.getClass().getMethod("replaceMeWithsetMillisecondMethod", Long.TYPE); setMillisecondMethod.setAccessible(true); + setVolumeMethod = o.getClass().getMethod("replaceMeWithsetVolumeMethod", Long.TYPE); + setVolumeMethod.setAccessible(true); + getVolumeMethod = o.getClass().getMethod("replaceMeWithgetVolumeMethod"); + getVolumeMethod.setAccessible(true); lastKnownVideoTime = 0; VideoInformation.lastKnownVideoTime = 0; @@ -226,7 +235,12 @@ public static void setCurrentVideoTime(long millis) { for (final SponsorSegment segment : segments) { if (segment.start > millis) { - if (segment.start > startTimerAtMillis) + long scheduleTime = segment.start; + if (muteEndTime > millis && muteEndTime < segment.start) { + scheduleTime = muteEndTime; + } + + if (scheduleTime > startTimerAtMillis) break; // it's more then START_TIMER_BEFORE_SEGMENT_MILLIS far away if (!segment.category.behaviour.skip) break; @@ -238,12 +252,12 @@ public static void setCurrentVideoTime(long millis) { @Override public void run() { skipSponsorTask = null; - lastKnownVideoTime = segment.start + 1; + lastKnownVideoTime = scheduleTime + 1; VideoInformation.lastKnownVideoTime = lastKnownVideoTime; new Handler(Looper.getMainLooper()).post(findAndSkipSegmentRunnable); } }; - sponsorTimer.schedule(skipSponsorTask, segment.start - millis); + sponsorTimer.schedule(skipSponsorTask, scheduleTime - millis); } else { if (VERBOSE) Log.d(TAG, "skipSponsorTask is already scheduled..."); @@ -472,6 +486,45 @@ public static void skipToMillisecond(long millisecond) { }); } + public static void setMute(final boolean value) { + if (setVolumeMethod == null || getVolumeMethod == null) { + Log.e(TAG, "setVolumeMethod or getVolumeMethod is null"); + return; + } + + + final Object currentObj = currentPlayerController.get(); + if (currentObj == null) { + Log.e(TAG, "currentObj is null (might have been collected by GC)"); + return; + } + + + if (VERBOSE) + Log.d(TAG, String.format("Requesting set mute to %b on thread %s", value, Thread.currentThread().toString())); + + new Handler(Looper.getMainLooper()).post(() -> { + try { + if (VERBOSE) + Log.i(TAG, "Setting to mute=" + value); + long currentVolume = getVolumeMethod.invoke(currentObj); + if (value && currentVolume > 0) { + lastKnownVolume = currentVolume; + } + + if (value) { + setVolumeMethod.invoke(currentObj, 0); + } else { + setVolumeMethod.invoke(currentObj, lastKnownVolume); + } + + currentlyMuted = value; + } catch (Exception e) { + Log.e(TAG, "Cannot skip to millisecond", e); + } + }); + } + private static void findAndSkipSegment(boolean wasClicked) { if (sponsorSegmentsOfCurrentVideo == null) @@ -480,13 +533,18 @@ private static void findAndSkipSegment(boolean wasClicked) { final long millis = lastKnownVideoTime; for (SponsorSegment segment : sponsorSegmentsOfCurrentVideo) { + if (currentlyMuted) { + setMute(false); + muteEndTime = -1; + } + if (segment.start > millis) break; if (segment.end < millis) continue; - SkipSegmentView.show(); + SkipSegmentView.show(segment.actionType == SponsorBlockSettings.ActionType.MUTE); if (!(segment.category.behaviour.skip || wasClicked)) return; @@ -501,13 +559,24 @@ private static void findAndSkipSegment(boolean wasClicked) { private static void skipSegment(SponsorSegment segment, boolean wasClicked) { // if (lastSkippedSegment == segment) return; // lastSkippedSegment = segment; - if (VERBOSE) - Log.d(TAG, "Skipping segment: " + segment.toString()); + if (VERBOSE) { + if (segment.actionType == SponsorBlockSettings.ActionType.SKIP) { + Log.d(TAG, "Skipping segment: " + segment.toString()); + } else if (segment.actionType == SponsorBlockSettings.ActionType.MUTE) { + Log.d(TAG, "Muting segment: " + segment.toString()); + } + } if (SponsorBlockSettings.showToastWhenSkippedAutomatically && !wasClicked) SkipSegmentView.notifySkipped(segment); - skipToMillisecond(segment.end + 2); + if (segment.actionType == SponsorBlockSettings.ActionType.SKIP) { + skipToMillisecond(segment.end + 2); + } else if (segment.actionType == SponsorBlockSettings.ActionType.MUTE) { + setMute(true); + if (segment.end > muteEndTime) muteEndTime = segment.end; + } + SkipSegmentView.hide(); if (segment.category == SponsorBlockSettings.SegmentInfo.UNSUBMITTED) { SponsorSegment[] newSegments = new SponsorSegment[sponsorSegmentsOfCurrentVideo.length - 1]; diff --git a/app/src/main/java/pl/jakubweg/SkipSegmentView.java b/app/src/main/java/pl/jakubweg/SkipSegmentView.java index 0d9fe81..44ec2f2 100644 --- a/app/src/main/java/pl/jakubweg/SkipSegmentView.java +++ b/app/src/main/java/pl/jakubweg/SkipSegmentView.java @@ -19,8 +19,8 @@ public class SkipSegmentView { public static final String TAG = "jakubweg.SkipSegmentView"; private static SponsorSegment lastNotifiedSegment; - public static void show() { - showSkipButton(); + public static void show(boolean mute) { + showSkipButton(mute); } public static void hide() { diff --git a/app/src/main/java/pl/jakubweg/SponsorBlockSettings.java b/app/src/main/java/pl/jakubweg/SponsorBlockSettings.java index 5a617d0..f7e5228 100644 --- a/app/src/main/java/pl/jakubweg/SponsorBlockSettings.java +++ b/app/src/main/java/pl/jakubweg/SponsorBlockSettings.java @@ -30,6 +30,7 @@ public class SponsorBlockSettings { public static final String PREFERENCES_KEY_SKIPPED_SEGMENTS_TIME = "sb-skipped-segments-time"; public static final String PREFERENCES_KEY_SHOW_TIME_WITHOUT_SEGMENTS = "sb-length-without-segments"; public static final String PREFERENCES_KEY_CATEGORY_COLOR_SUFFIX = "_color"; + public static final String PREFERENCES_KEY_MUTE_ENABLED = "sb-voting-enabled"; public static final SegmentBehaviour DefaultBehaviour = SegmentBehaviour.SKIP_AUTOMATICALLY; @@ -40,9 +41,11 @@ public class SponsorBlockSettings { public static boolean showToastWhenSkippedAutomatically = true; public static boolean countSkips = true; public static boolean showTimeWithoutSegments = true; + public static boolean isMuteEnabled = true; public static int adjustNewSegmentMillis = 150; public static String uuid = ""; public static String sponsorBlockUrlCategories = "[]"; + public static String sponsorBlockUrlActionTypes = "[%22skip%22]"; public static int skippedSegments; public static long skippedTime; @@ -124,6 +127,11 @@ public static void update(Context context) { else sponsorBlockUrlCategories = "[%22" + TextUtils.join("%22,%22", enabledCategories) + "%22]"; + isMuteEnabled = preferences.getBoolean(PREFERENCES_KEY_MUTE_ENABLED, isMuteEnabled); + if (isMuteEnabled) { + sponsorBlockUrlMute = "[%22skip%22, %mute%22]"; + } + skippedSegments = preferences.getInt(PREFERENCES_KEY_SKIPPED_SEGMENTS, skippedSegments); skippedTime = preferences.getLong(PREFERENCES_KEY_SKIPPED_SEGMENTS_TIME, skippedTime); @@ -249,4 +257,25 @@ public CharSequence getTitleWithDot() { return Html.fromHtml(String.format(" %s", color, title)); } } + + public enum ActionType { + SKIP("skip"), + MUTE("mute"); + + private String key; + + private static final Map mValuesMap = new HashMap<>(values().length); + static { + for (SegmentInfo value : valuesWithoutUnsubmitted()) + mValuesMap.put(value.key, value); + } + + ActionType(String key) { + this.key = key; + } + + public static SegmentInfo byKey(String key) { + return mValuesMap.get(key); + } + } } diff --git a/app/src/main/java/pl/jakubweg/objects/SponsorSegment.java b/app/src/main/java/pl/jakubweg/objects/SponsorSegment.java index 1819bba..0d6b4ef 100644 --- a/app/src/main/java/pl/jakubweg/objects/SponsorSegment.java +++ b/app/src/main/java/pl/jakubweg/objects/SponsorSegment.java @@ -6,12 +6,15 @@ public class SponsorSegment implements Comparable { public final long start; public final long end; public final SponsorBlockSettings.SegmentInfo category; + public final SponsorBlockSettings.ActionType actionType; public final String UUID; - public SponsorSegment(long start, long end, SponsorBlockSettings.SegmentInfo category, String UUID) { + public SponsorSegment(long start, long end, SponsorBlockSettings.SegmentInfo category, + SponsorBlockSettings.ActionType actionType, String UUID) { this.start = start; this.end = end; this.category = category; + this.actionType = actionType; this.UUID = UUID; } @@ -21,6 +24,7 @@ public String toString() { "start=" + start + ", end=" + end + ", category='" + category + '\'' + + ", actionType='" + actionType + '\'' + '}'; } diff --git a/app/src/main/java/pl/jakubweg/requests/Requester.java b/app/src/main/java/pl/jakubweg/requests/Requester.java index 2b7884c..b38d351 100644 --- a/app/src/main/java/pl/jakubweg/requests/Requester.java +++ b/app/src/main/java/pl/jakubweg/requests/Requester.java @@ -39,7 +39,7 @@ private Requester() {} public static synchronized SponsorSegment[] getSegments(String videoId) { List segments = new ArrayList<>(); try { - HttpURLConnection connection = getConnectionFromRoute(Route.GET_SEGMENTS, videoId, SponsorBlockSettings.sponsorBlockUrlCategories); + HttpURLConnection connection = getConnectionFromRoute(Route.GET_SEGMENTS, videoId, SponsorBlockSettings.sponsorBlockUrlCategories, SponsorBlockSettings.sponsorBlockUrlActionTypes); int responseCode = connection.getResponseCode(); videoHasSegments = false; timeWithoutSegments = ""; @@ -53,11 +53,13 @@ public static synchronized SponsorSegment[] getSegments(String videoId) { long start = (long) (segment.getDouble(0) * 1000); long end = (long) (segment.getDouble(1) * 1000); String category = obj.getString("category"); + String actionType = obj.getString("actionType"); String uuid = obj.getString("UUID"); SponsorBlockSettings.SegmentInfo segmentCategory = SponsorBlockSettings.SegmentInfo.byCategoryKey(category); + SponsorBlockSettings.ActionType segmentActionType = SponsorBlockSettings.ActionType.byKey(category); if (segmentCategory != null && segmentCategory.behaviour.showOnTimeBar) { - SponsorSegment sponsorSegment = new SponsorSegment(start, end, segmentCategory, uuid); + SponsorSegment sponsorSegment = new SponsorSegment(start, end, segmentCategory, segmentActionType, uuid); segments.add(sponsorSegment); } } diff --git a/app/src/main/java/pl/jakubweg/requests/Route.java b/app/src/main/java/pl/jakubweg/requests/Route.java index ce86b95..80e5bd7 100644 --- a/app/src/main/java/pl/jakubweg/requests/Route.java +++ b/app/src/main/java/pl/jakubweg/requests/Route.java @@ -6,7 +6,7 @@ import pl.jakubweg.SponsorBlockUtils; public class Route { - public static final Route GET_SEGMENTS = new Route(GET, "skipSegments?videoID={video_id}&categories={categories}"); + public static final Route GET_SEGMENTS = new Route(GET, "skipSegments?videoID={video_id}&categories={categories}&actionTypes={actionTypes}"); public static final Route VIEWED_SEGMENT = new Route(POST, "viewedVideoSponsorTime?UUID={segment_id}"); public static final Route GET_USER_STATS = new Route(GET, "userInfo?userID={user_id}&values=[\"userName\", \"minutesSaved\", \"segmentCount\", \"viewCount\"]"); public static final Route CHANGE_USERNAME = new Route(POST, "setUsername?userID={user_id}&username={username}"); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 129c4ab..ac090e3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -237,6 +237,7 @@ Show me Skip segment + Mute segment Comments removal Comments removal is turned off (new comments / phones only)