Add initial implementation of mute segments

This commit is contained in:
Ajay Ramachandran 2021-11-27 01:59:17 -05:00
parent 92d58b7963
commit 08c33e81ab
10 changed files with 137 additions and 17 deletions

View File

@ -1,7 +1,7 @@
# SponsorBlock YouTube Vanced Implementation # 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) 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 * 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 * Compile debug apk
* Decompile this apk using apktool https://github.com/iBotPeaches/Apktool * 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 * Take this decompiled folder and look for a folder labeled pl in one of your dex class folders

View File

@ -23,6 +23,7 @@ public class SkipSponsorButton extends FrameLayout {
String TAG = "SkipSponsorButton"; String TAG = "SkipSponsorButton";
public CharSequence skipSponsorTextViewText; public CharSequence skipSponsorTextViewText;
public CharSequence skipSponsorText; public CharSequence skipSponsorText;
public CharSequence muteSegmentText;
public ImageView skipSponsorButtonIcon; public ImageView skipSponsorButtonIcon;
public TextView skipSponsorTextView; public TextView skipSponsorTextView;
public int currentTextColor; public int currentTextColor;
@ -88,7 +89,8 @@ public class SkipSponsorButton extends FrameLayout {
Resources resources = context.getResources(); Resources resources = context.getResources();
this.defaultBottomMargin = resources.getDimensionPixelSize(getIdentifier(context, "skip_button_default_bottom_margin", "dimen")); // dimen:skip_button_default_bottom_margin 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.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() { this.skipSponsorBtnContainer.setOnClickListener(new View.OnClickListener() {
@Override @Override
@ -119,6 +121,14 @@ public class SkipSponsorButton extends FrameLayout {
super.dispatchDraw(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) { public static int getColor(Context context, int arg3) {
return Build.VERSION.SDK_INT < 23 ? context.getResources().getColor(arg3) : context.getColor(arg3); return Build.VERSION.SDK_INT < 23 ? context.getResources().getColor(arg3) : context.getColor(arg3);

View File

@ -38,8 +38,8 @@ public class SponsorBlockView {
} }
} }
public static void showSkipButton() { public static void showSkipButton(boolean mute) {
skipSponsorButtonVisibility(true); skipSponsorButtonVisibility(true, mute);
} }
public static void hideSkipButton() { public static void hideSkipButton() {
skipSponsorButtonVisibility(false); skipSponsorButtonVisibility(false);
@ -105,6 +105,10 @@ public class SponsorBlockView {
} }
private static void skipSponsorButtonVisibility(boolean visible) { private static void skipSponsorButtonVisibility(boolean visible) {
skipSponsorButtonVisibility(visible, false);
}
private static void skipSponsorButtonVisibility(boolean visible, boolean mute) {
SkipSponsorButton skipSponsorButton = _skipSponsorButton.get(); SkipSponsorButton skipSponsorButton = _skipSponsorButton.get();
if (skipSponsorButton == null) { if (skipSponsorButton == null) {
Log.e(TAG, "Unable to skipSponsorButtonVisibility"); Log.e(TAG, "Unable to skipSponsorButtonVisibility");
@ -113,6 +117,7 @@ public class SponsorBlockView {
visible &= shouldShowOnPlayerType; visible &= shouldShowOnPlayerType;
skipSponsorButton.setMuted(mute);
skipSponsorButton.setVisibility(visible ? View.VISIBLE : View.GONE); skipSponsorButton.setVisibility(visible ? View.VISIBLE : View.GONE);
bringLayoutToFront(); bringLayoutToFront();
} }

View File

@ -40,10 +40,15 @@ public class PlayerController {
public static SponsorSegment[] sponsorSegmentsOfCurrentVideo; public static SponsorSegment[] sponsorSegmentsOfCurrentVideo;
private static WeakReference<Object> currentPlayerController = new WeakReference<>(null); private static WeakReference<Object> currentPlayerController = new WeakReference<>(null);
private static Method setMillisecondMethod; private static Method setMillisecondMethod;
private static Method setVolumeMethod;
private static Method getVolumeMethod;
private static long allowNextSkipRequestTime = 0L; private static long allowNextSkipRequestTime = 0L;
private static String currentVideoId; private static String currentVideoId;
private static long currentVideoLength = 1L; private static long currentVideoLength = 1L;
private static long lastKnownVideoTime = -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 = () -> { private static final Runnable findAndSkipSegmentRunnable = () -> {
// Log.d(TAG, "findAndSkipSegmentRunnable"); // Log.d(TAG, "findAndSkipSegmentRunnable");
findAndSkipSegment(false); findAndSkipSegment(false);
@ -111,6 +116,10 @@ public class PlayerController {
try { try {
setMillisecondMethod = o.getClass().getMethod("replaceMeWithsetMillisecondMethod", Long.TYPE); setMillisecondMethod = o.getClass().getMethod("replaceMeWithsetMillisecondMethod", Long.TYPE);
setMillisecondMethod.setAccessible(true); setMillisecondMethod.setAccessible(true);
setVolumeMethod = o.getClass().getMethod("replaceMeWithsetVolumeMethod", Long.TYPE);
setVolumeMethod.setAccessible(true);
getVolumeMethod = o.getClass().getMethod("replaceMeWithgetVolumeMethod");
getVolumeMethod.setAccessible(true);
lastKnownVideoTime = 0; lastKnownVideoTime = 0;
VideoInformation.lastKnownVideoTime = 0; VideoInformation.lastKnownVideoTime = 0;
@ -226,7 +235,12 @@ public class PlayerController {
for (final SponsorSegment segment : segments) { for (final SponsorSegment segment : segments) {
if (segment.start > millis) { 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 break; // it's more then START_TIMER_BEFORE_SEGMENT_MILLIS far away
if (!segment.category.behaviour.skip) if (!segment.category.behaviour.skip)
break; break;
@ -238,12 +252,12 @@ public class PlayerController {
@Override @Override
public void run() { public void run() {
skipSponsorTask = null; skipSponsorTask = null;
lastKnownVideoTime = segment.start + 1; lastKnownVideoTime = scheduleTime + 1;
VideoInformation.lastKnownVideoTime = lastKnownVideoTime; VideoInformation.lastKnownVideoTime = lastKnownVideoTime;
new Handler(Looper.getMainLooper()).post(findAndSkipSegmentRunnable); new Handler(Looper.getMainLooper()).post(findAndSkipSegmentRunnable);
} }
}; };
sponsorTimer.schedule(skipSponsorTask, segment.start - millis); sponsorTimer.schedule(skipSponsorTask, scheduleTime - millis);
} else { } else {
if (VERBOSE) if (VERBOSE)
Log.d(TAG, "skipSponsorTask is already scheduled..."); Log.d(TAG, "skipSponsorTask is already scheduled...");
@ -472,6 +486,45 @@ public class PlayerController {
}); });
} }
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) { private static void findAndSkipSegment(boolean wasClicked) {
if (sponsorSegmentsOfCurrentVideo == null) if (sponsorSegmentsOfCurrentVideo == null)
@ -480,13 +533,18 @@ public class PlayerController {
final long millis = lastKnownVideoTime; final long millis = lastKnownVideoTime;
for (SponsorSegment segment : sponsorSegmentsOfCurrentVideo) { for (SponsorSegment segment : sponsorSegmentsOfCurrentVideo) {
if (currentlyMuted) {
setMute(false);
muteEndTime = -1;
}
if (segment.start > millis) if (segment.start > millis)
break; break;
if (segment.end < millis) if (segment.end < millis)
continue; continue;
SkipSegmentView.show(); SkipSegmentView.show(segment.actionType == SponsorBlockSettings.ActionType.MUTE);
if (!(segment.category.behaviour.skip || wasClicked)) if (!(segment.category.behaviour.skip || wasClicked))
return; return;
@ -501,13 +559,24 @@ public class PlayerController {
private static void skipSegment(SponsorSegment segment, boolean wasClicked) { private static void skipSegment(SponsorSegment segment, boolean wasClicked) {
// if (lastSkippedSegment == segment) return; // if (lastSkippedSegment == segment) return;
// lastSkippedSegment = segment; // lastSkippedSegment = segment;
if (VERBOSE) if (VERBOSE) {
Log.d(TAG, "Skipping segment: " + segment.toString()); 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) if (SponsorBlockSettings.showToastWhenSkippedAutomatically && !wasClicked)
SkipSegmentView.notifySkipped(segment); 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(); SkipSegmentView.hide();
if (segment.category == SponsorBlockSettings.SegmentInfo.UNSUBMITTED) { if (segment.category == SponsorBlockSettings.SegmentInfo.UNSUBMITTED) {
SponsorSegment[] newSegments = new SponsorSegment[sponsorSegmentsOfCurrentVideo.length - 1]; SponsorSegment[] newSegments = new SponsorSegment[sponsorSegmentsOfCurrentVideo.length - 1];

View File

@ -19,8 +19,8 @@ public class SkipSegmentView {
public static final String TAG = "jakubweg.SkipSegmentView"; public static final String TAG = "jakubweg.SkipSegmentView";
private static SponsorSegment lastNotifiedSegment; private static SponsorSegment lastNotifiedSegment;
public static void show() { public static void show(boolean mute) {
showSkipButton(); showSkipButton(mute);
} }
public static void hide() { public static void hide() {

View File

@ -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_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_SHOW_TIME_WITHOUT_SEGMENTS = "sb-length-without-segments";
public static final String PREFERENCES_KEY_CATEGORY_COLOR_SUFFIX = "_color"; 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; public static final SegmentBehaviour DefaultBehaviour = SegmentBehaviour.SKIP_AUTOMATICALLY;
@ -40,9 +41,11 @@ public class SponsorBlockSettings {
public static boolean showToastWhenSkippedAutomatically = true; public static boolean showToastWhenSkippedAutomatically = true;
public static boolean countSkips = true; public static boolean countSkips = true;
public static boolean showTimeWithoutSegments = true; public static boolean showTimeWithoutSegments = true;
public static boolean isMuteEnabled = true;
public static int adjustNewSegmentMillis = 150; public static int adjustNewSegmentMillis = 150;
public static String uuid = "<invalid>"; public static String uuid = "<invalid>";
public static String sponsorBlockUrlCategories = "[]"; public static String sponsorBlockUrlCategories = "[]";
public static String sponsorBlockUrlActionTypes = "[%22skip%22]";
public static int skippedSegments; public static int skippedSegments;
public static long skippedTime; public static long skippedTime;
@ -124,6 +127,11 @@ public class SponsorBlockSettings {
else else
sponsorBlockUrlCategories = "[%22" + TextUtils.join("%22,%22", enabledCategories) + "%22]"; 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); skippedSegments = preferences.getInt(PREFERENCES_KEY_SKIPPED_SEGMENTS, skippedSegments);
skippedTime = preferences.getLong(PREFERENCES_KEY_SKIPPED_SEGMENTS_TIME, skippedTime); skippedTime = preferences.getLong(PREFERENCES_KEY_SKIPPED_SEGMENTS_TIME, skippedTime);
@ -249,4 +257,25 @@ public class SponsorBlockSettings {
return Html.fromHtml(String.format("<font color=\"#%06X\">⬤</font> %s", color, title)); return Html.fromHtml(String.format("<font color=\"#%06X\">⬤</font> %s", color, title));
} }
} }
public enum ActionType {
SKIP("skip"),
MUTE("mute");
private String key;
private static final Map<String, SegmentInfo> 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);
}
}
} }

View File

@ -6,12 +6,15 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
public final long start; public final long start;
public final long end; public final long end;
public final SponsorBlockSettings.SegmentInfo category; public final SponsorBlockSettings.SegmentInfo category;
public final SponsorBlockSettings.ActionType actionType;
public final String UUID; 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.start = start;
this.end = end; this.end = end;
this.category = category; this.category = category;
this.actionType = actionType;
this.UUID = UUID; this.UUID = UUID;
} }
@ -21,6 +24,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
"start=" + start + "start=" + start +
", end=" + end + ", end=" + end +
", category='" + category + '\'' + ", category='" + category + '\'' +
", actionType='" + actionType + '\'' +
'}'; '}';
} }

View File

@ -39,7 +39,7 @@ public class Requester {
public static synchronized SponsorSegment[] getSegments(String videoId) { public static synchronized SponsorSegment[] getSegments(String videoId) {
List<SponsorSegment> segments = new ArrayList<>(); List<SponsorSegment> segments = new ArrayList<>();
try { 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(); int responseCode = connection.getResponseCode();
videoHasSegments = false; videoHasSegments = false;
timeWithoutSegments = ""; timeWithoutSegments = "";
@ -53,11 +53,13 @@ public class Requester {
long start = (long) (segment.getDouble(0) * 1000); long start = (long) (segment.getDouble(0) * 1000);
long end = (long) (segment.getDouble(1) * 1000); long end = (long) (segment.getDouble(1) * 1000);
String category = obj.getString("category"); String category = obj.getString("category");
String actionType = obj.getString("actionType");
String uuid = obj.getString("UUID"); String uuid = obj.getString("UUID");
SponsorBlockSettings.SegmentInfo segmentCategory = SponsorBlockSettings.SegmentInfo.byCategoryKey(category); SponsorBlockSettings.SegmentInfo segmentCategory = SponsorBlockSettings.SegmentInfo.byCategoryKey(category);
SponsorBlockSettings.ActionType segmentActionType = SponsorBlockSettings.ActionType.byKey(category);
if (segmentCategory != null && segmentCategory.behaviour.showOnTimeBar) { 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); segments.add(sponsorSegment);
} }
} }

View File

@ -6,7 +6,7 @@ import static pl.jakubweg.requests.Route.Method.POST;
import pl.jakubweg.SponsorBlockUtils; import pl.jakubweg.SponsorBlockUtils;
public class Route { 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 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 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}"); public static final Route CHANGE_USERNAME = new Route(POST, "setUsername?userID={user_id}&username={username}");

View File

@ -237,6 +237,7 @@
<string name="sb_guidelines_popup_open">Show me</string> <string name="sb_guidelines_popup_open">Show me</string>
<string name="skip_sponsor">Skip segment</string> <string name="skip_sponsor">Skip segment</string>
<string name="mute_segment">Mute segment</string>
<string name="litho_comments">Comments removal</string> <string name="litho_comments">Comments removal</string>
<string name="litho_comments_off">Comments removal is turned off (new comments / phones only)</string> <string name="litho_comments_off">Comments removal is turned off (new comments / phones only)</string>