revanced-integrations/app/src/main/java/app/revanced/integrations/sponsorblock/PlayerController.java

437 lines
17 KiB
Java
Raw Normal View History

2022-06-24 00:16:32 +02:00
package app.revanced.integrations.sponsorblock;
2020-08-24 17:47:57 +02:00
2022-06-24 00:16:32 +02:00
import static app.revanced.integrations.sponsorblock.SponsorBlockSettings.skippedSegments;
import static app.revanced.integrations.sponsorblock.SponsorBlockSettings.skippedTime;
import static app.revanced.integrations.sponsorblock.SponsorBlockUtils.timeWithoutSegments;
import static app.revanced.integrations.sponsorblock.SponsorBlockUtils.videoHasSegments;
2022-01-17 14:54:11 +01:00
2020-08-24 17:47:57 +02:00
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
2020-08-24 17:47:57 +02:00
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
2022-06-24 00:16:32 +02:00
2020-08-24 17:47:57 +02:00
import android.view.View;
import android.view.ViewGroup;
import java.lang.ref.WeakReference;
2021-04-16 19:46:10 +02:00
import java.lang.reflect.Field;
2020-08-24 17:47:57 +02:00
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
2022-06-24 00:16:32 +02:00
import app.revanced.integrations.utils.LogHelper;
import app.revanced.integrations.sponsorblock.player.VideoInformation;
import app.revanced.integrations.adremover.whitelist.Whitelist;
import app.revanced.integrations.sponsorblock.objects.SponsorSegment;
import app.revanced.integrations.sponsorblock.requests.SBRequester;
import app.revanced.integrations.utils.ReVancedUtils;
2022-06-24 00:16:32 +02:00
import app.revanced.integrations.utils.SharedPrefHelper;
2021-07-19 21:51:26 +02:00
2020-08-24 17:47:57 +02:00
@SuppressLint({"LongLogTag"})
public class PlayerController {
private static final Timer sponsorTimer = new Timer("sponsor-skip-timer");
public static WeakReference<Activity> playerActivity = new WeakReference<>(null);
public static SponsorSegment[] sponsorSegmentsOfCurrentVideo;
private static WeakReference<Object> currentPlayerController = new WeakReference<>(null);
private static Method setMillisecondMethod;
private static long allowNextSkipRequestTime = 0L;
private static String currentVideoId;
private static long currentVideoLength = 1L;
private static long lastKnownVideoTime = -1L;
2021-07-19 16:19:47 +02:00
private static final Runnable findAndSkipSegmentRunnable = () -> {
findAndSkipSegment(false);
2020-08-24 17:47:57 +02:00
};
private static float sponsorBarLeft = 1f;
private static float sponsorBarRight = 1f;
private static float sponsorBarThickness = 2f;
private static TimerTask skipSponsorTask = null;
public static String getCurrentVideoId() {
return currentVideoId;
}
public static void setCurrentVideoId(final String videoId) {
if (videoId == null) {
2021-07-23 17:44:42 +02:00
currentVideoId = null;
sponsorSegmentsOfCurrentVideo = null;
2020-08-24 17:47:57 +02:00
return;
}
Context context = ReVancedUtils.getContext();
SponsorBlockSettings.update(context);
2020-08-24 17:47:57 +02:00
if (!SponsorBlockSettings.isSponsorBlockEnabled) {
currentVideoId = null;
return;
}
if (videoId.equals(currentVideoId))
return;
currentVideoId = videoId;
sponsorSegmentsOfCurrentVideo = null;
LogHelper.debug(PlayerController.class, "setCurrentVideoId: videoId=" + videoId);
2020-08-24 17:47:57 +02:00
sponsorTimer.schedule(new TimerTask() {
@Override
public void run() {
2020-08-25 13:46:20 +02:00
executeDownloadSegments(currentVideoId);
2020-08-24 17:47:57 +02:00
}
}, 0);
}
/**
* Called when creating some kind of youtube internal player controlled, every time when new video starts to play
*/
public static void onCreate(final Object o) {
// "Plugin.printStackTrace();
if (o == null) {
LogHelper.printException(PlayerController.class, "onCreate called with null object");
2020-08-24 17:47:57 +02:00
return;
}
LogHelper.debug(PlayerController.class, String.format("onCreate called with object %s on thread %s", o.toString(), Thread.currentThread().toString()));
2020-08-24 17:47:57 +02:00
try {
2021-04-28 22:40:39 +02:00
setMillisecondMethod = o.getClass().getMethod("replaceMeWithsetMillisecondMethod", Long.TYPE);
2020-08-24 17:47:57 +02:00
setMillisecondMethod.setAccessible(true);
lastKnownVideoTime = 0;
2020-12-16 12:34:11 +01:00
VideoInformation.lastKnownVideoTime = 0;
2020-08-24 17:47:57 +02:00
currentVideoLength = 1;
currentPlayerController = new WeakReference<>(o);
SkipSegmentView.hide();
NewSegmentHelperLayout.hide();
} catch (Exception e) {
LogHelper.printException(PlayerController.class, "Exception while initializing skip method", e);
2020-08-24 17:47:57 +02:00
}
}
2020-08-25 13:46:20 +02:00
public static void executeDownloadSegments(String videoId) {
2022-01-17 23:38:25 +01:00
videoHasSegments = false;
timeWithoutSegments = "";
2022-01-23 22:36:01 +01:00
if (Whitelist.isChannelSBWhitelisted())
2022-01-17 23:38:25 +01:00
return;
2022-01-17 14:54:11 +01:00
SponsorSegment[] segments = SBRequester.getSegments(videoId);
2020-08-24 17:47:57 +02:00
Arrays.sort(segments);
for (SponsorSegment segment : segments) {
LogHelper.debug(PlayerController.class, "Detected segment: " + segment.toString());
}
2020-08-24 17:47:57 +02:00
sponsorSegmentsOfCurrentVideo = segments;
// new Handler(Looper.getMainLooper()).post(findAndSkipSegmentRunnable);
}
/**
* Called when it's time to update the UI with new second, about once per second, only when playing, also in background
*/
public static void setCurrentVideoTime(long millis) {
LogHelper.debug(PlayerController.class, "setCurrentVideoTime: current video time: " + millis);
2020-12-16 12:34:11 +01:00
VideoInformation.lastKnownVideoTime = millis;
2020-08-24 17:47:57 +02:00
if (!SponsorBlockSettings.isSponsorBlockEnabled) return;
lastKnownVideoTime = millis;
if (millis <= 0) return;
//findAndSkipSegment(false);
if (millis == currentVideoLength) {
SponsorBlockUtils.hideShieldButton();
SponsorBlockUtils.hideVoteButton();
return;
}
2020-08-24 17:47:57 +02:00
SponsorSegment[] segments = sponsorSegmentsOfCurrentVideo;
if (segments == null || segments.length == 0) return;
final long START_TIMER_BEFORE_SEGMENT_MILLIS = 1200;
final long startTimerAtMillis = millis + START_TIMER_BEFORE_SEGMENT_MILLIS;
for (final SponsorSegment segment : segments) {
if (segment.start > millis) {
if (segment.start > startTimerAtMillis)
break; // it's more then START_TIMER_BEFORE_SEGMENT_MILLIS far away
if (!segment.category.behaviour.skip)
break;
if (skipSponsorTask == null) {
LogHelper.debug(PlayerController.class, "Scheduling skipSponsorTask");
2020-08-24 17:47:57 +02:00
skipSponsorTask = new TimerTask() {
@Override
public void run() {
skipSponsorTask = null;
lastKnownVideoTime = segment.start + 1;
2020-12-16 12:34:11 +01:00
VideoInformation.lastKnownVideoTime = lastKnownVideoTime;
2020-08-24 17:47:57 +02:00
new Handler(Looper.getMainLooper()).post(findAndSkipSegmentRunnable);
}
};
sponsorTimer.schedule(skipSponsorTask, segment.start - millis);
} else {
LogHelper.debug(PlayerController.class, "skipSponsorTask is already scheduled...");
2020-08-24 17:47:57 +02:00
}
break;
}
if (segment.end < millis)
continue;
// we are in the segment!
if (segment.category.behaviour.skip) {
sendViewRequestAsync(millis, segment);
skipSegment(segment, false);
break;
} else {
SkipSegmentView.show();
return;
}
}
SkipSegmentView.hide();
}
private static void sendViewRequestAsync(final long millis, final SponsorSegment segment) {
2021-07-19 21:51:26 +02:00
if (segment.category != SponsorBlockSettings.SegmentInfo.UNSUBMITTED) {
Context context = ReVancedUtils.getContext();
2021-07-19 21:51:26 +02:00
if (context != null) {
2021-07-19 21:53:28 +02:00
long newSkippedTime = skippedTime + (segment.end - segment.start);
2022-06-24 00:16:32 +02:00
SharedPrefHelper.saveInt(context, SharedPrefHelper.SharedPrefNames.SPONSOR_BLOCK, SponsorBlockSettings.PREFERENCES_KEY_SKIPPED_SEGMENTS, skippedSegments + 1);
SharedPrefHelper.saveLong(context, SharedPrefHelper.SharedPrefNames.SPONSOR_BLOCK, SponsorBlockSettings.PREFERENCES_KEY_SKIPPED_SEGMENTS_TIME, newSkippedTime);
2021-07-19 21:51:26 +02:00
}
}
2021-07-19 16:19:47 +02:00
new Thread(() -> {
if (SponsorBlockSettings.countSkips &&
2021-07-19 17:32:06 +02:00
segment.category != SponsorBlockSettings.SegmentInfo.UNSUBMITTED &&
2021-07-19 16:19:47 +02:00
millis - segment.start < 2000) {
// Only skips from the start should count as a view
2022-01-17 14:54:11 +01:00
SBRequester.sendViewCountRequest(segment);
2020-08-24 17:47:57 +02:00
}
}).start();
}
/**
* Called very high frequency (once every about 100ms), also in background. It sometimes triggers when a video is paused (couple times in the row with the same value)
*/
public static void setCurrentVideoTimeHighPrecision(final long millis) {
if ((millis < lastKnownVideoTime && lastKnownVideoTime >= currentVideoLength) || millis == 0) {
SponsorBlockUtils.showShieldButton(); // skipping from end to the video will show the buttons again
SponsorBlockUtils.showVoteButton();
}
2020-12-16 12:34:11 +01:00
if (lastKnownVideoTime > 0) {
2020-08-24 17:47:57 +02:00
lastKnownVideoTime = millis;
2020-12-16 12:34:11 +01:00
VideoInformation.lastKnownVideoTime = lastKnownVideoTime;
2022-06-24 00:16:32 +02:00
} else
2020-08-24 17:47:57 +02:00
setCurrentVideoTime(millis);
}
public static long getCurrentVideoLength() {
return currentVideoLength;
}
2020-08-24 17:47:57 +02:00
public static long getLastKnownVideoTime() {
return lastKnownVideoTime;
}
/**
* Called before onDraw method on time bar object, sets video length in millis
*/
public static void setVideoLength(final long length) {
LogHelper.debug(PlayerController.class, "setVideoLength: length=" + length);
2020-08-24 17:47:57 +02:00
currentVideoLength = length;
}
public static void setSponsorBarAbsoluteLeft(final Rect rect) {
setSponsorBarAbsoluteLeft(rect.left);
}
public static void setSponsorBarAbsoluteLeft(final float left) {
LogHelper.debug(PlayerController.class, String.format("setSponsorBarLeft: left=%.2f", left));
2020-08-24 17:47:57 +02:00
sponsorBarLeft = left;
}
public static void setSponsorBarRect(final Object self) {
try {
2021-07-24 01:48:33 +02:00
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
2021-04-16 19:46:10 +02:00
field.setAccessible(true);
Rect rect = (Rect) field.get(self);
if (rect != null) {
setSponsorBarAbsoluteLeft(rect.left);
setSponsorBarAbsoluteRight(rect.right);
}
} catch (IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
}
2020-08-24 17:47:57 +02:00
public static void setSponsorBarAbsoluteRight(final Rect rect) {
setSponsorBarAbsoluteRight(rect.right);
}
public static void setSponsorBarAbsoluteRight(final float right) {
LogHelper.debug(PlayerController.class, String.format("setSponsorBarRight: right=%.2f", right));
2020-08-24 17:47:57 +02:00
sponsorBarRight = right;
}
public static void setSponsorBarThickness(final int thickness) {
setSponsorBarThickness((float) thickness);
}
public static void setSponsorBarThickness(final float thickness) {
// if (VERBOSE_DRAW_OPTIONS)
// LogH(PlayerController.class, String.format("setSponsorBarThickness: thickness=%.2f", thickness));
2020-08-24 17:47:57 +02:00
sponsorBarThickness = thickness;
}
public static void onSkipSponsorClicked() {
LogHelper.debug(PlayerController.class, "Skip segment clicked");
2020-08-24 17:47:57 +02:00
findAndSkipSegment(true);
}
public static void addSkipSponsorView15(final View view) {
playerActivity = new WeakReference<>((Activity) view.getContext());
LogHelper.debug(PlayerController.class, "addSkipSponsorView15: view=" + view.toString());
2020-10-15 17:53:00 +02:00
2021-07-19 16:19:47 +02:00
new Handler(Looper.getMainLooper()).postDelayed(() -> {
final ViewGroup viewGroup = (ViewGroup) ((ViewGroup) view).getChildAt(2);
Activity context = ((Activity) viewGroup.getContext());
NewSegmentHelperLayout.context = context;
2020-08-24 17:47:57 +02:00
}, 500);
}
public static void addSkipSponsorView14(final View view) {
playerActivity = new WeakReference<>((Activity) view.getContext());
LogHelper.debug(PlayerController.class, "addSkipSponsorView14: view=" + view.toString());
2021-07-19 16:19:47 +02:00
new Handler(Looper.getMainLooper()).postDelayed(() -> {
final ViewGroup viewGroup = (ViewGroup) view.getParent();
Activity activity = (Activity) viewGroup.getContext();
NewSegmentHelperLayout.context = activity;
2020-08-24 17:47:57 +02:00
}, 500);
}
/**
* Called when it's time to draw time bar
*/
public static void drawSponsorTimeBars(final Canvas canvas, final float posY) {
if (sponsorBarThickness < 0.1) return;
if (sponsorSegmentsOfCurrentVideo == null) return;
final float thicknessDiv2 = sponsorBarThickness / 2;
final float top = posY - thicknessDiv2;
final float bottom = posY + thicknessDiv2;
final float absoluteLeft = sponsorBarLeft;
final float absoluteRight = sponsorBarRight;
final float tmp1 = 1f / (float) currentVideoLength * (absoluteRight - absoluteLeft);
for (SponsorSegment segment : sponsorSegmentsOfCurrentVideo) {
float left = segment.start * tmp1 + absoluteLeft;
float right = segment.end * tmp1 + absoluteLeft;
canvas.drawRect(left, top, right, bottom, segment.category.paint);
}
}
// private final static Pattern videoIdRegex = Pattern.compile(".*\\.be\\/([A-Za-z0-9_\\-]{0,50}).*");
public static String substringVideoIdFromLink(String link) {
return link.substring(link.lastIndexOf('/') + 1);
}
public static void skipRelativeMilliseconds(int millisRelative) {
skipToMillisecond(lastKnownVideoTime + millisRelative);
}
public static void skipToMillisecond(long millisecond) {
// in 15.x if sponsor clip hits the end, then it crashes the app, because of too many function invocations
// I put this block so that skip can be made only once per some time
long now = System.currentTimeMillis();
if (now < allowNextSkipRequestTime) {
LogHelper.debug(PlayerController.class, "skipToMillisecond: to fast, slow down, because you'll fail");
2020-08-24 17:47:57 +02:00
return;
}
allowNextSkipRequestTime = now + 100;
if (setMillisecondMethod == null) {
LogHelper.printException(PlayerController.class, "setMillisecondMethod is null");
2020-08-24 17:47:57 +02:00
return;
}
final Object currentObj = currentPlayerController.get();
if (currentObj == null) {
LogHelper.printException(PlayerController.class, "currentObj is null (might have been collected by GC)");
2020-08-24 17:47:57 +02:00
return;
}
LogHelper.debug(PlayerController.class, String.format("Requesting skip to millis=%d on thread %s", millisecond, Thread.currentThread().toString()));
2020-08-24 17:47:57 +02:00
final long finalMillisecond = millisecond;
2021-07-19 16:19:47 +02:00
new Handler(Looper.getMainLooper()).post(() -> {
try {
LogHelper.debug(PlayerController.class, "Skipping to millis=" + finalMillisecond);
2021-07-19 16:19:47 +02:00
lastKnownVideoTime = finalMillisecond;
VideoInformation.lastKnownVideoTime = lastKnownVideoTime;
setMillisecondMethod.invoke(currentObj, finalMillisecond);
} catch (Exception e) {
LogHelper.printException(PlayerController.class, "Cannot skip to millisecond", e);
2020-08-24 17:47:57 +02:00
}
});
}
private static void findAndSkipSegment(boolean wasClicked) {
if (sponsorSegmentsOfCurrentVideo == null)
return;
final long millis = lastKnownVideoTime;
for (SponsorSegment segment : sponsorSegmentsOfCurrentVideo) {
if (segment.start > millis)
break;
if (segment.end < millis)
continue;
SkipSegmentView.show();
if (!(segment.category.behaviour.skip || wasClicked))
return;
sendViewRequestAsync(millis, segment);
skipSegment(segment, wasClicked);
break;
}
SkipSegmentView.hide();
}
private static void skipSegment(SponsorSegment segment, boolean wasClicked) {
// if (lastSkippedSegment == segment) return;
// lastSkippedSegment = segment;
LogHelper.debug(PlayerController.class, "Skipping segment: " + segment.toString());
2020-08-24 17:47:57 +02:00
if (SponsorBlockSettings.showToastWhenSkippedAutomatically && !wasClicked)
SkipSegmentView.notifySkipped(segment);
skipToMillisecond(segment.end + 2);
SkipSegmentView.hide();
2021-07-19 17:32:06 +02:00
if (segment.category == SponsorBlockSettings.SegmentInfo.UNSUBMITTED) {
2020-08-24 17:47:57 +02:00
SponsorSegment[] newSegments = new SponsorSegment[sponsorSegmentsOfCurrentVideo.length - 1];
int i = 0;
for (SponsorSegment sponsorSegment : sponsorSegmentsOfCurrentVideo) {
if (sponsorSegment != segment)
newSegments[i++] = sponsorSegment;
}
sponsorSegmentsOfCurrentVideo = newSegments;
}
}
}